Merge pull request #9518 from AllForNothing/scanner-im-v2

Improve scanner UI
This commit is contained in:
Will Sun 2019-10-23 17:01:26 +08:00 committed by GitHub
commit ee2de96c91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 341 additions and 61 deletions

View File

@ -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({});
}
};
const permissions = [
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE},
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
@ -194,7 +204,8 @@ describe('RepositoryComponent (inline template)', () => {
{ provide: LabelService, useClass: LabelDefaultService},
{ provide: UserPermissionService, useClass: UserPermissionDefaultService},
{ provide: ChannelService},
{ provide: OperationService }
{ provide: OperationService },
{ provide: ScanningResultService, useValue: fakedScanningResultService }
]
});
}));

View File

@ -315,6 +315,7 @@ export interface VulnerabilitySummary {
}
export interface SeveritySummary {
total: number;
fixable: number;
summary: {[key: string]: number};
}

View File

@ -73,6 +73,19 @@ export abstract class ScanningResultService {
* @memberOf ScanningResultService
*/
abstract startScanningAll(): Observable<any>;
/**
* Get scanner metadata
* @param uuid
* @memberOf ScanningResultService
*/
abstract getScannerMetadata(uuid: string): Observable<any>;
/**
* Get project scanner
* @param projectId
*/
abstract getProjectScanner(projectId: number): Observable<any>;
}
@Injectable()
@ -153,4 +166,14 @@ export class ScanningResultDefaultService extends ScanningResultService {
})
, catchError(error => observableThrowError(error)));
}
getScannerMetadata(uuid: string): Observable<any> {
return this.http.get(`/api/scanners/${uuid}/metadata`)
.pipe(map(response => response as any))
.pipe(catchError(error => observableThrowError(error)));
}
getProjectScanner(projectId: number): Observable<any> {
return this.http.get(`/api/projects/${projectId}/scanner`)
.pipe(map(response => response as any))
.pipe(catchError(error => observableThrowError(error)));
}
}

View File

@ -39,11 +39,11 @@
</section>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="vulnerability" [hidden]="hasCve">
<div class="col-md-4 col-sm-6 margin-top-5px">
<div class="vulnerability" [hidden]="hasCve || showStatBar">
<hbr-vulnerability-bar [repoName]="repositoryId" [tagId]="tagDetails.name" [summary]="vulnerabilitySummary"></hbr-vulnerability-bar>
</div>
<histogram-chart *ngIf="hasCve" class="margin-top-5px" [metadata]="passMetadataToChart()" [isWhiteBackground]="true"></histogram-chart>
<histogram-chart *ngIf="hasCve" [metadata]="passMetadataToChart()" [isWhiteBackground]="true"></histogram-chart>
</div>
<div *ngIf="!withAdmiral && tagDetails?.labels?.length">
<div class="third-column detail-title">{{'TAG.LABELS' | translate }}</div>

View File

