mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-03 15:43:39 +01:00
Provide 'Scan Now' menu in the tag list (#2819)
This commit is contained in:
parent
5c8be3502c
commit
aa681eb018
28
src/ui_ng/lib/src/channel/channel.service.ts
Normal file
28
src/ui_ng/lib/src/channel/channel.service.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observable } from "rxjs/Observable";
|
||||
|
||||
@Injectable()
|
||||
export class ChannelService {
|
||||
|
||||
//Declare for publishing scan event
|
||||
scanCommandSource = new Subject<string>();
|
||||
scanCommand$ = this.scanCommandSource.asObservable();
|
||||
|
||||
publishScanEvent(tagId: string): void {
|
||||
this.scanCommandSource.next(tagId);
|
||||
}
|
||||
}
|
1
src/ui_ng/lib/src/channel/index.ts
Normal file
1
src/ui_ng/lib/src/channel/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './channel.service';
|
@ -52,6 +52,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { TranslateServiceInitializer } from './i18n/index';
|
||||
import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils';
|
||||
import { ChannelService } from './channel/index';
|
||||
|
||||
/**
|
||||
* Declare default service configuration; all the endpoints will be defined in
|
||||
@ -203,7 +204,8 @@ export class HarborLibraryModule {
|
||||
useFactory: initConfig,
|
||||
deps: [TranslateServiceInitializer, SERVICE_CONFIG],
|
||||
multi: true
|
||||
}
|
||||
},
|
||||
ChannelService
|
||||
]
|
||||
};
|
||||
}
|
||||
@ -221,7 +223,8 @@ export class HarborLibraryModule {
|
||||
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService }
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
|
||||
ChannelService
|
||||
]
|
||||
};
|
||||
}
|
||||
|
@ -16,4 +16,5 @@ export * from './i18n/index';
|
||||
export * from './push-image/index';
|
||||
export * from './third-party/index';
|
||||
export * from './config/index';
|
||||
export * from './job-log-viewer/index';
|
||||
export * from './job-log-viewer/index';
|
||||
export * from './channel/index';
|
@ -27,8 +27,9 @@ export const TAG_TEMPLATE = `
|
||||
<clr-dg-placeholder>{{'TGA.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" *ngIf="canScanNow(t)" (click)="scanNow(t.name)">{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
|
||||
<button class="action-item" *ngIf="hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
<button class="action-item" (click)="showDigestId(t)">{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
|
||||
<button class="action-item" [hidden]="!hasProjectAdminRole" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell style="width: 80px;" [ngSwitch]="withClair">
|
||||
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)">{{t.name}}</a>
|
||||
@ -36,7 +37,7 @@ export const TAG_TEMPLATE = `
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell style="min-width: 180px;" class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">docker pull {{registryUrl}}/{{repoName}}:{{t.name}}</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 160px;" *ngIf="withClair">
|
||||
<hbr-vulnerability-bar [tagId]="t.name" [summary]="t.scan_overview" (startScanning)="scanTag($event)"></hbr-vulnerability-bar>
|
||||
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="t.scan_overview"></hbr-vulnerability-bar>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell style="width: 80px;" *ngIf="withNotary" [ngSwitch]="t.signature !== null">
|
||||
<clr-icon shape="check" *ngSwitchCase="true" style="color: #1D5100;"></clr-icon>
|
||||
|
@ -15,6 +15,7 @@ import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
|
||||
import { FILTER_DIRECTIVES } from '../filter/index'
|
||||
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
import { ChannelService } from '../channel/index';
|
||||
|
||||
describe('TagComponent (inline template)', () => {
|
||||
|
||||
@ -52,6 +53,7 @@ describe('TagComponent (inline template)', () => {
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||
|
@ -20,14 +20,17 @@ import {
|
||||
EventEmitter,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
ElementRef,
|
||||
OnDestroy
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { TagService } from '../service/tag.service';
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../shared/shared.const';
|
||||
import { ChannelService } from '../channel/index';
|
||||
import {
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
ConfirmationButtons
|
||||
} from '../shared/shared.const';
|
||||
|
||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
|
||||
@ -38,25 +41,23 @@ import { Tag, TagClickEvent } from '../service/interface';
|
||||
import { TAG_TEMPLATE } from './tag.component.html';
|
||||
import { TAG_STYLE } from './tag.component.css';
|
||||
|
||||
import { toPromise, CustomComparator, VULNERABILITY_SCAN_STATUS } from '../utils';
|
||||
import {
|
||||
toPromise,
|
||||
CustomComparator,
|
||||
VULNERABILITY_SCAN_STATUS
|
||||
} from '../utils';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { State, Comparator } from 'clarity-angular';
|
||||
|
||||
import { ScanningResultService } from '../service/index';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
|
||||
const STATE_CHECK_INTERVAL: number = 2000;//2s
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-tag',
|
||||
template: TAG_TEMPLATE,
|
||||
styles: [TAG_STYLE],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TagComponent implements OnInit, OnDestroy {
|
||||
export class TagComponent implements OnInit {
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() repoName: string;
|
||||
@ -83,11 +84,6 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
createdComparator: Comparator<Tag> = new CustomComparator<Tag>('created', 'date');
|
||||
|
||||
loading: boolean = false;
|
||||
|
||||
stateCheckTimer: Subscription;
|
||||
tagsInScanning: { [key: string]: any } = {};
|
||||
scanningTagCount: number = 0;
|
||||
|
||||
copyFailed: boolean = false;
|
||||
|
||||
@ViewChild('confirmationDialog')
|
||||
@ -99,8 +95,9 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
private errorHandler: ErrorHandler,
|
||||
private tagService: TagService,
|
||||
private translateService: TranslateService,
|
||||
private scanningService: ScanningResultService,
|
||||
private ref: ChangeDetectorRef) { }
|
||||
private ref: ChangeDetectorRef,
|
||||
private channel: ChannelService
|
||||
) { }
|
||||
|
||||
confirmDeletion(message: ConfirmationAcknowledgement) {
|
||||
if (message &&
|
||||
@ -135,18 +132,6 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.retrieve();
|
||||
|
||||
this.stateCheckTimer = Observable.timer(STATE_CHECK_INTERVAL, STATE_CHECK_INTERVAL).subscribe(() => {
|
||||
if (this.scanningTagCount > 0) {
|
||||
this.updateScanningStates();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.stateCheckTimer) {
|
||||
this.stateCheckTimer.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
retrieve() {
|
||||
@ -203,7 +188,7 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
this.copyFailed = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onTagClick(tag: Tag): void {
|
||||
if (tag) {
|
||||
let evt: TagClickEvent = {
|
||||
@ -215,49 +200,6 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
scanTag(tagId: string): void {
|
||||
//Double check
|
||||
if (this.tagsInScanning[tagId]) {
|
||||
return;
|
||||
}
|
||||
toPromise<any>(this.scanningService.startVulnerabilityScanning(this.repoName, tagId))
|
||||
.then(() => {
|
||||
//Add to scanning map
|
||||
this.tagsInScanning[tagId] = tagId;
|
||||
//Counting
|
||||
this.scanningTagCount += 1;
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
updateScanningStates(): void {
|
||||
toPromise<Tag[]>(this.tagService
|
||||
.getTags(this.repoName))
|
||||
.then(items => {
|
||||
console.debug("updateScanningStates called!");
|
||||
//Reset the scanning states
|
||||
this.tagsInScanning = {};
|
||||
this.scanningTagCount = 0;
|
||||
|
||||
items.forEach(item => {
|
||||
if (item.scan_overview) {
|
||||
if (item.scan_overview.scan_status === VULNERABILITY_SCAN_STATUS.pending ||
|
||||
item.scan_overview.scan_status === VULNERABILITY_SCAN_STATUS.running) {
|
||||
this.tagsInScanning[item.name] = item.name;
|
||||
this.scanningTagCount += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.tags = items;
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
onSuccess($event: any): void {
|
||||
this.copyFailed = false;
|
||||
//Directly close dialog
|
||||
@ -268,8 +210,33 @@ export class TagComponent implements OnInit, OnDestroy {
|
||||
//Show error
|
||||
this.copyFailed = true;
|
||||
//Select all text
|
||||
if(this.textInput){
|
||||
if (this.textInput) {
|
||||
this.textInput.nativeElement.select();
|
||||
}
|
||||
}
|
||||
|
||||
//Get vulnerability scanning status
|
||||
scanStatus(t: Tag): string {
|
||||
if (t && t.scan_overview && t.scan_overview.scan_status) {
|
||||
return t.scan_overview.scan_status;
|
||||
}
|
||||
|
||||
return VULNERABILITY_SCAN_STATUS.unknown;
|
||||
}
|
||||
|
||||
//Whether show the 'scan now' menu
|
||||
canScanNow(t: Tag): boolean {
|
||||
if (!this.withClair) { return false; }
|
||||
let st: string = this.scanStatus(t);
|
||||
|
||||
return st !== VULNERABILITY_SCAN_STATUS.pending &&
|
||||
st !== VULNERABILITY_SCAN_STATUS.running;
|
||||
}
|
||||
|
||||
//Trigger scan
|
||||
scanNow(tagId: string): void {
|
||||
if (tagId) {
|
||||
this.channel.publishScanEvent(tagId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { VulnerabilitySummary } from '../service/index';
|
||||
|
||||
import { ResultBarChartComponent, ScanState } from './result-bar-chart.component';
|
||||
import { ResultBarChartComponent } from './result-bar-chart.component';
|
||||
import { ResultTipComponent } from './result-tip.component';
|
||||
import { ScanningResultService, ScanningResultDefaultService } from '../service/scanning.service';
|
||||
import {
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService,
|
||||
TagService,
|
||||
TagDefaultService
|
||||
} from '../service/index';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { VULNERABILITY_SCAN_STATUS } from '../utils';
|
||||
import { ChannelService } from '../channel/index';
|
||||
|
||||
describe('ResultBarChartComponent (inline template)', () => {
|
||||
let component: ResultBarChartComponent;
|
||||
@ -52,7 +55,10 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
ResultTipComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig }
|
||||
ChannelService,
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||
{ provide: TagService, useValue: TagDefaultService },
|
||||
{ provide: ScanningResultService, useValue: ScanningResultDefaultService }
|
||||
]
|
||||
});
|
||||
|
||||
@ -62,7 +68,7 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
fixture = TestBed.createComponent(ResultBarChartComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.tagId = "mockTag";
|
||||
component.state = ScanState.UNKNOWN;
|
||||
component.summary = mockData;
|
||||
|
||||
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||
|
||||
@ -71,30 +77,28 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should inject the SERVICE_CONFIG', () => {
|
||||
expect(serviceConfig).toBeTruthy();
|
||||
expect(serviceConfig.vulnerabilityScanningBaseEndpoint).toEqual("/api/vulnerability/testing");
|
||||
});
|
||||
|
||||
it('should show a button if status is PENDING', async(() => {
|
||||
component.state = ScanState.PENDING;
|
||||
it('should show "not scanned" if status is STOPPED', async(() => {
|
||||
component.summary.scan_status = VULNERABILITY_SCAN_STATUS.stopped;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.scanning-button');
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('span');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent).toEqual('VULNERABILITY.STATE.STOPPED');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show progress if status is SCANNING', async(() => {
|
||||
component.state = ScanState.SCANNING;
|
||||
component.summary.scan_status = VULNERABILITY_SCAN_STATUS.running;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.progress');
|
||||
@ -103,10 +107,10 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
}));
|
||||
|
||||
it('should show QUEUED if status is QUEUED', async(() => {
|
||||
component.state = ScanState.QUEUED;
|
||||
component.summary.scan_status = VULNERABILITY_SCAN_STATUS.pending;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.bar-state');
|
||||
@ -119,11 +123,10 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
}));
|
||||
|
||||
it('should show summary bar chart if status is COMPLETED', async(() => {
|
||||
component.state = ScanState.COMPLETED;
|
||||
component.summary = mockData;
|
||||
component.summary.scan_status = VULNERABILITY_SCAN_STATUS.finished;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.bar-block-none');
|
||||
|
@ -1,35 +1,40 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnInit
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { VulnerabilitySummary } from '../service/index';
|
||||
|
||||
import { SCANNING_STYLES } from './scanning.css';
|
||||
import { BAR_CHART_COMPONENT_HTML } from './scanning.html';
|
||||
import { VULNERABILITY_SCAN_STATUS } from '../utils';
|
||||
import { VulnerabilitySeverity } from '../service/index';
|
||||
import {
|
||||
VulnerabilitySummary,
|
||||
VulnerabilitySeverity,
|
||||
TagService,
|
||||
ScanningResultService,
|
||||
Tag
|
||||
} from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { toPromise } from '../utils';
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
import { ChannelService } from '../channel/index';
|
||||
|
||||
export enum ScanState {
|
||||
COMPLETED, //Scanning work successfully completed
|
||||
ERROR, //Error occurred when scanning
|
||||
QUEUED, //Scanning job is queued
|
||||
SCANNING, //Scanning in progress
|
||||
PENDING, //Scanning not start
|
||||
UNKNOWN //Unknown status
|
||||
}
|
||||
const STATE_CHECK_INTERVAL: number = 2000;//2s
|
||||
const RETRY_TIMES: number = 3;
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-vulnerability-bar',
|
||||
styles: [SCANNING_STYLES],
|
||||
template: BAR_CHART_COMPONENT_HTML
|
||||
})
|
||||
export class ResultBarChartComponent implements OnInit {
|
||||
export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
@Input() repoName: string = "";
|
||||
@Input() tagId: string = "";
|
||||
@Input() state: ScanState = ScanState.PENDING;
|
||||
@Input() summary: VulnerabilitySummary = {
|
||||
scan_status: VULNERABILITY_SCAN_STATUS.unknown,
|
||||
scan_status: VULNERABILITY_SCAN_STATUS.stopped,
|
||||
severity: VulnerabilitySeverity.UNKNOWN,
|
||||
update_time: new Date(),
|
||||
components: {
|
||||
@ -37,63 +42,157 @@ export class ResultBarChartComponent implements OnInit {
|
||||
summary: []
|
||||
}
|
||||
};
|
||||
@Output() startScanning: EventEmitter<string> = new EventEmitter<string>();
|
||||
scanningInProgress: boolean = false;
|
||||
onSubmitting: boolean = false;
|
||||
retryCounter: number = 0;
|
||||
stateCheckTimer: Subscription;
|
||||
timerHandler: any;
|
||||
|
||||
constructor() { }
|
||||
constructor(
|
||||
private tagService: TagService,
|
||||
private scanningService: ScanningResultService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private channel: ChannelService,
|
||||
private ref: ChangeDetectorRef
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.summary && this.summary.scan_status) {
|
||||
switch (this.summary.scan_status) {
|
||||
case VULNERABILITY_SCAN_STATUS.unknown:
|
||||
this.state = ScanState.UNKNOWN;
|
||||
break;
|
||||
case VULNERABILITY_SCAN_STATUS.error:
|
||||
this.state = ScanState.ERROR;
|
||||
break;
|
||||
case VULNERABILITY_SCAN_STATUS.pending:
|
||||
this.state = ScanState.QUEUED;
|
||||
break;
|
||||
case VULNERABILITY_SCAN_STATUS.stopped:
|
||||
this.state = ScanState.PENDING;
|
||||
break;
|
||||
case VULNERABILITY_SCAN_STATUS.finished:
|
||||
this.state = ScanState.COMPLETED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
this.channel.scanCommand$.subscribe((tagId: string) => {
|
||||
if (this.tagId === tagId) {
|
||||
this.scanNow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.stateCheckTimer) {
|
||||
this.stateCheckTimer.unsubscribe();
|
||||
this.stateCheckTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
//Get vulnerability scanning status
|
||||
public get status(): string {
|
||||
if (this.summary && this.summary.scan_status) {
|
||||
return this.summary.scan_status;
|
||||
}
|
||||
|
||||
return VULNERABILITY_SCAN_STATUS.unknown;
|
||||
}
|
||||
|
||||
public get completed(): boolean {
|
||||
return this.state === ScanState.COMPLETED;
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.finished;
|
||||
}
|
||||
|
||||
public get error(): boolean {
|
||||
return this.state === ScanState.ERROR;
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.error;
|
||||
}
|
||||
|
||||
public get queued(): boolean {
|
||||
return this.state === ScanState.QUEUED;
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.pending;
|
||||
}
|
||||
|
||||
public get scanning(): boolean {
|
||||
return this.state === ScanState.SCANNING;
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.running;
|
||||
}
|
||||
|
||||
public get pending(): boolean {
|
||||
return this.state === ScanState.PENDING;
|
||||
public get stopped(): boolean {
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.stopped;
|
||||
}
|
||||
|
||||
public get unknown(): boolean {
|
||||
return this.state === ScanState.UNKNOWN;
|
||||
return this.status === VULNERABILITY_SCAN_STATUS.unknown;
|
||||
}
|
||||
|
||||
scanNow(): void {
|
||||
if (this.tagId && this.tagId !== '') {
|
||||
this.scanningInProgress = true;
|
||||
this.startScanning.emit(this.tagId);
|
||||
if (this.onSubmitting) {
|
||||
//Avoid duplicated submitting
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.repoName || !this.tagId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onSubmitting = true;
|
||||
|
||||
toPromise<any>(this.scanningService.startVulnerabilityScanning(this.repoName, this.tagId))
|
||||
.then(() => {
|
||||
this.onSubmitting = false;
|
||||
|
||||
//Forcely change status to queued after successful submitting
|
||||
this.summary.scan_status = VULNERABILITY_SCAN_STATUS.pending;
|
||||
|
||||
//Forcely refresh view
|
||||
this.forceRefreshView(1000);
|
||||
|
||||
//Start check status util the job is done
|
||||
if (!this.stateCheckTimer) {
|
||||
//Avoid duplicated subscribing
|
||||
this.stateCheckTimer = Observable.timer(STATE_CHECK_INTERVAL, STATE_CHECK_INTERVAL).subscribe(() => {
|
||||
this.getSummary();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.onSubmitting = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getSummary(): void {
|
||||
if (!this.repoName || !this.tagId) {
|
||||
return
|
||||
}
|
||||
|
||||
toPromise<Tag>(this.tagService.getTag(this.repoName, this.tagId))
|
||||
.then((t: Tag) => {
|
||||
//To keep the same summary reference, use value copy.
|
||||
this.copyValue(t.scan_overview);
|
||||
|
||||
//Forcely refresh view
|
||||
this.forceRefreshView(1000);
|
||||
|
||||
if (!this.queued && !this.scanning) {
|
||||
//Scanning should be done
|
||||
if (this.stateCheckTimer) {
|
||||
this.stateCheckTimer.unsubscribe();
|
||||
this.stateCheckTimer = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
this.retryCounter++;
|
||||
if (this.retryCounter >= RETRY_TIMES) {
|
||||
//Stop timer
|
||||
if (this.stateCheckTimer) {
|
||||
this.stateCheckTimer.unsubscribe();
|
||||
this.stateCheckTimer = null;
|
||||
}
|
||||
this.retryCounter = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
copyValue(newVal: VulnerabilitySummary): void {
|
||||
if (!newVal || !newVal.scan_status) { return; }
|
||||
this.summary.scan_status = newVal.scan_status;
|
||||
this.summary.severity = newVal.severity;
|
||||
this.summary.components = newVal.components;
|
||||
this.summary.update_time = newVal.update_time;
|
||||
}
|
||||
|
||||
forceRefreshView(duration: number): void {
|
||||
//Reset timer
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
}
|
||||
this.timerHandler = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => {
|
||||
if (this.timerHandler) {
|
||||
clearInterval(this.timerHandler);
|
||||
this.timerHandler = null;
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export const TIP_COMPONENT_HTML: string = `
|
||||
</div>
|
||||
<div>
|
||||
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} </span>
|
||||
<span>{{completeTimestamp | date}}</span>
|
||||
<span>{{completeTimestamp | date:'MM/dd/y HH:mm:ss'}}</span>
|
||||
</div>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
@ -89,11 +89,11 @@ export const GRID_COMPONENT_HTML: string = `
|
||||
|
||||
export const BAR_CHART_COMPONENT_HTML: string = `
|
||||
<div class="bar-wrapper">
|
||||
<div *ngIf="pending" class="bar-state">
|
||||
<button class="btn btn-link scanning-button" (click)="scanNow()" [disabled]="scanningInProgress">{{'VULNERABILITY.STATE.PENDING' | translate}}</button>
|
||||
<div *ngIf="stopped" class="bar-state">
|
||||
<span class="label">{{'VULNERABILITY.STATE.STOPPED' | translate}}</span>
|
||||
</div>
|
||||
<div *ngIf="queued" class="bar-state">
|
||||
<span>{{'VULNERABILITY.STATE.QUEUED' | translate}}</span>
|
||||
<span class="label label-orange">{{'VULNERABILITY.STATE.QUEUED' | translate}}</span>
|
||||
</div>
|
||||
<div *ngIf="error" class="bar-state bar-state-error">
|
||||
<clr-icon shape="info-circle" class="is-error" size="24"></clr-icon>
|
||||
@ -101,9 +101,9 @@ export const BAR_CHART_COMPONENT_HTML: string = `
|
||||
</div>
|
||||
<div *ngIf="scanning" class="bar-state bar-state-chart">
|
||||
<div>{{'VULNERABILITY.STATE.SCANNING' | translate}}</div>
|
||||
<div class="progress loop" style="height:2px;min-height:2px;"><progress></progress></div>
|
||||
<div class="progress loop" style="height:2px;"><progress></progress></div>
|
||||
</div>
|
||||
<div *ngIf="completed" class="bar-state bar-state-chart" style="z-index:1020;">
|
||||
<div *ngIf="completed" class="bar-state bar-state-chart">
|
||||
<hbr-vulnerability-summary-chart [summary]="summary"></hbr-vulnerability-summary-chart>
|
||||
</div>
|
||||
<div *ngIf="unknown" class="bar-state">
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.3.2",
|
||||
"harbor-ui": "0.3.18",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -462,7 +462,7 @@
|
||||
},
|
||||
"VULNERABILITY": {
|
||||
"STATE": {
|
||||
"PENDING": "SCAN NOW",
|
||||
"STOPPED": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "Error",
|
||||
"SCANNING": "Scanning",
|
||||
@ -495,7 +495,8 @@
|
||||
"PLURAL": "Vulnerabilities",
|
||||
"PLACEHOLDER": "Filter Vulnerabilities",
|
||||
"PACKAGE": "Package with",
|
||||
"PACKAGES": "Packages with"
|
||||
"PACKAGES": "Packages with",
|
||||
"SCAN_NOW": "Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Image",
|
||||
|
@ -461,7 +461,7 @@
|
||||
},
|
||||
"VULNERABILITY": {
|
||||
"STATE": {
|
||||
"PENDING": "SCAN NOW",
|
||||
"STOPPED": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "Error",
|
||||
"SCANNING": "Scanning",
|
||||
@ -494,7 +494,8 @@
|
||||
"PLURAL": "Vulnerabilities",
|
||||
"PLACEHOLDER": "Filter Vulnerabilities",
|
||||
"PACKAGE": "Package with",
|
||||
"PACKAGES": "Packages with"
|
||||
"PACKAGES": "Packages with",
|
||||
"SCAN_NOW": "Scan"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Image",
|
||||
|
@ -466,7 +466,7 @@
|
||||
},
|
||||
"VULNERABILITY": {
|
||||
"STATE": {
|
||||
"PENDING": "开始扫描",
|
||||
"STOPPED": "未扫描",
|
||||
"QUEUED": "已入队列",
|
||||
"ERROR": "错误",
|
||||
"SCANNING": "扫描中",
|
||||
@ -499,7 +499,8 @@
|
||||
"PLURAL": "缺陷",
|
||||
"PLACEHOLDER": "过滤缺陷",
|
||||
"PACKAGE": "个组件有",
|
||||
"PACKAGES": "个组件有"
|
||||
"PACKAGES": "个组件有",
|
||||
"SCAN_NOW": "扫描"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "推送镜像",
|
||||
|
Loading…
Reference in New Issue
Block a user