Compare commits
2 Commits
commit-1cf
...
commit-902
Author | SHA1 | Date | |
---|---|---|---|
|
9029b97b47 | ||
|
4db0c2ee8b |
@@ -11,37 +11,49 @@ jest.mock('react-router-dom', () => ({
|
|||||||
|
|
||||||
const mockedTagsData = [
|
const mockedTagsData = [
|
||||||
{
|
{
|
||||||
Digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
tag: 'latest',
|
||||||
Tag: 'latest',
|
vendor: 'test1',
|
||||||
LastUpdated: '2022-07-19T18:06:18.818788283Z',
|
manifests: [
|
||||||
Vendor: 'test1',
|
{
|
||||||
Size: '569130088',
|
lastUpdated: '2022-07-19T18:06:18.818788283Z',
|
||||||
Platform: {
|
digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
||||||
Os: 'linux',
|
size: '569130088',
|
||||||
Arch: 'amd64'
|
platform: {
|
||||||
}
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
tag: 'bullseye',
|
||||||
Tag: 'bullseye',
|
vendor: 'test1',
|
||||||
LastUpdated: '2022-07-19T18:06:18.818788283Z',
|
manifests: [
|
||||||
Vendor: 'test1',
|
{
|
||||||
Size: '569130088',
|
digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
||||||
Platform: {
|
lastUpdated: '2022-07-19T18:06:18.818788283Z',
|
||||||
Os: 'linux',
|
size: '569130088',
|
||||||
Arch: 'amd64'
|
platform: {
|
||||||
}
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
tag: '1.5.2',
|
||||||
Tag: '1.5.2',
|
vendor: 'test1',
|
||||||
LastUpdated: '2022-07-19T18:06:18.818788283Z',
|
manifests: [
|
||||||
Vendor: 'test1',
|
{
|
||||||
Size: '569130088',
|
lastUpdated: '2022-07-19T18:06:18.818788283Z',
|
||||||
Platform: {
|
digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559',
|
||||||
Os: 'linux',
|
size: '569130088',
|
||||||
Arch: 'amd64'
|
platform: {
|
||||||
}
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -60,7 +72,20 @@ describe('Tags component', () => {
|
|||||||
const tagLink = await screen.findByText('latest');
|
const tagLink = await screen.findByText('latest');
|
||||||
fireEvent.click(tagLink);
|
fireEvent.click(tagLink);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockedUsedNavigate).toHaveBeenCalledWith('tag/latest');
|
expect(mockedUsedNavigate).toHaveBeenCalledWith('tag/latest', { state: { digest: null } });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to specific manifest when clicking the digest', async () => {
|
||||||
|
render(<Tags tags={mockedTagsData} />);
|
||||||
|
const openBtn = screen.getAllByText(/digest/i);
|
||||||
|
await fireEvent.click(openBtn[0]);
|
||||||
|
const tagLink = await screen.findByText(/sha256:adca4/i);
|
||||||
|
fireEvent.click(tagLink);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockedUsedNavigate).toHaveBeenCalledWith('tag/latest', {
|
||||||
|
state: { digest: 'sha256:adca4815c494becc1bf053af0c4640b2d81ab1a779e6d649e1b8b92a75f1d559' }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||||
Tag: 'tag1',
|
Tag: 'tag1',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 5
|
Count: 5
|
||||||
@@ -20,6 +21,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag2',
|
RepoName: 'tag2',
|
||||||
Tag: 'tag2',
|
Tag: 'tag2',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'CRITICAL',
|
MaxSeverity: 'CRITICAL',
|
||||||
Count: 2
|
Count: 2
|
||||||
@@ -28,6 +30,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag3',
|
RepoName: 'tag3',
|
||||||
Tag: 'tag3',
|
Tag: 'tag3',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'LOW',
|
MaxSeverity: 'LOW',
|
||||||
Count: 7
|
Count: 7
|
||||||
@@ -36,6 +39,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag4',
|
RepoName: 'tag4',
|
||||||
Tag: 'tag4',
|
Tag: 'tag4',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 5
|
Count: 5
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||||
import { api } from 'api';
|
|
||||||
import HistoryLayers from 'components/Tag/Tabs/HistoryLayers';
|
import HistoryLayers from 'components/Tag/Tabs/HistoryLayers';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const mockLayersList = [
|
const mockLayersList = [
|
||||||
{
|
{
|
||||||
Layer: { Size: '2806054', Digest: '213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49', Score: null },
|
Layer: { Size: '2806054', Digest: '213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49' },
|
||||||
HistoryDescription: {
|
HistoryDescription: {
|
||||||
Created: '2022-08-09T17:19:53.274069586Z',
|
Created: '2022-08-09T17:19:53.274069586Z',
|
||||||
CreatedBy: '/bin/sh -c #(nop) ADD file:2a949686d9886ac7c10582a6c29116fd29d3077d02755e87e111870d63607725 in / ',
|
CreatedBy: '/bin/sh -c #(nop) ADD file:2a949686d9886ac7c10582a6c29116fd29d3077d02755e87e111870d63607725 in / ',
|
||||||
@@ -33,33 +32,20 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe('Layers page', () => {
|
describe('Layers page', () => {
|
||||||
it('renders the layers if there are any', async () => {
|
it('renders the layers if there are any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { Image: { History: mockLayersList } } } });
|
render(<HistoryLayers name="alpine:latest" history={mockLayersList} />);
|
||||||
render(<HistoryLayers name="alpine:latest" />);
|
|
||||||
expect(await screen.findAllByTestId('layer-card-container')).toHaveLength(1);
|
expect(await screen.findAllByTestId('layer-card-container')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders no layers if there are not any', async () => {
|
it('renders no layers if there are not any', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({
|
render(<HistoryLayers name="alpine:latest" history={[]} />);
|
||||||
status: 200,
|
|
||||||
data: { data: { History: { Tag: '', mockLayersList: [] } } }
|
|
||||||
});
|
|
||||||
render(<HistoryLayers name="alpine:latest" />);
|
|
||||||
await waitFor(() => expect(screen.getAllByText(/No Layer data available/i)).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText(/No Layer data available/i)).toHaveLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens dropdown and renders layer command and digest', async () => {
|
it('opens dropdown and renders layer command and digest', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { Image: { History: mockLayersList } } } });
|
render(<HistoryLayers name="alpine:latest" history={mockLayersList} />);
|
||||||
render(<HistoryLayers name="alpine:latest" />);
|
|
||||||
expect(screen.queryAllByText(/DIGEST/i)).toHaveLength(0);
|
expect(screen.queryAllByText(/DIGEST/i)).toHaveLength(0);
|
||||||
const openDetails = await screen.findAllByText(/details/i);
|
const openDetails = await screen.findAllByText(/details/i);
|
||||||
fireEvent.click(openDetails[0]);
|
fireEvent.click(openDetails[0]);
|
||||||
expect(await screen.findAllByText(/DIGEST/i)).toHaveLength(1);
|
expect(await screen.findAllByText(/DIGEST/i)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should log an error when data can't be fetched", async () => {
|
|
||||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
|
||||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
render(<HistoryLayers name="alpine:latest" />);
|
|
||||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -12,6 +12,7 @@ const mockDependentsList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||||
Tag: 'tag1',
|
Tag: 'tag1',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 5
|
Count: 5
|
||||||
@@ -20,6 +21,7 @@ const mockDependentsList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag2',
|
RepoName: 'tag2',
|
||||||
Tag: 'tag2',
|
Tag: 'tag2',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'CRITICAL',
|
MaxSeverity: 'CRITICAL',
|
||||||
Count: 2
|
Count: 2
|
||||||
@@ -28,6 +30,7 @@ const mockDependentsList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag3',
|
RepoName: 'tag3',
|
||||||
Tag: 'tag3',
|
Tag: 'tag3',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'LOW',
|
MaxSeverity: 'LOW',
|
||||||
Count: 5
|
Count: 5
|
||||||
@@ -36,6 +39,7 @@ const mockDependentsList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag4',
|
RepoName: 'tag4',
|
||||||
Tag: 'tag4',
|
Tag: 'tag4',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 3
|
Count: 3
|
||||||
|
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
|
|||||||
import { api } from 'api';
|
import { api } from 'api';
|
||||||
import TagDetails from 'components/Tag/TagDetails';
|
import TagDetails from 'components/Tag/TagDetails';
|
||||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
const TagDetailsThemeWrapper = () => {
|
const TagDetailsThemeWrapper = () => {
|
||||||
return (
|
return (
|
||||||
@@ -24,57 +24,157 @@ const mockImage = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
},
|
||||||
|
History: [
|
||||||
|
{
|
||||||
|
Layer: {
|
||||||
|
Size: '75181999',
|
||||||
|
Digest: 'sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621',
|
||||||
|
Score: null
|
||||||
|
},
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.526672082Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) ADD file:bd7a2aed6ede423b719ceb2f723e4ecdfa662b28639c8429731c878e86fb138b in / ',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.895811646Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201204',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:53.076477777Z',
|
||||||
|
CreatedBy: '/bin/sh -c #(nop) CMD ["/bin/bash"]',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf45etertdfg973e29',
|
||||||
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
|
Size: '75183423',
|
||||||
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
|
Platform: {
|
||||||
|
Os: 'windows',
|
||||||
|
Arch: 'amd64'
|
||||||
|
},
|
||||||
|
History: [
|
||||||
|
{
|
||||||
|
Layer: {
|
||||||
|
Size: '75181999',
|
||||||
|
Digest: 'sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621',
|
||||||
|
Score: null
|
||||||
|
},
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.526672082Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) ADD file:bd7a2aed6ede423b719ceb2f723e4ecdfa662b28639c8429731c878e86fb138b in / ',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.895811646Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201204',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:53.076477777Z',
|
||||||
|
CreatedBy: '/bin/sh -c #(nop) CMD ["/bin/bash"]',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e25',
|
||||||
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
|
Size: '75183423',
|
||||||
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
|
Platform: {
|
||||||
|
Os: 'linux',
|
||||||
|
Arch: 'arm'
|
||||||
|
},
|
||||||
|
History: [
|
||||||
|
{
|
||||||
|
Layer: {
|
||||||
|
Size: '75181999',
|
||||||
|
Digest: 'sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621',
|
||||||
|
Score: null
|
||||||
|
},
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.526672082Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) ADD file:bd7a2aed6ede423b719ceb2f723e4ecdfa662b28639c8429731c878e86fb138b in / ',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:52.895811646Z',
|
||||||
|
CreatedBy:
|
||||||
|
'/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201204',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Layer: null,
|
||||||
|
HistoryDescription: {
|
||||||
|
Created: '2020-12-08T00:22:53.076477777Z',
|
||||||
|
CreatedBy: '/bin/sh -c #(nop) CMD ["/bin/bash"]',
|
||||||
|
Author: '',
|
||||||
|
Comment: '',
|
||||||
|
EmptyLayer: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'CRITICAL',
|
MaxSeverity: 'CRITICAL',
|
||||||
Count: 10
|
Count: 10
|
||||||
},
|
},
|
||||||
Vendor: 'CentOS',
|
Vendor: 'CentOS'
|
||||||
History: [
|
|
||||||
{
|
|
||||||
Layer: {
|
|
||||||
Size: '75181999',
|
|
||||||
Digest: 'sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621',
|
|
||||||
Score: null
|
|
||||||
},
|
|
||||||
HistoryDescription: {
|
|
||||||
Created: '2020-12-08T00:22:52.526672082Z',
|
|
||||||
CreatedBy:
|
|
||||||
'/bin/sh -c #(nop) ADD file:bd7a2aed6ede423b719ceb2f723e4ecdfa662b28639c8429731c878e86fb138b in / ',
|
|
||||||
Author: '',
|
|
||||||
Comment: '',
|
|
||||||
EmptyLayer: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Layer: null,
|
|
||||||
HistoryDescription: {
|
|
||||||
Created: '2020-12-08T00:22:52.895811646Z',
|
|
||||||
CreatedBy:
|
|
||||||
'/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201204',
|
|
||||||
Author: '',
|
|
||||||
Comment: '',
|
|
||||||
EmptyLayer: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Layer: null,
|
|
||||||
HistoryDescription: {
|
|
||||||
Created: '2020-12-08T00:22:53.076477777Z',
|
|
||||||
CreatedBy: '/bin/sh -c #(nop) CMD ["/bin/bash"]',
|
|
||||||
Author: '',
|
|
||||||
Comment: '',
|
|
||||||
EmptyLayer: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,14 +182,18 @@ const mockImageNone = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'NONE',
|
MaxSeverity: 'NONE',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -102,14 +206,18 @@ const mockImageUnknown = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'UNKNOWN',
|
MaxSeverity: 'UNKNOWN',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -122,14 +230,18 @@ const mockImageFailed = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: '',
|
MaxSeverity: '',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -142,14 +254,18 @@ const mockImageLow = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'LOW',
|
MaxSeverity: 'LOW',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -162,14 +278,18 @@ const mockImageMedium = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'MEDIUM',
|
MaxSeverity: 'MEDIUM',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -182,14 +302,18 @@ const mockImageHigh = {
|
|||||||
Image: {
|
Image: {
|
||||||
RepoName: 'centos',
|
RepoName: 'centos',
|
||||||
Tag: '8',
|
Tag: '8',
|
||||||
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
Manifests: [
|
||||||
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
{
|
||||||
Size: '75183423',
|
Digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29',
|
||||||
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
LastUpdated: '2020-12-08T00:22:52.526672082Z',
|
||||||
Platform: {
|
Size: '75183423',
|
||||||
Os: 'linux',
|
ConfigDigest: 'sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78',
|
||||||
Arch: 'amd64'
|
Platform: {
|
||||||
},
|
Os: 'linux',
|
||||||
|
Arch: 'amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 10
|
Count: 10
|
||||||
@@ -206,6 +330,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||||
Tag: 'tag1',
|
Tag: 'tag1',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 5
|
Count: 5
|
||||||
@@ -214,6 +339,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag2',
|
RepoName: 'tag2',
|
||||||
Tag: 'tag2',
|
Tag: 'tag2',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'CRITICAL',
|
MaxSeverity: 'CRITICAL',
|
||||||
Count: 2
|
Count: 2
|
||||||
@@ -222,6 +348,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag3',
|
RepoName: 'tag3',
|
||||||
Tag: 'tag3',
|
Tag: 'tag3',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'LOW',
|
MaxSeverity: 'LOW',
|
||||||
Count: 7
|
Count: 7
|
||||||
@@ -230,6 +357,7 @@ const mockDependenciesList = {
|
|||||||
{
|
{
|
||||||
RepoName: 'tag4',
|
RepoName: 'tag4',
|
||||||
Tag: 'tag4',
|
Tag: 'tag4',
|
||||||
|
Manifests: [],
|
||||||
Vulnerabilities: {
|
Vulnerabilities: {
|
||||||
MaxSeverity: 'HIGH',
|
MaxSeverity: 'HIGH',
|
||||||
Count: 5
|
Count: 5
|
||||||
@@ -253,7 +381,8 @@ jest.mock('react-router-dom', () => ({
|
|||||||
useParams: () => {
|
useParams: () => {
|
||||||
return { name: 'test', tag: '1.0.1' };
|
return { name: 'test', tag: '1.0.1' };
|
||||||
},
|
},
|
||||||
useNavigate: () => mockUseNavigate
|
useNavigate: () => mockUseNavigate,
|
||||||
|
useLocation: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../host', () => ({
|
jest.mock('../../host', () => ({
|
||||||
@@ -296,6 +425,24 @@ describe('Tags details', () => {
|
|||||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show the data of the different manifests when switching between them', async () => {
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImage } });
|
||||||
|
render(<TagDetailsThemeWrapper />);
|
||||||
|
const manifestSelect = await screen.findByText(/linux\/amd64/i);
|
||||||
|
await userEvent.click(manifestSelect);
|
||||||
|
await userEvent.click(await screen.findByText(/windows\/amd64/i));
|
||||||
|
expect(await screen.findByText(/windows\/amd64/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preselect a manifest if data is received', async () => {
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImage } });
|
||||||
|
useLocation.mockImplementation(() => ({
|
||||||
|
state: { digest: 'sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e25' }
|
||||||
|
}));
|
||||||
|
render(<TagDetailsThemeWrapper />);
|
||||||
|
expect(await screen.findByText(/linux\/arm/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('should redirect to homepage if it receives invalid data', async () => {
|
it('should redirect to homepage if it receives invalid data', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: null, errors: ['testerror'] } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: null, errors: ['testerror'] } });
|
||||||
render(<TagDetailsThemeWrapper />);
|
render(<TagDetailsThemeWrapper />);
|
||||||
|
10
src/api.js
10
src/api.js
@@ -76,15 +76,13 @@ const endpoints = {
|
|||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned Documentation Vendor Labels} DownloadCount}}}`,
|
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned Documentation Vendor Labels} DownloadCount}}}`,
|
||||||
detailedRepoInfo: (name) =>
|
detailedRepoInfo: (name) =>
|
||||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor Size Platform {Os Arch}} Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Layers {Size Digest} Digest Tag Title Documentation DownloadCount Source Description Licenses History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}}}}}}`,
|
`/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 Digest LastUpdated Size ConfigDigest Platform {Os Arch} Vendor Licenses }}`,
|
`/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 }}`,
|
||||||
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }) =>
|
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }) =>
|
||||||
`/v2/_zot/ext/search?query={CVEListForImage(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
`/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}}}}`,
|
}}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
|
||||||
layersDetailsForImage: (name) =>
|
|
||||||
`/v2/_zot/ext/search?query={Image(image: "${name}"){History {Layer {Size Digest Score} HistoryDescription {Created CreatedBy Author Comment EmptyLayer} }}}`,
|
|
||||||
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
|
||||||
@@ -92,11 +90,11 @@ const endpoints = {
|
|||||||
dependsOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
dependsOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
||||||
`/v2/_zot/ext/search?query={BaseImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
`/v2/_zot/ext/search?query={BaseImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
}}){Page {TotalCount ItemCount} Results { RepoName Tag Description Digest Vendor DownloadCount LastUpdated Size Platform {Os Arch} IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
}}){Page {TotalCount ItemCount} Results { RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
||||||
isDependentOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
isDependentOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
||||||
`/v2/_zot/ext/search?query={DerivedImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
`/v2/_zot/ext/search?query={DerivedImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
}}){Page {TotalCount ItemCount} Results {RepoName Tag Description Digest Vendor DownloadCount LastUpdated Size Platform {Os Arch} IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
}}){Page {TotalCount ItemCount} Results {RepoName Tag Description Manifests {Digest Platform {Os Arch} Size} Vendor DownloadCount LastUpdated IsSigned Vulnerabilities {MaxSeverity Count}}}}`,
|
||||||
globalSearch: ({
|
globalSearch: ({
|
||||||
searchQuery = '""',
|
searchQuery = '""',
|
||||||
pageNumber = 1,
|
pageNumber = 1,
|
||||||
|
@@ -6,7 +6,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { api, endpoints } from 'api';
|
import { api, endpoints } from 'api';
|
||||||
import { host } from 'host';
|
import { host } from 'host';
|
||||||
import { mapToImage, mapToRepo } from 'utilities/objectModels';
|
import { mapToImage, mapToRepo } from 'utilities/objectModels';
|
||||||
import { createSearchParams, useNavigate, useSearchParams } from 'react-router-dom';
|
import { createSearchParams, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { debounce, isEmpty } from 'lodash';
|
import { debounce, isEmpty } from 'lodash';
|
||||||
import { useCombobox } from 'downshift';
|
import { useCombobox } from 'downshift';
|
||||||
import { HEADER_SEARCH_PAGE_SIZE } from 'utilities/paginationConstants';
|
import { HEADER_SEARCH_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||||
@@ -104,6 +104,7 @@ function SearchSuggestion() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isFailedSearch, setIsFailedSearch] = useState(false);
|
const [isFailedSearch, setIsFailedSearch] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const abortController = useMemo(() => new AbortController(), []);
|
const abortController = useMemo(() => new AbortController(), []);
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -180,7 +181,9 @@ function SearchSuggestion() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const searchCall = (value) => {
|
const searchCall = (value) => {
|
||||||
setQueryParams((prevState) => createSearchParams({ ...prevState, search: searchQuery }));
|
if (location.pathname?.includes('explore')) {
|
||||||
|
setQueryParams((prevState) => createSearchParams({ ...prevState, search: searchQuery }));
|
||||||
|
}
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
// if search term inclused the ':' character, search for images, if not, search repos
|
// if search term inclused the ':' character, search for images, if not, search repos
|
||||||
if (value?.includes(':')) {
|
if (value?.includes(':')) {
|
||||||
@@ -241,7 +244,7 @@ function SearchSuggestion() {
|
|||||||
root: classes.searchItemIconBg,
|
root: classes.searchItemIconBg,
|
||||||
img: classes.searchItemIcon
|
img: classes.searchItemIcon
|
||||||
}}
|
}}
|
||||||
src={`data:image/png;base64, ${suggestion.logo}`}
|
src={suggestion.logo ? `data:image/png;base64, ${suggestion.logo}` : ''}
|
||||||
>
|
>
|
||||||
<PhotoIcon className={classes.searchItemIcon} />
|
<PhotoIcon className={classes.searchItemIcon} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
@@ -20,7 +20,7 @@ import { TabContext, TabList, TabPanel } from '@mui/lab';
|
|||||||
|
|
||||||
import RepoDetailsMetadata from './RepoDetailsMetadata';
|
import RepoDetailsMetadata from './RepoDetailsMetadata';
|
||||||
import Loading from '../Shared/Loading';
|
import Loading from '../Shared/Loading';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniq } from 'lodash';
|
||||||
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||||
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
|
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
|
||||||
|
|
||||||
@@ -164,6 +164,7 @@ function RepoDetails() {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
api
|
api
|
||||||
.get(`${host()}${endpoints.detailedRepoInfo(name)}`, abortController.signal)
|
.get(`${host()}${endpoints.detailedRepoInfo(name)}`, abortController.signal)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -197,22 +198,13 @@ function RepoDetails() {
|
|||||||
|
|
||||||
const platformChips = () => {
|
const platformChips = () => {
|
||||||
const platforms = repoDetailData?.platforms || [];
|
const platforms = repoDetailData?.platforms || [];
|
||||||
|
const filteredPlatforms = platforms?.flatMap((platform) => [platform.Os, platform.Arch]);
|
||||||
|
|
||||||
return platforms.map((platform, index) => (
|
return uniq(filteredPlatforms).map((platform, index) => (
|
||||||
<Stack key={`stack${platform?.Os}${platform?.Arch}`} alignItems="center" direction="row" spacing={2}>
|
<Stack key={`stack${platform}`} alignItems="center" direction="row" spacing={2}>
|
||||||
<Chip
|
<Chip
|
||||||
key={`${name}${platform?.Os}${index}`}
|
key={`${name}${platform}${index}`}
|
||||||
label={platform?.Os}
|
label={platform}
|
||||||
onClick={handlePlatformChipClick}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#E0E5EB',
|
|
||||||
color: '#52637A',
|
|
||||||
fontSize: '0.8125rem'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
key={`${name}${platform?.Arch}${index}`}
|
|
||||||
label={platform?.Arch}
|
|
||||||
onClick={handlePlatformChipClick}
|
onClick={handlePlatformChipClick}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: '#E0E5EB',
|
backgroundColor: '#E0E5EB',
|
||||||
|
@@ -75,7 +75,7 @@ export default function Tags(props) {
|
|||||||
const [sortFilter, setSortFilter] = useState(tagsSortByCriteria.updateTimeDesc.value);
|
const [sortFilter, setSortFilter] = useState(tagsSortByCriteria.updateTimeDesc.value);
|
||||||
const renderTags = (tags) => {
|
const renderTags = (tags) => {
|
||||||
const selectedSort = Object.values(tagsSortByCriteria).find((sc) => sc.value === sortFilter);
|
const selectedSort = Object.values(tagsSortByCriteria).find((sc) => sc.value === sortFilter);
|
||||||
const filteredTags = tags.filter((t) => t.Tag?.includes(tagsFilter));
|
const filteredTags = tags.filter((t) => t.tag?.includes(tagsFilter));
|
||||||
if (selectedSort) {
|
if (selectedSort) {
|
||||||
filteredTags.sort(selectedSort.func);
|
filteredTags.sort(selectedSort.func);
|
||||||
}
|
}
|
||||||
@@ -84,13 +84,11 @@ export default function Tags(props) {
|
|||||||
filteredTags.map((tag) => {
|
filteredTags.map((tag) => {
|
||||||
return (
|
return (
|
||||||
<TagCard
|
<TagCard
|
||||||
key={tag.Tag}
|
key={tag.tag}
|
||||||
tag={tag.Tag}
|
tag={tag.tag}
|
||||||
lastUpdated={tag.LastUpdated}
|
lastUpdated={tag.lastUpdated}
|
||||||
digest={tag.Digest}
|
vendor={tag.vendor}
|
||||||
vendor={tag.Vendor}
|
manifests={tag.manifests}
|
||||||
size={tag.Size}
|
|
||||||
platform={tag.Platform}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@@ -16,7 +16,7 @@ import repocube4 from '../../assets/repocube-4.png';
|
|||||||
|
|
||||||
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||||
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
|
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, uniq } from 'lodash';
|
||||||
|
|
||||||
// temporary utility to get image
|
// temporary utility to get image
|
||||||
const randomIntFromInterval = (min, max) => {
|
const randomIntFromInterval = (min, max) => {
|
||||||
@@ -119,22 +119,12 @@ function RepoCard(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const platformChips = () => {
|
const platformChips = () => {
|
||||||
const platformsOsArch = platforms || [];
|
const filteredPlatforms = platforms?.flatMap((platform) => [platform.Os, platform.Arch]);
|
||||||
return platformsOsArch.map((platform, index) => (
|
return uniq(filteredPlatforms).map((platform, index) => (
|
||||||
<Stack key={`stack${platform?.Os}${platform?.Arch}`} alignItems="center" direction="row" spacing={2}>
|
<Stack key={`stack${platform}`} alignItems="center" direction="row" spacing={2}>
|
||||||
<Chip
|
<Chip
|
||||||
key={`${name}${platform?.Os}${index}`}
|
key={`${name}${platform}${index}`}
|
||||||
label={platform?.Os}
|
label={platform}
|
||||||
onClick={handlePlatformChipClick}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#E0E5EB',
|
|
||||||
color: '#52637A',
|
|
||||||
fontSize: '0.8125rem'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
key={`${name}${platform?.Arch}${index}`}
|
|
||||||
label={platform?.Arch}
|
|
||||||
onClick={handlePlatformChipClick}
|
onClick={handlePlatformChipClick}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: '#E0E5EB',
|
backgroundColor: '#E0E5EB',
|
||||||
|
@@ -60,7 +60,7 @@ const useStyles = makeStyles(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export default function TagCard(props) {
|
export default function TagCard(props) {
|
||||||
const { repoName, tag, lastUpdated, vendor, digest, size, platform } = props;
|
const { repoName, tag, lastUpdated, vendor, manifests } = props;
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -70,11 +70,11 @@ export default function TagCard(props) {
|
|||||||
: `Timestamp N/A`;
|
: `Timestamp N/A`;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const goToTags = () => {
|
const goToTags = (digest = null) => {
|
||||||
if (repoName) {
|
if (repoName) {
|
||||||
navigate(`/image/${encodeURIComponent(repoName)}/tag/${tag}`);
|
navigate(`/image/${encodeURIComponent(repoName)}/tag/${tag}`, { state: { digest } });
|
||||||
} else {
|
} else {
|
||||||
navigate(`tag/${tag}`);
|
navigate(`tag/${tag}`, { state: { digest } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,23 +135,38 @@ export default function TagCard(props) {
|
|||||||
<Typography variant="body1"> Size </Typography>
|
<Typography variant="body1"> Size </Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container item xs={12} direction={'row'}>
|
|
||||||
<Grid item xs={6} md={4}>
|
{manifests.map((el) => (
|
||||||
<Tooltip title={digest || ''} placement="top">
|
<Grid container item xs={12} key={el.digest} direction={'row'}>
|
||||||
<Typography variant="body1">{digest?.substr(0, 12)}</Typography>
|
<Grid item xs={6} md={4}>
|
||||||
</Tooltip>
|
<Tooltip title={el.digest || ''} placement="top">
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: '#1479FF', textDecorationLine: 'underline', cursor: 'pointer' }}
|
||||||
|
onClick={() => goToTags(el.digest)}
|
||||||
|
>
|
||||||
|
{el.digest?.substr(0, 12)}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
{el.platform?.Os}/{el.platform?.Arch}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={0}
|
||||||
|
md={4}
|
||||||
|
className="hide-on-mobile"
|
||||||
|
sx={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||||
|
>
|
||||||
|
<Typography sx={{ textAlign: 'right' }} variant="body1">
|
||||||
|
{transform.formatBytes(el.size)}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
))}
|
||||||
<Typography variant="body1">
|
|
||||||
{platform?.Os}/{platform?.Arch}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={0} md={4} className="hide-on-mobile" sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
||||||
<Typography sx={{ textAlign: 'right' }} variant="body1">
|
|
||||||
{transform.formatBytes(size)}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@@ -148,10 +148,8 @@ function DependsOn(props) {
|
|||||||
repoName={dependence.repoName}
|
repoName={dependence.repoName}
|
||||||
tag={dependence.tag}
|
tag={dependence.tag}
|
||||||
vendor={dependence.vendor}
|
vendor={dependence.vendor}
|
||||||
platform={dependence.platform}
|
|
||||||
isSigned={dependence.isSigned}
|
isSigned={dependence.isSigned}
|
||||||
size={dependence.size}
|
manifests={dependence.manifests}
|
||||||
digest={dependence.digest}
|
|
||||||
key={index}
|
key={index}
|
||||||
lastUpdated={dependence.lastUpdated}
|
lastUpdated={dependence.lastUpdated}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
// utility
|
|
||||||
import { api, endpoints } from '../../../api';
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { Divider, Stack, Typography } from '@mui/material';
|
import { Divider, Stack, Typography } from '@mui/material';
|
||||||
import LayerCard from '../../Shared/LayerCard.jsx';
|
import LayerCard from '../../Shared/LayerCard.jsx';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
import { host } from '../../../host';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import Loading from '../../Shared/Loading';
|
import Loading from '../../Shared/Loading';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
@@ -83,25 +78,8 @@ function HistoryLayers(props) {
|
|||||||
const { name, history } = props;
|
const { name, history } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (history && !isEmpty(history)) {
|
setHistoryData(history);
|
||||||
setHistoryData(history);
|
setIsLoading(false);
|
||||||
setIsLoading(false);
|
|
||||||
} else {
|
|
||||||
api
|
|
||||||
.get(`${host()}${endpoints.layersDetailsForImage(name)}`, abortController.signal)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.data && response.data.data) {
|
|
||||||
let layersHistory = response.data.data.Image;
|
|
||||||
setHistoryData(layersHistory?.History);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
setHistoryData([]);
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
@@ -129,7 +107,7 @@ function HistoryLayers(props) {
|
|||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<Stack direction="column" spacing={2} sx={{ marginTop: '1.7rem' }} data-testid="layer-card-container">
|
<Stack direction="column" spacing={2} sx={{ marginTop: '1.7rem' }} data-testid="layer-card-container">
|
||||||
{historyData ? (
|
{historyData?.length > 0 ? (
|
||||||
historyData.map((layer, index) => {
|
historyData.map((layer, index) => {
|
||||||
return (
|
return (
|
||||||
<LayerCard
|
<LayerCard
|
||||||
|
@@ -148,10 +148,8 @@ function IsDependentOn(props) {
|
|||||||
repoName={dependence.repoName}
|
repoName={dependence.repoName}
|
||||||
tag={dependence.tag}
|
tag={dependence.tag}
|
||||||
vendor={dependence.vendor}
|
vendor={dependence.vendor}
|
||||||
platform={dependence.platform}
|
|
||||||
isSigned={dependence.isSigned}
|
isSigned={dependence.isSigned}
|
||||||
size={dependence.size}
|
manifests={dependence.manifests}
|
||||||
digest={dependence.digest}
|
|
||||||
key={index}
|
key={index}
|
||||||
lastUpdated={dependence.lastUpdated}
|
lastUpdated={dependence.lastUpdated}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
||||||
|
|
||||||
// utility
|
// utility
|
||||||
@@ -19,7 +19,8 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Tab,
|
Tab,
|
||||||
Typography,
|
Typography,
|
||||||
InputBase
|
InputBase,
|
||||||
|
InputLabel
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
@@ -37,7 +38,7 @@ import VulnerabilitiesDetails from './Tabs/VulnerabilitiesDetails';
|
|||||||
import HistoryLayers from './Tabs/HistoryLayers';
|
import HistoryLayers from './Tabs/HistoryLayers';
|
||||||
import DependsOn from './Tabs/DependsOn';
|
import DependsOn from './Tabs/DependsOn';
|
||||||
import IsDependentOn from './Tabs/IsDependentOn';
|
import IsDependentOn from './Tabs/IsDependentOn';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, head } from 'lodash';
|
||||||
import Loading from '../Shared/Loading';
|
import Loading from '../Shared/Loading';
|
||||||
import { dockerPull, podmanPull, skopeoPull } from 'utilities/pullStrings';
|
import { dockerPull, podmanPull, skopeoPull } from 'utilities/pullStrings';
|
||||||
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||||
@@ -212,6 +213,7 @@ const randomImage = () => {
|
|||||||
|
|
||||||
function TagDetails() {
|
function TagDetails() {
|
||||||
const [imageDetailData, setImageDetailData] = useState({});
|
const [imageDetailData, setImageDetailData] = useState({});
|
||||||
|
const [selectedManifest, setSelectedManifest] = useState({});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [selectedTab, setSelectedTab] = useState('Layers');
|
const [selectedTab, setSelectedTab] = useState('Layers');
|
||||||
const [selectedPullTab, setSelectedPullTab] = useState('');
|
const [selectedPullTab, setSelectedPullTab] = useState('');
|
||||||
@@ -219,6 +221,10 @@ function TagDetails() {
|
|||||||
const mounted = useRef(false);
|
const mounted = useRef(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// check for optional preselected digest
|
||||||
|
const { state } = useLocation() || {};
|
||||||
|
const { digest } = state || '';
|
||||||
|
|
||||||
// get url param from <Route here (i.e. image name)
|
// get url param from <Route here (i.e. image name)
|
||||||
const { reponame, tag } = useParams();
|
const { reponame, tag } = useParams();
|
||||||
|
|
||||||
@@ -239,6 +245,16 @@ function TagDetails() {
|
|||||||
let imageInfo = response.data.data.Image;
|
let imageInfo = response.data.data.Image;
|
||||||
let imageData = mapToImage(imageInfo);
|
let imageData = mapToImage(imageInfo);
|
||||||
setImageDetailData(imageData);
|
setImageDetailData(imageData);
|
||||||
|
if (!isEmpty(digest)) {
|
||||||
|
const preselectedManifest = imageData.manifests?.find((el) => el.digest === digest);
|
||||||
|
if (preselectedManifest) {
|
||||||
|
setSelectedManifest(preselectedManifest);
|
||||||
|
} else {
|
||||||
|
setSelectedManifest(head(imageData.manifests));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSelectedManifest(head(imageData.manifests));
|
||||||
|
}
|
||||||
setPullString(dockerPull(imageData.name));
|
setPullString(dockerPull(imageData.name));
|
||||||
setSelectedPullTab(dockerPull(imageData.name));
|
setSelectedPullTab(dockerPull(imageData.name));
|
||||||
} else if (!isEmpty(response.data.errors)) {
|
} else if (!isEmpty(response.data.errors)) {
|
||||||
@@ -258,7 +274,7 @@ function TagDetails() {
|
|||||||
}, [reponame, tag]);
|
}, [reponame, tag]);
|
||||||
|
|
||||||
const getPlatform = () => {
|
const getPlatform = () => {
|
||||||
return imageDetailData?.platform ? imageDetailData.platform : '--/--';
|
return selectedManifest.platform ? selectedManifest.platform : '--/--';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (event, newValue) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
@@ -284,6 +300,11 @@ function TagDetails() {
|
|||||||
return 'Pull Image';
|
return 'Pull Image';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOSArchChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setSelectedManifest(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -327,9 +348,29 @@ function TagDetails() {
|
|||||||
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
|
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
|
||||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
<FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small">
|
||||||
|
<InputLabel>OS/Arch</InputLabel>
|
||||||
|
{!isEmpty(selectedManifest) && (
|
||||||
|
<Select
|
||||||
|
label="OS/Arch"
|
||||||
|
value={selectedManifest}
|
||||||
|
onChange={handleOSArchChange}
|
||||||
|
MenuProps={{ disableScrollLock: true }}
|
||||||
|
>
|
||||||
|
{imageDetailData.manifests.map((el) => (
|
||||||
|
<MenuItem key={el.digest} value={el}>
|
||||||
|
{`${el.platform?.Os}/${el.platform?.Arch}`}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Typography gutterBottom className={classes.digest}>
|
<Typography gutterBottom className={classes.digest}>
|
||||||
DIGEST: {imageDetailData?.digest}
|
DIGEST: {selectedManifest?.digest}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={0} md={4} className={`${classes.pull} hide-on-mobile`}>
|
<Grid item xs={0} md={4} className={`${classes.pull} hide-on-mobile`}>
|
||||||
@@ -527,19 +568,19 @@ function TagDetails() {
|
|||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TabPanel value="Layers" className={classes.tabPanel}>
|
<TabPanel value="Layers" className={classes.tabPanel}>
|
||||||
<HistoryLayers name={imageDetailData.name} history={imageDetailData.history} />
|
<HistoryLayers name={imageDetailData.name} history={selectedManifest.history} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="DependsOn" className={classes.tabPanel}>
|
<TabPanel value="DependsOn" className={classes.tabPanel}>
|
||||||
<DependsOn name={imageDetailData.name} />
|
<DependsOn name={imageDetailData.name} digest={selectedManifest.digest} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
|
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
|
||||||
<IsDependentOn name={imageDetailData.name} />
|
<IsDependentOn name={imageDetailData.name} digest={selectedManifest.digest} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
|
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
|
||||||
<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={imageDetailData?.digest} />
|
<ReferredBy repoName={reponame} digest={selectedManifest?.digest} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -549,8 +590,8 @@ function TagDetails() {
|
|||||||
<Grid item xs={12} md={4} className={classes.metadata}>
|
<Grid item xs={12} md={4} className={classes.metadata}>
|
||||||
<TagDetailsMetadata
|
<TagDetailsMetadata
|
||||||
platform={getPlatform()}
|
platform={getPlatform()}
|
||||||
size={imageDetailData?.size}
|
size={selectedManifest?.size}
|
||||||
lastUpdated={imageDetailData?.lastUpdated}
|
lastUpdated={selectedManifest?.lastUpdated}
|
||||||
license={imageDetailData?.license}
|
license={imageDetailData?.license}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@@ -20,7 +20,7 @@ const mapToRepo = (responseRepo) => {
|
|||||||
const mapToRepoFromRepoInfo = (responseRepoInfo) => {
|
const mapToRepoFromRepoInfo = (responseRepoInfo) => {
|
||||||
return {
|
return {
|
||||||
name: responseRepoInfo.Summary?.Name,
|
name: responseRepoInfo.Summary?.Name,
|
||||||
images: responseRepoInfo.Images,
|
images: responseRepoInfo.Images?.map((image) => mapToImage(image)) || [],
|
||||||
lastUpdated: responseRepoInfo.Summary?.LastUpdated,
|
lastUpdated: responseRepoInfo.Summary?.LastUpdated,
|
||||||
size: responseRepoInfo.Summary?.Size,
|
size: responseRepoInfo.Summary?.Size,
|
||||||
platforms: responseRepoInfo.Summary?.Platforms,
|
platforms: responseRepoInfo.Summary?.Platforms,
|
||||||
@@ -43,22 +43,40 @@ const mapToImage = (responseImage) => {
|
|||||||
return {
|
return {
|
||||||
repoName: responseImage.RepoName,
|
repoName: responseImage.RepoName,
|
||||||
tag: responseImage.Tag,
|
tag: responseImage.Tag,
|
||||||
lastUpdated: responseImage.LastUpdated,
|
manifests: responseImage.Manifests?.map((manifest) => mapToManifest(manifest)) || [],
|
||||||
size: responseImage.Size,
|
size: responseImage.Size,
|
||||||
digest: responseImage.Digest || responseImage.ConfigDigest,
|
downloadCount: responseImage.DownloadCount,
|
||||||
platform: responseImage.Platform,
|
lastUpdated: responseImage.LastUpdated,
|
||||||
vendor: responseImage.Vendor,
|
description: responseImage.Description,
|
||||||
history: responseImage.History,
|
isSigned: responseImage.IsSigned,
|
||||||
license: responseImage.Licenses,
|
license: responseImage.Licenses,
|
||||||
|
labels: responseImage.Labels,
|
||||||
|
title: responseImage.Title,
|
||||||
|
source: responseImage.Source,
|
||||||
|
documentation: responseImage.Documentation,
|
||||||
|
vendor: responseImage.Vendor,
|
||||||
|
authors: responseImage.Authors,
|
||||||
vulnerabiltySeverity: responseImage.Vulnerabilities?.MaxSeverity,
|
vulnerabiltySeverity: responseImage.Vulnerabilities?.MaxSeverity,
|
||||||
vulnerabilityCount: responseImage.Vulnerabilities?.Count,
|
vulnerabilityCount: responseImage.Vulnerabilities?.Count,
|
||||||
isSigned: responseImage.IsSigned,
|
|
||||||
logo: responseImage.Logo,
|
|
||||||
// frontend only prop to increase interop with Repo objects and code reusability
|
// frontend only prop to increase interop with Repo objects and code reusability
|
||||||
name: `${responseImage.RepoName}:${responseImage.Tag}`
|
name: `${responseImage.RepoName}:${responseImage.Tag}`
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapToManifest = (responseManifest) => {
|
||||||
|
return {
|
||||||
|
digest: responseManifest.Digest,
|
||||||
|
configDigest: responseManifest.ConfigDigest,
|
||||||
|
lastUpdated: responseManifest.LastUpdated,
|
||||||
|
size: responseManifest.Size,
|
||||||
|
platform: responseManifest.Platform,
|
||||||
|
downloadCount: responseManifest.DownloadCount,
|
||||||
|
layers: responseManifest.Layers,
|
||||||
|
history: responseManifest.History,
|
||||||
|
vulnerabilities: responseManifest.Vulnerabilities
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapCVEInfo = (cveInfo) => {
|
const mapCVEInfo = (cveInfo) => {
|
||||||
const cveList = cveInfo.map((cve) => {
|
const cveList = cveInfo.map((cve) => {
|
||||||
return {
|
return {
|
||||||
@@ -79,4 +97,4 @@ const mapReferrer = (referrer) => ({
|
|||||||
annotations: referrer.Annotations?.map((annotation) => ({ key: annotation.Key, value: annotation.Value }))
|
annotations: referrer.Annotations?.map((annotation) => ({ key: annotation.Key, value: annotation.Value }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export { mapToRepo, mapToImage, mapToRepoFromRepoInfo, mapCVEInfo, mapReferrer };
|
export { mapToRepo, mapToImage, mapToRepoFromRepoInfo, mapCVEInfo, mapReferrer, mapToManifest };
|
||||||
|
@@ -32,28 +32,28 @@ export const tagsSortByCriteria = {
|
|||||||
value: 'UPDATETIME_DESC',
|
value: 'UPDATETIME_DESC',
|
||||||
label: 'Newest',
|
label: 'Newest',
|
||||||
func: (a, b) => {
|
func: (a, b) => {
|
||||||
return DateTime.fromISO(b.LastUpdated).diff(DateTime.fromISO(a.LastUpdated));
|
return DateTime.fromISO(b.lastUpdated).diff(DateTime.fromISO(a.lastUpdated));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateTime: {
|
updateTime: {
|
||||||
value: 'UPDATETIME',
|
value: 'UPDATETIME',
|
||||||
label: 'Oldest',
|
label: 'Oldest',
|
||||||
func: (a, b) => {
|
func: (a, b) => {
|
||||||
return DateTime.fromISO(a.LastUpdated).diff(DateTime.fromISO(b.LastUpdated));
|
return DateTime.fromISO(a.lastUpdated).diff(DateTime.fromISO(b.lastUpdated));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alphabetic: {
|
alphabetic: {
|
||||||
value: 'ALPHABETIC',
|
value: 'ALPHABETIC',
|
||||||
label: 'A - Z',
|
label: 'A - Z',
|
||||||
func: (a, b) => {
|
func: (a, b) => {
|
||||||
return a.Tag?.localeCompare(b.Tag);
|
return a.tag?.localeCompare(b.tag);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alphabeticDesc: {
|
alphabeticDesc: {
|
||||||
value: 'ALPHABETIC_DESC',
|
value: 'ALPHABETIC_DESC',
|
||||||
label: 'Z - A',
|
label: 'Z - A',
|
||||||
func: (a, b) => {
|
func: (a, b) => {
|
||||||
return b.Tag?.localeCompare(a.Tag);
|
return b.tag?.localeCompare(a.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user