Skip to content

Commit 26c2e0e

Browse files
committed
Merge branch 'develop' of github.com:devtron-labs/devtron-fe-common-lib into fix/rb-compare-sync
2 parents 3888574 + db49271 commit 26c2e0e

24 files changed

+441
-166
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.0-beta-6",
3+
"version": "1.4.3",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

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

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

src/Common/SegmentedBarChart/SegmentedBarChart.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const SegmentedBarChart: React.FC<SegmentedBarChartProps> = ({
2525
countClassName,
2626
labelClassName,
2727
isProportional,
28+
swapLegendAndBar = false,
2829
}) => {
2930
const total = entities.reduce((sum, entity) => entity.value + sum, 0)
3031
const filteredEntities = entities.filter((entity) => entity.value)
@@ -70,20 +71,39 @@ const SegmentedBarChart: React.FC<SegmentedBarChartProps> = ({
7071
return null
7172
}
7273

74+
const renderLegend = () => (
75+
<div className={`flexbox flex-wrap dc__row-gap-4 ${isProportional ? 'dc__gap-24' : 'dc__gap-16'}`}>
76+
{renderContent()}
77+
</div>
78+
)
79+
80+
const renderBar = () => (
81+
<div className="flexbox dc__gap-2">
82+
{filteredEntities?.map((entity, index, map) => (
83+
<div
84+
key={entity.label}
85+
className={`h-8 ${index === 0 ? 'dc__left-radius-4' : ''} ${
86+
index === map.length - 1 ? 'dc__right-radius-4' : ''
87+
}`}
88+
style={{ backgroundColor: entity.color, width: calcSegmentWidth(entity) }}
89+
/>
90+
))}
91+
</div>
92+
)
93+
7394
return (
7495
<div className={`flexbox-col w-100 dc__gap-12 ${rootClassName}`}>
75-
<div className={`flexbox ${isProportional ? 'dc__gap-24' : 'dc__gap-16'}`}>{renderContent()}</div>
76-
<div className="flexbox dc__gap-2">
77-
{filteredEntities?.map((entity, index, map) => (
78-
<div
79-
key={entity.label}
80-
className={`h-8 ${index === 0 ? 'dc__left-radius-4' : ''} ${
81-
index === map.length - 1 ? 'dc__right-radius-4' : ''
82-
}`}
83-
style={{ backgroundColor: entity.color, width: calcSegmentWidth(entity) }}
84-
/>
85-
))}
86-
</div>
96+
{swapLegendAndBar ? (
97+
<>
98+
{renderBar()}
99+
{renderLegend()}
100+
</>
101+
) : (
102+
<>
103+
{renderLegend()}
104+
{renderBar()}
105+
</>
106+
)}
87107
</div>
88108
)
89109
}

src/Common/SegmentedBarChart/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export interface SegmentedBarChartProps {
2626
countClassName?: string
2727
labelClassName?: string
2828
isProportional?: boolean
29+
swapLegendAndBar?: boolean
2930
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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 { SUB_CATEGORIES } from '../SecurityModal/types'
7+
import { SEVERITIES } from '../SecurityModal/constants'
8+
import './securityCard.scss'
9+
import { getTotalSeverities } from '../utils'
10+
import { SECURITY_CONFIG } from '../constants'
11+
12+
const SecurityCard = ({ category, subCategory, severityCount = {}, handleCardClick }: SecurityCardProps) => {
13+
const totalCount = getTotalSeverities(severityCount)
14+
15+
const hasThreats: boolean = !!totalCount
16+
17+
const entities = Object.entries(SEVERITIES)
18+
.map(([key, severity]) => ({
19+
...severity,
20+
value: severityCount[key],
21+
}))
22+
.filter((entity) => !!entity.value)
23+
24+
const getTitleSubtitle = (): { title: string; subtitle?: string } => {
25+
switch (subCategory) {
26+
case SUB_CATEGORIES.EXPOSED_SECRETS:
27+
return hasThreats
28+
? { title: `${totalCount} exposed secrets` }
29+
: {
30+
title: 'No exposed secrets',
31+
subtitle: 'No exposed secrets like passwords, api keys, and tokens found',
32+
}
33+
case SUB_CATEGORIES.LICENSE:
34+
return hasThreats
35+
? { title: `${totalCount} license risks` }
36+
: { title: 'No license risks', subtitle: 'No license risks or compliance issues found' }
37+
case SUB_CATEGORIES.MISCONFIGURATIONS:
38+
return hasThreats
39+
? { title: `${totalCount} misconfigurations` }
40+
: { title: 'No misconfiguration', subtitle: 'No configuration issues detected in scanned files' }
41+
default:
42+
return hasThreats
43+
? { title: `${totalCount} vulnerabilities` }
44+
: { title: 'No vulnerabilities', subtitle: 'No vulnerabilities or potential threats found' }
45+
}
46+
}
47+
48+
const { title, subtitle } = getTitleSubtitle()
49+
50+
const onKeyDown = (event) => {
51+
if (event.key === 'Enter') {
52+
handleCardClick()
53+
}
54+
}
55+
56+
return (
57+
<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'}`}
59+
role="button"
60+
tabIndex={0}
61+
onClick={handleCardClick}
62+
onKeyDown={onKeyDown}
63+
>
64+
<div className="flexbox dc__content-space">
65+
<div className="flexbox-col">
66+
<span className="fs-12 fw-4 lh-1-5 cn-7">{SECURITY_CONFIG[category].label}</span>
67+
<div className="fs-15 fw-6 lh-1-5 cn-9 flex">
68+
<span className="security-card-title">{title}</span>
69+
<ICArrowRight className="icon-dim-20 dc__flip-270 scb-5 arrow-right" />
70+
</div>
71+
</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+
)}
77+
</div>
78+
<div className="flexbox-col dc__gap-12">
79+
{hasThreats || severityCount.success ? (
80+
<SegmentedBarChart
81+
entities={entities}
82+
labelClassName="fs-13 fw-4 lh-20 cn-9"
83+
countClassName="fs-13 fw-6 lh-20 cn-7"
84+
swapLegendAndBar
85+
/>
86+
) : (
87+
<div className="bcn-1 br-4 h-8" />
88+
)}
89+
{subtitle && <span className="cn-9 fs-13 lh-20">{subtitle}</span>}
90+
</div>
91+
</div>
92+
)
93+
}
94+
95+
export default SecurityCard
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { ScannedByToolModal } from '@Shared/Components/ScannedByToolModal'
2+
import { EMPTY_STATE_STATUS, SCAN_TOOL_ID_CLAIR, SCAN_TOOL_ID_TRIVY } from '@Shared/constants'
3+
import { useState } from 'react'
4+
import { GenericEmptyState } from '@Common/index'
5+
import { ReactComponent as NoVulnerability } from '@Icons/ic-vulnerability-not-found.svg'
6+
import SecurityCard from './SecurityCard'
7+
import { CATEGORIES, SecurityModalStateType, SUB_CATEGORIES } from '../SecurityModal/types'
8+
import { SecurityCardProps, SecurityDetailsCardsProps } from './types'
9+
import { SecurityModal } from '../SecurityModal'
10+
import { DEFAULT_SECURITY_MODAL_IMAGE_STATE } from '../SecurityModal/constants'
11+
import { ScanCategories, ScanSubCategories } from '../types'
12+
import { getSecurityConfig, getCompiledSecurityThreats, getTotalSeverities } from '../utils'
13+
import './securityCard.scss'
14+
15+
const SecurityDetailsCards = ({ scanResult, Sidebar }: SecurityDetailsCardsProps) => {
16+
const [showSecurityModal, setShowSecurityModal] = useState<boolean>(false)
17+
const [modalState, setModalState] = useState<SecurityModalStateType>(DEFAULT_SECURITY_MODAL_IMAGE_STATE)
18+
const { imageScan, codeScan, kubernetesManifest } = scanResult
19+
20+
const scanThreats = getCompiledSecurityThreats(scanResult)
21+
const threatCount = getTotalSeverities(scanThreats)
22+
23+
if (!threatCount) {
24+
return (
25+
<GenericEmptyState
26+
SvgImage={NoVulnerability}
27+
title={EMPTY_STATE_STATUS.CI_DEATILS_NO_VULNERABILITY_FOUND.TITLE}
28+
subTitle={EMPTY_STATE_STATUS.CI_DEATILS_NO_VULNERABILITY_FOUND.SUBTITLE}
29+
/>
30+
)
31+
}
32+
33+
const SECURITY_CONFIG = getSecurityConfig({
34+
imageScan: !!imageScan,
35+
codeScan: !!codeScan,
36+
kubernetesManifest: !!kubernetesManifest,
37+
})
38+
39+
const getScanToolId = (category: string) => {
40+
switch (category) {
41+
case CATEGORIES.CODE_SCAN:
42+
return codeScan?.scanToolName === 'TRIVY' ? SCAN_TOOL_ID_TRIVY : SCAN_TOOL_ID_CLAIR
43+
case CATEGORIES.KUBERNETES_MANIFEST:
44+
return kubernetesManifest?.scanToolName === 'TRIVY' ? SCAN_TOOL_ID_TRIVY : SCAN_TOOL_ID_CLAIR
45+
case CATEGORIES.IMAGE_SCAN:
46+
return imageScan?.vulnerability?.list?.[0].scanToolName === 'TRIVY'
47+
? SCAN_TOOL_ID_TRIVY
48+
: SCAN_TOOL_ID_CLAIR
49+
default:
50+
return SCAN_TOOL_ID_TRIVY
51+
}
52+
}
53+
54+
const handleOpenModal = (
55+
category: SecurityCardProps['category'],
56+
subCategory: SecurityCardProps['subCategory'],
57+
) => {
58+
setShowSecurityModal(true)
59+
setModalState({
60+
category,
61+
subCategory,
62+
detailViewData: null,
63+
})
64+
}
65+
66+
const handleCardClick =
67+
(category: SecurityCardProps['category'], subCategory: SecurityCardProps['subCategory']) => () =>
68+
handleOpenModal(category, subCategory)
69+
70+
const handleModalClose = () => {
71+
setShowSecurityModal(false)
72+
}
73+
74+
return (
75+
<>
76+
<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
89+
90+
return (
91+
<SecurityCard
92+
category={category}
93+
subCategory={subCategory}
94+
severityCount={severityCount}
95+
handleCardClick={handleCardClick(category, subCategory)}
96+
/>
97+
)
98+
})}
99+
</div>
100+
</div>
101+
))}
102+
</div>
103+
{showSecurityModal && (
104+
<SecurityModal
105+
isLoading={false}
106+
error={null}
107+
responseData={scanResult}
108+
handleModalClose={handleModalClose}
109+
Sidebar={Sidebar}
110+
defaultState={modalState}
111+
/>
112+
)}
113+
</>
114+
)
115+
}
116+
117+
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.security-card {
2+
&--threat {
3+
background: radial-gradient(25.91% 100% at 100% 0%, var(--R100) 0%, var(--N0) 100%);
4+
}
5+
&--secure {
6+
background: radial-gradient(25.91% 100% at 100% 0%, var(--G100) 0%, var(--N0) 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+
}
23+
24+
.security-cards {
25+
grid-template-columns: 1fr 1fr;
26+
grid-row-gap: 12px;
27+
grid-column-gap: 12px;
28+
}

0 commit comments

Comments
 (0)