Merge pull request #10853 from AllForNothing/fix-scan

Fix scanning function
This commit is contained in:
Will Sun 2020-02-26 18:56:31 +08:00 committed by GitHub
commit 26f71d47b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 361 additions and 160 deletions

View File

@ -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>&nbsp;{{'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>

View File

@ -9,7 +9,10 @@ export class AdditionsService {
constructor(private http: HttpClient) {
}
getDetailByLink(link: string): Observable<any> {
return this.http.get(link);
getDetailByLink(link: string, shouldReturnText?: boolean): Observable<any> {
if (shouldReturnText) {
return this.http.get(link, { observe: 'body', responseType: 'text'} );
}
return this.http.get(link);
}
}

View File

@ -1,38 +1,42 @@
<ng-container *ngIf="additionLinks">
<h4 class="margin-bottom-025">{{'ARTIFACT.ADDITIONS' | translate}}</h4>
<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>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getBuildHistory()">
<button clrTabLink id="build-history">{{ 'REPOSITORY.BUILD_HISTORY' | translate }}</button>
<clr-tab-content *clrIfActive>
<hbr-artifact-build-history [buildHistoryLink]="getBuildHistory()"></hbr-artifact-build-history>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getSummary()">
<button clrTabLink id="summary-link">{{'HELM_CHART.SUMMARY' | translate}}</button>
<clr-tab-content id="summary-content" *clrIfActive>
<hbr-artifact-summary [summaryLink]="getSummary()"></hbr-artifact-summary>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getDependencies()">
<button clrTabLink id="depend-link">{{'HELM_CHART.DEPENDENCIES' | translate}}</button>
<clr-tab-content id="depend-content" *clrIfActive>
<hbr-artifact-dependencies [dependenciesLink]="getDependencies()"></hbr-artifact-dependencies>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getValues()">
<button clrTabLink id="value-link">{{'HELM_CHART.VALUES' | translate}}</button>
<clr-tab-content id="value-content" *clrIfActive>
<hbr-artifact-values [valuesLink]="getValues()"></hbr-artifact-values>
</clr-tab-content>
</clr-tab>
</clr-tabs>
<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 [projectName]="projectName"
[projectId]="projectId"
[repoName]="repoName"
[digest]="digest" [vulnerabilitiesLink]="getVulnerability()"></hbr-artifact-vulnerabilities>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getBuildHistory()">
<button clrTabLink id="build-history">{{ 'REPOSITORY.BUILD_HISTORY' | translate }}</button>
<clr-tab-content *clrIfActive>
<hbr-artifact-build-history [buildHistoryLink]="getBuildHistory()"></hbr-artifact-build-history>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getSummary()">
<button clrTabLink id="summary-link">{{'HELM_CHART.SUMMARY' | translate}}</button>
<clr-tab-content id="summary-content" *clrIfActive>
<hbr-artifact-summary [summaryLink]="getSummary()"></hbr-artifact-summary>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getDependencies()">
<button clrTabLink id="depend-link">{{'HELM_CHART.DEPENDENCIES' | translate}}</button>
<clr-tab-content id="depend-content" *clrIfActive>
<hbr-artifact-dependencies [dependenciesLink]="getDependencies()"></hbr-artifact-dependencies>
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="getValues()">
<button clrTabLink id="value-link">{{'HELM_CHART.VALUES' | translate}}</button>
<clr-tab-content id="value-content" *clrIfActive>
<hbr-artifact-values [valuesLink]="getValues()"></hbr-artifact-values>
</clr-tab-content>
</clr-tab>
</clr-tabs>
</div>
</ng-container>

View File

@ -1,3 +1,6 @@
.margin-bottom-025 {
margin-bottom: 0.25rem;
}
.min-15 {
min-height: 15rem;
}

View File

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

View File

@ -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>&nbsp;{{'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>&nbsp;{{'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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,3 +5,7 @@
border: solid 1px #ddd;
}
}
.center {
justify-content: center;
align-items: center;
}

View File

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

View File

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

View File

@ -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">
<td class="left">{{item?.key}}</td>
<td class="left">{{item?.value}}</td>
</tr>
<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>

View File

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

View File

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

View File

@ -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 => {
this.values = 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];
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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