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
-
-
-
-
- }
- />
-
+
+
+
+
+
+ {renderCVESummary()}