Skip to content

Commit 19ea620

Browse files
committed
feat: breakdown image cards in imageScan codeScan and manifestScan
1 parent b63105d commit 19ea620

File tree

17 files changed

+334
-144
lines changed

17 files changed

+334
-144
lines changed

src/Assets/Icon/ic-shield-check.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { SegmentedBarChart } from '@Common/SegmentedBarChart'
2+
import { ReactComponent as ICShieldWarning } from '@Icons/ic-shield-warning-outline.svg'
3+
import { ReactComponent as ICShieldSecure } from '@Icons/ic-shield-check.svg'
4+
import { ReactComponent as ICArrowRight } from '@Icons/ic-caret-down-small.svg'
5+
import { SecurityCardProps } from './types'
6+
import { CATEGORIES, SeveritiesDTO, SUB_CATEGORIES } from '../SecurityModal/types'
7+
import './securityCard.scss'
8+
import { SEVERITIES } from '../SecurityModal/constants'
9+
10+
const SecurityCard = ({
11+
category,
12+
subCategory,
13+
severityCount = {},
14+
handleCardClick,
15+
rootClassName = '',
16+
}: SecurityCardProps) => {
17+
const totalCount = Object.entries(severityCount)
18+
.filter(([key]) => key !== SeveritiesDTO.SUCCESSES)
19+
.reduce((acc, [, value]) => acc + value, 0)
20+
21+
const hasThreats: boolean = !!totalCount
22+
23+
const entities = Object.entries(SEVERITIES)
24+
.map(([key, severity]) => ({
25+
...severity,
26+
value: severityCount[key],
27+
}))
28+
.filter((entity) => !!entity.value)
29+
30+
const getScanType = () => {
31+
switch (category) {
32+
case CATEGORIES.KUBERNETES_MANIFEST:
33+
return 'Manifest Scan'
34+
case CATEGORIES.CODE_SCAN:
35+
return 'Code Scan'
36+
default:
37+
return 'Image Scan'
38+
}
39+
}
40+
41+
const getTitleSubtitle = (): { title: string; subtitle?: string } => {
42+
switch (subCategory) {
43+
case SUB_CATEGORIES.EXPOSED_SECRETS:
44+
return hasThreats
45+
? { title: `${totalCount} exposed secrets` }
46+
: {
47+
title: 'No exposed secrets',
48+
subtitle: 'No exposed secrets like passwords, api keys, and tokens found',
49+
}
50+
case SUB_CATEGORIES.LICENSE:
51+
return hasThreats
52+
? { title: `${totalCount} license risks` }
53+
: { title: 'No license risks', subtitle: 'No license risks or compliance issues found' }
54+
case SUB_CATEGORIES.MISCONFIGURATIONS:
55+
return hasThreats
56+
? { title: `${totalCount} misconfigurations` }
57+
: { title: 'No misconfiguration', subtitle: 'No misconfigurations found' }
58+
default:
59+
return hasThreats
60+
? { title: `${totalCount} vulnerabilities` }
61+
: { title: 'No vulnerabilities', subtitle: 'No security vulnerability found' }
62+
}
63+
}
64+
65+
const { title, subtitle } = getTitleSubtitle()
66+
67+
return (
68+
<div
69+
className={`${rootClassName} min-w-500 w-50 bcn-0 p-20 flexbox-col dc__gap-16 br-8 dc__border security-card security-card${hasThreats ? '--threat' : '--secure'}`}
70+
role="button"
71+
tabIndex={0}
72+
onClick={handleCardClick}
73+
>
74+
<div className="flexbox dc__content-space">
75+
<div className="flexbox-col">
76+
<span className="fs-12 fw-4 lh-1-5 cn-7">{getScanType()}</span>
77+
<div className="fs-15 fw-6 lh-1-5 cn-9 flex">
78+
<span className="security-card-title">{title}</span>
79+
<ICArrowRight className="icon-dim-20 dc__flip-270 scb-5 arrow-right" />
80+
</div>
81+
</div>
82+
{hasThreats ? <ICShieldWarning className="icon-dim-24" /> : <ICShieldSecure className="icon-dim-24" />}
83+
</div>
84+
<div className="flexbox-col dc__gap-12">
85+
{hasThreats || severityCount.success ? (
86+
<SegmentedBarChart
87+
entities={entities}
88+
labelClassName="fs-13 fw-4 lh-20 cn-9"
89+
countClassName="fs-13 fw-6 lh-20 cn-7"
90+
/>
91+
) : (
92+
<div className="bcn-1 br-4 h-8" />
93+
)}
94+
{subtitle && <span>{subtitle}</span>}
95+
</div>
96+
</div>
97+
)
98+
}
99+
100+
export default SecurityCard
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { ScannedByToolModal } from '@Shared/Components/ScannedByToolModal'
2+
import { SCAN_TOOL_ID_CLAIR, SCAN_TOOL_ID_TRIVY } from '@Shared/constants'
3+
import { useState } from 'react'
4+
import SecurityCard from './SecurityCard'
5+
import { CATEGORIES, SecurityModalStateType, SUB_CATEGORIES } from '../SecurityModal/types'
6+
import { SecurityCardProps, SecurityDetailsCardsProps } from './types'
7+
import { SecurityModal } from '../SecurityModal'
8+
import { DEFAULT_SECURITY_MODAL_IMAGE_STATE } from '../SecurityModal/constants'
9+
10+
const SecurityDetailsCards = ({ scanResult, Sidebar }: SecurityDetailsCardsProps) => {
11+
const [showSecurityModal, setShowSecurityModal] = useState<boolean>(false)
12+
const [modalState, setModalState] = useState<SecurityModalStateType>(DEFAULT_SECURITY_MODAL_IMAGE_STATE)
13+
const { imageScan, codeScan, kubernetesManifest } = scanResult
14+
15+
const scanToolId =
16+
imageScan?.vulnerability?.list?.[0].scanToolName === 'TRIVY' ? SCAN_TOOL_ID_TRIVY : SCAN_TOOL_ID_CLAIR
17+
18+
const handleCardClick = (
19+
category: SecurityCardProps['category'],
20+
subCategory: SecurityCardProps['subCategory'],
21+
) => {
22+
setShowSecurityModal(true)
23+
setModalState({
24+
category,
25+
subCategory,
26+
detailViewData: null,
27+
})
28+
}
29+
30+
const handleModalClose = () => {
31+
setShowSecurityModal(false)
32+
}
33+
34+
return (
35+
<>
36+
<div className="flexbox-col dc__gap-20">
37+
{imageScan ? (
38+
<div className="flexbox-col dc__gap-12">
39+
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
40+
<span className="fs-13 fw-6 lh-1-5 cn-9">Image Scan</span>
41+
<ScannedByToolModal scanToolId={scanToolId} />
42+
</div>
43+
<div className="flexbox dc__gap-12">
44+
<SecurityCard
45+
category={CATEGORIES.IMAGE_SCAN}
46+
subCategory={SUB_CATEGORIES.VULNERABILITIES}
47+
severityCount={imageScan.vulnerability?.summary?.severities}
48+
handleCardClick={() =>
49+
handleCardClick(CATEGORIES.IMAGE_SCAN, SUB_CATEGORIES.VULNERABILITIES)
50+
}
51+
/>
52+
<SecurityCard
53+
category={CATEGORIES.IMAGE_SCAN}
54+
subCategory={SUB_CATEGORIES.LICENSE}
55+
severityCount={imageScan.license?.summary?.severities}
56+
handleCardClick={() => handleCardClick(CATEGORIES.IMAGE_SCAN, SUB_CATEGORIES.LICENSE)}
57+
/>
58+
</div>
59+
</div>
60+
) : null}
61+
{codeScan ? (
62+
<div className="flexbox-col dc__gap-12">
63+
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
64+
<span className="fs-13 fw-6 lh-1-5 cn-9">Code Scan</span>
65+
<ScannedByToolModal scanToolId={scanToolId} />
66+
</div>
67+
<div className="flexbox dc__gap-12">
68+
<SecurityCard
69+
category={CATEGORIES.CODE_SCAN}
70+
subCategory={SUB_CATEGORIES.VULNERABILITIES}
71+
severityCount={codeScan.vulnerability?.summary.severities}
72+
handleCardClick={() =>
73+
handleCardClick(CATEGORIES.CODE_SCAN, SUB_CATEGORIES.VULNERABILITIES)
74+
}
75+
/>
76+
<SecurityCard
77+
category={CATEGORIES.CODE_SCAN}
78+
subCategory={SUB_CATEGORIES.LICENSE}
79+
severityCount={codeScan.license?.summary?.severities}
80+
handleCardClick={() => handleCardClick(CATEGORIES.CODE_SCAN, SUB_CATEGORIES.LICENSE)}
81+
/>
82+
</div>
83+
<div className="flexbox dc__gap-12">
84+
<SecurityCard
85+
category={CATEGORIES.CODE_SCAN}
86+
subCategory={SUB_CATEGORIES.MISCONFIGURATIONS}
87+
severityCount={codeScan.misConfigurations?.misConfSummary?.status}
88+
handleCardClick={() =>
89+
handleCardClick(CATEGORIES.CODE_SCAN, SUB_CATEGORIES.MISCONFIGURATIONS)
90+
}
91+
/>
92+
<SecurityCard
93+
category={CATEGORIES.CODE_SCAN}
94+
subCategory={SUB_CATEGORIES.EXPOSED_SECRETS}
95+
severityCount={codeScan.exposedSecrets?.summary?.severities}
96+
handleCardClick={() =>
97+
handleCardClick(CATEGORIES.CODE_SCAN, SUB_CATEGORIES.EXPOSED_SECRETS)
98+
}
99+
/>
100+
</div>
101+
</div>
102+
) : null}
103+
{kubernetesManifest ? (
104+
<div className="flexbox-col dc__gap-12">
105+
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
106+
<span className="fs-13 fw-6 lh-1-5 cn-9">Manifest Scan</span>
107+
<ScannedByToolModal scanToolId={scanToolId} />
108+
</div>
109+
<div className="flexbox dc__gap-12">
110+
<SecurityCard
111+
category={CATEGORIES.KUBERNETES_MANIFEST}
112+
subCategory={SUB_CATEGORIES.MISCONFIGURATIONS}
113+
severityCount={kubernetesManifest.misConfigurations?.misConfSummary?.status}
114+
handleCardClick={() =>
115+
handleCardClick(CATEGORIES.KUBERNETES_MANIFEST, SUB_CATEGORIES.MISCONFIGURATIONS)
116+
}
117+
/>
118+
<SecurityCard
119+
category={CATEGORIES.KUBERNETES_MANIFEST}
120+
subCategory={SUB_CATEGORIES.EXPOSED_SECRETS}
121+
severityCount={kubernetesManifest.exposedSecrets?.summary?.severities}
122+
handleCardClick={() =>
123+
handleCardClick(CATEGORIES.KUBERNETES_MANIFEST, SUB_CATEGORIES.EXPOSED_SECRETS)
124+
}
125+
/>
126+
</div>
127+
</div>
128+
) : null}
129+
</div>
130+
{showSecurityModal && (
131+
<SecurityModal
132+
isLoading={false}
133+
error={null}
134+
responseData={scanResult}
135+
handleModalClose={handleModalClose}
136+
Sidebar={Sidebar}
137+
defaultState={modalState}
138+
/>
139+
)}
140+
</>
141+
)
142+
}
143+
144+
export default SecurityDetailsCards
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as SecurityCard } from './SecurityCard'
2+
export { default as SecurityDetailsCards } from './SecurityDetailsCards'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.security-card {
2+
&--threat {
3+
background: radial-gradient(25.91% 100% at 100% 0%, #FDE7E7 0%, #FFF 100%);
4+
}
5+
&--secure {
6+
background: radial-gradient(25.91% 100% at 100% 0%, #E9FBF4 0%, #FFF 100%);
7+
}
8+
9+
.arrow-right {
10+
visibility: hidden;
11+
}
12+
13+
&:hover {
14+
.arrow-right {
15+
visibility: visible;
16+
}
17+
18+
.security-card-title {
19+
color: var(--B500);
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
CATEGORIES,
3+
ScanResultDTO,
4+
SecurityModalPropsType,
5+
SeveritiesDTO,
6+
SUB_CATEGORIES,
7+
} from '../SecurityModal/types'
8+
9+
type Categories = keyof typeof CATEGORIES
10+
type SubCategories = keyof typeof SUB_CATEGORIES
11+
12+
export interface SecurityCardProps {
13+
category: (typeof CATEGORIES)[Categories]
14+
subCategory: (typeof SUB_CATEGORIES)[SubCategories]
15+
severityCount: Partial<Record<SeveritiesDTO, number>>
16+
handleCardClick: () => void
17+
rootClassName?: string
18+
}
19+
20+
export interface SecurityDetailsCardsProps extends Pick<SecurityModalPropsType, 'Sidebar'> {
21+
scanResult: ScanResultDTO
22+
}

src/Shared/Components/Security/SecurityModal/SecurityModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const SecurityModal: React.FC<SecurityModalPropsType> = ({
4040
error,
4141
responseData,
4242
hidePolicy = false,
43+
defaultState,
4344
}) => {
4445
const data = responseData ?? null
4546

@@ -50,7 +51,9 @@ const SecurityModal: React.FC<SecurityModalPropsType> = ({
5051
kubernetesManifest: !!data?.kubernetesManifest,
5152
}
5253

53-
const [state, setState] = useState<SecurityModalStateType>(getDefaultSecurityModalState(categoriesConfig))
54+
const [state, setState] = useState<SecurityModalStateType>(
55+
defaultState ?? getDefaultSecurityModalState(categoriesConfig),
56+
)
5457

5558
const setDetailViewData = (detailViewData: DetailViewDataType) => {
5659
setState((prevState) => ({
@@ -76,7 +79,7 @@ const SecurityModal: React.FC<SecurityModalPropsType> = ({
7679
onClick={handleModalClose}
7780
showAriaLabelInTippy={false}
7881
size={ComponentSizeType.xs}
79-
style={ButtonStyleType.neutral}
82+
style={ButtonStyleType.negativeGrey}
8083
variant={ButtonVariantType.borderLess}
8184
/>
8285
</div>

src/Shared/Components/Security/SecurityModal/constants.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
SecurityModalStateType,
1717
} from './types'
1818

19-
const DEFAULT_SECURITY_MODAL_IMAGE_STATE = {
19+
export const DEFAULT_SECURITY_MODAL_IMAGE_STATE = {
2020
category: CATEGORIES.IMAGE_SCAN,
2121
subCategory: SUB_CATEGORIES.VULNERABILITIES,
2222
detailViewData: null,

src/Shared/Components/Security/SecurityModal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type {
1515
SidebarDataType,
1616
GetResourceScanDetailsPayloadType,
1717
GetResourceScanDetailsResponseType,
18+
SeveritiesDTO,
1819
} from './types'
1920
export { getSidebarData, getProgressingStateForStatus } from './config'
2021
export { CATEGORY_LABELS } from './constants'

0 commit comments

Comments
 (0)