Cleaned up uses/used by tabs
Added license info to repodetails and tagdetails Added md parsing for relevant fields Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
c2f2cab6c0
commit
77162d1b95
18
package-lock.json
generated
18
package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"downshift": "^6.1.12",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^2.4.0",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"npm": "^8.11.0",
|
||||
"nth-check": "^2.0.1",
|
||||
"react": "^17.0.2",
|
||||
@ -12974,6 +12975,17 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-to-jsx": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
|
||||
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
@ -30397,6 +30409,12 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"markdown-to-jsx": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
|
||||
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
|
||||
"requires": {}
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"downshift": "^6.1.12",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^2.4.0",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"npm": "^8.11.0",
|
||||
"nth-check": "^2.0.1",
|
||||
"react": "^17.0.2",
|
||||
|
@ -8,16 +8,20 @@ const mockDependenciesList = {
|
||||
data: {
|
||||
BaseImageList: [
|
||||
{
|
||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64'
|
||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||
Tag: 'tag1'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag2'
|
||||
RepoName: 'tag2',
|
||||
Tag: 'tag2'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag3'
|
||||
RepoName: 'tag3',
|
||||
Tag: 'tag3'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag4'
|
||||
RepoName: 'tag4',
|
||||
Tag: 'tag4'
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -51,7 +55,7 @@ describe('Dependencies tab', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockDependenciesList });
|
||||
render(<RouterDependsWrapper />);
|
||||
expect(await screen.findAllByText(/published/i)).toHaveLength(4);
|
||||
expect(await screen.findAllByText(/Tag/i)).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('renders no dependencies if there are not any', async () => {
|
||||
|
@ -8,16 +8,20 @@ const mockDependentsList = {
|
||||
data: {
|
||||
DerivedImageList: [
|
||||
{
|
||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64'
|
||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||
Tag: 'tag1'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag2'
|
||||
RepoName: 'tag2',
|
||||
Tag: 'tag2'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag3'
|
||||
RepoName: 'tag3',
|
||||
Tag: 'tag3'
|
||||
},
|
||||
{
|
||||
RepoName: 'tag4'
|
||||
RepoName: 'tag4',
|
||||
Tag: 'tag4'
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -51,7 +55,7 @@ describe('Dependents tab', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockDependentsList });
|
||||
render(<RouterDependsWrapper />);
|
||||
expect(await screen.findAllByText(/published/i)).toHaveLength(4);
|
||||
expect(await screen.findAllByText(/tag/i)).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('renders no dependents if there are not any', async () => {
|
||||
|
@ -65,9 +65,9 @@ const endpoints = {
|
||||
(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}}`,
|
||||
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 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 Title Documentation DownloadCount Source Description Licenses 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} }}}`,
|
||||
`/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} }}}`,
|
||||
vulnerabilitiesForRepo: (name) =>
|
||||
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag, CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
|
||||
layersDetailsForImage: (name) =>
|
||||
@ -75,9 +75,9 @@ const endpoints = {
|
||||
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 Tag Description Vendor DownloadCount LastUpdated Platform {Os Arch} IsSigned}}`,
|
||||
`/v2/_zot/ext/search?query={BaseImageList(image: "${name}"){RepoName Tag Description Digest Vendor DownloadCount LastUpdated Size Platform {Os Arch} IsSigned}}`,
|
||||
isDependentOnForImage: (name) =>
|
||||
`/v2/_zot/ext/search?query={DerivedImageList(image: "${name}"){RepoName Tag Description Vendor DownloadCount LastUpdated Platform {Os Arch} IsSigned}}`,
|
||||
`/v2/_zot/ext/search?query={DerivedImageList(image: "${name}"){RepoName Tag Description Digest Vendor DownloadCount LastUpdated Size Platform {Os Arch} IsSigned}}`,
|
||||
globalSearch: ({ searchQuery = '""', pageNumber = 1, pageSize = 15, filter = {} }) => {
|
||||
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
|
||||
const paginationParam = `requestedPage: {limit:${pageSize} offset:${(pageNumber - 1) * pageSize}}`;
|
||||
|
@ -8,7 +8,7 @@ import { Divider, Typography, Stack } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../host';
|
||||
import Loading from './Loading';
|
||||
import RepoCard from './RepoCard';
|
||||
import TagCard from './TagCard';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
@ -96,14 +96,14 @@ function DependsOn(props) {
|
||||
return images?.length ? (
|
||||
images.map((dependence, index) => {
|
||||
return (
|
||||
<RepoCard
|
||||
name={dependence.RepoName}
|
||||
version={dependence.Tag}
|
||||
description={dependence.Description}
|
||||
<TagCard
|
||||
repoName={dependence.RepoName}
|
||||
tag={dependence.Tag}
|
||||
vendor={dependence.Vendor}
|
||||
downloads={dependence.DownloadCount}
|
||||
platforms={[dependence.Platform]}
|
||||
platform={dependence.Platform}
|
||||
isSigned={dependence.IsSigned}
|
||||
size={dependence.Size}
|
||||
digest={dependence.Digest}
|
||||
key={index}
|
||||
lastUpdated={dependence.LastUpdated}
|
||||
/>
|
||||
|
@ -8,7 +8,7 @@ import { Divider, Typography, Stack } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../host';
|
||||
import Loading from './Loading';
|
||||
import RepoCard from './RepoCard';
|
||||
import TagCard from './TagCard';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
@ -96,14 +96,14 @@ function IsDependentOn(props) {
|
||||
return images?.length ? (
|
||||
images.map((dependence, index) => {
|
||||
return (
|
||||
<RepoCard
|
||||
name={dependence.RepoName}
|
||||
version={dependence.Tag}
|
||||
description={dependence.Description}
|
||||
<TagCard
|
||||
repoName={dependence.RepoName}
|
||||
tag={dependence.Tag}
|
||||
vendor={dependence.Vendor}
|
||||
downloads={dependence.DownloadCount}
|
||||
platform={dependence.Platform}
|
||||
isSigned={dependence.IsSigned}
|
||||
platforms={[dependence.Platform]}
|
||||
size={dependence.Size}
|
||||
digest={dependence.Digest}
|
||||
key={index}
|
||||
lastUpdated={dependence.LastUpdated}
|
||||
/>
|
||||
|
@ -17,6 +17,7 @@ import repocube4 from '../assets/repocube-4.png';
|
||||
//icons
|
||||
import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined';
|
||||
import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
|
||||
// temporary utility to get image
|
||||
const randomIntFromInterval = (min, max) => {
|
||||
@ -219,7 +220,7 @@ function RepoCard(props) {
|
||||
<Stack alignItems="center" direction="row" spacing={1} pt={2}>
|
||||
<Tooltip title={getVendor()} placement="top">
|
||||
<Typography className={classes.vendor} variant="body2" noWrap>
|
||||
{getVendor()}
|
||||
{<Markdown options={{ forceInline: true }}>{getVendor()}</Markdown>}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title={getVersion()} placement="top">
|
||||
|
@ -145,7 +145,8 @@ function RepoDetails() {
|
||||
title: repoInfo.Summary?.NewestImage.Title,
|
||||
source: repoInfo.Summary?.NewestImage.Source,
|
||||
downloads: repoInfo.Summary?.NewestImage.DownloadCount,
|
||||
overview: repoInfo.Summary?.NewestImage.Documentation
|
||||
overview: repoInfo.Summary?.NewestImage.Documentation,
|
||||
license: repoInfo.Summary?.NewestImage.Licenses
|
||||
};
|
||||
setRepoDetailData(imageData);
|
||||
setTags(imageData.images);
|
||||
@ -344,6 +345,8 @@ function RepoDetails() {
|
||||
size={repoDetailData?.size}
|
||||
// @ts-ignore
|
||||
latestTag={repoDetailData?.newestTag}
|
||||
// @ts-ignore
|
||||
license={repoDetailData?.license}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Card, CardContent, Grid, Typography, Tooltip } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { DateTime } from 'luxon';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import React from 'react';
|
||||
import transform from '../utilities/transform';
|
||||
|
||||
@ -35,7 +36,7 @@ const useStyles = makeStyles(() => ({
|
||||
|
||||
function RepoDetailsMetadata(props) {
|
||||
const classes = useStyles();
|
||||
const { repoURL, totalDownloads, lastUpdated, size } = props;
|
||||
const { repoURL, totalDownloads, lastUpdated, size, license } = props;
|
||||
// @ts-ignore
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
|
||||
unit: ['weeks', 'days', 'hours', 'minutes']
|
||||
@ -94,6 +95,22 @@ function RepoDetailsMetadata(props) {
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>
|
||||
License
|
||||
</Typography>
|
||||
<Tooltip title={license || ' '} placement="top">
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>
|
||||
{license ? <Markdown>{license}</Markdown> : `License info not available`}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* <Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
|
164
src/components/TagCard.jsx
Normal file
164
src/components/TagCard.jsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Collapse,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
tableCellClasses,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import transform from 'utilities/transform';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tagCard: {
|
||||
marginBottom: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: 'none!important',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%'
|
||||
},
|
||||
card: {
|
||||
marginBottom: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
clickCursor: {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function TagCard(props) {
|
||||
const { repoName, tag, lastUpdated, vendor, digest, size, platform } = props;
|
||||
|
||||
//const tags = data && data.tags;
|
||||
const [open, setOpen] = useState(false);
|
||||
const classes = useStyles();
|
||||
// @ts-ignore
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
|
||||
unit: ['weeks', 'days', 'hours', 'minutes']
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goToTags = (tag) => {
|
||||
navigate(`tag/${tag}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="body1" align="left" sx={{ color: '#828282', fontSize: '1rem', paddingBottom: '0.5rem' }}>
|
||||
Tag
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
align="left"
|
||||
sx={{ color: '#1479FF', fontSize: '1rem', textDecorationLine: 'underline', cursor: 'pointer' }}
|
||||
onClick={() => goToTags(tag)}
|
||||
>
|
||||
{repoName && `${repoName}:`}
|
||||
{tag}
|
||||
</Typography>
|
||||
|
||||
<Stack sx={{ display: 'inline' }} direction="row" spacing={0.5}>
|
||||
<Typography variant="caption" sx={{ fontWeight: '400', fontSize: '0.8125rem' }}>
|
||||
Pushed
|
||||
</Typography>
|
||||
<Tooltip title={lastUpdated?.slice(0, 16) || ' '} placement="top">
|
||||
<Typography variant="caption" sx={{ fontWeight: '600', fontSize: '0.8125rem' }}>
|
||||
{lastDate || 'Date not available'} by{' '}
|
||||
<Markdown options={{ forceInline: true }}>{vendor || 'Vendor not available'}</Markdown>
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#1479FF',
|
||||
paddingTop: '1rem',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{!open ? 'See digest' : 'Hide digest'}
|
||||
</Typography>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Table size="small" padding="none" sx={{ [`& .${tableCellClasses.root}`]: { borderBottom: 'none' } }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">Digest</Typography>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">OS/ARCH</Typography>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">Size</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
key={digest}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(digest);
|
||||
}}
|
||||
className={classes.clickCursor}
|
||||
>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Tooltip title={digest || ''} placement="right">
|
||||
<Typography variant="body1">{digest?.substr(0, 12)}</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">
|
||||
{platform?.Os}/{platform?.Arch}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">{transform.formatBytes(size)}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -161,7 +161,8 @@ function TagDetails() {
|
||||
digest: imageInfo.ConfigDigest,
|
||||
platform: imageInfo.Platform,
|
||||
vendor: imageInfo.Vendor,
|
||||
history: imageInfo.History
|
||||
history: imageInfo.History,
|
||||
license: imageInfo.Licenses
|
||||
};
|
||||
setImageDetailData(imageData);
|
||||
setFullName(imageData.name + ':' + imageData.tag);
|
||||
@ -325,6 +326,8 @@ function TagDetails() {
|
||||
size={imageDetailData?.size}
|
||||
// @ts-ignore
|
||||
lastUpdated={imageDetailData?.lastUpdated}
|
||||
// @ts-ignore
|
||||
license={imageDetailData?.license}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Card, CardContent, Grid, Typography, Tooltip } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { DateTime } from 'luxon';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import React from 'react';
|
||||
import transform from '../utilities/transform';
|
||||
|
||||
@ -35,7 +36,7 @@ const useStyles = makeStyles(() => ({
|
||||
|
||||
function TagDetailsMetadata(props) {
|
||||
const classes = useStyles();
|
||||
const { platform, lastUpdated, size } = props;
|
||||
const { platform, lastUpdated, size, license } = props;
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
|
||||
unit: ['weeks', 'days', 'hours', 'minutes']
|
||||
});
|
||||
@ -81,6 +82,22 @@ function TagDetailsMetadata(props) {
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>
|
||||
License
|
||||
</Typography>
|
||||
<Tooltip title={license || ' '} placement="top">
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>
|
||||
{license ? <Markdown>{license}</Markdown> : `License info not available`}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
// react global
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
// components
|
||||
import Box from '@mui/material/Box';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import transform from 'utilities/transform';
|
||||
import { Card, CardContent, Divider, Stack, Tooltip } from '@mui/material';
|
||||
import { Card, CardContent, Divider } from '@mui/material';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import TagCard from './TagCard';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tagCard: {
|
||||
@ -56,139 +47,28 @@ const useStyles = makeStyles(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function TagCard(props) {
|
||||
const { tag, lastUpdated, vendors, digest, size, platform } = props;
|
||||
|
||||
//const tags = data && data.tags;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const classes = useStyles();
|
||||
// @ts-ignore
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
|
||||
unit: ['weeks', 'days', 'hours', 'minutes']
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goToTags = (tag) => {
|
||||
navigate(`tag/${tag}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="body1" align="left" sx={{ color: '#828282', fontSize: '1rem', paddingBottom: '0.5rem' }}>
|
||||
Tag
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
align="left"
|
||||
sx={{ color: '#1479FF', fontSize: '1rem', textDecorationLine: 'underline', cursor: 'pointer' }}
|
||||
onClick={() => goToTags(tag)}
|
||||
>
|
||||
{tag}
|
||||
</Typography>
|
||||
|
||||
<Stack sx={{ display: 'inline' }} direction="row" spacing={0.5}>
|
||||
<Typography variant="caption" sx={{ fontWeight: '400', fontSize: '0.8125rem' }}>
|
||||
Last pushed
|
||||
</Typography>
|
||||
<Tooltip title={lastUpdated?.slice(0, 16) || ' '} placement="top">
|
||||
<Typography variant="caption" sx={{ fontWeight: '600', fontSize: '0.8125rem' }}>
|
||||
{lastDate || 'Date not available'} by {vendors || 'Vendor not available'}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#1479FF',
|
||||
paddingTop: '1rem',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{!open ? 'See digest' : 'Hide digest'}
|
||||
</Typography>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Table size="small" padding="none" sx={{ [`& .${tableCellClasses.root}`]: { borderBottom: 'none' } }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">Digest</Typography>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">OS/ARCH</Typography>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">Size</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
key={digest}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(digest);
|
||||
}}
|
||||
className={classes.clickCursor}
|
||||
>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">{digest?.substr(0, 12)}</Typography>
|
||||
</TableCell>
|
||||
<TableCell style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">
|
||||
{platform.Os}/{platform.Arch}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row" style={{ color: '#696969' }}>
|
||||
<Typography variant="body1">{transform.formatBytes(size)}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// TagCard.propTypes = {
|
||||
// row: PropTypes.shape({
|
||||
// Layers: PropTypes.arrayOf(
|
||||
// PropTypes.shape({
|
||||
// Digest: PropTypes.string.isRequired,
|
||||
// Size: PropTypes.string.isRequired,
|
||||
// }),
|
||||
// ).isRequired,
|
||||
// Tag: PropTypes.string.isRequired,
|
||||
// }).isRequired,
|
||||
// };
|
||||
|
||||
const renderTags = (tags) => {
|
||||
const cmp =
|
||||
tags &&
|
||||
tags.map((tag) => {
|
||||
return (
|
||||
<TagCard
|
||||
key={tag.Tag}
|
||||
tag={tag.Tag}
|
||||
lastUpdated={tag.LastUpdated}
|
||||
digest={tag.Digest}
|
||||
vendors={tag.Vendor}
|
||||
size={tag.Size}
|
||||
platform={tag.Platform}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return cmp;
|
||||
};
|
||||
|
||||
export default function Tags(props) {
|
||||
const classes = useStyles();
|
||||
const { tags } = props;
|
||||
const renderTags = (tags) => {
|
||||
const cmp =
|
||||
tags &&
|
||||
tags.map((tag) => {
|
||||
return (
|
||||
<TagCard
|
||||
key={tag.Tag}
|
||||
tag={tag.Tag}
|
||||
lastUpdated={tag.LastUpdated}
|
||||
digest={tag.Digest}
|
||||
vendor={tag.Vendor}
|
||||
size={tag.Size}
|
||||
platform={tag.Platform}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return cmp;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.tagCard} data-testid="tags-container">
|
||||
<CardContent className={classes.content}>
|
||||
|
Loading…
Reference in New Issue
Block a user