import { Component, Input, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core'; import { Observable, Subscription } from 'rxjs/Rx'; import "rxjs/add/observable/timer"; import { VULNERABILITY_SCAN_STATUS } from '../utils'; import { VulnerabilitySummary, TagService, ScanningResultService, Tag } from '../service/index'; import { ErrorHandler } from '../error-handler/index'; import { toPromise } from '../utils'; import { ChannelService } from '../channel/index'; import { JobLogViewerComponent } from '../job-log-viewer/index'; const STATE_CHECK_INTERVAL: number = 2000; // 2s 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() repoName: string = ""; @Input() tagId: string = ""; @Input() summary: VulnerabilitySummary; onSubmitting: boolean = false; retryCounter: number = 0; stateCheckTimer: Subscription; scanSubscription: Subscription; timerHandler: any; @ViewChild("scanningLogViewer") scanningJobLogViewer: JobLogViewerComponent; constructor( private tagService: TagService, private scanningService: ScanningResultService, private errorHandler: ErrorHandler, private channel: ChannelService, private ref: ChangeDetectorRef ) { } ngOnInit(): void { this.scanSubscription = this.channel.scanCommand$.subscribe((tagId: string) => { let myFullTag: string = this.repoName + "/" + this.tagId; if (myFullTag === tagId) { this.scanNow(); } }); } ngOnDestroy(): void { if (this.stateCheckTimer) { this.stateCheckTimer.unsubscribe(); this.stateCheckTimer = null; } if (this.scanSubscription) { this.scanSubscription.unsubscribe(); }; } // Get vulnerability scanning status public get status(): string { if (this.summary && this.summary.scan_status) { return this.summary.scan_status; } return VULNERABILITY_SCAN_STATUS.stopped; } public get completed(): boolean { return this.status === VULNERABILITY_SCAN_STATUS.finished; } 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 unknown(): boolean { return this.status === VULNERABILITY_SCAN_STATUS.unknown; } scanNow(): void { if (this.onSubmitting) { // Avoid duplicated submitting return; } if (!this.repoName || !this.tagId) { return; } this.onSubmitting = true; toPromise(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, severity: null, components: null, update_time: null }; // 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(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 (!this.summary || !newVal || !newVal.scan_status) { return; } this.summary.scan_status = newVal.scan_status; this.summary.job_id = newVal.job_id; 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); } // Check error log viewLog(): void { if (this.summary && this.summary.job_id) { this.scanningJobLogViewer.open(this.summary.job_id); } } }