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,6 +205,10 @@ function TagDetails() {
}; };
return ( return (
<>
{isLoading ? (
<Loading />
) : (
<div className={classes.pageWrapper}> <div className={classes.pageWrapper}>
<Card className={classes.cardRoot}> <Card className={classes.cardRoot}>
<CardContent> <CardContent>
@ -218,7 +221,11 @@ function TagDetails() {
img: classes.avatar img: classes.avatar
}} }}
component="img" component="img"
image={randomImage()} image={
// @ts-ignore
// eslint-disable-next-line prettier/prettier
!isEmpty(imageDetailData?.logo) ? `data:image/ png;base64, ${imageDetailData?.logo}` : randomImage()
}
alt="icon" alt="icon"
/> />
<Typography variant="h3" className={classes.repoName}> <Typography variant="h3" className={classes.repoName}>
@ -241,10 +248,13 @@ function TagDetails() {
} }
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4} justifyContent="flex-start">
<Stack direction="row"> <Stack direction="row">
<Grid item xs={10}> <Grid item xs={10}>
<Typography variant="body1" sx={{ color: '#52637A', fontSize: '1rem', paddingTop: '0.75rem' }}> <Typography
variant="body1"
sx={{ color: '#52637A', fontSize: '1rem', paddingTop: '0.75rem', textAlign: 'left' }}
>
Pull this image Pull this image
</Typography> </Typography>
</Grid> </Grid>
@ -258,7 +268,7 @@ function TagDetails() {
</IconButton> </IconButton>
</Grid> </Grid>
</Stack> </Stack>
<FormControl sx={{ m: 1, paddingLeft: '1.5rem' }} variant="outlined"> <FormControl sx={{ m: 1 }} variant="outlined">
<Select <Select
className={classes.inputForm} className={classes.inputForm}
value={pullString} value={pullString}
@ -289,7 +299,12 @@ function TagDetails() {
sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }} sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }}
> >
<Tab value="Layers" label="Layers" className={classes.tabContent} /> <Tab value="Layers" label="Layers" className={classes.tabContent} />
<Tab value="DependsOn" label="Uses" className={classes.tabContent} data-testid="dependencies-tab" /> <Tab
value="DependsOn"
label="Uses"
className={classes.tabContent}
data-testid="dependencies-tab"
/>
<Tab value="IsDependentOn" label="Used by" className={classes.tabContent} /> <Tab value="IsDependentOn" label="Used by" className={classes.tabContent} />
<Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent} /> <Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent} />
</TabList> </TabList>
@ -334,6 +349,8 @@ function TagDetails() {
</CardContent> </CardContent>
</Card> </Card>
</div> </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
}; };