Add build history component to Harbor UI

Add build histroy section and navbar to tag-detail component
Add i18n items of build history
Format code.

Signed-off-by: Frank Kung <kfanjian@gmail.com>
This commit is contained in:
Frank Kung 2018-10-12 18:41:45 +08:00
parent 4b103d115a
commit fd9e3dc2b8
12 changed files with 421 additions and 230 deletions

View File

@ -382,3 +382,14 @@ export interface HelmChartSignature {
signed: boolean; signed: boolean;
prov_file: string; prov_file: string;
} }
/**
* The manifest of image.
*
**
* interface Manifest
*/
export interface Manifest {
manifset: Object;
config: string;
}

View File

@ -9,7 +9,7 @@ import {
HTTP_GET_OPTIONS HTTP_GET_OPTIONS
} from "../utils"; } from "../utils";
import { RequestQueryParams } from "./RequestQueryParams"; import { RequestQueryParams } from "./RequestQueryParams";
import { Tag } from "./interface"; import { Tag, Manifest } from "./interface";
/** /**
* For getting tag signatures. * For getting tag signatures.
@ -90,6 +90,19 @@ export abstract class TagService {
tagName: string, tagName: string,
labelId: number labelId: number
): Observable<any> | Promise<any> | any; ): Observable<any> | Promise<any> | any;
/**
* Get manifest of tag under the specified repository.
*
* @abstract
* returns {(Observable<Manifest> | Promise<Manifest> | Manifest)}
*
* @memberOf TagService
*/
abstract getManifest(
repositoryName: string,
tag: string
): Observable<Manifest> | Promise<Manifest> | Manifest;
} }
/** /**
@ -225,4 +238,19 @@ export class TagDefaultService extends TagService {
.then(response => response.status) .then(response => response.status)
.catch(error => Promise.reject(error)); .catch(error => Promise.reject(error));
} }
public getManifest(
repositoryName: string,
tag: string
): Observable<Manifest> | Promise<Manifest> | Manifest {
if (!repositoryName || !tag) {
return Promise.reject("Bad argument");
}
let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}/manifest`;
return this.http
.get(url, HTTP_GET_OPTIONS)
.toPromise()
.then(response => response.json() as Manifest)
.catch(error => Promise.reject(error));
}
} }

View File

@ -1,11 +1,14 @@
import { Type } from '@angular/core'; import { Type } from "@angular/core";
import { TagComponent } from './tag.component'; import { TagComponent } from "./tag.component";
import { TagDetailComponent } from './tag-detail.component'; import { TagDetailComponent } from "./tag-detail.component";
import { TagHistoryComponent } from "./tag-history.component";
export * from './tag.component'; export * from "./tag.component";
export * from './tag-detail.component'; export * from "./tag-detail.component";
export * from "./tag-history.component";
export const TAG_DIRECTIVES: Type<any>[] = [ export const TAG_DIRECTIVES: Type<any>[] = [
TagComponent, TagComponent,
TagDetailComponent TagDetailComponent,
TagHistoryComponent
]; ];

View File

@ -42,17 +42,29 @@
<div class="flex-block vulnerabilities-info"> <div class="flex-block vulnerabilities-info">
<div class="second-column"> <div class="second-column">
<div class="row-flex"> <div class="row-flex">
<div class="icon-position"><clr-icon shape="error" size="24" class="is-error"></clr-icon></div> <div class="icon-position">
<span class="detail-count">{{highCount}}</span> {{packageText(highCount) | translate}} {{haveText(highCount) | translate}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{suffixForHigh | translate }}</div> <clr-icon shape="error" size="24" class="is-error"></clr-icon>
</div>
<span class="detail-count">{{highCount}}</span> {{packageText(highCount) | translate}} {{haveText(highCount) | translate}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{suffixForHigh | translate }}
</div>
<div class="second-row row-flex"> <div class="second-row row-flex">
<div class="icon-position"><clr-icon shape="exclamation-triangle" size="24" class="tip-icon-medium"></clr-icon></div> <div class="icon-position">
<span class="detail-count">{{mediumCount}}</span> {{packageText(mediumCount) | translate}} {{haveText(mediumCount) | translate}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{suffixForMedium | translate }}</div> <clr-icon shape="exclamation-triangle" size="24" class="tip-icon-medium"></clr-icon>
</div>
<span class="detail-count">{{mediumCount}}</span> {{packageText(mediumCount) | translate}} {{haveText(mediumCount) | translate}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{suffixForMedium | translate }}
</div>
<div class="second-row row-flex"> <div class="second-row row-flex">
<div class="icon-position"><clr-icon shape="play" size="22" class="tip-icon-low rotate-90"></clr-icon></div> <div class="icon-position">
<span class="detail-count">{{lowCount}}</span> {{packageText(lowCount) | translate}} {{haveText(lowCount) | translate}} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{suffixForLow | translate }}</div> <clr-icon shape="play" size="22" class="tip-icon-low rotate-90"></clr-icon>
</div>
<span class="detail-count">{{lowCount}}</span> {{packageText(lowCount) | translate}} {{haveText(lowCount) | translate}} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{suffixForLow | translate }}
</div>
<div class="second-row row-flex"> <div class="second-row row-flex">
<div class="icon-position"><clr-icon shape="help" size="20"></clr-icon></div> <div class="icon-position">
<span class="detail-count">{{unknownCount}}</span> {{packageText(unknownCount) | translate}} {{haveText(unknownCount) | translate}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{suffixForUnknown | translate }}</div> <clr-icon shape="help" size="20"></clr-icon>
</div>
<span class="detail-count">{{unknownCount}}</span> {{packageText(unknownCount) | translate}} {{haveText(unknownCount) | translate}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{suffixForUnknown | translate }}
</div>
</div> </div>
</div> </div>
@ -67,7 +79,15 @@
</div> </div>
</div> </div>
</section> </section>
<section class="detail-section"> <ul id="configTabs" class="nav" role="tablist">
<li *ngIf="withClair" role="presentation" class="nav-item">
<button id="tag-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("tag-vulnerability")' type="button" (click)='tabLinkClick("tag-vulnerability")'>{{'REPOSITORY.VULNERABILITY' | translate}}</button>
</li>
<li role="presentation" class="nav-item">
<button id="tag-history" class="btn btn-link nav-link" aria-controls="history" [class.active]='isCurrentTabLink("tag-history")' type="button" (click)='tabLinkClick("tag-history")'>{{ 'REPOSITORY.BUILD_HISTORY' | translate }}</button>
</li>
</ul>
<section class="detail-section" id="vulnerability" role="tabpanel" aria-labelledby="tag-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
<div class="vulnerability-block"> <div class="vulnerability-block">
<hbr-vulnerabilities-grid [repositoryId]="repositoryId" [tagId]="tagId" [withAdminRole]="withAdminRole"></hbr-vulnerabilities-grid> <hbr-vulnerabilities-grid [repositoryId]="repositoryId" [tagId]="tagId" [withAdminRole]="withAdminRole"></hbr-vulnerabilities-grid>
</div> </div>
@ -75,4 +95,7 @@
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
</section> </section>
<section class="detail-section" id="history" role="tabpanel" aria-labelledby="tag-history" [hidden]='!isCurrentTabContent("history")'>
<hbr-tag-history [repositoryId]="repositoryId" [tagId]="tagId"></hbr-tag-history>
</section>
</div> </div>

View File

@ -1,134 +1,175 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit } from "@angular/core";
import { TagService, Tag, VulnerabilitySeverity } from '../service/index'; import { TagService, Tag, VulnerabilitySeverity } from "../service/index";
import { toPromise } from '../utils'; import { toPromise } from "../utils";
import { ErrorHandler } from '../error-handler/index'; import { ErrorHandler } from "../error-handler/index";
import {Label} from "../service/interface"; import { Label } from "../service/interface";
const TabLinkContentMap: { [index: string]: string } = {
"tag-history": "history",
"tag-vulnerability": "vulnerability"
};
@Component({ @Component({
selector: 'hbr-tag-detail', selector: "hbr-tag-detail",
templateUrl: './tag-detail.component.html', templateUrl: "./tag-detail.component.html",
styleUrls: ['./tag-detail.component.scss'], styleUrls: ["./tag-detail.component.scss"],
providers: [] providers: []
}) })
export class TagDetailComponent implements OnInit { export class TagDetailComponent implements OnInit {
_highCount: number = 0; _highCount: number = 0;
_mediumCount: number = 0; _mediumCount: number = 0;
_lowCount: number = 0; _lowCount: number = 0;
_unknownCount: number = 0; _unknownCount: number = 0;
labels: Label; labels: Label;
@Input() tagId: string; @Input()
@Input() repositoryId: string; tagId: string;
@Input() withAdmiral: boolean; @Input()
@Input() withClair: boolean; repositoryId: string;
@Input() withAdminRole: boolean; @Input()
tagDetails: Tag = { withAdmiral: boolean;
name: "--", @Input()
size: "--", withClair: boolean;
author: "--", @Input()
created: new Date(), withAdminRole: boolean;
architecture: "--", tagDetails: Tag = {
os: "--", name: "--",
docker_version: "--", size: "--",
digest: "--", author: "--",
labels: [], created: new Date(),
}; architecture: "--",
os: "--",
docker_version: "--",
digest: "--",
labels: []
};
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>(); @Output()
backEvt: EventEmitter<any> = new EventEmitter<any>();
constructor( currentTabID = "tag-vulnerability";
private tagService: TagService,
private errorHandler: ErrorHandler) { }
ngOnInit(): void { constructor(
if (this.repositoryId && this.tagId) { private tagService: TagService,
toPromise<Tag>(this.tagService.getTag(this.repositoryId, this.tagId)) private errorHandler: ErrorHandler
.then(response => { ) {}
this.tagDetails = response;
if (this.tagDetails && ngOnInit(): void {
this.tagDetails.scan_overview && if (this.repositoryId && this.tagId) {
this.tagDetails.scan_overview.components && toPromise<Tag>(this.tagService.getTag(this.repositoryId, this.tagId))
this.tagDetails.scan_overview.components.summary) { .then(response => {
this.tagDetails.scan_overview.components.summary.forEach(item => { this.tagDetails = response;
switch (item.severity) { if (
case VulnerabilitySeverity.UNKNOWN: this.tagDetails &&
this._unknownCount += item.count; this.tagDetails.scan_overview &&
break; this.tagDetails.scan_overview.components &&
case VulnerabilitySeverity.LOW: this.tagDetails.scan_overview.components.summary
this._lowCount += item.count; ) {
break; this.tagDetails.scan_overview.components.summary.forEach(item => {
case VulnerabilitySeverity.MEDIUM: switch (item.severity) {
this._mediumCount += item.count; case VulnerabilitySeverity.UNKNOWN:
break; this._unknownCount += item.count;
case VulnerabilitySeverity.HIGH: break;
this._highCount += item.count; case VulnerabilitySeverity.LOW:
break; this._lowCount += item.count;
default: break;
break; case VulnerabilitySeverity.MEDIUM:
} this._mediumCount += item.count;
}); break;
} case VulnerabilitySeverity.HIGH:
}) this._highCount += item.count;
.catch(error => this.errorHandler.error(error)); break;
} default:
break;
}
});
}
})
.catch(error => this.errorHandler.error(error));
} }
}
onBack(): void { onBack(): void {
this.backEvt.emit(this.repositoryId); this.backEvt.emit(this.repositoryId);
} }
getPackageText(count: number): string { getPackageText(count: number): string {
return count > 1 ? "VULNERABILITY.PACKAGES" : "VULNERABILITY.PACKAGE"; return count > 1 ? "VULNERABILITY.PACKAGES" : "VULNERABILITY.PACKAGE";
} }
packageText(count: number): string { packageText(count: number): string {
return count > 1 ? "VULNERABILITY.GRID.COLUMN_PACKAGES" : "VULNERABILITY.GRID.COLUMN_PACKAGE"; return count > 1
} ? "VULNERABILITY.GRID.COLUMN_PACKAGES"
: "VULNERABILITY.GRID.COLUMN_PACKAGE";
}
haveText(count: number): string { haveText(count: number): string {
return count > 1 ? "TAG.HAVE" : "TAG.HAS"; return count > 1 ? "TAG.HAVE" : "TAG.HAS";
} }
public get author(): string { public get author(): string {
return this.tagDetails && this.tagDetails.author ? this.tagDetails.author : 'TAG.ANONYMITY'; return this.tagDetails && this.tagDetails.author
} ? this.tagDetails.author
: "TAG.ANONYMITY";
}
public get highCount(): number { public get highCount(): number {
return this._highCount; return this._highCount;
} }
public get mediumCount(): number { public get mediumCount(): number {
return this._mediumCount; return this._mediumCount;
} }
public get lowCount(): number { public get lowCount(): number {
return this._lowCount; return this._lowCount;
} }
public get unknownCount(): number { public get unknownCount(): number {
return this._unknownCount; return this._unknownCount;
} }
public get scanCompletedDatetime(): Date { public get scanCompletedDatetime(): Date {
return this.tagDetails && this.tagDetails.scan_overview ? return this.tagDetails && this.tagDetails.scan_overview
this.tagDetails.scan_overview.update_time : null; ? this.tagDetails.scan_overview.update_time
} : null;
}
public get suffixForHigh(): string { public get suffixForHigh(): string {
return this.highCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR"; return this.highCount > 1
} ? "VULNERABILITY.PLURAL"
: "VULNERABILITY.SINGULAR";
}
public get suffixForMedium(): string { public get suffixForMedium(): string {
return this.mediumCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR"; return this.mediumCount > 1
} ? "VULNERABILITY.PLURAL"
: "VULNERABILITY.SINGULAR";
}
public get suffixForLow(): string { public get suffixForLow(): string {
return this.lowCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR"; return this.lowCount > 1
} ? "VULNERABILITY.PLURAL"
: "VULNERABILITY.SINGULAR";
}
public get suffixForUnknown(): string { public get suffixForUnknown(): string {
return this.unknownCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR"; return this.unknownCount > 1
} ? "VULNERABILITY.PLURAL"
: "VULNERABILITY.SINGULAR";
}
isCurrentTabLink(tabID: string): boolean {
return this.currentTabID === tabID;
}
isCurrentTabContent(ContentID: string): boolean {
return TabLinkContentMap[this.currentTabID] === ContentID;
}
tabLinkClick(tabID: string) {
this.currentTabID = tabID;
}
} }

View File

@ -0,0 +1,11 @@
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-column style="width: 160px;">{{ 'TAG.CREATION' | translate }}</clr-dg-column>
<clr-dg-column>{{ 'TAG.COMMAND' | translate }}</clr-dg-column>
<clr-dg-row *clrDgItems="let h of history" [clrDgItem]='h' class="history-item">
<clr-dg-cell>{{ h.created | date: 'short' }}</clr-dg-cell>
<clr-dg-cell>{{ h.created_by }}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{ history.length }} commands</clr-dg-footer>
</clr-datagrid>

View File

@ -0,0 +1,65 @@
import { Component, Input, Output, EventEmitter, OnInit } from "@angular/core";
import { TagService, Manifest } from "../service/index";
import { toPromise } from "../utils";
import { ErrorHandler } from "../error-handler/index";
@Component({
selector: "hbr-tag-history",
templateUrl: "./tag-history.component.html",
providers: []
})
export class TagHistoryComponent implements OnInit {
@Input()
tagId: string;
@Input()
repositoryId: string;
@Output()
backEvt: EventEmitter<any> = new EventEmitter<any>();
config: any = {};
history: Object[] = [];
loading: Boolean = false;
constructor(
private tagService: TagService,
private errorHandler: ErrorHandler
) {}
ngOnInit(): void {
if (this.repositoryId && this.tagId) {
this.retrieve(this.repositoryId, this.tagId);
}
}
retrieve(repositoryId: string, tagId: string) {
this.loading = true;
toPromise<Manifest>(
this.tagService.getManifest(this.repositoryId, this.tagId)
)
.then(data => {
this.config = JSON.parse(data.config);
this.config.history.forEach((ele: any) => {
if (ele.created_by !== undefined) {
ele.created_by = ele.created_by
.replace("/bin/sh -c #(nop)", "")
.trimLeft()
.replace("/bin/sh -c", "RUN");
} else {
ele.created_by = ele.comment;
}
this.history.push(ele);
});
this.loading = false;
})
.catch(error => {
this.errorHandler.error(error);
this.loading = false;
});
}
onBack(): void {
this.backEvt.emit(this.tagId);
}
}

View File

@ -16,7 +16,7 @@
"npm_pack": "cd lib/dist && npm pack", "npm_pack": "cd lib/dist && npm pack",
"package": "npm run build_lib && npm run npm_pack", "package": "npm run build_lib && npm run npm_pack",
"link_lib": "cd lib/dist && npm link && cd ../../ && npm link @harbor/ui", "link_lib": "cd lib/dist && npm link && cd ../../ && npm link @harbor/ui",
"build_all":"npm run build_lib && npm run link_lib && npm run build" "build_all": "npm run build_lib && npm run link_lib && npm run build"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@ -379,15 +379,15 @@
"IMMEDIATE": "Immediate", "IMMEDIATE": "Immediate",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"SETTING":"Options", "SETTING": "Options",
"TRIGGER":"Triggering Condition", "TRIGGER": "Triggering Condition",
"TARGETS":"Target", "TARGETS": "Target",
"MODE": "Mode", "MODE": "Mode",
"TRIGGER_MODE": "Trigger Mode", "TRIGGER_MODE": "Trigger Mode",
"SOURCE_PROJECT": "Source project", "SOURCE_PROJECT": "Source project",
"REPLICATE": "Replicate", "REPLICATE": "Replicate",
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted", "DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
"REPLICATE_IMMEDIATE":"Replicate existing images immediately", "REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New", "NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.", "DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
@ -447,6 +447,7 @@
"TAG": "Tag", "TAG": "Tag",
"SIZE": "Size", "SIZE": "Size",
"VULNERABILITY": "Vulnerability", "VULNERABILITY": "Vulnerability",
"BUILD_HISTORY": "Build History",
"SIGNED": "Signed", "SIGNED": "Signed",
"AUTHOR": "Author", "AUTHOR": "Author",
"CREATED": "Creation Time", "CREATED": "Creation Time",
@ -753,7 +754,9 @@
"COPY_ERROR": "Copy failed, please try to manually copy.", "COPY_ERROR": "Copy failed, please try to manually copy.",
"FILTER_FOR_TAGS": "Filter Tags", "FILTER_FOR_TAGS": "Filter Tags",
"AUTHOR": "Author", "AUTHOR": "Author",
"LABELS": "Labels" "LABELS": "Labels",
"CREATION": "Create on",
"COMMAND": "Commands"
}, },
"LABEL": { "LABEL": {
"LABEL": "Label", "LABEL": "Label",
@ -818,33 +821,32 @@
"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", "STATUS": "Status",
"START_TIME": "Start Time", "START_TIME": "Start Time",
"UPDATE_TIME": "Update Time", "END_TIME": "End Time",
"LOGS":"Logs", "DETAILS": "Details",
"PENDING":"Pending", "PENDING": "Pending",
"FINISHED":"Finished", "FINISHED": "Finished",
"STOPPED":"Stopped", "STOPPED": "Stopped",
"RUNNING":"Running", "RUNNING": "Running",
"ERROR":"Error", "ERROR": "Error",
"SCHEDULE": { "SCHEDULE": {
"NONE": "None", "NONE": "None",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"MANUAL": "Manual", "MANUAL": "Manual"
"ON": "on",
"AT": "at"
}, },
"GC": { "GC": {
"CURRENT_SCHEDULE": "Current Schedule", "CURRENT_SCHEDULE": "Current Schedule",
"GC_NOW": "GC NOW", "GC_NOW": "GC NOW",
"JOB_HISTORY":"GC Jobs History", "JOB_LIST": "GC Jobs List",
"JOB_ID":"Job ID", "JOB_ID": "Job ID",
"TRIGGER_TYPE": "Trigger Type", "TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs", "LATEST_JOBS": "Latest {{param}} Jobs",
"MSG_SUCCESS":"Garbage Collection Successful", "LOG_DETAIL": "Log Details",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set", "MSG_SUCCESS": "Garbage Collection Successful",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset" "MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
} }
} }

View File

@ -378,15 +378,15 @@
"IMMEDIATE": "Immediate", "IMMEDIATE": "Immediate",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"SETTING":"Options", "SETTING": "Options",
"TRIGGER":"Triggering Condition", "TRIGGER": "Triggering Condition",
"TARGETS":"Target", "TARGETS": "Target",
"MODE": "Mode", "MODE": "Mode",
"TRIGGER_MODE": "Trigger Mode", "TRIGGER_MODE": "Trigger Mode",
"SOURCE_PROJECT": "Source project", "SOURCE_PROJECT": "Source project",
"REPLICATE": "Replicate", "REPLICATE": "Replicate",
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted", "DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
"REPLICATE_IMMEDIATE":"Replicate existing images immediately", "REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New", "NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.", "DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
@ -446,6 +446,7 @@
"TAG": "Etiqueta", "TAG": "Etiqueta",
"SIZE": "Size", "SIZE": "Size",
"VULNERABILITY": "Vulnerability", "VULNERABILITY": "Vulnerability",
"BUILD_HISTORY": "Construir Historia",
"SIGNED": "Firmada", "SIGNED": "Firmada",
"AUTHOR": "Autor", "AUTHOR": "Autor",
"CREATED": "Fecha de creación", "CREATED": "Fecha de creación",
@ -752,7 +753,10 @@
"COPY_ERROR": "Copy failed, please try to manually copy.", "COPY_ERROR": "Copy failed, please try to manually copy.",
"FILTER_FOR_TAGS": "Etiquetas de filtro", "FILTER_FOR_TAGS": "Etiquetas de filtro",
"AUTHOR": "Author", "AUTHOR": "Author",
"LABELS": "Labels" "LABELS": "Labels",
"CREATION": "Tiempo de creación",
"COMMAND": "Mando"
}, },
"LABEL": { "LABEL": {
"LABEL": "Label", "LABEL": "Label",
@ -771,13 +775,13 @@
"NAME_ALREADY_EXISTS": "Label name already exists." "NAME_ALREADY_EXISTS": "Label name already exists."
}, },
"WEEKLY": { "WEEKLY": {
"MONDAY": "Monday", "MONDAY": "Monday",
"TUESDAY": "Tuesday", "TUESDAY": "Tuesday",
"WEDNESDAY": "Wednesday", "WEDNESDAY": "Wednesday",
"THURSDAY": "Thursday", "THURSDAY": "Thursday",
"FRIDAY": "Friday", "FRIDAY": "Friday",
"SATURDAY": "Saturday", "SATURDAY": "Saturday",
"SUNDAY": "Sunday" "SUNDAY": "Sunday"
}, },
"OPERATION": { "OPERATION": {
"LOCAL_EVENT": "Local Events", "LOCAL_EVENT": "Local Events",
@ -815,32 +819,31 @@
"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", "STATUS": "Status",
"START_TIME": "Start Time", "START_TIME": "Start Time",
"UPDATE_TIME": "Update Time", "END_TIME": "End Time",
"LOGS":"Logs", "DETAILS": "Details",
"PENDING":"Pending", "PENDING": "Pending",
"FINISHED":"Finished", "FINISHED": "Finished",
"STOPPED":"Stopped", "STOPPED": "Stopped",
"RUNNING":"Running", "RUNNING": "Running",
"ERROR":"Error", "ERROR": "Error",
"SCHEDULE": { "SCHEDULE": {
"NONE": "None", "NONE": "None",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"MANUAL": "Manual", "MANUAL": "Manual"
"ON": "on",
"AT": "at"
}, },
"GC": { "GC": {
"CURRENT_SCHEDULE": "Current Schedule", "CURRENT_SCHEDULE": "Current Schedule",
"GC_NOW": "GC NOW", "GC_NOW": "GC NOW",
"JOB_HISTORY":"GC Jobs History", "JOB_LIST": "GC Jobs List",
"JOB_ID":"Job ID", "JOB_ID": "Job ID",
"TRIGGER_TYPE": "Trigger Type", "TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs", "LATEST_JOBS": "Latest {{param}} Jobs",
"MSG_SUCCESS":"Garbage Collection Successful", "LOG_DETAIL": "Log Details",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set", "MSG_SUCCESS": "Garbage Collection Successful",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset" "MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
} }
} }

View File

@ -359,15 +359,15 @@
"IMMEDIATE": "Immediate", "IMMEDIATE": "Immediate",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"SETTING":"Options", "SETTING": "Options",
"TRIGGER":"Triggering Condition", "TRIGGER": "Triggering Condition",
"TARGETS":"Target", "TARGETS": "Target",
"MODE": "Mode", "MODE": "Mode",
"TRIGGER_MODE": "Trigger Mode", "TRIGGER_MODE": "Trigger Mode",
"SOURCE_PROJECT": "Source project", "SOURCE_PROJECT": "Source project",
"REPLICATE": "Replicate", "REPLICATE": "Replicate",
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted", "DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
"REPLICATE_IMMEDIATE":"Replicate existing images immediately", "REPLICATE_IMMEDIATE": "Replicate existing images immediately",
"NEW": "New", "NEW": "New",
"NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "NAME_TOOLTIP": "replication rule name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.",
"DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.", "DELETED_LABEL_INFO": "Deleted label(s) '{{param}}' referenced in the filter, click 'SAVE' to update the filter to enable this rule.",
@ -426,6 +426,7 @@
"TAG": "Tag", "TAG": "Tag",
"SIZE": "Taille", "SIZE": "Taille",
"VULNERABILITY": "Vulnérabilitée", "VULNERABILITY": "Vulnérabilitée",
"BUILD_HISTORY": "Construire l'histoire",
"SIGNED": "Signé", "SIGNED": "Signé",
"AUTHOR": "Auteur", "AUTHOR": "Auteur",
"CREATED": "Heure de Création", "CREATED": "Heure de Création",
@ -716,7 +717,9 @@
"COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement.", "COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement.",
"FILTER_FOR_TAGS": "Filter Tags", "FILTER_FOR_TAGS": "Filter Tags",
"AUTHOR": "Author", "AUTHOR": "Author",
"LABELS": "Labels" "LABELS": "Labels",
"CREATION": "Créer sur",
"COMMAND": "Les commandes"
}, },
"LABEL": { "LABEL": {
"LABEL": "Label", "LABEL": "Label",
@ -779,32 +782,31 @@
"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", "STATUS": "Status",
"START_TIME": "Start Time", "START_TIME": "Start Time",
"UPDATE_TIME": "Update Time", "END_TIME": "End Time",
"LOGS":"Logs", "DETAILS": "Details",
"PENDING":"Pending", "PENDING": "Pending",
"FINISHED":"Finished", "FINISHED": "Finished",
"STOPPED":"Stopped", "STOPPED": "Stopped",
"RUNNING":"Running", "RUNNING": "Running",
"ERROR":"Error", "ERROR": "Error",
"SCHEDULE": { "SCHEDULE": {
"NONE": "None", "NONE": "None",
"DAILY": "Daily", "DAILY": "Daily",
"WEEKLY": "Weekly", "WEEKLY": "Weekly",
"MANUAL": "Manual", "MANUAL": "Manual"
"ON": "on",
"AT": "at"
}, },
"GC": { "GC": {
"CURRENT_SCHEDULE": "Current Schedule", "CURRENT_SCHEDULE": "Current Schedule",
"GC_NOW": "GC NOW", "GC_NOW": "GC NOW",
"JOB_HISTORY":"GC Jobs History", "JOB_LIST": "GC Jobs List",
"JOB_ID":"Job ID", "JOB_ID": "Job ID",
"TRIGGER_TYPE": "Trigger Type", "TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs", "LATEST_JOBS": "Latest {{param}} Jobs",
"MSG_SUCCESS":"Garbage Collection Successful", "LOG_DETAIL": "Log Details",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set", "MSG_SUCCESS": "Garbage Collection Successful",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset" "MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
} }
} }

View File

@ -378,15 +378,15 @@
"IMMEDIATE": "即刻", "IMMEDIATE": "即刻",
"DAILY": "每天", "DAILY": "每天",
"WEEKLY": "每周", "WEEKLY": "每周",
"SETTING":"设置", "SETTING": "设置",
"TRIGGER":"触发条件", "TRIGGER": "触发条件",
"TARGETS":"目标", "TARGETS": "目标",
"MODE": "模式", "MODE": "模式",
"TRIGGER_MODE": "触发模式", "TRIGGER_MODE": "触发模式",
"SOURCE_PROJECT": "源项目", "SOURCE_PROJECT": "源项目",
"REPLICATE": "复制", "REPLICATE": "复制",
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。", "DELETE_REMOTE_IMAGES": "删除本地镜像时同时也删除远程的镜像。",
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。", "REPLICATE_IMMEDIATE": "立即复制现有的镜像。",
"NEW": "新增", "NEW": "新增",
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。", "NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。", "DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
@ -446,6 +446,7 @@
"TAG": "标签", "TAG": "标签",
"SIZE": "大小", "SIZE": "大小",
"VULNERABILITY": "漏洞", "VULNERABILITY": "漏洞",
"BUILD_HISTORY": "构建历史",
"SIGNED": "已签名", "SIGNED": "已签名",
"AUTHOR": "作者", "AUTHOR": "作者",
"CREATED": "创建时间", "CREATED": "创建时间",
@ -751,7 +752,9 @@
"COPY_ERROR": "拷贝失败,请尝试手动拷贝。", "COPY_ERROR": "拷贝失败,请尝试手动拷贝。",
"FILTER_FOR_TAGS": "过滤项目", "FILTER_FOR_TAGS": "过滤项目",
"AUTHOR": "作者", "AUTHOR": "作者",
"LABELS": "标签" "LABELS": "标签",
"CREATION": "创建时间",
"COMMAND": "命令"
}, },
"LABEL": { "LABEL": {
"LABEL": "标签", "LABEL": "标签",
@ -764,7 +767,7 @@
"COLOR": "颜色", "COLOR": "颜色",
"FILTER_Label_PLACEHOLDER": "过滤标签", "FILTER_Label_PLACEHOLDER": "过滤标签",
"NO_LABELS": "无标签", "NO_LABELS": "无标签",
"DELETION_TITLE_TARGET":"删除标签确认", "DELETION_TITLE_TARGET": "删除标签确认",
"DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?", "DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?",
"PLACEHOLDER": "未发现任何标签!", "PLACEHOLDER": "未发现任何标签!",
"NAME_ALREADY_EXISTS": "标签名已存在。" "NAME_ALREADY_EXISTS": "标签名已存在。"
@ -814,32 +817,31 @@
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。", "SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
"INCONRRECT_OLD_PWD": "旧密码不正确。", "INCONRRECT_OLD_PWD": "旧密码不正确。",
"UNKNOWN": "未知", "UNKNOWN": "未知",
"STATUS":"状态", "STATUS": "状态",
"START_TIME": "创建时间", "START_TIME": "开始时间",
"UPDATE_TIME": "更新时间", "END_TIME": "结束时间",
"LOGS":"日志", "DETAILS": "详情",
"PENDING":"未开始", "PENDING": "未开始",
"FINISHED":"已完成", "FINISHED": "已完成",
"STOPPED":"已停止", "STOPPED": "已停止",
"RUNNING":"执行中", "RUNNING": "执行中",
"ERROR":"错误", "ERROR": "错误",
"SCHEDULE": { "SCHEDULE": {
"NONE": "无", "NONE": "无",
"DAILY": "每天", "DAILY": "每天",
"WEEKLY": "每周", "WEEKLY": "每周",
"MANUAL": "手动", "MANUAL": "手动"
"ON": " ",
"AT": " "
}, },
"GC": { "GC": {
"CURRENT_SCHEDULE": "当前定时任务", "CURRENT_SCHEDULE": "当前定时任务",
"GC_NOW": "立即清理垃圾", "GC_NOW": "立即清理垃圾",
"JOB_HISTORY":"历史任务", "JOB_LIST": "任务列表",
"JOB_ID":"任务ID", "JOB_ID": "任务ID",
"TRIGGER_TYPE": "触发类型", "TRIGGER_TYPE": "触发类型",
"LATEST_JOBS": "最新的 {{param}} 个任务", "LATEST_JOBS": "最新的 {{param}} 个任务",
"MSG_SUCCESS":"垃圾回收成功", "LOG_DETAIL": "日志详情",
"MSG_SCHEDULE_SET":"垃圾回收定时任务设置成功", "MSG_SUCCESS": "垃圾回收成功",
"MSG_SCHEDULE_RESET":"垃圾回收定时任务已被重置" "MSG_SCHEDULE_SET": "垃圾回收定时任务设置成功",
"MSG_SCHEDULE_RESET": "垃圾回收定时任务已被重置"
} }
} }