Compare commits
3 Commits
commit-6cd
...
commit-5bf
Author | SHA1 | Date | |
---|---|---|---|
5bf7d5652c | |||
12f9229320 | |||
df19fa811c |
@ -42,7 +42,8 @@ const config = {
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
ignoreHTTPSErrors: true
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'only-on-failure'
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
@ -101,7 +102,7 @@ const config = {
|
||||
],
|
||||
|
||||
/* 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 */
|
||||
// webServer: {
|
||||
|
@ -22,6 +22,14 @@ const mockCVEList = {
|
||||
CVEListForImage: {
|
||||
Tag: '',
|
||||
Page: { ItemCount: 20, TotalCount: 20 },
|
||||
Summary: {
|
||||
Count: 5,
|
||||
UnknownCount: 1,
|
||||
LowCount: 1,
|
||||
MediumCount: 1,
|
||||
HighCount: 1,
|
||||
CriticalCount: 1,
|
||||
},
|
||||
CVEList: [
|
||||
{
|
||||
Id: 'CVE-2020-16156',
|
||||
@ -499,6 +507,7 @@ describe('Vulnerabilties page', () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ 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(/fixed in/i)).toHaveLength(20));
|
||||
});
|
||||
|
||||
@ -515,7 +524,7 @@ describe('Vulnerabilties page', () => {
|
||||
it('renders no vulnerabilities if there are not any', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({
|
||||
status: 200,
|
||||
data: { data: { CVEListForImage: { Tag: '', Page: {}, CVEList: [] } } }
|
||||
data: { data: { CVEListForImage: { Tag: '', Page: {}, CVEList: [], Summary: {} } } }
|
||||
});
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
||||
@ -574,18 +583,34 @@ describe('Vulnerabilties page', () => {
|
||||
fireEvent.click(downloadBtn[0]);
|
||||
expect(await screen.findByTestId('export-csv-menuItem')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('export-excel-menuItem')).toBeInTheDocument();
|
||||
const exportAsCSVBtn = screen.getByText(/CSV/i);
|
||||
const exportAsCSVBtn = screen.getByText(/csv/i);
|
||||
expect(exportAsCSVBtn).toBeInTheDocument();
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
await fireEvent.click(exportAsCSVBtn);
|
||||
expect(await screen.findByTestId('export-csv-menuItem')).not.toBeInTheDocument();
|
||||
fireEvent.click(downloadBtn[0]);
|
||||
const exportAsExcelBtn = screen.getByText(/MS Excel/i);
|
||||
const exportAsExcelBtn = screen.getByText(/xlsx/i);
|
||||
expect(exportAsExcelBtn).toBeInTheDocument();
|
||||
await fireEvent.click(exportAsExcelBtn);
|
||||
expect(await screen.findByTestId('export-excel-menuItem')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should expand/collapse the list of CVEs', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText('Fixed in')).toHaveLength(20));
|
||||
const collapseListBtn = await screen.findAllByTestId('ViewHeadlineIcon');
|
||||
fireEvent.click(collapseListBtn[0]);
|
||||
expect(await screen.findByText('Fixed in')).not.toBeVisible();
|
||||
const expandListBtn = await screen.findAllByTestId('ViewAgendaIcon');
|
||||
fireEvent.click(expandListBtn[0]);
|
||||
await waitFor(() => expect(screen.getAllByText('Fixed in')).toHaveLength(20));
|
||||
});
|
||||
|
||||
it('should handle fixed CVE query errors', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
|
@ -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}}}}`,
|
||||
|
@ -66,13 +66,18 @@ const useStyles = makeStyles((theme) => ({
|
||||
cursor: 'pointer',
|
||||
textAlign: 'center'
|
||||
},
|
||||
dropdownCVE: {
|
||||
color: '#1479FF',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
vulnerabilityCardDivider: {
|
||||
margin: '1rem 0'
|
||||
}
|
||||
}));
|
||||
function VulnerabilitiyCard(props) {
|
||||
const classes = useStyles();
|
||||
const { cve, name, platform } = props;
|
||||
const { cve, name, platform, expand } = props;
|
||||
const [openCVE, setOpenCVE] = useState(expand);
|
||||
const [openDesc, setOpenDesc] = useState(false);
|
||||
const [openFixed, setOpenFixed] = useState(false);
|
||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||
@ -122,6 +127,10 @@ function VulnerabilitiyCard(props) {
|
||||
};
|
||||
}, [openFixed, pageNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
setOpenCVE(expand);
|
||||
}, [expand]);
|
||||
|
||||
const loadMore = () => {
|
||||
if (loadingFixed || isEndOfList) return;
|
||||
setPageNumber((pageNumber) => pageNumber + 1);
|
||||
@ -166,49 +175,56 @@ function VulnerabilitiyCard(props) {
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Stack direction="row" spacing="1.25rem">
|
||||
{!openCVE ? (
|
||||
<KeyboardArrowRight className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
||||
)}
|
||||
<Typography variant="body1" align="left" className={classes.cveId}>
|
||||
{cve.id}
|
||||
</Typography>
|
||||
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
||||
</Stack>
|
||||
<Typography variant="body1" align="left" className={classes.cveSummary}>
|
||||
{cve.title}
|
||||
</Typography>
|
||||
<Divider className={classes.vulnerabilityCardDivider} />
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
|
||||
{!openFixed ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Fixed in</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ width: '100%', padding: '0.5rem 0' }}>
|
||||
{loadingFixed ? (
|
||||
'Loading...'
|
||||
<Collapse in={openCVE} timeout="auto" unmountOnExit>
|
||||
<Typography variant="body1" align="left" className={classes.cveSummary}>
|
||||
{cve.title}
|
||||
</Typography>
|
||||
<Divider className={classes.vulnerabilityCardDivider} />
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
|
||||
{!openFixed ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<Stack direction="row" sx={{ flexWrap: 'wrap' }}>
|
||||
{renderFixedVer()}
|
||||
{renderLoadMore()}
|
||||
</Stack>
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenDesc(!openDesc)}>
|
||||
{!openDesc ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Description</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openDesc} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ padding: '0.5rem 0' }}>
|
||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
||||
{cve.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography className={classes.dropdownText}>Fixed in</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ width: '100%', padding: '0.5rem 0' }}>
|
||||
{loadingFixed ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<Stack direction="row" sx={{ flexWrap: 'wrap' }}>
|
||||
{renderFixedVer()}
|
||||
{renderLoadMore()}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenDesc(!openDesc)}>
|
||||
{!openDesc ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Description</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openDesc} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ padding: '0.5rem 0' }}>
|
||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
||||
{cve.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
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;
|
@ -9,6 +9,7 @@ import {
|
||||
Stack,
|
||||
Typography,
|
||||
InputBase,
|
||||
ToggleButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Divider,
|
||||
@ -26,16 +27,29 @@ import DownloadIcon from '@mui/icons-material/Download';
|
||||
|
||||
import * as XLSX from 'xlsx';
|
||||
import exportFromJSON from 'export-from-json';
|
||||
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',
|
||||
@ -64,6 +78,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
search: {
|
||||
position: 'relative',
|
||||
maxWidth: '100%',
|
||||
flex: 0.95,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
@ -71,6 +86,20 @@ 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',
|
||||
justifyContent: 'right'
|
||||
},
|
||||
searchIcon: {
|
||||
color: '#52637A',
|
||||
paddingRight: '3%'
|
||||
@ -87,15 +116,12 @@ const useStyles = makeStyles((theme) => ({
|
||||
opacity: '1'
|
||||
}
|
||||
},
|
||||
export: {
|
||||
alignContent: 'right'
|
||||
},
|
||||
popper: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
padding: '0.3rem',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'left'
|
||||
}
|
||||
}));
|
||||
|
||||
@ -103,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(), []);
|
||||
@ -117,6 +144,8 @@ function VulnerabilitiesDetails(props) {
|
||||
const [anchorExport, setAnchorExport] = useState(null);
|
||||
const openExport = Boolean(anchorExport);
|
||||
|
||||
const [selectedViewMore, setSelectedViewMore] = useState(true);
|
||||
|
||||
const getCVERequestName = () => {
|
||||
return digest !== '' ? `${name}@${digest}` : `${name}:${tag}`;
|
||||
};
|
||||
@ -134,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);
|
||||
}
|
||||
@ -146,6 +189,7 @@ function VulnerabilitiesDetails(props) {
|
||||
console.error(e);
|
||||
setIsLoading(false);
|
||||
setCveData([]);
|
||||
setCVESummary(() => {});
|
||||
setIsEndOfList(true);
|
||||
});
|
||||
};
|
||||
@ -182,7 +226,7 @@ function VulnerabilitiesDetails(props) {
|
||||
const wb = XLSX.utils.book_new(),
|
||||
ws = XLSX.utils.json_to_sheet(allCveData);
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, name + '_' + tag);
|
||||
XLSX.utils.book_append_sheet(wb, ws, name.replaceAll('/', '_') + '_' + tag);
|
||||
|
||||
XLSX.writeFile(wb, `${name}:${tag}-vulnerabilities.xlsx`);
|
||||
|
||||
@ -263,13 +307,34 @@ function VulnerabilitiesDetails(props) {
|
||||
const renderCVEs = () => {
|
||||
return !isEmpty(cveData) ? (
|
||||
cveData.map((cve, index) => {
|
||||
return <VulnerabilitiyCard key={index} cve={cve} name={name} platform={platform} />;
|
||||
return <VulnerabilitiyCard key={index} cve={cve} name={name} platform={platform} expand={selectedViewMore} />;
|
||||
})
|
||||
) : (
|
||||
<div>{!isLoading && <Typography className={classes.none}> No Vulnerabilities </Typography>}</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
@ -286,14 +351,36 @@ function VulnerabilitiesDetails(props) {
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Vulnerabilities
|
||||
</Typography>
|
||||
<IconButton disableRipple onClick={handleClickExport} className={classes.export}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
<Snackbar
|
||||
open={openExport && isLoadingAllCve}
|
||||
message="Getting your data ready for export"
|
||||
action={<CircularProgress size="2rem" sx={{ color: '#FFFFFF' }} />}
|
||||
/>
|
||||
<Stack direction="row" spacing="1rem" className={classes.viewModes}>
|
||||
<IconButton disableRipple onClick={handleClickExport}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
<Snackbar
|
||||
open={openExport && isLoadingAllCve}
|
||||
message="Getting your data ready for export"
|
||||
action={<CircularProgress size="2rem" sx={{ color: '#FFFFFF' }} />}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="viewLess"
|
||||
title="Collapse list view"
|
||||
size="small"
|
||||
className={classes.view}
|
||||
selected={!selectedViewMore}
|
||||
onChange={() => setSelectedViewMore(false)}
|
||||
>
|
||||
<ViewHeadlineIcon />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="viewMore"
|
||||
title="Expand list view"
|
||||
size="small"
|
||||
className={classes.view}
|
||||
selected={selectedViewMore}
|
||||
onChange={() => setSelectedViewMore(true)}
|
||||
>
|
||||
<ViewAgendaIcon />
|
||||
</ToggleButton>
|
||||
</Stack>
|
||||
<Menu
|
||||
anchorEl={anchorExport}
|
||||
open={openExport}
|
||||
@ -315,7 +402,7 @@ function VulnerabilitiesDetails(props) {
|
||||
className={classes.popper}
|
||||
data-testid="export-csv-menuItem"
|
||||
>
|
||||
CSV
|
||||
csv
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5 }} />
|
||||
<MenuItem
|
||||
@ -325,10 +412,11 @@ function VulnerabilitiesDetails(props) {
|
||||
className={classes.popper}
|
||||
data-testid="export-excel-menuItem"
|
||||
>
|
||||
MS Excel
|
||||
xlsx
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Stack>
|
||||
{renderCVESummary()}
|
||||
<Stack className={classes.search}>
|
||||
<InputBase
|
||||
placeholder={'Search'}
|
||||
|
@ -37,6 +37,7 @@ test.describe('Tag page test', () => {
|
||||
await page.goto(`${hosts.ui}/image/${tagWithVulnerabilities.title}/tag/${tagWithVulnerabilities.tag}`);
|
||||
await page.getByRole('tab', { name: 'Vulnerabilities' }).click();
|
||||
await expect(page.getByTestId('vulnerability-container').locator('div').nth(1)).toBeVisible({ timeout: 100000 });
|
||||
await expect(page.getByText('CVE-').nth(0)).toBeVisible({ timeout: 100000 });
|
||||
await expect(await page.getByText('CVE-').count()).toBeGreaterThan(0);
|
||||
await expect(await page.getByText('CVE-').count()).toBeLessThanOrEqual(pageSizes.EXPLORE);
|
||||
});
|
||||
|
Reference in New Issue
Block a user