mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-30 06:03:45 +01:00
Implement tag detail component & refactor vul summary bar chart
This commit is contained in:
parent
4d2a2363a7
commit
2072fc237e
@ -45,6 +45,7 @@ export interface Tag extends Base {
|
||||
author: string;
|
||||
created: Date;
|
||||
signature?: string;
|
||||
vulnerability?: VulnerabilitySummary;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,28 +158,28 @@ export interface SystemInfo {
|
||||
|
||||
//Not finalized yet
|
||||
export enum VulnerabilitySeverity {
|
||||
LOW, MEDIUM, HIGH, UNKNOWN, NONE
|
||||
NONE, UNKNOWN, LOW, MEDIUM, HIGH
|
||||
}
|
||||
|
||||
export interface ScanningBaseResult {
|
||||
export interface VulnerabilityBase {
|
||||
id: string;
|
||||
severity: VulnerabilitySeverity;
|
||||
package: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface ScanningDetailResult extends ScanningBaseResult {
|
||||
export interface VulnerabilityItem extends VulnerabilityBase {
|
||||
fixedVersion: string;
|
||||
layer: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface ScanningResultSummary {
|
||||
totalComponents: number;
|
||||
noneComponents: number;
|
||||
completeTimestamp: Date;
|
||||
high: ScanningBaseResult[];
|
||||
medium: ScanningBaseResult[];
|
||||
low: ScanningBaseResult[];
|
||||
unknown: ScanningBaseResult[];
|
||||
export interface VulnerabilitySummary {
|
||||
total_package: number;
|
||||
package_with_none: number;
|
||||
package_with_high?: number;
|
||||
package_with_medium?: number;
|
||||
package_With_low?: number;
|
||||
package_with_unknown?: number;
|
||||
complete_timestamp: Date;
|
||||
}
|
@ -5,8 +5,10 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { Http, URLSearchParams } from '@angular/http';
|
||||
import { HTTP_JSON_OPTIONS } from '../utils';
|
||||
|
||||
import { ScanningDetailResult } from './interface';
|
||||
import { VulnerabilitySeverity, ScanningBaseResult, ScanningResultSummary } from './interface';
|
||||
import {
|
||||
VulnerabilityItem,
|
||||
VulnerabilitySummary
|
||||
} from './interface';
|
||||
|
||||
/**
|
||||
* Get the vulnerabilities scanning results for the specified tag.
|
||||
@ -21,22 +23,22 @@ export abstract class ScanningResultService {
|
||||
*
|
||||
* @abstract
|
||||
* @param {string} tagId
|
||||
* @returns {(Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary)}
|
||||
* @returns {(Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary)}
|
||||
*
|
||||
* @memberOf ScanningResultService
|
||||
*/
|
||||
abstract getScanningResultSummary(tagId: string): Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary;
|
||||
abstract getVulnerabilityScanningSummary(tagId: string): Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary;
|
||||
|
||||
/**
|
||||
* Get the detailed vulnerabilities scanning results.
|
||||
*
|
||||
* @abstract
|
||||
* @param {string} tagId
|
||||
* @returns {(Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[])}
|
||||
* @returns {(Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[])}
|
||||
*
|
||||
* @memberOf ScanningResultService
|
||||
*/
|
||||
abstract getScanningResults(tagId: string): Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[];
|
||||
abstract getVulnerabilityScanningResults(tagId: string): Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@ -47,7 +49,7 @@ export class ScanningResultDefaultService extends ScanningResultService {
|
||||
super();
|
||||
}
|
||||
|
||||
getScanningResultSummary(tagId: string): Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary {
|
||||
getVulnerabilityScanningSummary(tagId: string): Observable<VulnerabilitySummary> | Promise<VulnerabilitySummary> | VulnerabilitySummary {
|
||||
if (!tagId || tagId.trim() === '') {
|
||||
return Promise.reject('Bad argument');
|
||||
}
|
||||
@ -55,7 +57,7 @@ export class ScanningResultDefaultService extends ScanningResultService {
|
||||
return Observable.of({});
|
||||
}
|
||||
|
||||
getScanningResults(tagId: string): Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[] {
|
||||
getVulnerabilityScanningResults(tagId: string): Observable<VulnerabilityItem[]> | Promise<VulnerabilityItem[]> | VulnerabilityItem[] {
|
||||
if (!tagId || tagId.trim() === '') {
|
||||
return Promise.reject('Bad argument');
|
||||
}
|
||||
|
@ -52,7 +52,19 @@ export abstract class TagService {
|
||||
*
|
||||
* @memberOf TagService
|
||||
*/
|
||||
abstract deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<Tag> | any;
|
||||
abstract deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<any> | any;
|
||||
|
||||
/**
|
||||
* Get the specified tag.
|
||||
*
|
||||
* @abstract
|
||||
* @param {string} repositoryName
|
||||
* @param {string} tag
|
||||
* @returns {(Observable<Tag> | Promise<Tag> | Tag)}
|
||||
*
|
||||
* @memberOf TagService
|
||||
*/
|
||||
abstract getTag(repositoryName: string, tag: string, queryParams?: RequestQueryParams): Observable<Tag> | Promise<Tag> | Tag;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,4 +125,15 @@ export class TagDefaultService extends TagService {
|
||||
.then(response => response)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
public getTag(repositoryName: string, tag: string, queryParams?: RequestQueryParams): Observable<Tag> | Promise<Tag> | Tag {
|
||||
if (!repositoryName || !tag) {
|
||||
return Promise.reject("Bad argument");
|
||||
}
|
||||
|
||||
let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`;
|
||||
return this.http.get(url, HTTP_JSON_OPTIONS).toPromise()
|
||||
.then(response => response.json() as Tag)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { TagComponent } from './tag.component';
|
||||
import { TagDetailComponent } from './tag-detail.component';
|
||||
|
||||
export * from './tag.component';
|
||||
export * from './tag-detail.component';
|
||||
|
||||
export const TAG_DIRECTIVES: Type<any>[] = [
|
||||
TagComponent
|
||||
TagComponent,
|
||||
TagDetailComponent
|
||||
];
|
109
src/ui_ng/lib/src/tag/tag-detail.component.css.ts
Normal file
109
src/ui_ng/lib/src/tag/tag-detail.component.css.ts
Normal file
@ -0,0 +1,109 @@
|
||||
export const TAG_DETAIL_STYLES: string = `
|
||||
.overview-section {
|
||||
background-color: white;
|
||||
padding-bottom: 36px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
background-color: #fafafa;
|
||||
padding-left: 12px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.title-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.tag-name {
|
||||
font-weight: 300;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.tag-timestamp {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.rotate-90 {
|
||||
-webkit-transform: rotate(-90deg);
|
||||
/*Firefox*/
|
||||
-moz-transform: rotate(-90deg);
|
||||
/*Chrome*/
|
||||
-ms-transform: rotate(-90deg);
|
||||
/*IE9 、IE10*/
|
||||
-o-transform: rotate(-90deg);
|
||||
/*Opera*/
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.arrow-back {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.arrow-block {
|
||||
border-right: 2px solid #cccccc;
|
||||
margin-right: 6px;
|
||||
display: inline-flex;
|
||||
padding: 6px 6px 6px 12px;
|
||||
}
|
||||
|
||||
.vulnerability-block {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.summary-block {
|
||||
margin-top: 24px;
|
||||
display: inline-flex;
|
||||
flex-wrap: row wrap;
|
||||
}
|
||||
|
||||
.image-summary {
|
||||
margin-right: 36px;
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.flex-block {
|
||||
display: inline-flex;
|
||||
flex-wrap: row wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.vulnerabilities-info {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.vulnerabilities-info .third-column {
|
||||
margin-left: 36px;
|
||||
}
|
||||
|
||||
.vulnerabilities-info .second-column,
|
||||
.vulnerabilities-info .fourth-column {
|
||||
text-align: left;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.vulnerabilities-info .second-row {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.image-detail-label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.image-detail-value {
|
||||
text-align: left;
|
||||
margin-left: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
77
src/ui_ng/lib/src/tag/tag-detail.component.html.ts
Normal file
77
src/ui_ng/lib/src/tag/tag-detail.component.html.ts
Normal file
@ -0,0 +1,77 @@
|
||||
export const TAG_DETAIL_HTML: string = `
|
||||
<div>
|
||||
<section class="overview-section">
|
||||
<div class="title-wrapper">
|
||||
<div class="title-block arrow-block">
|
||||
<clr-icon class="rotate-90 arrow-back" shape="arrow" size="36" (click)="onBack()"></clr-icon>
|
||||
</div>
|
||||
<div class="title-block">
|
||||
<div class="tag-name">
|
||||
{{tagDetails.name}}:v{{tagDetails.docker_version}}
|
||||
</div>
|
||||
<div class="tag-timestamp">
|
||||
{{'TAG.CREATION_TIME_PREFIX' | translate }} {{tagDetails.created | date }} {{'TAG.CREATOR_PREFIX' | translate }} {{tagDetails.author}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-block">
|
||||
<div class="image-summary">
|
||||
<div class="detail-title">
|
||||
{{'TAG.IMAGE_DETAILS' | translate }}
|
||||
</div>
|
||||
<div class="flex-block">
|
||||
<div class="image-detail-label">
|
||||
<div>{{'TAG.ARCHITECTURE' | translate }}</div>
|
||||
<div>{{'TAG.OS' | translate }}</div>
|
||||
<div>{{'TAG.SCAN_COMPLETION_TIME' | translate }}</div>
|
||||
</div>
|
||||
<div class="image-detail-value">
|
||||
<div>{{tagDetails.architecture}}</div>
|
||||
<div>{{tagDetails.os}}</div>
|
||||
<div>{{scanCompletedDatetime | date}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="detail-title">
|
||||
{{'TAG.IMAGE_VULNERABILITIES' | translate }}
|
||||
</div>
|
||||
<div class="flex-block vulnerabilities-info">
|
||||
<div>
|
||||
<div>
|
||||
<clr-icon shape="error" size="24" class="is-error"></clr-icon>
|
||||
</div>
|
||||
<div class="second-row">
|
||||
<clr-icon shape="exclamation-triangle" size="24" class="is-warning"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="second-column">
|
||||
<div>{{highCount}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{suffixForHigh | translate }}</div>
|
||||
<div class="second-row">{{mediumCount}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{suffixForMedium | translate }}</div>
|
||||
</div>
|
||||
<div class="third-column">
|
||||
<div>
|
||||
<clr-icon shape="play" size="20" class="is-warning rotate-90"></clr-icon>
|
||||
</div>
|
||||
<div class="second-row">
|
||||
<clr-icon shape="help" size="20"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fourth-column">
|
||||
<div>{{lowCount}} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{suffixForLow | translate }}</div>
|
||||
<div class="second-row">{{unknownCount}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{suffixForUnknown | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="detail-section">
|
||||
<div class="vulnerability-block">
|
||||
<hbr-vulnerabilities-grid tagId="tagId"></hbr-vulnerabilities-grid>
|
||||
</div>
|
||||
<div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
118
src/ui_ng/lib/src/tag/tag-detail.component.spec.ts
Normal file
118
src/ui_ng/lib/src/tag/tag-detail.component.spec.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ResultGridComponent } from '../vulnerability-scanning/result-grid.component';
|
||||
import { TagDetailComponent } from './tag-detail.component';
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { Tag, VulnerabilitySummary } from '../service/interface';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { TagService, TagDefaultService, ScanningResultService, ScanningResultDefaultService } from '../service/index';
|
||||
|
||||
describe('TagDetailComponent (inline template)', () => {
|
||||
|
||||
let comp: TagDetailComponent;
|
||||
let fixture: ComponentFixture<TagDetailComponent>;
|
||||
let tagService: TagService;
|
||||
let spy: jasmine.Spy;
|
||||
let mockVulnerability: VulnerabilitySummary = {
|
||||
total_package: 124,
|
||||
package_with_none: 92,
|
||||
package_with_high: 10,
|
||||
package_with_medium: 6,
|
||||
package_With_low: 13,
|
||||
package_with_unknown: 3,
|
||||
complete_timestamp: new Date()
|
||||
};
|
||||
let mockTag: Tag = {
|
||||
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
|
||||
"name": "nginx",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"docker_version": "1.12.3",
|
||||
"author": "steven",
|
||||
"created": new Date("2016-11-08T22:41:15.912313785Z"),
|
||||
"signature": null,
|
||||
vulnerability: mockVulnerability
|
||||
};
|
||||
|
||||
let config: IServiceConfig = {
|
||||
repositoryBaseEndpoint: '/api/repositories/testing'
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
TagDetailComponent,
|
||||
ResultGridComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: TagService, useClass: TagDefaultService },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TagDetailComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
comp.tagId = "mock_tag";
|
||||
comp.repositoryId = "mock_repo";
|
||||
|
||||
tagService = fixture.debugElement.injector.get(TagService);
|
||||
spy = spyOn(tagService, 'getTag').and.returnValues(Promise.resolve(mockTag));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should load data', async(() => {
|
||||
expect(spy.calls.any).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should rightly display tag name and version', async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.tag-name');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.textContent.trim()).toEqual('nginx:v1.12.3');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display tag details', async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.image-detail-value');
|
||||
expect(el).toBeTruthy();
|
||||
let el2: HTMLElement = el.querySelector('div');
|
||||
expect(el2).toBeTruthy();
|
||||
expect(el2.textContent).toEqual("amd64");
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display vulnerability details', async(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.second-column');
|
||||
expect(el).toBeTruthy();
|
||||
let el2: HTMLElement = el.querySelector('div');
|
||||
expect(el2).toBeTruthy();
|
||||
expect(el2.textContent.trim()).toEqual("10 VULNERABILITY.SEVERITY.HIGH VULNERABILITY.PLURAL");
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
88
src/ui_ng/lib/src/tag/tag-detail.component.ts
Normal file
88
src/ui_ng/lib/src/tag/tag-detail.component.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
|
||||
import { TAG_DETAIL_STYLES } from './tag-detail.component.css';
|
||||
import { TAG_DETAIL_HTML } from './tag-detail.component.html';
|
||||
|
||||
import { TagService, Tag } from '../service/index';
|
||||
import { toPromise } from '../utils';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-tag-detail',
|
||||
styles: [TAG_DETAIL_STYLES],
|
||||
template: TAG_DETAIL_HTML,
|
||||
|
||||
providers: []
|
||||
})
|
||||
export class TagDetailComponent implements OnInit {
|
||||
@Input() tagId: string;
|
||||
@Input() repositoryId: string;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
author: "--",
|
||||
created: new Date(),
|
||||
architecture: "--",
|
||||
os: "--",
|
||||
docker_version: "--",
|
||||
digest: "--"
|
||||
};
|
||||
|
||||
@Output() backEvt: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
private tagService: TagService,
|
||||
private errorHandler: ErrorHandler) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.repositoryId && this.tagId) {
|
||||
toPromise<Tag>(this.tagService.getTag(this.repositoryId, this.tagId))
|
||||
.then(response => this.tagDetails = response)
|
||||
.catch(error => this.errorHandler.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
onBack(): void {
|
||||
this.backEvt.emit(this.tagId);
|
||||
}
|
||||
|
||||
public get highCount(): number {
|
||||
return this.tagDetails && this.tagDetails.vulnerability ?
|
||||
this.tagDetails.vulnerability.package_with_high : 0;
|
||||
}
|
||||
|
||||
public get mediumCount(): number {
|
||||
return this.tagDetails && this.tagDetails.vulnerability ?
|
||||
this.tagDetails.vulnerability.package_with_medium : 0;
|
||||
}
|
||||
|
||||
public get lowCount(): number {
|
||||
return this.tagDetails && this.tagDetails.vulnerability ?
|
||||
this.tagDetails.vulnerability.package_With_low : 0;
|
||||
}
|
||||
|
||||
public get unknownCount(): number {
|
||||
return this.tagDetails && this.tagDetails.vulnerability ?
|
||||
this.tagDetails.vulnerability.package_with_unknown : 0;
|
||||
}
|
||||
|
||||
public get scanCompletedDatetime(): Date {
|
||||
return this.tagDetails && this.tagDetails.vulnerability ?
|
||||
this.tagDetails.vulnerability.complete_timestamp : new Date();
|
||||
}
|
||||
|
||||
public get suffixForHigh(): string {
|
||||
return this.highCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForMedium(): string {
|
||||
return this.mediumCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForLow(): string {
|
||||
return this.lowCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get suffixForUnknown(): string {
|
||||
return this.unknownCount > 1 ? "VULNERABILITY.PLURAL" : "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@ describe('TagComponent (inline template)', ()=> {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('Should load data', async(()=>{
|
||||
it('should load data', async(()=>{
|
||||
expect(spy.calls.any).toBeTruthy();
|
||||
}));
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ScanningResultSummary, VulnerabilitySeverity, ScanningBaseResult } from '../service/index';
|
||||
import { VulnerabilitySummary } from '../service/index';
|
||||
|
||||
import { ResultBarChartComponent, ScanState } from './result-bar-chart.component';
|
||||
import { ResultTipComponent } from './result-tip.component';
|
||||
@ -16,11 +16,18 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
let component: ResultBarChartComponent;
|
||||
let fixture: ComponentFixture<ResultBarChartComponent>;
|
||||
let serviceConfig: IServiceConfig;
|
||||
let scanningService: ScanningResultService;
|
||||
let spy: jasmine.Spy;
|
||||
let testConfig: IServiceConfig = {
|
||||
vulnerabilityScanningBaseEndpoint: "/api/vulnerability/testing"
|
||||
};
|
||||
let mockData: VulnerabilitySummary = {
|
||||
total_package: 124,
|
||||
package_with_none: 92,
|
||||
package_with_high: 10,
|
||||
package_with_medium: 6,
|
||||
package_With_low: 13,
|
||||
package_with_unknown: 3,
|
||||
complete_timestamp: new Date()
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -32,8 +39,7 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
ResultTipComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig }
|
||||
]
|
||||
});
|
||||
|
||||
@ -43,52 +49,9 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
fixture = TestBed.createComponent(ResultBarChartComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.tagId = "mockTag";
|
||||
component.state = ScanState.COMPLETED;
|
||||
component.state = ScanState.UNKNOWN;
|
||||
|
||||
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||
scanningService = fixture.debugElement.injector.get(ScanningResultService);
|
||||
let mockData: ScanningResultSummary = {
|
||||
totalComponents: 21,
|
||||
noneComponents: 7,
|
||||
completeTimestamp: new Date(),
|
||||
high: [],
|
||||
medium: [],
|
||||
low: [],
|
||||
unknown: []
|
||||
};
|
||||
|
||||
for (let i = 0; i < 14; i++) {
|
||||
let res: ScanningBaseResult = {
|
||||
id: "CVE-2016-" + (8859 + i),
|
||||
package: "package_" + i,
|
||||
version: '4.' + i + ".0",
|
||||
severity: VulnerabilitySeverity.UNKNOWN
|
||||
};
|
||||
|
||||
switch (i % 4) {
|
||||
case 0:
|
||||
res.severity = VulnerabilitySeverity.HIGH;
|
||||
mockData.high.push(res);
|
||||
break;
|
||||
case 1:
|
||||
res.severity = VulnerabilitySeverity.MEDIUM;
|
||||
mockData.medium.push(res);
|
||||
break;
|
||||
case 2:
|
||||
res.severity = VulnerabilitySeverity.LOW;
|
||||
mockData.low.push(res);
|
||||
break;
|
||||
case 3:
|
||||
res.severity = VulnerabilitySeverity.UNKNOWN;
|
||||
mockData.unknown.push(res);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spy = spyOn(scanningService, 'getScanningResultSummary')
|
||||
.and.returnValue(Promise.resolve(mockData));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -102,22 +65,57 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
expect(serviceConfig.vulnerabilityScanningBaseEndpoint).toEqual("/api/vulnerability/testing");
|
||||
});
|
||||
|
||||
it('should inject and call the ScanningResultService', () => {
|
||||
expect(scanningService).toBeTruthy();
|
||||
expect(spy.calls.any()).toBe(true, 'getScanningResultSummary called');
|
||||
});
|
||||
|
||||
it('should get data from ScanningResultService', async(() => {
|
||||
it('should show a button if status is PENDING', async(() => {
|
||||
component.state = ScanState.PENDING;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.detectChanges();
|
||||
expect(component.summary).toBeTruthy();
|
||||
expect(component.summary.totalComponents).toEqual(21);
|
||||
expect(component.summary.high.length).toEqual(4);
|
||||
expect(component.summary.medium.length).toEqual(4);
|
||||
expect(component.summary.low.length).toEqual(3);
|
||||
expect(component.summary.noneComponents).toEqual(7);
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.scanning-button');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show progress if status is SCANNING', async(() => {
|
||||
component.state = ScanState.SCANNING;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.progress');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show QUEUED if status is QUEUED', async(() => {
|
||||
component.state = ScanState.QUEUED;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.bar-state');
|
||||
expect(el).toBeTruthy();
|
||||
let el2: HTMLElement = el.querySelector('span');
|
||||
expect(el2).toBeTruthy();
|
||||
expect(el2.textContent).toEqual('VULNERABILITY.STATE.QUEUED');
|
||||
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show summary bar chart if status is COMPLETED', async(() => {
|
||||
component.state = ScanState.COMPLETED;
|
||||
component.summary = mockData;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.bar-block-none');
|
||||
expect(el).not.toBeNull();
|
||||
expect(el.style.width).toEqual("74px");
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -2,16 +2,9 @@ import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnInit
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ScanningResultService,
|
||||
ScanningResultSummary
|
||||
} from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { toPromise } from '../utils';
|
||||
import { MAX_TIP_WIDTH } from './result-tip.component';
|
||||
import { VulnerabilitySummary } from '../service/index';
|
||||
import { SCANNING_STYLES } from './scanning.css';
|
||||
import { BAR_CHART_COMPONENT_HTML } from './scanning.html';
|
||||
|
||||
@ -25,37 +18,21 @@ export enum ScanState {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-scan-result-bar',
|
||||
selector: 'hbr-vulnerability-bar',
|
||||
styles: [SCANNING_STYLES],
|
||||
template: BAR_CHART_COMPONENT_HTML
|
||||
})
|
||||
export class ResultBarChartComponent implements OnInit {
|
||||
export class ResultBarChartComponent {
|
||||
@Input() tagId: string = "";
|
||||
@Input() state: ScanState = ScanState.UNKNOWN;
|
||||
@Input() summary: ScanningResultSummary = {
|
||||
totalComponents: 0,
|
||||
noneComponents: 0,
|
||||
completeTimestamp: new Date(),
|
||||
high: [],
|
||||
medium: [],
|
||||
low: [],
|
||||
unknown: []
|
||||
@Input() summary: VulnerabilitySummary = {
|
||||
total_package: 0,
|
||||
package_with_none: 0,
|
||||
complete_timestamp: new Date()
|
||||
};
|
||||
@Output() startScanning: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
constructor(
|
||||
private scanningService: ScanningResultService,
|
||||
private errorHandler: ErrorHandler) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
toPromise<ScanningResultSummary>(this.scanningService.getScanningResultSummary(this.tagId))
|
||||
.then((summary: ScanningResultSummary) => {
|
||||
this.summary = summary;
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
})
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
public get completed(): boolean {
|
||||
return this.state === ScanState.COMPLETED;
|
||||
@ -86,66 +63,4 @@ export class ResultBarChartComponent implements OnInit {
|
||||
this.startScanning.emit(this.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
public get hasHigh(): boolean {
|
||||
return this.summary && this.summary.high && this.summary.high.length > 0;
|
||||
}
|
||||
|
||||
public get hasMedium(): boolean {
|
||||
return this.summary && this.summary.medium && this.summary.medium.length > 0;
|
||||
}
|
||||
|
||||
public get hasLow(): boolean {
|
||||
return this.summary && this.summary.low && this.summary.low.length > 0;
|
||||
}
|
||||
|
||||
public get hasUnknown(): boolean {
|
||||
return this.summary && this.summary.unknown && this.summary.unknown.length > 0;
|
||||
}
|
||||
|
||||
public get hasNone(): boolean {
|
||||
return this.summary && this.summary.noneComponents > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the percent width of each severity.
|
||||
*
|
||||
* @param {string} flag
|
||||
* 'h': high
|
||||
* 'm': medium
|
||||
* 'l': low
|
||||
* 'u': unknown
|
||||
* 'n': none
|
||||
* @returns {number}
|
||||
*
|
||||
* @memberOf ResultBarChartComponent
|
||||
*/
|
||||
percent(flag: string): number {
|
||||
if (!this.summary || this.summary.totalComponents === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let numerator: number = 0;
|
||||
switch (flag) {
|
||||
case 'h':
|
||||
numerator = this.summary.high.length;
|
||||
break;
|
||||
case 'm':
|
||||
numerator = this.summary.medium.length;
|
||||
break;
|
||||
case 'l':
|
||||
numerator = this.summary.low.length;
|
||||
break;
|
||||
case 'u':
|
||||
numerator = this.summary.unknown.length;
|
||||
break;
|
||||
default:
|
||||
numerator = this.summary.noneComponents;
|
||||
break;
|
||||
}
|
||||
|
||||
return Math.round((numerator / this.summary.totalComponents) * MAX_TIP_WIDTH);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ScanningDetailResult, VulnerabilitySeverity, RequestQueryParams } from '../service/index';
|
||||
import { VulnerabilityItem, VulnerabilitySeverity, RequestQueryParams } from '../service/index';
|
||||
|
||||
import { ResultGridComponent } from './result-grid.component';
|
||||
import { ScanningResultService, ScanningResultDefaultService } from '../service/scanning.service';
|
||||
@ -43,9 +43,9 @@ describe('ResultGridComponent (inline template)', () => {
|
||||
|
||||
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||
scanningService = fixture.debugElement.injector.get(ScanningResultService);
|
||||
let mockData: ScanningDetailResult[] = [];
|
||||
let mockData: VulnerabilityItem[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
let res: ScanningDetailResult = {
|
||||
let res: VulnerabilityItem = {
|
||||
id: "CVE-2016-" + (8859 + i),
|
||||
severity: i % 2 === 0 ? VulnerabilitySeverity.HIGH : VulnerabilitySeverity.MEDIUM,
|
||||
package: "package_" + i,
|
||||
@ -57,7 +57,7 @@ describe('ResultGridComponent (inline template)', () => {
|
||||
mockData.push(res);
|
||||
}
|
||||
|
||||
spy = spyOn(scanningService, 'getScanningResults')
|
||||
spy = spyOn(scanningService, 'getVulnerabilityScanningResults')
|
||||
.and.returnValue(Promise.resolve(mockData));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import {
|
||||
ScanningResultService,
|
||||
ScanningDetailResult
|
||||
VulnerabilityItem
|
||||
} from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
|
||||
@ -10,12 +10,12 @@ import { GRID_COMPONENT_HTML } from './scanning.html';
|
||||
import { SCANNING_STYLES } from './scanning.css';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-scan-result-grid',
|
||||
selector: 'hbr-vulnerabilities-grid',
|
||||
styles: [SCANNING_STYLES],
|
||||
template: GRID_COMPONENT_HTML
|
||||
})
|
||||
export class ResultGridComponent implements OnInit {
|
||||
scanningResults: ScanningDetailResult[] = [];
|
||||
scanningResults: VulnerabilityItem[] = [];
|
||||
@Input() tagId: string;
|
||||
|
||||
constructor(
|
||||
@ -27,13 +27,13 @@ export class ResultGridComponent implements OnInit {
|
||||
this.loadResults(this.tagId);
|
||||
}
|
||||
|
||||
showDetail(result: ScanningDetailResult): void {
|
||||
showDetail(result: VulnerabilityItem): void {
|
||||
console.log(result.id);
|
||||
}
|
||||
|
||||
loadResults(tagId: string): void {
|
||||
toPromise<ScanningDetailResult[]>(this.scanningService.getScanningResults(tagId))
|
||||
.then((results: ScanningDetailResult[]) => {
|
||||
toPromise<VulnerabilityItem[]>(this.scanningService.getVulnerabilityScanningResults(tagId))
|
||||
.then((results: VulnerabilityItem[]) => {
|
||||
this.scanningResults = results;
|
||||
})
|
||||
.catch(error => { this.errorHandler.error(error) })
|
||||
|
@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ScanningDetailResult, VulnerabilitySeverity } from '../service/index';
|
||||
import { VulnerabilitySummary } from '../service/index';
|
||||
|
||||
import { ResultTipComponent } from './result-tip.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
@ -16,6 +16,15 @@ describe('ResultTipComponent (inline template)', () => {
|
||||
let testConfig: IServiceConfig = {
|
||||
vulnerabilityScanningBaseEndpoint: "/api/vulnerability/testing"
|
||||
};
|
||||
let mockData:VulnerabilitySummary = {
|
||||
total_package: 124,
|
||||
package_with_none: 90,
|
||||
package_with_high: 13,
|
||||
package_with_medium: 10,
|
||||
package_With_low: 10,
|
||||
package_with_unknown: 1,
|
||||
complete_timestamp: new Date()
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -31,14 +40,26 @@ describe('ResultTipComponent (inline template)', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ResultTipComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.percent = 50;
|
||||
component.summary = mockData;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
expect(component.severity).toEqual(VulnerabilitySeverity.UNKNOWN);
|
||||
});
|
||||
|
||||
it('should reader the bar with different width', async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('.bar-block-none');
|
||||
expect(el).not.toBeNull();
|
||||
expect(el.style.width).toEqual("73px");
|
||||
let el2: HTMLElement = fixture.nativeElement.querySelector('.bar-block-high');
|
||||
expect(el2).not.toBeNull();
|
||||
expect(el2.style.width).toEqual("10px");
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { VulnerabilitySummary, VulnerabilitySeverity } from '../service/index';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
ScanningBaseResult,
|
||||
VulnerabilitySeverity
|
||||
} from '../service/index';
|
||||
|
||||
import { SCANNING_STYLES } from './scanning.css';
|
||||
import { TIP_COMPONENT_HTML } from './scanning.html';
|
||||
|
||||
@ -11,127 +9,141 @@ export const MIN_TIP_WIDTH = 5;
|
||||
export const MAX_TIP_WIDTH = 100;
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-scan-result-tip',
|
||||
selector: 'hbr-vulnerability-summary-chart',
|
||||
template: TIP_COMPONENT_HTML,
|
||||
styles: [SCANNING_STYLES]
|
||||
})
|
||||
export class ResultTipComponent implements OnInit {
|
||||
_percent: number = 5;
|
||||
_tipTitle: string = '';
|
||||
_tipTitle: string = "";
|
||||
|
||||
@Input() severity: VulnerabilitySeverity = VulnerabilitySeverity.UNKNOWN;
|
||||
@Input() completeDateTime: Date = new Date(); //Temp
|
||||
@Input() data: ScanningBaseResult[] = [];
|
||||
@Input() noneNumber: number = 0;
|
||||
@Input()
|
||||
public get percent(): number {
|
||||
return this._percent;
|
||||
}
|
||||
@Input() summary: VulnerabilitySummary = {
|
||||
total_package: 0,
|
||||
package_with_none: 0,
|
||||
complete_timestamp: new Date()
|
||||
};
|
||||
|
||||
public set percent(percent: number) {
|
||||
this._percent = percent;
|
||||
if (this._percent < MIN_TIP_WIDTH) {
|
||||
this._percent = MIN_TIP_WIDTH;
|
||||
}
|
||||
|
||||
if (this._percent > MAX_TIP_WIDTH) {
|
||||
this._percent = MAX_TIP_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
_getSeverityKey(): string {
|
||||
switch (this.severity) {
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
return 'VULNERABILITY.CHART.SEVERITY_HIGH';
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
return 'VULNERABILITY.CHART.SEVERITY_MEDIUM';
|
||||
case VulnerabilitySeverity.LOW:
|
||||
return 'VULNERABILITY.CHART.SEVERITY_LOW';
|
||||
case VulnerabilitySeverity.NONE:
|
||||
return 'VULNERABILITY.CHART.SEVERITY_NONE';
|
||||
default:
|
||||
return 'VULNERABILITY.CHART.SEVERITY_UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private translateService: TranslateService) { }
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.translateService.get(this._getSeverityKey())
|
||||
this.translate.get('VULNERABILITY.CHART.TOOLTIPS_TITLE',
|
||||
{ totalVulnerability: this.totalVulnerabilities, totalPackages: this.summary.total_package })
|
||||
.subscribe((res: string) => this._tipTitle = res);
|
||||
}
|
||||
|
||||
tipWidth(severity: VulnerabilitySeverity): string {
|
||||
let n: number = 0;
|
||||
let m: number = this.summary ? this.summary.total_package : 0;
|
||||
|
||||
if (m === 0) {
|
||||
return 0 + 'px';
|
||||
}
|
||||
|
||||
switch (severity) {
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
n = this.highCount;
|
||||
break;
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
n = this.mediumCount;
|
||||
break;
|
||||
case VulnerabilitySeverity.LOW:
|
||||
n = this.lowCount;
|
||||
break;
|
||||
case VulnerabilitySeverity.UNKNOWN:
|
||||
n = this.unknownCount;
|
||||
break;
|
||||
case VulnerabilitySeverity.NONE:
|
||||
n = this.noneCount;
|
||||
break;
|
||||
default:
|
||||
n = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
let width: number = Math.round((n/m)*MAX_TIP_WIDTH);
|
||||
if(width < MIN_TIP_WIDTH){
|
||||
width = MIN_TIP_WIDTH;
|
||||
}
|
||||
|
||||
return width + 'px';
|
||||
}
|
||||
|
||||
|
||||
unitText(count: number): string {
|
||||
if (count > 1) {
|
||||
return "VULNERABILITY.PLURAL";
|
||||
}
|
||||
|
||||
return "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
public get totalVulnerabilities(): number {
|
||||
return this.summary.total_package - this.summary.package_with_none;
|
||||
}
|
||||
|
||||
public get hasHigh(): boolean {
|
||||
return this.highCount > 0;
|
||||
}
|
||||
|
||||
public get hasMedium(): boolean {
|
||||
return this.mediumCount > 0;
|
||||
}
|
||||
|
||||
public get hasLow(): boolean {
|
||||
return this.lowCount > 0;
|
||||
}
|
||||
|
||||
public get hasUnknown(): boolean {
|
||||
return this.unknownCount > 0;
|
||||
}
|
||||
|
||||
public get hasNone(): boolean {
|
||||
return this.noneCount > 0;
|
||||
}
|
||||
|
||||
public get tipTitle(): string {
|
||||
if (!this.data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let dataSize: number = this.data.length;
|
||||
return this._tipTitle + ' (' + dataSize + ')';
|
||||
return this._tipTitle;
|
||||
}
|
||||
|
||||
public get hasResultsToList(): boolean {
|
||||
return this.data &&
|
||||
this.data.length > 0 && (
|
||||
this.severity !== VulnerabilitySeverity.NONE &&
|
||||
this.severity !== VulnerabilitySeverity.UNKNOWN
|
||||
);
|
||||
public get highCount(): number {
|
||||
return this.summary && this.summary.package_with_high ? this.summary.package_with_high : 0;
|
||||
}
|
||||
|
||||
public get tipWidth(): string {
|
||||
return this.percent + 'px';
|
||||
public get mediumCount(): number {
|
||||
return this.summary && this.summary.package_with_medium ? this.summary.package_with_medium : 0;
|
||||
}
|
||||
|
||||
public get tipClass(): string {
|
||||
let baseClass: string = "tip-wrapper tip-block";
|
||||
|
||||
switch (this.severity) {
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
return baseClass + " bar-block-high";
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
return baseClass + " bar-block-medium";
|
||||
case VulnerabilitySeverity.LOW:
|
||||
return baseClass + " bar-block-low";
|
||||
case VulnerabilitySeverity.NONE:
|
||||
return baseClass + " bar-block-none";
|
||||
default:
|
||||
return baseClass + " bar-block-unknown"
|
||||
}
|
||||
|
||||
public get lowCount(): number {
|
||||
return this.summary && this.summary.package_With_low ? this.summary.package_With_low : 0;
|
||||
}
|
||||
|
||||
public get isHigh(): boolean {
|
||||
return this.severity === VulnerabilitySeverity.HIGH;
|
||||
public get unknownCount(): number {
|
||||
return this.summary && this.summary.package_with_unknown ? this.summary.package_with_unknown : 0;
|
||||
}
|
||||
public get noneCount(): number {
|
||||
return this.summary && this.summary.package_with_none ? this.summary.package_with_none : 0;
|
||||
}
|
||||
|
||||
public get isMedium(): boolean {
|
||||
return this.severity === VulnerabilitySeverity.MEDIUM;
|
||||
public get highSuffix(): string {
|
||||
return this.unitText(this.highCount);
|
||||
}
|
||||
|
||||
public get isLow(): boolean {
|
||||
return this.severity === VulnerabilitySeverity.LOW;
|
||||
public get mediumSuffix(): string {
|
||||
return this.unitText(this.mediumCount);
|
||||
}
|
||||
|
||||
public get isNone(): boolean {
|
||||
return this.severity === VulnerabilitySeverity.NONE;
|
||||
public get lowSuffix(): string {
|
||||
return this.unitText(this.lowCount);
|
||||
}
|
||||
|
||||
public get isUnknown(): boolean {
|
||||
return this.severity === VulnerabilitySeverity.UNKNOWN;
|
||||
public get unknownSuffix(): string {
|
||||
return this.unitText(this.unknownCount);
|
||||
}
|
||||
|
||||
public get tipIconClass(): string {
|
||||
switch (this.severity) {
|
||||
case VulnerabilitySeverity.HIGH:
|
||||
return "is-error";
|
||||
case VulnerabilitySeverity.MEDIUM:
|
||||
return "is-warning";
|
||||
case VulnerabilitySeverity.LOW:
|
||||
return "is-info";
|
||||
case VulnerabilitySeverity.NONE:
|
||||
return "is-success";
|
||||
default:
|
||||
return "is-highlight"
|
||||
}
|
||||
public get noneSuffix(): string {
|
||||
return this.unitText(this.noneCount);
|
||||
}
|
||||
|
||||
public get maxWidth(): string {
|
||||
return MAX_TIP_WIDTH+"px";
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export const SCANNING_STYLES: string = `
|
||||
|
||||
.bar-tooltip-font {
|
||||
font-size: 13px;
|
||||
color: #565656;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bar-tooltip-font-title {
|
||||
@ -63,19 +63,16 @@ export const SCANNING_STYLES: string = `
|
||||
}
|
||||
|
||||
.bar-summary {
|
||||
margin-top: 5px;
|
||||
margin-top: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.bar-scanning-time {
|
||||
margin-left: 26px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.bar-summary ul {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.bar-summary ul li {
|
||||
list-style-type: none;
|
||||
margin: 2px;
|
||||
.bar-summary-item {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
`;
|
@ -1,24 +1,40 @@
|
||||
export const TIP_COMPONENT_HTML: string = `
|
||||
<div class="tip-wrapper tip-position" [style.width]='tipWidth'>
|
||||
<div class="tip-wrapper tip-position" [style.width]='maxWidth'>
|
||||
<clr-tooltip [clrTooltipDirection]="'top-right'" [clrTooltipSize]="'lg'">
|
||||
<div class="{{tipClass}}" [style.width]='tipWidth'></div>
|
||||
<div class="tip-wrapper tip-block bar-block-high" [style.width]='tipWidth(4)'></div>
|
||||
<div class="tip-wrapper tip-block bar-block-medium" [style.width]='tipWidth(3)'></div>
|
||||
<div class="tip-wrapper tip-block bar-block-low" [style.width]='tipWidth(2)'></div>
|
||||
<div class="tip-wrapper tip-block bar-block-unknown" [style.width]='tipWidth(1)'></div>
|
||||
<div class="tip-wrapper tip-block bar-block-none" [style.width]='tipWidth(0)'></div>
|
||||
<clr-tooltip-content>
|
||||
<div>
|
||||
<clr-icon *ngIf="isHigh" shape="exclamation-circle" class="{{tipIconClass}}" size="24"></clr-icon>
|
||||
<clr-icon *ngIf="isMedium" shape="exclamation-triangle" class="{{tipIconClass}}" size="24"></clr-icon>
|
||||
<clr-icon *ngIf="isLow" shape="info-circle" class="{{tipIconClass}}" size="24"></clr-icon>
|
||||
<clr-icon *ngIf="isNone" shape="check-circle" class="{{tipIconClass}}" size="24"></clr-icon>
|
||||
<clr-icon *ngIf="isUnknown" shape="help" class="{{tipIconClass}}" size="16"></clr-icon>
|
||||
<span class="bar-tooltip-font bar-tooltip-font-title">{{tipTitle}}</span>
|
||||
</div>
|
||||
<div class="bar-summary bar-tooltip-font">
|
||||
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} </span>
|
||||
<span>{{completeDateTime | date}}</span>
|
||||
<div *ngIf="hasResultsToList">
|
||||
<ul *ngFor="let item of data">
|
||||
<li>{{item.id}} {{item.version}} {{item.package}}</li>
|
||||
</ul>
|
||||
<div class="bar-summary bar-tooltip-fon">
|
||||
<div *ngIf="hasHigh" class="bar-summary-item">
|
||||
<clr-icon shape="exclamation-circle" class="is-error" size="24"></clr-icon>
|
||||
<span>{{highCount}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{ highSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasMedium" class="bar-summary-item">
|
||||
<clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="24"></clr-icon>
|
||||
<span>{{mediumCount}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{ mediumSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasLow" class="bar-summary-item">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span>{{lowCount}} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{ lowSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasUnknown" class="bar-summary-item">
|
||||
<clr-icon shape="help" size="24"></clr-icon>
|
||||
<span>{{unknownCount}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{ unknownSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasNone" class="bar-summary-item">
|
||||
<clr-icon shape="check-circle" class="is-success" size="24"></clr-icon>
|
||||
<span>{{noneCount}} {{'VULNERABILITY.SEVERITY.NONE' | translate }} {{ noneSuffix | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} </span>
|
||||
<span>{{summary.complete_timestamp | date}}</span>
|
||||
</div>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
@ -75,11 +91,7 @@ export const BAR_CHART_COMPONENT_HTML: string = `
|
||||
<div class="progress loop" style="height:2px;min-height:2px;"><progress></progress></div>
|
||||
</div>
|
||||
<div *ngIf="completed" class="bar-state">
|
||||
<hbr-scan-result-tip *ngIf="hasHigh" [severity]="2" [completeDateTime]="summary.completeTimestamp" [data]="summary.high" [percent]='percent("h")'></hbr-scan-result-tip>
|
||||
<hbr-scan-result-tip *ngIf="hasMedium" [severity]="1" [completeDateTime]="summary.completeTimestamp" [data]="summary.medium" [percent]='percent("m")'></hbr-scan-result-tip>
|
||||
<hbr-scan-result-tip *ngIf="hasLow" [severity]="0" [completeDateTime]="summary.completeTimestamp" [data]="summary.low" [percent]='percent("l")'></hbr-scan-result-tip>
|
||||
<hbr-scan-result-tip *ngIf="hasUnknown" [severity]="3" [completeDateTime]="summary.completeTimestamp" [data]="summary.unknown" [percent]='percent("u")'></hbr-scan-result-tip>
|
||||
<hbr-scan-result-tip *ngIf="hasNone" [severity]="4" [completeDateTime]="summary.completeTimestamp" [noneNumber]="summary.noneComponents" [percent]='percent("n")'></hbr-scan-result-tip>
|
||||
<hbr-vulnerability-summary-chart [summary]="summary"></hbr-vulnerability-summary-chart>
|
||||
</div>
|
||||
<div *ngIf="unknown" class="bar-state">
|
||||
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
|
||||
|
Loading…
Reference in New Issue
Block a user