patch: signature display redesign (#427)

Signed-off-by: Raul-Cristian Kele <raulkeleblk@gmail.com>
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
Co-authored-by: Raul-Cristian Kele <raulkeleblk@gmail.com>
This commit is contained in:
Andreea Lupu 2024-03-24 22:12:31 +02:00 committed by Alexander Burmatov
parent d6cd38145d
commit cc4030fc96
18 changed files with 443 additions and 113 deletions

View File

@ -1,4 +1,4 @@
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { api } from 'api';
import Explore from 'components/Explore/Explore';
@ -38,7 +38,7 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: 'w',
IsSigned: false,
SignatureInfo: [],
Licenses: '',
Vendor: '',
Labels: '',
@ -63,7 +63,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: false,
Author: ''
},
{
Tool: 'notation',
IsTrusted: false,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -88,7 +99,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -113,7 +135,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -138,7 +171,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -167,7 +211,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -192,7 +247,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: 'author1'
},
{
Tool: 'notation',
IsTrusted: true,
Author: 'author2'
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -225,7 +291,7 @@ const filteredMockImageListWindows = () => {
};
const filteredMockImageListSigned = () => {
const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.IsSigned);
const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.SignatureInfo?.length > 0);
return {
GlobalSearch: {
Page: { TotalCount: 6, ItemCount: 6 },
@ -273,7 +339,22 @@ describe('Explore component', () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(1);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(6);
expect(await screen.findAllByTestId('untrusted-icon')).toHaveLength(2);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(10);
const allUntrustedSignaturesIcons = await screen.findAllByTestId("untrusted-icon");
fireEvent.mouseOver(allUntrustedSignaturesIcons[0]);
expect(await screen.findByText("Signed-by: Unknown")).toBeInTheDocument();
const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon");
fireEvent.mouseOver(allTrustedSignaturesIcons[8]);
expect(await screen.findByText("Tool: cosign")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument();
fireEvent.mouseOver(allTrustedSignaturesIcons[9]);
expect(await screen.findByText("Tool: notation")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument();
const allNoSignedIcons = await screen.findAllByTestId("unverified-icon");
fireEvent.mouseOver(allNoSignedIcons[0]);
expect(await screen.findByText("Not signed")).toBeInTheDocument();
});
it('renders vulnerability icons', async () => {

View File

@ -32,7 +32,7 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: 'w',
IsSigned: false,
SignatureInfo: [],
Licenses: '',
Vendor: '',
Labels: '',
@ -49,7 +49,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -66,7 +77,18 @@ const mockImageList = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -91,7 +113,7 @@ const mockImageListRecent = {
NewestImage: {
Tag: 'latest',
Description: 'w',
IsSigned: false,
SignatureInfo: [],
Licenses: '',
Vendor: '',
Labels: '',
@ -108,7 +130,18 @@ const mockImageListRecent = {
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: ''
},
{
Tool: 'notation',
IsTrusted: true,
Author: ''
}
],
Licenses: '',
Vendor: '',
Labels: '',
@ -230,7 +263,7 @@ describe('Home component', () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
render(<HomeWrapper />);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(4);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(5);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(10);
});
it('renders vulnerability icons', async () => {

View File

@ -51,6 +51,18 @@ const mockRepoDetailsData = {
NewestImage: {
RepoName: 'mongo',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: 'author1'
},
{
Tool: 'notation',
IsTrusted: true,
Author: 'author2'
}
],
Vulnerabilities: {
MaxSeverity: 'CRITICAL',
Count: 15
@ -285,6 +297,20 @@ describe('Repo details component', () => {
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(1);
});
it('renders signature icons', async () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetailsThemeWrapper />);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(2);
const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon");
fireEvent.mouseOver(allTrustedSignaturesIcons[0]);
expect(await screen.findByText("Tool: cosign")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument();
fireEvent.mouseOver(allTrustedSignaturesIcons[1]);
expect(await screen.findByText("Tool: notation")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument();
});
it("should log error if data can't be fetched", async () => {
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});

View File

@ -174,7 +174,20 @@ const mockImage = {
MaxSeverity: 'CRITICAL',
Count: 10
},
Vendor: 'CentOS'
Vendor: 'CentOS',
IsSigned: true,
SignatureInfo: [
{
Tool: 'cosign',
IsTrusted: true,
Author: 'author1'
},
{
Tool: 'notation',
IsTrusted: true,
Author: 'author2'
}
]
}
};
@ -963,6 +976,20 @@ describe('Tags details', () => {
expect(await screen.findByTestId('high-vulnerability-icon')).toBeInTheDocument();
});
it('renders signature icons', async () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetailsThemeWrapper />);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(2);
const allTrustedSignaturesIcons = await screen.findAllByTestId("verified-icon");
fireEvent.mouseOver(allTrustedSignaturesIcons[0]);
expect(await screen.findByText("Tool: cosign")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author1")).toBeInTheDocument();
fireEvent.mouseOver(allTrustedSignaturesIcons[1]);
expect(await screen.findByText("Tool: notation")).toBeInTheDocument();
expect(await screen.findByText("Signed-by: author2")).toBeInTheDocument();
});
it('should copy the docker pull string to clipboard', async () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetailsThemeWrapper />);

View File

@ -221,7 +221,6 @@ function Explore({ searchInputValue }) {
description={item.description}
downloads={item.downloads}
stars={item.stars}
isSigned={item.isSigned}
signatureInfo={item.signatureInfo}
isBookmarked={item.isBookmarked}
isStarred={item.isStarred}

View File

@ -261,7 +261,6 @@ function Home() {
description={item.description}
downloads={item.downloads}
stars={item.stars}
isSigned={item.isSigned}
signatureInfo={item.signatureInfo}
isBookmarked={item.isBookmarked}
isStarred={item.isStarred}

View File

@ -11,6 +11,7 @@ import { host } from '../../host';
import { useParams, useNavigate, createSearchParams } from 'react-router-dom';
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
import { isAuthenticated } from 'utilities/authUtilities';
import filterConstants from 'utilities/filterConstants';
// components
import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography, IconButton } from '@mui/material';
@ -260,6 +261,28 @@ function RepoDetails() {
return lastDate;
};
const getSignatureChips = () => {
const cosign = repoDetailData.signatureInfo
?.map((s) => s.tool)
.includes(filterConstants.signatureToolConstants.COSIGN)
? repoDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN)
: null;
const notation = repoDetailData.signatureInfo
?.map((s) => s.tool)
.includes(filterConstants.signatureToolConstants.NOTATION)
? repoDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION)
: null;
const sigArray = [];
if (cosign) sigArray.push(cosign);
if (notation) sigArray.push(notation);
if (sigArray.length === 0) return <SignatureIconCheck />;
return sigArray.map((sig, index) => (
<div className="hide-on-mobile" key={`${name}sig${index}`}>
<SignatureIconCheck signatureInfo={sig} />
</div>
));
};
return (
<>
{isLoading ? (
@ -288,10 +311,7 @@ function RepoDetails() {
</Stack>
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
<VulnerabilityIconCheck vulnerabilitySeverity={repoDetailData?.vulnerabilitySeverity} />
<SignatureIconCheck
isSigned={repoDetailData.isSigned}
signatureInfo={repoDetailData.signatureInfo}
/>
{getSignatureChips()}
</Stack>
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={1}>
{isAuthenticated() && (
@ -304,13 +324,20 @@ function RepoDetails() {
</IconButton>
)}
{isAuthenticated() && (
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
{repoDetailData?.isBookmarked ? (
<BookmarkIcon data-testid="bookmarked" />
) : (
<BookmarkBorderIcon data-testid="not-bookmarked" />
)}
</IconButton>
<Stack
alignItems="center"
sx={{ width: { xs: '100%', md: 'auto' } }}
direction="row"
spacing={2}
>
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
{repoDetailData?.isBookmarked ? (
<BookmarkIcon data-testid="bookmarked" />
) : (
<BookmarkBorderIcon data-testid="not-bookmarked" />
)}
</IconButton>
</Stack>
)}
</Stack>
</Stack>

View File

@ -10,7 +10,7 @@ import repocube3 from '../../assets/repocube-3.png';
import repocube4 from '../../assets/repocube-4.png';
import { isEmpty } from 'lodash';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { VulnerabilityIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
// temporary utility to get image
const randomIntFromInterval = (min, max) => {
@ -67,7 +67,7 @@ const useStyles = makeStyles(() => ({
function PreviewCard(props) {
const classes = useStyles();
const navigate = useNavigate();
const { name, isSigned, vulnerabilityData, logo } = props;
const { name, vulnerabilityData, logo } = props;
const goToDetails = () => {
navigate(`/image/${encodeURIComponent(name)}`);
@ -108,7 +108,6 @@ function PreviewCard(props) {
</Tooltip>
<Stack direction="row" spacing={0.5} sx={{ marginLeft: 'auto', marginRight: 0 }}>
<VulnerabilityIconCheck {...vulnerabilityData} />
<SignatureIconCheck isSigned={isSigned} />
</Stack>
</Stack>
</Grid>

View File

@ -32,15 +32,16 @@ import StarIcon from '@mui/icons-material/Star';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import { useTheme } from '@emotion/react';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
import filterConstants from 'utilities/filterConstants';
// placeholder images
import repocube1 from '../../assets/repocube-1.png';
import repocube2 from '../../assets/repocube-2.png';
import repocube3 from '../../assets/repocube-3.png';
import repocube4 from '../../assets/repocube-4.png';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
// temporary utility to get image
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
@ -186,7 +187,6 @@ function RepoCard(props) {
description,
downloads,
stars,
isSigned,
signatureInfo,
lastUpdated,
version,
@ -296,6 +296,24 @@ function RepoCard(props) {
);
};
const getSignatureChips = () => {
const cosign = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.COSIGN)
? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN)
: null;
const notation = signatureInfo?.map((s) => s.tool).includes(filterConstants.signatureToolConstants.NOTATION)
? signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION)
: null;
const sigArray = [];
if (cosign) sigArray.push(cosign);
if (notation) sigArray.push(notation);
if (sigArray.length === 0) return <SignatureIconCheck />;
return sigArray.map((sig, index) => (
<div className="hide-on-mobile" key={`${name}sig${index}`}>
<SignatureIconCheck signatureInfo={sig} />
</div>
));
};
return (
<Card variant="outlined" className={classes.card} data-testid="repo-card">
<CardActionArea
@ -323,12 +341,10 @@ function RepoCard(props) {
{name}
</Typography>
</Tooltip>
<div className="hide-on-mobile">
<div className="hide-on-mobile" style={{ display: 'inline-flex' }}>
<VulnerabilityIconCheck {...vulnerabilityData} className="hide-on-mobile" />
</div>
<div className="hide-on-mobile">
<SignatureIconCheck isSigned={isSigned} signatureInfo={signatureInfo} className="hide-on-mobile" />
</div>
{getSignatureChips()}
</Stack>
<Tooltip title={description || 'Description not available'} placement="top">
<Typography className={classes.description} pt={1} sx={{ fontSize: 12 }} gutterBottom noWrap>

View File

@ -1,19 +1,17 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Typography, Stack } from '@mui/material';
import { isEmpty } from 'lodash';
import { getStrongestSignature, getAllAuthorsOfSignatures } from 'utilities/vulnerabilityAndSignatureCheck';
function SignatureTooltip({ isSigned, signatureInfo }) {
const { tool, isTrusted, author } = !isEmpty(signatureInfo)
? signatureInfo[0]
: { tool: 'Unknown', isTrusted: 'Unknown', author: 'Unknown' };
function SignatureTooltip({ signatureInfo }) {
const strongestSignature = useMemo(() => getStrongestSignature(signatureInfo));
return (
return isEmpty(strongestSignature) ? (
<Typography>Not signed</Typography>
) : (
<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>
<Typography>Tool: {strongestSignature?.tool || 'Unknown'}</Typography>
<Typography>Signed-by: {getAllAuthorsOfSignatures(signatureInfo) || 'Unknown'}</Typography>
</Stack>
);
}

View File

@ -107,7 +107,7 @@ function DependsOn(props) {
repoName={dependence.repoName}
tag={dependence.tag}
vendor={dependence.vendor}
isSigned={dependence.isSigned}
signatureInfo={dependence.signatureInfo}
manifests={dependence.manifests}
key={index}
lastUpdated={dependence.lastUpdated}

View File

@ -107,7 +107,7 @@ function IsDependentOn(props) {
repoName={dependence.repoName}
tag={dependence.tag}
vendor={dependence.vendor}
isSigned={dependence.isSigned}
signatureInfo={dependence.signatureInfo}
manifests={dependence.manifests}
key={index}
lastUpdated={dependence.lastUpdated}

View File

@ -5,6 +5,7 @@ import React, { useEffect, useMemo, useState, useRef } from 'react';
import { api, endpoints } from '../../api';
import { host } from '../../host';
import { mapToImage } from '../../utilities/objectModels';
import filterConstants from 'utilities/filterConstants';
import { isEmpty, head } from 'lodash';
// components
@ -224,6 +225,28 @@ function TagDetails() {
}
};
const getSignatureChips = () => {
const cosign = imageDetailData.signatureInfo
?.map((s) => s.tool)
.includes(filterConstants.signatureToolConstants.COSIGN)
? imageDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.COSIGN)
: null;
const notation = imageDetailData.signatureInfo
?.map((s) => s.tool)
.includes(filterConstants.signatureToolConstants.NOTATION)
? imageDetailData.signatureInfo.filter((si) => si.tool == filterConstants.signatureToolConstants.NOTATION)
: null;
const sigArray = [];
if (cosign) sigArray.push(cosign);
if (notation) sigArray.push(notation);
if (sigArray.length === 0) return <SignatureIconCheck />;
return sigArray.map((sig, index) => (
<div className="hide-on-mobile" key={`${name}sig${index}`}>
<SignatureIconCheck signatureInfo={sig} />
</div>
));
};
return (
<>
{isLoading ? (
@ -256,15 +279,12 @@ function TagDetails() {
</Typography>
</Stack>
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={1}>
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
<VulnerabilityIconCheck
vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity}
count={imageDetailData.vulnerabilityCount}
/>
<SignatureIconCheck
isSigned={imageDetailData.isSigned}
signatureInfo={imageDetailData.signatureInfo}
/>
{getSignatureChips()}
</Stack>
</Stack>
<Stack direction="row" alignItems="center" spacing="1rem">

View File

@ -69,6 +69,11 @@ const archFilters = [
}
];
const filterConstants = { osFilters, imageFilters, archFilters };
const signatureToolConstants = {
COSIGN: 'cosign',
NOTATION: 'notation'
};
const filterConstants = { osFilters, imageFilters, archFilters, signatureToolConstants };
export default filterConstants;

View File

@ -132,12 +132,12 @@ const mapSignatureInfo = (signatureInfo) => {
return signatureInfo
? {
tool: signatureInfo.Tool,
isTrusted: signatureInfo.IsTrusted?.toString(),
isTrusted: signatureInfo.IsTrusted,
author: signatureInfo.Author
}
: {
tool: 'Unknown',
isTrusted: 'Unknown',
isTrusted: false,
author: 'Unknown'
};
};

View File

@ -1,3 +1,4 @@
import { isEmpty } from 'lodash';
import React from 'react';
import {
NoneVulnerabilityIcon,
@ -12,14 +13,26 @@ import {
CriticalVulnerabilityChip,
UnverifiedSignatureIcon,
VerifiedSignatureIcon,
UnverifiedSignatureChip,
VerifiedSignatureChip,
UnknownVulnerabilityIcon,
UnknownVulnerabilityChip,
FailedScanIcon,
FailedScanChip
FailedScanChip,
NotTrustedSignatureIcon
} from './vulnerabilityAndSignatureComponents';
const getStrongestSignature = (signatureInfo) => {
if (isEmpty(signatureInfo)) return null;
const trusted = signatureInfo.find((si) => si.isTrusted);
if (!isEmpty(trusted)) return trusted;
return signatureInfo[0];
};
const getAllAuthorsOfSignatures = (signatureInfo) => {
if (isEmpty(signatureInfo)) return '';
const signatureAuthors = signatureInfo.filter((si) => si.isTrusted).map((si) => si.author);
return signatureAuthors.join(',');
};
const VulnerabilityIconCheck = ({ vulnerabilitySeverity }) => {
let result;
let vulnerabilityStringTitle = '';
@ -84,20 +97,17 @@ const VulnerabilityChipCheck = ({ vulnerabilitySeverity }) => {
return result;
};
const SignatureIconCheck = ({ isSigned, signatureInfo }) => {
if (isSigned) {
return <VerifiedSignatureIcon signatureInfo={signatureInfo} />;
} else {
return <UnverifiedSignatureIcon signatureInfo={signatureInfo} />;
}
const SignatureIconCheck = ({ signatureInfo }) => {
const strongestSignature = getStrongestSignature(signatureInfo);
if (strongestSignature === null) return <UnverifiedSignatureIcon signatureInfo={signatureInfo} />;
if (strongestSignature.isTrusted) return <VerifiedSignatureIcon signatureInfo={signatureInfo} />;
return <NotTrustedSignatureIcon signatureInfo={signatureInfo} />;
};
const SignatureChipCheck = ({ isSigned }) => {
if (isSigned) {
return <VerifiedSignatureChip />;
} else {
return <UnverifiedSignatureChip />;
}
export {
VulnerabilityIconCheck,
VulnerabilityChipCheck,
SignatureIconCheck,
getStrongestSignature,
getAllAuthorsOfSignatures
};
export { VulnerabilityIconCheck, VulnerabilityChipCheck, SignatureIconCheck, SignatureChipCheck };

View File

@ -1,9 +1,10 @@
import React from 'react';
import { Chip, Tooltip } from '@mui/material';
import { Chip, Tooltip, Badge } 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';
import filterConstants from 'utilities/filterConstants';
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" />,
@ -14,11 +15,15 @@ const OutlinedBugIcon = createSvgIcon(
'OutlinedBug'
);
const UnverifiedShieldIcon = createSvgIcon(
<path d="M12.4837 2C13.6167 2 19.5627 4.041 20.3487 4.828C21.0047 5.484 20.9947 6.014 20.9487 8.557C20.9307 9.575 20.9057 10.962 20.9057 12.879C20.9057 19.761 13.0357 22.223 12.7007 22.324C12.6297 22.346 12.5567 22.356 12.4837 22.356C12.4107 22.356 12.3377 22.346 12.2667 22.324C11.9317 22.223 4.06165 19.761 4.06165 12.879C4.06165 10.959 4.03665 9.572 4.01865 8.554C4.01044 8.10043 4.00337 7.71095 4.00104 7.37341L4.00073 6.9925C4.00922 5.74112 4.1264 5.32 4.61765 4.828C5.40465 4.041 11.3507 2 12.4837 2ZM12.4837 3.5C11.6357 3.5 6.28465 5.384 5.66765 5.899C5.54931 6.018 5.50535 6.19514 5.49972 6.89808L5.49926 7.16877C5.50045 7.51182 5.50742 7.95335 5.51765 8.526C5.53665 9.552 5.56165 10.947 5.56165 12.879C5.56165 18.08 11.2837 20.389 12.4827 20.814C13.6807 20.387 19.4057 18.065 19.4057 12.879C19.4057 10.949 19.4307 9.555 19.4487 8.529C19.4592 7.95581 19.4663 7.51389 19.4674 7.17033L19.4668 6.89918C19.4605 6.19482 19.4138 6.01519 19.2877 5.889C18.6817 5.384 13.3317 3.5 12.4837 3.5ZM11.1346 9.5372L12.4837 10.887L13.8328 9.5372C14.1258 9.2442 14.5998 9.2442 14.8928 9.5372C15.1858 9.8302 15.1858 10.3042 14.8928 10.5972L13.5437 11.947L14.8926 13.2952C15.1856 13.5882 15.1856 14.0622 14.8926 14.3552C14.7466 14.5022 14.5546 14.5752 14.3626 14.5752C14.1706 14.5752 13.9786 14.5022 13.8326 14.3552L12.4837 13.007L11.1348 14.3552C10.9888 14.5022 10.7968 14.5752 10.6048 14.5752C10.4128 14.5752 10.2208 14.5022 10.0748 14.3552C9.78175 14.0622 9.78175 13.5882 10.0748 13.2952L11.4237 11.947L10.0746 10.5972C9.78155 10.3042 9.78155 9.8302 10.0746 9.5372C10.3676 9.2442 10.8416 9.2442 11.1346 9.5372Z" />,
<path d="M9,0,0,4v6c0,5.55,3.84,10.74,9,12,5.16-1.26,9-6.45,9-12V4Zm7,10a10.47,10.47,0,0,1-7,9.93A10.47,10.47,0,0,1,2,10V5.3L9,2.19,16,5.3ZM7,7.74l1.22,1.4c.32.36.58.7.86,1.06H9.1c.28-.39.56-.72.84-1.07l1.2-1.39h1.68L9.91,10.89l3,3.37H11.14L9.89,12.79c-.33-.38-.62-.74-.92-1.13h0c-.28.39-.58.74-.9,1.13L6.8,14.26H5.09l3-3.33L5.23,7.74Z" />,
'UnverifiedShield'
);
const VerifiedShieldIcon = createSvgIcon(
<path d="M12.4836 2C13.6166 2 19.5616 4.041 20.3486 4.828C21.0046 5.484 20.9946 6.014 20.9486 8.554C20.9306 9.572 20.9056 10.959 20.9056 12.879C20.9056 19.761 13.0356 22.223 12.7006 22.324C12.6296 22.346 12.5566 22.356 12.4836 22.356C12.4106 22.356 12.3376 22.346 12.2666 22.324C11.9316 22.223 4.06162 19.761 4.06162 12.879C4.06162 10.962 4.03662 9.575 4.01862 8.557C4.01041 8.10289 4.00334 7.71298 4.00102 7.37507L4.00073 6.99377C4.00931 5.74113 4.12687 5.32 4.61962 4.828C5.40462 4.041 11.3496 2 12.4836 2ZM12.4836 3.5C11.6356 3.5 6.28562 5.384 5.66862 5.899C5.48662 6.082 5.47962 6.4 5.51862 8.529C5.53662 9.555 5.56162 10.949 5.56162 12.879C5.56162 18.08 11.2836 20.389 12.4826 20.814C13.6806 20.387 19.4056 18.065 19.4056 12.879C19.4056 10.947 19.4306 9.552 19.4496 8.526C19.4876 6.399 19.4806 6.081 19.2876 5.889C18.6826 5.384 13.3316 3.5 12.4836 3.5ZM16.2051 9.3395C16.4981 9.6325 16.4981 10.1075 16.2051 10.4005L12.3071 14.2995C12.1951 14.4123 12.0505 14.4854 11.8952 14.5102L11.7771 14.5195C11.5781 14.5195 11.3871 14.4405 11.2461 14.2995L9.35412 12.4055C9.06212 12.1125 9.06212 11.6365 9.35512 11.3445C9.64712 11.0515 10.1231 11.0515 10.4161 11.3445L11.7771 12.7075L15.1451 9.3395C15.4381 9.0465 15.9121 9.0465 16.2051 9.3395Z" />,
const CVerifiedShieldIcon = createSvgIcon(
<path d="M11.8,13.64a1.85,1.85,0,0,1,0,.25.9.9,0,0,1,0,.18.33.33,0,0,1,0,.12.47.47,0,0,1-.09.12,1.25,1.25,0,0,1-.25.18c-.13.07-.28.13-.45.2a4.13,4.13,0,0,1-.61.16,4.35,4.35,0,0,1-.74.06,3.93,3.93,0,0,1-1.41-.24A2.91,2.91,0,0,1,7.1,14a3.32,3.32,0,0,1-.67-1.2A5.2,5.2,0,0,1,6.2,11.1a5.37,5.37,0,0,1,.25-1.72,3.85,3.85,0,0,1,.72-1.26,3,3,0,0,1,1.12-.77,3.63,3.63,0,0,1,1.42-.26,3,3,0,0,1,.61,0,2.66,2.66,0,0,1,.54.14,2.34,2.34,0,0,1,.45.19,1.84,1.84,0,0,1,.28.19l.11.13s0,.09.05.14,0,.12,0,.19a2.35,2.35,0,0,1,0,.28,2.63,2.63,0,0,1,0,.3.88.88,0,0,1,0,.2.52.52,0,0,1-.07.11.17.17,0,0,1-.1,0,.38.38,0,0,1-.22-.1c-.09-.07-.21-.14-.35-.23a3.08,3.08,0,0,0-.51-.23,2.41,2.41,0,0,0-.7-.1A1.67,1.67,0,0,0,9,8.57a1.58,1.58,0,0,0-.6.52A2.54,2.54,0,0,0,8,9.92,4.15,4.15,0,0,0,7.86,11,4.31,4.31,0,0,0,8,12.17a2.19,2.19,0,0,0,.39.81,1.55,1.55,0,0,0,.61.47,2,2,0,0,0,.82.16,2.17,2.17,0,0,0,.71-.1A2.45,2.45,0,0,0,11,13.3l.35-.21a.38.38,0,0,1,.21-.1.17.17,0,0,1,.1,0,.17.17,0,0,1,.06.09c0,.05,0,.11,0,.2A3,3,0,0,1,11.8,13.64ZM9,0,0,4v6c0,5.55,3.84,10.74,9,12,5.16-1.26,9-6.45,9-12V4Zm7,10a10.47,10.47,0,0,1-7,9.93A10.47,10.47,0,0,1,2,10V5.3L9,2.19,16,5.3Z" />,
'VerifiedShield'
);
const NVerifiedShieldIcon = createSvgIcon(
<path d="M12.13,14.25a.6.6,0,0,1-.05.24.45.45,0,0,1-.13.17.39.39,0,0,1-.19.1.52.52,0,0,1-.21,0h-.66a1.79,1.79,0,0,1-.36,0,.72.72,0,0,1-.27-.15,1.06,1.06,0,0,1-.24-.3c-.08-.12-.17-.28-.27-.47L7.87,10.29c-.11-.21-.22-.44-.34-.68s-.21-.48-.3-.71h0l0,.84c0,.28,0,.56,0,.86v4a.17.17,0,0,1,0,.1.19.19,0,0,1-.11.08.81.81,0,0,1-.21.05l-.35,0-.34,0A.81.81,0,0,1,6,14.75a.19.19,0,0,1-.11-.08.17.17,0,0,1,0-.1V7.75A.51.51,0,0,1,6,7.34a.56.56,0,0,1,.39-.14h.83a1.82,1.82,0,0,1,.37,0,.84.84,0,0,1,.27.13,1.06,1.06,0,0,1,.23.24A3.9,3.9,0,0,1,8.35,8l1.47,2.78.26.49.24.49.23.47c.07.16.15.32.22.47h0c0-.27,0-.56,0-.85s0-.58,0-.85V7.43a.17.17,0,0,1,0-.1.29.29,0,0,1,.12-.09.9.9,0,0,1,.22,0h.68a.72.72,0,0,1,.2,0s.09,0,.11.09a.17.17,0,0,1,0,.1ZM9,0,0,4v6c0,5.55,3.84,10.74,9,12,5.16-1.26,9-6.45,9-12V4Zm7,10a10.47,10.47,0,0,1-7,9.93A10.47,10.47,0,0,1,2,10V5.3L9,2.19,16,5.3Z" />,
'VerifiedShield'
);
@ -222,8 +227,9 @@ const CriticalVulnerabilityChip = () => {
const UnverifiedSignatureIcon = ({ signatureInfo }) => {
return (
<Tooltip title={<SignatureTooltip isSigned={false} signatureInfo={signatureInfo} />} placement="top">
<Tooltip title={<SignatureTooltip signatureInfo={signatureInfo} />} placement="top">
<UnverifiedShieldIcon
viewBox="0 0 18 22"
sx={{
color: '#E53935',
padding: '0.2rem',
@ -237,22 +243,119 @@ const UnverifiedSignatureIcon = ({ signatureInfo }) => {
</Tooltip>
);
};
const NotTrustedSignatureIcon = ({ signatureInfo }) => {
return (
<Tooltip title={<SignatureTooltip signatureInfo={signatureInfo} />} placement="top">
{(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.NOTATION && (
<Badge
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
overlap="circular"
color="warning"
badgeContent={signatureInfo.length}
>
<NVerifiedShieldIcon
viewBox="0 0 18 22"
sx={{
backgroundColor: '#FCE2B8!important',
color: '#FB8C00',
alignSelf: 'center',
padding: '0.2rem',
background: '#E8F5E9',
borderRadius: '1rem',
height: '1.5rem',
width: '1.6rem'
}}
data-testid="untrusted-icon"
/>
</Badge>
)) ||
(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.COSIGN && (
<Badge
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
overlap="circular"
color="warning"
badgeContent={signatureInfo.length}
>
<CVerifiedShieldIcon
viewBox="0 0 18 22"
sx={{
backgroundColor: '#FCE2B8!important',
color: '#FB8C00',
alignSelf: 'center',
padding: '0.2rem',
background: '#E8F5E9',
borderRadius: '1rem',
height: '1.5rem',
width: '1.6rem'
}}
data-testid="untrusted-icon"
/>
</Badge>
))}
</Tooltip>
);
};
const VerifiedSignatureIcon = ({ signatureInfo }) => {
return (
<Tooltip title={<SignatureTooltip isSigned={true} signatureInfo={signatureInfo} />} placement="top">
<VerifiedShieldIcon
viewBox="0 0 24 24"
sx={{
color: '#43A047',
alignSelf: 'center',
padding: '0.2rem',
background: '#E8F5E9',
borderRadius: '1rem',
height: '1.5rem',
width: '1.6rem'
}}
data-testid="verified-icon"
/>
<Tooltip title={<SignatureTooltip signatureInfo={signatureInfo} />} placement="top">
{(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.NOTATION && (
<Badge
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
overlap="circular"
color="success"
badgeContent={signatureInfo.length}
>
<NVerifiedShieldIcon
viewBox="0 0 18 22"
sx={{
color: '#43A047',
alignSelf: 'center',
padding: '0.2rem',
background: '#E8F5E9',
borderRadius: '1rem',
height: '1.5rem',
width: '1.6rem'
}}
data-testid="verified-icon"
/>
</Badge>
)) ||
(signatureInfo[0]?.tool && signatureInfo[0].tool == filterConstants.signatureToolConstants.COSIGN && (
<Badge
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
overlap="circular"
color="success"
badgeContent={signatureInfo.length}
>
<CVerifiedShieldIcon
viewBox="0 0 18 22"
sx={{
color: '#43A047',
alignSelf: 'center',
padding: '0.2rem',
background: '#E8F5E9',
borderRadius: '1rem',
height: '1.5rem',
width: '1.6rem'
}}
data-testid="verified-icon"
/>
</Badge>
))}
</Tooltip>
);
};
@ -270,19 +373,6 @@ const UnverifiedSignatureChip = () => {
/>
);
};
const VerifiedSignatureChip = () => {
return (
<Chip
label="Verified Signature"
sx={{ backgroundColor: '#E8F5E9', color: '#388E3C', fontSize: '0.8125rem' }}
variant="filled"
onDelete={() => {
return;
}}
deleteIcon={<VerifiedShieldIcon sx={{ color: '#388E3C!important' }} />}
/>
);
};
export {
NoneVulnerabilityIcon,
@ -298,9 +388,9 @@ export {
HighVulnerabilityChip,
CriticalVulnerabilityChip,
UnverifiedSignatureIcon,
NotTrustedSignatureIcon,
VerifiedSignatureIcon,
UnverifiedSignatureChip,
VerifiedSignatureChip,
FailedScanIcon,
FailedScanChip
};

View File

@ -149,7 +149,7 @@ const getRepoListOrderedAlpha = () => {
// };
const getRepoCardNameForLocator = (repo) => {
return `${repo?.repo} ${repo?.tags[0]?.description?.slice(0, 10)}`;
return new RegExp(`${repo?.repo} \\d${repo?.tags[0]?.description?.slice(0, 10)}`);
};
export {