feat: add cve summary in vulnerability tab (#416)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
12f9229320
commit
5bf7d5652c
@ -42,7 +42,8 @@ const config = {
|
|||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
ignoreHTTPSErrors: true
|
ignoreHTTPSErrors: true,
|
||||||
|
screenshot: 'only-on-failure'
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
@ -101,7 +102,7 @@ const config = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||||
// outputDir: 'test-results/',
|
outputDir: 'test-results/',
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
// webServer: {
|
// webServer: {
|
||||||
|
@ -22,6 +22,14 @@ const mockCVEList = {
|
|||||||
CVEListForImage: {
|
CVEListForImage: {
|
||||||
Tag: '',
|
Tag: '',
|
||||||
Page: { ItemCount: 20, TotalCount: 20 },
|
Page: { ItemCount: 20, TotalCount: 20 },
|
||||||
|
Summary: {
|
||||||
|
Count: 5,
|
||||||
|
UnknownCount: 1,
|
||||||
|
LowCount: 1,
|
||||||
|
MediumCount: 1,
|
||||||
|
HighCount: 1,
|
||||||
|
CriticalCount: 1,
|
||||||
|
},
|
||||||
CVEList: [
|
CVEList: [
|
||||||
{
|
{
|
||||||
Id: 'CVE-2020-16156',
|
Id: 'CVE-2020-16156',
|
||||||
@ -499,6 +507,7 @@ describe('Vulnerabilties page', () => {
|
|||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||||
|
await waitFor(() => expect(screen.getAllByText('Total 5')).toHaveLength(1));
|
||||||
await waitFor(() => expect(screen.getAllByText(/fixed in/i)).toHaveLength(20));
|
await waitFor(() => expect(screen.getAllByText(/fixed in/i)).toHaveLength(20));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -515,7 +524,7 @@ describe('Vulnerabilties page', () => {
|
|||||||
it('renders no vulnerabilities if there are not any', async () => {
|
it('renders no vulnerabilities if there are not any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({
|
jest.spyOn(api, 'get').mockResolvedValue({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: { data: { CVEListForImage: { Tag: '', Page: {}, CVEList: [] } } }
|
data: { data: { CVEListForImage: { Tag: '', Page: {}, CVEList: [], Summary: {} } } }
|
||||||
});
|
});
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
||||||
|
@ -97,7 +97,7 @@ const endpoints = {
|
|||||||
if (!isEmpty(searchTerm)) {
|
if (!isEmpty(searchTerm)) {
|
||||||
query += `, searchedCVE: "${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) =>
|
allVulnerabilitiesForRepo: (name) =>
|
||||||
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity Reference PackageList {Name InstalledVersion FixedVersion}}}}`,
|
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity Reference PackageList {Name InstalledVersion FixedVersion}}}}`,
|
||||||
|
92
src/components/Shared/VulnerabilityCountCard.jsx
Normal file
92
src/components/Shared/VulnerabilityCountCard.jsx
Normal file
@ -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 (
|
||||||
|
<Stack direction="row" spacing="0.5em">
|
||||||
|
<div className={[classes.cveCountCard].join(' ')}>Total {total}</div>
|
||||||
|
<div className={classes.severityList}>
|
||||||
|
<Tooltip title="Critical">
|
||||||
|
<div className={[classes.cveCountCard, classes.criticalSeverity].join(' ')}>C {critical}</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="High">
|
||||||
|
<div className={[classes.cveCountCard, classes.highSeverity].join(' ')}>H {high}</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Medium">
|
||||||
|
<div className={[classes.cveCountCard, classes.mediumSeverity].join(' ')}>M {medium}</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Low">
|
||||||
|
<div className={[classes.cveCountCard, classes.lowSeverity].join(' ')}>L {low}</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Unknown">
|
||||||
|
<div className={[classes.cveCountCard, classes.unknownSeverity].join(' ')}>U {unknown}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VulnerabilitiyCountCard;
|
@ -31,14 +31,25 @@ import ViewHeadlineIcon from '@mui/icons-material/ViewHeadline';
|
|||||||
import ViewAgendaIcon from '@mui/icons-material/ViewAgenda';
|
import ViewAgendaIcon from '@mui/icons-material/ViewAgenda';
|
||||||
|
|
||||||
import VulnerabilitiyCard from '../../Shared/VulnerabilityCard';
|
import VulnerabilitiyCard from '../../Shared/VulnerabilityCard';
|
||||||
|
import VulnerabilityCountCard from '../../Shared/VulnerabilityCountCard';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
searchAndDisplayBar: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginBottom: '0'
|
marginBottom: '0'
|
||||||
},
|
},
|
||||||
|
cveCountSummary: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: '0'
|
||||||
|
},
|
||||||
cveId: {
|
cveId: {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
@ -67,6 +78,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
search: {
|
search: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
|
flex: 0.95,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@ -74,15 +86,18 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
border: '0.063rem solid #E7E7E7',
|
border: '0.063rem solid #E7E7E7',
|
||||||
borderRadius: '0.625rem'
|
borderRadius: '0.625rem'
|
||||||
},
|
},
|
||||||
|
expandableSearchInput: {
|
||||||
|
flexGrow: 0.95
|
||||||
|
},
|
||||||
view: {
|
view: {
|
||||||
alignContent: 'right',
|
alignContent: 'right',
|
||||||
variant: 'outlined'
|
variant: 'outlined'
|
||||||
},
|
},
|
||||||
viewModes: {
|
viewModes: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
alignItems: 'baseline',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'right',
|
|
||||||
justifyContent: 'right'
|
justifyContent: 'right'
|
||||||
},
|
},
|
||||||
searchIcon: {
|
searchIcon: {
|
||||||
@ -114,6 +129,7 @@ function VulnerabilitiesDetails(props) {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [cveData, setCveData] = useState([]);
|
const [cveData, setCveData] = useState([]);
|
||||||
const [allCveData, setAllCveData] = useState([]);
|
const [allCveData, setAllCveData] = useState([]);
|
||||||
|
const [cveSummary, setCVESummary] = useState({});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isLoadingAllCve, setIsLoadingAllCve] = useState(true);
|
const [isLoadingAllCve, setIsLoadingAllCve] = useState(true);
|
||||||
const abortController = useMemo(() => new AbortController(), []);
|
const abortController = useMemo(() => new AbortController(), []);
|
||||||
@ -147,9 +163,23 @@ function VulnerabilitiesDetails(props) {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data && response.data.data) {
|
if (response.data && response.data.data) {
|
||||||
let cveInfo = response.data.data.CVEListForImage?.CVEList;
|
let cveInfo = response.data.data.CVEListForImage?.CVEList;
|
||||||
|
let summary = response.data.data.CVEListForImage?.Summary;
|
||||||
let cveListData = mapCVEInfo(cveInfo);
|
let cveListData = mapCVEInfo(cveInfo);
|
||||||
setCveData((previousState) => (pageNumber === 1 ? cveListData : [...previousState, ...cveListData]));
|
setCveData((previousState) => (pageNumber === 1 ? cveListData : [...previousState, ...cveListData]));
|
||||||
setIsEndOfList(response.data.data.CVEListForImage.Page?.ItemCount < EXPLORE_PAGE_SIZE);
|
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) {
|
} else if (response.data.errors) {
|
||||||
setIsEndOfList(true);
|
setIsEndOfList(true);
|
||||||
}
|
}
|
||||||
@ -159,6 +189,7 @@ function VulnerabilitiesDetails(props) {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setCveData([]);
|
setCveData([]);
|
||||||
|
setCVESummary(() => {});
|
||||||
setIsEndOfList(true);
|
setIsEndOfList(true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -283,6 +314,27 @@ function VulnerabilitiesDetails(props) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderCVESummary = () => {
|
||||||
|
if (cveSummary === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Test');
|
||||||
|
|
||||||
|
return !isEmpty(cveSummary) ? (
|
||||||
|
<VulnerabilityCountCard
|
||||||
|
total={cveSummary.Count}
|
||||||
|
critical={cveSummary.CriticalCount}
|
||||||
|
high={cveSummary.HighCount}
|
||||||
|
medium={cveSummary.MediumCount}
|
||||||
|
low={cveSummary.LowCount}
|
||||||
|
unknown={cveSummary.UnknownCount}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderListBottom = () => {
|
const renderListBottom = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
@ -364,6 +416,7 @@ function VulnerabilitiesDetails(props) {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{renderCVESummary()}
|
||||||
<Stack className={classes.search}>
|
<Stack className={classes.search}>
|
||||||
<InputBase
|
<InputBase
|
||||||
placeholder={'Search'}
|
placeholder={'Search'}
|
||||||
|
Loading…
Reference in New Issue
Block a user