Compare commits
3 Commits
commit-2b3
...
commit-2f9
Author | SHA1 | Date | |
---|---|---|---|
2f94cc30ae | |||
ddf1d9224b | |||
7471fb58a8 |
@ -1,12 +1,9 @@
|
|||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { api } from 'api';
|
|
||||||
import ReferredBy from 'components/Tag/Tabs/ReferredBy';
|
import ReferredBy from 'components/Tag/Tabs/ReferredBy';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const mockReferrersList = {
|
const mockReferrersList = [
|
||||||
data: {
|
|
||||||
Referrers: [
|
|
||||||
{
|
{
|
||||||
MediaType: 'application/vnd.oci.artifact.manifest.v1+json',
|
MediaType: 'application/vnd.oci.artifact.manifest.v1+json',
|
||||||
ArtifactType: 'application/vnd.example.icecream.v1',
|
ArtifactType: 'application/vnd.example.icecream.v1',
|
||||||
@ -39,9 +36,7 @@ const mockReferrersList = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// useNavigate mock
|
// useNavigate mock
|
||||||
const mockedUsedNavigate = jest.fn();
|
const mockedUsedNavigate = jest.fn();
|
||||||
@ -57,27 +52,17 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe('Referred by tab', () => {
|
describe('Referred by tab', () => {
|
||||||
it('should render referrers if there are any', async () => {
|
it('should render referrers if there are any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockReferrersList });
|
render(<ReferredBy referrers={mockReferrersList} />);
|
||||||
render(<ReferredBy repoName="golang" digest="test" />);
|
|
||||||
expect(await screen.findAllByText('Media type: application/vnd.oci.artifact.manifest.v1+json')).toHaveLength(2);
|
expect(await screen.findAllByText('Media type: application/vnd.oci.artifact.manifest.v1+json')).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders no referrers if there aren't any", async () => {
|
it("renders no referrers if there aren't any", async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { Referrers: [] } } });
|
render(<ReferredBy referrers={[]} />);
|
||||||
render(<ReferredBy repoName="golang" digest="test" />);
|
|
||||||
expect(await screen.findByText(/Nothing found/i)).toBeInTheDocument();
|
expect(await screen.findByText(/Nothing found/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log an error if the request fails', async () => {
|
|
||||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
|
||||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
render(<ReferredBy repoName="golang" digest="test" />);
|
|
||||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the digest when clicking the dropdowns', async () => {
|
it('should display the digest when clicking the dropdowns', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockReferrersList });
|
render(<ReferredBy referrers={mockReferrersList} />);
|
||||||
render(<ReferredBy repoName="golang" digest="test" />);
|
|
||||||
const firstDigest = (await screen.findAllByText(/digest/i))[0];
|
const firstDigest = (await screen.findAllByText(/digest/i))[0];
|
||||||
expect(firstDigest).toBeInTheDocument();
|
expect(firstDigest).toBeInTheDocument();
|
||||||
await userEvent.click(firstDigest);
|
await userEvent.click(firstDigest);
|
||||||
@ -91,8 +76,7 @@ describe('Referred by tab', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display the annotations when clicking the dropdown', async () => {
|
it('should display the annotations when clicking the dropdown', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockReferrersList });
|
render(<ReferredBy referrers={mockReferrersList} />);
|
||||||
render(<ReferredBy repoName="golang" digest="test" />);
|
|
||||||
const firstAnnotations = (await screen.findAllByText(/ANNOTATIONS/i))[0];
|
const firstAnnotations = (await screen.findAllByText(/ANNOTATIONS/i))[0];
|
||||||
expect(firstAnnotations).toBeInTheDocument();
|
expect(firstAnnotations).toBeInTheDocument();
|
||||||
await userEvent.click(firstAnnotations);
|
await userEvent.click(firstAnnotations);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { render, screen, waitFor, fireEvent } 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 { api } from 'api';
|
||||||
import VulnerabilitiesDetails from 'components/Tag/Tabs/VulnerabilitiesDetails';
|
import VulnerabilitiesDetails from 'components/Tag/Tabs/VulnerabilitiesDetails';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -432,6 +433,14 @@ const mockCVEList = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockCVEListFiltered = {
|
||||||
|
CVEListForImage: {
|
||||||
|
Tag: '',
|
||||||
|
Page: { ItemCount: 20, TotalCount: 20 },
|
||||||
|
CVEList: mockCVEList.CVEListForImage.CVEList.filter((e) => e.Id.includes('2022'))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const mockCVEFixed = {
|
const mockCVEFixed = {
|
||||||
pageOne: {
|
pageOne: {
|
||||||
ImageListWithCVEFixed: {
|
ImageListWithCVEFixed: {
|
||||||
@ -488,6 +497,16 @@ describe('Vulnerabilties page', () => {
|
|||||||
await waitFor(() => expect(screen.getAllByText(/fixed in/i)).toHaveLength(20));
|
await waitFor(() => expect(screen.getAllByText(/fixed in/i)).toHaveLength(20));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends filtered query if user types in the search bar', async () => {
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
||||||
|
render(<StateVulnerabilitiesWrapper />);
|
||||||
|
const cveSearchInput = screen.getByPlaceholderText(/search for/i);
|
||||||
|
jest.spyOn(api, 'get').mockRejectedValueOnce({ status: 200, data: { data: mockCVEListFiltered } });
|
||||||
|
await userEvent.type(cveSearchInput, '2022');
|
||||||
|
expect((await screen.queryAllByText(/2023/i).length) === 0);
|
||||||
|
expect((await screen.findAllByText(/2022/i)).length === 6);
|
||||||
|
});
|
||||||
|
|
||||||
it('renders no vulnerabilities if there are not any', async () => {
|
it('renders no vulnerabilities if there are not any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({
|
jest.spyOn(api, 'get').mockResolvedValue({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
13
src/api.js
13
src/api.js
@ -78,11 +78,16 @@ const endpoints = {
|
|||||||
detailedRepoInfo: (name) =>
|
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 NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag 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 NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Title Documentation DownloadCount Source Description Licenses}}}}`,
|
||||||
detailedImageInfo: (name, tag) =>
|
detailedImageInfo: (name, tag) =>
|
||||||
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned Vulnerabilities {MaxSeverity Count} 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 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 }) =>
|
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }, searchTerm = '') => {
|
||||||
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
let query = `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
}}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
|
}}`;
|
||||||
|
if (!isEmpty(searchTerm)) {
|
||||||
|
query += `, searchedCVE: "${searchTerm}"`;
|
||||||
|
}
|
||||||
|
return `${query}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`;
|
||||||
|
},
|
||||||
imageListWithCVEFixed: (cveId, repoName, { pageNumber = 1, pageSize = 3 }) =>
|
imageListWithCVEFixed: (cveId, repoName, { pageNumber = 1, pageSize = 3 }) =>
|
||||||
`/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}", requestedPage: {limit:${pageSize} offset:${
|
`/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}", requestedPage: {limit:${pageSize} offset:${
|
||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
|
@ -269,7 +269,7 @@ function Explore({ searchInputValue }) {
|
|||||||
if (!isLoading && !isEndOfList) {
|
if (!isLoading && !isEndOfList) {
|
||||||
return <div ref={listBottom} />;
|
return <div ref={listBottom} />;
|
||||||
}
|
}
|
||||||
return '';
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import React, { useEffect, useState, useMemo } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { makeStyles } from '@mui/styles';
|
import { makeStyles } from '@mui/styles';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { Divider, Typography, Stack } from '@mui/material';
|
import { Divider, Typography, Stack } from '@mui/material';
|
||||||
import ReferrerCard from '../../Shared/ReferrerCard';
|
import ReferrerCard from '../../Shared/ReferrerCard';
|
||||||
import Loading from '../../Shared/Loading';
|
import Loading from '../../Shared/Loading';
|
||||||
import { api, endpoints } from 'api';
|
|
||||||
import { host } from '../../../host';
|
|
||||||
import { mapReferrer } from 'utilities/objectModels';
|
import { mapReferrer } from 'utilities/objectModels';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
@ -17,36 +15,24 @@ const useStyles = makeStyles(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
function ReferredBy(props) {
|
function ReferredBy(props) {
|
||||||
const { repoName, digest } = props;
|
const { referrers } = props;
|
||||||
const [referrers, setReferrers] = useState([]);
|
const [referrersData, setReferrersData] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const abortController = useMemo(() => new AbortController(), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api
|
if (!isEmpty(referrers)) {
|
||||||
.get(`${host()}${endpoints.referrers({ repo: repoName, digest })}`, abortController.signal)
|
const mappedReferrersData = referrers.map((referrer) => mapReferrer(referrer));
|
||||||
.then((response) => {
|
setReferrersData(mappedReferrersData);
|
||||||
if (response.data && response.data.data) {
|
} else {
|
||||||
let referrersData = response.data.data.Referrers?.map((referrer) => mapReferrer(referrer));
|
setReferrersData([]);
|
||||||
if (!isEmpty(referrersData)) {
|
|
||||||
setReferrers(referrersData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
abortController.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderReferrers = () => {
|
const renderReferrers = () => {
|
||||||
return !isEmpty(referrers) ? (
|
return !isEmpty(referrersData) ? (
|
||||||
referrers.map((referrer, index) => {
|
referrersData.map((referrer, index) => {
|
||||||
return (
|
return (
|
||||||
<ReferrerCard
|
<ReferrerCard
|
||||||
key={index}
|
key={index}
|
||||||
@ -63,16 +49,6 @@ function ReferredBy(props) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderListBottom = () => {
|
|
||||||
if (isLoading) {
|
|
||||||
return <Loading />;
|
|
||||||
}
|
|
||||||
if (!isLoading) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="referred-by-container">
|
<div data-testid="referred-by-container">
|
||||||
<Typography
|
<Typography
|
||||||
@ -95,8 +71,7 @@ function ReferredBy(props) {
|
|||||||
/>
|
/>
|
||||||
<Stack direction="column" spacing={2}>
|
<Stack direction="column" spacing={2}>
|
||||||
<Stack direction="column" spacing={2}>
|
<Stack direction="column" spacing={2}>
|
||||||
{renderReferrers()}
|
{isLoading ? <Loading /> : renderReferrers()}
|
||||||
{renderListBottom()}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,16 +5,17 @@ import { api, endpoints } from '../../../api';
|
|||||||
|
|
||||||
// components
|
// components
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { Box, Card, CardContent, Divider, Stack, Typography } from '@mui/material';
|
import { Box, Card, CardContent, Divider, Stack, Typography, InputBase } from '@mui/material';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
import { host } from '../../../host';
|
import { host } from '../../../host';
|
||||||
import { isEmpty } from 'lodash';
|
import { debounce, isEmpty } from 'lodash';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Loading from '../../Shared/Loading';
|
import Loading from '../../Shared/Loading';
|
||||||
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
|
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
|
||||||
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||||
import { mapCVEInfo } from 'utilities/objectModels';
|
import { mapCVEInfo } from 'utilities/objectModels';
|
||||||
import { CVE_FIXEDIN_PAGE_SIZE, EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
import { CVE_FIXEDIN_PAGE_SIZE, EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
card: {
|
card: {
|
||||||
@ -81,6 +82,25 @@ const useStyles = makeStyles(() => ({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
position: 'relative',
|
||||||
|
minWidth: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: '1.7rem',
|
||||||
|
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||||
|
border: '0.125rem solid #E7E7E7',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
zIndex: 1155
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
color: '#52637A',
|
||||||
|
paddingRight: '3%'
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
color: '#464141',
|
||||||
|
marginLeft: 1,
|
||||||
|
width: '90%'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -236,27 +256,27 @@ function VulnerabilitiesDetails(props) {
|
|||||||
const { name, tag } = props;
|
const { name, tag } = props;
|
||||||
|
|
||||||
// pagination props
|
// pagination props
|
||||||
|
const [cveFilter, setCveFilter] = useState('');
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||||
const listBottom = useRef(null);
|
const listBottom = useRef(null);
|
||||||
|
|
||||||
const getPaginatedCVEs = () => {
|
const getPaginatedCVEs = () => {
|
||||||
setIsLoading(true);
|
|
||||||
api
|
api
|
||||||
.get(
|
.get(
|
||||||
`${host()}${endpoints.vulnerabilitiesForRepo(`${name}:${tag}`, { pageNumber, pageSize: EXPLORE_PAGE_SIZE })}`,
|
`${host()}${endpoints.vulnerabilitiesForRepo(
|
||||||
|
`${name}:${tag}`,
|
||||||
|
{ pageNumber, pageSize: EXPLORE_PAGE_SIZE },
|
||||||
|
cveFilter
|
||||||
|
)}`,
|
||||||
abortController.signal
|
abortController.signal
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data && response.data.data) {
|
if (response.data && response.data.data) {
|
||||||
let cveInfo = response.data.data.CVEListForImage?.CVEList;
|
let cveInfo = response.data.data.CVEListForImage?.CVEList;
|
||||||
let cveListData = mapCVEInfo(cveInfo);
|
let cveListData = mapCVEInfo(cveInfo);
|
||||||
const newCVEList = [...cveData, ...cveListData];
|
setCveData((previousState) => (pageNumber === 1 ? cveListData : [...previousState, ...cveListData]));
|
||||||
setCveData(newCVEList);
|
setIsEndOfList(response.data.data.CVEListForImage.Page?.ItemCount < EXPLORE_PAGE_SIZE);
|
||||||
setIsEndOfList(
|
|
||||||
response.data.data.CVEListForImage.Page?.ItemCount < EXPLORE_PAGE_SIZE ||
|
|
||||||
newCVEList.length >= response.data.data.CVEListForImage?.Page?.TotalCount
|
|
||||||
);
|
|
||||||
} else if (response.data.errors) {
|
} else if (response.data.errors) {
|
||||||
setIsEndOfList(true);
|
setIsEndOfList(true);
|
||||||
}
|
}
|
||||||
@ -270,11 +290,25 @@ function VulnerabilitiesDetails(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetPagination = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setIsEndOfList(false);
|
||||||
|
if (pageNumber !== 1) {
|
||||||
|
setPageNumber(1);
|
||||||
|
} else {
|
||||||
|
getPaginatedCVEs();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCveFilterChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setCveFilter(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedChangeHandler = useMemo(() => debounce(handleCveFilterChange, 300));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPaginatedCVEs();
|
getPaginatedCVEs();
|
||||||
return () => {
|
|
||||||
abortController.abort();
|
|
||||||
};
|
|
||||||
}, [pageNumber]);
|
}, [pageNumber]);
|
||||||
|
|
||||||
// setup intersection obeserver for infinite scroll
|
// setup intersection obeserver for infinite scroll
|
||||||
@ -302,6 +336,18 @@ function VulnerabilitiesDetails(props) {
|
|||||||
};
|
};
|
||||||
}, [isLoading, isEndOfList]);
|
}, [isLoading, isEndOfList]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading) return;
|
||||||
|
resetPagination();
|
||||||
|
}, [cveFilter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
debouncedChangeHandler.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderCVEs = () => {
|
const renderCVEs = () => {
|
||||||
return !isEmpty(cveData) ? (
|
return !isEmpty(cveData) ? (
|
||||||
cveData.map((cve, index) => {
|
cveData.map((cve, index) => {
|
||||||
@ -347,6 +393,17 @@ function VulnerabilitiesDetails(props) {
|
|||||||
width: '100%'
|
width: '100%'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Stack className={classes.search} direction="row" alignItems="center" justifyContent="space-between" spacing={2}>
|
||||||
|
<InputBase
|
||||||
|
style={{ paddingLeft: 10, height: 46, color: 'rgba(0, 0, 0, 0.6)' }}
|
||||||
|
placeholder={'Search for Vulnerabilities...'}
|
||||||
|
className={classes.input}
|
||||||
|
onChange={debouncedChangeHandler}
|
||||||
|
/>
|
||||||
|
<div className={classes.searchIcon}>
|
||||||
|
<SearchIcon />
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
<Stack direction="column" spacing={2}>
|
<Stack direction="column" spacing={2}>
|
||||||
<Stack direction="column" spacing={2}>
|
<Stack direction="column" spacing={2}>
|
||||||
{renderCVEs()}
|
{renderCVEs()}
|
||||||
|
@ -580,7 +580,7 @@ function TagDetails() {
|
|||||||
<VulnerabilitiesDetails name={reponame} tag={tag} />
|
<VulnerabilitiesDetails name={reponame} tag={tag} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="ReferredBy" className={classes.tabPanel}>
|
<TabPanel value="ReferredBy" className={classes.tabPanel}>
|
||||||
<ReferredBy repoName={reponame} digest={selectedManifest?.digest} />
|
<ReferredBy referrers={imageDetailData.referrers} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -44,6 +44,7 @@ const mapToImage = (responseImage) => {
|
|||||||
repoName: responseImage.RepoName,
|
repoName: responseImage.RepoName,
|
||||||
tag: responseImage.Tag,
|
tag: responseImage.Tag,
|
||||||
manifests: responseImage.Manifests?.map((manifest) => mapToManifest(manifest)) || [],
|
manifests: responseImage.Manifests?.map((manifest) => mapToManifest(manifest)) || [],
|
||||||
|
referrers: responseImage.Referrers,
|
||||||
size: responseImage.Size,
|
size: responseImage.Size,
|
||||||
downloadCount: responseImage.DownloadCount,
|
downloadCount: responseImage.DownloadCount,
|
||||||
lastUpdated: responseImage.LastUpdated,
|
lastUpdated: responseImage.LastUpdated,
|
||||||
|
@ -60,8 +60,19 @@ def pull_modify_push_image(logger, registry, image_name, tag, cosign_password,
|
|||||||
metafile = '{}_{}_metadata.json'.format(image_name, tag)
|
metafile = '{}_{}_metadata.json'.format(image_name, tag)
|
||||||
metafile = os.path.join(meta_dir_name, metafile)
|
metafile = os.path.join(meta_dir_name, metafile)
|
||||||
|
|
||||||
cmd = [image_update_script_path, "-r", registry, "-i", image_name, "-t", tag, "-c", cosign_password,
|
cmd = [image_update_script_path, "-r", registry, "-i", image_name, "-t", tag, "-f", metafile]
|
||||||
"-f", metafile, "-m", multiarch, "-u", username, "-p", password, "--data-dir", data_dir]
|
|
||||||
|
if data_dir:
|
||||||
|
cmd.extend(["--data-dir", data_dir])
|
||||||
|
|
||||||
|
if username:
|
||||||
|
cmd.extend(["-u", username, "-p", password])
|
||||||
|
|
||||||
|
if cosign_password:
|
||||||
|
cmd.extend(["-c", cosign_password])
|
||||||
|
|
||||||
|
if multiarch:
|
||||||
|
cmd.extend(["-m", multiarch])
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
cmd.append("-d")
|
cmd.append("-d")
|
||||||
|
@ -11,27 +11,91 @@ username=""
|
|||||||
debug=0
|
debug=0
|
||||||
data_dir=$(pwd)
|
data_dir=$(pwd)
|
||||||
|
|
||||||
options=$(getopt -o dr:i:t:u:p:c:m:f: -l debug,registry:,image:,tag:,username:,password:,cosign-password:,multiarch:,file:,data-dir: -- "$@")
|
while (( "$#" )); do
|
||||||
if [ $? -ne 0 ]; then
|
case $1 in
|
||||||
usage $0
|
-r|--registry)
|
||||||
exit 0
|
if [ -z "$2" ]; then
|
||||||
fi
|
echo "Option registry requires an argument"
|
||||||
|
exit 1
|
||||||
eval set -- "$options"
|
fi
|
||||||
while :; do
|
registry=$2;
|
||||||
case "$1" in
|
shift 2
|
||||||
-r|--registry) registry=$2; shift 2;;
|
;;
|
||||||
-i|--image) image=$2; shift 2;;
|
-i|--image)
|
||||||
-t|--tag) tag=$2; shift 2;;
|
if [ -z "$2" ]; then
|
||||||
-u|--username) username=$2; shift 2;;
|
echo "Option image requires an argument"
|
||||||
-p|--password) username=$2; shift 2;;
|
exit 1
|
||||||
-c|--cosign-password) cosign_password=$2; shift 2;;
|
fi
|
||||||
-m|--multiarch) multiarch=$2; shift 2;;
|
image=$2
|
||||||
-f|--file) metafile=$2; shift 2;;
|
shift 2
|
||||||
--data-dir) data_dir=$2; shift 2;;
|
;;
|
||||||
-d|--debug) debug=1; shift 1;;
|
-t|--tag)
|
||||||
--) shift 1; break;;
|
if [ -z "$2" ]; then
|
||||||
*) usage $0; exit 1;;
|
echo "Option tag requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tag=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-u|--username)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option username requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
username=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--password)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option password requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
password=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-c|--cosign-password)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option cosign-password requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cosign_password=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-m|--multiarch)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option multiarch requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
multiarch=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-f|--file)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option metafile requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
metafile=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--data-dir)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Option data-dir requires an argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
data_dir=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-d|--debug)
|
||||||
|
debug=1
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift 1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -90,8 +154,8 @@ license="$(cat ${docker_docs_dir}/${image}/license.md)"
|
|||||||
vendor="$(cat ${docker_docs_dir}/${image}/maintainer.md)"
|
vendor="$(cat ${docker_docs_dir}/${image}/maintainer.md)"
|
||||||
logo=$(base64 -w 0 ${docker_docs_dir}/${image}/logo.png)
|
logo=$(base64 -w 0 ${docker_docs_dir}/${image}/logo.png)
|
||||||
echo ${repo}
|
echo ${repo}
|
||||||
sed -i "s|%%GITHUB-REPO%%|${repo}|g" ${docker_docs_dir}/${image}/maintainer.md
|
sed -i.bak "s|%%GITHUB-REPO%%|${repo}|g" ${docker_docs_dir}/${image}/maintainer.md; rm ${docker_docs_dir}/${image}/maintainer.md.bak
|
||||||
sed -i "s|%%IMAGE%%|${image}|g" ${docker_docs_dir}/${image}/content.md
|
sed -i.bak "s|%%IMAGE%%|${image}|g" ${docker_docs_dir}/${image}/content.md; rm ${docker_docs_dir}/${image}/content.md.bak
|
||||||
doc=$(cat ${docker_docs_dir}/${image}/content.md)
|
doc=$(cat ${docker_docs_dir}/${image}/content.md)
|
||||||
|
|
||||||
local_image_ref_skopeo=oci:${images_dir}:${image}-${tag}
|
local_image_ref_skopeo=oci:${images_dir}:${image}-${tag}
|
||||||
|
Reference in New Issue
Block a user