@ -48,6 +48,7 @@ describe("TagDetailComponent (inline template)", () => {
end_time: new Date(),
summary: {
total: 124,
fixable: 50,
summary: {
"High": 5,
"Low": 5

View File

@ -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 {

View File

@ -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];

View File

@ -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;
}
}

View File

@ -32,6 +32,7 @@ describe('ResultBarChartComponent (inline template)', () => {
end_time: new Date(),
summary: {
total: 124,
fixable: 50,
summary: {
"High": 5,
"Low": 5

View File

@ -10,7 +10,7 @@
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasScanImagePermission" (click)="scanNow()"><clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [clrLoading]="scanBtnState" [disabled]="!hasScanImagePermission || !hasEnabledScanner" (click)="scanNow()"><clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'id'">{{'VULNERABILITY.GRID.COLUMN_ID' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'severity'">{{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}}</clr-dg-column>

View File

@ -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) => {

View File

@ -1,14 +1,15 @@
<div class="tip-wrapper tip-position width-210">
<clr-tooltip>
<div clrTooltipTrigger class="tip-block">
<ng-container *ngIf="!isNone">
<div *ngIf="criticalCount > 0" class="tip-wrapper bar-block-critical shadow-critical width-30">{{criticalCount}}</div>
<div *ngIf="highCount > 0" class="margin-left-5 tip-wrapper bar-block-high shadow-high width-30">{{highCount}}</div>
<div *ngIf="mediumCount > 0" class="margin-left-5 tip-wrapper bar-block-medium shadow-medium width-30">{{mediumCount}}</div>
<div *ngIf="lowCount > 0" class="margin-left-5 tip-wrapper bar-block-low shadow-low width-30">{{lowCount}}</div>
<div *ngIf="negligibleCount > 0" class="margin-left-5 tip-wrapper bar-block-none shadow-none width-30">{{negligibleCount}}</div>
<div *ngIf="unknownCount > 0" class="margin-left-5 tip-wrapper bar-block-unknown shadow-unknown width-30">{{unknownCount}}</div>
</ng-container>
<div *ngIf="!isNone" class="circle-block">
<div class="level-border" [className]="getClass()">{{vulnerabilitySummary?.severity | slice:0:1}}</div>
<div class="black-point margin-left-5"></div>
<span class="margin-left-5">{{total}}</span>
<span class="margin-left-5">{{'SCANNER.TOTAL' | translate}}</span>
<div class="black-point margin-left-10"></div>
<span class="margin-left-5">{{fixableCount}}</span>
<span class="margin-left-5">{{'SCANNER.FIXABLE' | translate}}</span>
</div>
<div *ngIf="isNone" class="margin-left-5 tip-wrapper bar-block-none shadow-none width-150">{{'VULNERABILITY.NO_VULNERABILITY' | translate }}</div>
</div>
<clr-tooltip-content class="w-800" [clrPosition]="'right'" [clrSize]="'lg'" *clrIfOpen>
@ -46,6 +47,10 @@
<div class="bar-summary bar-tooltip-fon" *ngIf="!isNone">
<histogram-chart [isWhiteBackground]="false" [metadata]="passMetadataToChart()"></histogram-chart>
</div>
<div>
<span class="bar-scanning-time">{{'SCANNER.DURATION' | translate }}</span>
<span class="margin-left-5">{{duration()}}</span>
</div>
<div>
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} </span>
<span>{{completeTimestamp | date:'short'}}</span>

View File

@ -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%;
}

View File

@ -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 [
{

View File

@ -20,6 +20,7 @@ describe('ResultTipComponent (inline template)', () => {
end_time: new Date(),
summary: {
total: 124,
fixable: 50,
summary: {
"High": 5,
"Low": 5

View File

@ -65,9 +65,12 @@
</clr-dg-cell>
<clr-dg-cell>{{scanner.url}}</clr-dg-cell>
<clr-dg-cell>
<span *ngIf="scanner.health;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
<ng-template #elseBlock>
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
<span *ngIf="scanner.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
<ng-template #elseBlockLoading>
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
<ng-template #elseBlock>
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
</ng-template>
</ng-template>
</clr-dg-cell>
<clr-dg-cell>{{!scanner.disabled}}</clr-dg-cell>

View File

@ -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();

View File

@ -11,7 +11,7 @@
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
</div>
<clr-control-error *ngIf="!isNameValid">
{{nameTooltip | translate}}
<span id="name-error">{{nameTooltip | translate}}</span>
</clr-control-error>
</div>
</div>
@ -33,7 +33,7 @@
<span class="spinner spinner-inline" [hidden]="!checkEndpointOnGoing"></span>
</div>
<clr-control-error *ngIf="!isEndpointValid || showEndpointError">
{{endpointTooltip | translate}}
<span id="endpoint-error">{{endpointTooltip | translate}}</span>
</clr-control-error>
</div>
</div>
@ -73,7 +73,7 @@
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="!isPasswordValid">
{{"SCANNER.PASSWORD_REQUIRED" | translate}}
<span id="pwd-error">{{"SCANNER.PASSWORD_REQUIRED" | translate}}</span>
</clr-control-error>
</div>
</div>

View File

@ -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();
});
});

View File

@ -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() {
}
}

View File

@ -18,8 +18,13 @@
<span id="scanner-name" class="scanner-name">{{scanner?.name}}</span>
<button *ngIf="scanners && scanners.length > 0" id="edit-scanner" class="btn btn-primary btn-sm" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
<span *ngIf="scanner?.disabled" class="label label-warning ml-1">{{'SCANNER.DISABLED' | translate}}</span>
<span *ngIf="scanner?.health" class="label label-success ml-1">{{'SCANNER.HEALTHY' | translate}}</span>
<span *ngIf="!scanner?.health" class="label label-danger ml-1">{{'SCANNER.UNHEALTHY' | translate}}</span>
<span *ngIf="scanner?.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
<ng-template #elseBlockLoading>
<span *ngIf="scanner?.metadata;else elseBlock" class="label label-success ml-1">{{'SCANNER.HEALTHY' | translate}}</span>
<ng-template #elseBlock>
<span class="label label-danger ml-1">{{'SCANNER.UNHEALTHY' | translate}}</span>
</ng-template>
</ng-template>
</div>
</div>
</div>
@ -33,29 +38,29 @@
</div>
</div>
</div>
<div class="clr-form-control" *ngIf="scanner?.scanner">
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.name">
<label class="clr-control-label">{{'SCANNER.ADAPTER' | translate}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input [ngModel]="scanner?.scanner" readonly class="clr-input width-240" type="text" id="scanner-scanner"
<input [ngModel]="scanner?.metadata?.scanner?.name" readonly class="clr-input width-240" type="text" id="scanner-scanner"
autocomplete="off">
</div>
</div>
</div>
<div class="clr-form-control" *ngIf="scanner?.vendor">
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.vendor">
<label class="clr-control-label">{{'SCANNER.VENDOR' | translate}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input [ngModel]="scanner?.vendor" readonly class="clr-input width-240" type="text" id="scanner-vendor"
<input [ngModel]="scanner?.metadata?.scanner?.vendor" readonly class="clr-input width-240" type="text" id="scanner-vendor"
autocomplete="off">
</div>
</div>
</div>
<div class="clr-form-control" *ngIf="scanner?.version">
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.version">
<label class="clr-control-label">{{'SCANNER.VERSION' | translate}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input [ngModel]="scanner?.version" readonly class="clr-input width-240" type="text" id="scanner-version"
<input [ngModel]="scanner?.metadata?.scanner?.version" readonly class="clr-input width-240" type="text" id="scanner-version"
autocomplete="off">
</div>
</div>
@ -76,8 +81,13 @@
<clr-dg-cell>{{scanner.name}}</clr-dg-cell>
<clr-dg-cell>{{scanner.url}}</clr-dg-cell>
<clr-dg-cell>
<span *ngIf="scanner.health" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
<span *ngIf="!scanner.health" class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
<span *ngIf="scanner.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2">Loading...</span>
<ng-template #elseBlockLoading>
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
<ng-template #elseBlock>
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
</ng-template>
</ng-template>
</clr-dg-cell>
<clr-dg-cell>
<span *ngIf="scanner.is_default" class="label label-info">{{scanner.is_default}}</span>

View File

@ -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

View File

@ -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:"
}
}

View File

@ -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:"
}
}

View File

@ -1261,6 +1261,9 @@
"ENABLED": "Enabled",
"ENABLE": "Enable",
"DISABLE": "Disable",
"DELETE_SUCCESS": "Successfully deleted"
"DELETE_SUCCESS": "Successfully deleted",
"TOTAL": "Total",
"FIXABLE": "Fixable",
"DURATION": "Duration:"
}
}

View File

@ -1286,7 +1286,10 @@
"ENABLED": "Enabled",
"ENABLE": "Enable",
"DISABLE": "Disable",
"DELETE_SUCCESS": "Successfully deleted"
"DELETE_SUCCESS": "Successfully deleted",
"TOTAL": "Total",
"FIXABLE": "Fixable",
"DURATION": "Duration:"
}
}

View File

@ -1291,6 +1291,9 @@
"ENABLED": "Enabled",
"ENABLE": "Enable",
"DISABLE": "Disable",
"DELETE_SUCCESS": "Successfully deleted"
"DELETE_SUCCESS": "Successfully deleted",
"TOTAL": "Total",
"FIXABLE": "Fixable",
"DURATION": "Duration:"
}
}

View File

@ -1288,6 +1288,9 @@
"ENABLED": "启用",
"ENABLE": "启用",
"DISABLE": "禁用",
"DELETE_SUCCESS": "删除成功"
"DELETE_SUCCESS": "删除成功",
"TOTAL": "总计",
"FIXABLE": "可修复",
"DURATION": "扫描用时:"
}
}