refactor: Applied backend model mapping

Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
Raul Kele 2022-11-01 14:40:33 +02:00
parent 26783e61e5
commit 33dcc936bb
27 changed files with 135 additions and 229 deletions

10
.eslintignore Normal file
View File

@ -0,0 +1,10 @@
**/.git
**/.svn
**/.hg
**/node_modules
**/.github
README.md
LICENSE
Makefile
**/coverage
**/build

View File

@ -6,3 +6,5 @@
README.md
LICENSE
Makefile
**/coverage
**/build

View File

@ -28,7 +28,7 @@ function App() {
<Route path="/home" element={<HomePage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/image/:name" element={<RepoPage />} />
<Route path="/image/:name/tag/:tag" element={<TagPage />} />
<Route path="/image/:reponame/tag/:tag" element={<TagPage />} />
</Route>
<Route element={<AuthWrapper isLoggedIn={!isLoggedIn} redirect="/" />}>
<Route

View File

@ -8,7 +8,6 @@ import { MemoryRouter } from 'react-router-dom';
// router mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -136,7 +135,6 @@ afterEach(() => {
describe('Explore component', () => {
it("fetches image data and renders the list of images based on it's filters", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
expect(await screen.findByText(/alpine/i)).toBeInTheDocument();
@ -145,14 +143,12 @@ describe('Explore component', () => {
});
it('displays the no data message if no data is received', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { GlobalSearch: { Repos: [] } } } });
render(<StateExploreWrapper />);
expect(await screen.findByText(/Looks like/i)).toBeInTheDocument();
});
it('renders signature icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(1);
@ -160,7 +156,6 @@ describe('Explore component', () => {
});
it('renders vulnerability icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(1);
@ -172,7 +167,6 @@ describe('Explore component', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<StateExploreWrapper />);
@ -180,7 +174,6 @@ describe('Explore component', () => {
});
it("should render the sort filter and be able to change it's value", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
const selectFilter = await screen.findByText('Relevance');

View File

@ -6,7 +6,6 @@ import React from 'react';
// useNavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -129,7 +128,6 @@ afterEach(() => {
describe('Home component', () => {
it('fetches image data and renders popular, bookmarks and recently updated', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<Home />);
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(2));
@ -138,7 +136,6 @@ describe('Home component', () => {
});
it('renders signature icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<Home />);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(2);
@ -146,7 +143,6 @@ describe('Home component', () => {
});
it('renders vulnerability icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<Home />);
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(2);
@ -156,7 +152,6 @@ describe('Home component', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Home />);

View File

@ -7,7 +7,6 @@ import userEvent from '@testing-library/user-event';
// useNavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -25,7 +24,6 @@ describe('Signin component automatic navigation', () => {
it('navigates to homepage when auth is disabled', async () => {
// mock request to check auth
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: {} });
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
await waitFor(() => {
@ -37,7 +35,6 @@ describe('Signin component automatic navigation', () => {
describe('Sign in form', () => {
beforeEach(() => {
// mock auth check request
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 401, data: {} });
});
@ -70,7 +67,6 @@ describe('Sign in form', () => {
it('should log in the user and navigate to homepage if login is successful', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
const submitButton = await screen.findByText('Continue');
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } });
fireEvent.click(submitButton);
await waitFor(() => {

View File

@ -12,7 +12,6 @@ const mockUseLocationValue = {
};
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useParams: () => {
return { name: 'test' };
@ -161,34 +160,27 @@ afterEach(() => {
describe('Repo details component', () => {
it('fetches repo detailed data and renders', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails />);
expect(await screen.findByText('test')).toBeInTheDocument();
});
it('renders vulnerability icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails />);
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsNone } });
render(<RepoDetails />);
expect(await screen.findAllByTestId('none-vulnerability-icon')).toHaveLength(1);
// @ts-ignore
// jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsUnknown } });
// render(<RepoDetails />);
// expect(await screen.findAllByTestId('unknown-vulnerability-icon')).toHaveLength(1);
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsLow } });
render(<RepoDetails />);
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(1);
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsMedium } });
render(<RepoDetails />);
expect(await screen.findAllByTestId('medium-vulnerability-icon')).toHaveLength(1);
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsHigh } });
render(<RepoDetails />);
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(1);
@ -202,7 +194,6 @@ describe('Repo details component', () => {
});
it('should switch between tabs', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails />);
expect(await screen.findByTestId('overview-container')).toBeInTheDocument();

View File

@ -4,7 +4,6 @@ import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useLocation: () => ({
pathname: 'localhost:3000/image/test',

View File

@ -4,7 +4,6 @@ import React from 'react';
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));

View File

@ -6,7 +6,6 @@ import PreviewCard from 'components/PreviewCard';
// usenavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));

View File

@ -6,7 +6,6 @@ import RepoCard from 'components/RepoCard';
// usenavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));

View File

@ -7,7 +7,6 @@ import React from 'react';
// router mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -71,7 +70,6 @@ afterEach(() => {
describe('Search component', () => {
it('should display suggestions when user searches', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<SearchSuggestion />);
const searchInput = screen.getByPlaceholderText(/search for content/i);
@ -81,7 +79,6 @@ describe('Search component', () => {
});
it('should navigate to repo page when a repo suggestion is clicked', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<SearchSuggestion />);
const searchInput = screen.getByPlaceholderText(/search for content/i);
@ -92,7 +89,6 @@ describe('Search component', () => {
});
it('should navigate to repo page when a image suggestion is clicked', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
render(<SearchSuggestion />);
const searchInput = screen.getByPlaceholderText(/search for content/i);
@ -103,7 +99,6 @@ describe('Search component', () => {
});
it('should log an error if it doesnt receive an ok response for repo search', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<SearchSuggestion />);
@ -113,7 +108,6 @@ describe('Search component', () => {
});
it('should log an error if it doesnt receive an ok response for image search', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<SearchSuggestion />);

View File

@ -56,7 +56,6 @@ const RouterDependsWrapper = () => {
// useNavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -68,14 +67,12 @@ afterEach(() => {
describe('Dependencies tab', () => {
it('should render the dependencies if there are any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockDependenciesList });
render(<RouterDependsWrapper />);
expect(await screen.findAllByText(/Tag/i)).toHaveLength(8);
});
it('renders no dependencies if there are not any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({
status: 200,
data: { data: { BaseImageList: [] } }
@ -85,7 +82,6 @@ describe('Dependencies tab', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<RouterDependsWrapper />);

View File

@ -33,14 +33,12 @@ afterEach(() => {
describe('Layers page', () => {
it('renders the layers if there are any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { Image: { History: mockLayersList } } } });
render(<HistoryLayers name="alpine:latest" />);
expect(await screen.findAllByTestId('layer-card-container')).toHaveLength(2);
});
it('renders no layers if there are not any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({
status: 200,
data: { data: { History: { Tag: '', mockLayersList: [] } } }
@ -50,7 +48,6 @@ describe('Layers page', () => {
});
it('renders hash layers', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { Image: { History: mockLayersList } } } });
render(<HistoryLayers name="alpine:latest" />);
expect(await screen.findAllByTestId('hash-typography')).toHaveLength(1);
@ -60,7 +57,6 @@ describe('Layers page', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<HistoryLayers name="alpine:latest" />);

View File

@ -56,7 +56,6 @@ const RouterDependsWrapper = () => {
// useNavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate
}));
@ -68,14 +67,12 @@ afterEach(() => {
describe('Dependents tab', () => {
it('should render the dependents if there are any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockDependentsList });
render(<RouterDependsWrapper />);
expect(await screen.findAllByText(/tag/i)).toHaveLength(8);
});
it('renders no dependents if there are not any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({
status: 200,
data: { data: { DerivedImageList: [] } }
@ -85,7 +82,6 @@ describe('Dependents tab', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<RouterDependsWrapper />);

View File

@ -170,7 +170,6 @@ Object.assign(navigator, {
});
jest.mock('react-router-dom', () => ({
// @ts-ignore
...jest.requireActual('react-router-dom'),
useParams: () => {
return { name: 'test', tag: '1.0.1' };
@ -193,7 +192,6 @@ afterEach(() => {
describe('Tags details', () => {
it('should show tabs and allow nagivation between them', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetails />);
const dependenciesTab = await screen.findByTestId('dependencies-tab');
@ -210,34 +208,32 @@ describe('Tags details', () => {
});
it('should show tag details metadata', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetails />);
expect(await screen.findByTestId('tagDetailsMetadata-container')).toBeInTheDocument();
});
it('renders vulnerability icons', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetails />);
expect(await screen.findByTestId('critical-vulnerability-icon')).toBeInTheDocument();
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageNone } });
render(<TagDetails />);
expect(await screen.findByTestId('none-vulnerability-icon')).toBeInTheDocument();
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageUnknown } });
render(<TagDetails />);
expect(await screen.findByTestId('unknown-vulnerability-icon')).toBeInTheDocument();
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageLow } });
render(<TagDetails />);
expect(await screen.findByTestId('low-vulnerability-icon')).toBeInTheDocument();
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageMedium } });
render(<TagDetails />);
expect(await screen.findByTestId('medium-vulnerability-icon')).toBeInTheDocument();
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageHigh } });
render(<TagDetails />);
expect(await screen.findByTestId('high-vulnerability-icon')).toBeInTheDocument();
@ -246,7 +242,7 @@ describe('Tags details', () => {
it('should copy the pull string to clipboard', async () => {
jest
.spyOn(api, 'get')
// @ts-ignore
.mockResolvedValue({ status: 200, data: { data: mockImage } });
render(<TagDetails />);
fireEvent.click(await screen.findByTestId('pullcopy-btn'));

View File

@ -452,7 +452,6 @@ afterEach(() => {
describe('Vulnerabilties page', () => {
it('renders the vulnerabilities if there are any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
render(<StateVulnerabilitiesWrapper />);
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
@ -460,7 +459,6 @@ describe('Vulnerabilties page', () => {
});
it('renders no vulnerabilities if there are not any', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({
status: 200,
data: { data: { CVEListForImage: { Tag: '', CVEList: [] } } }
@ -470,7 +468,6 @@ describe('Vulnerabilties page', () => {
});
it('should open and close description dropdown for vulnerabilities', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
render(<StateVulnerabilitiesWrapper />);
await waitFor(() => expect(screen.getAllByText(/description/i)).toHaveLength(20));
@ -486,7 +483,6 @@ describe('Vulnerabilties page', () => {
});
it("should log an error when data can't be fetched", async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<StateVulnerabilitiesWrapper />);
@ -496,9 +492,9 @@ describe('Vulnerabilties page', () => {
it('should find out which version fixes the CVEs', async () => {
jest
.spyOn(api, 'get')
// @ts-ignore
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
// @ts-ignore
.mockResolvedValue({ status: 200, data: { data: mockCVEFixed } });
render(<StateVulnerabilitiesWrapper />);
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));

View File

@ -1,4 +1,3 @@
// @ts-nocheck
import axios from 'axios';
import { isEmpty } from 'lodash';
import { sortByCriteria } from 'utilities/sortCriteria';

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState, useMemo } from 'react';
import { isEmpty } from 'lodash';
// utility
import { api, endpoints } from '../api';
@ -9,6 +10,7 @@ import makeStyles from '@mui/styles/makeStyles';
import { host } from '../host';
import Loading from './Loading';
import TagCard from './TagCard';
import { mapToImage } from 'utilities/objectModels';
const useStyles = makeStyles(() => ({
card: {
@ -78,7 +80,7 @@ function DependsOn(props) {
.get(`${host()}${endpoints.dependsOnForImage(name)}`, abortController.signal)
.then((response) => {
if (response.data && response.data.data) {
let imagesData = response.data.data.BaseImageList;
let imagesData = response.data.data.BaseImageList?.map((img) => mapToImage(img));
setImages(imagesData);
}
setIsLoading(false);
@ -93,20 +95,19 @@ function DependsOn(props) {
}, []);
const renderDependencies = () => {
return images?.length ? (
return !isEmpty(images) ? (
images.map((dependence, index) => {
return (
<TagCard
repoName={dependence.RepoName}
tag={dependence.Tag}
vendor={dependence.Vendor}
platform={dependence.Platform}
isSigned={dependence.IsSigned}
size={dependence.Size}
digest={dependence.Digest}
repoName={dependence.repoName}
tag={dependence.tag}
vendor={dependence.vendor}
platform={dependence.platform}
isSigned={dependence.isSigned}
size={dependence.size}
digest={dependence.digest}
key={index}
vulnerabiltySeverity={dependence.Vulnerabilities?.MaxSeverity}
lastUpdated={dependence.LastUpdated}
lastUpdated={dependence.lastUpdated}
/>
);
})

View File

@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { isEmpty } from 'lodash';
// utility
import { api, endpoints } from '../api';
@ -9,6 +10,7 @@ import makeStyles from '@mui/styles/makeStyles';
import { host } from '../host';
import Loading from './Loading';
import TagCard from './TagCard';
import { mapToImage } from 'utilities/objectModels';
const useStyles = makeStyles(() => ({
card: {
@ -78,7 +80,7 @@ function IsDependentOn(props) {
.get(`${host()}${endpoints.isDependentOnForImage(name)}`, abortController.signal)
.then((response) => {
if (response.data && response.data.data) {
let imageData = response.data.data.DerivedImageList;
let imageData = response.data.data.DerivedImageList?.map((img) => mapToImage(img));
setImages(imageData);
}
setIsLoading(false);
@ -93,20 +95,19 @@ function IsDependentOn(props) {
}, []);
const renderDependents = () => {
return images?.length ? (
return !isEmpty(images) ? (
images.map((dependence, index) => {
return (
<TagCard
repoName={dependence.RepoName}
tag={dependence.Tag}
vendor={dependence.Vendor}
platform={dependence.Platform}
isSigned={dependence.IsSigned}
size={dependence.Size}
digest={dependence.Digest}
repoName={dependence.repoName}
tag={dependence.tag}
vendor={dependence.vendor}
platform={dependence.platform}
isSigned={dependence.isSigned}
size={dependence.size}
digest={dependence.digest}
key={index}
vulnerabiltySeverity={dependence.Vulnerabilities?.MaxSeverity}
lastUpdated={dependence.LastUpdated}
lastUpdated={dependence.lastUpdated}
/>
);
})

View File

@ -22,8 +22,8 @@ import RepoDetailsMetadata from './RepoDetailsMetadata';
import Loading from './Loading';
import { isEmpty } from 'lodash';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
// @ts-ignore
const useStyles = makeStyles(() => ({
pageWrapper: {
backgroundColor: '#FFFFFF',
@ -123,7 +123,7 @@ const randomImage = () => {
function RepoDetails() {
const [repoDetailData, setRepoDetailData] = useState({});
const [tags, setTags] = useState([]);
// @ts-ignore
const [isLoading, setIsLoading] = useState(true);
const [selectedTab, setSelectedTab] = useState('Overview');
// get url param from <Route here (i.e. image name)
@ -137,25 +137,7 @@ function RepoDetails() {
.then((response) => {
if (response.data && response.data.data) {
let repoInfo = response.data.data.ExpandedRepoInfo;
let imageData = {
name: name,
images: repoInfo.Images,
lastUpdated: repoInfo.Summary?.LastUpdated,
size: repoInfo.Summary?.Size,
platforms: repoInfo.Summary?.Platforms,
vendors: repoInfo.Summary?.Vendors,
newestTag: repoInfo.Summary?.NewestImage,
description: repoInfo.Summary?.NewestImage?.Description,
title: repoInfo.Summary?.NewestImage?.Title,
source: repoInfo.Summary?.NewestImage?.Source,
downloads: repoInfo.Summary?.NewestImage?.DownloadCount,
overview: repoInfo.Summary?.NewestImage?.Documentation,
license: repoInfo.Summary?.NewestImage?.Licenses,
vulnerabiltySeverity: repoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
vulnerabilityCount: repoInfo.Summary?.NewestImage?.Vulnerabilities?.Count,
isSigned: repoInfo.Summary?.NewestImage?.IsSigned,
logo: repoInfo.Summary?.NewestImage?.Logo
};
let imageData = mapToRepoFromRepoInfo(repoInfo);
setRepoDetailData(imageData);
setTags(imageData.images);
}
@ -173,7 +155,6 @@ function RepoDetails() {
}, [name]);
const platformChips = () => {
// @ts-ignore
const platforms = repoDetailData?.platforms || [];
return platforms.map((platform, index) => (
@ -200,8 +181,6 @@ function RepoDetails() {
));
};
// @ts-ignore
// @ts-ignore
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
};
@ -220,10 +199,7 @@ function RepoDetails() {
alignSelf: 'stretch'
}}
>
{
// @ts-ignore
repoDetailData.description || 'Description not available'
}
{repoDetailData.description || 'Description not available'}
</Typography>
</CardContent>
</Card>
@ -247,13 +223,10 @@ function RepoDetails() {
img: classes.avatar
}}
component="img"
// @ts-ignore
// eslint-disable-next-line prettier/prettier
image={
// @ts-ignore
!isEmpty(repoDetailData?.logo)
? // @ts-ignore
`data:image/png;base64, ${repoDetailData?.logo}`
? `data:image/png;base64, ${repoDetailData?.logo}`
: randomImage()
}
alt="icon"
@ -262,19 +235,10 @@ function RepoDetails() {
{name}
</Typography>
<VulnerabilityIconCheck
vulnerabilitySeverity={
// @ts-ignore
repoDetailData.vulnerabiltySeverity
}
// @ts-ignore
vulnerabilitySeverity={repoDetailData.vulnerabiltySeverity}
count={repoDetailData?.vulnerabilityCount}
/>
<SignatureIconCheck
isSigned={
// @ts-ignore
repoDetailData.isSigned
}
/>
<SignatureIconCheck isSigned={repoDetailData.isSigned} />
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
</Stack>
<Typography
@ -283,10 +247,7 @@ function RepoDetails() {
gutterBottom
align="left"
>
{
// @ts-ignore
repoDetailData?.title || 'Title not available'
}
{repoDetailData?.title || 'Title not available'}
</Typography>
<Stack alignItems="center" sx={{ paddingLeft: '4rem' }} direction="row" spacing={2} pt={1}>
{platformChips()}
@ -320,17 +281,11 @@ function RepoDetails() {
</Grid>
<Grid item xs={4} className={classes.metadata}>
<RepoDetailsMetadata
// @ts-ignore
totalDownloads={repoDetailData?.downloads}
// @ts-ignore
repoURL={repoDetailData?.source}
// @ts-ignore
lastUpdated={repoDetailData?.lastUpdated}
// @ts-ignore
size={repoDetailData?.size}
// @ts-ignore
latestTag={repoDetailData?.newestTag}
// @ts-ignore
license={repoDetailData?.license}
/>
</Grid>

View File

@ -37,7 +37,7 @@ const useStyles = makeStyles(() => ({
function RepoDetailsMetadata(props) {
const classes = useStyles();
const { repoURL, totalDownloads, lastUpdated, size, license } = props;
// @ts-ignore
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
unit: ['weeks', 'days', 'hours', 'minutes']
});

View File

@ -65,7 +65,7 @@ export default function TagCard(props) {
//const tags = data && data.tags;
const [open, setOpen] = useState(false);
const classes = useStyles();
// @ts-ignore
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({
unit: ['weeks', 'days', 'hours', 'minutes']
});

View File

@ -3,7 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react';
// utility
import { api, endpoints } from '../api';
import { mapToImage } from '../utilities/objectModels';
// components
import {
Box,
@ -41,7 +41,6 @@ import Loading from './Loading';
import { dockerPull, podmanPull, skopeoPull } from 'utilities/pullStrings';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
// @ts-ignore
const useStyles = makeStyles(() => ({
pageWrapper: {
backgroundColor: '#FFFFFF',
@ -157,8 +156,7 @@ function TagDetails() {
const abortController = useMemo(() => new AbortController(), []);
// get url param from <Route here (i.e. image name)
const { name, tag } = useParams();
const [fullName, setFullName] = useState(name + ':' + tag);
const { reponame, tag } = useParams();
const [pullString, setPullString] = useState('');
const [snackBarOpen, setSnackbarOpen] = useState(false);
@ -170,28 +168,13 @@ function TagDetails() {
window?.scrollTo(0, 0);
setIsLoading(true);
api
.get(`${host()}${endpoints.detailedImageInfo(name, tag)}`, abortController.signal)
.get(`${host()}${endpoints.detailedImageInfo(reponame, tag)}`, abortController.signal)
.then((response) => {
if (response.data && response.data.data) {
let imageInfo = response.data.data.Image;
let imageData = {
name: imageInfo.RepoName,
tag: imageInfo.Tag,
lastUpdated: imageInfo.LastUpdated,
size: imageInfo.Size,
digest: imageInfo.ConfigDigest,
platform: imageInfo.Platform,
vendor: imageInfo.Vendor,
history: imageInfo.History,
license: imageInfo.Licenses,
vulnerabiltySeverity: imageInfo.Vulnerabilities?.MaxSeverity,
vulnerabilityCount: imageInfo.Vulnerabilities?.Count,
isSigned: imageInfo.IsSigned,
logo: imageInfo.Logo
};
let imageData = mapToImage(imageInfo);
setImageDetailData(imageData);
setFullName(imageData.name + ':' + imageData.tag);
setPullString(dockerPull(imageData.name + ':' + imageData.tag));
setPullString(dockerPull(imageData.name));
}
setIsLoading(false);
})
@ -203,14 +186,12 @@ function TagDetails() {
return () => {
abortController.abort();
};
}, [name, tag]);
}, [reponame, tag]);
const getPlatform = () => {
// @ts-ignore
return imageDetailData?.platform ? imageDetailData.platform : '--/--';
};
// @ts-ignore
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
};
@ -245,32 +226,20 @@ function TagDetails() {
}}
component="img"
image={
// @ts-ignore
// eslint-disable-next-line prettier/prettier
!isEmpty(imageDetailData?.logo)
? // @ts-ignore
`data:image/ png;base64, ${imageDetailData?.logo}`
? `data:image/ png;base64, ${imageDetailData?.logo}`
: randomImage()
}
alt="icon"
/>
<Typography variant="h3" className={classes.repoName}>
{name}:{tag}
{reponame}:{tag}
</Typography>
<VulnerabilityIconCheck
vulnerabilitySeverity={
// @ts-ignore
imageDetailData.vulnerabiltySeverity
}
// @ts-ignore
vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity}
count={imageDetailData.vulnerabilityCount}
/>
<SignatureIconCheck
isSigned={
// @ts-ignore
imageDetailData.isSigned
}
/>
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
</Stack>
<Typography
@ -279,11 +248,7 @@ function TagDetails() {
gutterBottom
align="left"
>
DIGEST:{' '}
{
// @ts-ignore
imageDetailData?.digest
}
DIGEST: {imageDetailData?.digest}
</Typography>
</Grid>
<Grid item xs={4} className={classes.pull}>
@ -320,14 +285,14 @@ function TagDetails() {
classes: { root: classes.copyStringSelect, list: classes.textEllipsis }
}}
>
<MenuItem className={classes.textEllipsis} value={dockerPull(fullName)}>
<Typography noWrap>{dockerPull(fullName)}</Typography>
<MenuItem className={classes.textEllipsis} value={dockerPull(imageDetailData.name)}>
<Typography noWrap>{dockerPull(imageDetailData.name)}</Typography>
</MenuItem>
<MenuItem className={classes.textEllipsis} value={podmanPull(fullName)}>
<Typography noWrap>{podmanPull(fullName)}</Typography>
<MenuItem className={classes.textEllipsis} value={podmanPull(imageDetailData.name)}>
<Typography noWrap>{podmanPull(imageDetailData.name)}</Typography>
</MenuItem>
<MenuItem className={classes.textEllipsis} value={skopeoPull(fullName)}>
<Typography noWrap>{skopeoPull(fullName)}</Typography>
<MenuItem className={classes.textEllipsis} value={skopeoPull(imageDetailData.name)}>
<Typography noWrap>{skopeoPull(imageDetailData.name)}</Typography>
</MenuItem>
</Select>
</FormControl>
@ -355,22 +320,16 @@ function TagDetails() {
<Grid container>
<Grid item xs={12}>
<TabPanel value="Layers" className={classes.tabPanel}>
<HistoryLayers
name={fullName}
history={
// @ts-ignore
imageDetailData.history
}
/>
<HistoryLayers name={imageDetailData.name} history={imageDetailData.history} />
</TabPanel>
<TabPanel value="DependsOn" className={classes.tabPanel}>
<DependsOn name={fullName} />
<DependsOn name={imageDetailData.name} />
</TabPanel>
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
<IsDependentOn name={fullName} />
<IsDependentOn name={imageDetailData.name} />
</TabPanel>
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
<VulnerabilitiesDetails name={name} tag={tag} />
<VulnerabilitiesDetails name={reponame} tag={tag} />
</TabPanel>
</Grid>
</Grid>
@ -379,13 +338,9 @@ function TagDetails() {
</Grid>
<Grid item xs={4} className={classes.metadata}>
<TagDetailsMetadata
// @ts-ignore
platform={getPlatform()}
// @ts-ignore
size={imageDetailData?.size}
// @ts-ignore
lastUpdated={imageDetailData?.lastUpdated}
// @ts-ignore
license={imageDetailData?.license}
/>
</Grid>

View File

@ -13,6 +13,7 @@ import { Link } from 'react-router-dom';
import Loading from './Loading';
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { mapCVEInfo } from 'utilities/objectModels';
const useStyles = makeStyles(() => ({
card: {
@ -96,7 +97,7 @@ function VulnerabilitiyCard(props) {
}
setLoadingFixed(true);
api
.get(`${host()}${endpoints.imageListWithCVEFixed(cve.Id, name)}`)
.get(`${host()}${endpoints.imageListWithCVEFixed(cve.id, name)}`)
.then((response) => {
if (response.data && response.data.data) {
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.map((e) => e.Tag);
@ -129,14 +130,14 @@ function VulnerabilitiyCard(props) {
<Stack sx={{ flexDirection: 'row' }}>
<Typography variant="body1" align="left" className={classes.values}>
{' '}
{cve.Id}
{cve.id}
</Typography>
</Stack>
<VulnerabilityChipCheck vulnerabilitySeverity={cve.Severity} />
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
<Stack sx={{ flexDirection: 'row' }}>
<Typography variant="body1" align="left" className={classes.values}>
{' '}
{cve.Title}
{cve.title}
</Typography>
</Stack>
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
@ -167,7 +168,7 @@ function VulnerabilitiyCard(props) {
<Box>
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
{' '}
{cve.Description}{' '}
{cve.description}{' '}
</Typography>
</Box>
</Collapse>
@ -190,9 +191,7 @@ function VulnerabilitiesDetails(props) {
.then((response) => {
if (response.data && response.data.data) {
let cveInfo = response.data.data.CVEListForImage;
let cveListData = {
cveList: cveInfo?.CVEList
};
let cveListData = mapCVEInfo(cveInfo);
setCveData(cveListData);
}
setIsLoading(false);
@ -249,14 +248,7 @@ function VulnerabilitiesDetails(props) {
width: '100%'
}}
/>
{isLoading ? (
<Loading />
) : (
renderCVEs(
// @ts-ignore
cveData?.cveList
)
)}
{isLoading ? <Loading /> : renderCVEs(cveData?.cveList)}
</>
);
}

View File

@ -17,14 +17,60 @@ const mapToRepo = (responseRepo) => {
};
};
const mapToRepoFromRepoInfo = (responseRepoInfo) => {
return {
name: responseRepoInfo.Summary?.Name,
images: responseRepoInfo.Images,
lastUpdated: responseRepoInfo.Summary?.LastUpdated,
size: responseRepoInfo.Summary?.Size,
platforms: responseRepoInfo.Summary?.Platforms,
vendors: responseRepoInfo.Summary?.Vendors,
newestTag: responseRepoInfo.Summary?.NewestImage,
description: responseRepoInfo.Summary?.NewestImage?.Description,
title: responseRepoInfo.Summary?.NewestImage?.Title,
source: responseRepoInfo.Summary?.NewestImage?.Source,
downloads: responseRepoInfo.Summary?.NewestImage?.DownloadCount,
overview: responseRepoInfo.Summary?.NewestImage?.Documentation,
license: responseRepoInfo.Summary?.NewestImage?.Licenses,
vulnerabiltySeverity: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
vulnerabilityCount: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.Count,
isSigned: responseRepoInfo.Summary?.NewestImage?.IsSigned,
logo: responseRepoInfo.Summary?.NewestImage?.Logo
};
};
const mapToImage = (responseImage) => {
return {
repoName: responseImage.RepoName,
tag: responseImage.Tag,
lastUpdated: responseImage.LastUpdated,
size: responseImage.Size,
digest: responseImage.ConfigDigest,
platform: responseImage.Platform,
vendor: responseImage.Vendor,
history: responseImage.History,
license: responseImage.Licenses,
vulnerabiltySeverity: responseImage.Vulnerabilities?.MaxSeverity,
vulnerabilityCount: responseImage.Vulnerabilities?.Count,
isSigned: responseImage.IsSigned,
logo: responseImage.Logo,
// frontend only prop to increase interop with Repo objects and code reusability
name: `${responseImage.RepoName}:${responseImage.Tag}`,
logo: responseImage.Logo
name: `${responseImage.RepoName}:${responseImage.Tag}`
};
};
export { mapToRepo, mapToImage };
const mapCVEInfo = (cveInfo) => {
const cveList = cveInfo.CVEList?.map((cve) => {
return {
id: cve.Id,
severity: cve.Severity,
title: cve.Title,
description: cve.Description
};
});
return {
cveList
};
};
export { mapToRepo, mapToImage, mapToRepoFromRepoInfo, mapCVEInfo };

View File

@ -15,7 +15,7 @@ const transform = {
let value = bytes / Math.pow(k, unitIdx);
// minimum 2 significant digits
// @ts-ignore
value = value < 10 ? value.toPrecision(2) : Math.round(value);
return value + ' ' + DATA_UNITS[unitIdx];