mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-25 01:58:35 +01:00
Implement vulnerability scanning components
This commit is contained in:
parent
12660e0ea6
commit
7a0a423cc8
@ -39,7 +39,8 @@
|
|||||||
"mutationobserver-shim": "^0.3.2",
|
"mutationobserver-shim": "^0.3.2",
|
||||||
"@ngx-translate/core": "^6.0.0",
|
"@ngx-translate/core": "^6.0.0",
|
||||||
"@ngx-translate/http-loader": "0.0.3",
|
"@ngx-translate/http-loader": "0.0.3",
|
||||||
"ngx-cookie": "^1.0.0"
|
"ngx-cookie": "^1.0.0",
|
||||||
|
"intl": "^1.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "^1.0.0",
|
"@angular/cli": "^1.0.0",
|
||||||
|
@ -18,6 +18,7 @@ import { SERVICE_CONFIG, IServiceConfig } from './service.config';
|
|||||||
import { CONFIRMATION_DIALOG_DIRECTIVES } from './confirmation-dialog/index';
|
import { CONFIRMATION_DIALOG_DIRECTIVES } from './confirmation-dialog/index';
|
||||||
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
||||||
import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
|
import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index';
|
||||||
|
import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AccessLogService,
|
AccessLogService,
|
||||||
@ -29,7 +30,9 @@ import {
|
|||||||
RepositoryService,
|
RepositoryService,
|
||||||
RepositoryDefaultService,
|
RepositoryDefaultService,
|
||||||
TagService,
|
TagService,
|
||||||
TagDefaultService
|
TagDefaultService,
|
||||||
|
ScanningResultService,
|
||||||
|
ScanningResultDefaultService
|
||||||
} from './service/index';
|
} from './service/index';
|
||||||
import {
|
import {
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
@ -82,7 +85,10 @@ export interface HarborModuleConfig {
|
|||||||
repositoryService?: Provider,
|
repositoryService?: Provider,
|
||||||
|
|
||||||
//Service implementation for tag
|
//Service implementation for tag
|
||||||
tagService?: Provider
|
tagService?: Provider,
|
||||||
|
|
||||||
|
//Service implementation for vulnerability scanning
|
||||||
|
scanningService?: Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,7 +143,8 @@ export function initConfig(translateService: TranslateService, config: IServiceC
|
|||||||
REPLICATION_DIRECTIVES,
|
REPLICATION_DIRECTIVES,
|
||||||
LIST_REPLICATION_RULE_DIRECTIVES,
|
LIST_REPLICATION_RULE_DIRECTIVES,
|
||||||
CREATE_EDIT_RULE_DIRECTIVES,
|
CREATE_EDIT_RULE_DIRECTIVES,
|
||||||
DATETIME_PICKER_DIRECTIVES
|
DATETIME_PICKER_DIRECTIVES,
|
||||||
|
VULNERABILITY_DIRECTIVES
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
LOG_DIRECTIVES,
|
LOG_DIRECTIVES,
|
||||||
@ -152,7 +159,8 @@ export function initConfig(translateService: TranslateService, config: IServiceC
|
|||||||
REPLICATION_DIRECTIVES,
|
REPLICATION_DIRECTIVES,
|
||||||
LIST_REPLICATION_RULE_DIRECTIVES,
|
LIST_REPLICATION_RULE_DIRECTIVES,
|
||||||
CREATE_EDIT_RULE_DIRECTIVES,
|
CREATE_EDIT_RULE_DIRECTIVES,
|
||||||
DATETIME_PICKER_DIRECTIVES
|
DATETIME_PICKER_DIRECTIVES,
|
||||||
|
VULNERABILITY_DIRECTIVES
|
||||||
],
|
],
|
||||||
providers: []
|
providers: []
|
||||||
})
|
})
|
||||||
@ -169,6 +177,7 @@ export class HarborLibraryModule {
|
|||||||
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },
|
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },
|
||||||
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||||
|
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||||
//Do initializing
|
//Do initializing
|
||||||
TranslateService,
|
TranslateService,
|
||||||
{
|
{
|
||||||
@ -191,7 +200,8 @@ export class HarborLibraryModule {
|
|||||||
config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService },
|
config.endpointService || { provide: EndpointService, useClass: EndpointDefaultService },
|
||||||
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },
|
config.replicationService || { provide: ReplicationService, useClass: ReplicationDefaultService },
|
||||||
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||||
config.tagService || { provide: TagService, useClass: TagDefaultService }
|
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||||
|
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -435,6 +435,34 @@ export const EN_US_LANG: any = {
|
|||||||
"IN_PROGRESS": "Search...",
|
"IN_PROGRESS": "Search...",
|
||||||
"BACK": "Back"
|
"BACK": "Back"
|
||||||
},
|
},
|
||||||
|
"VULNERABILITY": {
|
||||||
|
"STATE": {
|
||||||
|
"PENDING": "SCAN NOW",
|
||||||
|
"QUEUED": "Queued",
|
||||||
|
"ERROR": "Error",
|
||||||
|
"SCANNING": "Scanning",
|
||||||
|
"UNKNOWN": "Unknown"
|
||||||
|
},
|
||||||
|
"GRID": {
|
||||||
|
"PLACEHOLDER": "We couldn't find any scanning results!",
|
||||||
|
"COLUMN_ID": "Vulnerability",
|
||||||
|
"COLUMN_SEVERITY": "Severity",
|
||||||
|
"COLUMN_PACKAGE": "Package",
|
||||||
|
"COLUMN_VERSION": "Current version",
|
||||||
|
"COLUMN_FIXED": "Fixed in version",
|
||||||
|
"COLUMN_LAYER": "Introduced in layer",
|
||||||
|
"FOOT_ITEMS": "Items",
|
||||||
|
"FOOT_OF": "of"
|
||||||
|
},
|
||||||
|
"CHART": {
|
||||||
|
"SCANNING_TIME": "Scan completed",
|
||||||
|
"SEVERITY_HIGH": "High severity",
|
||||||
|
"SEVERITY_MEDIUM": "Medium severity",
|
||||||
|
"SEVERITY_LOW": "Low severity",
|
||||||
|
"SEVERITY_UNKNOWN": "Unknown",
|
||||||
|
"SEVERITY_NONE": "No Vulnerabilities"
|
||||||
|
},
|
||||||
|
},
|
||||||
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
|
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later.",
|
||||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
|
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue your action.",
|
||||||
"FORBIDDEN_ERROR": "You do not have the proper privileges to perform the action.",
|
"FORBIDDEN_ERROR": "You do not have the proper privileges to perform the action.",
|
||||||
|
@ -433,6 +433,34 @@ export const ES_ES_LANG: any = {
|
|||||||
"IN_PROGRESS": "Buscar...",
|
"IN_PROGRESS": "Buscar...",
|
||||||
"BACK": "Volver"
|
"BACK": "Volver"
|
||||||
},
|
},
|
||||||
|
"VULNERABILITY": {
|
||||||
|
"STATE": {
|
||||||
|
"PENDING": "SCAN NOW",
|
||||||
|
"QUEUED": "Queued",
|
||||||
|
"ERROR": "Error",
|
||||||
|
"SCANNING": "Scanning",
|
||||||
|
"UNKNOWN": "Unknown"
|
||||||
|
},
|
||||||
|
"GRID": {
|
||||||
|
"PLACEHOLDER": "We couldn't find any scanning results!",
|
||||||
|
"COLUMN_ID": "Vulnerability",
|
||||||
|
"COLUMN_SEVERITY": "Severity",
|
||||||
|
"COLUMN_PACKAGE": "Package",
|
||||||
|
"COLUMN_VERSION": "Current version",
|
||||||
|
"COLUMN_FIXED": "Fixed in version",
|
||||||
|
"COLUMN_LAYER": "Introduced in layer",
|
||||||
|
"FOOT_ITEMS": "Items",
|
||||||
|
"FOOT_OF": "of"
|
||||||
|
},
|
||||||
|
"CHART": {
|
||||||
|
"SCANNING_TIME": "Scan completed",
|
||||||
|
"SEVERITY_HIGH": "High severity",
|
||||||
|
"SEVERITY_MEDIUM": "Medium severity",
|
||||||
|
"SEVERITY_LOW": "Low severity",
|
||||||
|
"SEVERITY_UNKNOWN": "Unknown",
|
||||||
|
"SEVERITY_NONE": "No Vulnerabilities"
|
||||||
|
},
|
||||||
|
},
|
||||||
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
|
"UNKNOWN_ERROR": "Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo más tarde.",
|
||||||
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
|
"UNAUTHORIZED_ERROR": "La sesión no es válida o ha caducado. Necesita identificarse de nuevo para llevar a cabo esa acción.",
|
||||||
"FORBIDDEN_ERROR": "No tienes permisos para llevar a cabo esa acción.",
|
"FORBIDDEN_ERROR": "No tienes permisos para llevar a cabo esa acción.",
|
||||||
|
@ -435,6 +435,34 @@ export const ZH_CN_LANG: any = {
|
|||||||
"IN_PROGRESS": "搜索中...",
|
"IN_PROGRESS": "搜索中...",
|
||||||
"BACK": "返回"
|
"BACK": "返回"
|
||||||
},
|
},
|
||||||
|
"VULNERABILITY": {
|
||||||
|
"STATE": {
|
||||||
|
"PENDING": "开始扫描",
|
||||||
|
"QUEUED": "已入队列",
|
||||||
|
"ERROR": "错误",
|
||||||
|
"SCANNING": "扫描中",
|
||||||
|
"UNKNOWN": "未知"
|
||||||
|
},
|
||||||
|
"GRID": {
|
||||||
|
"PLACEHOLDER": "没有扫描结果!",
|
||||||
|
"COLUMN_ID": "缺陷码",
|
||||||
|
"COLUMN_SEVERITY": "严重度",
|
||||||
|
"COLUMN_PACKAGE": "组件",
|
||||||
|
"COLUMN_VERSION": "当前版本",
|
||||||
|
"COLUMN_FIXED": "修复版本",
|
||||||
|
"COLUMN_LAYER": "引入层",
|
||||||
|
"FOOT_ITEMS": "项目",
|
||||||
|
"FOOT_OF": "总共"
|
||||||
|
},
|
||||||
|
"CHART": {
|
||||||
|
"SCANNING_TIME": "扫描完成",
|
||||||
|
"SEVERITY_HIGH": "严重",
|
||||||
|
"SEVERITY_MEDIUM": "中等",
|
||||||
|
"SEVERITY_LOW": "低",
|
||||||
|
"SEVERITY_UNKNOWN": "未知",
|
||||||
|
"SEVERITY_NONE": "无缺陷"
|
||||||
|
},
|
||||||
|
},
|
||||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
"UNKNOWN_ERROR": "发生未知错误,请稍后再试。",
|
||||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续。",
|
||||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限。",
|
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限。",
|
||||||
|
@ -9,3 +9,4 @@ export * from './endpoint/index';
|
|||||||
export * from './repository/index';
|
export * from './repository/index';
|
||||||
export * from './tag/index';
|
export * from './tag/index';
|
||||||
export * from './replication/index';
|
export * from './replication/index';
|
||||||
|
export * from './vulnerability-scanning/index';
|
@ -12,7 +12,7 @@ import { ErrorHandler } from '../error-handler/index';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { FilterComponent } from '../filter/filter.component';
|
import { FilterComponent } from '../filter/filter.component';
|
||||||
|
|
||||||
describe('RecentLogComponent', () => {
|
describe('RecentLogComponent (inline template)', () => {
|
||||||
let component: RecentLogComponent;
|
let component: RecentLogComponent;
|
||||||
let fixture: ComponentFixture<RecentLogComponent>;
|
let fixture: ComponentFixture<RecentLogComponent>;
|
||||||
let serviceConfig: IServiceConfig;
|
let serviceConfig: IServiceConfig;
|
||||||
|
@ -17,6 +17,10 @@ import 'core-js/es6/reflect';
|
|||||||
|
|
||||||
import 'core-js/es7/reflect';
|
import 'core-js/es7/reflect';
|
||||||
|
|
||||||
|
import 'intl';
|
||||||
|
import 'intl/locale-data/jsonp/en';
|
||||||
|
import 'intl/locale-data/jsonp/es';
|
||||||
|
import 'intl/locale-data/jsonp/zh';
|
||||||
|
|
||||||
import 'zone.js/dist/zone';
|
import 'zone.js/dist/zone';
|
||||||
|
|
||||||
|
@ -84,5 +84,13 @@ export interface IServiceConfig {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
* @memberOf IServiceConfig
|
* @memberOf IServiceConfig
|
||||||
*/
|
*/
|
||||||
enablei18Support?: boolean
|
enablei18Support?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base endpoint of the service used to handle vulnerability scanning.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberOf IServiceConfig
|
||||||
|
*/
|
||||||
|
vulnerabilityScanningBaseEndpoint?: string;
|
||||||
}
|
}
|
@ -5,3 +5,4 @@ export * from './replication.service';
|
|||||||
export * from './repository.service';
|
export * from './repository.service';
|
||||||
export * from './tag.service';
|
export * from './tag.service';
|
||||||
export * from './RequestQueryParams';
|
export * from './RequestQueryParams';
|
||||||
|
export * from './scanning.service';
|
@ -144,3 +144,31 @@ export interface SessionInfo {
|
|||||||
hasSignedIn?: boolean;
|
hasSignedIn?: boolean;
|
||||||
registryUrl?: string;
|
registryUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Not finalized yet
|
||||||
|
export enum VulnerabilitySeverity {
|
||||||
|
LOW, MEDIUM, HIGH, UNKNOWN, NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanningBaseResult {
|
||||||
|
id: string;
|
||||||
|
severity: VulnerabilitySeverity;
|
||||||
|
package: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanningDetailResult extends ScanningBaseResult {
|
||||||
|
fixedVersion: string;
|
||||||
|
layer: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanningResultSummary {
|
||||||
|
totalComponents: number;
|
||||||
|
noneComponents: number;
|
||||||
|
completeTimestamp: Date;
|
||||||
|
high: ScanningBaseResult[];
|
||||||
|
medium: ScanningBaseResult[];
|
||||||
|
low: ScanningBaseResult[];
|
||||||
|
unknown: ScanningBaseResult[];
|
||||||
|
}
|
41
src/ui_ng/lib/src/service/scanning.service.spec.ts
Normal file
41
src/ui_ng/lib/src/service/scanning.service.spec.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ScanningResultService, ScanningResultDefaultService } from './scanning.service';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
|
||||||
|
describe('ScanningResultService', () => {
|
||||||
|
const mockConfig: IServiceConfig = {
|
||||||
|
vulnerabilityScanningBaseEndpoint: "/api/vulnerability/testing"
|
||||||
|
};
|
||||||
|
|
||||||
|
let config: IServiceConfig;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ScanningResultDefaultService,
|
||||||
|
{
|
||||||
|
provide: ScanningResultService,
|
||||||
|
useClass: ScanningResultDefaultService
|
||||||
|
}, {
|
||||||
|
provide: SERVICE_CONFIG,
|
||||||
|
useValue: mockConfig
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
config = TestBed.get(SERVICE_CONFIG);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be initialized', inject([ScanningResultDefaultService], (service: ScanningResultService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should inject the right config', () => {
|
||||||
|
expect(config).toBeTruthy();
|
||||||
|
expect(config.vulnerabilityScanningBaseEndpoint).toEqual("/api/vulnerability/testing");
|
||||||
|
});
|
||||||
|
});
|
65
src/ui_ng/lib/src/service/scanning.service.ts
Normal file
65
src/ui_ng/lib/src/service/scanning.service.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import { Injectable, Inject } from "@angular/core";
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the vulnerabilities scanning results for the specified tag.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @abstract
|
||||||
|
* @class ScanningResultService
|
||||||
|
*/
|
||||||
|
export abstract class ScanningResultService {
|
||||||
|
/**
|
||||||
|
* Get the summary of vulnerability scanning result.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {string} tagId
|
||||||
|
* @returns {(Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary)}
|
||||||
|
*
|
||||||
|
* @memberOf ScanningResultService
|
||||||
|
*/
|
||||||
|
abstract getScanningResultSummary(tagId: string): Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the detailed vulnerabilities scanning results.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {string} tagId
|
||||||
|
* @returns {(Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[])}
|
||||||
|
*
|
||||||
|
* @memberOf ScanningResultService
|
||||||
|
*/
|
||||||
|
abstract getScanningResults(tagId: string): Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ScanningResultDefaultService extends ScanningResultService {
|
||||||
|
constructor(
|
||||||
|
private http: Http,
|
||||||
|
@Inject(SERVICE_CONFIG) private config: IServiceConfig) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getScanningResultSummary(tagId: string): Observable<ScanningResultSummary> | Promise<ScanningResultSummary> | ScanningResultSummary {
|
||||||
|
if (!tagId || tagId.trim() === '') {
|
||||||
|
return Promise.reject('Bad argument');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.of({});
|
||||||
|
}
|
||||||
|
|
||||||
|
getScanningResults(tagId: string): Observable<ScanningDetailResult[]> | Promise<ScanningDetailResult[]> | ScanningDetailResult[] {
|
||||||
|
if (!tagId || tagId.trim() === '') {
|
||||||
|
return Promise.reject('Bad argument');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.of([]);
|
||||||
|
}
|
||||||
|
}
|
13
src/ui_ng/lib/src/vulnerability-scanning/index.ts
Normal file
13
src/ui_ng/lib/src/vulnerability-scanning/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Type } from "@angular/core";
|
||||||
|
import { ResultGridComponent } from './result-grid.component';
|
||||||
|
import { ResultBarChartComponent } from './result-bar-chart.component';
|
||||||
|
import { ResultTipComponent } from './result-tip.component';
|
||||||
|
|
||||||
|
export * from "./result-grid.component";
|
||||||
|
export * from './result-bar-chart.component';
|
||||||
|
|
||||||
|
export const VULNERABILITY_DIRECTIVES: Type<any>[] = [
|
||||||
|
ResultGridComponent,
|
||||||
|
ResultTipComponent,
|
||||||
|
ResultBarChartComponent
|
||||||
|
];
|
@ -0,0 +1,124 @@
|
|||||||
|
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
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 { ResultBarChartComponent, ScanState } from './result-bar-chart.component';
|
||||||
|
import { ResultTipComponent } from './result-tip.component';
|
||||||
|
import { ScanningResultService, ScanningResultDefaultService } from '../service/scanning.service';
|
||||||
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
import { ErrorHandler } from '../error-handler/index';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
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"
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResultBarChartComponent,
|
||||||
|
ResultTipComponent],
|
||||||
|
providers: [
|
||||||
|
ErrorHandler,
|
||||||
|
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||||
|
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResultBarChartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.tagId = "mockTag";
|
||||||
|
component.state = ScanState.COMPLETED;
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject the SERVICE_CONFIG', () => {
|
||||||
|
expect(serviceConfig).toBeTruthy();
|
||||||
|
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(() => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,151 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
OnInit
|
||||||
|
} 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 { SCANNING_STYLES } from './scanning.css';
|
||||||
|
import { BAR_CHART_COMPONENT_HTML } from './scanning.html';
|
||||||
|
|
||||||
|
export enum ScanState {
|
||||||
|
COMPLETED, //Scanning work successfully completed
|
||||||
|
ERROR, //Error occurred when scanning
|
||||||
|
QUEUED, //Scanning job is queued
|
||||||
|
SCANNING, //Scanning in progress
|
||||||
|
PENDING, //Scanning not start
|
||||||
|
UNKNOWN //Unknown status
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-scan-result-bar',
|
||||||
|
styles: [SCANNING_STYLES],
|
||||||
|
template: BAR_CHART_COMPONENT_HTML
|
||||||
|
})
|
||||||
|
export class ResultBarChartComponent implements OnInit {
|
||||||
|
@Input() tagId: string = "";
|
||||||
|
@Input() state: ScanState = ScanState.UNKNOWN;
|
||||||
|
@Input() summary: ScanningResultSummary = {
|
||||||
|
totalComponents: 0,
|
||||||
|
noneComponents: 0,
|
||||||
|
completeTimestamp: new Date(),
|
||||||
|
high: [],
|
||||||
|
medium: [],
|
||||||
|
low: [],
|
||||||
|
unknown: []
|
||||||
|
};
|
||||||
|
@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);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public get completed(): boolean {
|
||||||
|
return this.state === ScanState.COMPLETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get error(): boolean {
|
||||||
|
return this.state === ScanState.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get queued(): boolean {
|
||||||
|
return this.state === ScanState.QUEUED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get scanning(): boolean {
|
||||||
|
return this.state === ScanState.SCANNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pending(): boolean {
|
||||||
|
return this.state === ScanState.PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get unknown(): boolean {
|
||||||
|
return this.state === ScanState.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
scanNow(): void {
|
||||||
|
if (this.tagId && this.tagId !== '') {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
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 { ResultGridComponent } from './result-grid.component';
|
||||||
|
import { ScanningResultService, ScanningResultDefaultService } from '../service/scanning.service';
|
||||||
|
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||||
|
import { ErrorHandler } from '../error-handler/index';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
describe('ResultGridComponent (inline template)', () => {
|
||||||
|
let component: ResultGridComponent;
|
||||||
|
let fixture: ComponentFixture<ResultGridComponent>;
|
||||||
|
let serviceConfig: IServiceConfig;
|
||||||
|
let scanningService: ScanningResultService;
|
||||||
|
let spy: jasmine.Spy;
|
||||||
|
let testConfig: IServiceConfig = {
|
||||||
|
vulnerabilityScanningBaseEndpoint: "/api/vulnerability/testing"
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
declarations: [ResultGridComponent],
|
||||||
|
providers: [
|
||||||
|
ErrorHandler,
|
||||||
|
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||||
|
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResultGridComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.tagId = "mockTag";
|
||||||
|
|
||||||
|
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||||
|
scanningService = fixture.debugElement.injector.get(ScanningResultService);
|
||||||
|
let mockData: ScanningDetailResult[] = [];
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
let res: ScanningDetailResult = {
|
||||||
|
id: "CVE-2016-" + (8859 + i),
|
||||||
|
severity: i % 2 === 0 ? VulnerabilitySeverity.HIGH : VulnerabilitySeverity.MEDIUM,
|
||||||
|
package: "package_" + i,
|
||||||
|
layer: "layer_" + i,
|
||||||
|
version: '4.' + i + ".0",
|
||||||
|
fixedVersion: '4.' + i + '.11',
|
||||||
|
description: "Mock data"
|
||||||
|
};
|
||||||
|
mockData.push(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
spy = spyOn(scanningService, 'getScanningResults')
|
||||||
|
.and.returnValue(Promise.resolve(mockData));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject the SERVICE_CONFIG', () => {
|
||||||
|
expect(serviceConfig).toBeTruthy();
|
||||||
|
expect(serviceConfig.vulnerabilityScanningBaseEndpoint).toEqual("/api/vulnerability/testing");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject and call the ScanningResultService', () => {
|
||||||
|
expect(scanningService).toBeTruthy();
|
||||||
|
expect(spy.calls.any()).toBe(true, 'getScanningResults called');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get data from ScanningResultService', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.scanningResults).toBeTruthy();
|
||||||
|
expect(component.scanningResults.length).toEqual(30);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should render data to view', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let de: DebugElement = fixture.debugElement.query(del => del.classes['datagrid-cell']);
|
||||||
|
expect(de).toBeTruthy();
|
||||||
|
let el: HTMLElement = de.nativeElement;
|
||||||
|
expect(el).toBeTruthy();
|
||||||
|
expect(el.textContent.trim()).toEqual('CVE-2016-8859');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,41 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ScanningResultService,
|
||||||
|
ScanningDetailResult
|
||||||
|
} from '../service/index';
|
||||||
|
import { ErrorHandler } from '../error-handler/index';
|
||||||
|
|
||||||
|
import { toPromise } from '../utils';
|
||||||
|
import { GRID_COMPONENT_HTML } from './scanning.html';
|
||||||
|
import { SCANNING_STYLES } from './scanning.css';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-scan-result-grid',
|
||||||
|
styles: [SCANNING_STYLES],
|
||||||
|
template: GRID_COMPONENT_HTML
|
||||||
|
})
|
||||||
|
export class ResultGridComponent implements OnInit {
|
||||||
|
scanningResults: ScanningDetailResult[] = [];
|
||||||
|
@Input() tagId: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private scanningService: ScanningResultService,
|
||||||
|
private errorHandler: ErrorHandler
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadResults(this.tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDetail(result: ScanningDetailResult): void {
|
||||||
|
console.log(result.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadResults(tagId: string): void {
|
||||||
|
toPromise<ScanningDetailResult[]>(this.scanningService.getScanningResults(tagId))
|
||||||
|
.then((results: ScanningDetailResult[]) => {
|
||||||
|
this.scanningResults = results;
|
||||||
|
})
|
||||||
|
.catch(error => { this.errorHandler.error(error) })
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
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 { ResultTipComponent } from './result-tip.component';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
describe('ResultTipComponent (inline template)', () => {
|
||||||
|
let component: ResultTipComponent;
|
||||||
|
let fixture: ComponentFixture<ResultTipComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
declarations: [ResultTipComponent]
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResultTipComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.percent = 50;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
expect(component.severity).toEqual(VulnerabilitySeverity.UNKNOWN);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
137
src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts
Normal file
137
src/ui_ng/lib/src/vulnerability-scanning/result-tip.component.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export const MIN_TIP_WIDTH = 5;
|
||||||
|
export const MAX_TIP_WIDTH = 100;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hbr-scan-result-tip',
|
||||||
|
template: TIP_COMPONENT_HTML,
|
||||||
|
styles: [SCANNING_STYLES]
|
||||||
|
})
|
||||||
|
export class ResultTipComponent implements OnInit {
|
||||||
|
_percent: number = 5;
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.translateService.get(this._getSeverityKey())
|
||||||
|
.subscribe((res: string) => this._tipTitle = res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tipTitle(): string {
|
||||||
|
if (!this.data) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataSize: number = this.data.length;
|
||||||
|
return this._tipTitle + ' (' + dataSize + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hasResultsToList(): boolean {
|
||||||
|
return this.data &&
|
||||||
|
this.data.length > 0 && (
|
||||||
|
this.severity !== VulnerabilitySeverity.NONE &&
|
||||||
|
this.severity !== VulnerabilitySeverity.UNKNOWN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tipWidth(): string {
|
||||||
|
return this.percent + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 isHigh(): boolean {
|
||||||
|
return this.severity === VulnerabilitySeverity.HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isMedium(): boolean {
|
||||||
|
return this.severity === VulnerabilitySeverity.MEDIUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isLow(): boolean {
|
||||||
|
return this.severity === VulnerabilitySeverity.LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isNone(): boolean {
|
||||||
|
return this.severity === VulnerabilitySeverity.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isUnknown(): boolean {
|
||||||
|
return this.severity === VulnerabilitySeverity.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
src/ui_ng/lib/src/vulnerability-scanning/scanning.css.ts
Normal file
81
src/ui_ng/lib/src/vulnerability-scanning/scanning.css.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
export const SCANNING_STYLES: string = `
|
||||||
|
.bar-wrapper {
|
||||||
|
width: 150px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-state {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scanning-button {
|
||||||
|
height: 24px;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
vertical-align: middle;
|
||||||
|
top: -6px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
max-height: 16px;
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-position {
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-block {
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-block-high {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-block-medium {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-block-low {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-block-none {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-block-unknown {
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-tooltip-font {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #565656;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-tooltip-font-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-summary {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-scanning-time {
|
||||||
|
margin-left: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-summary ul {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-summary ul li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
`;
|
89
src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts
Normal file
89
src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
export const TIP_COMPONENT_HTML: string = `
|
||||||
|
<div class="tip-wrapper tip-position" [style.width]='tipWidth'>
|
||||||
|
<clr-tooltip [clrTooltipDirection]="'top-right'" [clrTooltipSize]="'lg'">
|
||||||
|
<div class="{{tipClass}}" [style.width]='tipWidth'></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>
|
||||||
|
</div>
|
||||||
|
</clr-tooltip-content>
|
||||||
|
</clr-tooltip>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GRID_COMPONENT_HTML: string = `
|
||||||
|
<div>
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column [clrDgField]="'id'">{{'VULNERABILITY.GRID.COLUMN_ID' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'severity'">{{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'package'">{{'VULNERABILITY.GRID.COLUMN_PACKAGE' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'version'">{{'VULNERABILITY.GRID.COLUMN_VERSION' | translate}} version</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'fixedVersion'">{{'VULNERABILITY.GRID.COLUMN_FIXED' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgField]="'layer'">{{'VULNERABILITY.GRID.COLUMN_LAYER' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column>Description</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-placeholder>{{'VULNERABILITY.GRID.PLACEHOLDER' | translate}}</clr-dg-placeholder>
|
||||||
|
<clr-dg-row *clrDgItems="let res of scanningResults">
|
||||||
|
<clr-dg-action-overflow>
|
||||||
|
<button class="action-item" (click)="showDetail(res)">Detail</button>
|
||||||
|
</clr-dg-action-overflow>
|
||||||
|
<clr-dg-cell>{{res.id}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.severity}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.package}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.version}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.fixedVersion}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.layer}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{res.description}}</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'VULNERABILITY.GRID.FOOT_OF' | translate}} {{pagination.totalItems}} {{'VULNERABILITY.GRID.FOOT_ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="25" [clrDgTotalItems]="scanningResults.length"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const BAR_CHART_COMPONENT_HTML: string = `
|
||||||
|
<div class="bar-wrapper">
|
||||||
|
<div *ngIf="pending" class="bar-state">
|
||||||
|
<button class="btn btn-link scanning-button" (click)="scanNow()">{{'VULNERABILITY.STATE.PENDING' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="queued" class="bar-state">
|
||||||
|
<span>{{'VULNERABILITY.STATE.QUEUED' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="error" class="bar-state">
|
||||||
|
<clr-icon shape="info-circle" class="is-error" size="24"></clr-icon>
|
||||||
|
<span style="margin-left:-5px;">{{'VULNERABILITY.STATE.ERROR' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="scanning" class="bar-state">
|
||||||
|
<div>{{'VULNERABILITY.STATE.SCANNING' | translate}}</div>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="unknown" class="bar-state">
|
||||||
|
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
|
||||||
|
<span style="margin-left:-5px;">{{'VULNERABILITY.STATE.UNKNOWN' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
Loading…
Reference in New Issue
Block a user