mirror of https://github.com/goharbor/harbor.git
326 lines
11 KiB
TypeScript
326 lines
11 KiB
TypeScript
import {
|
|
Component,
|
|
EventEmitter,
|
|
Input,
|
|
OnDestroy,
|
|
OnInit,
|
|
Output,
|
|
} from '@angular/core';
|
|
import { Subscription, timer } from 'rxjs';
|
|
import { finalize } from 'rxjs/operators';
|
|
import { ScannerVo } from '../../../../../shared/services';
|
|
import { ErrorHandler } from '../../../../../shared/units/error-handler';
|
|
import {
|
|
clone,
|
|
CURRENT_BASE_HREF,
|
|
dbEncodeURIComponent,
|
|
DEFAULT_SUPPORTED_MIME_TYPES,
|
|
VULNERABILITY_SCAN_STATUS,
|
|
} from '../../../../../shared/units/utils';
|
|
import { ArtifactService } from '../../../../../../../ng-swagger-gen/services/artifact.service';
|
|
import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact';
|
|
import { NativeReportSummary } from '../../../../../../../ng-swagger-gen/models/native-report-summary';
|
|
import {
|
|
EventService,
|
|
HarborEvent,
|
|
} from '../../../../../services/event-service/event.service';
|
|
import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service';
|
|
import { ScanType } from 'ng-swagger-gen/models';
|
|
import { ScanTypes } from '../../../../../shared/entities/shared.const';
|
|
|
|
const STATE_CHECK_INTERVAL: number = 3000; // 3s
|
|
const RETRY_TIMES: number = 3;
|
|
|
|
@Component({
|
|
selector: 'hbr-vulnerability-bar',
|
|
templateUrl: './result-bar-chart-component.html',
|
|
styleUrls: ['./scanning.scss'],
|
|
})
|
|
export class ResultBarChartComponent implements OnInit, OnDestroy {
|
|
@Input() inputScanner: ScannerVo;
|
|
@Input() repoName: string = '';
|
|
@Input() projectName: string = '';
|
|
@Input() artifactDigest: string = '';
|
|
@Input() summary: NativeReportSummary;
|
|
onSubmitting: boolean = false;
|
|
onStopping: boolean = false;
|
|
retryCounter: number = 0;
|
|
stateCheckTimer: Subscription;
|
|
scanSubscription: Subscription;
|
|
stopSubscription: Subscription;
|
|
timerHandler: any;
|
|
@Output()
|
|
submitFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
|
|
// if sending stop scan request is finished, emit to farther component
|
|
@Output()
|
|
submitStopFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
|
|
@Output()
|
|
scanFinished: EventEmitter<Artifact> = new EventEmitter<Artifact>();
|
|
|
|
constructor(
|
|
private artifactService: ArtifactService,
|
|
private scanService: ScanService,
|
|
private errorHandler: ErrorHandler,
|
|
private eventService: EventService
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
if (
|
|
(this.status === VULNERABILITY_SCAN_STATUS.RUNNING ||
|
|
this.status === VULNERABILITY_SCAN_STATUS.PENDING) &&
|
|
!this.stateCheckTimer
|
|
) {
|
|
// Avoid duplicated subscribing
|
|
this.stateCheckTimer = timer(0, STATE_CHECK_INTERVAL).subscribe(
|
|
() => {
|
|
this.getSummary();
|
|
}
|
|
);
|
|
}
|
|
if (!this.scanSubscription) {
|
|
this.scanSubscription = this.eventService.subscribe(
|
|
HarborEvent.START_SCAN_ARTIFACT,
|
|
(artifactDigest: string) => {
|
|
let myFullTag: string =
|
|
this.repoName + '/' + this.artifactDigest;
|
|
if (myFullTag === artifactDigest) {
|
|
this.scanNow();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
if (!this.stopSubscription) {
|
|
this.stopSubscription = this.eventService.subscribe(
|
|
HarborEvent.STOP_SCAN_ARTIFACT,
|
|
(artifactDigest: string) => {
|
|
let myFullTag: string =
|
|
this.repoName + '/' + this.artifactDigest;
|
|
if (myFullTag === artifactDigest) {
|
|
this.stopScan();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
if (this.stateCheckTimer) {
|
|
this.stateCheckTimer.unsubscribe();
|
|
this.stateCheckTimer = null;
|
|
}
|
|
if (this.scanSubscription) {
|
|
this.scanSubscription.unsubscribe();
|
|
this.scanSubscription = null;
|
|
}
|
|
if (this.stopSubscription) {
|
|
this.stopSubscription.unsubscribe();
|
|
this.stopSubscription = 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.NOT_SCANNED;
|
|
}
|
|
|
|
public get completed(): boolean {
|
|
return this.status === VULNERABILITY_SCAN_STATUS.SUCCESS;
|
|
}
|
|
|
|
public get error(): boolean {
|
|
return this.status === VULNERABILITY_SCAN_STATUS.ERROR;
|
|
}
|
|
|
|
public get queued(): boolean {
|
|
return this.status === VULNERABILITY_SCAN_STATUS.PENDING;
|
|
}
|
|
|
|
public get scanning(): boolean {
|
|
return this.status === VULNERABILITY_SCAN_STATUS.RUNNING;
|
|
}
|
|
public get stopped(): boolean {
|
|
return this.status === VULNERABILITY_SCAN_STATUS.STOPPED;
|
|
}
|
|
public get otherStatus(): boolean {
|
|
return !(
|
|
this.completed ||
|
|
this.error ||
|
|
this.queued ||
|
|
this.scanning ||
|
|
this.stopped
|
|
);
|
|
}
|
|
|
|
scanNow(): void {
|
|
if (this.onSubmitting) {
|
|
// Avoid duplicated submitting
|
|
console.error('duplicated submit');
|
|
return;
|
|
}
|
|
|
|
if (!this.repoName || !this.artifactDigest) {
|
|
console.error('bad repository or tag');
|
|
return;
|
|
}
|
|
|
|
this.onSubmitting = true;
|
|
|
|
this.scanService
|
|
.scanArtifact({
|
|
projectName: this.projectName,
|
|
reference: this.artifactDigest,
|
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
|
scanType: <ScanType>{
|
|
scan_type: ScanTypes.VULNERABILITY,
|
|
},
|
|
})
|
|
.pipe(finalize(() => this.submitFinish.emit(false)))
|
|
.subscribe(
|
|
() => {
|
|
this.onSubmitting = false;
|
|
// Forcely change status to queued after successful submitting
|
|
this.summary = {
|
|
scan_status: VULNERABILITY_SCAN_STATUS.PENDING,
|
|
};
|
|
// Start check status util the job is done
|
|
if (!this.stateCheckTimer) {
|
|
// Avoid duplicated subscribing
|
|
this.stateCheckTimer = timer(
|
|
STATE_CHECK_INTERVAL,
|
|
STATE_CHECK_INTERVAL
|
|
).subscribe(() => {
|
|
this.getSummary();
|
|
});
|
|
}
|
|
},
|
|
error => {
|
|
this.onSubmitting = false;
|
|
if (error && error.error && error.error.code === 409) {
|
|
console.error(error.error.message);
|
|
} else {
|
|
this.errorHandler.error(error);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
getSummary(): void {
|
|
if (!this.repoName || !this.artifactDigest) {
|
|
return;
|
|
}
|
|
this.artifactService
|
|
.getArtifact({
|
|
projectName: this.projectName,
|
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
|
reference: this.artifactDigest,
|
|
withScanOverview: true,
|
|
XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES,
|
|
})
|
|
.subscribe(
|
|
(artifact: Artifact) => {
|
|
// To keep the same summary reference, use value copy.
|
|
if (artifact.scan_overview) {
|
|
this.copyValue(
|
|
Object.values(artifact.scan_overview)[0]
|
|
);
|
|
}
|
|
if (!this.queued && !this.scanning) {
|
|
// Scanning should be done
|
|
if (this.stateCheckTimer) {
|
|
this.stateCheckTimer.unsubscribe();
|
|
this.stateCheckTimer = null;
|
|
}
|
|
this.scanFinished.emit(artifact);
|
|
}
|
|
this.eventService.publish(
|
|
HarborEvent.UPDATE_VULNERABILITY_INFO,
|
|
artifact
|
|
);
|
|
},
|
|
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: NativeReportSummary): void {
|
|
if (!this.summary || !newVal || !newVal.scan_status) {
|
|
return;
|
|
}
|
|
this.summary = clone(newVal);
|
|
}
|
|
viewLog(): string {
|
|
return `${CURRENT_BASE_HREF}/projects/${
|
|
this.projectName
|
|
}/repositories/${dbEncodeURIComponent(this.repoName)}/artifacts/${
|
|
this.artifactDigest
|
|
}/scan/${this.summary.report_id}/log`;
|
|
}
|
|
getScanner(): ScannerVo {
|
|
if (this.summary && this.summary.scanner) {
|
|
return this.summary.scanner;
|
|
}
|
|
return this.inputScanner;
|
|
}
|
|
stopScan() {
|
|
if (this.onStopping) {
|
|
// Avoid duplicated stopping command
|
|
console.error('duplicated stopping command');
|
|
return;
|
|
}
|
|
if (!this.repoName || !this.artifactDigest) {
|
|
console.error('bad repository or artifact');
|
|
return;
|
|
}
|
|
this.onStopping = true;
|
|
|
|
this.scanService
|
|
.stopScanArtifact({
|
|
projectName: this.projectName,
|
|
reference: this.artifactDigest,
|
|
repositoryName: dbEncodeURIComponent(this.repoName),
|
|
scanType: <ScanType>{
|
|
scan_type: ScanTypes.VULNERABILITY,
|
|
},
|
|
})
|
|
.pipe(
|
|
finalize(() => {
|
|
this.submitStopFinish.emit(false);
|
|
this.onStopping = false;
|
|
})
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
// Start check status util the job is done
|
|
if (!this.stateCheckTimer) {
|
|
// Avoid duplicated subscribing
|
|
this.stateCheckTimer = timer(
|
|
STATE_CHECK_INTERVAL,
|
|
STATE_CHECK_INTERVAL
|
|
).subscribe(() => {
|
|
this.getSummary();
|
|
});
|
|
}
|
|
this.errorHandler.info(
|
|
'VULNERABILITY.TRIGGER_STOP_SUCCESS'
|
|
);
|
|
},
|
|
error => {
|
|
this.errorHandler.error(error);
|
|
}
|
|
);
|
|
}
|
|
}
|