Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bifurcate image cards in imageScan codeScan and manifestScan #500

Merged
merged 12 commits into from
Jan 13, 2025
Merged
3 changes: 3 additions & 0 deletions src/Assets/Icon/ic-shield-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/Icon/ic-shield-warning-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions src/Shared/Components/Security/SecurityDetailsCards/SecurityCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { SegmentedBarChart } from '@Common/SegmentedBarChart'
import { ReactComponent as ICShieldWarning } from '@Icons/ic-shield-warning-outline.svg'
import { ReactComponent as ICShieldSecure } from '@Icons/ic-shield-check.svg'
import { ReactComponent as ICArrowRight } from '@Icons/ic-caret-down-small.svg'
import { SecurityCardProps } from './types'
import { CATEGORIES, SUB_CATEGORIES } from '../SecurityModal/types'
import { CATEGORY_LABELS, SEVERITIES } from '../SecurityModal/constants'
import './securityCard.scss'
import { getTotalSeverities } from '../utils'

const SecurityCard = ({
category,
subCategory,
severityCount = {},
handleCardClick,
rootClassName = '',
}: SecurityCardProps) => {
const totalCount = getTotalSeverities(severityCount)

const hasThreats: boolean = !!totalCount

const entities = Object.entries(SEVERITIES)
.map(([key, severity]) => ({
...severity,
value: severityCount[key],
}))
.filter((entity) => !!entity.value)

const getScanType = () => {
AbhishekA1509 marked this conversation as resolved.
Show resolved Hide resolved
switch (category) {
case CATEGORIES.KUBERNETES_MANIFEST:
return CATEGORY_LABELS.KUBERNETES_MANIFEST
case CATEGORIES.CODE_SCAN:
return CATEGORY_LABELS.CODE_SCAN
default:
return CATEGORY_LABELS.IMAGE_SCAN
}
}

const getTitleSubtitle = (): { title: string; subtitle?: string } => {
switch (subCategory) {
case SUB_CATEGORIES.EXPOSED_SECRETS:
return hasThreats
? { title: `${totalCount} exposed secrets` }
: {
title: 'No exposed secrets',
subtitle: 'No exposed secrets like passwords, api keys, and tokens found',
}
case SUB_CATEGORIES.LICENSE:
return hasThreats
? { title: `${totalCount} license risks` }
: { title: 'No license risks', subtitle: 'No license risks or compliance issues found' }
case SUB_CATEGORIES.MISCONFIGURATIONS:
return hasThreats
? { title: `${totalCount} misconfigurations` }
: { title: 'No misconfiguration', subtitle: 'No misconfigurations found' }
default:
return hasThreats
? { title: `${totalCount} vulnerabilities` }
: { title: 'No vulnerabilities', subtitle: 'No security vulnerability found' }
}
}

const { title, subtitle } = getTitleSubtitle()

const onKeyDown = (event) => {
if (event.key === 'Enter') {
handleCardClick()
}
}

return (
<div
className={`${rootClassName} w-100 bcn-0 p-20 flexbox-col dc__gap-16 br-8 dc__border security-card security-card${hasThreats ? '--threat' : '--secure'}`}
role="button"
AbhishekA1509 marked this conversation as resolved.
Show resolved Hide resolved
tabIndex={0}
onClick={handleCardClick}
onKeyDown={onKeyDown}
>
<div className="flexbox dc__content-space">
<div className="flexbox-col">
<span className="fs-12 fw-4 lh-1-5 cn-7">{getScanType()}</span>
<div className="fs-15 fw-6 lh-1-5 cn-9 flex">
<span className="security-card-title">{title}</span>
<ICArrowRight className="icon-dim-20 dc__flip-270 scb-5 arrow-right" />
</div>
</div>
{hasThreats ? (
<ICShieldWarning className="icon-dim-24 scr-5 dc__no-shrink" />
) : (
<ICShieldSecure className="icon-dim-24 scg-5 dc__no-shrink" />
)}
</div>
<div className="flexbox-col dc__gap-12">
{hasThreats || severityCount.success ? (
<SegmentedBarChart
entities={entities}
labelClassName="fs-13 fw-4 lh-20 cn-9"
countClassName="fs-13 fw-6 lh-20 cn-7"
/>
) : (
<div className="bcn-1 br-4 h-8" />
)}
{subtitle && <span>{subtitle}</span>}
</div>
</div>
)
}

