mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-18 16:25:16 +01:00
Merge pull request #9518 from AllForNothing/scanner-im-v2
Improve scanner UI
This commit is contained in:
commit
ee2de96c91
@ -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 }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -315,6 +315,7 @@ export interface VulnerabilitySummary {
|
||||
}
|
||||
export interface SeveritySummary {
|
||||
total: number;
|
||||
fixable: number;
|
||||
summary: {[key: string]: number};
|
||||
}
|
||||
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -48,6 +48,7 @@ describe("TagDetailComponent (inline template)", () => {
|
||||
end_time: new Date(),
|
||||
summary: {
|
||||
total: 124,
|
||||
fixable: 50,
|
||||
summary: {
|
||||
"High": 5,
|
||||
"Low": 5
|
||||
|
@ -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 {
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
end_time: new Date(),
|
||||
summary: {
|
||||
total: 124,
|
||||
fixable: 50,
|
||||
summary: {
|
||||
"High": 5,
|
||||
"Low": 5
|
||||
|
@ -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> {{'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> {{'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>
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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 [
|
||||
{
|
||||
|
@ -20,6 +20,7 @@ describe('ResultTipComponent (inline template)', () => {
|
||||
end_time: new Date(),
|
||||
summary: {
|
||||
total: 124,
|
||||
fixable: 50,
|
||||
summary: {
|
||||
"High": 5,
|
||||
"Low": 5
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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:"
|
||||
}
|
||||
}
|
||||
|
@ -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:"
|
||||
}
|
||||
}
|
||||
|
@ -1261,6 +1261,9 @@
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
}
|
||||
}
|
||||
|
@ -1286,7 +1286,10 @@
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1291,6 +1291,9 @@
|
||||
"ENABLED": "Enabled",
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"DELETE_SUCCESS": "Successfully deleted"
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
}
|
||||
}
|
||||
|
@ -1288,6 +1288,9 @@
|
||||
"ENABLED": "启用",
|
||||
"ENABLE": "启用",
|
||||
"DISABLE": "禁用",
|
||||
"DELETE_SUCCESS": "删除成功"
|
||||
"DELETE_SUCCESS": "删除成功",
|
||||
"TOTAL": "总计",
|
||||
"FIXABLE": "可修复",
|
||||
"DURATION": "扫描用时:"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user