feat(cve): add more information
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
parent
f4a6030d93
commit
0edfe0f73a
@ -483,6 +483,12 @@ const mockCVEFixed = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
pageNotFixed: {
|
||||||
|
ImageListWithCVEFixed: {
|
||||||
|
Page: { TotalCount: 0, ItemCount: 0 },
|
||||||
|
Results: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -504,15 +510,23 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe('Vulnerabilties page', () => {
|
describe('Vulnerabilties page', () => {
|
||||||
it('renders the vulnerabilities if there are any', async () => {
|
it('renders the vulnerabilities if there are any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
let getCall = jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||||
|
for (let i=0; i<21; i++) {
|
||||||
|
getCall.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
|
}
|
||||||
|
getCall.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('Total 5')).toHaveLength(1));
|
||||||
await waitFor(() => expect(screen.getAllByText(/fixed in/i)).toHaveLength(20));
|
await waitFor(() => expect(screen.getAllByText(/Fixed in/)).toHaveLength(20));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends filtered query if user types in the search bar', async () => {
|
it('sends filtered query if user types in the search bar', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
let getCall = jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||||
|
for (let i=0; i<21; i++) {
|
||||||
|
getCall.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
|
}
|
||||||
|
getCall.mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
const cveSearchInput = screen.getByPlaceholderText(/search/i);
|
const cveSearchInput = screen.getByPlaceholderText(/search/i);
|
||||||
jest.spyOn(api, 'get').mockRejectedValueOnce({ status: 200, data: { data: mockCVEListFiltered } });
|
jest.spyOn(api, 'get').mockRejectedValueOnce({ status: 200, data: { data: mockCVEListFiltered } });
|
||||||
@ -522,7 +536,11 @@ describe('Vulnerabilties page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a collapsable search bar', async () => {
|
it('should have a collapsable search bar', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
let getCall = jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||||
|
for (let i=0; i<21; i++) {
|
||||||
|
getCall.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
|
}
|
||||||
|
getCall.mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
const cveSearchInput = screen.getByPlaceholderText(/search/i);
|
const cveSearchInput = screen.getByPlaceholderText(/search/i);
|
||||||
const expandSearch = cveSearchInput.parentElement.parentElement.parentElement.parentElement.childNodes[0];
|
const expandSearch = cveSearchInput.parentElement.parentElement.parentElement.parentElement.childNodes[0];
|
||||||
@ -544,19 +562,14 @@ describe('Vulnerabilties page', () => {
|
|||||||
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open and close description dropdown for vulnerabilities', async () => {
|
it('should show description for vulnerabilities', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||||
|
.mockResolvedValue({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText(/description/i)).toHaveLength(20));
|
await waitFor(() => expect(screen.getAllByText(/Description/)).toHaveLength(20));
|
||||||
const openText = screen.getAllByText(/description/i);
|
|
||||||
await fireEvent.click(openText[0]);
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getAllByText(/CPAN 2.28 allows Signature Verification Bypass./i)).toHaveLength(1)
|
expect(screen.getAllByText(/CPAN 2.28 allows Signature Verification Bypass./i)).toHaveLength(1)
|
||||||
);
|
);
|
||||||
await fireEvent.click(openText[0]);
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.queryByText(/CPAN 2.28 allows Signature Verification Bypass./i)).not.toBeInTheDocument()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should log an error when data can't be fetched", async () => {
|
it("should log an error when data can't be fetched", async () => {
|
||||||
@ -574,12 +587,12 @@ describe('Vulnerabilties page', () => {
|
|||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageTwo } });
|
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageTwo } });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||||
await fireEvent.click(screen.getAllByText(/fixed in/i)[0]);
|
|
||||||
await waitFor(() => expect(screen.getByText('1.0.16')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('1.0.16')).toBeInTheDocument());
|
||||||
const loadMoreBtn = screen.getByText(/load more/i);
|
await waitFor(() => expect(screen.getAllByText(/load more/i).length).toBeGreaterThan(0));
|
||||||
expect(loadMoreBtn).toBeInTheDocument();
|
const nrLoadButtons = screen.getAllByText(/load more/i).length
|
||||||
|
const loadMoreBtn = screen.getAllByText(/load more/i)[0];
|
||||||
await fireEvent.click(loadMoreBtn);
|
await fireEvent.click(loadMoreBtn);
|
||||||
await waitFor(() => expect(loadMoreBtn).not.toBeInTheDocument());
|
await waitFor(() => expect(screen.getAllByText(/load more/i).length).toBe(nrLoadButtons-1));
|
||||||
expect(await screen.findByText('latest')).toBeInTheDocument();
|
expect(await screen.findByText('latest')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -587,10 +600,11 @@ describe('Vulnerabilties page', () => {
|
|||||||
const xlsxMock = jest.createMockFromModule('xlsx');
|
const xlsxMock = jest.createMockFromModule('xlsx');
|
||||||
xlsxMock.writeFile = jest.fn();
|
xlsxMock.writeFile = jest.fn();
|
||||||
|
|
||||||
jest
|
let getCall = jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||||
.spyOn(api, 'get')
|
for (let i=0; i<21; i++) {
|
||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
getCall.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
}
|
||||||
|
getCall.mockResolvedValueOnce({ 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));
|
||||||
const downloadBtn = await screen.findAllByTestId('DownloadIcon');
|
const downloadBtn = await screen.findAllByTestId('DownloadIcon');
|
||||||
@ -610,10 +624,11 @@ describe('Vulnerabilties page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should expand/collapse the list of CVEs', async () => {
|
it('should expand/collapse the list of CVEs', async () => {
|
||||||
jest
|
let getCall = jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
||||||
.spyOn(api, 'get')
|
for (let i=0; i<21; i++) {
|
||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
getCall.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageNotFixed } });
|
||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } });
|
}
|
||||||
|
getCall.mockResolvedValueOnce({ 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('Fixed in')).toHaveLength(20));
|
await waitFor(() => expect(screen.getAllByText('Fixed in')).toHaveLength(20));
|
||||||
@ -629,12 +644,11 @@ describe('Vulnerabilties page', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(api, 'get')
|
.spyOn(api, 'get')
|
||||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||||
.mockRejectedValue({ status: 500, data: {} });
|
.mockRejectedValueOnce({ status: 500, data: {} });
|
||||||
render(<StateVulnerabilitiesWrapper />);
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
await fireEvent.click(screen.getAllByText(/fixed in/i)[0]);
|
await waitFor(() => expect(screen.getAllByText(/not fixed/i).length).toBeGreaterThan(0));
|
||||||
await waitFor(() => expect(screen.getByText(/not fixed/i)).toBeInTheDocument());
|
await waitFor(() => expect(error).toBeCalled());
|
||||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -100,7 +100,7 @@ const endpoints = {
|
|||||||
if (!isEmpty(excludedTerm)) {
|
if (!isEmpty(excludedTerm)) {
|
||||||
query += `, excludedCVE: "${excludedTerm}"`;
|
query += `, excludedCVE: "${excludedTerm}"`;
|
||||||
}
|
}
|
||||||
return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}} Summary {Count UnknownCount LowCount MediumCount HighCount CriticalCount}}}`;
|
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) =>
|
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}}}}`,
|
||||||
|
@ -29,18 +29,46 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginTop: '2rem',
|
marginTop: '2rem',
|
||||||
marginBottom: '2rem'
|
marginBottom: '2rem'
|
||||||
},
|
},
|
||||||
|
cardCollapsed: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
background: '#FFFFFF',
|
||||||
|
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||||
|
border: '1px solid #E0E5EB',
|
||||||
|
borderRadius: '0.75rem',
|
||||||
|
flex: 'none',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
color: '#606060',
|
color: '#606060',
|
||||||
padding: '2% 3% 2% 3%',
|
padding: '2% 3% 2% 3%',
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
|
contentCollapsed: {
|
||||||
|
textAlign: 'left',
|
||||||
|
color: '#606060',
|
||||||
|
padding: '1% 3% 1% 3%',
|
||||||
|
width: '100%',
|
||||||
|
'&:last-child': {
|
||||||
|
paddingBottom: '1%'
|
||||||
|
}
|
||||||
|
},
|
||||||
cveId: {
|
cveId: {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
textDecoration: 'underline'
|
textDecoration: 'underline'
|
||||||
},
|
},
|
||||||
|
cveIdCollapsed: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
flexBasis: '19%'
|
||||||
|
},
|
||||||
cveSummary: {
|
cveSummary: {
|
||||||
color: theme.palette.secondary.dark,
|
color: theme.palette.secondary.dark,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
@ -48,6 +76,13 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
marginTop: '0.5rem'
|
marginTop: '0.5rem'
|
||||||
},
|
},
|
||||||
|
cveSummaryCollapsed: {
|
||||||
|
color: theme.palette.secondary.dark,
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
flexBasis: '82%'
|
||||||
|
},
|
||||||
link: {
|
link: {
|
||||||
color: '#52637A',
|
color: '#52637A',
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
@ -72,14 +107,15 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
vulnerabilityCardDivider: {
|
vulnerabilityCardDivider: {
|
||||||
margin: '1rem 0'
|
margin: '1rem 0'
|
||||||
|
},
|
||||||
|
cveInfo: {
|
||||||
|
marginTop: '2%'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
function VulnerabilitiyCard(props) {
|
function VulnerabilitiyCard(props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { cve, name, platform, expand } = props;
|
const { cve, name, platform, expand } = props;
|
||||||
const [openCVE, setOpenCVE] = useState(expand);
|
const [openCVE, setOpenCVE] = useState(expand);
|
||||||
const [openDesc, setOpenDesc] = useState(false);
|
|
||||||
const [openFixed, setOpenFixed] = useState(false);
|
|
||||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||||
const [fixedInfo, setFixedInfo] = useState([]);
|
const [fixedInfo, setFixedInfo] = useState([]);
|
||||||
const abortController = useMemo(() => new AbortController(), []);
|
const abortController = useMemo(() => new AbortController(), []);
|
||||||
@ -89,7 +125,7 @@ function VulnerabilitiyCard(props) {
|
|||||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||||
|
|
||||||
const getPaginatedResults = () => {
|
const getPaginatedResults = () => {
|
||||||
if (!openFixed || isEndOfList) {
|
if (isEndOfList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoadingFixed(true);
|
setLoadingFixed(true);
|
||||||
@ -125,7 +161,7 @@ function VulnerabilitiyCard(props) {
|
|||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
}, [openFixed, pageNumber]);
|
}, [pageNumber]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpenCVE(expand);
|
setOpenCVE(expand);
|
||||||
@ -172,33 +208,81 @@ function VulnerabilitiyCard(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.card} raised>
|
<Card className={openCVE ? classes.card : classes.cardCollapsed} raised>
|
||||||
<CardContent className={classes.content}>
|
<CardContent className={openCVE ? classes.content : classes.contentCollapsed}>
|
||||||
<Stack direction="row" spacing="1.25rem">
|
<Stack direction="row" spacing={openCVE ? '1.25rem' : '0.5rem'}>
|
||||||
{!openCVE ? (
|
{!openCVE ? (
|
||||||
<KeyboardArrowRight className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
<KeyboardArrowRight className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
||||||
) : (
|
) : (
|
||||||
<KeyboardArrowDown className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
<KeyboardArrowDown className={classes.dropdownCVE} onClick={() => setOpenCVE(!openCVE)} />
|
||||||
)}
|
)}
|
||||||
<Typography variant="body1" align="left" className={classes.cveId}>
|
<Typography variant="body1" align="left" className={openCVE ? classes.cveId : classes.cveIdCollapsed}>
|
||||||
{cve.id}
|
{cve.id}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{openCVE ? (
|
||||||
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
||||||
|
) : (
|
||||||
|
<Stack direction="row" spacing="0.5rem" flexBasis="90%">
|
||||||
|
<div style={{ transform: 'scale(0.8)', flexBasis: '18%', flexShrink: '0' }}>
|
||||||
|
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
||||||
|
</div>
|
||||||
|
<Typography variant="body1" align="left" className={classes.cveSummaryCollapsed}>
|
||||||
|
{cve.title}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Collapse in={openCVE} timeout="auto" unmountOnExit>
|
<Collapse in={openCVE} timeout="auto" unmountOnExit>
|
||||||
<Typography variant="body1" align="left" className={classes.cveSummary}>
|
<Typography variant="body1" align="left" className={classes.cveSummary}>
|
||||||
{cve.title}
|
{cve.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider className={classes.vulnerabilityCardDivider} />
|
<Divider className={classes.vulnerabilityCardDivider} />
|
||||||
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
|
<Typography variant="body2" align="left" className={classes.cveInfo}>
|
||||||
{!openFixed ? (
|
External reference
|
||||||
<KeyboardArrowRight className={classes.dropdownText} />
|
</Typography>
|
||||||
) : (
|
<Typography
|
||||||
<KeyboardArrowDown className={classes.dropdownText} />
|
variant="body2"
|
||||||
)}
|
align="left"
|
||||||
<Typography className={classes.dropdownText}>Fixed in</Typography>
|
sx={{ color: '#0F2139', fontSize: '1rem', textDecoration: 'underline' }}
|
||||||
|
component={Link}
|
||||||
|
to={cve.reference}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{cve.reference}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" align="left" className={classes.cveInfo}>
|
||||||
|
Packages
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="column" sx={{ width: '100%', padding: '0.5rem 0' }}>
|
||||||
|
<Stack direction="row" spacing="1.25rem" display="flex">
|
||||||
|
<Typography variant="body1" flexBasis="33.33%">
|
||||||
|
Name
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" flexBasis="33.33%" textAlign="right">
|
||||||
|
Installed Version
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" flexBasis="33.33%" textAlign="right">
|
||||||
|
Fixed Version
|
||||||
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
{cve.packageList.map((el) => (
|
||||||
|
<Stack direction="row" key={cve.packageName} spacing="1.25rem" display="flex">
|
||||||
|
<Typography variant="body1" color="primary" flexBasis="33.33%">
|
||||||
|
{el.packageName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="primary" flexBasis="33.33%" textAlign="right">
|
||||||
|
{el.packageInstalledVersion}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="primary" flexBasis="33.33%" textAlign="right">
|
||||||
|
{el.packageFixedVersion}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
<Typography variant="body2" align="left" className={classes.cveInfo}>
|
||||||
|
Fixed in
|
||||||
|
</Typography>
|
||||||
<Box sx={{ width: '100%', padding: '0.5rem 0' }}>
|
<Box sx={{ width: '100%', padding: '0.5rem 0' }}>
|
||||||
{loadingFixed ? (
|
{loadingFixed ? (
|
||||||
'Loading...'
|
'Loading...'
|
||||||
@ -209,23 +293,15 @@ function VulnerabilitiyCard(props) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
<Typography variant="body2" align="left" className={classes.cveInfo}>
|
||||||
<Stack className={classes.dropdown} onClick={() => setOpenDesc(!openDesc)}>
|
Description
|
||||||
{!openDesc ? (
|
</Typography>
|
||||||
<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' }}>
|
<Box sx={{ padding: '0.5rem 0' }}>
|
||||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
||||||
{cve.description}
|
{cve.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Collapse>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -481,9 +481,11 @@ function VulnerabilitiesDetails(props) {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Stack direction="column" spacing={selectedViewMore ? '1rem' : '0.5rem'}>
|
||||||
{renderCVEs()}
|
{renderCVEs()}
|
||||||
{renderListBottom()}
|
{renderListBottom()}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,13 @@ const mapCVEInfo = (cveInfo) => {
|
|||||||
id: cve.Id,
|
id: cve.Id,
|
||||||
severity: cve.Severity,
|
severity: cve.Severity,
|
||||||
title: cve.Title,
|
title: cve.Title,
|
||||||
description: cve.Description
|
description: cve.Description,
|
||||||
|
reference: cve.Reference,
|
||||||
|
packageList: cve.PackageList?.map((pkg) => ({
|
||||||
|
packageName: pkg.Name,
|
||||||
|
packageInstalledVersion: pkg.InstalledVersion,
|
||||||
|
packageFixedVersion: pkg.FixedVersion
|
||||||
|
}))
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return cveList;
|
return cveList;
|
||||||
|
@ -37,8 +37,8 @@ test.describe('Tag page test', () => {
|
|||||||
await page.goto(`${hosts.ui}/image/${tagWithVulnerabilities.title}/tag/${tagWithVulnerabilities.tag}`);
|
await page.goto(`${hosts.ui}/image/${tagWithVulnerabilities.title}/tag/${tagWithVulnerabilities.tag}`);
|
||||||
await page.getByRole('tab', { name: 'Vulnerabilities' }).click();
|
await page.getByRole('tab', { name: 'Vulnerabilities' }).click();
|
||||||
await expect(page.getByTestId('vulnerability-container').locator('div').nth(1)).toBeVisible({ timeout: 100000 });
|
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(page.getByText(/CVE-/).nth(0)).toBeVisible({ timeout: 100000 });
|
||||||
await expect(await page.getByText('CVE-').count()).toBeGreaterThan(0);
|
await expect(await page.getByText(/CVE-/).count()).toBeGreaterThan(0);
|
||||||
await expect(await page.getByText('CVE-').count()).toBeLessThanOrEqual(pageSizes.EXPLORE);
|
await expect(await page.getByText(/CVE-/).count()).toBeLessThanOrEqual(pageSizes.EXPLORE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user