Implement job log viewer

This commit is contained in:
Steven Zou 2017-07-18 17:47:05 +08:00
parent a9a79736b4
commit 9057f751e3
20 changed files with 373 additions and 160 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.2.0", "version": "0.3.0",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"scripts": { "scripts": {
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json", "start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.2.0", "version": "0.3.0",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"author": "VMware", "author": "VMware",
"module": "index.js", "module": "index.js",

View File

@ -4,7 +4,13 @@ export const VULNERABILITY_CONFIG_HTML: string = `
<label class="section-title" *ngIf="showSubTitle">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label> <label class="section-title" *ngIf="showSubTitle">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label>
<div class="form-group"> <div class="form-group">
<label>{{ 'CONFIG.SCANNING.DB_REFRESH_TIME' | translate }}</label> <label>{{ 'CONFIG.SCANNING.DB_REFRESH_TIME' | translate }}</label>
<clr-dropdown [clrMenuPosition]="'bottom-right'" style="margin-top:-8px;" class="clr-dropdown-override"> <clr-tooltip *ngIf="!isClairDBFullyReady" [clrTooltipDirection]="'top-right'" [clrTooltipSize]="'md'">
<clr-icon shape="warning" class="is-warning" size="22"></clr-icon>
<clr-tooltip-content>
<span>{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}</span>
</clr-tooltip-content>
</clr-tooltip>
<clr-dropdown *ngIf="isClairDBFullyReady" [clrMenuPosition]="'bottom-right'" style="margin-top:-8px;" class="clr-dropdown-override">
<button class="btn btn-link btn-font" clrDropdownToggle> <button class="btn btn-link btn-font" clrDropdownToggle>
{{ updatedTimestamp }} {{ updatedTimestamp }}
<clr-icon shape="caret down"></clr-icon> <clr-icon shape="caret down"></clr-icon>
@ -23,7 +29,6 @@ export const VULNERABILITY_CONFIG_HTML: string = `
<select id="scanAllPolicy" name="scanAllPolicy" [disabled]="!editable" [(ngModel)]="scanningType"> <select id="scanAllPolicy" name="scanAllPolicy" [disabled]="!editable" [(ngModel)]="scanningType">
<option value="none">{{ 'CONFIG.SCANNING.NONE_POLICY' | translate }}</option> <option value="none">{{ 'CONFIG.SCANNING.NONE_POLICY' | translate }}</option>
<option value="daily">{{ 'CONFIG.SCANNING.DAILY_POLICY' | translate }}</option> <option value="daily">{{ 'CONFIG.SCANNING.DAILY_POLICY' | translate }}</option>
<option value="on_refresh">{{ 'CONFIG.SCANNING.REFRESH_POLICY' | translate }}</option>
</select> </select>
</div> </div>
<input type="time" name="dailyTimePicker" [disabled]="!editable" [hidden]="!showTimePicker" [(ngModel)]="dailyTime" /> <input type="time" name="dailyTimePicker" [disabled]="!editable" [hidden]="!showTimePicker" [(ngModel)]="dailyTime" />

View File