export default SecurityCard
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { ScannedByToolModal } from '@Shared/Components/ScannedByToolModal'
import { SCAN_TOOL_ID_CLAIR, SCAN_TOOL_ID_TRIVY } from '@Shared/constants'
import { useState } from 'react'
import SecurityCard from './SecurityCard'
import { CATEGORIES, SecurityModalStateType, SUB_CATEGORIES } from '../SecurityModal/types'
import { SecurityCardProps, SecurityDetailsCardsProps } from './types'
import { SecurityModal } from '../SecurityModal'
import { DEFAULT_SECURITY_MODAL_IMAGE_STATE } from '../SecurityModal/constants'
import { ScanCategories, ScanSubCategories } from '../types'
import './securityCard.scss'
import { getSecurityConfig } from '../utils'

const SecurityDetailsCards = ({ scanResult, Sidebar }: SecurityDetailsCardsProps) => {
const [showSecurityModal, setShowSecurityModal] = useState<boolean>(false)
const [modalState, setModalState] = useState<SecurityModalStateType>(DEFAULT_SECURITY_MODAL_IMAGE_STATE)
const { imageScan, codeScan, kubernetesManifest } = scanResult

const SECURITY_CONFIG = getSecurityConfig({
imageScan: !!imageScan,
codeScan: !!codeScan,
kubernetesManifest: !!kubernetesManifest,
})

const getScanToolId = (category: string) => {
switch (category) {
case CATEGORIES.CODE_SCAN:
return codeScan?.scanToolName === 'TRIVY' ? SCAN_TOOL_ID_TRIVY : SCAN_TOOL_ID_CLAIR
case CATEGORIES.KUBERNETES_MANIFEST:
return kubernetesManifest?.scanToolName === 'TRIVY' ? SCAN_TOOL_ID_TRIVY : SCAN_TOOL_ID_CLAIR
case CATEGORIES.IMAGE_SCAN:
return imageScan?.vulnerability?.list?.[0].scanToolName === 'TRIVY'
? SCAN_TOOL_ID_TRIVY
: SCAN_TOOL_ID_CLAIR
default:
return SCAN_TOOL_ID_TRIVY
}
}

const handleCardClick = (
category: SecurityCardProps['category'],
subCategory: SecurityCardProps['subCategory'],
) => {
setShowSecurityModal(true)
setModalState({
category,
subCategory,
detailViewData: null,
})
}

const handleModalClose = () => {
setShowSecurityModal(false)
}

return (
<>
<div className="flexbox-col dc__gap-20">
{Object.values(CATEGORIES).map((category: ScanCategories) => {
AbhishekA1509 marked this conversation as resolved.
Show resolved Hide resolved
if (!SECURITY_CONFIG[category]) {
return null
}
const scanToolId = getScanToolId(category)
AbhishekA1509 marked this conversation as resolved.
Show resolved Hide resolved
return (
<div className="flexbox-col dc__gap-12" key={category}>
<div className="flexbox dc__content-space pb-8 dc__border-bottom-n1">
<span className="fs-13 fw-6 lh-1-5 cn-9">{SECURITY_CONFIG[category].label}</span>
<ScannedByToolModal scanToolId={scanToolId} />
</div>
<div className="dc__grid security-cards">
{SECURITY_CONFIG[category].subCategories.map((subCategory: ScanSubCategories) => {
const severityCount =
subCategory === SUB_CATEGORIES.MISCONFIGURATIONS
? scanResult[category][subCategory]?.misConfSummary?.status
: scanResult[category][subCategory]?.summary?.severities

return (
<SecurityCard
category={category}
subCategory={subCategory}
severityCount={severityCount}
handleCardClick={() => handleCardClick(category, subCategory)}
/>
)
})}
</div>
</div>
)
})}
</div>
{showSecurityModal && (
<SecurityModal
isLoading={false}
error={null}
responseData={scanResult}
handleModalClose={handleModalClose}
Sidebar={Sidebar}
defaultState={modalState}
/>
)}
</>
)
}

