mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-29 21:54:13 +01:00
Merge pull request #10853 from AllForNothing/fix-scan
Fix scanning function
This commit is contained in:
commit
26f71d47b3
@ -71,7 +71,7 @@
|
||||
[(clrDgSelected)]="selectedRow">
|
||||
<clr-dg-action-bar>
|
||||
<button [clrLoading]="scanBtnState" type="button" class="btn btn-secondary scan-btn"
|
||||
[disabled]="!(canScanNow() && selectedRow.length==1 && hasEnabledScanner && !depth)" (click)="scanNow()">
|
||||
[disabled]="!(canScanNow() && selectedRow.length==1 && hasEnabledScanner && hasScanImagePermission && !depth)" (click)="scanNow()">
|
||||
<clr-icon shape="shield-check" size="16"></clr-icon> {{'VULNERABILITY.SCAN_NOW' | translate}}
|
||||
</button>
|
||||
|
||||
@ -225,7 +225,7 @@
|
||||
<div class="cell">
|
||||
<hbr-vulnerability-bar [scanner]="handleScanOverview(artifact.scan_overview)?.scanner"
|
||||
(submitFinish)="submitFinish($event)" [projectName]="projectName" [repoName]="repoName"
|
||||
[artifactId]="artifact.id" [summary]="handleScanOverview(artifact.scan_overview)">
|
||||
[artifactDigest]="artifact.digest" [summary]="handleScanOverview(artifact.scan_overview)">
|
||||
</hbr-vulnerability-bar>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
|
@ -9,7 +9,10 @@ export class AdditionsService {
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
getDetailByLink(link: string): Observable<any> {
|
||||
getDetailByLink(link: string, shouldReturnText?: boolean): Observable<any> {
|
||||
if (shouldReturnText) {
|
||||
return this.http.get(link, { observe: 'body', responseType: 'text'} );
|
||||
}
|
||||
return this.http.get(link);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
<ng-container *ngIf="additionLinks">
|
||||
<h4 class="margin-bottom-025">{{'ARTIFACT.ADDITIONS' | translate}}</h4>
|
||||
<div class="min-15">
|
||||
<clr-tabs>
|
||||
<clr-tab *ngIf="getVulnerability()">
|
||||
<button clrTabLink id="vulnerability">{{'REPOSITORY.VULNERABILITY' | translate}}</button>
|
||||
<clr-tab-content id="vulnerability-content" *clrIfActive>
|
||||
<hbr-artifact-vulnerabilities *ngIf="getBuildHistory()"
|
||||
[vulnerabilitiesLink]="getVulnerability()"></hbr-artifact-vulnerabilities>
|
||||
<hbr-artifact-vulnerabilities [projectName]="projectName"
|
||||
[projectId]="projectId"
|
||||
[repoName]="repoName"
|
||||
[digest]="digest" [vulnerabilitiesLink]="getVulnerability()"></hbr-artifact-vulnerabilities>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab *ngIf="getBuildHistory()">
|
||||
@ -33,6 +36,7 @@
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
</clr-tabs>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
.margin-bottom-025 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.min-15 {
|
||||
min-height: 15rem;
|
||||
}
|
@ -10,6 +10,13 @@ import { AdditionLink } from "../../../../../../ng-swagger-gen/models/addition-l
|
||||
})
|
||||
export class ArtifactAdditionsComponent implements OnInit {
|
||||
@Input() additionLinks: AdditionLinks;
|
||||
@Input() projectName: string;
|
||||
@Input()
|
||||
projectId: number;
|
||||
@Input()
|
||||
repoName: string;
|
||||
@Input()
|
||||
digest: string;
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -2,7 +2,6 @@
|
||||
<div>
|
||||
<div class="row flex-items-xs-right rightPos">
|
||||
<div class="flex-xs-middle option-right">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'VULNERABILITY.PLACEHOLDER' | translate}}"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -10,7 +9,19 @@
|
||||
<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-secondary" [clrLoading]="scanBtnState" [disabled]="!hasEnabledScanner"><clr-icon shape="shield-check" size="16"></clr-icon> {{'VULNERABILITY.SCAN_NOW' | translate}}</button>
|
||||
<div class="clr-row center">
|
||||
<div class="clr-col-1">
|
||||
<button (click)="scanNow()" type="button" class="btn btn-secondary" [clrLoading]="scanBtnState" [disabled]="!(hasEnabledScanner && hasScanningPermission && !onSendingScanCommand)"><clr-icon shape="shield-check" size="16"></clr-icon> {{'VULNERABILITY.SCAN_NOW' | translate}}</button>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<div [hidden]="!shouldShowBar()">
|
||||
<hbr-vulnerability-bar [scanner]="scanner"
|
||||
(submitFinish)="submitFinish($event)" [projectName]="projectName" [repoName]="repoName"
|
||||
[artifactDigest]="digest">
|
||||
</hbr-vulnerability-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'id'">{{'VULNERABILITY.GRID.COLUMN_ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="severitySort">{{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}}</clr-dg-column>
|
||||
|
@ -11,4 +11,38 @@
|
||||
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.center {
|
||||
align-items: center;
|
||||
}
|
||||
.label-critical {
|
||||
background:red;
|
||||
color:#621501;
|
||||
}
|
||||
|
||||
|
||||
.label-danger {
|
||||
background:#e64524!important;
|
||||
color:#621501!important;
|
||||
}
|
||||
.label-medium {
|
||||
background-color: orange;
|
||||
color:#621501;
|
||||
}
|
||||
.label-low {
|
||||
background: #007CBB;
|
||||
color:#cab6b1;
|
||||
}
|
||||
.label-negligible {
|
||||
background-color: green;
|
||||
color:#bad7ba;
|
||||
}
|
||||
.label-unknown {
|
||||
background-color: grey;
|
||||
color:#bad7ba;
|
||||
}
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ArtifactVulnerabilitiesComponent } from './artifact-vulnerabilities.component';
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
@ -7,9 +6,11 @@ import { AdditionsService } from "../additions.service";
|
||||
import { of } from "rxjs";
|
||||
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { VulnerabilityItem } from "../../../../../../lib/services";
|
||||
import { ScanningResultService, UserPermissionService, VulnerabilityItem } from "../../../../../../lib/services";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
import { ChannelService } from "../../../../../../lib/services/channel.service";
|
||||
import { DEFAULT_SUPPORTED_MIME_TYPE } from "../../../../../../lib/utils/utils";
|
||||
|
||||
|
||||
describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
@ -35,16 +36,35 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
description: 'just a test'
|
||||
},
|
||||
];
|
||||
let scanOverview = {};
|
||||
scanOverview[DEFAULT_SUPPORTED_MIME_TYPE] = {};
|
||||
scanOverview[DEFAULT_SUPPORTED_MIME_TYPE].vulnerabilities = mockedVulnerabilities;
|
||||
const mockedLink: AdditionLink = {
|
||||
absolute: false,
|
||||
href: '/test'
|
||||
};
|
||||
const fakedAdditionsService = {
|
||||
getDetailByLink() {
|
||||
return of(mockedVulnerabilities);
|
||||
return of(scanOverview);
|
||||
}
|
||||
};
|
||||
const fakedUserPermissionService = {
|
||||
hasProjectPermissions() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
const fakedScanningResultService = {
|
||||
getProjectScanner() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
const fakedChannelService = {
|
||||
ArtifactDetail$: {
|
||||
subscribe() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@ -60,7 +80,10 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
declarations: [ArtifactVulnerabilitiesComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{provide: AdditionsService, useValue: fakedAdditionsService}
|
||||
{provide: AdditionsService, useValue: fakedAdditionsService},
|
||||
{provide: UserPermissionService, useValue: fakedUserPermissionService},
|
||||
{provide: ScanningResultService, useValue: fakedScanningResultService},
|
||||
{provide: ChannelService, useValue: fakedChannelService},
|
||||
],
|
||||
schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
@ -72,6 +95,10 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ArtifactVulnerabilitiesComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.hasScanningPermission = true;
|
||||
component.hasEnabledScanner = true;
|
||||
component.vulnerabilitiesLink = mockedLink;
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@ -79,8 +106,6 @@ describe('ArtifactVulnerabilitiesComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should get vulnerability list and render', async () => {
|
||||
component.vulnerabilitiesLink = mockedLink;
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||
|
@ -1,11 +1,23 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { AdditionsService } from "../additions.service";
|
||||
import { ClrDatagridComparatorInterface, ClrLoadingState } from "@clr/angular";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { VulnerabilityItem } from "../../../../../../lib/services";
|
||||
import {
|
||||
ScannerVo,
|
||||
ScanningResultService,
|
||||
UserPermissionService,
|
||||
USERSTATICPERMISSION,
|
||||
VulnerabilityItem
|
||||
} from "../../../../../../lib/services";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
import { SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../../../lib/utils/utils";
|
||||
import {
|
||||
DEFAULT_SUPPORTED_MIME_TYPE,
|
||||
SEVERITY_LEVEL_MAP,
|
||||
VULNERABILITY_SEVERITY
|
||||
} from "../../../../../../lib/utils/utils";
|
||||
import { ChannelService } from "../../../../../../lib/services/channel.service";
|
||||
import { ResultBarChartComponent } from "../../../vulnerability-scanning/result-bar-chart.component";
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-artifact-vulnerabilities',
|
||||
@ -15,17 +27,33 @@ import { SEVERITY_LEVEL_MAP, VULNERABILITY_SEVERITY } from "../../../../../../li
|
||||
export class ArtifactVulnerabilitiesComponent implements OnInit {
|
||||
@Input()
|
||||
vulnerabilitiesLink: AdditionLink;
|
||||
@Input()
|
||||
projectName: string;
|
||||
@Input()
|
||||
projectId: number;
|
||||
@Input()
|
||||
repoName: string;
|
||||
@Input()
|
||||
digest: string;
|
||||
scan_overview: any;
|
||||
scanner: ScannerVo;
|
||||
|
||||
scanningResults: VulnerabilityItem[] = [];
|
||||
loading: boolean = false;
|
||||
shouldShowLoading: boolean = true;
|
||||
hasEnabledScanner: boolean = false;
|
||||
scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
severitySort: ClrDatagridComparatorInterface<VulnerabilityItem>;
|
||||
|
||||
hasScanningPermission: boolean = false;
|
||||
onSendingScanCommand: boolean = false;
|
||||
hasShowLoading: boolean = false;
|
||||
@ViewChild(ResultBarChartComponent, {static: false})
|
||||
resultBarChartComponent: ResultBarChartComponent;
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private additionsService: AdditionsService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private scanningService: ScanningResultService,
|
||||
private channel: ChannelService,
|
||||
) {
|
||||
const that = this;
|
||||
this.severitySort = {
|
||||
@ -37,29 +65,61 @@ export class ArtifactVulnerabilitiesComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.getVulnerabilities();
|
||||
this.getScanningPermission();
|
||||
this.getProjectScanner();
|
||||
this.channel.ArtifactDetail$.subscribe(tag => {
|
||||
this.getVulnerabilities();
|
||||
});
|
||||
}
|
||||
|
||||
getVulnerabilities() {
|
||||
if (this.vulnerabilitiesLink
|
||||
&& !this.vulnerabilitiesLink.absolute
|
||||
&& this.vulnerabilitiesLink.href) {
|
||||
// only show loading for one time
|
||||
if (this.shouldShowLoading) {
|
||||
if (!this.hasShowLoading) {
|
||||
this.loading = true;
|
||||
this.shouldShowLoading = false;
|
||||
this.hasShowLoading = true;
|
||||
}
|
||||
this.additionsService.getDetailByLink(this.vulnerabilitiesLink.href)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.scanningResults = res;
|
||||
this.scan_overview = res;
|
||||
if (this.scan_overview && this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]) {
|
||||
this.scanningResults = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].vulnerabilities;
|
||||
this.scanner = this.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE].scanner;
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getScanningPermission(): void {
|
||||
const permissions = [
|
||||
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE },
|
||||
];
|
||||
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
|
||||
this.hasScanningPermission = results[0];
|
||||
// only has label permission
|
||||
}, error => this.errorHandler.error(error));
|
||||
}
|
||||
getProjectScanner(): void {
|
||||
this.hasEnabledScanner = false;
|
||||
this.scanBtnState = ClrLoadingState.LOADING;
|
||||
this.scanningService.getProjectScanner(this.projectId)
|
||||
.subscribe(response => {
|
||||
if (response && "{}" !== JSON.stringify(response) && !response.disabled
|
||||
&& response.health === "healthy") {
|
||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
||||
this.hasEnabledScanner = true;
|
||||
} else {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
}, error => {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
getLevel(v: VulnerabilityItem): number {
|
||||
if (v && v.severity && SEVERITY_LEVEL_MAP[v.severity]) {
|
||||
return SEVERITY_LEVEL_MAP[v.severity];
|
||||
@ -88,4 +148,15 @@ export class ArtifactVulnerabilitiesComponent implements OnInit {
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
scanNow() {
|
||||
this.onSendingScanCommand = true;
|
||||
this.channel.publishScanEvent(this.repoName + "/" + this.digest);
|
||||
}
|
||||
submitFinish(e: boolean) {
|
||||
this.onSendingScanCommand = e;
|
||||
}
|
||||
shouldShowBar(): boolean {
|
||||
return this.resultBarChartComponent
|
||||
&& (this.resultBarChartComponent.queued || this.resultBarChartComponent.scanning || this.resultBarChartComponent.error);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,10 @@
|
||||
<div class="row flex-items-xs-center dep-container">
|
||||
<div class="col-md-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left">{{'HELM_CHART.NAME' | translate}}</th>
|
||||
<th class="left">{{'HELM_CHART.VERSION' | translate}}</th>
|
||||
<th class="left">{{'HELM_CHART.REPO' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let dep of dependencyList">
|
||||
<td class="left">{{dep.name}}</td>
|
||||
<td class="left">{{dep.version}}</td>
|
||||
<td class="left"><a href="{{dep.repository}}">{{dep.repository}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column>{{'HELM_CHART.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'HELM_CHART.REPO' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let dep of dependencyList" [clrDgItem]='dep' class="history-item">
|
||||
<clr-dg-cell class="left">{{dep.name}}</clr-dg-cell>
|
||||
<clr-dg-cell class="left">{{dep.version}}</clr-dg-cell>
|
||||
<clr-dg-cell class="left"><a href="{{dep.repository}}">{{dep.repository}}</a></clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
</clr-datagrid>
|
@ -7,6 +7,7 @@ import { ArtifactDependency } from "../models";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../../lib/entities/service.config";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { CURRENT_BASE_HREF } from "../../../../../../lib/utils/utils";
|
||||
|
||||
|
||||
@ -41,7 +42,8 @@ describe('DependenciesComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot()
|
||||
TranslateModule.forRoot(),
|
||||
ClarityModule
|
||||
],
|
||||
declarations: [DependenciesComponent],
|
||||
providers: [
|
||||
@ -70,7 +72,7 @@ describe('DependenciesComponent', () => {
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const trs = fixture.nativeElement.getElementsByTagName('tr');
|
||||
expect(trs.length).toEqual(3);
|
||||
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||
expect(rows.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,8 @@ import { ArtifactDependency } from "../models";
|
||||
import { AdditionsService } from "../additions.service";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
import { pipe } from "rxjs";
|
||||
import { finalize } from "rxjs/operators";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -18,6 +20,7 @@ export class DependenciesComponent implements OnInit {
|
||||
@Input()
|
||||
dependenciesLink: AdditionLink;
|
||||
dependencyList: ArtifactDependency[] = [];
|
||||
loading: boolean = false;
|
||||
constructor( private errorHandler: ErrorHandler,
|
||||
private additionsService: AdditionsService) {}
|
||||
|
||||
@ -28,7 +31,10 @@ export class DependenciesComponent implements OnInit {
|
||||
if (this.dependenciesLink
|
||||
&& !this.dependenciesLink.absolute
|
||||
&& this.dependenciesLink.href) {
|
||||
this.additionsService.getDetailByLink(this.dependenciesLink.href).subscribe(
|
||||
this.loading = true;
|
||||
this.additionsService.getDetailByLink(this.dependenciesLink.href)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.dependencyList = res;
|
||||
}, error => {
|
||||
|
@ -34,7 +34,7 @@ export interface Addition {
|
||||
export enum ADDITIONS {
|
||||
VULNERABILITIES = 'vulnerabilities',
|
||||
BUILD_HISTORY = 'build_history',
|
||||
SUMMARY = 'readme',
|
||||
SUMMARY = 'readme.md',
|
||||
VALUES = 'values.yaml',
|
||||
DEPENDENCIES = 'dependencies'
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
<div class="row content-wrapper">
|
||||
<div class="row content-wrapper" *ngIf="!loading">
|
||||
<div class="col-md-8 md-container pl-1">
|
||||
<div *ngIf="readme" class="md-div" [innerHTML]="readme | markdown"></div>
|
||||
<div *ngIf="!readme">{{'HELM_CHART.NO_README' | translate}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table"></table>
|
||||
<div *ngIf="loading" class="clr-row mt-3 center">
|
||||
<span class="spinner spinner-md"></span>
|
||||
</div>
|
@ -5,3 +5,7 @@
|
||||
border: solid 1px #ddd;
|
||||
}
|
||||
}
|
||||
.center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -195,6 +195,6 @@ describe('SummaryComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const tables = fixture.nativeElement.getElementsByTagName('table');
|
||||
expect(tables.length).toEqual(2);
|
||||
expect(tables.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
import { AdditionsService } from "../additions.service";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
import { finalize } from "rxjs/operators";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -16,6 +17,7 @@ import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
export class SummaryComponent implements OnInit {
|
||||
@Input() summaryLink: AdditionLink;
|
||||
readme: string;
|
||||
loading: boolean = false;
|
||||
constructor(
|
||||
private errorHandler: ErrorHandler,
|
||||
private additionsService: AdditionsService
|
||||
@ -28,7 +30,10 @@ export class SummaryComponent implements OnInit {
|
||||
if (this.summaryLink
|
||||
&& !this.summaryLink.absolute
|
||||
&& this.summaryLink.href) {
|
||||
this.additionsService.getDetailByLink(this.summaryLink.href).subscribe(
|
||||
this.loading = true;
|
||||
this.additionsService.getDetailByLink(this.summaryLink.href, true)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.readme = res;
|
||||
}, error => {
|
||||
|
@ -2,22 +2,34 @@
|
||||
<div *ngIf="valueMode" class="title-container">
|
||||
<label>{{'HELM_CHART.SHOW_KV' | translate }}</label>
|
||||
</div>
|
||||
<div *ngIf="!valueMode" class="title-container">
|
||||
<label>{{'HELM_CHART.SHOW_YAML' | translate }}</label>
|
||||
</div>
|
||||
<div class="switch-container">
|
||||
<span class="card-btn" (click)="showYamlFile(false)" (mouseenter)="mouseEnter('value') " (mouseleave)="mouseLeave('value')">
|
||||
<clr-icon size="24" shape="view-list" title='list values' [ngClass]="{'is-highlight': isValueMode || isHovering('value') }"></clr-icon>
|
||||
</span>
|
||||
<span class="list-btn" (click)="showYamlFile(true)" (mouseenter)="mouseEnter('yaml') " (mouseleave)="mouseLeave('yaml')">
|
||||
<clr-icon size="24" shape="file" title="yaml file" [ngClass]="{'is-highlight': !isValueMode || isHovering('yaml') }"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row value-container">
|
||||
<div class="row value-container" *ngIf="!loading">
|
||||
<div class="col-xs-8" *ngIf="valueMode">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr *ngFor="let item of values | keyvalue">
|
||||
<tr *ngFor="let item of valuesObj | keyvalue">
|
||||
<td class="left">{{item?.key}}</td>
|
||||
<td class="left">{{item?.value}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-8" *ngIf="!valueMode">
|
||||
<div class="yaml-container" [innerHTML]="values | language : 'yaml' | markdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="loading" class="clr-row mt-1 center">
|
||||
<span class="spinner spinner-md"></span>
|
||||
</div>
|
@ -1,15 +1,16 @@
|
||||
.value-container {
|
||||
::ng-deep pre {
|
||||
min-height: fit-content;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.values-header {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
|
||||
pre {
|
||||
max-height: max-content;
|
||||
padding-left: 21px;
|
||||
}
|
||||
.center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -16,11 +16,11 @@ describe('ValuesComponent', () => {
|
||||
let component: ValuesComponent;
|
||||
let fixture: ComponentFixture<ValuesComponent>;
|
||||
|
||||
const mockedValues = {
|
||||
"adminserver.image.pullPolicy": "IfNotPresent",
|
||||
"adminserver.image.repository": "vmware/harbor-adminserver",
|
||||
"adminserver.image.tag": "dev"
|
||||
};
|
||||
const mockedValues = `
|
||||
adminserver.image.pullPolicy: IfNotPresent,
|
||||
adminserver.image.repository: vmware/harbor-adminserver,
|
||||
adminserver.image.tag: dev
|
||||
`;
|
||||
const fakedAdditionsService = {
|
||||
getDetailByLink() {
|
||||
return of(mockedValues);
|
||||
@ -56,15 +56,16 @@ describe('ValuesComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ValuesComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.valueMode = true;
|
||||
component.valuesLink = mockedLink;
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
/*it('should create', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});*/
|
||||
});
|
||||
it('should get values and render', async () => {
|
||||
component.valuesLink = mockedLink;
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const trs = fixture.nativeElement.getElementsByTagName('tr');
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
import { AdditionsService } from "../additions.service";
|
||||
import { AdditionLink } from "../../../../../../../ng-swagger-gen/models/addition-link";
|
||||
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
|
||||
|
||||
import * as yaml from "js-yaml";
|
||||
import { finalize } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-artifact-values",
|
||||
@ -17,22 +18,31 @@ export class ValuesComponent implements OnInit {
|
||||
@Input()
|
||||
valuesLink: AdditionLink;
|
||||
|
||||
values: any;
|
||||
values: string;
|
||||
valuesObj: object = {};
|
||||
|
||||
// Default set to yaml file
|
||||
valueMode = true;
|
||||
valueMode = false;
|
||||
valueHover = false;
|
||||
yamlHover = true;
|
||||
|
||||
loading: boolean = false;
|
||||
constructor(private errorHandler: ErrorHandler,
|
||||
private additionsService: AdditionsService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.valuesLink && !this.valuesLink.absolute && this.valuesLink.href) {
|
||||
this.additionsService.getDetailByLink(this.valuesLink.href).subscribe(
|
||||
this.loading = true;
|
||||
this.additionsService.getDetailByLink(this.valuesLink.href, true)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
res => {
|
||||
try {
|
||||
this.format(yaml.safeLoad(res));
|
||||
this.values = res;
|
||||
} catch (e) {
|
||||
this.errorHandler.error(e);
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
@ -71,4 +81,21 @@ export class ValuesComponent implements OnInit {
|
||||
this.yamlHover = false;
|
||||
}
|
||||
}
|
||||
format(obj: object) {
|
||||
for (let name in obj) {
|
||||
if (obj.hasOwnProperty(name)) {
|
||||
if (obj[name] instanceof Object) {
|
||||
for (let key in obj[name]) {
|
||||
if (obj[name].hasOwnProperty(key)) {
|
||||
obj[`${name}.${key}`] = obj[name][key];
|
||||
}
|
||||
}
|
||||
delete obj[name];
|
||||
this.format(obj);
|
||||
} else {
|
||||
this.valuesObj[name] = obj[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,12 @@
|
||||
(refreshArtifact)="refreshArtifact()"></artifact-tag>
|
||||
|
||||
<!-- Additions -->
|
||||
<artifact-additions [additionLinks]="artifact?.addition_links"></artifact-additions>
|
||||
<artifact-additions
|
||||
[projectName]="projectName"
|
||||
[projectId]="projectId"
|
||||
[repoName]="repositoryName"
|
||||
[digest]="artifactDigest"
|
||||
[additionLinks]="artifact?.addition_links"></artifact-additions>
|
||||
</ng-container>
|
||||
<div *ngIf="loading" class="clr-row mt-3 center">
|
||||
<span class="spinner spinner-md"></span>
|
||||
|
@ -22,7 +22,7 @@
|
||||
</clr-control-error>
|
||||
</label>
|
||||
</label>
|
||||
<label>
|
||||
<label class="ml-1">
|
||||
<button type="button" class="btn btn-sm btn-outline" (click)="cancelAddTag()">{{
|
||||
'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
|
@ -119,7 +119,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy
|
||||
return this.systemInfo && this.systemInfo.has_ca_root;
|
||||
}
|
||||
|
||||
goIntoRepo(repoEvt: RepositoryItem): void {
|
||||
goIntoRepo(repoEvt: NewRepository): void {
|
||||
let linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.split('/')[1]];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import {
|
||||
JobLogService,
|
||||
ScanningResultDefaultService,
|
||||
ScanningResultService,
|
||||
VulnerabilitySummary
|
||||
} from "../../../../lib/services";
|
||||
import { CURRENT_BASE_HREF, VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
|
||||
import { SharedModule } from "../../../../lib/utils/shared/shared.module";
|
||||
import { ErrorHandler } from "../../../../lib/utils/error-handler";
|
||||
import { ChannelService } from "../../../../lib/services/channel.service";
|
||||
import { ArtifactDefaultService, ArtifactService } from "../artifact/artifact.service";
|
||||
import { NativeReportSummary } from "../../../../../ng-swagger-gen/models/native-report-summary";
|
||||
|
||||
describe('ResultBarChartComponent (inline template)', () => {
|
||||
let component: ResultBarChartComponent;
|
||||
@ -24,10 +24,10 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
let testConfig: IServiceConfig = {
|
||||
vulnerabilityScanningBaseEndpoint: CURRENT_BASE_HREF + "/vulnerability/testing"
|
||||
};
|
||||
let mockData: VulnerabilitySummary = {
|
||||
let mockData: NativeReportSummary = {
|
||||
scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS,
|
||||
severity: "High",
|
||||
end_time: new Date(),
|
||||
end_time: new Date().toUTCString(),
|
||||
summary: {
|
||||
total: 124,
|
||||
fixable: 50,
|
||||
@ -64,7 +64,7 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ResultBarChartComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.artifactId = "mockTag";
|
||||
component.artifactDigest = "mockTag";
|
||||
component.summary = mockData;
|
||||
|
||||
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||
|
@ -7,13 +7,19 @@ import {
|
||||
} from '@angular/core';
|
||||
import { Subscription , timer} from "rxjs";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { ScannerVo, ScanningResultService, VulnerabilitySummary } from "../../../../lib/services";
|
||||
import { ArtifactDefaultService } from "../artifact/artifact.service";
|
||||
import { ScannerVo, ScanningResultService } from "../../../../lib/services";
|
||||
import { ErrorHandler } from "../../../../lib/utils/error-handler";
|
||||
import { ChannelService } from "../../../../lib/services/channel.service";
|
||||
import { clone, CURRENT_BASE_HREF, DEFAULT_SUPPORTED_MIME_TYPE, VULNERABILITY_SCAN_STATUS } from "../../../../lib/utils/utils";
|
||||
import { ArtifactFront as Artifact } from "../artifact/artifact";
|
||||
import { NativeReportSummary } from '../../../../../ng-swagger-gen/models/native-report-summary';
|
||||
import {
|
||||
clone,
|
||||
CURRENT_BASE_HREF,
|
||||
DEFAULT_SUPPORTED_MIME_TYPE,
|
||||
VULNERABILITY_SCAN_STATUS
|
||||
} from "../../../../lib/utils/utils";
|
||||
import { ArtifactService } from "../../../../../ng-swagger-gen/services/artifact.service";
|
||||
import { Artifact } from "../../../../../ng-swagger-gen/models/artifact";
|
||||
import { NativeReportSummary } from "../../../../../ng-swagger-gen/models/native-report-summary";
|
||||
|
||||
|
||||
const STATE_CHECK_INTERVAL: number = 3000; // 3s
|
||||
const RETRY_TIMES: number = 3;
|
||||
@ -27,8 +33,8 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
@Input() scanner: ScannerVo;
|
||||
@Input() repoName: string = "";
|
||||
@Input() projectName: string = "";
|
||||
@Input() artifactId: string = "";
|
||||
@Input() summary: VulnerabilitySummary;
|
||||
@Input() artifactDigest: string = "";
|
||||
@Input() summary: NativeReportSummary;
|
||||
onSubmitting: boolean = false;
|
||||
retryCounter: number = 0;
|
||||
stateCheckTimer: Subscription;
|
||||
@ -38,11 +44,10 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
submitFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
|
||||
constructor(
|
||||
private artifactService: ArtifactDefaultService,
|
||||
private artifactService: ArtifactService,
|
||||
private scanningService: ScanningResultService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private channel: ChannelService,
|
||||
private ref: ChangeDetectorRef,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -55,9 +60,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
this.getSummary();
|
||||
});
|
||||
}
|
||||
this.scanSubscription = this.channel.scanCommand$.subscribe((artifactId: string) => {
|
||||
let myFullTag: string = this.repoName + "/" + this.artifactId;
|
||||
if (myFullTag === artifactId) {
|
||||
this.scanSubscription = this.channel.scanCommand$.subscribe((artifactDigest: string) => {
|
||||
let myFullTag: string = this.repoName + "/" + this.artifactDigest;
|
||||
if (myFullTag === artifactDigest) {
|
||||
this.scanNow();
|
||||
}
|
||||
});
|
||||
@ -107,26 +112,21 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.repoName || !this.artifactId) {
|
||||
if (!this.repoName || !this.artifactDigest) {
|
||||
console.log("bad repository or tag");
|
||||
return;
|
||||
}
|
||||
|
||||
this.onSubmitting = true;
|
||||
|
||||
this.scanningService.startVulnerabilityScanning(this.projectName, this.repoName, this.artifactId)
|
||||
this.scanningService.startVulnerabilityScanning(this.projectName, this.repoName, this.artifactDigest)
|
||||
.pipe(finalize(() => this.submitFinish.emit(false)))
|
||||
.subscribe(() => {
|
||||
this.onSubmitting = false;
|
||||
|
||||
// Forcely change status to queued after successful submitting
|
||||
this.summary = {
|
||||
scan_status: VULNERABILITY_SCAN_STATUS.PENDING,
|
||||
};
|
||||
|
||||
// Forcely refresh view
|
||||
this.forceRefreshView(1000);
|
||||
|
||||
// Start check status util the job is done
|
||||
if (!this.stateCheckTimer) {
|
||||
// Avoid duplicated subscribing
|
||||
@ -145,20 +145,22 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getSummary(): void {
|
||||
if (!this.repoName || !this.artifactId) {
|
||||
if (!this.repoName || !this.artifactDigest) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.tagService.getTag(this.repoName, this.artifactId)
|
||||
this.artifactService.getArtifactFromDigest(this.projectName, this.repoName, this.artifactId)
|
||||
this.artifactService.getArtifact({
|
||||
projectName: this.projectName,
|
||||
repositoryName: this.repoName,
|
||||
reference: this.artifactDigest,
|
||||
withScanOverview: true
|
||||
})
|
||||
.subscribe((artifact: Artifact) => {
|
||||
// To keep the same summary reference, use value copy.
|
||||
if (artifact.scan_overview) {
|
||||
this.copyValue(artifact.scan_overview[DEFAULT_SUPPORTED_MIME_TYPE]);
|
||||
}
|
||||
// Forcely refresh view
|
||||
this.forceRefreshView(1000);
|
||||
|
||||
if (!this.queued && !this.scanning) {
|
||||
// Scanning should be done
|
||||
if (this.stateCheckTimer) {
|
||||
@ -185,22 +187,8 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
if (!this.summary || !newVal || !newVal.scan_status) { return; }
|
||||
this.summary = clone(newVal);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
viewLog(): string {
|
||||
return `${ CURRENT_BASE_HREF }/projects/${this.projectName}/repositories/${this.repoName}
|
||||
/artifacts/${this.artifactId}/scan/${this.summary.report_id}/log`;
|
||||
/artifacts/${this.artifactDigest}/scan/${this.summary.report_id}/log`;
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export abstract class ScanningResultService {
|
||||
abstract startVulnerabilityScanning(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
artifactId: string
|
||||
artifactDigest: string
|
||||
): Observable<any>;
|
||||
|
||||
/**
|
||||
@ -148,15 +148,15 @@ export class ScanningResultDefaultService extends ScanningResultService {
|
||||
startVulnerabilityScanning(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
artifactId: string
|
||||
artifactDigest: string
|
||||
): Observable<any> {
|
||||
if (!repoName || repoName.trim() === "" || !artifactId || artifactId.trim() === "") {
|
||||
if (!repoName || repoName.trim() === "" || !artifactDigest || artifactDigest.trim() === "") {
|
||||
return observableThrowError("Bad argument");
|
||||
}
|
||||
|
||||
return this.http
|
||||
.post(
|
||||
`${ CURRENT_BASE_HREF }/projects//${projectName}/repositories/${repoName}/artifacts/${artifactId}/scan`,
|
||||
`${ CURRENT_BASE_HREF }/projects//${projectName}/repositories/${repoName}/artifacts/${artifactDigest}/scan`,
|
||||
HTTP_JSON_OPTIONS
|
||||
)
|
||||
.pipe(map(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user