Compare commits
7 Commits
commit-087
...
commit-19e
Author | SHA1 | Date | |
---|---|---|---|
19e366ee1f | |||
b41fb2f841 | |||
b787273b84 | |||
9ecd46e4d0 | |||
845726cd08 | |||
ac84c375c0 | |||
96008d67be |
13
.github/workflows/end-to-end-test.yml
vendored
13
.github/workflows/end-to-end-test.yml
vendored
@ -23,6 +23,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Cleanup disk space
|
||||
run: |
|
||||
# To free up ~15 GB of disk space
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/share/boost
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
|
||||
- name: Checkout zui repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@ -116,6 +124,11 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE
|
||||
make playwright-browsers
|
||||
|
||||
- name: Trigger CVE scanning
|
||||
run: |
|
||||
# trigger CVE scanning for all images before running the tests
|
||||
curl -X POST -H "Content-Type: application/json" -m 600 --data '{ "query": "{ ImageListForCVE (id:\"CVE-2021-43616\") { Results { RepoName Tag } } }" }' http://$REGISTRY_HOST:$REGISTRY_PORT/v2/_zot/ext/search
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
|
@ -22,6 +22,29 @@ const mockImage = {
|
||||
vendor: '',
|
||||
size: '585',
|
||||
tags: '',
|
||||
isSigned: true,
|
||||
signatureInfo: [
|
||||
{
|
||||
Tool: 'cosign',
|
||||
IsTrusted: false,
|
||||
Author: ''
|
||||
},
|
||||
{
|
||||
Tool: 'cosign',
|
||||
IsTrusted: false,
|
||||
Author: ''
|
||||
},
|
||||
{
|
||||
Tool: 'cosign',
|
||||
IsTrusted: false,
|
||||
Author: ''
|
||||
},
|
||||
{
|
||||
Tool: 'cosign',
|
||||
IsTrusted: false,
|
||||
Author: ''
|
||||
}
|
||||
],
|
||||
platforms: [{ Os: 'linux', Arch: 'amd64' }]
|
||||
};
|
||||
|
||||
@ -34,6 +57,8 @@ const RepoCardWrapper = (props) => {
|
||||
version={image.latestVersion}
|
||||
description={image.description}
|
||||
vendor={image.vendor}
|
||||
isSigned={image.isSigned}
|
||||
signatureInfo={image.signatureInfo}
|
||||
key={1}
|
||||
lastUpdated={image.lastUpdated}
|
||||
platforms={image.platforms}
|
||||
|
12
src/api.js
12
src/api.js
@ -84,11 +84,11 @@ const endpoints = {
|
||||
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
|
||||
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned Documentation Vendor Labels} IsStarred IsBookmarked DownloadCount}}}`,
|
||||
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned SignatureInfo { Tool IsTrusted Author } Documentation Vendor Labels} IsStarred IsBookmarked DownloadCount}}}`,
|
||||
detailedRepoInfo: (name) =>
|
||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
|
||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
|
||||
detailedImageInfo: (name, tag) =>
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned Vulnerabilities {MaxSeverity Count} Referrers {MediaType ArtifactType Size Digest Annotations{Key Value}} Tag Manifests {History {Layer {Size Digest} HistoryDescription {CreatedBy EmptyLayer}} Digest ConfigDigest LastUpdated Size Platform {Os Arch}} Vendor Licenses }}`,
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Referrers {MediaType ArtifactType Size Digest Annotations{Key Value}} Tag Manifests {History {Layer {Size Digest} HistoryDescription {CreatedBy EmptyLayer}} Digest ConfigDigest LastUpdated Size Platform {Os Arch}} Vendor Licenses }}`,
|
||||
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }, searchTerm = '') => {
|
||||
let query = `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
@ -113,11 +113,11 @@ const endpoints = {
|
||||
dependsOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
||||
`/v2/_zot/ext/search?query={BaseImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}){Page {TotalCount ItemCount} Results { RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
||||
}}){Page {TotalCount ItemCount} Results { RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count}}}}`,
|
||||
isDependentOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
||||
`/v2/_zot/ext/search?query={DerivedImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}){Page {TotalCount ItemCount} Results {RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
||||
}}){Page {TotalCount ItemCount} Results {RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count}}}}`,
|
||||
globalSearch: ({
|
||||
searchQuery = '""',
|
||||
pageNumber = 1,
|
||||
@ -136,7 +136,7 @@ const endpoints = {
|
||||
if (filter.IsBookmarked) filterParam += ` IsBookmarked: ${filter.IsBookmarked}`;
|
||||
filterParam += '}';
|
||||
if (Object.keys(filter).length === 0) filterParam = '';
|
||||
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned Licenses Vendor Labels } DownloadCount}}}`;
|
||||
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned SignatureInfo { Tool IsTrusted Author } Licenses Vendor Labels } DownloadCount}}}`;
|
||||
},
|
||||
imageSuggestions: ({ searchQuery = '""', pageNumber = 1, pageSize = 15 }) => {
|
||||
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
|
||||
|
15
src/assets/noData.svg
Normal file
15
src/assets/noData.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="235" height="240" viewBox="0 0 235 240" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="34.8711" y="16.2017" width="36" height="249.944" rx="18" transform="rotate(-26.7465 34.8711 16.2017)" fill="#F15527" fill-opacity="0.2"/>
|
||||
<rect x="156.871" y="36.2017" width="36" height="100.235" rx="18" transform="rotate(-26.7465 156.871 36.2017)" fill="#F15527" fill-opacity="0.2"/>
|
||||
<rect x="0.871094" y="138.037" width="26.745" height="74.4663" rx="13.3725" transform="rotate(-26.7465 0.871094 138.037)" fill="#F15527" fill-opacity="0.2"/>
|
||||
<g clip-path="url(#clip0_2865_33046)">
|
||||
<path d="M117.5 199C161.225 199 197 163.225 197 119.5C197 75.775 161.225 40 117.5 40C73.775 40 38 75.775 38 119.5C38 163.225 73.775 199 117.5 199ZM119.885 82.0555C121.475 80.6245 123.383 79.75 125.45 79.75C127.596 79.75 129.425 80.6245 131.094 82.0555C132.605 83.725 133.4 85.633 133.4 87.7C133.4 89.8465 132.605 91.675 131.094 93.3445C129.425 94.855 127.596 95.65 125.45 95.65C123.383 95.65 121.475 94.855 119.885 93.3445C118.375 91.675 117.5 89.8465 117.5 87.7C117.5 85.633 118.375 83.725 119.885 82.0555ZM100.01 119.261C100.01 119.261 117.261 105.588 123.542 105.031C129.425 104.554 128.232 111.311 127.676 114.809L127.597 115.286C126.484 119.5 125.132 124.588 123.78 129.438C120.759 140.488 117.818 151.3 118.534 153.287C119.328 155.991 124.257 152.572 127.835 150.187C128.312 149.869 128.709 149.551 129.107 149.312C129.107 149.312 129.743 148.676 130.379 149.551C130.538 149.789 130.697 150.028 130.856 150.187C131.572 151.3 131.969 151.697 131.015 152.333L130.697 152.492C128.948 153.685 121.475 158.932 118.454 160.84C115.194 162.987 102.713 170.142 104.621 156.229C106.291 146.451 108.517 138.023 110.266 131.425C113.525 119.5 114.956 114.094 107.642 118.785C104.701 120.534 102.951 121.646 101.918 122.362C101.043 122.998 100.964 122.998 100.408 121.965L100.169 121.487L99.7715 120.852C99.215 120.057 99.215 119.977 100.01 119.261Z" fill="#0F2139"/>
|
||||
<path d="M119.885 82.0555C121.475 80.6245 123.383 79.75 125.45 79.75C127.596 79.75 129.425 80.6245 131.094 82.0555C132.605 83.725 133.4 85.633 133.4 87.7C133.4 89.8465 132.605 91.675 131.094 93.3445C129.425 94.855 127.596 95.65 125.45 95.65C123.383 95.65 121.475 94.855 119.885 93.3445C118.375 91.675 117.5 89.8465 117.5 87.7C117.5 85.633 118.375 83.725 119.885 82.0555Z" fill="white"/>
|
||||
<path d="M100.01 119.261C100.01 119.261 117.261 105.588 123.542 105.031C129.425 104.554 128.232 111.311 127.676 114.809L127.597 115.286C126.484 119.5 125.132 124.588 123.78 129.438C120.759 140.488 117.818 151.3 118.534 153.287C119.328 155.991 124.257 152.572 127.835 150.187C128.312 149.869 128.709 149.551 129.107 149.312C129.107 149.312 129.743 148.676 130.379 149.551C130.538 149.789 130.697 150.028 130.856 150.187C131.572 151.3 131.969 151.697 131.015 152.333L130.697 152.492C128.948 153.685 121.475 158.932 118.454 160.84C115.194 162.987 102.713 170.142 104.621 156.229C106.291 146.451 108.517 138.023 110.266 131.425C113.525 119.5 114.956 114.094 107.642 118.785C104.701 120.534 102.951 121.646 101.918 122.362C101.043 122.998 100.964 122.998 100.408 121.965L100.169 121.487L99.7715 120.852C99.215 120.057 99.215 119.977 100.01 119.261Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2865_33046">
|
||||
<rect width="159" height="159" fill="white" transform="translate(38 40)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 1.7 KiB |
@ -221,6 +221,7 @@ function Explore({ searchInputValue }) {
|
||||
description={item.description}
|
||||
downloads={item.downloads}
|
||||
isSigned={item.isSigned}
|
||||
signatureInfo={item.signatureInfo}
|
||||
isBookmarked={item.isBookmarked}
|
||||
vendor={item.vendor}
|
||||
platforms={item.platforms}
|
||||
|
@ -10,6 +10,7 @@ import { useNavigate, createSearchParams } from 'react-router-dom';
|
||||
import { sortByCriteria } from 'utilities/sortCriteria';
|
||||
import { HOME_POPULAR_PAGE_SIZE, HOME_RECENT_PAGE_SIZE, HOME_BOOKMARKS_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
import { isEmpty } from 'lodash';
|
||||
import NoDataComponent from 'components/Shared/NoDataComponent';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
gridWrapper: {
|
||||
@ -199,6 +200,15 @@ function Home() {
|
||||
navigate({ pathname: `/explore`, search: createSearchParams({ [type]: value }).toString() });
|
||||
};
|
||||
|
||||
const isNoData = () =>
|
||||
!isLoading &&
|
||||
!isLoadingBookmarks &&
|
||||
!isLoadingPopular &&
|
||||
!isLoadingRecent &&
|
||||
bookmarkData.length === 0 &&
|
||||
popularData.length === 0 &&
|
||||
recentData.length === 0;
|
||||
|
||||
const renderCards = (cardArray) => {
|
||||
return (
|
||||
cardArray &&
|
||||
@ -210,6 +220,7 @@ function Home() {
|
||||
description={item.description}
|
||||
downloads={item.downloads}
|
||||
isSigned={item.isSigned}
|
||||
signatureInfo={item.signatureInfo}
|
||||
isBookmarked={item.isBookmarked}
|
||||
vendor={item.vendor}
|
||||
platforms={item.platforms}
|
||||
@ -226,68 +237,68 @@ function Home() {
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<Stack alignItems="center" className={classes.gridWrapper}>
|
||||
<Stack className={classes.sectionHeaderContainer} sx={{ paddingTop: '3rem' }}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Most popular images
|
||||
</Typography>
|
||||
</div>
|
||||
<div onClick={() => handleClickViewAll('sortby', sortByCriteria.downloads.value)}>
|
||||
<Typography variant="body2" className={classes.viewAll}>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{isLoadingPopular ? <Loading /> : renderCards(popularData)}
|
||||
{/* currently most popular will be by downloads until stars are implemented */}
|
||||
<Stack className={classes.sectionHeaderContainer}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Recently updated images
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.viewAll}
|
||||
onClick={() => handleClickViewAll('sortby', sortByCriteria.updateTime.value)}
|
||||
>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{isLoadingRecent ? <Loading /> : renderCards(recentData)}
|
||||
{!isEmpty(bookmarkData) && (
|
||||
<>
|
||||
<Stack className={classes.sectionHeaderContainer}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Bookmarks
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.viewAll}
|
||||
onClick={() => handleClickViewAll('filter', 'IsBookmarked')}
|
||||
>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{isLoadingBookmarks ? <Loading /> : renderCards(bookmarkData)}
|
||||
</>
|
||||
)}
|
||||
const renderContent = () => {
|
||||
return isNoData() === true ? (
|
||||
<NoDataComponent text="No images" />
|
||||
) : (
|
||||
<Stack alignItems="center" className={classes.gridWrapper}>
|
||||
<Stack className={classes.sectionHeaderContainer} sx={{ paddingTop: '3rem' }}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Most popular images
|
||||
</Typography>
|
||||
</div>
|
||||
<div onClick={() => handleClickViewAll('sortby', sortByCriteria.downloads.value)}>
|
||||
<Typography variant="body2" className={classes.viewAll}>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{isLoadingPopular ? <Loading /> : renderCards(popularData, isLoadingPopular)}
|
||||
{/* currently most popular will be by downloads until stars are implemented */}
|
||||
<Stack className={classes.sectionHeaderContainer}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Recently updated images
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.viewAll}
|
||||
onClick={() => handleClickViewAll('sortby', sortByCriteria.updateTime.value)}
|
||||
>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{isLoadingRecent ? <Loading /> : renderCards(recentData, isLoadingRecent)}
|
||||
{!isEmpty(bookmarkData) && (
|
||||
<>
|
||||
<Stack className={classes.sectionHeaderContainer}>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Bookmarks
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.viewAll}
|
||||
onClick={() => handleClickViewAll('filter', 'IsBookmarked')}
|
||||
>
|
||||
View all
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{isLoadingBookmarks ? <Loading /> : renderCards(bookmarkData, isLoadingBookmarks)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return <>{isLoading ? <Loading /> : renderContent()}</>;
|
||||
}
|
||||
|
||||
export default Home;
|
||||
|
@ -20,7 +20,7 @@ import Alert from '@mui/material/Alert';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Loading from '../Shared/Loading';
|
||||
|
||||
import { GoogleLoginButton, GithubLoginButton, DexLoginButton } from './ThirdPartyLoginComponents';
|
||||
import { GoogleLoginButton, GithubLoginButton, OIDCLoginButton } from './ThirdPartyLoginComponents';
|
||||
|
||||
// styling
|
||||
import { makeStyles } from '@mui/styles';
|
||||
@ -284,14 +284,15 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
|
||||
let isGoogle = isObject(authMethods.openid?.providers?.google);
|
||||
// let isGitlab = isObject(authMethods.openid?.providers?.gitlab);
|
||||
let isGithub = isObject(authMethods.openid?.providers?.github);
|
||||
let isDex = isObject(authMethods.openid?.providers?.dex);
|
||||
let isOIDC = isObject(authMethods.openid?.providers?.oidc);
|
||||
let oidcName = authMethods.openid?.providers?.oidc?.name;
|
||||
|
||||
return (
|
||||
<Stack direction="column" spacing="1rem" className={classes.thirdPartyLoginContainer}>
|
||||
{isGithub && <GithubLoginButton handleClick={handleClickExternalLogin} />}
|
||||
{isGoogle && <GoogleLoginButton handleClick={handleClickExternalLogin} />}
|
||||
{/* {isGitlab && <GitlabLoginButton handleClick={handleClickExternalLogin} />} */}
|
||||
{isDex && <DexLoginButton handleClick={handleClickExternalLogin} />}
|
||||
{isOIDC && <OIDCLoginButton handleClick={handleClickExternalLogin} oidcName={oidcName} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@ -308,7 +309,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
|
||||
Sign In
|
||||
</Typography>
|
||||
<Typography align="left" className={classes.subtext} variant="body1" gutterBottom>
|
||||
Welcome back! Please enter your details.
|
||||
Welcome back! Please login.
|
||||
</Typography>
|
||||
{renderThirdPartyLoginMethods()}
|
||||
{Object.keys(authMethods).length > 1 && <Divider className={classes.divider}>or</Divider>}
|
||||
|
@ -80,14 +80,15 @@ function GitlabLoginButton({ handleClick }) {
|
||||
);
|
||||
}
|
||||
|
||||
function DexLoginButton({ handleClick }) {
|
||||
function OIDCLoginButton({ handleClick, oidcName }) {
|
||||
const classes = useStyles();
|
||||
const loginWithName = oidcName || 'OIDC';
|
||||
|
||||
return (
|
||||
<Button fullWidth variant="contained" className={classes.button} onClick={(e) => handleClick(e, 'dex')}>
|
||||
Sign in with Dex
|
||||
<Button fullWidth variant="contained" className={classes.button} onClick={(e) => handleClick(e, 'oidc')}>
|
||||
Sign in with {loginWithName}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export { GithubLoginButton, GoogleLoginButton, GitlabLoginButton, DexLoginButton };
|
||||
export { GithubLoginButton, GoogleLoginButton, GitlabLoginButton, OIDCLoginButton };
|
||||
|
@ -271,7 +271,10 @@ function RepoDetails() {
|
||||
</Stack>
|
||||
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
|
||||
<VulnerabilityIconCheck vulnerabilitySeverity={repoDetailData?.vulnerabilitySeverity} />
|
||||
<SignatureIconCheck isSigned={repoDetailData.isSigned} />
|
||||
<SignatureIconCheck
|
||||
isSigned={repoDetailData.isSigned}
|
||||
signatureInfo={repoDetailData.signatureInfo}
|
||||
/>
|
||||
</Stack>
|
||||
{isAuthenticated() && (
|
||||
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
|
||||
|
40
src/components/Shared/NoDataComponent.jsx
Normal file
40
src/components/Shared/NoDataComponent.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
// react global
|
||||
import React from 'react';
|
||||
|
||||
// components
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
|
||||
//styling
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
|
||||
import nodataImage from '../../assets/noData.svg';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
noDataContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
noDataImage: {
|
||||
maxWidth: '233px',
|
||||
maxHeight: '240px'
|
||||
},
|
||||
noDataText: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600',
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}));
|
||||
|
||||
function NoDataComponent({ text }) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Stack className={classes.noDataContainer}>
|
||||
<img src={nodataImage} className={classes.noDataImage} />
|
||||
<Typography className={classes.noDataText}>{text ? text : 'No Data'}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoDataComponent;
|
@ -184,6 +184,7 @@ function RepoCard(props) {
|
||||
description,
|
||||
downloads,
|
||||
isSigned,
|
||||
signatureInfo,
|
||||
lastUpdated,
|
||||
version,
|
||||
vulnerabilityData,
|
||||
@ -290,7 +291,7 @@ function RepoCard(props) {
|
||||
<VulnerabilityIconCheck {...vulnerabilityData} className="hide-on-mobile" />
|
||||
</div>
|
||||
<div className="hide-on-mobile">
|
||||
<SignatureIconCheck isSigned={isSigned} className="hide-on-mobile" />
|
||||
<SignatureIconCheck isSigned={isSigned} signatureInfo={signatureInfo} className="hide-on-mobile" />
|
||||
</div>
|
||||
</Stack>
|
||||
<Tooltip title={description || 'Description not available'} placement="top">
|
||||
|
21
src/components/Shared/SignatureTooltip.jsx
Normal file
21
src/components/Shared/SignatureTooltip.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Typography, Stack } from '@mui/material';
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
function SignatureTooltip({ isSigned, signatureInfo }) {
|
||||
const { tool, isTrusted, author } = !isEmpty(signatureInfo)
|
||||
? signatureInfo[0]
|
||||
: { tool: 'Unknown', isTrusted: 'Unknown', author: 'Unknown' };
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Typography>{isSigned ? 'Verified Signature' : 'Unverified Signature'}</Typography>
|
||||
<Typography>Tool: {tool}</Typography>
|
||||
<Typography>Trusted: {!isEmpty(isTrusted) ? isTrusted : 'Unknown'}</Typography>
|
||||
<Typography>Author: {!isEmpty(author) ? author : 'Unknown'}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignatureTooltip;
|
@ -260,7 +260,10 @@ function TagDetails() {
|
||||
vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity}
|
||||
count={imageDetailData.vulnerabilityCount}
|
||||
/>
|
||||
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
|
||||
<SignatureIconCheck
|
||||
isSigned={imageDetailData.isSigned}
|
||||
signatureInfo={imageDetailData.signatureInfo}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" alignItems="center" spacing="1rem">
|
||||
|
@ -5,6 +5,7 @@ const mapToRepo = (responseRepo) => {
|
||||
tags: responseRepo.NewestImage?.Labels,
|
||||
description: responseRepo.NewestImage?.Description,
|
||||
isSigned: responseRepo.NewestImage?.IsSigned,
|
||||
signatureInfo: responseRepo.NewestImage?.SignatureInfo?.map((sigInfo) => mapSignatureInfo(sigInfo)),
|
||||
isBookmarked: responseRepo.IsBookmarked,
|
||||
isStarred: responseRepo.IsStarred,
|
||||
platforms: responseRepo.Platforms,
|
||||
@ -37,6 +38,7 @@ const mapToRepoFromRepoInfo = (responseRepoInfo) => {
|
||||
vulnerabilitySeverity: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
|
||||
vulnerabilityCount: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.Count,
|
||||
isSigned: responseRepoInfo.Summary?.NewestImage?.IsSigned,
|
||||
signatureInfo: responseRepoInfo.Summary?.NewestImage?.SignatureInfo?.map((sigInfo) => mapSignatureInfo(sigInfo)),
|
||||
isBookmarked: responseRepoInfo.Summary?.IsBookmarked,
|
||||
isStarred: responseRepoInfo.Summary?.IsStarred,
|
||||
logo: responseRepoInfo.Summary?.NewestImage?.Logo
|
||||
@ -54,6 +56,7 @@ const mapToImage = (responseImage) => {
|
||||
lastUpdated: responseImage.LastUpdated,
|
||||
description: responseImage.Description,
|
||||
isSigned: responseImage.IsSigned,
|
||||
signatureInfo: responseImage.SignatureInfo?.map((sigInfo) => mapSignatureInfo(sigInfo)),
|
||||
license: responseImage.Licenses,
|
||||
labels: responseImage.Labels,
|
||||
title: responseImage.Title,
|
||||
@ -94,6 +97,20 @@ const mapCVEInfo = (cveInfo) => {
|
||||
return cveList;
|
||||
};
|
||||
|
||||
const mapSignatureInfo = (signatureInfo) => {
|
||||
return signatureInfo
|
||||
? {
|
||||
tool: signatureInfo.Tool,
|
||||
isTrusted: signatureInfo.IsTrusted?.toString(),
|
||||
author: signatureInfo.Author
|
||||
}
|
||||
: {
|
||||
tool: 'Unknown',
|
||||
isTrusted: 'Unknown',
|
||||
author: 'Unknown'
|
||||
};
|
||||
};
|
||||
|
||||
const mapReferrer = (referrer) => ({
|
||||
mediaType: referrer.MediaType,
|
||||
artifactType: referrer.ArtifactType,
|
||||
|
@ -84,11 +84,11 @@ const VulnerabilityChipCheck = ({ vulnerabilitySeverity }) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const SignatureIconCheck = ({ isSigned }) => {
|
||||
const SignatureIconCheck = ({ isSigned, signatureInfo }) => {
|
||||
if (isSigned) {
|
||||
return <VerifiedSignatureIcon />;
|
||||
return <VerifiedSignatureIcon signatureInfo={signatureInfo} />;
|
||||
} else {
|
||||
return <UnverifiedSignatureIcon />;
|
||||
return <UnverifiedSignatureIcon signatureInfo={signatureInfo} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { Chip, Tooltip } from '@mui/material';
|
||||
import SvgIcon from '@mui/material/SvgIcon';
|
||||
import { ReactComponent as failedScanBug } from '../assets/failedScan.svg';
|
||||
import { createSvgIcon } from '@mui/material/utils';
|
||||
import SignatureTooltip from 'components/Shared/SignatureTooltip';
|
||||
|
||||
const FilledBugIcon = createSvgIcon(
|
||||
<path d="M17.0293 5.13093V6.1543H18.3828L21.2414 3.24068L22.2621 4.27812L19.5552 7.03876L19.5879 7.12668C20.1841 8.73695 20.4862 10.4449 20.4793 12.1662C20.4793 12.5064 20.4678 12.8466 20.4448 13.186L20.4397 13.2634H24V14.7334H20.2569L20.2466 14.7932C19.9431 16.4882 19.3517 18.0338 18.5466 19.335L18.4862 19.4335L21.9276 22.9608L20.9052 24L17.6121 20.6239L17.5138 20.7365C16.0259 22.4333 14.0983 23.4514 11.9983 23.4514C9.86724 23.4514 7.91207 22.4016 6.41552 20.6573L6.31552 20.5413L3.08966 23.833L2.06897 22.792L5.45345 19.3403L5.39483 19.2436C4.61897 17.9618 4.04655 16.4478 3.75 14.7932L3.73966 14.7334H0V13.2634H3.55862L3.55345 13.1843C3.53103 12.8502 3.51897 12.509 3.51897 12.1644C3.51202 10.4654 3.80581 8.77905 4.38621 7.18646L4.41897 7.1003L1.64138 4.2535L2.66379 3.21606L5.53103 6.1543H6.96724V5.13093C6.96724 3.77012 7.49729 2.46505 8.4408 1.50281C9.3843 0.540578 10.664 0 11.9983 0C13.3326 0 14.6123 0.540578 15.5558 1.50281C16.4993 2.46505 17.0293 3.77012 17.0293 5.13093Z" />,
|
||||
@ -144,7 +145,7 @@ const CriticalVulnerabilityIcon = ({ vulnerabilityStringTitle }) => {
|
||||
const NoneVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="No Vulnerability"
|
||||
label="None"
|
||||
sx={{ backgroundColor: '#E8F5E9', color: '#388E3C', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -158,7 +159,7 @@ const NoneVulnerabilityChip = () => {
|
||||
const UnknownVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="Unknown Vulnerability"
|
||||
label="Unknown"
|
||||
sx={{ backgroundColor: '#ECEFF1', color: '#52637A', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -186,7 +187,7 @@ const FailedScanChip = () => {
|
||||
const LowVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="Low Vulnerability"
|
||||
label="Low"
|
||||
sx={{ backgroundColor: '#FFF3E0', color: '#FB8C00', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -200,7 +201,7 @@ const LowVulnerabilityChip = () => {
|
||||
const MediumVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="Medium Vulnerability"
|
||||
label="Medium"
|
||||
sx={{ backgroundColor: '#FFF3E0', color: '#FB8C00', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -214,7 +215,7 @@ const MediumVulnerabilityChip = () => {
|
||||
const HighVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="High Vulnerability"
|
||||
label="High"
|
||||
sx={{ backgroundColor: '#FEEBEE', color: '#E53935', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -228,7 +229,7 @@ const HighVulnerabilityChip = () => {
|
||||
const CriticalVulnerabilityChip = () => {
|
||||
return (
|
||||
<Chip
|
||||
label="Critical Vulnerability"
|
||||
label="Critical"
|
||||
sx={{ backgroundColor: '#FEEBEE', color: '#E53935', fontSize: '0.8125rem' }}
|
||||
variant="filled"
|
||||
onDelete={() => {
|
||||
@ -240,9 +241,9 @@ const CriticalVulnerabilityChip = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const UnverifiedSignatureIcon = () => {
|
||||
const UnverifiedSignatureIcon = ({ signatureInfo }) => {
|
||||
return (
|
||||
<Tooltip title="Unverified Signature" placement="top">
|
||||
<Tooltip title={<SignatureTooltip isSigned={false} signatureInfo={signatureInfo} />} placement="top">
|
||||
<UnverifiedShieldIcon
|
||||
sx={{
|
||||
color: '#E53935',
|
||||
@ -257,9 +258,9 @@ const UnverifiedSignatureIcon = () => {
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
const VerifiedSignatureIcon = () => {
|
||||
const VerifiedSignatureIcon = ({ signatureInfo }) => {
|
||||
return (
|
||||
<Tooltip title="Verified Signature" placement="top">
|
||||
<Tooltip title={<SignatureTooltip isSigned={true} signatureInfo={signatureInfo} />} placement="top">
|
||||
<VerifiedShieldIcon
|
||||
viewBox="0 0 24 24"
|
||||
sx={{
|
||||
|
@ -23,9 +23,9 @@ const endpoints = {
|
||||
globalSearch: (searchTerm, sortCriteria, pageNumber = 1, pageSize = 10) =>
|
||||
`/v2/_zot/ext/search?query={GlobalSearch(query:%22${searchTerm}%22,%20requestedPage:%20{limit:${pageSize}%20offset:${
|
||||
10 * (pageNumber - 1)
|
||||
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20Licenses%20Vendor%20Labels%20}%20DownloadCount}}}`,
|
||||
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Licenses%20Vendor%20Labels%20}%20DownloadCount}}}`,
|
||||
image: (name) =>
|
||||
`/v2/_zot/ext/search?query={Image(image:%20%22${name}%22){RepoName%20IsSigned%20Vulnerabilities%20{MaxSeverity%20Count}%20%20Referrers%20{MediaType%20ArtifactType%20Size%20Digest%20Annotations{Key%20Value}}%20Tag%20Manifests%20{History%20{Layer%20{Size%20Digest}%20HistoryDescription%20{CreatedBy%20EmptyLayer}}%20Digest%20ConfigDigest%20LastUpdated%20Size%20Platform%20{Os%20Arch}}%20Vendor%20Licenses%20}}`
|
||||
`/v2/_zot/ext/search?query={Image(image:%20%22${name}%22){RepoName%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Vulnerabilities%20{MaxSeverity%20Count}%20%20Referrers%20{MediaType%20ArtifactType%20Size%20Digest%20Annotations{Key%20Value}}%20Tag%20Manifests%20{History%20{Layer%20{Size%20Digest}%20HistoryDescription%20{CreatedBy%20EmptyLayer}}%20Digest%20ConfigDigest%20LastUpdated%20Size%20Platform%20{Os%20Arch}}%20Vendor%20Licenses%20}}`
|
||||
};
|
||||
|
||||
export { hosts, endpoints, sortCriteria, pageSizes };
|
||||
|
Reference in New Issue
Block a user