diff --git a/src/api.js b/src/api.js index 0596edf9..9ea5687c 100644 --- a/src/api.js +++ b/src/api.js @@ -97,7 +97,7 @@ const endpoints = { if (!isEmpty(searchTerm)) { query += `, searchedCVE: "${searchTerm}"`; } - return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`; + return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}} Summary {Count UnknownCount LowCount MediumCount HighCount CriticalCount}}}`; }, allVulnerabilitiesForRepo: (name) => `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity Reference PackageList {Name InstalledVersion FixedVersion}}}}`, diff --git a/src/components/Shared/VulnerabilityCountCard.jsx b/src/components/Shared/VulnerabilityCountCard.jsx new file mode 100644 index 00000000..5cd08ce1 --- /dev/null +++ b/src/components/Shared/VulnerabilityCountCard.jsx @@ -0,0 +1,89 @@ +import React from 'react'; + +import makeStyles from '@mui/styles/makeStyles'; +import { Stack, Tooltip } from '@mui/material'; + +const criticalColor = '#ff5c74'; +const criticalBorderColor = '#f9546d'; + +const highColor = '#ff6840'; +const highBorderColor = '#ee6b49'; + +const mediumColor = '#ffa052'; +const mediumBorderColor = '#f19d5b'; + +const lowColor = '#f9f486'; +const lowBorderColor = '#f0ed94'; + +const unknownColor = '#f2ffdd'; +const unknownBorderColor = '#e9f4d7'; + +const useStyles = makeStyles((theme) => ({ + cveCountCard: { + display: 'flex', + alignItems: 'center', + paddingLeft: '0.5rem', + paddingRight: '0.5rem', + color: theme.palette.primary.main, + fontSize: '1rem', + fontWeight: '600', + borderRadius: '3px', + marginBottom: '0' + }, + criticalSeverity: { + backgroundColor: criticalColor, + border: '1px solid ' + criticalBorderColor + }, + highSeverity: { + backgroundColor: highColor, + border: '1px solid ' + highBorderColor + }, + mediumSeverity: { + backgroundColor: mediumColor, + border: '1px solid ' + mediumBorderColor + }, + lowSeverity: { + backgroundColor: lowColor, + border: '1px solid ' + lowBorderColor + }, + unknownSeverity: { + backgroundColor: unknownColor, + border: '1px solid ' + unknownBorderColor + }, + severityList: { + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + gap: '0.5em' + } +})); + +function VulnerabilitiyCountCard(props) { + const classes = useStyles(); + const { total, critical, high, medium, low, unknown } = props; + + return ( + +
Total {total}
+
+ +
C {critical}
+
+ +
H {high}
+
+ +
M {medium}
+
+ +
L {low}
+
+ +
U {unknown}
+
+
+
+ ); +} + +export default VulnerabilitiyCountCard; diff --git a/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx b/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx index 1872f6a0..1da1b941 100644 --- a/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx +++ b/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx @@ -28,6 +28,7 @@ import * as XLSX from 'xlsx'; import exportFromJSON from 'export-from-json'; import VulnerabilitiyCard from '../../Shared/VulnerabilityCard'; +import VulnerabilityCountCard from '../../Shared/VulnerabilityCountCard'; const useStyles = makeStyles((theme) => ({ title: { @@ -36,6 +37,12 @@ const useStyles = makeStyles((theme) => ({ fontWeight: '600', marginBottom: '0' }, + cveCountSummary: { + color: theme.palette.primary.main, + fontSize: '1.5rem', + fontWeight: '600', + marginBottom: '0' + }, cveId: { color: theme.palette.primary.main, fontSize: '1rem', @@ -103,6 +110,7 @@ function VulnerabilitiesDetails(props) { const classes = useStyles(); const [cveData, setCveData] = useState([]); const [allCveData, setAllCveData] = useState([]); + const [cveSummary, setCVESummary] = useState({}); const [isLoading, setIsLoading] = useState(true); const [isLoadingAllCve, setIsLoadingAllCve] = useState(true); const abortController = useMemo(() => new AbortController(), []); @@ -134,9 +142,23 @@ function VulnerabilitiesDetails(props) { .then((response) => { if (response.data && response.data.data) { let cveInfo = response.data.data.CVEListForImage?.CVEList; + let summary = response.data.data.CVEListForImage?.Summary; let cveListData = mapCVEInfo(cveInfo); setCveData((previousState) => (pageNumber === 1 ? cveListData : [...previousState, ...cveListData])); setIsEndOfList(response.data.data.CVEListForImage.Page?.ItemCount < EXPLORE_PAGE_SIZE); + setCVESummary((previousState) => { + if (isEmpty(summary)) { + return previousState; + } + return { + Count: summary.Count, + UnknownCount: summary.UnknownCount, + LowCount: summary.LowCount, + MediumCount: summary.MediumCount, + HighCount: summary.HighCount, + CriticalCount: summary.CriticalCount + }; + }); } else if (response.data.errors) { setIsEndOfList(true); } @@ -146,6 +168,7 @@ function VulnerabilitiesDetails(props) { console.error(e); setIsLoading(false); setCveData([]); + setCVESummary(() => {}); setIsEndOfList(true); }); }; @@ -270,6 +293,24 @@ function VulnerabilitiesDetails(props) { ); }; + const renderCVESummary = () => { + if (cveSummary === undefined) { + return; + } + return !isEmpty(cveSummary) ? ( + + ) : ( +
{!isLoading && No Vulnerabilities }
+ ); + }; + const renderListBottom = () => { if (isLoading) { return ; @@ -283,51 +324,54 @@ function VulnerabilitiesDetails(props) { return ( - - Vulnerabilities - - - - - } - /> - - - csv - - - + + Vulnerabilities + + + + + } + /> + - MS Excel - - + + csv + + + + MS Excel + + + + {renderCVESummary()}