From 7cec6c7b59167dc30db50c6d247b7c5f6e67b6ad Mon Sep 17 00:00:00 2001 From: sshijun Date: Mon, 21 Oct 2019 14:17:39 +0800 Subject: [PATCH] Improve scanner UI Signed-off-by: sshijun --- .../repository/repository.component.spec.ts | 17 +++++- src/portal/lib/src/service/interface.ts | 1 + .../lib/src/service/scanning.service.ts | 23 ++++++++ .../lib/src/tag/tag-detail.component.html | 6 +- .../lib/src/tag/tag-detail.component.spec.ts | 1 + .../lib/src/tag/tag-detail.component.ts | 2 + src/portal/lib/src/tag/tag.component.ts | 32 ++++++---- .../histogram-chart.component.ts | 2 +- .../result-bar-chart.component.spec.ts | 1 + .../result-grid.component.html | 2 +- .../result-grid.component.ts | 40 ++++++++++++- .../result-tip-histogram.component.html | 21 ++++--- .../result-tip-histogram.component.scss | 53 +++++++++++++++++ .../result-tip-histogram.component.ts | 59 ++++++++++++++++++- .../result-tip.component.spec.ts | 1 + .../scanner/config-scanner.component.html | 9 ++- .../scanner/config-scanner.component.ts | 17 ++++++ .../new-scanner-form.component.html | 6 +- .../new-scanner-form.component.spec.ts | 10 ++-- src/portal/src/app/config/scanner/scanner.ts | 5 +- .../project/scanner/scanner.component.html | 30 ++++++---- .../app/project/scanner/scanner.component.ts | 30 ++++++++++ src/portal/src/i18n/lang/en-us-lang.json | 7 ++- src/portal/src/i18n/lang/es-es-lang.json | 7 ++- src/portal/src/i18n/lang/fr-fr-lang.json | 5 +- src/portal/src/i18n/lang/pt-br-lang.json | 5 +- src/portal/src/i18n/lang/tr-tr-lang.json | 5 +- src/portal/src/i18n/lang/zh-cn-lang.json | 5 +- 28 files changed, 341 insertions(+), 61 deletions(-) diff --git a/src/portal/lib/src/repository/repository.component.spec.ts b/src/portal/lib/src/repository/repository.component.spec.ts index 5d9c3de46..bbeb3a28f 100644 --- a/src/portal/lib/src/repository/repository.component.spec.ts +++ b/src/portal/lib/src/repository/repository.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed, async, } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; import { DebugElement} from '@angular/core'; import { RouterTestingModule } from '@angular/router/testing'; @@ -25,7 +24,13 @@ import { ChannelService } from '../channel/index'; import { LabelPieceComponent } from "../label-piece/label-piece.component"; import { LabelDefaultService, LabelService } from "../service/label.service"; import { OperationService } from "../operation/operation.service"; -import { ProjectDefaultService, ProjectService, RetagDefaultService, RetagService } from "../service"; +import { + ProjectDefaultService, + ProjectService, + RetagDefaultService, + RetagService, ScanningResultDefaultService, + ScanningResultService +} from "../service"; import { UserPermissionDefaultService, UserPermissionService } from "../service/permission.service"; import { USERSTATICPERMISSION } from "../service/permission-static"; import { of } from "rxjs"; @@ -158,6 +163,11 @@ describe('RepositoryComponent (inline template)', () => { let mockHasRetagImagePermission: boolean = true; let mockHasDeleteImagePermission: boolean = true; let mockHasScanImagePermission: boolean = true; + let fakedScanningResultService = { + getProjectScanner() { + return of({}); + } + }; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -188,7 +198,8 @@ describe('RepositoryComponent (inline template)', () => { { provide: LabelService, useClass: LabelDefaultService}, { provide: UserPermissionService, useClass: UserPermissionDefaultService}, { provide: ChannelService}, - { provide: OperationService } + { provide: OperationService }, + { provide: ScanningResultService, useValue: fakedScanningResultService } ] }); })); diff --git a/src/portal/lib/src/service/interface.ts b/src/portal/lib/src/service/interface.ts index b2a67cce0..b709be0ed 100644 --- a/src/portal/lib/src/service/interface.ts +++ b/src/portal/lib/src/service/interface.ts @@ -315,6 +315,7 @@ export interface VulnerabilitySummary { } export interface SeveritySummary { total: number; + fixable: number; summary: {[key: string]: number}; } diff --git a/src/portal/lib/src/service/scanning.service.ts b/src/portal/lib/src/service/scanning.service.ts index 6f564d497..348a6a7f2 100644 --- a/src/portal/lib/src/service/scanning.service.ts +++ b/src/portal/lib/src/service/scanning.service.ts @@ -73,6 +73,19 @@ export abstract class ScanningResultService { * @memberOf ScanningResultService */ abstract startScanningAll(): Observable; + + /** + * Get scanner metadata + * @param uuid + * @memberOf ScanningResultService + */ + abstract getScannerMetadata(uuid: string): Observable; + + /** + * Get project scanner + * @param projectId + */ + abstract getProjectScanner(projectId: number): Observable; } @Injectable() @@ -153,4 +166,14 @@ export class ScanningResultDefaultService extends ScanningResultService { }) , catchError(error => observableThrowError(error))); } + getScannerMetadata(uuid: string): Observable { + return this.http.get(`/api/scanners/${uuid}/metadata`) + .pipe(map(response => response as any)) + .pipe(catchError(error => observableThrowError(error))); + } + getProjectScanner(projectId: number): Observable { + return this.http.get(`/api/projects/${projectId}/scanner`) + .pipe(map(response => response as any)) + .pipe(catchError(error => observableThrowError(error))); + } } diff --git a/src/portal/lib/src/tag/tag-detail.component.html b/src/portal/lib/src/tag/tag-detail.component.html index bc5d47b83..ca50fd5a8 100644 --- a/src/portal/lib/src/tag/tag-detail.component.html +++ b/src/portal/lib/src/tag/tag-detail.component.html @@ -39,11 +39,11 @@ -
-
+
+
- +
{{'TAG.LABELS' | translate }}
diff --git a/src/portal/lib/src/tag/tag-detail.component.spec.ts b/src/portal/lib/src/tag/tag-detail.component.spec.ts index 93838d7eb..12233bc09 100644 --- a/src/portal/lib/src/tag/tag-detail.component.spec.ts +++ b/src/portal/lib/src/tag/tag-detail.component.spec.ts @@ -48,6 +48,7 @@ describe("TagDetailComponent (inline template)", () => { end_time: new Date(), summary: { total: 124, + fixable: 50, summary: { "High": 5, "Low": 5 diff --git a/src/portal/lib/src/tag/tag-detail.component.ts b/src/portal/lib/src/tag/tag-detail.component.ts index 38e1b91bd..ce7c76b97 100644 --- a/src/portal/lib/src/tag/tag-detail.component.ts +++ b/src/portal/lib/src/tag/tag-detail.component.ts @@ -56,6 +56,7 @@ export class TagDetailComponent implements OnInit { hasVulnerabilitiesListPermission: boolean; hasBuildHistoryPermission: boolean; @Input() projectId: number; + showStatBar: boolean = true; constructor( private tagService: TagService, public channel: ChannelService, @@ -83,6 +84,7 @@ export class TagDetailComponent implements OnInit { && tagDetails.scan_overview && tagDetails.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]) { this.vulnerabilitySummary = tagDetails.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]; + this.showStatBar = false; } } onBack(): void { diff --git a/src/portal/lib/src/tag/tag.component.ts b/src/portal/lib/src/tag/tag.component.ts index 2024c3fc0..6e44b6e0a 100644 --- a/src/portal/lib/src/tag/tag.component.ts +++ b/src/portal/lib/src/tag/tag.component.ts @@ -27,7 +27,12 @@ import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'r import { TranslateService } from "@ngx-translate/core"; import { Comparator, Label, State, Tag, TagClickEvent } from "../service/interface"; -import { RequestQueryParams, RetagService, TagService, VulnerabilitySeverity } from "../service/index"; +import { + RequestQueryParams, + RetagService, + ScanningResultService, + TagService, +} from "../service/index"; import { ErrorHandler } from "../error-handler/error-handler"; import { ChannelService } from "../channel/index"; import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../shared/shared.const"; @@ -54,7 +59,6 @@ import { operateChanges, OperateInfo, OperationState } from "../operation/operat import { OperationService } from "../operation/operation.service"; import { ImageNameInputComponent } from "../image-name-input/image-name-input.component"; import { errorHandler as errorHandFn } from "../shared/shared.utils"; -import { HttpClient } from "@angular/common/http"; import { ClrLoadingState } from "@clr/angular"; export interface LabelState { @@ -160,7 +164,7 @@ export class TagComponent implements OnInit, AfterViewInit { private ref: ChangeDetectorRef, private operationService: OperationService, private channel: ChannelService, - private http: HttpClient + private scanningService: ScanningResultService ) { } ngOnInit() { @@ -759,19 +763,27 @@ export class TagComponent implements OnInit, AfterViewInit { getProjectScanner(): void { this.hasEnabledScanner = false; this.scanBtnState = ClrLoadingState.LOADING; - this.http.get(`/api/projects/${this.projectId}/scanner`) - .pipe(map(response => response as any)) - .pipe(catchError(error => observableThrowError(error))) + this.scanningService.getProjectScanner(this.projectId) .subscribe(response => { - if (response && "{}" !== JSON.stringify(response) && !response.disable - && response.health) { - this.hasEnabledScanner = true; + if (response && "{}" !== JSON.stringify(response) && !response.disabled + && response.uuid) { + this.getScannerMetadata(response.uuid); + } else { + this.scanBtnState = ClrLoadingState.ERROR; } - this.scanBtnState = ClrLoadingState.SUCCESS; }, error => { this.scanBtnState = ClrLoadingState.ERROR; }); } + getScannerMetadata(uuid: string) { + this.scanningService.getScannerMetadata(uuid) + .subscribe(response => { + this.hasEnabledScanner = true; + this.scanBtnState = ClrLoadingState.SUCCESS; + }, error => { + this.scanBtnState = ClrLoadingState.ERROR; + }); + } handleScanOverview(scanOverview: any) { if (scanOverview) { return scanOverview[DEFAULT_SUPPORTED_MIME_TYPE]; diff --git a/src/portal/lib/src/vulnerability-scanning/histogram-chart/histogram-chart.component.ts b/src/portal/lib/src/vulnerability-scanning/histogram-chart/histogram-chart.component.ts index 293aa59fe..2c52eb1f5 100644 --- a/src/portal/lib/src/vulnerability-scanning/histogram-chart/histogram-chart.component.ts +++ b/src/portal/lib/src/vulnerability-scanning/histogram-chart/histogram-chart.component.ts @@ -127,6 +127,6 @@ export class HistogramChartComponent implements OnInit, AfterViewInit, DoCheck { }); } this.max = count; - this.scale = Math.ceil(count / 4); + this.scale = Math.ceil(count / 40) * 10; } } diff --git a/src/portal/lib/src/vulnerability-scanning/result-bar-chart.component.spec.ts b/src/portal/lib/src/vulnerability-scanning/result-bar-chart.component.spec.ts index 60bacb316..f563038ba 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-bar-chart.component.spec.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-bar-chart.component.spec.ts @@ -32,6 +32,7 @@ describe('ResultBarChartComponent (inline template)', () => { end_time: new Date(), summary: { total: 124, + fixable: 50, summary: { "High": 5, "Low": 5 diff --git a/src/portal/lib/src/vulnerability-scanning/result-grid.component.html b/src/portal/lib/src/vulnerability-scanning/result-grid.component.html index 7c340399e..4ebb30c87 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-grid.component.html +++ b/src/portal/lib/src/vulnerability-scanning/result-grid.component.html @@ -10,7 +10,7 @@
- + {{'VULNERABILITY.GRID.COLUMN_ID' | translate}} {{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}} diff --git a/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts b/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts index b77929505..f4d17536a 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts @@ -10,7 +10,8 @@ import { ChannelService } from "../channel/channel.service"; import { UserPermissionService } from "../service/permission.service"; import { USERSTATICPERMISSION } from "../service/permission-static"; import { DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SEVERITY } from '../utils'; -import { finalize } from "rxjs/operators"; +import { finalize, map } from "rxjs/operators"; +import { ClrLoadingState } from "@clr/angular"; @Component({ @@ -22,10 +23,13 @@ export class ResultGridComponent implements OnInit { scanningResults: VulnerabilityItem[] = []; dataCache: VulnerabilityItem[] = []; loading: boolean = false; + shouldShowLoading: boolean = true; @Input() tagId: string; @Input() repositoryId: string; @Input() projectId: number; hasScanImagePermission: boolean; + hasEnabledScanner: boolean = false; + scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; constructor( private scanningService: ScanningResultService, private channel: ChannelService, @@ -39,11 +43,41 @@ export class ResultGridComponent implements OnInit { this.channel.tagDetail$.subscribe(tag => { this.loadResults(this.repositoryId, this.tagId); }); - + if (this.projectId) { + this.getProjectScanner(); + } + } + getProjectScanner(): void { + this.hasEnabledScanner = false; + this.scanBtnState = ClrLoadingState.LOADING; + this.scanningService.getProjectScanner(this.projectId) + .subscribe(response => { + if (response && "{}" !== JSON.stringify(response) && !response.disabled + && response.uuid) { + this.getScannerMetadata(response.uuid); + } else { + this.scanBtnState = ClrLoadingState.ERROR; + } + }, error => { + this.scanBtnState = ClrLoadingState.ERROR; + }); + } + getScannerMetadata(uuid: string) { + this.scanningService.getScannerMetadata(uuid) + .subscribe(response => { + this.hasEnabledScanner = true; + this.scanBtnState = ClrLoadingState.SUCCESS; + }, error => { + this.scanBtnState = ClrLoadingState.ERROR; + }); } loadResults(repositoryId: string, tagId: string): void { - this.loading = true; + // only show loading for one time + if (this.shouldShowLoading) { + this.loading = true; + this.shouldShowLoading = false; + } this.scanningService.getVulnerabilityScanningResults(repositoryId, tagId) .pipe(finalize(() => this.loading = false)) .subscribe((results) => { diff --git a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.html b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.html index 27a0ba3fc..eb1bdde53 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.html +++ b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.html @@ -1,14 +1,15 @@
- -
{{criticalCount}}
-
{{highCount}}
-
{{mediumCount}}
-
{{lowCount}}
-
{{negligibleCount}}
-
{{unknownCount}}
-
+
+
{{vulnerabilitySummary?.severity | slice:0:1}}
+
+ {{total}} + {{'SCANNER.TOTAL' | translate}} +
+ {{fixableCount}} + {{'SCANNER.FIXABLE' | translate}} +
{{'VULNERABILITY.NO_VULNERABILITY' | translate }}
@@ -46,6 +47,10 @@
+
+ {{'SCANNER.DURATION' | translate }} + {{duration()}} +
{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} {{completeTimestamp | date:'short'}} diff --git a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.scss b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.scss index c52d761e7..6c007c97f 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.scss +++ b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.scss @@ -208,6 +208,9 @@ hr { .margin-left-5 { margin-left: 5px; } +.margin-left-10 { + margin-left: 10px; +} .width-30 { width: 30px; @@ -220,3 +223,53 @@ hr { .width-150 { width: 150px; } +.circle-block { + color: #575757; + display: flex; + align-items: center; + div:first-child { + display: inline-block; + border-radius: 50%; + height: 20px; + width: 20px; + line-height: 20px; + font-size: 14px; + text-align: center; + } +} + +.level-border { + border:1px solid #f8b5b4; +} + +.level-critical { + background:red; + color:#621501; + +} +.level-high { + background:#e64524; + color:#621501; +} +.level-medium { + background-color: orange; + color:#621501; +} +.level-low { + background: #007CBB; + color:#cab6b1; +} +.level-negligible { + background-color: green; + color:#bad7ba; +} +.level-unknown { + background-color: grey; + color:#bad7ba; +} +.black-point { + display: inline-block; + width: 4px;background-color: #000; + height: 4px; + border-radius: 50%; +} diff --git a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts index c4f345e24..c50cc1c36 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts @@ -3,6 +3,18 @@ import { VulnerabilitySummary } from "../../service"; import { VULNERABILITY_SCAN_STATUS, VULNERABILITY_SEVERITY } from "../../utils"; import { TranslateService } from "@ngx-translate/core"; +const MIN = 60; +const MIN_STR = "min "; +const SEC_STR = "sec"; +const CLASS_MAP = { + "CRITICAL": "level-critical", + "HIGH": "level-high", + "MEDIUM": "level-medium", + "LOW": "level-low", + "NEGLIGIBLE": "level-negligible", + "UNKNOWN": "level-unknown" +}; + @Component({ selector: 'hbr-result-tip-histogram', templateUrl: './result-tip-histogram.component.html', @@ -52,7 +64,13 @@ export class ResultTipHistogramComponent implements OnInit { this.vulnerabilitySummary.summary) { return this.vulnerabilitySummary.summary.total; } - + return 0; + } + get fixableCount() { + if (this.vulnerabilitySummary && + this.vulnerabilitySummary.summary && this.vulnerabilitySummary.summary.fixable) { + return this.vulnerabilitySummary.summary.fixable; + } return 0; } @@ -72,7 +90,21 @@ export class ResultTipHistogramComponent implements OnInit { return 0; } - + duration(): string { + if (this.vulnerabilitySummary && this.vulnerabilitySummary.duration) { + let str = ''; + const min = Math.floor(this.vulnerabilitySummary.duration / MIN); + if (min) { + str += min + MIN_STR; + } + const sec = this.vulnerabilitySummary.duration % MIN; + if (sec) { + str += sec + SEC_STR; + } + return str; + } + return '0'; + } get highCount(): number { if (this.sevSummary) { return this.sevSummary[VULNERABILITY_SEVERITY.HIGH]; @@ -139,6 +171,29 @@ export class ResultTipHistogramComponent implements OnInit { return this.total === 0; } + getClass(): string { + if (this.vulnerabilitySummary && this.vulnerabilitySummary.severity) { + if (this.isCritical) { + return CLASS_MAP.CRITICAL; + } + if (this.isHigh) { + return CLASS_MAP.HIGH; + } + if (this.isMedium) { + return CLASS_MAP.MEDIUM; + } + if (this.isLow) { + return CLASS_MAP.LOW; + } + if (this.isNegligible) { + return CLASS_MAP.NEGLIGIBLE; + } + if (this.isUnknown) { + return CLASS_MAP.UNKNOWN; + } + } + return null; + } passMetadataToChart() { return [ { diff --git a/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts b/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts index 45c6d29a1..ad3bc03ad 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts @@ -20,6 +20,7 @@ describe('ResultTipComponent (inline template)', () => { end_time: new Date(), summary: { total: 124, + fixable: 50, summary: { "High": 5, "Low": 5 diff --git a/src/portal/src/app/config/scanner/config-scanner.component.html b/src/portal/src/app/config/scanner/config-scanner.component.html index 5f9be35e3..87273a988 100644 --- a/src/portal/src/app/config/scanner/config-scanner.component.html +++ b/src/portal/src/app/config/scanner/config-scanner.component.html @@ -65,9 +65,12 @@ {{scanner.url}} - {{'SCANNER.HEALTHY' | translate}} - - {{'SCANNER.UNHEALTHY' | translate}} + + + {{'SCANNER.HEALTHY' | translate}} + + {{'SCANNER.UNHEALTHY' | translate}} + {{!scanner.disabled}} diff --git a/src/portal/src/app/config/scanner/config-scanner.component.ts b/src/portal/src/app/config/scanner/config-scanner.component.ts index dd2e58562..b3c9d24c0 100644 --- a/src/portal/src/app/config/scanner/config-scanner.component.ts +++ b/src/portal/src/app/config/scanner/config-scanner.component.ts @@ -57,10 +57,27 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { .pipe(finalize(() => this.onGoing = false)) .subscribe(response => { this.scanners = response; + this.getMetadataForAll(); }, error => { this.errorHandler.error(error); }); } + getMetadataForAll() { + if (this.scanners && this.scanners.length > 0) { + this.scanners.forEach((scanner, index) => { + if (scanner.uuid ) { + this.scanners[index].loadingMetadata = true; + this.configScannerService.getScannerMetadata(scanner.uuid) + .pipe(finalize(() => this.scanners[index].loadingMetadata = false)) + .subscribe(response => { + this.scanners[index].metadata = response; + }, error => { + this.scanners[index].metadata = null; + }); + } + }); + } + } addNewScanner(): void { this.newScannerDialog.open(); diff --git a/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.html b/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.html index f80424f30..157b1ba28 100644 --- a/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.html +++ b/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.html @@ -11,7 +11,7 @@
- {{nameTooltip | translate}} + {{nameTooltip | translate}}
@@ -33,7 +33,7 @@
- {{endpointTooltip | translate}} + {{endpointTooltip | translate}}
@@ -73,7 +73,7 @@ - {{"SCANNER.PASSWORD_REQUIRED" | translate}} + {{"SCANNER.PASSWORD_REQUIRED" | translate}} diff --git a/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.spec.ts b/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.spec.ts index c78f276b7..5b44d7795 100644 --- a/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.spec.ts +++ b/src/portal/src/app/config/scanner/new-scanner-form/new-scanner-form.component.spec.ts @@ -65,9 +65,9 @@ describe('NewScannerFormComponent', () => { nameInput.blur(); nameInput.dispatchEvent(new Event('blur')); setTimeout(() => { - let el = fixture.nativeElement.querySelector('clr-control-error'); + let el = fixture.nativeElement.querySelector('#name-error'); expect(el).toBeFalsy(); - }, 900); + }, 11000); }); it('endpoint url should be valid', () => { @@ -79,9 +79,9 @@ describe('NewScannerFormComponent', () => { urlInput.blur(); urlInput.dispatchEvent(new Event('blur')); setTimeout(() => { - let el = fixture.nativeElement.querySelector('clr-control-error'); + let el = fixture.nativeElement.querySelector('#endpoint-error'); expect(el).toBeFalsy(); - }, 900); + }, 11000); }); it('auth should be valid', () => { @@ -96,7 +96,7 @@ describe('NewScannerFormComponent', () => { passwordInput.value = "12345"; usernameInput.dispatchEvent(new Event('input')); passwordInput.dispatchEvent(new Event('input')); - let el = fixture.nativeElement.querySelector('clr-control-error'); + let el = fixture.nativeElement.querySelector('#pwd-error'); expect(el).toBeFalsy(); }); }); diff --git a/src/portal/src/app/config/scanner/scanner.ts b/src/portal/src/app/config/scanner/scanner.ts index cf5d27cf1..b4190e403 100644 --- a/src/portal/src/app/config/scanner/scanner.ts +++ b/src/portal/src/app/config/scanner/scanner.ts @@ -1,3 +1,5 @@ +import { ScannerMetadata } from "./scanner-metadata"; + export class Scanner { name?: string; description?: string; @@ -13,7 +15,8 @@ export class Scanner { update_time?: any; vendor?: string; version?: string; - health?: boolean; + metadata?: ScannerMetadata; + loadingMetadata?: boolean; constructor() { } } diff --git a/src/portal/src/app/project/scanner/scanner.component.html b/src/portal/src/app/project/scanner/scanner.component.html index d9d772105..185c4d5d6 100644 --- a/src/portal/src/app/project/scanner/scanner.component.html +++ b/src/portal/src/app/project/scanner/scanner.component.html @@ -18,8 +18,13 @@ {{scanner?.name}} {{'SCANNER.DISABLED' | translate}} - {{'SCANNER.HEALTHY' | translate}} - {{'SCANNER.UNHEALTHY' | translate}} + + + {{'SCANNER.HEALTHY' | translate}} + + {{'SCANNER.UNHEALTHY' | translate}} + + @@ -33,29 +38,29 @@ -
+
-
-
+
-
-
+
-
@@ -76,8 +81,13 @@ {{scanner.name}} {{scanner.url}} - {{'SCANNER.HEALTHY' | translate}} - {{'SCANNER.UNHEALTHY' | translate}} + Loading... + + {{'SCANNER.HEALTHY' | translate}} + + {{'SCANNER.UNHEALTHY' | translate}} + + {{scanner.is_default}} diff --git a/src/portal/src/app/project/scanner/scanner.component.ts b/src/portal/src/app/project/scanner/scanner.component.ts index 19ec8985c..ae43ae53c 100644 --- a/src/portal/src/app/project/scanner/scanner.component.ts +++ b/src/portal/src/app/project/scanner/scanner.component.ts @@ -56,11 +56,24 @@ export class ScannerComponent implements OnInit { .subscribe(response => { if (response && "{}" !== JSON.stringify(response)) { this.scanner = response; + this.getScannerMetadata(); } }, error => { this.errorHandler.error(error); }); } + getScannerMetadata() { + if (this.scanner && this.scanner.uuid) { + this.scanner.loadingMetadata = true; + this.configScannerService.getScannerMetadata(this.scanner.uuid) + .pipe(finalize(() => this.scanner.loadingMetadata = false)) + .subscribe(response => { + this.scanner.metadata = response; + }, error => { + this.scanner.metadata = null; + }); + } + } getScanners() { this.loading = true; this.configScannerService.getScanners() @@ -75,6 +88,22 @@ export class ScannerComponent implements OnInit { this.errorHandler.error(error); }); } + getMetadataForAll() { + if (this.scanners && this.scanners.length > 0) { + this.scanners.forEach((scanner, index) => { + if (scanner.uuid ) { + this.scanners[index].loadingMetadata = true; + this.configScannerService.getScannerMetadata(scanner.uuid) + .pipe(finalize(() => this.scanners[index].loadingMetadata = false)) + .subscribe(response => { + this.scanners[index].metadata = response; + }, error => { + this.scanners[index].metadata = null; + }); + } + }); + } + } close() { this.opened = false; this.selectedScanner = null; @@ -87,6 +116,7 @@ export class ScannerComponent implements OnInit { this.selectedScanner = s; } }); + this.getMetadataForAll(); } get valid(): boolean { return this.selectedScanner diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index a522ef5ac..0b369c003 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -637,7 +637,7 @@ "FILTER_FOR_REPOSITORIES": "Filter Repositories", "TAG": "Tag", "SIZE": "Size", - "VULNERABILITY": "Vulnerability", + "VULNERABILITY": "Vulnerabilities", "BUILD_HISTORY": "Build History", "SIGNED": "Signed", "AUTHOR": "Author", @@ -1292,6 +1292,9 @@ "ENABLED": "Enabled", "ENABLE": "Enable", "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted" + "DELETE_SUCCESS": "Successfully deleted", + "TOTAL": "Total", + "FIXABLE": "Fixable", + "DURATION": "Duration:" } } diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 74ae5b372..500d09b0e 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -638,7 +638,7 @@ "FILTER_FOR_REPOSITORIES": "Filtrar Repositorios", "TAG": "Etiqueta", "SIZE": "Size", - "VULNERABILITY": "Vulnerability", + "VULNERABILITY": "Vulnerabilities", "BUILD_HISTORY": "Construir Historia", "SIGNED": "Firmada", "AUTHOR": "Autor", @@ -1289,6 +1289,9 @@ "ENABLED": "Enabled", "ENABLE": "Enable", "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted" + "DELETE_SUCCESS": "Successfully deleted", + "TOTAL": "Total", + "FIXABLE": "Fixable", + "DURATION": "Duration:" } } diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 222bd4031..863ce0c0d 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1261,6 +1261,9 @@ "ENABLED": "Enabled", "ENABLE": "Enable", "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted" + "DELETE_SUCCESS": "Successfully deleted", + "TOTAL": "Total", + "FIXABLE": "Fixable", + "DURATION": "Duration:" } } diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index d7720b35a..45bc09147 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1286,7 +1286,10 @@ "ENABLED": "Enabled", "ENABLE": "Enable", "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted" + "DELETE_SUCCESS": "Successfully deleted", + "TOTAL": "Total", + "FIXABLE": "Fixable", + "DURATION": "Duration:" } } diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index b6f23a2b8..28ec1051c 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1291,6 +1291,9 @@ "ENABLED": "Enabled", "ENABLE": "Enable", "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted" + "DELETE_SUCCESS": "Successfully deleted", + "TOTAL": "Total", + "FIXABLE": "Fixable", + "DURATION": "Duration:" } } diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 60ba80bbd..21abac5bd 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1288,6 +1288,9 @@ "ENABLED": "启用", "ENABLE": "启用", "DISABLE": "禁用", - "DELETE_SUCCESS": "删除成功" + "DELETE_SUCCESS": "删除成功", + "TOTAL": "总计", + "FIXABLE": "可修复", + "DURATION": "扫描用时:" } }