refactor(registries): migrate repos table to react [EE-6451] (#10830)

pull/10528/head
Chaim Lev-Ari 2024-01-02 14:04:15 +07:00 committed by GitHub
parent 3ae430bdd8
commit ba19aab8dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 27 deletions

View File

@ -7,6 +7,8 @@ import {
DefaultRegistryDomain,
DefaultRegistryName,
} from '@/react/portainer/registries/ListView/DefaultRegistry';
import { RepositoriesDatatable } from '@/react/portainer/registries/repositories/ListView/RepositoriesDatatable';
import { withUIRouter } from '@/react-tools/withUIRouter';
export const registriesModule = angular
.module('portainer.app.react.components.registries', [])
@ -21,4 +23,8 @@ export const registriesModule = angular
.component(
'defaultRegistryDomain',
r2a(withReactQuery(DefaultRegistryDomain), [])
)
.component(
'registryRepositoriesDatatable',
r2a(withUIRouter(withReactQuery(RepositoriesDatatable)), ['dataset'])
).name;

View File

@ -25,6 +25,13 @@ export function useTableState<
);
}
export function useTableStateWithStorage(
...args: Parameters<typeof createPersistedStore>
) {
const [store] = useState(() => createPersistedStore(...args));
return useTableState(store, args[0]);
}
export function useTableStateWithoutStorage(
defaultSortKey?: string
): BasicTableSettings & {

View File

@ -1,3 +0,0 @@
export const queryKeys = {
registries: () => ['registries'] as const,
};

View File

@ -1,6 +1,6 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { Catalog, Repository } from './types/registry';
import { Catalog } from './types/registry';
export async function listRegistryCatalogs(registryId: number) {
try {
@ -12,21 +12,3 @@ export async function listRegistryCatalogs(registryId: number) {
throw parseAxiosError(err as Error, 'Failed to get catalog of registry');
}
}
export async function listRegistryCatalogsRepository(
registryId: number,
repositoryName: string
) {
try {
const { data } = await axios.get<Repository>(
`/registries/${registryId}/v2/${repositoryName}/tags/list`,
{}
);
return data;
} catch (err) {
throw parseAxiosError(
err as Error,
'Failed to get catelog repository of regisry'
);
}
}

View File

@ -0,0 +1,23 @@
import { Book } from 'lucide-react';
import { Datatable } from '@@/datatables';
import { useTableStateWithStorage } from '@@/datatables/useTableState';
import { Repository } from './types';
import { columns } from './columns';
export function RepositoriesDatatable({ dataset }: { dataset?: Repository[] }) {
const tableState = useTableStateWithStorage('registryRepositories');
return (
<Datatable
title="Repositories"
titleIcon={Book}
columns={columns}
dataset={dataset || []}
isLoading={!dataset}
settingsManager={tableState}
emptyContentLabel="No repository available."
disableSelect
/>
);
}

View File

@ -0,0 +1,68 @@
import { createColumnHelper, CellContext } from '@tanstack/react-table';
import { useCurrentStateAndParams } from '@uirouter/react';
import { Link } from '@@/Link';
import { useRepositoryTags } from './useRepositoryTags';
import { Repository } from './types';
const helper = createColumnHelper<Repository>();
export const columns = [
helper.accessor('Name', {
header: 'Repository',
cell: NameCell,
}),
helper.display({
header: 'Tags count',
cell: TagsCell,
}),
];
function useParams() {
const {
params: { endpointId, id },
} = useCurrentStateAndParams();
const registryId = number(id);
if (!registryId) {
throw new Error('Missing registry id');
}
return {
environmentId: number(endpointId),
registryId,
};
}
function number(value: string | undefined) {
const num = parseInt(value || '', 10);
return Number.isNaN(num) ? undefined : num;
}
function NameCell({ getValue }: CellContext<Repository, string>) {
const { environmentId } = useParams();
const name = getValue();
return (
<Link
to="portainer.registries.registry.repository"
params={{ repository: name, endpointId: environmentId }}
title={name}
>
{name}
</Link>
);
}
function TagsCell({ row }: CellContext<Repository, unknown>) {
const { environmentId, registryId } = useParams();
const tagsQuery = useRepositoryTags({
environmentId,
registryId,
repository: row.original.Name,
});
return tagsQuery.data?.tags.length || 0;
}

View File

@ -0,0 +1,3 @@
export interface Repository {
Name: string;
}

View File

@ -0,0 +1,72 @@
import { useQuery } from 'react-query';
import _ from 'lodash';
import { Environment } from '@/react/portainer/environments/types';
import axios from '@/portainer/services/axios';
import { Registry } from '../../types/registry';
import { buildUrl } from '../../queries/build-url';
import { queryKeys } from '../../queries/query-keys';
export function useRepositoryTags({
registryId,
...params
}: {
registryId: Registry['Id'];
repository: string;
environmentId?: Environment['Id'];
}) {
return useQuery({
queryKey: [...queryKeys.item(registryId), params] as const,
queryFn: () =>
getRepositoryTags({
...params,
registryId,
n: 100,
last: '',
}),
staleTime: 1 * 60 * 1000, // 1 minute
});
}
export async function getRepositoryTags(
{
environmentId,
registryId,
repository,
n,
last,
}: {
registryId: Registry['Id'];
repository: string;
environmentId?: Environment['Id'];
n?: number;
last?: string;
},
acc: { name: string; tags: string[] } = { name: '', tags: [] }
): Promise<{ name: string; tags: string[] }> {
const { data, headers } = await axios.get<{ name: string; tags: string[] }>(
`${buildUrl(registryId)}/v2/${repository}/tags/list`,
{
params: {
id: registryId,
endpointId: environmentId,
repository,
n,
last,
},
}
);
acc.name = data.name;
acc.tags = _.concat(acc.tags, data.tags);
if (headers.link) {
const last = data.tags[data.tags.length - 1];
return getRepositoryTags(
{ registryId, repository, n, last, environmentId },
acc
);
}
return acc;
}

View File

@ -7,11 +7,6 @@ export type Catalog = {
repositories: string[];
};
export type Repository = {
name: string;
tags: string[];
};
export enum RegistryTypes {
ANONYMOUS,
QUAY,