diff --git a/src/api.js b/src/api.js index 0596edf..9ea5687 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 0000000..c478136 --- /dev/null +++ b/src/components/Shared/VulnerabilityCountCard.jsx @@ -0,0 +1,92 @@ +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 fontSize = '0.75rem'; + +const useStyles = makeStyles((theme) => ({ + cveCountCard: { + display: 'flex', + alignItems: 'center', + paddingLeft: '0.5rem', + paddingRight: '0.5rem', + color: theme.palette.primary.main, + fontSize: fontSize, + fontWeight: '600', + borderRadius: '3px', + marginBottom: '0' + }, + severityList: { + fontSize: fontSize, + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + gap: '0.5em' + }, + 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 + } +})); + +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 f80612e..6d34ef4 100644 --- a/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx +++ b/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx @@ -31,14 +31,25 @@ import ViewHeadlineIcon from '@mui/icons-material/ViewHeadline'; import ViewAgendaIcon from '@mui/icons-material/ViewAgenda'; import VulnerabilitiyCard from '../../Shared/VulnerabilityCard'; +import VulnerabilityCountCard from '../../Shared/VulnerabilityCountCard'; const useStyles = makeStyles((theme) => ({ + searchAndDisplayBar: { + display: 'flex', + justifyContent: 'space-between' + }, title: { color: theme.palette.primary.main, fontSize: '1.5rem', 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', @@ -67,6 +78,7 @@ const useStyles = makeStyles((theme) => ({ search: { position: 'relative', maxWidth: '100%', + flex: 0.95, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', @@ -74,15 +86,18 @@ const useStyles = makeStyles((theme) => ({ border: '0.063rem solid #E7E7E7', borderRadius: '0.625rem' }, + expandableSearchInput: { + flexGrow: 0.95 + }, view: { alignContent: 'right', variant: 'outlined' }, viewModes: { position: 'relative', + alignItems: 'baseline', maxWidth: '100%', flexDirection: 'row', - alignItems: 'right', justifyContent: 'right' }, searchIcon: { @@ -114,6 +129,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(), []); @@ -147,9 +163,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); } @@ -159,6 +189,7 @@ function VulnerabilitiesDetails(props) { console.error(e); setIsLoading(false); setCveData([]); + setCVESummary(() => {}); setIsEndOfList(true); }); }; @@ -283,6 +314,24 @@ function VulnerabilitiesDetails(props) { ); }; + const renderCVESummary = () => { + if (cveSummary === undefined) { + return; + } + return !isEmpty(cveSummary) ? ( + + ) : ( +
{!isLoading && No Vulnerabilities }
+ ); + }; + const renderListBottom = () => { if (isLoading) { return ; @@ -364,6 +413,7 @@ function VulnerabilitiesDetails(props) { + {renderCVESummary()}