feat: Implemented fixed-in, fixed tagdetails page query
Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
988249588f
commit
9baeb5bba2
@ -4,64 +4,56 @@ import TagDetails from 'components/TagDetails';
|
||||
import React from 'react';
|
||||
|
||||
const mockImage = {
|
||||
ExpandedRepoInfo: {
|
||||
Images: [
|
||||
Image: {
|
||||
RepoName: 'centos',
|
||||
Tag: '8',
|
||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||
Size: '75183423',
|
||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||
Platform: {
|
||||
Os: 'linux',
|
||||
Arch: 'amd64'
|
||||
},
|
||||
Vendor: 'CentOS',
|
||||
History: [
|
||||
{
|
||||
Digest: '7374731e3dd3112d41ece21cf2db5a16f11a51b33bf065e98c767893f50d3dec',
|
||||
Tag: 'latest',
|
||||
Layers: [
|
||||
{
|
||||
Size: '28572596',
|
||||
Digest: '3b65ec22a9e96affe680712973e88355927506aa3f792ff03330f3a3eb601a98'
|
||||
},
|
||||
{
|
||||
Size: '1835',
|
||||
Digest: '016bc871e2b33f0e2a37272769ebd6defdb4b702f0d41ec1e685f0366b64e64a'
|
||||
},
|
||||
{
|
||||
Size: '3059542',
|
||||
Digest: '9ddd649edd82d79ffc6f573cd5da7909ae50596b95aca684a571aff6e36aa8cb'
|
||||
},
|
||||
{
|
||||
Size: '6506025',
|
||||
Digest: '39bf776c01e412c9cf35ea7a41f97370c486dee27a2aab228cf2e850a8863e8b'
|
||||
},
|
||||
{
|
||||
Size: '149',
|
||||
Digest: 'f7f0405a2fe343547a60a9d4182261ca02d70bb9e47d6cd248f3285d6b41e64c'
|
||||
},
|
||||
{
|
||||
Size: '1447',
|
||||
Digest: '89785d0d9c65afe73fbd9bcb29c451090ca84df0e128cf1ecf5712c036e8c9d2'
|
||||
},
|
||||
{
|
||||
Size: '261',
|
||||
Digest: 'fd40d84c80b0302ca13faab8210d8c7082814f6f2ab576b3a61f467d03e1cb0b'
|
||||
},
|
||||
{
|
||||
Size: '193228772',
|
||||
Digest: 'd50d65ac4752500ab9f3c24c86b4aa218bea9a0bb0a837ae54ffe2e6d2454f5a'
|
||||
},
|
||||
{
|
||||
Size: '5067',
|
||||
Digest: '255e24cbd370c0055e0d31e063e63c792fa68aff9e25a7ac0a21d39cf6d47573'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
Summary: {
|
||||
Name: 'mongo',
|
||||
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
||||
Size: '231383863',
|
||||
Platforms: [
|
||||
{
|
||||
Os: 'linux',
|
||||
Arch: 'amd64'
|
||||
Layer: {
|
||||
Size: '75181999',
|
||||
Digest: 'sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621',
|
||||
Score: null
|
||||
},
|
||||
HistoryDescription: {
|
||||
Created: '2020-12-08T00:22:52.526672082Z',
|
||||
CreatedBy:
|
||||
'/bin/sh -c #(nop) ADD file:bd7a2aed6ede423b719ceb2f723e4ecdfa662b28639c8429731c878e86fb138b in / ',
|
||||
Author: '',
|
||||
Comment: '',
|
||||
EmptyLayer: false
|
||||
}
|
||||
],
|
||||
Vendors: [''],
|
||||
NewestImage: null
|
||||
}
|
||||
},
|
||||
{
|
||||
Layer: null,
|
||||
HistoryDescription: {
|
||||
Created: '2020-12-08T00:22:52.895811646Z',
|
||||
CreatedBy:
|
||||
'/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201204',
|
||||
Author: '',
|
||||
Comment: '',
|
||||
EmptyLayer: true
|
||||
}
|
||||
},
|
||||
{
|
||||
Layer: null,
|
||||
HistoryDescription: {
|
||||
Created: '2020-12-08T00:22:53.076477777Z',
|
||||
CreatedBy: '/bin/sh -c #(nop) CMD ["/bin/bash"]',
|
||||
Author: '',
|
||||
Comment: '',
|
||||
EmptyLayer: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@ -73,6 +65,10 @@ jest.mock('react-router-dom', () => ({
|
||||
}
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
window.scrollTo = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
|
@ -2,9 +2,14 @@ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { api } from 'api';
|
||||
import VulnerabilitiesDetails from 'components/VulnerabilitiesDetails';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const StateVulnerabilitiesWrapper = () => {
|
||||
return <VulnerabilitiesDetails name="mongo" />;
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<VulnerabilitiesDetails name="mongo" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const mockCVEList = {
|
||||
@ -426,6 +431,20 @@ const mockCVEList = {
|
||||
}
|
||||
};
|
||||
|
||||
const mockCVEFixed = {
|
||||
ImageListWithCVEFixed: [
|
||||
{
|
||||
Tag: '1.0.16'
|
||||
},
|
||||
{
|
||||
Tag: '0.4.33'
|
||||
},
|
||||
{
|
||||
Tag: '1.0.17'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
@ -468,4 +487,16 @@ describe('Vulnerabilties page', () => {
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
});
|
||||
|
||||
it('should find out which version fixes the CVEs', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
// @ts-ignore
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||
// @ts-ignore
|
||||
.mockResolvedValue({ status: 200, data: { data: mockCVEFixed } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText('1.0.16')).toHaveLength(20));
|
||||
});
|
||||
});
|
||||
|
@ -64,10 +64,14 @@ const endpoints = {
|
||||
'/v2/_zot/ext/search?query={RepoListWithNewestImage(){Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Description Licenses Title Source Documentation History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}} Vendor Labels} DownloadCount}}',
|
||||
detailedRepoInfo: (name) =>
|
||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Tag Layers {Size Digest}} Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName Layers {Size Digest} Digest Tag Title Documentation DownloadCount Source Description History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}}}}}}`,
|
||||
detailedImageInfo: (name, tag) =>
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName Tag Digest LastUpdated Size ConfigDigest Platform {Os Arch} Vendor History {Layer {Size Digest Score} HistoryDescription {Created CreatedBy Author Comment EmptyLayer} }}}`,
|
||||
vulnerabilitiesForRepo: (name) =>
|
||||
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag, CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
|
||||
layersDetailsForImage: (name) =>
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}"){History {Layer {Size Digest Score} HistoryDescription {Created CreatedBy Author Comment EmptyLayer} }}}`,
|
||||
imageListWithCVEFixed: (cveId, repoName) =>
|
||||
`/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}") {Tag}}`,
|
||||
dependsOnForImage: (name) => `/v2/_zot/ext/search?query={BaseImageList(image: "${name}"){RepoName}}`,
|
||||
isDependentOnForImage: (name) => `/v2/_zot/ext/search?query={DerivedImageList(image: "${name}"){RepoName}}`,
|
||||
globalSearch: ({ searchQuery = '""', pageNumber = 1, pageSize = 15, filter = {} }) => {
|
||||
|
@ -9,6 +9,7 @@ import { Card, CardContent, Divider, Grid, Stack, Typography } from '@mui/materi
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../host';
|
||||
import Monitor from '../assets/Monitor.png';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
@ -116,23 +117,28 @@ function HistoryLayers(props) {
|
||||
const [historyData, setHistoryData] = useState([]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const { name } = props;
|
||||
const { name, history } = props;
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.get(`${host()}${endpoints.layersDetailsForImage(name)}`)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let layersHistory = response.data.data.Image;
|
||||
setHistoryData(layersHistory?.History);
|
||||
setIsLoaded(true);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setHistoryData([]);
|
||||
setIsLoaded(false);
|
||||
});
|
||||
if (history && !isEmpty(history)) {
|
||||
setHistoryData(history);
|
||||
setIsLoaded(true);
|
||||
} else {
|
||||
api
|
||||
.get(`${host()}${endpoints.layersDetailsForImage(name)}`)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let layersHistory = response.data.data.Image;
|
||||
setHistoryData(layersHistory?.History);
|
||||
setIsLoaded(true);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setHistoryData([]);
|
||||
setIsLoaded(false);
|
||||
});
|
||||
}
|
||||
}, [name]);
|
||||
|
||||
return (
|
||||
|
@ -175,18 +175,6 @@ function RepoDetails() {
|
||||
// return list[Math.floor(Math.random() * list.length)];
|
||||
// }
|
||||
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <Chip label="None Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// };
|
||||
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
|
@ -118,61 +118,52 @@ const randomImage = () => {
|
||||
};
|
||||
|
||||
function TagDetails() {
|
||||
const [repoDetailData, setRepoDetailData] = useState({});
|
||||
const [imageDetailData, setImageDetailData] = useState({});
|
||||
// @ts-ignore
|
||||
//const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedTab, setSelectedTab] = useState('Layers');
|
||||
const [tagName, setTagName] = useState('');
|
||||
const [fullName, setFullName] = useState('');
|
||||
|
||||
// get url param from <Route here (i.e. image name)
|
||||
const { name } = useParams();
|
||||
const { name, tag } = useParams();
|
||||
const classes = useStyles();
|
||||
// const { description, overviewTitle, dependencies, dependents } = props;
|
||||
|
||||
useEffect(() => {
|
||||
// if same-page navigation because of tag update, following 2 lines help ux
|
||||
setSelectedTab('Layers');
|
||||
window?.scrollTo(0, 0);
|
||||
api
|
||||
.get(`${host()}${endpoints.detailedRepoInfo(name)}`)
|
||||
.get(`${host()}${endpoints.detailedImageInfo(name, tag)}`)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoInfo = response.data.data.ExpandedRepoInfo;
|
||||
let imageInfo = response.data.data.Image;
|
||||
let imageData = {
|
||||
name: name,
|
||||
tags: repoInfo.Images[0]?.Tag,
|
||||
lastUpdated: repoInfo.Summary?.LastUpdated,
|
||||
size: repoInfo.Summary?.Size,
|
||||
latestDigest: repoInfo.Images[0].Digest,
|
||||
layers: repoInfo.Images[0].Layers,
|
||||
platforms: repoInfo.Summary?.Platforms,
|
||||
vendors: repoInfo.Summary?.Vendors,
|
||||
newestTag: repoInfo.Summary?.NewestImage?.Tag
|
||||
name: imageInfo.RepoName,
|
||||
tag: imageInfo.Tag,
|
||||
lastUpdated: imageInfo.LastUpdated,
|
||||
size: imageInfo.Size,
|
||||
digest: imageInfo.ConfigDigest,
|
||||
platform: imageInfo.Platform,
|
||||
vendor: imageInfo.Vendor,
|
||||
history: imageInfo.History
|
||||
};
|
||||
setRepoDetailData(imageData);
|
||||
setTagName(imageData.name + ':' + imageData.newestTag);
|
||||
setImageDetailData(imageData);
|
||||
setFullName(imageData.name + ':' + imageData.tag);
|
||||
|
||||
//setIsLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setRepoDetailData({});
|
||||
setImageDetailData({});
|
||||
});
|
||||
}, [name]);
|
||||
}, [name, tag]);
|
||||
//function that returns a random element from an array
|
||||
// function getRandom(list) {
|
||||
// return list[Math.floor(Math.random() * list.length)];
|
||||
// }
|
||||
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <Chip label="No Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// };
|
||||
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
@ -184,7 +175,7 @@ function TagDetails() {
|
||||
|
||||
const getPlatform = () => {
|
||||
// @ts-ignore
|
||||
return repoDetailData?.platforms ? repoDetailData.platforms[0] : '--/--';
|
||||
return imageDetailData?.platform ? imageDetailData.platform : '--/--';
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
@ -192,31 +183,6 @@ function TagDetails() {
|
||||
setSelectedTab(newValue);
|
||||
};
|
||||
|
||||
//will need this but not for now
|
||||
// const renderDependencies = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Dependecies ({dependencies || '---'})</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
// const renderDependents = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Dependents ({dependents || '---'})</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
// const renderVulnerabilities = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Vulnerabilities</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className={classes.pageWrapper}>
|
||||
<Card className={classes.cardRoot}>
|
||||
@ -237,7 +203,7 @@ function TagDetails() {
|
||||
{name}:
|
||||
{
|
||||
// @ts-ignore
|
||||
repoDetailData?.newestTag
|
||||
tag
|
||||
}
|
||||
</Typography>
|
||||
{/* {vulnerabilityCheck()}
|
||||
@ -253,7 +219,7 @@ function TagDetails() {
|
||||
Digest:{' '}
|
||||
{
|
||||
// @ts-ignore
|
||||
repoDetailData?.latestDigest
|
||||
imageDetailData?.digest
|
||||
}
|
||||
</Typography>
|
||||
</Grid>
|
||||
@ -280,13 +246,19 @@ function TagDetails() {
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<TabPanel value="Layers" className={classes.tabPanel}>
|
||||
<HistoryLayers name={tagName} />
|
||||
<HistoryLayers
|
||||
name={fullName}
|
||||
history={
|
||||
// @ts-ignore
|
||||
imageDetailData.history
|
||||
}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value="DependsOn" className={classes.tabPanel}>
|
||||
<DependsOn name={tagName} />
|
||||
<DependsOn name={fullName} />
|
||||
</TabPanel>
|
||||
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
|
||||
<IsDependentOn name={tagName} />
|
||||
<IsDependentOn name={fullName} />
|
||||
</TabPanel>
|
||||
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
|
||||
<VulnerabilitiesDetails name={name} />
|
||||
@ -299,11 +271,11 @@ function TagDetails() {
|
||||
<Grid item xs={4} className={classes.metadata}>
|
||||
<TagDetailsMetadata
|
||||
// @ts-ignore
|
||||
platforms={getPlatform()}
|
||||
platform={getPlatform()}
|
||||
// @ts-ignore
|
||||
size={repoDetailData?.size}
|
||||
size={imageDetailData?.size}
|
||||
// @ts-ignore
|
||||
lastUpdated={repoDetailData?.lastUpdated}
|
||||
lastUpdated={imageDetailData?.lastUpdated}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -35,7 +35,7 @@ const useStyles = makeStyles(() => ({
|
||||
|
||||
function TagDetailsMetadata(props) {
|
||||
const classes = useStyles();
|
||||
const { platforms, lastUpdated, size } = props;
|
||||
const { platform, lastUpdated, size } = props;
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
|
||||
unit: 'days'
|
||||
});
|
||||
@ -48,7 +48,7 @@ function TagDetailsMetadata(props) {
|
||||
OS/Arch
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.metadataBody}>
|
||||
{platforms.Os || `----`} / {platforms.Arch || `----`}
|
||||
{platform?.Os || `----`} / {platform?.Arch || `----`}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
@ -11,6 +11,8 @@ import { host } from '../host';
|
||||
import PestControlOutlinedIcon from '@mui/icons-material/PestControlOutlined';
|
||||
import PestControlIcon from '@mui/icons-material/PestControl';
|
||||
import Monitor from '../assets/Monitor.png';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
@ -46,7 +48,15 @@ const useStyles = makeStyles(() => ({
|
||||
fontSize: '1rem',
|
||||
fontWeight: '600',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
paddingTop: '0.5rem',
|
||||
textOverflow: 'ellipsis'
|
||||
},
|
||||
link: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
},
|
||||
monitor: {
|
||||
width: '27.25rem',
|
||||
@ -154,8 +164,40 @@ const vulnerabilityCheck = (status) => {
|
||||
|
||||
function VulnerabilitiyCard(props) {
|
||||
const classes = useStyles();
|
||||
const { cve } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const { cve, name } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||
const [fixedInfo, setFixedInfo] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingFixed(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.imageListWithCVEFixed(cve.Id, name)}`)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.map((e) => e.Tag);
|
||||
setFixedInfo(fixedTagsList);
|
||||
}
|
||||
setLoadingFixed(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const renderFixedVer = () => {
|
||||
if (!isEmpty(fixedInfo)) {
|
||||
return fixedInfo.map((tag, index) => {
|
||||
return (
|
||||
<Link key={index} to={`/image/${encodeURIComponent(name)}/tag/${tag}`} className={classes.link}>
|
||||
{tag}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return 'Not fixed';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
@ -179,6 +221,15 @@ function VulnerabilitiyCard(props) {
|
||||
{cve.Title}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack sx={{ flexDirection: 'row' }}>
|
||||
<Typography variant="body1" align="left" className={classes.title}>
|
||||
Fixed In:{' '}
|
||||
</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.values} noWrap>
|
||||
{' '}
|
||||
{loadingFixed ? 'Loading...' : renderFixedVer()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#1479FF',
|
||||
@ -234,7 +285,7 @@ function VulnerabilitiesDetails(props) {
|
||||
return (
|
||||
cves &&
|
||||
cves.map((cve, index) => {
|
||||
return <VulnerabilitiyCard key={index} cve={cve} />;
|
||||
return <VulnerabilitiyCard key={index} cve={cve} name={name} />;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user