feat: Implemented logo display

Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
Raul Kele 2022-10-18 13:41:27 +03:00
parent ac782c04d0
commit 2dc4a35c87
10 changed files with 176 additions and 199 deletions

View File

@ -102,7 +102,7 @@ describe('Tags details', () => {
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} }); jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {}); const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<TagDetails />); render(<TagDetails />);
await waitFor(() => expect(error).toBeCalledTimes(2)); await waitFor(() => expect(error).toBeCalledTimes(1));
}); });
it('should show tag details metadata', async () => { it('should show tag details metadata', async () => {
// @ts-ignore // @ts-ignore

View File

@ -63,11 +63,11 @@ const endpoints = {
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) => repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${ `/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
(pageNumber - 1) * pageSize (pageNumber - 1) * pageSize
}}){Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Description Licenses Title Source IsSigned Documentation History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}} Vendor Labels} DownloadCount}}`, }}){Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Description Licenses Logo Title Source IsSigned Documentation History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}} Vendor Labels} DownloadCount}}`,
detailedRepoInfo: (name) => detailedRepoInfo: (name) =>
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Tag LastUpdated Vendor Size Platform {Os Arch} } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName Layers {Size Digest} Digest Tag Title Documentation DownloadCount Source Description Licenses History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}}}}}}`, `/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Tag LastUpdated Vendor Size Platform {Os Arch} } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName Layers {Size Digest} Digest Tag Logo Title Documentation DownloadCount Source Description Licenses History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}}}}}}`,
detailedImageInfo: (name, tag) => detailedImageInfo: (name, tag) =>
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName Tag Digest LastUpdated Size ConfigDigest Platform {Os Arch} Vendor Licenses History {Layer {Size Digest Score} HistoryDescription {Created CreatedBy Author Comment EmptyLayer} }}}`, `/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName Tag Digest LastUpdated Size ConfigDigest Platform {Os Arch} Vendor Licenses Logo}}`,
vulnerabilitiesForRepo: (name) => vulnerabilitiesForRepo: (name) =>
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag, CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`, `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag, CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
layersDetailsForImage: (name) => layersDetailsForImage: (name) =>
@ -87,7 +87,7 @@ const endpoints = {
if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`; if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`;
filterParam += '}'; filterParam += '}';
if (Object.keys(filter).length === 0) filterParam = ''; if (Object.keys(filter).length === 0) filterParam = '';
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Repos {Name LastUpdated Size Platforms { Os Arch } NewestImage { Tag Description IsSigned Licenses Vendor Labels } DownloadCount}}}`; return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Repos {Name LastUpdated Size Platforms { Os Arch } NewestImage { Tag Description IsSigned Logo Licenses Vendor Labels } DownloadCount}}}`;
} }
}; };

View File

@ -114,6 +114,7 @@ function Explore() {
platforms={item.platforms} platforms={item.platforms}
key={index} key={index}
lastUpdated={item.lastUpdated} lastUpdated={item.lastUpdated}
logo={item.logo}
/> />
); );
}) })

View File

@ -44,7 +44,7 @@ function FilterCard(props) {
const filterRows = filters; const filterRows = filters;
return filterRows.map((filter, index) => { return filterRows.map((filter, index) => {
return ( return (
<Tooltip key={index} title={filter.tooltip ?? filter.label} placement="right"> <Tooltip key={index} title={filter.tooltip ?? filter.label} placement="top" arrow>
<FormControlLabel <FormControlLabel
componentsProps={{ typography: { variant: 'body2' } }} componentsProps={{ typography: { variant: 'body2' } }}
control={<Checkbox />} control={<Checkbox />}

View File

@ -96,7 +96,7 @@ function Home() {
homeData.slice(0, 4).map((item, index) => { homeData.slice(0, 4).map((item, index) => {
return ( return (
<Grid item xs={3} key={index}> <Grid item xs={3} key={index}>
<PreviewCard name={item.name} lastUpdated={item.lastUpdated} isSigned={item.isSigned} /> <PreviewCard name={item.name} lastUpdated={item.lastUpdated} isSigned={item.isSigned} logo={item.logo} />
</Grid> </Grid>
); );
}) })
@ -141,6 +141,7 @@ function Home() {
platforms={item.platforms} platforms={item.platforms}
key={index} key={index}
lastUpdated={item.lastUpdated} lastUpdated={item.lastUpdated}
logo={item.logo}
/> />
); );
}) })

View File

@ -12,6 +12,7 @@ import repocube4 from '../assets/repocube-4.png';
//icons //icons
import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined'; import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined';
import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined'; import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined';
import { isEmpty } from 'lodash';
//import GppMaybeOutlinedIcon from '@mui/icons-material/GppMaybeOutlined'; //import GppMaybeOutlinedIcon from '@mui/icons-material/GppMaybeOutlined';
// temporary utility to get image // temporary utility to get image
@ -73,7 +74,7 @@ const useStyles = makeStyles(() => ({
function PreviewCard(props) { function PreviewCard(props) {
const classes = useStyles(); const classes = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
const { name, isSigned } = props; const { name, isSigned, logo } = props;
const goToDetails = () => { const goToDetails = () => {
navigate(`/image/${encodeURIComponent(name)}`); navigate(`/image/${encodeURIComponent(name)}`);
@ -139,7 +140,7 @@ function PreviewCard(props) {
img: classes.avatar img: classes.avatar
}} }}
component="img" component="img"
image={randomImage()} image={!isEmpty(logo) ? `data:image/png;base64, ${logo}` : randomImage()}
alt="icon" alt="icon"
/> />
<Tooltip title={name} placement="top"> <Tooltip title={name} placement="top">

View File

@ -18,6 +18,7 @@ import repocube4 from '../assets/repocube-4.png';
import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined'; import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined';
import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined'; import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined';
import { Markdown } from 'utilities/MarkdowntojsxWrapper'; import { Markdown } from 'utilities/MarkdowntojsxWrapper';
import { isEmpty } from 'lodash';
// temporary utility to get image // temporary utility to get image
const randomIntFromInterval = (min, max) => { const randomIntFromInterval = (min, max) => {
@ -90,12 +91,7 @@ const useStyles = makeStyles(() => ({
function RepoCard(props) { function RepoCard(props) {
const classes = useStyles(); const classes = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
const { name, vendor, platforms, description, downloads, isSigned, lastUpdated, version } = props; const { name, vendor, platforms, description, downloads, isSigned, lastUpdated, version, logo } = props;
//function that returns a random element from an array
// function getRandom(list) {
// return list[Math.floor(Math.random() * list.length)];
// }
const goToDetails = () => { const goToDetails = () => {
navigate(`/image/${encodeURIComponent(name)}`); navigate(`/image/${encodeURIComponent(name)}`);
@ -197,7 +193,7 @@ function RepoCard(props) {
img: classes.avatar img: classes.avatar
}} }}
component="img" component="img"
image={randomImage()} image={!isEmpty(logo) ? `data:image/png;base64, ${logo}` : randomImage()}
alt="icon" alt="icon"
/> />
<Tooltip title={name} placement="top"> <Tooltip title={name} placement="top">

View File

@ -19,6 +19,7 @@ import repocube4 from '../assets/repocube-4.png';
import { TabContext, TabList, TabPanel } from '@mui/lab'; import { TabContext, TabList, TabPanel } from '@mui/lab';
import RepoDetailsMetadata from './RepoDetailsMetadata'; import RepoDetailsMetadata from './RepoDetailsMetadata';
import Loading from './Loading'; import Loading from './Loading';
import { isEmpty } from 'lodash';
// @ts-ignore // @ts-ignore
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
@ -141,12 +142,13 @@ function RepoDetails() {
platforms: repoInfo.Summary?.Platforms, platforms: repoInfo.Summary?.Platforms,
vendors: repoInfo.Summary?.Vendors, vendors: repoInfo.Summary?.Vendors,
newestTag: repoInfo.Summary?.NewestImage, newestTag: repoInfo.Summary?.NewestImage,
description: repoInfo.Summary?.NewestImage.Description, description: repoInfo.Summary?.NewestImage?.Description,
title: repoInfo.Summary?.NewestImage.Title, title: repoInfo.Summary?.NewestImage?.Title,
source: repoInfo.Summary?.NewestImage.Source, source: repoInfo.Summary?.NewestImage?.Source,
downloads: repoInfo.Summary?.NewestImage.DownloadCount, downloads: repoInfo.Summary?.NewestImage?.DownloadCount,
overview: repoInfo.Summary?.NewestImage.Documentation, overview: repoInfo.Summary?.NewestImage?.Documentation,
license: repoInfo.Summary?.NewestImage.Licenses license: repoInfo.Summary?.NewestImage?.Licenses,
logo: repoInfo.Summary?.NewestImage?.Logo
}; };
setRepoDetailData(imageData); setRepoDetailData(imageData);
setTags(imageData.images); setTags(imageData.images);
@ -163,19 +165,6 @@ function RepoDetails() {
abortController.abort(); abortController.abort();
}; };
}, [name]); }, [name]);
//function that returns a random element from an array
// function getRandom(list) {
// return list[Math.floor(Math.random() * list.length)];
// }
// 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" }} />}/>;
// const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
// const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
// return(getRandom(arrSignature));
// }
const platformChips = () => { const platformChips = () => {
// @ts-ignore // @ts-ignore
@ -234,22 +223,6 @@ function RepoDetails() {
); );
}; };
// 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>);
// };
return ( return (
<> <>
{isLoading ? ( {isLoading ? (
@ -267,7 +240,9 @@ function RepoDetails() {
img: classes.avatar img: classes.avatar
}} }}
component="img" component="img"
image={randomImage()} // @ts-ignore
// eslint-disable-next-line prettier/prettier
image={!isEmpty(repoDetailData?.logo) ? `data:image/png;base64, ${repoDetailData?.logo}` : randomImage()}
alt="icon" alt="icon"
/> />
<Typography variant="h3" className={classes.repoName}> <Typography variant="h3" className={classes.repoName}>
@ -304,12 +279,6 @@ function RepoDetails() {
> >
<Tab value="Overview" label="Overview" className={classes.tabContent} /> <Tab value="Overview" label="Overview" className={classes.tabContent} />
<Tab value="Tags" label="Tags" className={classes.tabContent} /> <Tab value="Tags" label="Tags" className={classes.tabContent} />
{/* <Tab value="Dependencies" label={`${dependencies || 0} Dependencies`} className={classes.tabContent}/>
<Tab value="Dependents" label={`${dependents || 0} Dependents`} className={classes.tabContent}/>
<Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent}/>
<Tab value="6" label="Tab 6" className={classes.tabContent}/>
<Tab value="7" label="Tab 7" className={classes.tabContent}/>
<Tab value="8" label="Tab 8" className={classes.tabContent}/> */}
</TabList> </TabList>
<Grid container> <Grid container>
<Grid item xs={12}> <Grid item xs={12}>
@ -319,15 +288,6 @@ function RepoDetails() {
<TabPanel value="Tags" className={classes.tabPanel}> <TabPanel value="Tags" className={classes.tabPanel}>
<Tags tags={tags} /> <Tags tags={tags} />
</TabPanel> </TabPanel>
{/* <TabPanel value="Dependencies" className={classes.tabPanel}>
{renderDependencies()}
</TabPanel>
<TabPanel value="Dependents" className={classes.tabPanel}>
{renderDependents()}
</TabPanel>
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
{renderVulnerabilities()}
</TabPanel> */}
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>

View File

@ -35,6 +35,8 @@ import VulnerabilitiesDetails from './VulnerabilitiesDetails';
import HistoryLayers from './HistoryLayers'; import HistoryLayers from './HistoryLayers';
import DependsOn from './DependsOn'; import DependsOn from './DependsOn';
import IsDependentOn from './IsDependentOn'; import IsDependentOn from './IsDependentOn';
import { isEmpty } from 'lodash';
import Loading from './Loading';
// @ts-ignore // @ts-ignore
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
@ -133,8 +135,7 @@ const randomImage = () => {
function TagDetails() { function TagDetails() {
const [imageDetailData, setImageDetailData] = useState({}); const [imageDetailData, setImageDetailData] = useState({});
// @ts-ignore const [isLoading, setIsLoading] = useState(true);
//const [isLoading, setIsLoading] = useState(false);
const [selectedTab, setSelectedTab] = useState('Layers'); const [selectedTab, setSelectedTab] = useState('Layers');
const abortController = useMemo(() => new AbortController(), []); const abortController = useMemo(() => new AbortController(), []);
@ -148,6 +149,7 @@ function TagDetails() {
// if same-page navigation because of tag update, following 2 lines help ux // if same-page navigation because of tag update, following 2 lines help ux
setSelectedTab('Layers'); setSelectedTab('Layers');
window?.scrollTo(0, 0); window?.scrollTo(0, 0);
setIsLoading(true);
api api
.get(`${host()}${endpoints.detailedImageInfo(name, tag)}`, abortController.signal) .get(`${host()}${endpoints.detailedImageInfo(name, tag)}`, abortController.signal)
.then((response) => { .then((response) => {
@ -162,11 +164,12 @@ function TagDetails() {
platform: imageInfo.Platform, platform: imageInfo.Platform,
vendor: imageInfo.Vendor, vendor: imageInfo.Vendor,
history: imageInfo.History, history: imageInfo.History,
license: imageInfo.Licenses license: imageInfo.Licenses,
logo: imageInfo.Logo
}; };
setImageDetailData(imageData); setImageDetailData(imageData);
setFullName(imageData.name + ':' + imageData.tag); setFullName(imageData.name + ':' + imageData.tag);
//setIsLoading(false); setIsLoading(false);
} }
}) })
.catch((e) => { .catch((e) => {
@ -177,10 +180,6 @@ function TagDetails() {
abortController.abort(); abortController.abort();
}; };
}, [name, tag]); }, [name, tag]);
//function that returns a random element from an array
// function getRandom(list) {
// return list[Math.floor(Math.random() * list.length)];
// }
// const signatureCheck = () => { // 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 unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
@ -206,134 +205,152 @@ function TagDetails() {
}; };
return ( return (
<div className={classes.pageWrapper}> <>
<Card className={classes.cardRoot}> {isLoading ? (
<CardContent> <Loading />
<Grid container className={classes.header}> ) : (
<Grid item xs={8}> <div className={classes.pageWrapper}>
<Stack alignItems="center" direction="row" spacing={2}> <Card className={classes.cardRoot}>
<CardMedia <CardContent>
classes={{ <Grid container className={classes.header}>
root: classes.media, <Grid item xs={8}>
img: classes.avatar <Stack alignItems="center" direction="row" spacing={2}>
}} <CardMedia
component="img" classes={{
image={randomImage()} root: classes.media,
alt="icon" img: classes.avatar
/> }}
<Typography variant="h3" className={classes.repoName}> component="img"
{name}:{tag} image={
</Typography> // @ts-ignore
{/* {vulnerabilityCheck()} // eslint-disable-next-line prettier/prettier
!isEmpty(imageDetailData?.logo) ? `data:image/ png;base64, ${imageDetailData?.logo}` : randomImage()
}
alt="icon"
/>
<Typography variant="h3" className={classes.repoName}>
{name}:{tag}
</Typography>
{/* {vulnerabilityCheck()}
{signatureCheck()} */} {signatureCheck()} */}
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */} {/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
</Stack> </Stack>
<Typography <Typography
pt={1} pt={1}
sx={{ fontSize: 16, lineHeight: '1.5rem', color: 'rgba(0, 0, 0, 0.6)', paddingLeft: '4rem' }} sx={{ fontSize: 16, lineHeight: '1.5rem', color: 'rgba(0, 0, 0, 0.6)', paddingLeft: '4rem' }}
gutterBottom gutterBottom
align="left" align="left"
> >
DIGEST:{' '} DIGEST:{' '}
{ {
// @ts-ignore // @ts-ignore
imageDetailData?.digest imageDetailData?.digest
} }
</Typography>
</Grid>
<Grid item xs={4}>
<Stack direction="row">
<Grid item xs={10}>
<Typography variant="body1" sx={{ color: '#52637A', fontSize: '1rem', paddingTop: '0.75rem' }}>
Pull this image
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={2}> <Grid item xs={4} justifyContent="flex-start">
<IconButton <Stack direction="row">
aria-label="copy" <Grid item xs={10}>
onClick={() => navigator.clipboard.writeText(pullString)} <Typography
data-testid="pullcopy-btn" variant="body1"
> sx={{ color: '#52637A', fontSize: '1rem', paddingTop: '0.75rem', textAlign: 'left' }}
<ContentCopyIcon /> >
</IconButton> Pull this image
</Grid> </Typography>
</Stack>
<FormControl sx={{ m: 1, paddingLeft: '1.5rem' }} variant="outlined">
<Select
className={classes.inputForm}
value={pullString}
onChange={handleSelectionChange}
inputProps={{ 'aria-label': 'Without label' }}
sx={{ m: 1, width: '20.625rem', borderRadius: '0.5rem', color: '#14191F', alignContent: 'left' }}
>
<MenuItem value={`docker pull ${hostRoot()}/${fullName}`}>
docker pull {hostRoot()}/{fullName}
</MenuItem>
<MenuItem value={`podman pull ${hostRoot()}/${fullName}`}>
podman pull {hostRoot()}/{fullName}
</MenuItem>
<MenuItem value={`skopeo copy docker://${hostRoot()}/${fullName}`}>
skopeo copy docker://{hostRoot()}/{fullName}
</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<Grid container>
<Grid item xs={8} className={classes.tabs}>
<TabContext value={selectedTab}>
<Box>
<TabList
onChange={handleTabChange}
TabIndicatorProps={{ className: classes.selectedTab }}
sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }}
>
<Tab value="Layers" label="Layers" className={classes.tabContent} />
<Tab value="DependsOn" label="Uses" className={classes.tabContent} data-testid="dependencies-tab" />
<Tab value="IsDependentOn" label="Used by" className={classes.tabContent} />
<Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent} />
</TabList>
<Grid container>
<Grid item xs={12}>
<TabPanel value="Layers" className={classes.tabPanel}>
<HistoryLayers
name={fullName}
history={
// @ts-ignore
imageDetailData.history
}
/>
</TabPanel>
<TabPanel value="DependsOn" className={classes.tabPanel}>
<DependsOn name={fullName} />
</TabPanel>
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
<IsDependentOn name={fullName} />
</TabPanel>
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
<VulnerabilitiesDetails name={name} tag={tag} />
</TabPanel>
</Grid> </Grid>
</Grid> <Grid item xs={2}>
</Box> <IconButton
</TabContext> aria-label="copy"
</Grid> onClick={() => navigator.clipboard.writeText(pullString)}
<Grid item xs={4} className={classes.metadata}> data-testid="pullcopy-btn"
<TagDetailsMetadata >
// @ts-ignore <ContentCopyIcon />
platform={getPlatform()} </IconButton>
// @ts-ignore </Grid>
size={imageDetailData?.size} </Stack>
// @ts-ignore <FormControl sx={{ m: 1 }} variant="outlined">
lastUpdated={imageDetailData?.lastUpdated} <Select
// @ts-ignore className={classes.inputForm}
license={imageDetailData?.license} value={pullString}
/> onChange={handleSelectionChange}
</Grid> inputProps={{ 'aria-label': 'Without label' }}
</Grid> sx={{ m: 1, width: '20.625rem', borderRadius: '0.5rem', color: '#14191F', alignContent: 'left' }}
</CardContent> >
</Card> <MenuItem value={`docker pull ${hostRoot()}/${fullName}`}>
</div> docker pull {hostRoot()}/{fullName}
</MenuItem>
<MenuItem value={`podman pull ${hostRoot()}/${fullName}`}>
podman pull {hostRoot()}/{fullName}
</MenuItem>
<MenuItem value={`skopeo copy docker://${hostRoot()}/${fullName}`}>
skopeo copy docker://{hostRoot()}/{fullName}
</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<Grid container>
<Grid item xs={8} className={classes.tabs}>
<TabContext value={selectedTab}>
<Box>
<TabList
onChange={handleTabChange}
TabIndicatorProps={{ className: classes.selectedTab }}
sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }}
>
<Tab value="Layers" label="Layers" className={classes.tabContent} />
<Tab
value="DependsOn"
label="Uses"
className={classes.tabContent}
data-testid="dependencies-tab"
/>
<Tab value="IsDependentOn" label="Used by" className={classes.tabContent} />
<Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent} />
</TabList>
<Grid container>
<Grid item xs={12}>
<TabPanel value="Layers" className={classes.tabPanel}>
<HistoryLayers
name={fullName}
history={
// @ts-ignore
imageDetailData.history
}
/>
</TabPanel>
<TabPanel value="DependsOn" className={classes.tabPanel}>
<DependsOn name={fullName} />
</TabPanel>
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
<IsDependentOn name={fullName} />
</TabPanel>
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
<VulnerabilitiesDetails name={name} tag={tag} />
</TabPanel>
</Grid>
</Grid>
</Box>
</TabContext>
</Grid>
<Grid item xs={4} className={classes.metadata}>
<TagDetailsMetadata
// @ts-ignore
platform={getPlatform()}
// @ts-ignore
size={imageDetailData?.size}
// @ts-ignore
lastUpdated={imageDetailData?.lastUpdated}
// @ts-ignore
license={imageDetailData?.license}
/>
</Grid>
</Grid>
</CardContent>
</Card>
</div>
)}
</>
); );
} }

View File

@ -9,6 +9,7 @@ const mapToRepo = (responseRepo) => {
licenses: responseRepo.NewestImage?.Licenses, licenses: responseRepo.NewestImage?.Licenses,
size: responseRepo.Size, size: responseRepo.Size,
vendor: responseRepo.NewestImage?.Vendor, vendor: responseRepo.NewestImage?.Vendor,
logo: responseRepo.NewestImage?.Logo,
lastUpdated: responseRepo.LastUpdated, lastUpdated: responseRepo.LastUpdated,
downloads: responseRepo.DownloadCount downloads: responseRepo.DownloadCount
}; };