export default SecurityDetailsCards
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SecurityCard } from './SecurityCard'
export { default as SecurityDetailsCards } from './SecurityDetailsCards'
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.security-card {
&--threat {
background: radial-gradient(25.91% 100% at 100% 0%, var(--R100) 0%, var(--N0) 100%);
}
&--secure {
AbhishekA1509 marked this conversation as resolved.
Show resolved Hide resolved
background: radial-gradient(25.91% 100% at 100% 0%, var(--G100) 0%, var(--N0) 100%);
}

.arrow-right {
visibility: hidden;
}

&:hover {
.arrow-right {
visibility: visible;
}

.security-card-title {
color: var(--B500);
}
}
}

.security-cards {
grid-template-columns: 1fr 1fr;
grid-row-gap: 12px;
grid-column-gap: 12px;
}
14 changes: 14 additions & 0 deletions src/Shared/Components/Security/SecurityDetailsCards/types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ScanResultDTO, SecurityModalPropsType, SeveritiesDTO } from '../SecurityModal/types'
import { ScanCategories, ScanSubCategories } from '../types'

export interface SecurityCardProps {
category: ScanCategories
subCategory: ScanSubCategories
severityCount: Partial<Record<SeveritiesDTO, number>>
handleCardClick: () => void
rootClassName?: string
}

export interface SecurityDetailsCardsProps extends Pick<SecurityModalPropsType, 'Sidebar'> {
scanResult: ScanResultDTO
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const SecurityModal: React.FC<SecurityModalPropsType> = ({
error,
responseData,
hidePolicy = false,
defaultState,
}) => {
const data = responseData ?? null

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

const [state, setState] = useState<SecurityModalStateType>(getDefaultSecurityModalState(categoriesConfig))
const [state, setState] = useState<SecurityModalStateType>(
defaultState ?? getDefaultSecurityModalState(categoriesConfig),
)

const setDetailViewData = (detailViewData: DetailViewDataType) => {
setState((prevState) => ({
Expand All @@ -76,7 +79,7 @@ const SecurityModal: React.FC<SecurityModalPropsType> = ({
onClick={handleModalClose}
showAriaLabelInTippy={false}
size={ComponentSizeType.xs}
style={ButtonStyleType.neutral}
style={ButtonStyleType.negativeGrey}
variant={ButtonVariantType.borderLess}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
SecurityModalStateType,
} from './types'

const DEFAULT_SECURITY_MODAL_IMAGE_STATE = {
export const DEFAULT_SECURITY_MODAL_IMAGE_STATE = {
category: CATEGORIES.IMAGE_SCAN,
subCategory: SUB_CATEGORIES.VULNERABILITIES,
detailViewData: null,
Expand Down
1 change: 1 addition & 0 deletions src/Shared/Components/Security/SecurityModal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
GetResourceScanDetailsPayloadType,
GetResourceScanDetailsResponseType,
} from './types'
export { SeveritiesDTO } from './types'
export { getSidebarData, getProgressingStateForStatus } from './config'
export { CATEGORY_LABELS } from './constants'
export { getSecurityScan } from './service'
1 change: 1 addition & 0 deletions src/Shared/Components/Security/SecurityModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ interface SecurityModalBaseProps {
handleModalClose: (event?: React.MouseEvent<HTMLElement>) => void
Sidebar?: React.FC<SidebarPropsType>
hidePolicy?: boolean
defaultState?: SecurityModalStateType
}

export type SecurityModalPropsType = SecurityModalBaseProps
Expand Down

This file was deleted.

Loading
Loading