@ -206,6 +206,10 @@ export class VulnerabilityConfigComponent {
this.vulnerabilityConfig.scan_all_policy.value.type === "daily"; this.vulnerabilityConfig.scan_all_policy.value.type === "daily";
} }
get isClairDBFullyReady(): boolean {
return this.clairDBStatus && this.clairDBStatus.overall_last_update > 0;
}
constructor( constructor(
private scanningService: ScanningResultService, private scanningService: ScanningResultService,
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,

View File

@ -20,6 +20,7 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
describe('CreateEditRuleComponent (inline template)', ()=>{ describe('CreateEditRuleComponent (inline template)', ()=>{
@ -175,7 +176,8 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
ConfirmationDialogComponent, ConfirmationDialogComponent,
DatePickerComponent, DatePickerComponent,
FilterComponent, FilterComponent,
InlineAlertComponent InlineAlertComponent,
JobLogViewerComponent
], ],
providers: [ providers: [
ErrorHandler, ErrorHandler,

View File

@ -23,6 +23,7 @@ import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index'; import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from './push-image/index'; import { PUSH_IMAGE_BUTTON_DIRECTIVES } from './push-image/index';
import { CONFIGURATION_DIRECTIVES } from './config/index'; import { CONFIGURATION_DIRECTIVES } from './config/index';
import { JOB_LOG_VIEWER_DIRECTIVES } from './job-log-viewer/index';
import { import {
SystemInfoService, SystemInfoService,
@ -153,7 +154,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
DATETIME_PICKER_DIRECTIVES, DATETIME_PICKER_DIRECTIVES,
VULNERABILITY_DIRECTIVES, VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES, PUSH_IMAGE_BUTTON_DIRECTIVES,
CONFIGURATION_DIRECTIVES CONFIGURATION_DIRECTIVES,
JOB_LOG_VIEWER_DIRECTIVES
], ],
exports: [ exports: [
LOG_DIRECTIVES, LOG_DIRECTIVES,
@ -173,6 +175,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
VULNERABILITY_DIRECTIVES, VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES, PUSH_IMAGE_BUTTON_DIRECTIVES,
CONFIGURATION_DIRECTIVES, CONFIGURATION_DIRECTIVES,
JOB_LOG_VIEWER_DIRECTIVES,
TranslateModule TranslateModule
], ],
providers: [] providers: []

View File

@ -16,3 +16,4 @@ export * from './i18n/index';
export * from './push-image/index'; export * from './push-image/index';
export * from './third-party/index'; export * from './third-party/index';
export * from './config/index'; export * from './config/index';
export * from './job-log-viewer/index';

View File

@ -0,0 +1,9 @@
import { Type } from '@angular/core';
import { JobLogViewerComponent } from './job-log-viewer.component';
export * from './job-log-viewer.component';
export const JOB_LOG_VIEWER_DIRECTIVES: Type<any>[] = [
JobLogViewerComponent
];

View File

@ -0,0 +1,58 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DebugElement } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ReplicationService, ReplicationDefaultService } from '../service/index';
import { JobLogViewerComponent } from './job-log-viewer.component';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ErrorHandler } from '../error-handler/index';
import { SharedModule } from '../shared/shared.module';
describe('JobLogViewerComponent (inline template)', () => {
let component: JobLogViewerComponent;
let fixture: ComponentFixture<JobLogViewerComponent>;
let serviceConfig: IServiceConfig;
let replicationService: ReplicationService;
let spy: jasmine.Spy;
let testConfig: IServiceConfig = {
replicationJobEndpoint: "/api/jobs/replication/testing"
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
BrowserAnimationsModule
],
declarations: [JobLogViewerComponent],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: testConfig },
{ provide: ReplicationService, useClass: ReplicationDefaultService }
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(JobLogViewerComponent);
component = fixture.componentInstance;
serviceConfig = TestBed.get(SERVICE_CONFIG);
replicationService = fixture.debugElement.injector.get(ReplicationService);
spy = spyOn(replicationService, 'getJobLog')
.and.returnValues(Promise.resolve("job log text"));
fixture.detectChanges();
});
it('should be created', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
expect(serviceConfig).toBeTruthy();
expect(serviceConfig.replicationJobEndpoint).toEqual("/api/jobs/replication/testing");
component.open(16);
});
});

View File

