Update the style for severity (#19525)

1.Related issue #19249

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2023-11-02 17:02:32 +08:00 committed by GitHub
parent b337f51e7e
commit d0a9754786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 127 additions and 580 deletions

View File

@ -63,3 +63,7 @@
.min-width {
min-width: 9rem !important;
}
.label {
min-width: 3rem;
}

View File

@ -93,3 +93,12 @@ export function getRepoLink(proId: number | string, repoName: string): any {
}
export const CVSS3_REG = /^([0-9]|10)(\.\d)?$/;
export enum SeverityColors {
CRITICAL = '#FF4D2E',
HIGH = '#FF8F3D',
MEDIUM = '#FFCE66',
LOW = '#FFF1AD',
NONE = '#2EC0FF',
NA = 'gray',
}

View File

@ -9,6 +9,8 @@ import {
} from '@angular/core';
import { DangerousArtifact } from '../../../../../../../ng-swagger-gen/models/dangerous-artifact';
import * as echarts from 'echarts/core';
import { SeverityColors } from '../security-hub.interface';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-single-bar',
@ -23,6 +25,8 @@ export class SingleBarComponent implements OnChanges {
@ViewChild('container', { static: true })
container: ElementRef;
constructor(private translate: TranslateService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes && changes['dangerousArtifact']) {
this.initChart();
@ -31,74 +35,87 @@ export class SingleBarComponent implements OnChanges {
initChart() {
const chart = echarts.init(this.container.nativeElement);
chart.setOption({
color: ['red', '#e64524', 'orange'],
title: {
text: '',
},
tooltip: {
formatter: '{b}: {c}',
textStyle: {
fontSize: '12px',
whiteSpace: 'nowrap',
const [severity, c, h, m] = [
'VULNERABILITY.GRID.COLUMN_SEVERITY',
'VULNERABILITY.SEVERITY.CRITICAL',
'VULNERABILITY.SEVERITY.HIGH',
'VULNERABILITY.SEVERITY.MEDIUM',
];
this.translate.get([severity, c, h, m]).subscribe(res => {
chart.setOption({
color: [
SeverityColors.CRITICAL,
SeverityColors.HIGH,
SeverityColors.MEDIUM,
],
title: {
text: '',
},
},
legend: {
show: false,
},
series: [
{
type: 'pie',
radius: '65%',
name: 'Severity',
// adjust the start angle
startAngle: 180,
itemStyle: {
borderRadius: 2,
borderWidth: 1,
tooltip: {
formatter: '{b}: {c}',
textStyle: {
fontSize: '12px',
whiteSpace: 'nowrap',
},
labelLine: {
show: false,
},
label: {
show: true,
position: 'inside',
formatter: '{c}',
fontSize: '9px',
},
data: [
{
name: 'Critical',
value: this.dangerousArtifact?.critical_cnt || 0,
},
legend: {
show: false,
},
series: [
{
type: 'pie',
radius: '65%',
name: res[severity],
// adjust the start angle
startAngle: 180,
itemStyle: {
borderRadius: 2,
borderWidth: 1,
},
{
name: 'High',
value: this.dangerousArtifact?.high_cnt || 0,
labelLine: {
show: false,
},
{
name: 'Medium',
value: this.dangerousArtifact?.medium_cnt || 0,
label: {
show: true,
position: 'inside',
formatter: '{c}',
fontSize: '9px',
},
{
// make a record to fill the bottom 50%
value:
this.dangerousArtifact?.critical_cnt +
this.dangerousArtifact?.high_cnt +
this.dangerousArtifact?.medium_cnt || 0,
itemStyle: {
// stop the chart from rendering this piece
color: 'none',
decal: {
symbol: 'none',
data: [
{
name: res[c],
value:
this.dangerousArtifact?.critical_cnt || 0,
},
{
name: res[h],
value: this.dangerousArtifact?.high_cnt || 0,
},
{
name: res[m],
value: this.dangerousArtifact?.medium_cnt || 0,
},
{
// make a record to fill the bottom 50%
value:
this.dangerousArtifact?.critical_cnt +
this.dangerousArtifact?.high_cnt +
this.dangerousArtifact?.medium_cnt || 0,
itemStyle: {
// stop the chart from rendering this piece
color: 'none',
decal: {
symbol: 'none',
},
},
label: {
show: false,
},
},
label: {
show: false,
},
},
],
},
],
],
},
],
});
});
}
}

View File

@ -106,3 +106,7 @@ $row-height: 48px;
display: flex;
align-items: center;
}
.label {
min-width: 3rem;
}

View File

@ -11,7 +11,12 @@ import {
import { SecurityhubService } from '../../../../../../../ng-swagger-gen/services/securityhub.service';
import { SecuritySummary } from '../../../../../../../ng-swagger-gen/models/security-summary';
import { MessageHandlerService } from '../../../../../shared/services/message-handler.service';
import { getDigestLink, severityText, VUL_ID } from '../security-hub.interface';
import {
getDigestLink,
SeverityColors,
severityText,
VUL_ID,
} from '../security-hub.interface';
import { HAS_STYLE_MODE, StyleMode } from '../../../../../services/theme';
import { Subscription } from 'rxjs';
import {
@ -97,7 +102,14 @@ export class VulnerabilitySummaryComponent implements OnInit, OnDestroy {
];
this.translate.get([severity, c, h, m, l, n, u]).subscribe(res => {
this.chart.setOption({
color: ['red', '#e64524', 'orange', '#007CBB', 'grey', 'green'],
color: [
SeverityColors.CRITICAL,
SeverityColors.HIGH,
SeverityColors.MEDIUM,
SeverityColors.LOW,
SeverityColors.NA,
SeverityColors.NONE,
],
title: {
text: '',
},

View File

@ -14,7 +14,6 @@ import { BuildHistoryComponent } from './artifact-additions/build-history/build-
import { ArtifactVulnerabilitiesComponent } from './artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component';
import { ArtifactDefaultService, ArtifactService } from './artifact.service';
import { ArtifactDetailRoutingResolverService } from '../../../../services/routing-resolvers/artifact-detail-routing-resolver.service';
import { ResultTipComponent } from './vulnerability-scanning/result-tip.component';
import { ResultBarChartComponent } from './vulnerability-scanning/result-bar-chart.component';
import { ResultTipHistogramComponent } from './vulnerability-scanning/result-tip-histogram/result-tip-histogram.component';
import { HistogramChartComponent } from './vulnerability-scanning/histogram-chart/histogram-chart.component';
@ -80,7 +79,6 @@ const routes: Routes = [
DependenciesComponent,
BuildHistoryComponent,
ArtifactVulnerabilitiesComponent,
ResultTipComponent,
ResultBarChartComponent,
ResultTipHistogramComponent,
HistogramChartComponent,

View File

@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultBarChartComponent } from './result-bar-chart.component';
import { ResultTipComponent } from './result-tip.component';
import { ResultTipHistogramComponent } from './result-tip-histogram/result-tip-histogram.component';
import { HistogramChartComponent } from './histogram-chart/histogram-chart.component';
import {
@ -35,7 +34,6 @@ describe('ResultBarChartComponent (inline template)', () => {
imports: [SharedTestingModule],
declarations: [
ResultBarChartComponent,
ResultTipComponent,
ResultTipHistogramComponent,
HistogramChartComponent,
],

View File

@ -125,21 +125,10 @@ $twenty-two-pixel: 18px;
}
}
.label.label-medium {
background-color: #ffe4a9;
border: 1px solid orange;
color: orange;
}
.tip-icon-medium {
color: orange;
}
.label.label-low {
background: rgb(251 255 0 / 38%);
color: #c5c50b;
border: 1px solid #e6e63f;
}
.tip-icon-low {
color: #007CBB;
@ -178,30 +167,6 @@ hr {
margin-left: 3px;
}
.shadow-critical {
box-shadow: 1px -1px 1px red;
}
.shadow-high {
box-shadow: 1px -1px 1px #e64524;
}
.shadow-medium {
box-shadow: 1px -1px 1px orange;
}
.shadow-low {
box-shadow: 1px -1px 1px #007CBB;
}
.shadow-none {
box-shadow: 1px -1px 1px green;
}
.shadow-unknown {
box-shadow: 1px -1px 1px gray;
}
.w-360 {
width: 360px !important;
}
@ -248,23 +213,23 @@ hr {
}
.level-critical {
background:red;
color:red;
background:#FF4D2E;
color:#FF4D2E;
}
.level-high {
background:#e64524;
color:#e64524;
background:#FF8F3D;
color:#FF8F3D;
}
.level-medium {
background-color: orange;
color:orange;
background-color: #FFCE66;
color:#FFCE66;
}
.level-low {
background: #007CBB;
color:#007CBB;
background: #FFF1AD;
color:#FFF1AD;
}
.level-none {

View File

@ -9,6 +9,7 @@ import {
VULNERABILITY_SEVERITY,
} from '../../../../../../shared/units/utils';
import { HAS_STYLE_MODE, StyleMode } from '../../../../../../services/theme';
import { SeverityColors } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface';
const MIN = 60;
const MIN_STR = 'min ';
@ -229,27 +230,27 @@ export class ResultTipHistogramComponent implements OnInit {
{
text: 'VULNERABILITY.SEVERITY.CRITICAL',
value: this.criticalCount ? this.criticalCount : 0,
color: 'red',
color: SeverityColors.CRITICAL,
},
{
text: 'VULNERABILITY.SEVERITY.HIGH',
value: this.highCount ? this.highCount : 0,
color: '#e64524',
color: SeverityColors.HIGH,
},
{
text: 'VULNERABILITY.SEVERITY.MEDIUM',
value: this.mediumCount ? this.mediumCount : 0,
color: 'orange',
color: SeverityColors.MEDIUM,
},
{
text: 'VULNERABILITY.SEVERITY.LOW',
value: this.lowCount ? this.lowCount : 0,
color: '#007CBB',
color: SeverityColors.LOW,
},
{
text: 'VULNERABILITY.SEVERITY.NONE',
value: this.noneCount ? this.noneCount : 0,
color: 'grey',
color: SeverityColors.NA,
},
];
}

View File

@ -1,176 +0,0 @@
<div class="tip-wrapper tip-position" [style.width]="maxWidth">
<clr-tooltip>
<div clrTooltipTrigger class="tip-block">
<div
class="tip-wrapper bar-block-high"
[style.width]="tipWidth(5)"></div>
<div
class="tip-wrapper bar-block-medium"
[style.width]="tipWidth(4)"></div>
<div
class="tip-wrapper bar-block-low"
[style.width]="tipWidth(3)"></div>
<div
class="tip-wrapper bar-block-unknown"
[style.width]="tipWidth(2)"></div>
<div
class="tip-wrapper bar-block-none"
[style.width]="tipWidth(1)"></div>
</div>
<clr-tooltip-content
[clrPosition]="'right'"
[clrSize]="'lg'"
*clrIfOpen>
<div [ngSwitch]="scanLevel" class="bar-tooltip-font-larger">
<ng-template [ngSwitchCase]="5">
<clr-icon
shape="exclamation-circle"
class="is-error"
size="32"></clr-icon>
<span
>{{ 'VULNERABILITY.OVERALL_SEVERITY' | translate }}
<span class="font-weight-600">{{
'VULNERABILITY.SEVERITY.HIGH'
| translate
| titlecase
}}</span></span
>
</ng-template>
<ng-template [ngSwitchCase]="4">
<clr-icon
*ngIf="hasMedium"
shape="exclamation-triangle"
class="tip-icon-medium"
size="30"></clr-icon>
<span
>{{ 'VULNERABILITY.OVERALL_SEVERITY' | translate }}
<span class="font-weight-600">{{
'VULNERABILITY.SEVERITY.MEDIUM'
| translate
| titlecase
}}</span></span
>
</ng-template>
<ng-template [ngSwitchCase]="3">
<clr-icon
shape="play"
class="tip-icon-low rotate-90"
size="28"></clr-icon>
<span
>{{ 'VULNERABILITY.OVERALL_SEVERITY' | translate }}
<span class="font-weight-600">{{
'VULNERABILITY.SEVERITY.LOW' | translate | titlecase
}}</span></span
>
</ng-template>
<ng-template [ngSwitchCase]="2">
<clr-icon
shape="help"
size="24"
class="help-icon"></clr-icon>
<span
>{{ 'VULNERABILITY.OVERALL_SEVERITY' | translate }}
<span class="font-weight-600">{{
'VULNERABILITY.SEVERITY.UNKNOWN'
| translate
| titlecase
}}</span></span
>
</ng-template>
<ng-template [ngSwitchCase]="1">
<clr-icon
shape="check-circle"
class="is-success"
size="32"></clr-icon>
<span>{{
'VULNERABILITY.NO_VULNERABILITY' | translate
}}</span>
</ng-template>
</div>
<hr />
<div>
<span class="bar-tooltip-font bar-tooltip-font-title">{{
tipTitle
}}</span>
</div>
<div class="bar-summary bar-tooltip-fon">
<div *ngIf="hasHigh" class="bar-summary-item">
<span
><clr-icon
shape="exclamation-circle"
class="is-error"
size="24"></clr-icon
></span>
<span>{{ highCount }}</span
><span
>{{ getPackageText(highCount) | translate }}
{{ haveText(highCount) | translate }}
{{ 'VULNERABILITY.SEVERITY.HIGH' | translate }}
{{ unitText(highCount) | translate }}</span
>
</div>
<div *ngIf="hasMedium" class="bar-summary-item">
<span
><clr-icon
*ngIf="hasMedium"
shape="exclamation-triangle"
class="tip-icon-medium"
size="22"></clr-icon
></span>
<span>{{ mediumCount }}</span
><span
>{{ getPackageText(mediumCount) | translate }}
{{ haveText(mediumCount) | translate }}
{{ 'VULNERABILITY.SEVERITY.MEDIUM' | translate }}
{{ unitText(mediumCount) | translate }}</span
>
</div>
<div *ngIf="hasLow" class="bar-summary-item">
<span
><clr-icon
shape="play"
class="tip-icon-low rotate-90"
size="20"></clr-icon
></span>
<span>{{ lowCount }}</span
><span
>{{ getPackageText(lowCount) | translate }}
{{ haveText(lowCount) | translate }}
{{ 'VULNERABILITY.SEVERITY.LOW' | translate }}
{{ unitText(lowCount) | translate }}</span
>
</div>
<div *ngIf="hasUnknown" class="bar-summary-item">
<span><clr-icon shape="help" size="18"></clr-icon></span>
<span>{{ unknownCount }}</span
><span
>{{ getPackageText(unknownCount) | translate }}
{{ haveText(unknownCount) | translate }}
{{ 'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}
{{ unitText(unknownCount) | translate }}</span
>
</div>
<div *ngIf="hasNone" class="bar-summary-item">
<span
><clr-icon
shape="check-circle"
class="is-success"
size="24"></clr-icon
></span>
<span>{{ noneCount }}</span
><span>{{
'VULNERABILITY.SEVERITY.NONE' | translate
}}</span>
</div>
</div>
<div>
<span class="bar-scanning-time"
>{{ 'VULNERABILITY.CHART.SCANNING_TIME' | translate }}
</span>
<span>{{
completeTimestamp | harborDatetime : 'MM/dd/y HH:mm:ss'
}}</span>
</div>
</clr-tooltip-content>
</clr-tooltip>
</div>

View File

@ -1,67 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultTipComponent } from './result-tip.component';
import {
UserPermissionDefaultService,
UserPermissionService,
VulnerabilitySummary,
} from '../../../../../shared/services';
import { VULNERABILITY_SCAN_STATUS } from '../../../../../shared/units/utils';
import { SharedTestingModule } from '../../../../../shared/shared.module';
describe('ResultTipComponent (inline template)', () => {
let component: ResultTipComponent;
let fixture: ComponentFixture<ResultTipComponent>;
let mockData: VulnerabilitySummary = {
scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS,
severity: 'High',
end_time: new Date(),
summary: {
total: 124,
fixable: 50,
summary: {
High: 5,
Low: 5,
},
},
};
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SharedTestingModule],
declarations: [ResultTipComponent],
providers: [
{
provide: UserPermissionService,
useClass: UserPermissionDefaultService,
},
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResultTipComponent);
component = fixture.componentInstance;
component.summary = mockData;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should reader the bar with different width', () => {
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('0px');
let el2: HTMLElement =
fixture.nativeElement.querySelector('.bar-block-high');
expect(el2).not.toBeNull();
expect(el2.style.width).toEqual('0px');
});
});
});

View File

@ -1,198 +0,0 @@
import { Component, Input } from '@angular/core';
import {
VulnerabilitySeverity,
VulnerabilitySummary,
} from '../../../../../shared/services';
import { VULNERABILITY_SCAN_STATUS } from '../../../../../shared/units/utils';
export const MIN_TIP_WIDTH = 5;
export const MAX_TIP_WIDTH = 100;
@Component({
selector: 'hbr-vulnerability-summary-chart',
templateUrl: './result-tip.component.html',
styleUrls: ['./scanning.scss'],
})
export class ResultTipComponent {
_tipTitle: string = '';
_highCount: number = 0;
_mediumCount: number = 0;
_lowCount: number = 0;
_unknownCount: number = 0;
_noneCount: number = 0;
totalPackages: number = 0;
packagesWithVul: number = 0;
@Input() summary: VulnerabilitySummary = {
scan_status: VULNERABILITY_SCAN_STATUS.NOT_SCANNED,
severity: '',
end_time: new Date(),
};
get scanLevel() {
let level;
if (this._highCount && this._highCount >= 1) {
level = VulnerabilitySeverity.HIGH;
} else if (this._mediumCount && this._mediumCount >= 1) {
level = VulnerabilitySeverity.MEDIUM;
} else if (this._lowCount && this._lowCount >= 1) {
level = VulnerabilitySeverity.LOW;
} else if (this._unknownCount && this._unknownCount >= 1) {
level = VulnerabilitySeverity.UNKNOWN;
} else if (this.totalPackages === 0) {
level = VulnerabilitySeverity.UNKNOWN;
} else {
level = VulnerabilitySeverity.NONE;
}
return level;
}
constructor() {}
tipWidth(severity: VulnerabilitySeverity): string {
let n: number = 0;
let m: number = this.totalPackages;
if (m === 0) {
// If no packages recognized, then show grey
if (severity === VulnerabilitySeverity.UNKNOWN) {
return MAX_TIP_WIDTH + 'px';
} else {
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:
// Show all the left as green bar
n =
m -
(this.highCount +
this.mediumCount +
this.lowCount +
this.unknownCount);
if (n < 0) {
n = 0;
}
break;
default:
n = 0;
break;
}
let width: number = Math.round((n / m) * MAX_TIP_WIDTH);
if (width > 0 && width < MIN_TIP_WIDTH) {
width = MIN_TIP_WIDTH;
}
return width + 'px';
}
unitText(count: number): string {
if (count > 1) {
return 'VULNERABILITY.PLURAL';
}
return 'VULNERABILITY.SINGULAR';
}
packageText(count: number): string {
return count > 1 ? 'VULNERABILITY.PACKAGES' : 'VULNERABILITY.PACKAGE';
}
getPackageText(count: number): string {
return count > 1
? 'VULNERABILITY.GRID.COLUMN_PACKAGES'
: 'VULNERABILITY.GRID.COLUMN_PACKAGE';
}
haveText(count: number): string {
return count > 1 ? 'TAG.HAVE' : 'TAG.HAS';
}
public get completeTimestamp(): Date {
return this.summary && this.summary.end_time
? this.summary.end_time
: new Date();
}
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 {
return this._tipTitle;
}
public get highCount(): number {
return this._highCount;
}
public get mediumCount(): number {
return this._mediumCount;
}
public get lowCount(): number {
return this._lowCount;
}
public get unknownCount(): number {
return this._unknownCount;
}
public get noneCount(): number {
return this._noneCount;
}
public get highSuffix(): string {
return this.unitText(this.highCount);
}
public get mediumSuffix(): string {
return this.unitText(this.mediumCount);
}
public get lowSuffix(): string {
return this.unitText(this.lowCount);
}
public get unknownSuffix(): string {
return this.unitText(this.unknownCount);
}
public get noneSuffix(): string {
return this.unitText(this.noneCount);
}
public get maxWidth(): string {
return MAX_TIP_WIDTH + 20 + 'px';
}
}

View File

@ -44,30 +44,6 @@
max-width: 120px;
}
.bar-block-high {
background-color: #e64524;
}
.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: #fff;
}
.bar-tooltip-font-title {
font-weight: 600;