diff --git a/src/api.js b/src/api.js index 111b2aaf..ea4a6531 100644 --- a/src/api.js +++ b/src/api.js @@ -1,11 +1,11 @@ import axios from 'axios'; import { isEmpty } from 'lodash'; import { sortByCriteria } from 'utilities/sortCriteria'; -import { logoutUser } from 'utilities/authUtilities'; +import { isAuthenticationEnabled, logoutUser } from 'utilities/authUtilities'; import { host } from 'host'; axios.interceptors.request.use((config) => { - if (config.url.includes(endpoints.authConfig)) { + if (config.url.includes(endpoints.authConfig) || !isAuthenticationEnabled()) { config.withCredentials = false; } else { config.headers['X-ZOT-API-CLIENT'] = 'zot-ui'; @@ -98,10 +98,18 @@ const endpoints = { } return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`; }, - imageListWithCVEFixed: (cveId, repoName, { pageNumber = 1, pageSize = 3 }) => - `/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}", requestedPage: {limit:${pageSize} offset:${ + imageListWithCVEFixed: (cveId, repoName, { pageNumber = 1, pageSize = 3 }, filter = {}) => { + let filterParam = ''; + if (filter.Os || filter.Arch) { + filterParam = `,filter:{`; + if (filter.Os) filterParam += ` Os:${!isEmpty(filter.Os) ? `${JSON.stringify(filter.Os)}` : '""'}`; + if (filter.Arch) filterParam += ` Arch:${!isEmpty(filter.Arch) ? `${JSON.stringify(filter.Arch)}` : '""'}`; + filterParam += '}'; + } + return `/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}", requestedPage: {limit:${pageSize} offset:${ (pageNumber - 1) * pageSize - }}) {Page {TotalCount ItemCount} Results {Tag}}}`, + }}${filterParam}) {Page {TotalCount ItemCount} Results {Tag}}}`; + }, dependsOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) => `/v2/_zot/ext/search?query={BaseImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${ (pageNumber - 1) * pageSize diff --git a/src/components/Shared/VulnerabilityCard.jsx b/src/components/Shared/VulnerabilityCard.jsx index 4a921516..0ee3b7cd 100644 --- a/src/components/Shared/VulnerabilityCard.jsx +++ b/src/components/Shared/VulnerabilityCard.jsx @@ -72,7 +72,7 @@ const useStyles = makeStyles((theme) => ({ })); function VulnerabilitiyCard(props) { const classes = useStyles(); - const { cve, name } = props; + const { cve, name, platform } = props; const [openDesc, setOpenDesc] = useState(false); const [openFixed, setOpenFixed] = useState(false); const [loadingFixed, setLoadingFixed] = useState(true); @@ -90,7 +90,12 @@ function VulnerabilitiyCard(props) { setLoadingFixed(true); api .get( - `${host()}${endpoints.imageListWithCVEFixed(cve.id, name, { pageNumber, pageSize: CVE_FIXEDIN_PAGE_SIZE })}`, + `${host()}${endpoints.imageListWithCVEFixed( + cve.id, + name, + { pageNumber, pageSize: CVE_FIXEDIN_PAGE_SIZE }, + platform ? { Os: platform.Os, Arch: platform.Arch } : {} + )}`, abortController.signal ) .then((response) => { diff --git a/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx b/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx index bddb6a1b..aa05ec86 100644 --- a/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx +++ b/src/components/Tag/Tabs/VulnerabilitiesDetails.jsx @@ -73,7 +73,7 @@ function VulnerabilitiesDetails(props) { const [cveData, setCveData] = useState([]); const [isLoading, setIsLoading] = useState(true); const abortController = useMemo(() => new AbortController(), []); - const { name, tag } = props; + const { name, tag, digest, platform } = props; // pagination props const [cveFilter, setCveFilter] = useState(''); @@ -81,11 +81,15 @@ function VulnerabilitiesDetails(props) { const [isEndOfList, setIsEndOfList] = useState(false); const listBottom = useRef(null); + const getCVERequestName = () => { + return digest !== '' ? `${name}@${digest}` : `${name}:${tag}`; + }; + const getPaginatedCVEs = () => { api .get( `${host()}${endpoints.vulnerabilitiesForRepo( - `${name}:${tag}`, + getCVERequestName(), { pageNumber, pageSize: EXPLORE_PAGE_SIZE }, cveFilter )}`, @@ -171,7 +175,7 @@ function VulnerabilitiesDetails(props) { const renderCVEs = () => { return !isEmpty(cveData) ? ( cveData.map((cve, index) => { - return <VulnerabilitiyCard key={index} cve={cve} name={name} />; + return <VulnerabilitiyCard key={index} cve={cve} name={name} platform={platform} />; }) ) : ( <div>{!isLoading && <Typography className={classes.none}> No Vulnerabilities </Typography>}</div> diff --git a/src/components/Tag/TagDetails.jsx b/src/components/Tag/TagDetails.jsx index a43fb91a..a9cf0c6a 100644 --- a/src/components/Tag/TagDetails.jsx +++ b/src/components/Tag/TagDetails.jsx @@ -59,7 +59,6 @@ const useStyles = makeStyles((theme) => ({ fontSize: '1rem', lineHeight: '1.5rem', color: '#52637A', - padding: '1rem 0 0 0', maxWidth: '100%', [theme.breakpoints.down('md')]: { padding: '0.5rem 0 0 0', @@ -209,7 +208,14 @@ function TagDetails() { case 'IsDependentOn': return <IsDependentOn name={imageDetailData.name} digest={selectedManifest.digest} />; case 'Vulnerabilities': - return <VulnerabilitiesDetails name={reponame} tag={tag} />; + return ( + <VulnerabilitiesDetails + name={reponame} + tag={tag} + digest={selectedManifest?.digest} + platform={selectedManifest.platform} + /> + ); case 'ReferredBy': return <ReferredBy referrers={imageDetailData.referrers} />; default: @@ -227,10 +233,10 @@ function TagDetails() { <Card className={classes.cardRoot}> <CardContent className={classes.cardContent}> <Grid container> - <Grid item xs={12} md={8} className={classes.header}> + <Grid item xs={12} md={9} className={classes.header}> <Stack alignItems="center" - sx={{ width: { xs: '100%', md: 'auto' } }} + sx={{ width: { xs: '100%', md: 'auto' }, marginBottom: '1rem' }} direction={{ xs: 'column', md: 'row' }} spacing={1} > @@ -256,30 +262,29 @@ function TagDetails() { /> <SignatureIconCheck isSigned={imageDetailData.isSigned} /> </Stack> - - <Stack sx={{ width: { xs: '100%', md: 'auto' } }}> - <FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small"> - <InputLabel>OS/Arch</InputLabel> - {!isEmpty(selectedManifest) && ( - <Select - label="OS/Arch" - value={selectedManifest} - onChange={handleOSArchChange} - MenuProps={{ disableScrollLock: true }} - > - {imageDetailData.manifests.map((el) => ( - <MenuItem key={el.digest} value={el}> - {`${el.platform?.Os}/${el.platform?.Arch}`} - </MenuItem> - ))} - </Select> - )} - </FormControl> - </Stack> </Stack> - <Typography gutterBottom className={classes.digest}> - Digest: {selectedManifest?.digest} - </Typography> + <Stack direction="row" alignItems="center" spacing="1rem"> + <FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small"> + <InputLabel>OS/Arch</InputLabel> + {!isEmpty(selectedManifest) && ( + <Select + label="OS/Arch" + value={selectedManifest} + onChange={handleOSArchChange} + MenuProps={{ disableScrollLock: true }} + > + {imageDetailData.manifests.map((el) => ( + <MenuItem key={el.digest} value={el}> + {`${el.platform?.Os}/${el.platform?.Arch}`} + </MenuItem> + ))} + </Select> + )} + </FormControl> + <Typography gutterBottom className={classes.digest}> + Digest: {selectedManifest?.digest} + </Typography> + </Stack> </Grid> </Grid> </CardContent>