mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-22 23:51:27 +01:00
commit
04e9190870
@ -382,3 +382,14 @@ export interface HelmChartSignature {
|
||||
signed: boolean;
|
||||
prov_file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The manifest of image.
|
||||
*
|
||||
**
|
||||
* interface Manifest
|
||||
*/
|
||||
export interface Manifest {
|
||||
manifset: Object;
|
||||
config: string;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
HTTP_GET_OPTIONS
|
||||
} from "../utils";
|
||||
import { RequestQueryParams } from "./RequestQueryParams";
|
||||
import { Tag } from "./interface";
|
||||
import { Tag, Manifest } from "./interface";
|
||||
|
||||
/**
|
||||
* For getting tag signatures.
|
||||
@ -90,6 +90,19 @@ export abstract class TagService {
|
||||
tagName: string,
|
||||
labelId: number
|
||||
): 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)
|
||||
.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));
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { TagComponent } from './tag.component';
|
||||
import { TagDetailComponent } from './tag-detail.component';
|
||||
import { Type } from "@angular/core";
|
||||
import { TagComponent } from "./tag.component";
|
||||
import { TagDetailComponent } from "./tag-detail.component";
|
||||
import { TagHistoryComponent } from "./tag-history.component";
|
||||
|
||||
export * from './tag.component';
|
||||
export * from './tag-detail.component';
|
||||
export * from "./tag.component";
|
||||
export * from "./tag-detail.component";
|
||||
export * from "./tag-history.component";
|
||||
|
||||
export const TAG_DIRECTIVES: Type<any>[] = [
|
||||
TagComponent,
|
||||
TagDetailComponent
|
||||
TagDetailComponent,
|
||||
TagHistoryComponent
|
||||
];
|
||||
|
@ -20,7 +20,7 @@
|
||||
<label class="detail-label">{{'TAG.ARCHITECTURE' | translate }}</label>
|
||||
<div class="image-details" [title]="tagDetails.architecture">{{tagDetails.architecture}}</div>
|
||||
</section>
|
||||
<section class="detail-row">
|
||||
<section class="detail-row">
|
||||
<label class="detail-label">{{'TAG.OS' | translate }}</label>
|
||||
<div class="image-details" [title]="tagDetails.os">{{tagDetails.os}}</div>
|
||||
</section>
|
||||
@ -42,17 +42,29 @@
|
||||
<div class="flex-block vulnerabilities-info">
|
||||
<div class="second-column">
|
||||
<div class="row-flex">
|
||||
<div class="icon-position"><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="icon-position">
|
||||
<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="icon-position"><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="icon-position">
|
||||
<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="icon-position"><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="icon-position">
|
||||
<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="icon-position"><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 class="icon-position">
|
||||
<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>
|
||||
|
||||
@ -67,7 +79,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<hbr-vulnerabilities-grid [repositoryId]="repositoryId" [tagId]="tagId" [withAdminRole]="withAdminRole"></hbr-vulnerabilities-grid>
|
||||
</div>
|
||||
@ -75,4 +95,7 @@
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</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>
|
@ -1,89 +1,118 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, async } from "@angular/core/testing";
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ResultGridComponent } from '../vulnerability-scanning/result-grid.component';
|
||||
import { TagDetailComponent } from './tag-detail.component';
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { ResultGridComponent } from "../vulnerability-scanning/result-grid.component";
|
||||
import { TagDetailComponent } from "./tag-detail.component";
|
||||
import { TagHistoryComponent } from "./tag-history.component";
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { Tag, VulnerabilitySummary, VulnerabilityItem, VulnerabilitySeverity } from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index';
|
||||
import { FilterComponent } from '../filter/index';
|
||||
import { VULNERABILITY_SCAN_STATUS } from '../utils';
|
||||
import {VULNERABILITY_DIRECTIVES} from "../vulnerability-scanning/index";
|
||||
import {LabelPieceComponent} from "../label-piece/label-piece.component";
|
||||
import {JobLogViewerComponent} from "../job-log-viewer/job-log-viewer.component";
|
||||
import {ChannelService} from "../channel/channel.service";
|
||||
import {JobLogService, JobLogDefaultService} from "../service/job-log.service";
|
||||
|
||||
describe('TagDetailComponent (inline template)', () => {
|
||||
import { ErrorHandler } from "../error-handler/error-handler";
|
||||
import {
|
||||
Tag,
|
||||
Manifest,
|
||||
VulnerabilitySummary,
|
||||
VulnerabilityItem,
|
||||
VulnerabilitySeverity
|
||||
} from "../service/interface";
|
||||
import { SERVICE_CONFIG, IServiceConfig } from "../service.config";
|
||||
import {
|
||||
TagService,
|
||||
TagDefaultService,
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService
|
||||
} from "../service/index";
|
||||
import { FilterComponent } from "../filter/index";
|
||||
import { VULNERABILITY_SCAN_STATUS } from "../utils";
|
||||
import { VULNERABILITY_DIRECTIVES } from "../vulnerability-scanning/index";
|
||||
import { LabelPieceComponent } from "../label-piece/label-piece.component";
|
||||
import { JobLogViewerComponent } from "../job-log-viewer/job-log-viewer.component";
|
||||
import { ChannelService } from "../channel/channel.service";
|
||||
import {
|
||||
JobLogService,
|
||||
JobLogDefaultService
|
||||
} from "../service/job-log.service";
|
||||
|
||||
describe("TagDetailComponent (inline template)", () => {
|
||||
let comp: TagDetailComponent;
|
||||
let fixture: ComponentFixture<TagDetailComponent>;
|
||||
let tagService: TagService;
|
||||
let scanningService: ScanningResultService;
|
||||
let spy: jasmine.Spy;
|
||||
let vulSpy: jasmine.Spy;
|
||||
let manifestSpy: jasmine.Spy;
|
||||
let mockVulnerability: VulnerabilitySummary = {
|
||||
scan_status: VULNERABILITY_SCAN_STATUS.finished,
|
||||
severity: 5,
|
||||
update_time: new Date(),
|
||||
components: {
|
||||
total: 124,
|
||||
summary: [{
|
||||
severity: 1,
|
||||
count: 90
|
||||
}, {
|
||||
severity: 3,
|
||||
count: 10
|
||||
}, {
|
||||
severity: 4,
|
||||
count: 10
|
||||
}, {
|
||||
severity: 5,
|
||||
count: 13
|
||||
}]
|
||||
summary: [
|
||||
{
|
||||
severity: 1,
|
||||
count: 90
|
||||
},
|
||||
{
|
||||
severity: 3,
|
||||
count: 10
|
||||
},
|
||||
{
|
||||
severity: 4,
|
||||
count: 10
|
||||
},
|
||||
{
|
||||
severity: 5,
|
||||
count: 13
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
let mockTag: Tag = {
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "nginx",
|
||||
"size": "2049",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
"author": "steven",
|
||||
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
"signature": null,
|
||||
"scan_overview": mockVulnerability,
|
||||
"labels": [],
|
||||
digest:
|
||||
"sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
name: "nginx",
|
||||
size: "2049",
|
||||
architecture: "amd64",
|
||||
os: "linux",
|
||||
docker_version: "1.12.3",
|
||||
author: "steven",
|
||||
created: new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
signature: null,
|
||||
scan_overview: mockVulnerability,
|
||||
labels: []
|
||||
};
|
||||
|
||||
let config: IServiceConfig = {
|
||||
repositoryBaseEndpoint: '/api/repositories/testing'
|
||||
repositoryBaseEndpoint: "/api/repositories/testing"
|
||||
};
|
||||
|
||||
let mockManifest: Manifest = {
|
||||
manifset: {},
|
||||
// tslint:disable-next-line:max-line-length
|
||||
config: `{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"30e1a2427aa2325727a092488d304505780501585a6ccf5a6a53c4d83a826101","container_config":{"Hostname":"30e1a2427aa2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"/bin/sh\\"]"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2018-01-09T21:10:58.579708634Z","docker_version":"17.06.2-ce","history":[{"created":"2018-01-09T21:10:58.365737589Z","created_by":"/bin/sh -c #(nop) ADD file:093f0723fa46f6cdbd6f7bd146448bb70ecce54254c35701feeceb956414622f in / "},{"created":"2018-01-09T21:10:58.579708634Z","created_by":"/bin/sh -c #(nop) CMD [\\"/bin/sh\\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:cd7100a72410606589a54b932cabd804a17f9ae5b42a1882bd56d263e02b6215"]}}`
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
imports: [SharedModule],
|
||||
declarations: [
|
||||
TagDetailComponent,
|
||||
TagHistoryComponent,
|
||||
ResultGridComponent,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
LabelPieceComponent,
|
||||
JobLogViewerComponent,
|
||||
LabelPieceComponent,
|
||||
JobLogViewerComponent,
|
||||
FilterComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
JobLogDefaultService,
|
||||
{provide: JobLogService, useClass: JobLogDefaultService},
|
||||
{ provide: JobLogService, useClass: JobLogDefaultService },
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||
{
|
||||
provide: ScanningResultService,
|
||||
useClass: ScanningResultDefaultService
|
||||
}
|
||||
]
|
||||
});
|
||||
}));
|
||||
@ -96,54 +125,83 @@ describe('TagDetailComponent (inline template)', () => {
|
||||
comp.repositoryId = "mock_repo";
|
||||
|
||||
tagService = fixture.debugElement.injector.get(TagService);
|
||||
spy = spyOn(tagService, 'getTag').and.returnValues(Promise.resolve(mockTag));
|
||||
spy = spyOn(tagService, "getTag").and.returnValues(
|
||||
Promise.resolve(mockTag)
|
||||
);
|
||||
|
||||
let mockData: VulnerabilityItem[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
let res: VulnerabilityItem = {
|
||||
id: "CVE-2016-" + (8859 + i),
|
||||
severity: i % 2 === 0 ? VulnerabilitySeverity.HIGH : VulnerabilitySeverity.MEDIUM,
|
||||
severity:
|
||||
i % 2 === 0
|
||||
? VulnerabilitySeverity.HIGH
|
||||
: VulnerabilitySeverity.MEDIUM,
|
||||
package: "package_" + i,
|
||||
link: "https://security-tracker.debian.org/tracker/CVE-2016-4484",
|
||||
layer: "layer_" + i,
|
||||
version: '4.' + i + ".0",
|
||||
fixedVersion: '4.' + i + '.11',
|
||||
version: "4." + i + ".0",
|
||||
fixedVersion: "4." + i + ".11",
|
||||
description: "Mock data"
|
||||
};
|
||||
mockData.push(res);
|
||||
}
|
||||
scanningService = fixture.debugElement.injector.get(ScanningResultService);
|
||||
vulSpy = spyOn(scanningService, 'getVulnerabilityScanningResults').and.returnValue(Promise.resolve(mockData));
|
||||
vulSpy = spyOn(
|
||||
scanningService,
|
||||
"getVulnerabilityScanningResults"
|
||||
).and.returnValue(Promise.resolve(mockData));
|
||||
manifestSpy = spyOn(tagService, "getManifest").and.returnValues(
|
||||
Promise.resolve(mockManifest)
|
||||
);
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should load data', async(() => {
|
||||
it("should load data", async(() => {
|
||||
expect(spy.calls.any).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should rightly display tag name and version', async(() => {
|
||||
it("should load history data", async(() => {
|
||||
expect(manifestSpy.calls.any).toBeTruthy();
|
||||
}));
|
||||
|
||||
it("should rightly display tag name and version", async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.custom-h2');
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector(".custom-h2");
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent.trim()).toEqual('mock_repo:nginx');
|
||||
expect(el.textContent.trim()).toEqual("mock_repo:nginx");
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display tag details', async(() => {
|
||||
it("should display tag details", async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.image-detail-label .image-details');
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector(
|
||||
".image-detail-label .image-details"
|
||||
);
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent).toEqual("steven");
|
||||
});
|
||||
}));
|
||||
|
||||
it("should render history info", async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let els: HTMLElement[] = fixture.nativeElement.querySelectorAll(
|
||||
".history-item"
|
||||
);
|
||||
expect(els).toBeTruthy();
|
||||
expect(els.length).toBe(2);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
@ -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 { toPromise } from '../utils';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import {Label} from "../service/interface";
|
||||
import { TagService, Tag, VulnerabilitySeverity } from "../service/index";
|
||||
import { toPromise } from "../utils";
|
||||
import { ErrorHandler } from "../error-handler/index";
|
||||
import { Label } from "../service/interface";
|
||||
|
||||
const TabLinkContentMap: { [index: string]: string } = {
|
||||
"tag-history": "history",
|
||||
"tag-vulnerability": "vulnerability"
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-tag-detail',
|
||||
templateUrl: './tag-detail.component.html',
|
||||
styleUrls: ['./tag-detail.component.scss'],
|
||||
selector: "hbr-tag-detail",
|
||||
templateUrl: "./tag-detail.component.html",
|
||||
styleUrls: ["./tag-detail.component.scss"],
|
||||
|
||||
providers: []
|
||||
providers: []
|
||||
})
|
||||
export class TagDetailComponent implements OnInit {
|
||||
_highCount: number = 0;
|
||||
_mediumCount: number = 0;
|
||||
_lowCount: number = 0;
|
||||
_unknownCount: number = 0;
|
||||
labels: Label;
|
||||
_highCount: number = 0;
|
||||
_mediumCount: number = 0;
|
||||
_lowCount: number = 0;
|
||||
_unknownCount: number = 0;
|
||||
labels: Label;
|
||||
|
||||
@Input() tagId: string;
|
||||
@Input() repositoryId: string;
|
||||
@Input() withAdmiral: boolean;
|
||||
@Input() withClair: boolean;
|
||||
@Input() withAdminRole: boolean;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
size: "--",
|
||||
author: "--",
|
||||
created: new Date(),
|
||||
architecture: "--",
|
||||
os: "--",
|
||||
docker_version: "--",
|
||||
digest: "--",
|
||||
labels: [],
|
||||
};
|
||||
@Input()
|
||||
tagId: string;
|
||||
@Input()
|
||||
repositoryId: string;
|
||||
@Input()
|
||||
withAdmiral: boolean;
|
||||
@Input()
|
||||
withClair: boolean;
|
||||
@Input()
|
||||
withAdminRole: boolean;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
size: "--",
|
||||
author: "--",
|
||||
created: new Date(),
|
||||
architecture: "--",
|
||||
os: "--",
|
||||
docker_version: "--",
|
||||
digest: "--",
|
||||
labels: []
|
||||
};
|
||||
|
||||
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output()
|
||||
backEvt: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
private tagService: TagService,
|
||||
private errorHandler: ErrorHandler) { }
|
||||
currentTabID = "tag-vulnerability";
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.repositoryId && this.tagId) {
|
||||
toPromise<Tag>(this.tagService.getTag(this.repositoryId, this.tagId))
|
||||
.then(response => {
|
||||
this.tagDetails = response;
|
||||
if (this.tagDetails &&
|
||||
this.tagDetails.scan_overview &&
|
||||
this.tagDetails.scan_overview.components &&
|
||||
this.tagDetails.scan_overview.components.summary) {
|
||||
this.tagDetails.scan_overview.components.summary.forEach(item => {
|
||||
switch (item.severity) {
|
||||
case VulnerabilitySeverity.UNKNOWN:
|
||||
this._unknownCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.LOW:
|
||||
this._lowCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
this._mediumCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
this._highCount += item.count;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
constructor(
|
||||
private tagService: TagService,
|
||||
private errorHandler: ErrorHandler
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.repositoryId && this.tagId) {
|
||||
toPromise<Tag>(this.tagService.getTag(this.repositoryId, this.tagId))
|
||||
.then(response => {
|
||||
this.tagDetails = response;
|
||||
if (
|
||||
this.tagDetails &&
|
||||
this.tagDetails.scan_overview &&
|
||||
this.tagDetails.scan_overview.components &&
|
||||
this.tagDetails.scan_overview.components.summary
|
||||
) {
|
||||
this.tagDetails.scan_overview.components.summary.forEach(item => {
|
||||
switch (item.severity) {
|
||||
case VulnerabilitySeverity.UNKNOWN:
|
||||
this._unknownCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.LOW:
|
||||
this._lowCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
this._mediumCount += item.count;
|
||||
break;
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
this._highCount += item.count;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
onBack(): void {
|
||||
this.backEvt.emit(this.repositoryId);
|
||||
}
|
||||
onBack(): void {
|
||||
this.backEvt.emit(this.repositoryId);
|
||||
}
|
||||
|
||||
getPackageText(count: number): string {
|
||||
return count > 1 ? "VULNERABILITY.PACKAGES" : "VULNERABILITY.PACKAGE";
|
||||
}
|
||||
getPackageText(count: number): string {
|
||||
return count > 1 ? "VULNERABILITY.PACKAGES" : "VULNERABILITY.PACKAGE";
|
||||
}
|
||||
|
||||
packageText(count: number): string {
|
||||
return count > 1 ? "VULNERABILITY.GRID.COLUMN_PACKAGES" : "VULNERABILITY.GRID.COLUMN_PACKAGE";
|
||||
}
|
||||
packageText(count: number): string {
|
||||
return count > 1
|
||||
? "VULNERABILITY.GRID.COLUMN_PACKAGES"
|
||||
: "VULNERABILITY.GRID.COLUMN_PACKAGE";
|
||||
}
|
||||
|
||||
haveText(count: number): string {
|
||||
return count > 1 ? "TAG.HAVE" : "TAG.HAS";
|
||||
}
|
||||
haveText(count: number): string {
|
||||
return count > 1 ? "TAG.HAVE" : "TAG.HAS";
|
||||
}
|
||||
|
||||
public get author(): string {
|
||||
return this.tagDetails && this.tagDetails.author ? this.tagDetails.author : 'TAG.ANONYMITY';
|
||||
}
|
||||
public get author(): string {
|
||||
return this.tagDetails && this.tagDetails.author
|
||||
? this.tagDetails.author
|
||||
: "TAG.ANONYMITY";
|
||||
}
|
||||
|
||||
public get highCount(): number {
|
||||
return this._highCount;
|
||||
}
|
||||
public get highCount(): number {
|
||||
return this._highCount;
|
||||
}
|
||||
|
||||
public get mediumCount(): number {
|
||||
return this._mediumCount;
|
||||
}
|
||||
public get mediumCount(): number {
|
||||
return this._mediumCount;
|
||||
}
|
||||
|
||||
public get lowCount(): number {
|
||||
return this._lowCount;
|
||||
}
|
||||
public get lowCount(): number {
|
||||
return this._lowCount;
|
||||
}
|
||||
|
||||
public get unknownCount(): number {
|
||||
return this._unknownCount;
|
||||
}
|
||||
public get unknownCount(): number {
|
||||
return this._unknownCount;
|
||||
}
|
||||
|
||||
public get scanCompletedDatetime(): Date {
|
||||
return this.tagDetails && this.tagDetails.scan_overview ?
|
||||
this.tagDetails.scan_overview.update_time : null;
|
||||
}
|
||||
public get scanCompletedDatetime(): Date {
|
||||
return this.tagDetails && this.tagDetails.scan_overview
|
||||
? this.tagDetails.scan_overview.update_time
|
||||
: null;
|
||||
}
|
||||
|
||||
public get suffixForHigh(): string {
|
||||
return this.highCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
public get suffixForHigh(): string {
|
||||
return this.highCount > 1
|
||||
? "VULNERABILITY.PLURAL"
|
||||
: "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForMedium(): string {
|
||||
return this.mediumCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
public get suffixForMedium(): string {
|
||||
return this.mediumCount > 1
|
||||
? "VULNERABILITY.PLURAL"
|
||||
: "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForLow(): string {
|
||||
return this.lowCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
public get suffixForLow(): string {
|
||||
return this.lowCount > 1
|
||||
? "VULNERABILITY.PLURAL"
|
||||
: "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForUnknown(): string {
|
||||
return this.unknownCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
public get suffixForUnknown(): string {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
11
src/portal/lib/src/tag/tag-history.component.html
Normal file
11
src/portal/lib/src/tag/tag-history.component.html
Normal file
@ -0,0 +1,11 @@
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column class="history-time">{{ '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>
|
3
src/portal/lib/src/tag/tag-history.component.scss
Normal file
3
src/portal/lib/src/tag/tag-history.component.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.history-time {
|
||||
width: 160px;
|
||||
}
|
66
src/portal/lib/src/tag/tag-history.component.ts
Normal file
66
src/portal/lib/src/tag/tag-history.component.ts
Normal file
@ -0,0 +1,66 @@
|
||||
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",
|
||||
styleUrls: ["./tag-history.component.scss"],
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
"npm_pack": "cd lib/dist && npm pack",
|
||||
"package": "npm run build_lib && npm run npm_pack",
|
||||
"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,
|
||||
"dependencies": {
|
||||
|
@ -379,15 +379,15 @@
|
||||
"IMMEDIATE": "Immediate",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING":"Options",
|
||||
"TRIGGER":"Triggering Condition",
|
||||
"TARGETS":"Target",
|
||||
"SETTING": "Options",
|
||||
"TRIGGER": "Triggering Condition",
|
||||
"TARGETS": "Target",
|
||||
"MODE": "Mode",
|
||||
"TRIGGER_MODE": "Trigger Mode",
|
||||
"SOURCE_PROJECT": "Source project",
|
||||
"REPLICATE": "Replicate",
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||
"DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
|
||||
"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.",
|
||||
"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",
|
||||
"SIZE": "Size",
|
||||
"VULNERABILITY": "Vulnerability",
|
||||
"BUILD_HISTORY": "Build History",
|
||||
"SIGNED": "Signed",
|
||||
"AUTHOR": "Author",
|
||||
"CREATED": "Creation Time",
|
||||
@ -753,7 +754,9 @@
|
||||
"COPY_ERROR": "Copy failed, please try to manually copy.",
|
||||
"FILTER_FOR_TAGS": "Filter Tags",
|
||||
"AUTHOR": "Author",
|
||||
"LABELS": "Labels"
|
||||
"LABELS": "Labels",
|
||||
"CREATION": "Create on",
|
||||
"COMMAND": "Commands"
|
||||
},
|
||||
"LABEL": {
|
||||
"LABEL": "Label",
|
||||
@ -818,15 +821,15 @@
|
||||
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
|
||||
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS":"Status",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Start Time",
|
||||
"UPDATE_TIME": "Update Time",
|
||||
"LOGS":"Logs",
|
||||
"PENDING":"Pending",
|
||||
"FINISHED":"Finished",
|
||||
"STOPPED":"Stopped",
|
||||
"RUNNING":"Running",
|
||||
"ERROR":"Error",
|
||||
"LOGS": "Logs",
|
||||
"PENDING": "Pending",
|
||||
"FINISHED": "Finished",
|
||||
"STOPPED": "Stopped",
|
||||
"RUNNING": "Running",
|
||||
"ERROR": "Error",
|
||||
"SCHEDULE": {
|
||||
"NONE": "None",
|
||||
"DAILY": "Daily",
|
||||
@ -834,17 +837,17 @@
|
||||
"MANUAL": "Manual",
|
||||
"ON": "on",
|
||||
"AT": "at"
|
||||
},
|
||||
},
|
||||
"GC": {
|
||||
"CURRENT_SCHEDULE": "Current Schedule",
|
||||
"GC_NOW": "GC NOW",
|
||||
"JOB_HISTORY":"GC Jobs History",
|
||||
"JOB_ID":"Job ID",
|
||||
"JOB_HISTORY": "GC Jobs History",
|
||||
"JOB_ID": "Job ID",
|
||||
"TRIGGER_TYPE": "Trigger Type",
|
||||
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset"
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -378,15 +378,15 @@
|
||||
"IMMEDIATE": "Immediate",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING":"Options",
|
||||
"TRIGGER":"Triggering Condition",
|
||||
"TARGETS":"Target",
|
||||
"SETTING": "Options",
|
||||
"TRIGGER": "Triggering Condition",
|
||||
"TARGETS": "Target",
|
||||
"MODE": "Mode",
|
||||
"TRIGGER_MODE": "Trigger Mode",
|
||||
"SOURCE_PROJECT": "Source project",
|
||||
"REPLICATE": "Replicate",
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||
"DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
|
||||
"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.",
|
||||
"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",
|
||||
"SIZE": "Size",
|
||||
"VULNERABILITY": "Vulnerability",
|
||||
"BUILD_HISTORY": "Construir Historia",
|
||||
"SIGNED": "Firmada",
|
||||
"AUTHOR": "Autor",
|
||||
"CREATED": "Fecha de creación",
|
||||
@ -752,7 +753,10 @@
|
||||
"COPY_ERROR": "Copy failed, please try to manually copy.",
|
||||
"FILTER_FOR_TAGS": "Etiquetas de filtro",
|
||||
"AUTHOR": "Author",
|
||||
"LABELS": "Labels"
|
||||
"LABELS": "Labels",
|
||||
"CREATION": "Tiempo de creación",
|
||||
"COMMAND": "Mando"
|
||||
|
||||
},
|
||||
"LABEL": {
|
||||
"LABEL": "Label",
|
||||
@ -771,13 +775,13 @@
|
||||
"NAME_ALREADY_EXISTS": "Label name already exists."
|
||||
},
|
||||
"WEEKLY": {
|
||||
"MONDAY": "Monday",
|
||||
"TUESDAY": "Tuesday",
|
||||
"WEDNESDAY": "Wednesday",
|
||||
"THURSDAY": "Thursday",
|
||||
"FRIDAY": "Friday",
|
||||
"SATURDAY": "Saturday",
|
||||
"SUNDAY": "Sunday"
|
||||
"MONDAY": "Monday",
|
||||
"TUESDAY": "Tuesday",
|
||||
"WEDNESDAY": "Wednesday",
|
||||
"THURSDAY": "Thursday",
|
||||
"FRIDAY": "Friday",
|
||||
"SATURDAY": "Saturday",
|
||||
"SUNDAY": "Sunday"
|
||||
},
|
||||
"OPERATION": {
|
||||
"LOCAL_EVENT": "Local Events",
|
||||
@ -815,15 +819,15 @@
|
||||
"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.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS":"Status",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Start Time",
|
||||
"UPDATE_TIME": "Update Time",
|
||||
"LOGS":"Logs",
|
||||
"PENDING":"Pending",
|
||||
"FINISHED":"Finished",
|
||||
"STOPPED":"Stopped",
|
||||
"RUNNING":"Running",
|
||||
"ERROR":"Error",
|
||||
"LOGS": "Logs",
|
||||
"PENDING": "Pending",
|
||||
"FINISHED": "Finished",
|
||||
"STOPPED": "Stopped",
|
||||
"RUNNING": "Running",
|
||||
"ERROR": "Error",
|
||||
"SCHEDULE": {
|
||||
"NONE": "None",
|
||||
"DAILY": "Daily",
|
||||
@ -831,16 +835,16 @@
|
||||
"MANUAL": "Manual",
|
||||
"ON": "on",
|
||||
"AT": "at"
|
||||
},
|
||||
},
|
||||
"GC": {
|
||||
"CURRENT_SCHEDULE": "Current Schedule",
|
||||
"GC_NOW": "GC NOW",
|
||||
"JOB_HISTORY":"GC Jobs History",
|
||||
"JOB_ID":"Job ID",
|
||||
"JOB_HISTORY": "GC Jobs History",
|
||||
"JOB_ID": "Job ID",
|
||||
"TRIGGER_TYPE": "Trigger Type",
|
||||
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset"
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,15 +359,15 @@
|
||||
"IMMEDIATE": "Immediate",
|
||||
"DAILY": "Daily",
|
||||
"WEEKLY": "Weekly",
|
||||
"SETTING":"Options",
|
||||
"TRIGGER":"Triggering Condition",
|
||||
"TARGETS":"Target",
|
||||
"SETTING": "Options",
|
||||
"TRIGGER": "Triggering Condition",
|
||||
"TARGETS": "Target",
|
||||
"MODE": "Mode",
|
||||
"TRIGGER_MODE": "Trigger Mode",
|
||||
"SOURCE_PROJECT": "Source project",
|
||||
"REPLICATE": "Replicate",
|
||||
"DELETE_REMOTE_IMAGES":"Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE":"Replicate existing images immediately",
|
||||
"DELETE_REMOTE_IMAGES": "Delete remote images when locally deleted",
|
||||
"REPLICATE_IMMEDIATE": "Replicate existing images immediately",
|
||||
"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.",
|
||||
"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",
|
||||
"SIZE": "Taille",
|
||||
"VULNERABILITY": "Vulnérabilitée",
|
||||
"BUILD_HISTORY": "Construire l'histoire",
|
||||
"SIGNED": "Signé",
|
||||
"AUTHOR": "Auteur",
|
||||
"CREATED": "Heure de Création",
|
||||
@ -716,7 +717,9 @@
|
||||
"COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement.",
|
||||
"FILTER_FOR_TAGS": "Filter Tags",
|
||||
"AUTHOR": "Author",
|
||||
"LABELS": "Labels"
|
||||
"LABELS": "Labels",
|
||||
"CREATION": "Créer sur",
|
||||
"COMMAND": "Les commandes"
|
||||
},
|
||||
"LABEL": {
|
||||
"LABEL": "Label",
|
||||
@ -779,15 +782,15 @@
|
||||
"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.",
|
||||
"UNKNOWN": "n. d.",
|
||||
"STATUS":"Status",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Start Time",
|
||||
"UPDATE_TIME": "Update Time",
|
||||
"LOGS":"Logs",
|
||||
"PENDING":"Pending",
|
||||
"FINISHED":"Finished",
|
||||
"STOPPED":"Stopped",
|
||||
"RUNNING":"Running",
|
||||
"ERROR":"Error",
|
||||
"LOGS": "Logs",
|
||||
"PENDING": "Pending",
|
||||
"FINISHED": "Finished",
|
||||
"STOPPED": "Stopped",
|
||||
"RUNNING": "Running",
|
||||
"ERROR": "Error",
|
||||
"SCHEDULE": {
|
||||
"NONE": "None",
|
||||
"DAILY": "Daily",
|
||||
@ -795,16 +798,16 @@
|
||||
"MANUAL": "Manual",
|
||||
"ON": "on",
|
||||
"AT": "at"
|
||||
},
|
||||
},
|
||||
"GC": {
|
||||
"CURRENT_SCHEDULE": "Current Schedule",
|
||||
"GC_NOW": "GC NOW",
|
||||
"JOB_HISTORY":"GC Jobs History",
|
||||
"JOB_ID":"Job ID",
|
||||
"JOB_HISTORY": "GC Jobs History",
|
||||
"JOB_ID": "Job ID",
|
||||
"TRIGGER_TYPE": "Trigger Type",
|
||||
"LATEST_JOBS": "Latest {{param}} Jobs",
|
||||
"MSG_SUCCESS":"Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset"
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
}
|
||||
}
|
||||
|
@ -378,15 +378,15 @@
|
||||
"IMMEDIATE": "即刻",
|
||||
"DAILY": "每天",
|
||||
"WEEKLY": "每周",
|
||||
"SETTING":"设置",
|
||||
"TRIGGER":"触发条件",
|
||||
"TARGETS":"目标",
|
||||
"SETTING": "设置",
|
||||
"TRIGGER": "触发条件",
|
||||
"TARGETS": "目标",
|
||||
"MODE": "模式",
|
||||
"TRIGGER_MODE": "触发模式",
|
||||
"SOURCE_PROJECT": "源项目",
|
||||
"REPLICATE": "复制",
|
||||
"DELETE_REMOTE_IMAGES":"删除本地镜像时同时也删除远程的镜像。",
|
||||
"REPLICATE_IMMEDIATE":"立即复制现有的镜像。",
|
||||
"DELETE_REMOTE_IMAGES": "删除本地镜像时同时也删除远程的镜像。",
|
||||
"REPLICATE_IMMEDIATE": "立即复制现有的镜像。",
|
||||
"NEW": "新增",
|
||||
"NAME_TOOLTIP": "项目名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。",
|
||||
"DELETED_LABEL_INFO": "过滤项有被删除的标签 {{param}} , 点击保存按钮更新过滤项使规则可用。",
|
||||
@ -446,6 +446,7 @@
|
||||
"TAG": "标签",
|
||||
"SIZE": "大小",
|
||||
"VULNERABILITY": "漏洞",
|
||||
"BUILD_HISTORY": "构建历史",
|
||||
"SIGNED": "已签名",
|
||||
"AUTHOR": "作者",
|
||||
"CREATED": "创建时间",
|
||||
@ -751,7 +752,9 @@
|
||||
"COPY_ERROR": "拷贝失败,请尝试手动拷贝。",
|
||||
"FILTER_FOR_TAGS": "过滤项目",
|
||||
"AUTHOR": "作者",
|
||||
"LABELS": "标签"
|
||||
"LABELS": "标签",
|
||||
"CREATION": "创建时间",
|
||||
"COMMAND": "命令"
|
||||
},
|
||||
"LABEL": {
|
||||
"LABEL": "标签",
|
||||
@ -764,7 +767,7 @@
|
||||
"COLOR": "颜色",
|
||||
"FILTER_Label_PLACEHOLDER": "过滤标签",
|
||||
"NO_LABELS": "无标签",
|
||||
"DELETION_TITLE_TARGET":"删除标签确认",
|
||||
"DELETION_TITLE_TARGET": "删除标签确认",
|
||||
"DELETION_SUMMARY_TARGET": "确认删除标签 {{param}}?",
|
||||
"PLACEHOLDER": "未发现任何标签!",
|
||||
"NAME_ALREADY_EXISTS": "标签名已存在。"
|
||||
@ -814,15 +817,15 @@
|
||||
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
|
||||
"INCONRRECT_OLD_PWD": "旧密码不正确。",
|
||||
"UNKNOWN": "未知",
|
||||
"STATUS":"状态",
|
||||
"STATUS": "状态",
|
||||
"START_TIME": "创建时间",
|
||||
"UPDATE_TIME": "更新时间",
|
||||
"LOGS":"日志",
|
||||
"PENDING":"未开始",
|
||||
"FINISHED":"已完成",
|
||||
"STOPPED":"已停止",
|
||||
"RUNNING":"执行中",
|
||||
"ERROR":"错误",
|
||||
"LOGS": "日志",
|
||||
"PENDING": "未开始",
|
||||
"FINISHED": "已完成",
|
||||
"STOPPED": "已停止",
|
||||
"RUNNING": "执行中",
|
||||
"ERROR": "错误",
|
||||
"SCHEDULE": {
|
||||
"NONE": "无",
|
||||
"DAILY": "每天",
|
||||
@ -830,16 +833,16 @@
|
||||
"MANUAL": "手动",
|
||||
"ON": " ",
|
||||
"AT": " "
|
||||
},
|
||||
},
|
||||
"GC": {
|
||||
"CURRENT_SCHEDULE": "当前定时任务",
|
||||
"GC_NOW": "立即清理垃圾",
|
||||
"JOB_HISTORY":"历史任务",
|
||||
"JOB_ID":"任务ID",
|
||||
"JOB_HISTORY": "历史任务",
|
||||
"JOB_ID": "任务ID",
|
||||
"TRIGGER_TYPE": "触发类型",
|
||||
"LATEST_JOBS": "最新的 {{param}} 个任务",
|
||||
"MSG_SUCCESS":"垃圾回收成功",
|
||||
"MSG_SCHEDULE_SET":"垃圾回收定时任务设置成功",
|
||||
"MSG_SCHEDULE_RESET":"垃圾回收定时任务已被重置"
|
||||
"MSG_SUCCESS": "垃圾回收成功",
|
||||
"MSG_SCHEDULE_SET": "垃圾回收定时任务设置成功",
|
||||
"MSG_SCHEDULE_RESET": "垃圾回收定时任务已被重置"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user