feat: starred repos implementation (#399)
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
parent
e97e04eee5
commit
d9370fb9c1
2
.github/workflows/end-to-end-test.yml
vendored
2
.github/workflows/end-to-end-test.yml
vendored
@ -94,7 +94,7 @@ jobs:
|
|||||||
- name: Build zot
|
- name: Build zot
|
||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/zot
|
cd $GITHUB_WORKSPACE/zot
|
||||||
make binary
|
make binary ZUI_BUILD_PATH=$GITHUB_WORKSPACE/build
|
||||||
ls -l bin/
|
ls -l bin/
|
||||||
|
|
||||||
- name: Bringup zot server
|
- name: Bringup zot server
|
||||||
|
@ -34,6 +34,7 @@ const mockImageList = {
|
|||||||
Size: '2806985',
|
Size: '2806985',
|
||||||
LastUpdated: '2022-08-09T17:19:53.274069586Z',
|
LastUpdated: '2022-08-09T17:19:53.274069586Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: 'w',
|
Description: 'w',
|
||||||
@ -58,6 +59,7 @@ const mockImageList = {
|
|||||||
Size: '231383863',
|
Size: '231383863',
|
||||||
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -82,6 +84,7 @@ const mockImageList = {
|
|||||||
Size: '369311301',
|
Size: '369311301',
|
||||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -106,6 +109,7 @@ const mockImageList = {
|
|||||||
Size: '369311301',
|
Size: '369311301',
|
||||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -130,6 +134,7 @@ const mockImageList = {
|
|||||||
Size: '369311301',
|
Size: '369311301',
|
||||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -158,6 +163,7 @@ const mockImageList = {
|
|||||||
Size: '369311301',
|
Size: '369311301',
|
||||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -182,6 +188,7 @@ const mockImageList = {
|
|||||||
Size: '369311301',
|
Size: '369311301',
|
||||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
Tag: 'latest',
|
Tag: 'latest',
|
||||||
Description: '',
|
Description: '',
|
||||||
@ -338,4 +345,13 @@ describe('Explore component', () => {
|
|||||||
await userEvent.click(bookmarkButton);
|
await userEvent.click(bookmarkButton);
|
||||||
expect(await screen.findAllByTestId('bookmarked')).toHaveLength(1);
|
expect(await screen.findAllByTestId('bookmarked')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should star a repo if star button is clicked', async () => {
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
||||||
|
render(<StateExploreWrapper />);
|
||||||
|
const starButton = (await screen.findAllByTestId('star-button'))[0];
|
||||||
|
jest.spyOn(api, 'put').mockResolvedValueOnce({ status: 200, data: {} });
|
||||||
|
await userEvent.click(starButton);
|
||||||
|
expect(await screen.findAllByTestId('starred')).toHaveLength(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -164,6 +164,48 @@ const mockImageListBookmarks = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockImageListStars = {
|
||||||
|
GlobalSearch: {
|
||||||
|
Page: { TotalCount: 3, ItemCount: 2 },
|
||||||
|
Repos: [
|
||||||
|
{
|
||||||
|
Name: 'alpine',
|
||||||
|
Size: '2806985',
|
||||||
|
LastUpdated: '2022-08-09T17:19:53.274069586Z',
|
||||||
|
NewestImage: {
|
||||||
|
Tag: 'latest',
|
||||||
|
Description: 'w',
|
||||||
|
IsSigned: false,
|
||||||
|
Licenses: '',
|
||||||
|
Vendor: '',
|
||||||
|
Labels: '',
|
||||||
|
Vulnerabilities: {
|
||||||
|
MaxSeverity: 'LOW',
|
||||||
|
Count: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'mongo',
|
||||||
|
Size: '231383863',
|
||||||
|
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
||||||
|
NewestImage: {
|
||||||
|
Tag: 'latest',
|
||||||
|
Description: '',
|
||||||
|
IsSigned: true,
|
||||||
|
Licenses: '',
|
||||||
|
Vendor: '',
|
||||||
|
Labels: '',
|
||||||
|
Vulnerabilities: {
|
||||||
|
MaxSeverity: 'HIGH',
|
||||||
|
Count: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.scrollTo = jest.fn();
|
window.scrollTo = jest.fn();
|
||||||
});
|
});
|
||||||
@ -178,8 +220,8 @@ describe('Home component', () => {
|
|||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
||||||
render(<HomeWrapper />);
|
render(<HomeWrapper />);
|
||||||
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(3));
|
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(4));
|
||||||
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(3));
|
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(4));
|
||||||
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(1));
|
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -187,16 +229,16 @@ describe('Home component', () => {
|
|||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
||||||
render(<HomeWrapper />);
|
render(<HomeWrapper />);
|
||||||
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(3);
|
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(4);
|
||||||
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(4);
|
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders vulnerability icons', async () => {
|
it('renders vulnerability icons', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
|
||||||
render(<HomeWrapper />);
|
render(<HomeWrapper />);
|
||||||
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(3);
|
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(4);
|
||||||
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(3);
|
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(4);
|
||||||
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
|
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -204,16 +246,17 @@ describe('Home component', () => {
|
|||||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
render(<HomeWrapper />);
|
render(<HomeWrapper />);
|
||||||
await waitFor(() => expect(error).toBeCalledTimes(3));
|
await waitFor(() => expect(error).toBeCalledTimes(4));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should redirect to explore page when clicking view all popular', async () => {
|
it('should redirect to explore page when clicking view all popular', async () => {
|
||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
|
||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
|
||||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListBookmarks } });
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListBookmarks } });
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListStars } });
|
||||||
render(<HomeWrapper />);
|
render(<HomeWrapper />);
|
||||||
const viewAllButtons = await screen.findAllByText(/view all/i);
|
const viewAllButtons = await screen.findAllByText(/view all/i);
|
||||||
expect(viewAllButtons).toHaveLength(3);
|
expect(viewAllButtons).toHaveLength(4);
|
||||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: [] } });
|
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: [] } });
|
||||||
fireEvent.click(viewAllButtons[0]);
|
fireEvent.click(viewAllButtons[0]);
|
||||||
expect(mockedUsedNavigate).toHaveBeenCalledWith({
|
expect(mockedUsedNavigate).toHaveBeenCalledWith({
|
||||||
@ -230,5 +273,10 @@ describe('Home component', () => {
|
|||||||
pathname: `/explore`,
|
pathname: `/explore`,
|
||||||
search: createSearchParams({ filter: 'IsBookmarked' }).toString()
|
search: createSearchParams({ filter: 'IsBookmarked' }).toString()
|
||||||
});
|
});
|
||||||
|
fireEvent.click(viewAllButtons[3]);
|
||||||
|
expect(mockedUsedNavigate).toHaveBeenCalledWith({
|
||||||
|
pathname: `/explore`,
|
||||||
|
search: createSearchParams({ filter: 'IsStarred' }).toString()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -47,6 +47,7 @@ const mockRepoDetailsData = {
|
|||||||
Size: '451554070',
|
Size: '451554070',
|
||||||
Vendors: ['[The Node.js Docker Team](https://github.com/nodejs/docker-node)\n'],
|
Vendors: ['[The Node.js Docker Team](https://github.com/nodejs/docker-node)\n'],
|
||||||
IsBookmarked: false,
|
IsBookmarked: false,
|
||||||
|
IsStarred: false,
|
||||||
NewestImage: {
|
NewestImage: {
|
||||||
RepoName: 'mongo',
|
RepoName: 'mongo',
|
||||||
IsSigned: true,
|
IsSigned: true,
|
||||||
@ -316,4 +317,13 @@ describe('Repo details component', () => {
|
|||||||
await userEvent.click(bookmarkButton);
|
await userEvent.click(bookmarkButton);
|
||||||
expect(await screen.findByTestId('bookmarked')).toBeInTheDocument();
|
expect(await screen.findByTestId('bookmarked')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should star a repo if star button is clicked', async () => {
|
||||||
|
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockRepoDetailsData } });
|
||||||
|
render(<RepoDetailsThemeWrapper />);
|
||||||
|
const starButton = await screen.findByTestId('star-button');
|
||||||
|
jest.spyOn(api, 'put').mockResolvedValue({ status: 200, data: {} });
|
||||||
|
await userEvent.click(starButton);
|
||||||
|
expect(await screen.findByTestId('starred')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -84,7 +84,7 @@ const endpoints = {
|
|||||||
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
|
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
|
||||||
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
|
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
|
||||||
(pageNumber - 1) * pageSize
|
(pageNumber - 1) * pageSize
|
||||||
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned SignatureInfo { Tool IsTrusted Author } Documentation Vendor Labels} IsStarred IsBookmarked DownloadCount}}}`,
|
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned SignatureInfo { Tool IsTrusted Author } Documentation Vendor Labels} IsStarred IsBookmarked StarCount DownloadCount}}}`,
|
||||||
detailedRepoInfo: (name) =>
|
detailedRepoInfo: (name) =>
|
||||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
|
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
|
||||||
detailedImageInfo: (name, tag) =>
|
detailedImageInfo: (name, tag) =>
|
||||||
@ -134,9 +134,10 @@ const endpoints = {
|
|||||||
if (filter.Arch) filterParam += ` Arch:${!isEmpty(filter.Arch) ? `${JSON.stringify(filter.Arch)}` : '""'}`;
|
if (filter.Arch) filterParam += ` Arch:${!isEmpty(filter.Arch) ? `${JSON.stringify(filter.Arch)}` : '""'}`;
|
||||||
if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`;
|
if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`;
|
||||||
if (filter.IsBookmarked) filterParam += ` IsBookmarked: ${filter.IsBookmarked}`;
|
if (filter.IsBookmarked) filterParam += ` IsBookmarked: ${filter.IsBookmarked}`;
|
||||||
|
if (filter.IsStarred) filterParam += ` IsStarred: ${filter.IsStarred}`;
|
||||||
filterParam += '}';
|
filterParam += '}';
|
||||||
if (Object.keys(filter).length === 0) filterParam = '';
|
if (Object.keys(filter).length === 0) filterParam = '';
|
||||||
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned SignatureInfo { Tool IsTrusted Author } Licenses Vendor Labels } DownloadCount}}}`;
|
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned SignatureInfo { Tool IsTrusted Author } Licenses Vendor Labels } StarCount DownloadCount}}}`;
|
||||||
},
|
},
|
||||||
imageSuggestions: ({ searchQuery = '""', pageNumber = 1, pageSize = 15 }) => {
|
imageSuggestions: ({ searchQuery = '""', pageNumber = 1, pageSize = 15 }) => {
|
||||||
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
|
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
|
||||||
@ -145,7 +146,8 @@ const endpoints = {
|
|||||||
},
|
},
|
||||||
referrers: ({ repo, digest, type = '' }) =>
|
referrers: ({ repo, digest, type = '' }) =>
|
||||||
`/v2/_zot/ext/search?query={Referrers(repo: "${repo}" digest: "${digest}" type: "${type}"){MediaType ArtifactType Size Digest Annotations{Key Value}}}`,
|
`/v2/_zot/ext/search?query={Referrers(repo: "${repo}" digest: "${digest}" type: "${type}"){MediaType ArtifactType Size Digest Annotations{Key Value}}}`,
|
||||||
bookmarkToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleBookmark`
|
bookmarkToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleBookmark`,
|
||||||
|
starToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleStar`
|
||||||
};
|
};
|
||||||
|
|
||||||
export { api, endpoints };
|
export { api, endpoints };
|
||||||
|
@ -220,9 +220,11 @@ function Explore({ searchInputValue }) {
|
|||||||
version={item.latestVersion}
|
version={item.latestVersion}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
downloads={item.downloads}
|
downloads={item.downloads}
|
||||||
|
stars={item.stars}
|
||||||
isSigned={item.isSigned}
|
isSigned={item.isSigned}
|
||||||
signatureInfo={item.signatureInfo}
|
signatureInfo={item.signatureInfo}
|
||||||
isBookmarked={item.isBookmarked}
|
isBookmarked={item.isBookmarked}
|
||||||
|
isStarred={item.isStarred}
|
||||||
vendor={item.vendor}
|
vendor={item.vendor}
|
||||||
platforms={item.platforms}
|
platforms={item.platforms}
|
||||||
key={index}
|
key={index}
|
||||||
|
@ -8,7 +8,12 @@ import { mapToRepo } from 'utilities/objectModels';
|
|||||||
import Loading from '../Shared/Loading';
|
import Loading from '../Shared/Loading';
|
||||||
import { useNavigate, createSearchParams } from 'react-router-dom';
|
import { useNavigate, createSearchParams } from 'react-router-dom';
|
||||||
import { sortByCriteria } from 'utilities/sortCriteria';
|
import { sortByCriteria } from 'utilities/sortCriteria';
|
||||||
import { HOME_POPULAR_PAGE_SIZE, HOME_RECENT_PAGE_SIZE, HOME_BOOKMARKS_PAGE_SIZE } from 'utilities/paginationConstants';
|
import {
|
||||||
|
HOME_POPULAR_PAGE_SIZE,
|
||||||
|
HOME_RECENT_PAGE_SIZE,
|
||||||
|
HOME_BOOKMARKS_PAGE_SIZE,
|
||||||
|
HOME_STARS_PAGE_SIZE
|
||||||
|
} from 'utilities/paginationConstants';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import NoDataComponent from 'components/Shared/NoDataComponent';
|
import NoDataComponent from 'components/Shared/NoDataComponent';
|
||||||
|
|
||||||
@ -89,6 +94,8 @@ function Home() {
|
|||||||
const [isLoadingRecent, setIsLoadingRecent] = useState(true);
|
const [isLoadingRecent, setIsLoadingRecent] = useState(true);
|
||||||
const [bookmarkData, setBookmarkData] = useState([]);
|
const [bookmarkData, setBookmarkData] = useState([]);
|
||||||
const [isLoadingBookmarks, setIsLoadingBookmarks] = useState(true);
|
const [isLoadingBookmarks, setIsLoadingBookmarks] = useState(true);
|
||||||
|
const [starData, setStarData] = useState([]);
|
||||||
|
const [isLoadingStars, setIsLoadingStars] = useState(true);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const abortController = useMemo(() => new AbortController(), []);
|
const abortController = useMemo(() => new AbortController(), []);
|
||||||
@ -185,12 +192,44 @@ function Home() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStars = () => {
|
||||||
|
setIsLoadingStars(true);
|
||||||
|
api
|
||||||
|
.get(
|
||||||
|
`${host()}${endpoints.globalSearch({
|
||||||
|
searchQuery: '',
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: HOME_STARS_PAGE_SIZE,
|
||||||
|
sortBy: sortByCriteria.relevance?.value,
|
||||||
|
filter: { IsStarred: true }
|
||||||
|
})}`,
|
||||||
|
abortController.signal
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data && response.data.data) {
|
||||||
|
let repoList = response.data.data.GlobalSearch.Repos;
|
||||||
|
let repoData = repoList.map((responseRepo) => {
|
||||||
|
return mapToRepo(responseRepo);
|
||||||
|
});
|
||||||
|
setStarData(repoData);
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsLoadingStars(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsLoadingStars(false);
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
getPopularData();
|
getPopularData();
|
||||||
getRecentData();
|
getRecentData();
|
||||||
getBookmarks();
|
getBookmarks();
|
||||||
|
getStars();
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
@ -203,9 +242,11 @@ function Home() {
|
|||||||
const isNoData = () =>
|
const isNoData = () =>
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
!isLoadingBookmarks &&
|
!isLoadingBookmarks &&
|
||||||
|
!isLoadingStars &&
|
||||||
!isLoadingPopular &&
|
!isLoadingPopular &&
|
||||||
!isLoadingRecent &&
|
!isLoadingRecent &&
|
||||||
bookmarkData.length === 0 &&
|
bookmarkData.length === 0 &&
|
||||||
|
starData.length === 0 &&
|
||||||
popularData.length === 0 &&
|
popularData.length === 0 &&
|
||||||
recentData.length === 0;
|
recentData.length === 0;
|
||||||
|
|
||||||
@ -219,9 +260,11 @@ function Home() {
|
|||||||
version={item.latestVersion}
|
version={item.latestVersion}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
downloads={item.downloads}
|
downloads={item.downloads}
|
||||||
|
stars={item.stars}
|
||||||
isSigned={item.isSigned}
|
isSigned={item.isSigned}
|
||||||
signatureInfo={item.signatureInfo}
|
signatureInfo={item.signatureInfo}
|
||||||
isBookmarked={item.isBookmarked}
|
isBookmarked={item.isBookmarked}
|
||||||
|
isStarred={item.isStarred}
|
||||||
vendor={item.vendor}
|
vendor={item.vendor}
|
||||||
platforms={item.platforms}
|
platforms={item.platforms}
|
||||||
key={index}
|
key={index}
|
||||||
@ -294,6 +337,27 @@ function Home() {
|
|||||||
{isLoadingBookmarks ? <Loading /> : renderCards(bookmarkData, isLoadingBookmarks)}
|
{isLoadingBookmarks ? <Loading /> : renderCards(bookmarkData, isLoadingBookmarks)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{!isEmpty(starData) && (
|
||||||
|
<>
|
||||||
|
<Stack className={classes.sectionHeaderContainer}>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||||
|
Stars
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
className={classes.viewAll}
|
||||||
|
onClick={() => handleClickViewAll('filter', 'IsStarred')}
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
{isLoadingStars ? <Loading /> : renderCards(starData, isLoadingStars)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,8 @@ import { useParams, useNavigate, createSearchParams } from 'react-router-dom';
|
|||||||
import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography, IconButton } from '@mui/material';
|
import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography, IconButton } from '@mui/material';
|
||||||
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
||||||
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
|
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
|
||||||
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
|
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
|
||||||
// placeholder images
|
// placeholder images
|
||||||
@ -230,6 +232,17 @@ function RepoDetails() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStarClick = () => {
|
||||||
|
api.put(`${host()}${endpoints.starToggle(name)}`, abortController.signal).then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
setRepoDetailData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
isStarred: !prevState.isStarred
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getVendor = () => {
|
const getVendor = () => {
|
||||||
return `${repoDetailData.newestTag?.Vendor || 'Vendor not available'} •`;
|
return `${repoDetailData.newestTag?.Vendor || 'Vendor not available'} •`;
|
||||||
};
|
};
|
||||||
@ -276,15 +289,26 @@ function RepoDetails() {
|
|||||||
signatureInfo={repoDetailData.signatureInfo}
|
signatureInfo={repoDetailData.signatureInfo}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{isAuthenticated() && (
|
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={1}>
|
||||||
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
|
{isAuthenticated() && (
|
||||||
{repoDetailData?.isBookmarked ? (
|
<IconButton component="span" onClick={handleStarClick} data-testid="star-button">
|
||||||
<BookmarkIcon data-testid="bookmarked" />
|
{repoDetailData?.isStarred ? (
|
||||||
) : (
|
<StarIcon data-testid="starred" />
|
||||||
<BookmarkBorderIcon data-testid="not-bookmarked" />
|
) : (
|
||||||
)}
|
<StarBorderIcon data-testid="not-starred" />
|
||||||
</IconButton>
|
)}
|
||||||
)}
|
</IconButton>
|
||||||
|
)}
|
||||||
|
{isAuthenticated() && (
|
||||||
|
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
|
||||||
|
{repoDetailData?.isBookmarked ? (
|
||||||
|
<BookmarkIcon data-testid="bookmarked" />
|
||||||
|
) : (
|
||||||
|
<BookmarkBorderIcon data-testid="not-bookmarked" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Typography gutterBottom className={classes.repoTitle}>
|
<Typography gutterBottom className={classes.repoTitle}>
|
||||||
{repoDetailData?.title || 'Title not available'}
|
{repoDetailData?.title || 'Title not available'}
|
||||||
|
@ -28,6 +28,8 @@ import {
|
|||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
||||||
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
|
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
|
||||||
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
|
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
// placeholder images
|
// placeholder images
|
||||||
@ -183,17 +185,24 @@ function RepoCard(props) {
|
|||||||
platforms,
|
platforms,
|
||||||
description,
|
description,
|
||||||
downloads,
|
downloads,
|
||||||
|
stars,
|
||||||
isSigned,
|
isSigned,
|
||||||
signatureInfo,
|
signatureInfo,
|
||||||
lastUpdated,
|
lastUpdated,
|
||||||
version,
|
version,
|
||||||
vulnerabilityData,
|
vulnerabilityData,
|
||||||
isBookmarked
|
isBookmarked,
|
||||||
|
isStarred
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// keep a local bookmark state to display in the ui dynamically on updates
|
// keep a local bookmark state to display in the ui dynamically on updates
|
||||||
const [currentBookmarkValue, setCurrentBookmarkValue] = useState(isBookmarked);
|
const [currentBookmarkValue, setCurrentBookmarkValue] = useState(isBookmarked);
|
||||||
|
|
||||||
|
// keep a local star state to display in the ui dynamically on updates
|
||||||
|
const [currentStarValue, setCurrentStarValue] = useState(isStarred);
|
||||||
|
|
||||||
|
const [currentStarCount, setCurrentStarCount] = useState(stars);
|
||||||
|
|
||||||
const goToDetails = () => {
|
const goToDetails = () => {
|
||||||
navigate(`/image/${encodeURIComponent(name)}`);
|
navigate(`/image/${encodeURIComponent(name)}`);
|
||||||
};
|
};
|
||||||
@ -215,6 +224,23 @@ function RepoCard(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStarClick = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
api.put(`${host()}${endpoints.starToggle(name)}`, abortController.signal).then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
setCurrentStarValue((prevState) => !prevState);
|
||||||
|
currentStarValue
|
||||||
|
? setCurrentStarCount((prevState) => {
|
||||||
|
return !isNaN(prevState) ? prevState - 1 : prevState;
|
||||||
|
})
|
||||||
|
: setCurrentStarCount((prevState) => {
|
||||||
|
return !isNaN(prevState) ? prevState + 1 : prevState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const platformChips = () => {
|
const platformChips = () => {
|
||||||
const filteredPlatforms = uniq(platforms?.flatMap((platform) => [platform.Os, platform.Arch]));
|
const filteredPlatforms = uniq(platforms?.flatMap((platform) => [platform.Os, platform.Arch]));
|
||||||
const hiddenChips = filteredPlatforms.length - MAX_PLATFORM_CHIPS;
|
const hiddenChips = filteredPlatforms.length - MAX_PLATFORM_CHIPS;
|
||||||
@ -260,6 +286,16 @@ function RepoCard(props) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderStar = () => {
|
||||||
|
return (
|
||||||
|
isAuthenticated() && (
|
||||||
|
<IconButton component="span" onClick={handleStarClick} data-testid="star-button">
|
||||||
|
{currentStarValue ? <StarIcon data-testid="starred" /> : <StarBorderIcon data-testid="not-starred" />}
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant="outlined" className={classes.card} data-testid="repo-card">
|
<Card variant="outlined" className={classes.card} data-testid="repo-card">
|
||||||
<CardActionArea
|
<CardActionArea
|
||||||
@ -337,6 +373,15 @@ function RepoCard(props) {
|
|||||||
#1
|
#1
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid> */}
|
</Grid> */}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
{renderStar()}
|
||||||
|
<Typography variant="body2" component="span" className={classes.contentRightLabel}>
|
||||||
|
Stars •
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" component="span" className={classes.contentRightValue}>
|
||||||
|
{!isNaN(currentStarCount) ? currentStarCount : `not available`}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
<Grid container item xs={12} className={classes.contentRightActions}>
|
<Grid container item xs={12} className={classes.contentRightActions}>
|
||||||
<Grid item>{renderBookmark()}</Grid>
|
<Grid item>{renderBookmark()}</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -19,6 +19,11 @@ const imageFilters = [
|
|||||||
label: 'Bookmarks',
|
label: 'Bookmarks',
|
||||||
value: 'IsBookmarked',
|
value: 'IsBookmarked',
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Starred Repositories',
|
||||||
|
value: 'IsStarred',
|
||||||
|
type: 'boolean'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ const mapToRepo = (responseRepo) => {
|
|||||||
logo: responseRepo.NewestImage?.Logo,
|
logo: responseRepo.NewestImage?.Logo,
|
||||||
lastUpdated: responseRepo.LastUpdated,
|
lastUpdated: responseRepo.LastUpdated,
|
||||||
downloads: responseRepo.DownloadCount,
|
downloads: responseRepo.DownloadCount,
|
||||||
|
stars: responseRepo.StarCount,
|
||||||
vulnerabiltySeverity: responseRepo.NewestImage?.Vulnerabilities?.MaxSeverity,
|
vulnerabiltySeverity: responseRepo.NewestImage?.Vulnerabilities?.MaxSeverity,
|
||||||
vulnerabilityCount: responseRepo.NewestImage?.Vulnerabilities?.Count
|
vulnerabilityCount: responseRepo.NewestImage?.Vulnerabilities?.Count
|
||||||
};
|
};
|
||||||
@ -33,6 +34,7 @@ const mapToRepoFromRepoInfo = (responseRepoInfo) => {
|
|||||||
title: responseRepoInfo.Summary?.NewestImage?.Title,
|
title: responseRepoInfo.Summary?.NewestImage?.Title,
|
||||||
source: responseRepoInfo.Summary?.NewestImage?.Source,
|
source: responseRepoInfo.Summary?.NewestImage?.Source,
|
||||||
downloads: responseRepoInfo.Summary?.NewestImage?.DownloadCount,
|
downloads: responseRepoInfo.Summary?.NewestImage?.DownloadCount,
|
||||||
|
stars: responseRepoInfo.Summary?.NewestImage?.StarCount,
|
||||||
overview: responseRepoInfo.Summary?.NewestImage?.Documentation,
|
overview: responseRepoInfo.Summary?.NewestImage?.Documentation,
|
||||||
license: responseRepoInfo.Summary?.NewestImage?.Licenses,
|
license: responseRepoInfo.Summary?.NewestImage?.Licenses,
|
||||||
vulnerabilitySeverity: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
|
vulnerabilitySeverity: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
|
||||||
@ -53,6 +55,7 @@ const mapToImage = (responseImage) => {
|
|||||||
referrers: responseImage.Referrers,
|
referrers: responseImage.Referrers,
|
||||||
size: responseImage.Size,
|
size: responseImage.Size,
|
||||||
downloadCount: responseImage.DownloadCount,
|
downloadCount: responseImage.DownloadCount,
|
||||||
|
starCount: responseImage.StarCount,
|
||||||
lastUpdated: responseImage.LastUpdated,
|
lastUpdated: responseImage.LastUpdated,
|
||||||
description: responseImage.Description,
|
description: responseImage.Description,
|
||||||
isSigned: responseImage.IsSigned,
|
isSigned: responseImage.IsSigned,
|
||||||
@ -79,6 +82,7 @@ const mapToManifest = (responseManifest) => {
|
|||||||
size: responseManifest.Size,
|
size: responseManifest.Size,
|
||||||
platform: responseManifest.Platform,
|
platform: responseManifest.Platform,
|
||||||
downloadCount: responseManifest.DownloadCount,
|
downloadCount: responseManifest.DownloadCount,
|
||||||
|
starCount: responseManifest.StarCount,
|
||||||
layers: responseManifest.Layers,
|
layers: responseManifest.Layers,
|
||||||
history: responseManifest.History,
|
history: responseManifest.History,
|
||||||
vulnerabilities: responseManifest.Vulnerabilities
|
vulnerabilities: responseManifest.Vulnerabilities
|
||||||
|
@ -4,6 +4,7 @@ const HOME_PAGE_SIZE = 10;
|
|||||||
const HOME_POPULAR_PAGE_SIZE = 3;
|
const HOME_POPULAR_PAGE_SIZE = 3;
|
||||||
const HOME_RECENT_PAGE_SIZE = 2;
|
const HOME_RECENT_PAGE_SIZE = 2;
|
||||||
const HOME_BOOKMARKS_PAGE_SIZE = 2;
|
const HOME_BOOKMARKS_PAGE_SIZE = 2;
|
||||||
|
const HOME_STARS_PAGE_SIZE = 2;
|
||||||
const CVE_FIXEDIN_PAGE_SIZE = 5;
|
const CVE_FIXEDIN_PAGE_SIZE = 5;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -13,5 +14,6 @@ export {
|
|||||||
CVE_FIXEDIN_PAGE_SIZE,
|
CVE_FIXEDIN_PAGE_SIZE,
|
||||||
HOME_POPULAR_PAGE_SIZE,
|
HOME_POPULAR_PAGE_SIZE,
|
||||||
HOME_RECENT_PAGE_SIZE,
|
HOME_RECENT_PAGE_SIZE,
|
||||||
HOME_BOOKMARKS_PAGE_SIZE
|
HOME_BOOKMARKS_PAGE_SIZE,
|
||||||
|
HOME_STARS_PAGE_SIZE
|
||||||
};
|
};
|
||||||
|
@ -17,13 +17,13 @@ const pageSizes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const endpoints = {
|
const endpoints = {
|
||||||
repoList: `/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage:%20{limit:15%20offset:0}){Results%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20%20Licenses%20Logo%20Title%20Source%20IsSigned%20Documentation%20Vendor%20Labels}%20DownloadCount}}}`,
|
repoList: `/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage:%20{limit:15%20offset:0}){Results%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20%20Licenses%20Logo%20Title%20Source%20IsSigned%20Documentation%20Vendor%20Labels}%20StarCount%20DownloadCount}}}`,
|
||||||
detailedRepoInfo: (name) =>
|
detailedRepoInfo: (name) =>
|
||||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:%22${name}%22){Images%20{Manifests%20{Digest%20Platform%20{Os%20Arch}%20Size}%20Vulnerabilities%20{MaxSeverity%20Count}%20Tag%20LastUpdated%20Vendor%20}%20Summary%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20Vendors%20NewestImage%20{RepoName%20IsSigned%20Vulnerabilities%20{MaxSeverity%20Count}%20Manifests%20{Digest}%20Tag%20Title%20Documentation%20DownloadCount%20Source%20Description%20Licenses}}}}`,
|
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:%22${name}%22){Images%20{Manifests%20{Digest%20Platform%20{Os%20Arch}%20Size}%20Vulnerabilities%20{MaxSeverity%20Count}%20Tag%20LastUpdated%20Vendor%20}%20Summary%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20Vendors%20NewestImage%20{RepoName%20IsSigned%20Vulnerabilities%20{MaxSeverity%20Count}%20Manifests%20{Digest}%20Tag%20Title%20Documentation%20DownloadCount%20Source%20Description%20Licenses}}}}`,
|
||||||
globalSearch: (searchTerm, sortCriteria, pageNumber = 1, pageSize = 10) =>
|
globalSearch: (searchTerm, sortCriteria, pageNumber = 1, pageSize = 10) =>
|
||||||
`/v2/_zot/ext/search?query={GlobalSearch(query:%22${searchTerm}%22,%20requestedPage:%20{limit:${pageSize}%20offset:${
|
`/v2/_zot/ext/search?query={GlobalSearch(query:%22${searchTerm}%22,%20requestedPage:%20{limit:${pageSize}%20offset:${
|
||||||
10 * (pageNumber - 1)
|
10 * (pageNumber - 1)
|
||||||
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Licenses%20Vendor%20Labels%20}%20DownloadCount}}}`,
|
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Licenses%20Vendor%20Labels%20}%20StarCount%20DownloadCount}}}`,
|
||||||
image: (name) =>
|
image: (name) =>
|
||||||
`/v2/_zot/ext/search?query={Image(image:%20%22${name}%22){RepoName%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Vulnerabilities%20{MaxSeverity%20Count}%20%20Referrers%20{MediaType%20ArtifactType%20Size%20Digest%20Annotations{Key%20Value}}%20Tag%20Manifests%20{History%20{Layer%20{Size%20Digest}%20HistoryDescription%20{CreatedBy%20EmptyLayer}}%20Digest%20ConfigDigest%20LastUpdated%20Size%20Platform%20{Os%20Arch}}%20Vendor%20Licenses%20}}`
|
`/v2/_zot/ext/search?query={Image(image:%20%22${name}%22){RepoName%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Vulnerabilities%20{MaxSeverity%20Count}%20%20Referrers%20{MediaType%20ArtifactType%20Size%20Digest%20Annotations{Key%20Value}}%20Tag%20Manifests%20{History%20{Layer%20{Size%20Digest}%20HistoryDescription%20{CreatedBy%20EmptyLayer}}%20Digest%20ConfigDigest%20LastUpdated%20Size%20Platform%20{Os%20Arch}}%20Vendor%20Licenses%20}}`
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user