feat(cve): filter cves by severity
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
parent
c268991495
commit
e037c6c577
@ -462,6 +462,24 @@ const mockCVEListFiltered = {
|
||||
}
|
||||
};
|
||||
|
||||
const mockCVEListFilteredBySeverity = (severity) => {
|
||||
return {
|
||||
CVEListForImage: {
|
||||
Tag: '',
|
||||
Page: { ItemCount: 20, TotalCount: 20 },
|
||||
Summary: {
|
||||
Count: 5,
|
||||
UnknownCount: 1,
|
||||
LowCount: 1,
|
||||
MediumCount: 1,
|
||||
HighCount: 1,
|
||||
CriticalCount: 1,
|
||||
},
|
||||
CVEList: mockCVEList.CVEListForImage.CVEList.filter((e) => e.Severity.includes(severity))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const mockCVEListFilteredExclude = {
|
||||
CVEListForImage: {
|
||||
Tag: '',
|
||||
@ -507,12 +525,6 @@ const mockCVEFixed = {
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
pageNotFixed: {
|
||||
ImageListWithCVEFixed: {
|
||||
Page: { TotalCount: 0, ItemCount: 0 },
|
||||
Results: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -541,6 +553,44 @@ describe('Vulnerabilties page', () => {
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE/)).toHaveLength(20));
|
||||
});
|
||||
|
||||
it('renders the vulnerabilities by severity', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText('Total 5')).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(20));
|
||||
expect(screen.getByLabelText('Medium')).toBeInTheDocument();
|
||||
const mediumSeverity = await screen.getByLabelText('Medium');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('MEDIUM') } });
|
||||
fireEvent.click(mediumSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(6));
|
||||
expect(screen.getByLabelText('High')).toBeInTheDocument();
|
||||
const highSeverity = await screen.getByLabelText('High');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('HIGH') } });
|
||||
fireEvent.click(highSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
|
||||
expect(screen.getByLabelText('Critical')).toBeInTheDocument();
|
||||
const criticalSeverity = await screen.getByLabelText('Critical');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('CRITICAL') } });
|
||||
fireEvent.click(criticalSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
|
||||
expect(screen.getByLabelText('Low')).toBeInTheDocument();
|
||||
const lowSeverity = await screen.getByLabelText('Low');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('LOW') } });
|
||||
fireEvent.click(lowSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(10));
|
||||
expect(screen.getByLabelText('Unknown')).toBeInTheDocument();
|
||||
const unknownSeverity = await screen.getByLabelText('Unknown');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('UNKNOWN') } });
|
||||
fireEvent.click(unknownSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(1));
|
||||
expect(screen.getByText('Total 5')).toBeInTheDocument();
|
||||
const totalSeverity = await screen.getByText('Total 5');
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEListFilteredBySeverity('') } });
|
||||
fireEvent.click(totalSeverity);
|
||||
await waitFor(() => expect(screen.getAllByText(/CVE-/)).toHaveLength(20));
|
||||
});
|
||||
|
||||
it('sends filtered query if user types in the search bar', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
|
11
src/api.js
11
src/api.js
@ -90,7 +90,13 @@ const endpoints = {
|
||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor IsDeletable } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
|
||||
detailedImageInfo: (name, tag) =>
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Referrers {MediaType ArtifactType Size Digest Annotations{Key Value}} Tag Manifests {History {Layer {Size Digest} HistoryDescription {CreatedBy EmptyLayer}} Digest ConfigDigest LastUpdated Size Platform {Os Arch}} Vendor Licenses }}`,
|
||||
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }, searchTerm = '', excludedTerm = '') => {
|
||||
vulnerabilitiesForRepo: (
|
||||
name,
|
||||
{ pageNumber = 1, pageSize = 15 },
|
||||
searchTerm = '',
|
||||
excludedTerm = '',
|
||||
severity = ''
|
||||
) => {
|
||||
let query = `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}`;
|
||||
@ -100,6 +106,9 @@ const endpoints = {
|
||||
if (!isEmpty(excludedTerm)) {
|
||||
query += `, excludedCVE: "${excludedTerm}"`;
|
||||
}
|
||||
if (!isEmpty(severity)) {
|
||||
query += `, severity: "${severity}"`;
|
||||
}
|
||||
return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity Reference PackageList {Name InstalledVersion FixedVersion}} Summary {Count UnknownCount LowCount MediumCount HighCount CriticalCount}}}`;
|
||||
},
|
||||
allVulnerabilitiesForRepo: (name) =>
|
||||
|
@ -18,6 +18,8 @@ const lowBorderColor = '#f0ed94';
|
||||
const unknownColor = '#f2ffdd';
|
||||
const unknownBorderColor = '#e9f4d7';
|
||||
|
||||
const totalBorderColor = '#e0e5eb';
|
||||
|
||||
const fontSize = '0.75rem';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@ -30,7 +32,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
fontSize: fontSize,
|
||||
fontWeight: '600',
|
||||
borderRadius: '3px',
|
||||
marginBottom: '0'
|
||||
marginBottom: '0',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
totalSeverity: {
|
||||
border: '1px solid ' + totalBorderColor
|
||||
},
|
||||
severityList: {
|
||||
fontSize: fontSize,
|
||||
@ -63,25 +69,27 @@ const useStyles = makeStyles((theme) => ({
|
||||
|
||||
function VulnerabilitiyCountCard(props) {
|
||||
const classes = useStyles();
|
||||
const { total, critical, high, medium, low, unknown } = props;
|
||||
const { total, critical, high, medium, low, unknown, filterBySeverity } = props;
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing="0.5em">
|
||||
<div className={[classes.cveCountCard].join(' ')}>Total {total}</div>
|
||||
<Tooltip title="Total" onClick={() => filterBySeverity('')}>
|
||||
<div className={[classes.cveCountCard, classes.totalSeverity].join(' ')}>Total {total}</div>
|
||||
</Tooltip>
|
||||
<div className={classes.severityList}>
|
||||
<Tooltip title="Critical">
|
||||
<Tooltip title="Critical" onClick={() => filterBySeverity('CRITICAL')}>
|
||||
<div className={[classes.cveCountCard, classes.criticalSeverity].join(' ')}>C {critical}</div>
|
||||
</Tooltip>
|
||||
<Tooltip title="High">
|
||||
<Tooltip title="High" onClick={() => filterBySeverity('HIGH')}>
|
||||
<div className={[classes.cveCountCard, classes.highSeverity].join(' ')}>H {high}</div>
|
||||
</Tooltip>
|
||||
<Tooltip title="Medium">
|
||||
<Tooltip title="Medium" onClick={() => filterBySeverity('MEDIUM')}>
|
||||
<div className={[classes.cveCountCard, classes.mediumSeverity].join(' ')}>M {medium}</div>
|
||||
</Tooltip>
|
||||
<Tooltip title="Low">
|
||||
<Tooltip title="Low" onClick={() => filterBySeverity('LOW')}>
|
||||
<div className={[classes.cveCountCard, classes.lowSeverity].join(' ')}>L {low}</div>
|
||||
</Tooltip>
|
||||
<Tooltip title="Unknown">
|
||||
<Tooltip title="Unknown" onClick={() => filterBySeverity('UNKNOWN')}>
|
||||
<div className={[classes.cveCountCard, classes.unknownSeverity].join(' ')}>U {unknown}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -158,6 +158,7 @@ function VulnerabilitiesDetails(props) {
|
||||
// pagination props
|
||||
const [cveFilter, setCveFilter] = useState('');
|
||||
const [cveExcludeFilter, setCveExcludeFilter] = useState('');
|
||||
const [cveSeverityFilter, setCveSeverityFilter] = useState('');
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||
const listBottom = useRef(null);
|
||||
@ -178,7 +179,8 @@ function VulnerabilitiesDetails(props) {
|
||||
getCVERequestName(),
|
||||
{ pageNumber, pageSize: EXPLORE_PAGE_SIZE },
|
||||
cveFilter,
|
||||
cveExcludeFilter
|
||||
cveExcludeFilter,
|
||||
cveSeverityFilter
|
||||
)}`,
|
||||
abortController.signal
|
||||
)
|
||||
@ -321,7 +323,7 @@ function VulnerabilitiesDetails(props) {
|
||||
useEffect(() => {
|
||||
if (isLoading) return;
|
||||
resetPagination();
|
||||
}, [cveFilter, cveExcludeFilter]);
|
||||
}, [cveFilter, cveExcludeFilter, cveSeverityFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@ -352,8 +354,6 @@ function VulnerabilitiesDetails(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Test');
|
||||
|
||||
return !isEmpty(cveSummary) ? (
|
||||
<VulnerabilityCountCard
|
||||
total={cveSummary.Count}
|
||||
@ -362,6 +362,7 @@ function VulnerabilitiesDetails(props) {
|
||||
medium={cveSummary.MediumCount}
|
||||
low={cveSummary.LowCount}
|
||||
unknown={cveSummary.UnknownCount}
|
||||
filterBySeverity={setCveSeverityFilter}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
Loading…
Reference in New Issue
Block a user