mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Implement gc UI
Signed-off-by: Meina Zhou <meinaz@vmware.com>
This commit is contained in:
parent
002e5a2b70
commit
aefb97dec8
@ -13,7 +13,7 @@ import {
|
|||||||
clone
|
clone
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { ErrorHandler } from '../error-handler/index';
|
import { ErrorHandler } from '../error-handler/index';
|
||||||
import { SystemSettingsComponent, VulnerabilityConfigComponent } from './index';
|
import { SystemSettingsComponent, VulnerabilityConfigComponent} from './index';
|
||||||
import { Configuration } from './config';
|
import { Configuration } from './config';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Component, Input, Output, EventEmitter, ViewChild, Inject } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, ViewChild, Inject } from '@angular/core';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { Configuration } from '../config';
|
import { Configuration } from '../config';
|
||||||
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';
|
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"types": [],
|
"typeRoots": ["node_modules/@types"],
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es2015"
|
"es2015"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"description": "Harbor UI with Clarity",
|
"description": "Harbor UI with Clarity",
|
||||||
"angular-cli": {},
|
"angular-cli": {},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ng serve --aot --ssl true --ssl-key ssl/server.key --ssl-cert ssl/server.crt --host 0.0.0.0 --proxy-config proxy.config.json",
|
"start": "ng serve --ssl true --ssl-key ssl/server.key --ssl-cert ssl/server.crt --host 0.0.0.0 --proxy-config proxy.config.json",
|
||||||
"lint": "tslint \"src/**/*.ts\"",
|
"lint": "tslint \"src/**/*.ts\"",
|
||||||
"lint:lib": "tslint \"lib/**/*.ts\" -e \"lib/dist/**/*\" ",
|
"lint:lib": "tslint \"lib/**/*.ts\" -e \"lib/dist/**/*\" ",
|
||||||
"test": "ng test harbor-portal",
|
"test": "ng test harbor-portal",
|
||||||
|
@ -20,12 +20,21 @@ import { HarborRoutingModule } from './harbor-routing.module';
|
|||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { AccountModule } from './account/account.module';
|
import { AccountModule } from './account/account.module';
|
||||||
import { ConfigurationModule } from './config/config.module';
|
import { ConfigurationModule } from './config/config.module';
|
||||||
|
import { registerLocaleData } from '@angular/common';
|
||||||
|
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
import { AppConfigService } from './app-config.service';
|
import { AppConfigService } from './app-config.service';
|
||||||
import {SkinableConfig} from "./skinable-config.service";
|
import { SkinableConfig } from "./skinable-config.service";
|
||||||
import { ProjectConfigComponent } from './project/project-config/project-config.component';
|
import { ProjectConfigComponent } from './project/project-config/project-config.component';
|
||||||
|
|
||||||
|
import zh from '@angular/common/locales/zh-Hans';
|
||||||
|
import es from '@angular/common/locales/es';
|
||||||
|
import localeFr from '@angular/common/locales/fr';
|
||||||
|
registerLocaleData(zh, 'zh-cn');
|
||||||
|
registerLocaleData(es, 'es-es');
|
||||||
|
registerLocaleData(localeFr, 'fr-fr');
|
||||||
|
|
||||||
|
|
||||||
export function initConfig(configService: AppConfigService, skinableService: SkinableConfig) {
|
export function initConfig(configService: AppConfigService, skinableService: SkinableConfig) {
|
||||||
return () => {
|
return () => {
|
||||||
skinableService.getCustomFile();
|
skinableService.getCustomFile();
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
<li role="presentation" class="nav-item" *ngIf="withClair">
|
<li role="presentation" class="nav-item" *ngIf="withClair">
|
||||||
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>{{'CONFIG.VULNERABILITY' | translate}}</button>
|
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>{{'CONFIG.VULNERABILITY' | translate}}</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li role="presentation" class="nav-item" *ngIf="hasAdminRole">
|
||||||
|
<button id="config-gc" class="btn btn-link nav-link" aria-controls="gc" [class.active]='isCurrentTabLink("config-gc")' type="button" (click)='tabLinkClick("config-gc")'>{{'CONFIG.GC' | translate}}</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
||||||
<config-auth [allConfig]="allConfig"></config-auth>
|
<config-auth [allConfig]="allConfig"></config-auth>
|
||||||
@ -34,6 +37,9 @@
|
|||||||
<section id="vulnerability" *ngIf="withClair" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
|
<section id="vulnerability" *ngIf="withClair" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
|
||||||
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
|
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="gc" *ngIf="hasAdminRole" role="tabpanel" aria-labelledby="config-gc" [hidden]='!isCurrentTabContent("gc")'>
|
||||||
|
<gc-config></gc-config>
|
||||||
|
</section>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-primary" (click)="save()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
<button type="button" class="btn btn-primary" (click)="save()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||||
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { Configuration, StringValueItem, SystemSettingsComponent, VulnerabilityConfigComponent } from '@harbor/ui';
|
import { Configuration, StringValueItem, SystemSettingsComponent, VulnerabilityConfigComponent} from '@harbor/ui';
|
||||||
|
|
||||||
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';
|
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';
|
||||||
import { SessionService } from '../shared/session.service';
|
import { SessionService } from '../shared/session.service';
|
||||||
@ -24,6 +24,7 @@ import { MessageHandlerService } from '../shared/message-handler/message-handler
|
|||||||
import { AppConfigService } from '../app-config.service';
|
import { AppConfigService } from '../app-config.service';
|
||||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||||
|
import { GcComponent} from './gc/gc.component';
|
||||||
import { ConfigurationService } from './config.service';
|
import { ConfigurationService } from './config.service';
|
||||||
|
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ const TabLinkContentMap = {
|
|||||||
'config-email': 'email',
|
'config-email': 'email',
|
||||||
'config-system': 'system_settings',
|
'config-system': 'system_settings',
|
||||||
'config-vulnerability': 'vulnerability',
|
'config-vulnerability': 'vulnerability',
|
||||||
|
'config-gc': 'gc',
|
||||||
'config-label': 'system_label',
|
'config-label': 'system_label',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,6 +55,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
|
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
|
||||||
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
|
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
|
||||||
|
@ViewChild(GcComponent) gcConfig: GcComponent;
|
||||||
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
|
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
|
||||||
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
|
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
|
||||||
|
|
||||||
@ -201,7 +204,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get hideBtn(): boolean {
|
public get hideBtn(): boolean {
|
||||||
return this.currentTabId === 'config-label';
|
return this.currentTabId === 'config-label' || this.currentTabId === 'config-gc';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hideMailTestingSpinner(): boolean {
|
public get hideMailTestingSpinner(): boolean {
|
||||||
|
@ -19,6 +19,12 @@ import { ConfigurationComponent } from './config.component';
|
|||||||
import { ConfigurationService } from './config.service';
|
import { ConfigurationService } from './config.service';
|
||||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||||
|
import { GcComponent } from './gc/gc.component';
|
||||||
|
import { GcRepoService } from './gc/gc.service';
|
||||||
|
import { GcApiRepository } from './gc/gc.api.repository';
|
||||||
|
import { GcViewModelFactory } from './gc/gc.viewmodel.factory';
|
||||||
|
import { GcUtility } from './gc/gc.utility';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -29,8 +35,9 @@ import { ConfigurationEmailComponent } from './email/config-email.component';
|
|||||||
ConfigurationComponent,
|
ConfigurationComponent,
|
||||||
ConfigurationAuthComponent,
|
ConfigurationAuthComponent,
|
||||||
ConfigurationEmailComponent,
|
ConfigurationEmailComponent,
|
||||||
|
GcComponent
|
||||||
],
|
],
|
||||||
exports: [ConfigurationComponent],
|
exports: [ConfigurationComponent],
|
||||||
providers: [ConfigurationService]
|
providers: [ConfigurationService, GcRepoService, GcApiRepository, GcViewModelFactory, GcUtility ]
|
||||||
})
|
})
|
||||||
export class ConfigurationModule { }
|
export class ConfigurationModule { }
|
||||||
|
49
src/portal/src/app/config/gc/gc.api.repository.ts
Normal file
49
src/portal/src/app/config/gc/gc.api.repository.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http } from '@angular/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GcApiRepository {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: Http,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public postSchedule(param): Observable<any> {
|
||||||
|
return this.http.post("/api/system/gc/schedule", param)
|
||||||
|
.pipe(catchError(err => Observable.throw(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public putSchedule(param): Observable<any> {
|
||||||
|
return this.http.put("/api/system/gc/schedule", param)
|
||||||
|
.pipe(catchError(err => Observable.throw(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSchedule(): Observable<any> {
|
||||||
|
return this.http.get("/api/system/gc/schedule")
|
||||||
|
.pipe(catchError(err => Observable.throw(err)))
|
||||||
|
.pipe(map(response => response.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLog(id): Observable<any> {
|
||||||
|
return this.http.get("/api/system/gc/" + id + "/log")
|
||||||
|
.pipe(catchError(err => Observable.throw(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStatus(id): Observable<any> {
|
||||||
|
return this.http.get("/api/system/gc/" + id)
|
||||||
|
.pipe(catchError(err => Observable.throw(err)))
|
||||||
|
.pipe(map(response => response.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getJobs(): Observable<any> {
|
||||||
|
return this.http.get("/api/system/gc")
|
||||||
|
.pipe(catchError(err => Observable.throw(err)))
|
||||||
|
.pipe(map(response => response.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
src/portal/src/app/config/gc/gc.component.html
Normal file
50
src/portal/src/app/config/gc/gc.component.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<div class="normal-wrapper flex-layout" *ngIf="!isEditMode">
|
||||||
|
<span>{{'GC.CURRENT_SCHEDULE' | translate}}</span>
|
||||||
|
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
|
||||||
|
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.WEEKLY">{{'GC.ON' | translate}} {{originWeekDay.text | translate}}</span>
|
||||||
|
<span [hidden]="originScheduleType===SCHEDULE_TYPE.NONE">{{'GC.AT' | translate}} {{originOffTime.text}}</span>
|
||||||
|
<button class="btn btn-outline" (click)="editSchedule()">{{'BUTTON.EDIT' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="setting-wrapper flex-layout" *ngIf="isEditMode">
|
||||||
|
<label for="gcPolicy">{{'CONFIG.GC' | translate}}</label>
|
||||||
|
<div class="select">
|
||||||
|
<select id="gcPolicy" name="gcPolicy" [(ngModel)]="scheduleType">
|
||||||
|
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.DAILY">{{'SCHEDULE.DAILY' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.WEEKLY">{{'SCHEDULE.WEEKLY' | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<section [hidden]="scheduleType!== SCHEDULE_TYPE.WEEKLY" class="select day-selector-wrapper">
|
||||||
|
<span>{{'GC.ON' | translate}}</span>
|
||||||
|
<select id="daySelector" name="daySelector" [(ngModel)]="weekDay">
|
||||||
|
<option *ngFor="let d of weekDays" [ngValue]="d">{{d.text | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</section>
|
||||||
|
<section [hidden]="scheduleType===SCHEDULE_TYPE.NONE">
|
||||||
|
<span>{{'GC.AT' | translate}}</span>
|
||||||
|
<input type="time" name="dailyTimePicker" required [(ngModel)]="dailyTime" />
|
||||||
|
</section>
|
||||||
|
<button class="btn btn-primary btn-sm" (click)="scheduleGc()">{{'BUTTON.SAVE' | translate}}</button>
|
||||||
|
<button class="btn btn-primary btn-sm" (click)="isEditMode = false" >{{'BUTTON.CANCEL' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success btn-sm gc-start-btn" (click)="gcNow()">{{'GC.GC_NOW' | translate}}</button>
|
||||||
|
<div class="job-header">{{'GC.JOB_LIST' | translate}}</div>
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>{{'GC.JOB_ID' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'START_TIME' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'END_TIME' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>{{'DETAILS' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]='job'>
|
||||||
|
<clr-dg-cell>{{job.id }}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{'SCHEDULE.'+ job.type.toUpperCase() | translate }}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{job.status.toUpperCase() | translate}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{job.createTime | date:'medium'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{job.updateTime | date:'medium'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<a target="_blank" href="/api/system/gc/{{job.id}}/log">{{'GC.LOG_DETAIL' | translate}}</a>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer>{{'GC.LATEST_JOBS' | translate :{param: jobs.length} }}</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
20
src/portal/src/app/config/gc/gc.component.scss
Normal file
20
src/portal/src/app/config/gc/gc.component.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
.flex-layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin:20px 0;
|
||||||
|
> * {
|
||||||
|
margin-right:35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-start-btn {
|
||||||
|
width:150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-header {
|
||||||
|
margin:20px 0 -10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-selector-wrapper {
|
||||||
|
display: flex;
|
||||||
|
}
|
25
src/portal/src/app/config/gc/gc.component.spec.ts
Normal file
25
src/portal/src/app/config/gc/gc.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GcComponent } from './gc.component';
|
||||||
|
|
||||||
|
describe('GcComponent', () => {
|
||||||
|
let component: GcComponent;
|
||||||
|
let fixture: ComponentFixture<GcComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ GcComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GcComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
134
src/portal/src/app/config/gc/gc.component.ts
Normal file
134
src/portal/src/app/config/gc/gc.component.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter, ViewChild, OnInit } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { GcJobViewModel, WeekDay } from "./gcLog";
|
||||||
|
import { GcViewModelFactory } from "./gc.viewmodel.factory";
|
||||||
|
import { GcRepoService } from "./gc.service";
|
||||||
|
import { WEEKDAYS, SCHEDULE_TYPE } from './gc.const';
|
||||||
|
import { GcUtility } from './gc.utility';
|
||||||
|
import { ErrorHandler } from '@harbor/ui';
|
||||||
|
@Component({
|
||||||
|
selector: 'gc-config',
|
||||||
|
templateUrl: './gc.component.html',
|
||||||
|
styleUrls: ['./gc.component.scss']
|
||||||
|
})
|
||||||
|
export class GcComponent implements OnInit {
|
||||||
|
jobs: Array<GcJobViewModel> = [];
|
||||||
|
schedule: any;
|
||||||
|
originScheduleType: string;
|
||||||
|
originOffTime: any = { value: null, text: "" };
|
||||||
|
originWeekDay: any = { value: null, text: "" };
|
||||||
|
scheduleType: string;
|
||||||
|
isEditMode: boolean = false;
|
||||||
|
weekDays = WEEKDAYS;
|
||||||
|
SCHEDULE_TYPE = SCHEDULE_TYPE;
|
||||||
|
weekDay: WeekDay = WEEKDAYS[0];
|
||||||
|
dailyTime: string;
|
||||||
|
|
||||||
|
constructor(private gcRepoService: GcRepoService,
|
||||||
|
private gcViewModelFactory: GcViewModelFactory,
|
||||||
|
private gcUtility: GcUtility,
|
||||||
|
private errorHandler: ErrorHandler,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
translate.setDefaultLang('en-us');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCurrentSchedule();
|
||||||
|
this.getJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSchedule() {
|
||||||
|
this.gcRepoService.getSchedule().subscribe(schedule => {
|
||||||
|
this.initSchedule(schedule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private initSchedule(schedule: any) {
|
||||||
|
if (schedule && schedule.length > 0) {
|
||||||
|
this.schedule = schedule[0];
|
||||||
|
const cron = this.schedule.schedule;
|
||||||
|
this.originScheduleType = cron.type;
|
||||||
|
this.originWeekDay = this.weekDays[cron.weekday];
|
||||||
|
let dailyTime = this.gcUtility.getDailyTime(cron.offtime);
|
||||||
|
this.originOffTime = { value: cron.offtime, text: dailyTime };
|
||||||
|
} else {
|
||||||
|
this.originScheduleType = SCHEDULE_TYPE.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editSchedule() {
|
||||||
|
this.isEditMode = true;
|
||||||
|
this.scheduleType = this.originScheduleType;
|
||||||
|
if (this.originWeekDay.value) {
|
||||||
|
this.weekDay = this.originWeekDay;
|
||||||
|
} else {
|
||||||
|
this.weekDay = this.weekDays[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.originOffTime.value) {
|
||||||
|
this.dailyTime = this.originOffTime.text;
|
||||||
|
} else {
|
||||||
|
this.dailyTime = "00:00";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getJobs() {
|
||||||
|
this.gcRepoService.getJobs().subscribe(jobs => {
|
||||||
|
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gcNow(): void {
|
||||||
|
this.gcRepoService.manualGc().subscribe(response => {
|
||||||
|
this.translate.get('GC.MSG_SUCCESS').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
this.getJobs();
|
||||||
|
}, error => {
|
||||||
|
this.errorHandler.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleGc(): void {
|
||||||
|
let offTime = this.gcUtility.getOffTime(this.dailyTime);
|
||||||
|
if (this.schedule) {
|
||||||
|
this.gcRepoService.putScheduleGc(this.scheduleType, offTime, this.weekDay.value).subscribe(response => {
|
||||||
|
this.translate.get('GC.MSG_SCHEDULE_RESET').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
this.originScheduleType = this.scheduleType;
|
||||||
|
this.originWeekDay = this.weekDay;
|
||||||
|
this.originOffTime = { value: offTime, text: this.dailyTime };
|
||||||
|
this.isEditMode = false;
|
||||||
|
this.getJobs();
|
||||||
|
}, error => {
|
||||||
|
this.translate.get('GC.MSG_ERROR').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.gcRepoService.postScheduleGc(this.scheduleType, offTime, this.weekDay.value).subscribe(response => {
|
||||||
|
this.translate.get('GC.MSG_SCHEDULE_SET').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
this.schedule = {
|
||||||
|
schedule: {
|
||||||
|
type: this.scheduleType,
|
||||||
|
offTime: offTime,
|
||||||
|
weekDay: this.weekDay.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.originScheduleType = this.scheduleType;
|
||||||
|
this.originWeekDay = this.weekDay;
|
||||||
|
this.originOffTime = { value: offTime, text: this.dailyTime };
|
||||||
|
this.isEditMode = false;
|
||||||
|
this.getJobs();
|
||||||
|
}, error => {
|
||||||
|
this.translate.get('GC.MSG_ERROR').subscribe((res: string) => {
|
||||||
|
this.errorHandler.info(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/portal/src/app/config/gc/gc.const.ts
Normal file
20
src/portal/src/app/config/gc/gc.const.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const WEEKDAYS = [
|
||||||
|
{value: 0, text: "WEEKLY.MONDAY"},
|
||||||
|
{value: 1, text: "WEEKLY.TUESDAY"},
|
||||||
|
{value: 2, text: "WEEKLY.WEDNESDAY"},
|
||||||
|
{value: 3, text: "WEEKLY.THURSDAY"},
|
||||||
|
{value: 4, text: "WEEKLY.FRIDAY"},
|
||||||
|
{value: 5, text: "WEEKLY.SATURDAY"},
|
||||||
|
{value: 6, text: "WEEKLY.SUNDAY"}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SCHEDULE_TYPE = {
|
||||||
|
NONE: "None",
|
||||||
|
DAILY: "Daily",
|
||||||
|
WEEKLY: "Weekly"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
63
src/portal/src/app/config/gc/gc.service.ts
Normal file
63
src/portal/src/app/config/gc/gc.service.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http } from '@angular/http';
|
||||||
|
import { Observable, Subscription, Subject, of } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { GcApiRepository } from './gc.api.repository';
|
||||||
|
import { ErrorHandler } from '@harbor/ui';
|
||||||
|
import { GcJobData } from './gcLog';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GcRepoService {
|
||||||
|
|
||||||
|
constructor(private http: Http,
|
||||||
|
private gcApiRepository: GcApiRepository,
|
||||||
|
private errorHandler: ErrorHandler) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public manualGc(): Observable <any> {
|
||||||
|
let param = {
|
||||||
|
"schedule": {
|
||||||
|
"type": "Manual"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return this.gcApiRepository.postSchedule(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getJobs(): Observable <GcJobData []> {
|
||||||
|
return this.gcApiRepository.getJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLog(id): Observable <any> {
|
||||||
|
return this.gcApiRepository.getLog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSchedule(): Observable <any> {
|
||||||
|
return this.gcApiRepository.getSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
public postScheduleGc(type, offTime, weekday ?): Observable <any> {
|
||||||
|
let param = {
|
||||||
|
"schedule": {
|
||||||
|
"type": type,
|
||||||
|
"offtime": offTime,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (weekday) {
|
||||||
|
param.schedule["weekday"] = weekday;
|
||||||
|
}
|
||||||
|
return this.gcApiRepository.postSchedule(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public putScheduleGc(type, offTime, weekday ?): Observable <any> {
|
||||||
|
let param = {
|
||||||
|
"schedule": {
|
||||||
|
"type": type,
|
||||||
|
"offtime": offTime,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (weekday) {
|
||||||
|
param.schedule["weekday"] = weekday;
|
||||||
|
}
|
||||||
|
return this.gcApiRepository.putSchedule(param);
|
||||||
|
}
|
||||||
|
}
|
62
src/portal/src/app/config/gc/gc.utility.ts
Normal file
62
src/portal/src/app/config/gc/gc.utility.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
const ONE_HOUR_SECONDS: number = 3600;
|
||||||
|
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GcUtility {
|
||||||
|
private _localTime: Date = new Date();
|
||||||
|
public getOffTime(v: string) {
|
||||||
|
let values: string[] = v.split(":");
|
||||||
|
if (!values || values.length !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let hours: number = +values[0];
|
||||||
|
let minutes: number = +values[1];
|
||||||
|
// Convert to UTC time
|
||||||
|
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||||
|
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
|
||||||
|
utcTimes += timezoneOffset * 60;
|
||||||
|
if (utcTimes < 0) {
|
||||||
|
utcTimes += ONE_DAY_SECONDS;
|
||||||
|
}
|
||||||
|
if (utcTimes >= ONE_DAY_SECONDS) {
|
||||||
|
utcTimes -= ONE_DAY_SECONDS;
|
||||||
|
}
|
||||||
|
return utcTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDailyTime(v: number ) {
|
||||||
|
let timeOffset: number = 0; // seconds
|
||||||
|
timeOffset = + v;
|
||||||
|
// Convert to current time
|
||||||
|
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||||
|
// Local time
|
||||||
|
timeOffset = timeOffset - timezoneOffset * 60;
|
||||||
|
if (timeOffset < 0) {
|
||||||
|
timeOffset = timeOffset + ONE_DAY_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeOffset >= ONE_DAY_SECONDS) {
|
||||||
|
timeOffset -= ONE_DAY_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To time string
|
||||||
|
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
|
||||||
|
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
|
||||||
|
|
||||||
|
let timeStr: string = "" + hours;
|
||||||
|
if (hours < 10) {
|
||||||
|
timeStr = "0" + timeStr;
|
||||||
|
}
|
||||||
|
if (minutes < 10) {
|
||||||
|
timeStr += ":0";
|
||||||
|
} else {
|
||||||
|
timeStr += ":";
|
||||||
|
}
|
||||||
|
timeStr += minutes;
|
||||||
|
|
||||||
|
return timeStr;
|
||||||
|
}
|
||||||
|
}
|
23
src/portal/src/app/config/gc/gc.viewmodel.factory.ts
Normal file
23
src/portal/src/app/config/gc/gc.viewmodel.factory.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { GcJobData, GcJobViewModel } from './gcLog';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GcViewModelFactory {
|
||||||
|
public createJobViewModel(jobs: GcJobData[]): GcJobViewModel[] {
|
||||||
|
let gcViewModels: GcJobViewModel[] = [];
|
||||||
|
for (let job of jobs) {
|
||||||
|
|
||||||
|
let createTime = new Date(job.creation_time);
|
||||||
|
let updateTime = new Date(job.update_time);
|
||||||
|
gcViewModels.push({
|
||||||
|
id: job.id,
|
||||||
|
type: job.schedule.type,
|
||||||
|
status: job.job_status,
|
||||||
|
createTime: createTime,
|
||||||
|
updateTime: updateTime,
|
||||||
|
details: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return gcViewModels;
|
||||||
|
}
|
||||||
|
}
|
37
src/portal/src/app/config/gc/gcLog.ts
Normal file
37
src/portal/src/app/config/gc/gcLog.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export class GcJobData {
|
||||||
|
id: number;
|
||||||
|
job_name: string;
|
||||||
|
job_kind: string;
|
||||||
|
schedule: Schedule;
|
||||||
|
job_status: string;
|
||||||
|
job_uuid: string;
|
||||||
|
creation_time: string;
|
||||||
|
update_time: string;
|
||||||
|
delete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Schedule {
|
||||||
|
type: string;
|
||||||
|
weekday: number;
|
||||||
|
offtime: number;
|
||||||
|
}
|
||||||
|
export class GcJobViewModel {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
createTime: Date;
|
||||||
|
updateTime: Date;
|
||||||
|
details: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WeekDay {
|
||||||
|
value: number;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GcScheduleViewModel {
|
||||||
|
type: string;
|
||||||
|
weekDay: string;
|
||||||
|
dailyTime: string;
|
||||||
|
}
|
||||||
|
|
@ -555,6 +555,7 @@
|
|||||||
"REPO_READ_ONLY": "Repository Read Only",
|
"REPO_READ_ONLY": "Repository Read Only",
|
||||||
"SYSTEM": "System Settings",
|
"SYSTEM": "System Settings",
|
||||||
"VULNERABILITY": "Vulnerability",
|
"VULNERABILITY": "Vulnerability",
|
||||||
|
"GC": "Garbage Collection",
|
||||||
"CONFIRM_TITLE": "Confirm to cancel",
|
"CONFIRM_TITLE": "Confirm to cancel",
|
||||||
"CONFIRM_SUMMARY": "Some changes have not been saved. Do you want to discard them?",
|
"CONFIRM_SUMMARY": "Some changes have not been saved. Do you want to discard them?",
|
||||||
"SAVE_SUCCESS": "Configuration has been successfully saved.",
|
"SAVE_SUCCESS": "Configuration has been successfully saved.",
|
||||||
@ -810,5 +811,33 @@
|
|||||||
"PRECONDITION_FAILED": "We are unable to perform your action because of a precondition failure.",
|
"PRECONDITION_FAILED": "We are unable to perform your action because of a precondition failure.",
|
||||||
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
|
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
|
||||||
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
|
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
|
||||||
"UNKNOWN": "n/a"
|
"UNKNOWN": "n/a",
|
||||||
|
"STATUS":"Status",
|
||||||
|
"START_TIME": "Start Time",
|
||||||
|
"END_TIME": "End Time",
|
||||||
|
"DETAILS":"Details",
|
||||||
|
"PENDING":"Pending",
|
||||||
|
"FINISHED":"Finished",
|
||||||
|
"SCHEDULE": {
|
||||||
|
"NONE": "None",
|
||||||
|
"DAILY": "Daily",
|
||||||
|
"WEEKLY": "Weekly",
|
||||||
|
"MANUAL": "Manual"
|
||||||
|
},
|
||||||
|
"GC": {
|
||||||
|
"CURRENT_SCHEDULE": "Current Schedule",
|
||||||
|
"ON": "on",
|
||||||
|
"AT": "at",
|
||||||
|
"GC_NOW": "GC NOW",
|
||||||
|
"JOB_LIST":"GC Jobs List",
|
||||||
|
"JOB_ID":"Job ID",
|
||||||
|
"TRIGGER_TYPE": "Trigger Type",
|
||||||
|
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||||
|
"LOG_DETAIL":"Log Details",
|
||||||
|
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||||
|
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||||
|
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
|
||||||
|
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -554,6 +554,7 @@
|
|||||||
"REPO_READ_ONLY": "Repository Read Only",
|
"REPO_READ_ONLY": "Repository Read Only",
|
||||||
"SYSTEM": "Opciones del Sistema",
|
"SYSTEM": "Opciones del Sistema",
|
||||||
"VULNERABILITY": "Vulnerability",
|
"VULNERABILITY": "Vulnerability",
|
||||||
|
"GC": "Garbage Collection",
|
||||||
"CONFIRM_TITLE": "Confirma cancelación",
|
"CONFIRM_TITLE": "Confirma cancelación",
|
||||||
"CONFIRM_SUMMARY": "Algunos cambios no han sido guardados aún. ¿Quiere descartarlos?",
|
"CONFIRM_SUMMARY": "Algunos cambios no han sido guardados aún. ¿Quiere descartarlos?",
|
||||||
"SAVE_SUCCESS": "La configuración ha sido guardada satisfactoriamente.",
|
"SAVE_SUCCESS": "La configuración ha sido guardada satisfactoriamente.",
|
||||||
@ -595,7 +596,9 @@
|
|||||||
"ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.",
|
"ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.",
|
||||||
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday.",
|
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday.",
|
||||||
"VERIFY_CERT": "Verify Cert from LDAP Server",
|
"VERIFY_CERT": "Verify Cert from LDAP Server",
|
||||||
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. "
|
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
|
||||||
|
"GC_POLICY": ""
|
||||||
|
|
||||||
},
|
},
|
||||||
"LDAP": {
|
"LDAP": {
|
||||||
"URL": "LDAP URL",
|
"URL": "LDAP URL",
|
||||||
@ -805,5 +808,32 @@
|
|||||||
"PRECONDITION_FAILED": "No hemos podido llevar a cabo la acción debido a un error de precondición.",
|
"PRECONDITION_FAILED": "No hemos podido llevar a cabo la acción debido a un error de precondición.",
|
||||||
"SERVER_ERROR": "No hemos podido llevar a cabo la acción debido a un error interno.",
|
"SERVER_ERROR": "No hemos podido llevar a cabo la acción debido a un error interno.",
|
||||||
"INCONRRECT_OLD_PWD": "La contraseña antigua no es correcta.",
|
"INCONRRECT_OLD_PWD": "La contraseña antigua no es correcta.",
|
||||||
"UNKNOWN": "n/a"
|
"UNKNOWN": "n/a",
|
||||||
|
"STATUS":"Status",
|
||||||
|
"START_TIME": "Start Time",
|
||||||
|
"END_TIME": "End Time",
|
||||||
|
"DETAILS":"Details",
|
||||||
|
"PENDING":"Pending",
|
||||||
|
"FINISHED":"Finished",
|
||||||
|
"SCHEDULE": {
|
||||||
|
"NONE": "None",
|
||||||
|
"DAILY": "Daily",
|
||||||
|
"WEEKLY": "Weekly",
|
||||||
|
"MANUAL": "Manual"
|
||||||
|
},
|
||||||
|
"GC": {
|
||||||
|
"CURRENT_SCHEDULE": "Current Schedule",
|
||||||
|
"ON": "on",
|
||||||
|
"AT": "at",
|
||||||
|
"GC_NOW": "GC NOW",
|
||||||
|
"JOB_LIST":"GC Jobs List",
|
||||||
|
"JOB_ID":"Job ID",
|
||||||
|
"TRIGGER_TYPE": "Trigger Type",
|
||||||
|
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||||
|
"LOG_DETAIL":"Log Details",
|
||||||
|
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||||
|
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||||
|
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
|
||||||
|
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
|
||||||
|
}
|
||||||
}
|
}
|
@ -553,6 +553,7 @@
|
|||||||
"PRO_CREATION_ADMIN": "Les Administrateurs Seulement",
|
"PRO_CREATION_ADMIN": "Les Administrateurs Seulement",
|
||||||
"ROOT_CERT": "Enregistrer le Certificat Racine",
|
"ROOT_CERT": "Enregistrer le Certificat Racine",
|
||||||
"ROOT_CERT_LINK": "Télécharger",
|
"ROOT_CERT_LINK": "Télécharger",
|
||||||
|
"GC": "Garbage Collection",
|
||||||
"TOOLTIP": {
|
"TOOLTIP": {
|
||||||
"SELF_REGISTRATION_ENABLE": "Activer l'inscription.",
|
"SELF_REGISTRATION_ENABLE": "Activer l'inscription.",
|
||||||
"SELF_REGISTRATION_DISABLE": "Désactiver l'inscription.",
|
"SELF_REGISTRATION_DISABLE": "Désactiver l'inscription.",
|
||||||
@ -566,7 +567,8 @@
|
|||||||
"PRO_CREATION_RESTRICTION": "L'indicateur pour définir quels utilisateurs ont le droit de créer des projets. Par défaut, tout le monde peut créer un projet. Définissez sur 'Administrateur Seulement' pour que seul un administrateur puisse créer un projet.",
|
"PRO_CREATION_RESTRICTION": "L'indicateur pour définir quels utilisateurs ont le droit de créer des projets. Par défaut, tout le monde peut créer un projet. Définissez sur 'Administrateur Seulement' pour que seul un administrateur puisse créer un projet.",
|
||||||
"ROOT_CERT_DOWNLOAD": "Téléchargez le certificat racine du dépôt.",
|
"ROOT_CERT_DOWNLOAD": "Téléchargez le certificat racine du dépôt.",
|
||||||
"SCANNING_POLICY": "Définissez la politique d'analyse des images en fonction des différentes exigences. 'Aucune' : pas de politique active; 'Tousles jours à' : déclenchement du balayage à l'heure spécifiée tous les jours.",
|
"SCANNING_POLICY": "Définissez la politique d'analyse des images en fonction des différentes exigences. 'Aucune' : pas de politique active; 'Tousles jours à' : déclenchement du balayage à l'heure spécifiée tous les jours.",
|
||||||
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. "
|
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
|
||||||
|
"GC_POLICY": ""
|
||||||
},
|
},
|
||||||
"LDAP": {
|
"LDAP": {
|
||||||
"URL": "URL LDAP",
|
"URL": "URL LDAP",
|
||||||
@ -769,5 +771,32 @@
|
|||||||
"PRECONDITION_FAILED": "Nous ne pouvons pas exécuter votre action en raison d'un échec de conditions préalables.",
|
"PRECONDITION_FAILED": "Nous ne pouvons pas exécuter votre action en raison d'un échec de conditions préalables.",
|
||||||
"SERVER_ERROR": "Nous ne sommes pas en mesure d'exécuter votre action parce que des erreurs internes de serveur se sont produites.",
|
"SERVER_ERROR": "Nous ne sommes pas en mesure d'exécuter votre action parce que des erreurs internes de serveur se sont produites.",
|
||||||
"INCONRRECT_OLD_PWD": "L'ancien mot de passe est incorrect.",
|
"INCONRRECT_OLD_PWD": "L'ancien mot de passe est incorrect.",
|
||||||
"UNKNOWN": "n. d."
|
"UNKNOWN": "n. d.",
|
||||||
|
"STATUS":"Status",
|
||||||
|
"START_TIME": "Start Time",
|
||||||
|
"END_TIME": "End Time",
|
||||||
|
"DETAILS":"Details",
|
||||||
|
"PENDING":"Pending",
|
||||||
|
"FINISHED":"Finished",
|
||||||
|
"SCHEDULE": {
|
||||||
|
"NONE": "None",
|
||||||
|
"DAILY": "Daily",
|
||||||
|
"WEEKLY": "Weekly",
|
||||||
|
"MANUAL": "Manual"
|
||||||
|
},
|
||||||
|
"GC": {
|
||||||
|
"CURRENT_SCHEDULE": "Current Schedule",
|
||||||
|
"ON": "on",
|
||||||
|
"AT": "at",
|
||||||
|
"GC_NOW": "GC NOW",
|
||||||
|
"JOB_LIST":"GC Jobs List",
|
||||||
|
"JOB_ID":"Job ID",
|
||||||
|
"TRIGGER_TYPE": "Trigger Type",
|
||||||
|
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||||
|
"LOG_DETAIL":"Log Details",
|
||||||
|
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||||
|
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||||
|
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
|
||||||
|
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,6 +554,7 @@
|
|||||||
"REPO_READ_ONLY": "仓库只读",
|
"REPO_READ_ONLY": "仓库只读",
|
||||||
"SYSTEM": "系统设置",
|
"SYSTEM": "系统设置",
|
||||||
"VULNERABILITY": "漏洞",
|
"VULNERABILITY": "漏洞",
|
||||||
|
"GC": "垃圾清理",
|
||||||
"CONFIRM_TITLE": "确认取消",
|
"CONFIRM_TITLE": "确认取消",
|
||||||
"CONFIRM_SUMMARY": "配置项有改动, 确定取消?",
|
"CONFIRM_SUMMARY": "配置项有改动, 确定取消?",
|
||||||
"SAVE_SUCCESS": "变更的配置项成功保存。",
|
"SAVE_SUCCESS": "变更的配置项成功保存。",
|
||||||
@ -806,5 +807,32 @@
|
|||||||
"PRECONDITION_FAILED": "验证前置条件失败, 无法执行操作。",
|
"PRECONDITION_FAILED": "验证前置条件失败, 无法执行操作。",
|
||||||
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
|
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
|
||||||
"INCONRRECT_OLD_PWD": "旧密码不正确。",
|
"INCONRRECT_OLD_PWD": "旧密码不正确。",
|
||||||
"UNKNOWN": "未知"
|
"UNKNOWN": "未知",
|
||||||
|
"STATUS":"状态",
|
||||||
|
"START_TIME": "开始时间",
|
||||||
|
"END_TIME": "结束时间",
|
||||||
|
"DETAILS":"详情",
|
||||||
|
"PENDING":"未开始",
|
||||||
|
"FINISHED":"已完成",
|
||||||
|
"SCHEDULE": {
|
||||||
|
"NONE": "无",
|
||||||
|
"DAILY": "每天",
|
||||||
|
"WEEKLY": "每周",
|
||||||
|
"MANUAL": "手动"
|
||||||
|
},
|
||||||
|
"GC": {
|
||||||
|
"CURRENT_SCHEDULE": "当前定时任务",
|
||||||
|
"ON": " ",
|
||||||
|
"AT": " ",
|
||||||
|
"GC_NOW": "立即清理垃圾",
|
||||||
|
"JOB_LIST":"任务列表",
|
||||||
|
"JOB_ID":"任务ID",
|
||||||
|
"TRIGGER_TYPE": "触发类型",
|
||||||
|
"LATEST_JOBS": "最新的 {{param}} 个任务",
|
||||||
|
"LOG_DETAIL":"日志详情",
|
||||||
|
"MSG_SUCCESS":"垃圾回收成功",
|
||||||
|
"MSG_SCHEDULE_SET":"垃圾回收定时任务设置成功",
|
||||||
|
"MSG_SCHEDULE_RESET":"垃圾回收定时任务已被重置",
|
||||||
|
"MSG_ERROR":"您的垃圾回收请求提交过于频繁,请稍候重试"
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user