@ -0,0 +1,33 @@
export const JOB_LOG_VIEWER_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalSize]="'xl'">
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{'REPLICATION.JOB_LOG_VIEWER' | translate }}</h3>
<div class="modal-body">
<div class="loading-back" [hidden]="!onGoing">
<span class="spinner spinner-md"></span>
</div>
<pre [hidden]="onGoing">
<code>
{{log}}
</code>
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="close()">{{ 'BUTTON.CLOSE' | translate}}</button>
</div>
</clr-modal>
`;
export const JOB_LOG_VIEWER_STYLES: string = `
.log-viewer-title {
line-height: 24px;
color: #000000;
font-size: 22px;
}
.loading-back {
height: 358px;
display: flex;
align-items: center;
justify-content: center;
}
`;

View File

@ -0,0 +1,60 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { JOB_LOG_VIEWER_TEMPLATE, JOB_LOG_VIEWER_STYLES } from './job-log-viewer.component.template';
import { ReplicationService } from '../service/index';
import { ErrorHandler } from '../error-handler/index';
import { toPromise } from '../utils';
@Component({
selector: 'job-log-viewer',
template: JOB_LOG_VIEWER_TEMPLATE,
styles: [JOB_LOG_VIEWER_STYLES]
})
export class JobLogViewerComponent {
opened: boolean = false;
log: string = '';
onGoing: boolean = true;
constructor(
private replicationService: ReplicationService,
private errorHandler: ErrorHandler
) { }
open(jobId: number | string): void {
this.opened = true;
this.load(jobId);
}
close(): void {
this.opened = false;
this.log = "";
}
load(jobId: number | string): void {
this.onGoing = true;
toPromise<string>(this.replicationService.getJobLog(jobId))
.then((log: string) => {
this.onGoing = false;
this.log = log;
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
});
}
}

View File

@ -61,7 +61,7 @@ export const REPLICATION_TEMPLATE: string = `
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell> <clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell> <clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell> <clr-dg-cell>
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK"> <a href="javascript:void(0);" (click)="viewLog(j.id)">
<clr-icon shape="clipboard"></clr-icon> <clr-icon shape="clipboard"></clr-icon>
</a> </a>
</clr-dg-cell> </clr-dg-cell>
@ -73,4 +73,5 @@ export const REPLICATION_TEMPLATE: string = `
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div> </div>
<job-log-viewer #replicationLogViewer></job-log-viewer>
</div>`; </div>`;

View File

@ -18,6 +18,7 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service'; import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service'; import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
describe('Replication Component (inline template)', ()=>{ describe('Replication Component (inline template)', ()=>{
@ -175,7 +176,8 @@ describe('Replication Component (inline template)', ()=>{
ConfirmationDialogComponent, ConfirmationDialogComponent,
DatePickerComponent, DatePickerComponent,
FilterComponent, FilterComponent,
InlineAlertComponent InlineAlertComponent,
JobLogViewerComponent
], ],
providers: [ providers: [
ErrorHandler, ErrorHandler,

View File

@ -17,7 +17,7 @@ import { NgModel } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ListReplicationRuleComponent} from '../list-replication-rule/list-replication-rule.component'; import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component';
import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component'; import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
@ -32,24 +32,26 @@ import { Comparator } from 'clarity-angular';
import { REPLICATION_TEMPLATE } from './replication.component.html'; import { REPLICATION_TEMPLATE } from './replication.component.html';
import { REPLICATION_STYLE } from './replication.component.css'; import { REPLICATION_STYLE } from './replication.component.css';
const ruleStatus: {[key: string]: any} = [ import { JobLogViewerComponent } from '../job-log-viewer/index';
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS'},
{ 'key': '1', 'description': 'REPLICATION.ENABLED'}, const ruleStatus: { [key: string]: any } = [
{ 'key': '0', 'description': 'REPLICATION.DISABLED'} { 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
{ 'key': '1', 'description': 'REPLICATION.ENABLED' },
{ 'key': '0', 'description': 'REPLICATION.DISABLED' }
]; ];
const jobStatus: {[key: string]: any} = [ const jobStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL' }, { 'key': 'all', 'description': 'REPLICATION.ALL' },
{ 'key': 'pending', 'description': 'REPLICATION.PENDING' }, { 'key': 'pending', 'description': 'REPLICATION.PENDING' },
{ 'key': 'running', 'description': 'REPLICATION.RUNNING' }, { 'key': 'running', 'description': 'REPLICATION.RUNNING' },
{ 'key': 'error', 'description': 'REPLICATION.ERROR' }, { 'key': 'error', 'description': 'REPLICATION.ERROR' },
{ 'key': 'retrying', 'description': 'REPLICATION.RETRYING' }, { 'key': 'retrying', 'description': 'REPLICATION.RETRYING' },
{ 'key': 'stopped' , 'description': 'REPLICATION.STOPPED' }, { 'key': 'stopped', 'description': 'REPLICATION.STOPPED' },
{ 'key': 'finished', 'description': 'REPLICATION.FINISHED' }, { 'key': 'finished', 'description': 'REPLICATION.FINISHED' },
{ 'key': 'canceled', 'description': 'REPLICATION.CANCELED' } { 'key': 'canceled', 'description': 'REPLICATION.CANCELED' }
]; ];
const optionalSearch: {} = {0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE'}; const optionalSearch: {} = { 0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE' };
export class SearchOption { export class SearchOption {
ruleId: number | string; ruleId: number | string;
@ -67,7 +69,7 @@ export class SearchOption {
@Component({ @Component({
selector: 'hbr-replication', selector: 'hbr-replication',
template: REPLICATION_TEMPLATE, template: REPLICATION_TEMPLATE,
styles: [ REPLICATION_STYLE ] styles: [REPLICATION_STYLE]
}) })
export class ReplicationComponent implements OnInit { export class ReplicationComponent implements OnInit {
@ -79,10 +81,10 @@ export class ReplicationComponent implements OnInit {
search: SearchOption = new SearchOption(); search: SearchOption = new SearchOption();
ruleStatus = ruleStatus; ruleStatus = ruleStatus;
currentRuleStatus: {key: string, description: string}; currentRuleStatus: { key: string, description: string };
jobStatus = jobStatus; jobStatus = jobStatus;
currentJobStatus: {key: string, description: string}; currentJobStatus: { key: string, description: string };
changedRules: ReplicationRule[]; changedRules: ReplicationRule[];
initSelectedId: number | string; initSelectedId: number | string;
@ -104,6 +106,9 @@ export class ReplicationComponent implements OnInit {
@ViewChild(CreateEditRuleComponent) @ViewChild(CreateEditRuleComponent)
createEditPolicyComponent: CreateEditRuleComponent; createEditPolicyComponent: CreateEditRuleComponent;
@ViewChild("replicationLogViewer")
replicationLogViewer: JobLogViewerComponent;
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date'); creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date'); updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date');
@ -124,9 +129,9 @@ export class ReplicationComponent implements OnInit {
} }
openEditRule(rule: ReplicationRule) { openEditRule(rule: ReplicationRule) {
if(rule) { if (rule) {
let editable = true; let editable = true;
if(rule.enabled === 1) { if (rule.enabled === 1) {
editable = false; editable = false;
} }
this.createEditPolicyComponent.openCreateEditRule(editable, rule.id); this.createEditPolicyComponent.openCreateEditRule(editable, rule.id);
@ -144,9 +149,9 @@ export class ReplicationComponent implements OnInit {
toPromise<ReplicationJob[]>(this.replicationService toPromise<ReplicationJob[]>(this.replicationService
.getJobs(this.search.ruleId, params)) .getJobs(this.search.ruleId, params))
.then( .then(
response=>{ response => {
this.jobs = response; this.jobs = response;
}).catch(error=>{ }).catch(error => {
this.errorHandler.error(error); this.errorHandler.error(error);
}); });
} }
@ -174,7 +179,7 @@ export class ReplicationComponent implements OnInit {
doFilterRuleStatus($event: any) { doFilterRuleStatus($event: any) {
if ($event && $event.target && $event.target["value"]) { if ($event && $event.target && $event.target["value"]) {
let status = $event.target["value"]; let status = $event.target["value"];
this.currentRuleStatus = this.ruleStatus.find((r: any)=>r.key === status); this.currentRuleStatus = this.ruleStatus.find((r: any) => r.key === status);
this.listReplicationRule.filterRuleStatus(this.currentRuleStatus.key); this.listReplicationRule.filterRuleStatus(this.currentRuleStatus.key);
} }
} }
@ -183,8 +188,8 @@ export class ReplicationComponent implements OnInit {
if ($event && $event.target && $event.target["value"]) { if ($event && $event.target && $event.target["value"]) {
let status = $event.target["value"]; let status = $event.target["value"];
this.currentJobStatus = this.jobStatus.find((r: any)=>r.key === status); this.currentJobStatus = this.jobStatus.find((r: any) => r.key === status);
if(this.currentJobStatus.key === 'all') { if (this.currentJobStatus.key === 'all') {
status = ''; status = '';
} }
this.search.status = status; this.search.status = status;
@ -199,7 +204,7 @@ export class ReplicationComponent implements OnInit {
} }
reloadRules(isReady: boolean) { reloadRules(isReady: boolean) {
if(isReady) { if (isReady) {
this.search.ruleName = ''; this.search.ruleName = '';
this.listReplicationRule.retrieveRules(this.search.ruleName); this.listReplicationRule.retrieveRules(this.search.ruleName);
} }
@ -226,4 +231,10 @@ export class ReplicationComponent implements OnInit {
this.search.endTimestamp = toTimestamp; this.search.endTimestamp = toTimestamp;
this.fetchReplicationJobs(); this.fetchReplicationJobs();
} }
viewLog(jobId: number | string): void {
if (this.replicationLogViewer) {
this.replicationLogViewer.open(jobId);
}
}
} }

View File

@ -114,6 +114,16 @@ export abstract class ReplicationService {
* @memberOf ReplicationService * @memberOf ReplicationService
*/ */
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[]; abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[];
/**
* Get the log of the specified job.
*
* @abstract
* @param {(number | string)} jobId
* @returns {(Observable<string> | Promise<string> | string)}
* @memberof ReplicationService
*/
abstract getJobLog(jobId: number | string): Observable<string> | Promise<string> | string;
} }
/** /**
@ -242,4 +252,15 @@ export class ReplicationDefaultService extends ReplicationService {
.then(response => response.json() as ReplicationJob[]) .then(response => response.json() as ReplicationJob[])
.catch(error => Promise.reject(error)); .catch(error => Promise.reject(error));
} }
public getJobLog(jobId: number | string): Observable<string> | Promise<string> | string {
if (!jobId || jobId <= 0) {
return Promise.reject('Bad argument');
}
let logUrl: string = `${this._jobBaseUrl}/${jobId}/log`;
return this.http.get(logUrl).toPromise()
.then(response => response.text())
.catch(error => Promise.reject(error));
}
} }

View File

@ -16,7 +16,7 @@ export const TIP_COMPONENT_HTML: string = `
<span>{{highCount}} {{packageText(highCount) | translate }} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}</span> <span>{{highCount}} {{packageText(highCount) | translate }} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}</span>
</div> </div>
<div *ngIf="hasMedium" class="bar-summary-item"> <div *ngIf="hasMedium" class="bar-summary-item">
<clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="20"></clr-icon> <clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="22"></clr-icon>
<span>{{mediumCount}} {{packageText(mediumCount) | translate }} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}</span> <span>{{mediumCount}} {{packageText(mediumCount) | translate }} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}</span>
</div> </div>
<div *ngIf="hasLow" class="bar-summary-item"> <div *ngIf="hasLow" class="bar-summary-item">
@ -24,7 +24,7 @@ export const TIP_COMPONENT_HTML: string = `
<span>{{lowCount}} {{packageText(lowCount) | translate }} {{'VULNERABILITY.SEVERITY.LOW' | translate }}</span> <span>{{lowCount}} {{packageText(lowCount) | translate }} {{'VULNERABILITY.SEVERITY.LOW' | translate }}</span>
</div> </div>
<div *ngIf="hasUnknown" class="bar-summary-item"> <div *ngIf="hasUnknown" class="bar-summary-item">
<clr-icon shape="help" size="24"></clr-icon> <clr-icon shape="help" size="20"></clr-icon>
<span>{{unknownCount}} {{packageText(unknownCount) | translate }} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}</span> <span>{{unknownCount}} {{packageText(unknownCount) | translate }} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}</span>
</div> </div>
<div *ngIf="hasNone" class="bar-summary-item"> <div *ngIf="hasNone" class="bar-summary-item">

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.9.8", "clarity-icons": "^0.9.8",
"clarity-ui": "^0.9.8", "clarity-ui": "^0.9.8",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"harbor-ui": "0.2.87", "harbor-ui": "0.3.2",
"intl": "^1.2.5", "intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2", "mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0", "ngx-cookie": "^1.0.0",

View File

@ -273,7 +273,8 @@
"FOUND_ERROR_IN_JOBS": "Found errors in the replication job(s), please check.", "FOUND_ERROR_IN_JOBS": "Found errors in the replication job(s), please check.",
"INVALID_DATE": "Invalid date.", "INVALID_DATE": "Invalid date.",
"PLACEHOLDER": "We couldn't find any replication rules!", "PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!" "JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log"
}, },
"DESTINATION": { "DESTINATION": {
"NEW_ENDPOINT": "New Endpoint", "NEW_ENDPOINT": "New Endpoint",
@ -395,7 +396,7 @@
"TOKEN_EXPIRATION": "The expiration time (in minutes) of a token created by the token service. Default is 30 minutes.", "TOKEN_EXPIRATION": "The expiration time (in minutes) of a token created by the token service. Default is 30 minutes.",
"PRO_CREATION_RESTRICTION": "The flag to define what users have permission to create projects. By default, everyone can create a project. Set to 'Admin Only' so that only an administrator can create a project.", "PRO_CREATION_RESTRICTION": "The flag to define what users have permission to create projects. By default, everyone can create a project. Set to 'Admin Only' so that only an administrator can create a project.",
"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; 'Upon Refresh': Triggering scanning when database refreshed." "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday."
}, },
"LDAP": { "LDAP": {
"URL": "LDAP URL", "URL": "LDAP URL",

View File

@ -273,7 +273,8 @@
"FOUND_ERROR_IN_JOBS": "Se han encontrado errores en el trabajo de replicación. Por favor, compruébelos.", "FOUND_ERROR_IN_JOBS": "Se han encontrado errores en el trabajo de replicación. Por favor, compruébelos.",
"INVALID_DATE": "Fecha invalida.", "INVALID_DATE": "Fecha invalida.",
"PLACEHOLDER": "We couldn't find any replication rules!", "PLACEHOLDER": "We couldn't find any replication rules!",
"JOB_PLACEHOLDER": "We couldn't find any replication jobs!" "JOB_PLACEHOLDER": "We couldn't find any replication jobs!",
"JOB_LOG_VIEWER": "View Replication Job Log"
}, },
"DESTINATION": { "DESTINATION": {
"NEW_ENDPOINT": "Nuevo Endpoint", "NEW_ENDPOINT": "Nuevo Endpoint",
@ -396,7 +397,7 @@
"TOKEN_EXPIRATION": "El tiempo de expiración (en minutos) del token creado por el servicio de tokens. Por defecto son 30 minutos.", "TOKEN_EXPIRATION": "El tiempo de expiración (en minutos) del token creado por el servicio de tokens. Por defecto son 30 minutos.",
"PRO_CREATION_RESTRICTION": "Marca para definir qué usuarios tienen permisos para crear proyectos. Por defecto, todos pueden crear proyectos. Seleccione 'Solo Administradores' para que solamente los administradores puedan crear proyectos.", "PRO_CREATION_RESTRICTION": "Marca para definir qué usuarios tienen permisos para crear proyectos. Por defecto, todos pueden crear proyectos. Seleccione 'Solo Administradores' para que solamente los administradores puedan crear proyectos.",
"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; 'Upon Refresh': Triggering scanning when database refreshed." "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday."
}, },
"LDAP": { "LDAP": {
"URL": "LDAP URL", "URL": "LDAP URL",

View File

@ -273,7 +273,8 @@
"FOUND_ERROR_IN_JOBS": "复制任务中包含错误,请检查。", "FOUND_ERROR_IN_JOBS": "复制任务中包含错误,请检查。",
"INVALID_DATE": "无效日期。", "INVALID_DATE": "无效日期。",
"PLACEHOLDER": "未发现任何复制规则!", "PLACEHOLDER": "未发现任何复制规则!",
"JOB_PLACEHOLDER": "未发现任何复制任务!" "JOB_PLACEHOLDER": "未发现任何复制任务!",
"JOB_LOG_VIEWER": "查看复制任务日志"
}, },
"DESTINATION": { "DESTINATION": {
"NEW_ENDPOINT": "新建目标", "NEW_ENDPOINT": "新建目标",
@ -395,7 +396,7 @@
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间分钟默认为30分钟。", "TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间分钟默认为30分钟。",
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。", "PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。",
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书.", "ROOT_CERT_DOWNLOAD": "下载镜像库根证书.",
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描;‘缺陷库刷新后’:当缺陷数据库刷新后。" "SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。"
}, },
"LDAP": { "LDAP": {
"URL": "LDAP URL", "URL": "LDAP URL",