Skip to content

Commit 310651f

Browse files
Merge pull request #510 from devtron-labs/fix/security-card-threats
feat: add failed state in scan security card
2 parents 612b099 + 9398f79 commit 310651f

File tree

7 files changed

+157
-42
lines changed

7 files changed

+157
-42
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.4.8-beta-2",
3+
"version": "1.4.8-beta-3",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Shared/Components/Security/SecurityDetailsCards/SecurityCard.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SegmentedBarChart } from '@Common/SegmentedBarChart'
22
import { ReactComponent as ICShieldWarning } from '@Icons/ic-shield-warning-outline.svg'
33
import { ReactComponent as ICShieldSecure } from '@Icons/ic-shield-check.svg'
4+
import { ReactComponent as ICError } from '@Icons/ic-error-exclamation.svg'
45
import { ReactComponent as ICArrowRight } from '@Icons/ic-caret-down-small.svg'
56
import { SecurityCardProps } from './types'
67
import { SUB_CATEGORIES } from '../SecurityModal/types'
@@ -9,19 +10,41 @@ import './securityCard.scss'
910
import { getTotalSeverities } from '../utils'
1011
import { SECURITY_CONFIG } from '../constants'
1112

12-
const SecurityCard = ({ category, subCategory, severityCount = {}, handleCardClick }: SecurityCardProps) => {
13-
const totalCount = getTotalSeverities(severityCount)
13+
const SecurityCard = ({
14+
category,
15+
subCategory,
16+
severities = {},
17+
handleCardClick,
18+
scanFailed = false,
19+
}: SecurityCardProps) => {
20+
const totalCount = getTotalSeverities(severities)
1421

1522
const hasThreats: boolean = !!totalCount
1623

1724
const entities = Object.entries(SEVERITIES)
1825
.map(([key, severity]) => ({
1926
...severity,
20-
value: severityCount[key],
27+
value: severities[key],
2128
}))
2229
.filter((entity) => !!entity.value)
2330

31+
const getInfoIcon = () => {
32+
if (scanFailed) {
33+
return <ICError className="icon-dim-24 dc__no-shrink" />
34+
}
35+
return hasThreats ? (
36+
<ICShieldWarning className="icon-dim-24 scr-5 dc__no-shrink" />
37+
) : (
38+
<ICShieldSecure className="icon-dim-24 scg-5 dc__no-shrink" />
39+
)
40+
}
41+
2442
const getTitleSubtitle = (): { title: string; subtitle?: string } => {
43+
if (scanFailed) {
44+
return subCategory === SUB_CATEGORIES.VULNERABILITIES
45+
? { title: 'Vulnerability scan failed', subtitle: 'Failed' }
46+
: { title: 'License scan failed', subtitle: 'Failed' }
47+
}
2548
switch (subCategory) {
2649
case SUB_CATEGORIES.EXPOSED_SECRETS:
2750
return hasThreats
@@ -55,7 +78,7 @@ const SecurityCard = ({ category, subCategory, severityCount = {}, handleCardCli
5578

5679
return (
5780
<div
58-
className={`w-100 bcn-0 p-20 flexbox-col dc__gap-16 br-8 dc__border security-card security-card${hasThreats ? '--threat' : '--secure'}`}
81+
className={`w-100 p-20 flexbox-col dc__gap-16 br-8 dc__border security-card security-card${hasThreats || scanFailed ? '--threat' : '--secure'}`}
5982
role="button"
6083
tabIndex={0}
6184
onClick={handleCardClick}
@@ -69,22 +92,18 @@ const SecurityCard = ({ category, subCategory, severityCount = {}, handleCardCli
6992
<ICArrowRight className="icon-dim-20 dc__flip-270 scb-5 arrow-right" />
7093
</div>
7194
</div>
72-
{hasThreats ? (
73-
<ICShieldWarning className="icon-dim-24 scr-5 dc__no-shrink" />
74-
) : (
75-
<ICShieldSecure className="icon-dim-24 scg-5 dc__no-shrink" />
76-
)}
95+
{getInfoIcon()}
7796
</div>
7897
<div className="flexbox-col dc__gap-12">
79-
{hasThreats || severityCount.success ? (
98+
{scanFailed || !(hasThreats || severities.success) ? (
99+
<div className="bcn-1 br-4 h-8" />
100+
) : (
80101
<SegmentedBarChart
81102
entities={entities}
82103
labelClassName="fs-13 fw-4 lh-20 cn-9"
83104
countClassName="fs-13 fw-6 lh-20 cn-7"
84105
swapLegendAndBar
85106
/>
86-
) : (
87-
<div className="bcn-1 br-4 h-8" />
88107
)}
89108
{subtitle && <span className="cn-9 fs-13 lh-20">{subtitle}</span>}
90109
</div>

src/Shared/Components/Security/SecurityDetailsCards/SecurityDetailsCards.tsx

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { EMPTY_STATE_STATUS, SCAN_TOOL_ID_CLAIR, SCAN_TOOL_ID_TRIVY } from '@Sha
33
import { useState } from 'react'
44
import { GenericEmptyState } from '@Common/index'
55
import { ReactComponent as NoVulnerability } from '@Icons/ic-vulnerability-not-found.svg'
6+
import { GenericSectionErrorState } from '@Shared/Components/GenericSectionErrorState'
67
import SecurityCard from './SecurityCard'
78
import { CATEGORIES, SecurityModalStateType, SUB_CATEGORIES } from '../SecurityModal/types'
89
import { SecurityCardProps, SecurityDetailsCardsProps } from './types'
910
import { SecurityModal } from '../SecurityModal'
1011
import { DEFAULT_SECURITY_MODAL_IMAGE_STATE } from '../SecurityModal/constants'
1112
import { ScanCategories, ScanSubCategories } from '../types'
12-
import { getSecurityConfig, getCompiledSecurityThreats, getTotalSeverities } from '../utils'
13+
import { getSecurityConfig, getCompiledSecurityThreats, getTotalSeverities, getStatusForScanList } from '../utils'
1314
import './securityCard.scss'
1415

1516
const SecurityDetailsCards = ({ scanResult, Sidebar }: SecurityDetailsCardsProps) => {
@@ -74,31 +75,62 @@ const SecurityDetailsCards = ({ scanResult, Sidebar }: SecurityDetailsCardsProps
7475
return (
7576
<>
7677
<div className="flexbox-col dc__gap-20 mw-600 dc__mxw-1200">
77-
{Object.keys(SECURITY_CONFIG).map((category: ScanCategories) => (
78-
<div className="flexbox-col dc__gap-12" key={category}>
79-
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
80-
<span className="fs-13 fw-6 lh-1-5 cn-9">{SECURITY_CONFIG[category].label}</span>
81-
<ScannedByToolModal scanToolId={getScanToolId(category)} />
82-
</div>
83-
<div className="dc__grid security-cards">
84-
{SECURITY_CONFIG[category].subCategories.map((subCategory: ScanSubCategories) => {
85-
const severityCount =
86-
subCategory === SUB_CATEGORIES.MISCONFIGURATIONS
87-
? scanResult[category][subCategory]?.misConfSummary?.status
88-
: scanResult[category][subCategory]?.summary?.severities
78+
{Object.keys(SECURITY_CONFIG).map((category: ScanCategories) => {
79+
const categoryFailed: boolean =
80+
category !== CATEGORIES.IMAGE_SCAN &&
81+
(scanResult.codeScan?.status === 'Failed' || scanResult.kubernetesManifest?.status === 'Failed')
8982

90-
return (
91-
<SecurityCard
92-
category={category}
93-
subCategory={subCategory}
94-
severityCount={severityCount}
95-
handleCardClick={handleCardClick(category, subCategory)}
83+
return (
84+
<div className="flexbox-col dc__gap-12" key={category}>
85+
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
86+
<span className="fs-13 fw-6 lh-1-5 cn-9">{SECURITY_CONFIG[category].label}</span>
87+
<ScannedByToolModal scanToolId={getScanToolId(category)} />
88+
</div>
89+
{categoryFailed ? (
90+
<div className="dc__border br-8">
91+
<GenericSectionErrorState
92+
title={
93+
category === CATEGORIES.CODE_SCAN
94+
? 'Code scan failed'
95+
: 'Manifest scan failed'
96+
}
97+
subTitle=""
98+
description=""
9699
/>
97-
)
98-
})}
100+
</div>
101+
) : (
102+
<div className="dc__grid security-cards">
103+
{SECURITY_CONFIG[category].subCategories.map((subCategory: ScanSubCategories) => {
104+
// Explicit handling if subcategory is null
105+
if (!scanResult[category][subCategory]) {
106+
return null
107+
}
108+
109+
const scanFailed: boolean =
110+
category === CATEGORIES.IMAGE_SCAN &&
111+
getStatusForScanList(scanResult[category][subCategory].list ?? []) ===
112+
'Failed'
113+
114+
const severities =
115+
subCategory === SUB_CATEGORIES.MISCONFIGURATIONS
116+
? scanResult[category][subCategory]?.misConfSummary?.status
117+
: scanResult[category][subCategory]?.summary?.severities
118+
119+
return (
120+
<SecurityCard
121+
category={category}
122+
subCategory={subCategory}
123+
severities={severities}
124+
handleCardClick={handleCardClick(category, subCategory)}
125+
scanFailed={scanFailed}
126+
/>
127+
)
128+
})}
129+
</div>
130+
)}
99131
</div>
100-
</div>
101-
))}
132+
)
133+
})}
102134
</div>
103135
{showSecurityModal && (
104136
<SecurityModal

src/Shared/Components/Security/SecurityDetailsCards/types.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { ScanCategories, ScanSubCategories } from '../types'
44
export interface SecurityCardProps {
55
category: ScanCategories
66
subCategory: ScanSubCategories
7-
severityCount: Partial<Record<SeveritiesDTO, number>>
7+
severities: Partial<Record<SeveritiesDTO, number>>
88
handleCardClick: () => void
9+
scanFailed?: boolean
910
}
1011

1112
export interface SecurityDetailsCardsProps extends Pick<SecurityModalPropsType, 'Sidebar'> {

src/Shared/Components/Security/Vulnerabilities/Vulnerabilities.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getSecurityScan } from '../SecurityModal/service'
2121
import { SecurityCard } from '../SecurityDetailsCards'
2222
import { CATEGORIES, SUB_CATEGORIES } from '../SecurityModal/types'
2323
import { SecurityModal } from '../SecurityModal'
24+
import { getStatusForScanList } from '../utils'
2425

2526
const Vulnerabilities = ({
2627
isScanned,
@@ -90,13 +91,19 @@ const Vulnerabilities = ({
9091
setShowSecurityModal(false)
9192
}
9293

94+
const imageScanVulnerabilities = scanResultResponse.result?.imageScan?.vulnerability
95+
const imageScanList = imageScanVulnerabilities?.list || []
96+
97+
const scanFailed: boolean = getStatusForScanList(imageScanList) === 'Failed'
98+
9399
return (
94100
<div className="p-12">
95101
<SecurityCard
96102
category={CATEGORIES.IMAGE_SCAN}
97103
subCategory={SUB_CATEGORIES.VULNERABILITIES}
98-
severityCount={scanResultResponse?.result?.imageScan?.vulnerability?.summary?.severities}
104+
severities={imageScanVulnerabilities?.summary?.severities}
99105
handleCardClick={handleCardClick}
106+
scanFailed={scanFailed}
100107
/>
101108
{showSecurityModal && (
102109
<SecurityModal

src/Shared/Components/Security/utils.tsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { SECURITY_CONFIG } from './constants'
22
import { ScanResultDTO, SeveritiesDTO } from './SecurityModal'
3-
import { CATEGORIES, SUB_CATEGORIES } from './SecurityModal/types'
3+
import {
4+
CATEGORIES,
5+
ImageScanLicenseListType,
6+
ImageScanVulnerabilityListType,
7+
StatusType,
8+
SUB_CATEGORIES,
9+
} from './SecurityModal/types'
410
import { CategoriesConfig, SecurityConfigType, ScanCategories, ScanSubCategories } from './types'
511

612
export const getCVEUrlFromCVEName = (cveName: string): string =>
@@ -43,7 +49,10 @@ export const getCompiledSecurityThreats = (scanResult: ScanResultDTO): Partial<R
4349
subCategory === SUB_CATEGORIES.MISCONFIGURATIONS
4450
? scanResult[category][subCategory]?.misConfSummary?.status
4551
: scanResult[category][subCategory]?.summary?.severities
46-
threatsArray.push(severity)
52+
53+
if (Object.keys(severity || {}).length) {
54+
threatsArray.push(severity)
55+
}
4756
})
4857
})
4958

@@ -60,3 +69,50 @@ export const getCompiledSecurityThreats = (scanResult: ScanResultDTO): Partial<R
6069

6170
return scanThreats
6271
}
72+
73+
const getIsStatusProgressing = (status: StatusType['status']): boolean =>
74+
status === 'Progressing' || status === 'Running'
75+
76+
export const getStatusForScanList = (
77+
scanList: ImageScanVulnerabilityListType[] | ImageScanLicenseListType[],
78+
): StatusType['status'] => {
79+
const scanProgressing = scanList.some((scan) => getIsStatusProgressing(scan.status))
80+
if (scanProgressing) {
81+
return 'Progressing'
82+
}
83+
const scanFailed = scanList.some((scan) => scan.status === 'Failed')
84+
if (scanFailed) {
85+
return 'Failed'
86+
}
87+
return 'Completed'
88+
}
89+
90+
export const getSecurityScanStatus = (scanResult: ScanResultDTO): StatusType['status'] => {
91+
const imageScanList = scanResult.imageScan?.vulnerability?.list ?? []
92+
const licenseScanList = scanResult.imageScan?.license?.list ?? []
93+
const codeScanStatus = scanResult.codeScan?.status
94+
const manifestScanStatus = scanResult.kubernetesManifest?.status
95+
96+
const imageScanStatus = getStatusForScanList(imageScanList)
97+
const licenseScanStatus = getStatusForScanList(licenseScanList)
98+
99+
if (
100+
imageScanStatus === 'Progressing' ||
101+
licenseScanStatus === 'Progressing' ||
102+
getIsStatusProgressing(codeScanStatus) ||
103+
getIsStatusProgressing(manifestScanStatus)
104+
) {
105+
return 'Progressing'
106+
}
107+
108+
if (
109+
imageScanStatus === 'Failed' ||
110+
licenseScanStatus === 'Failed' ||
111+
codeScanStatus === 'Failed' ||
112+
manifestScanStatus === 'Failed'
113+
) {
114+
return 'Failed'
115+
}
116+
117+
return 'Completed'
118+
}

0 commit comments

Comments
 (0)