mirror of https://github.com/portainer/portainer
refactor(registries): migrate repos table to react [EE-6451] (#10830)
parent
3ae430bdd8
commit
ba19aab8dc
|
@ -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;
|
||||
|
|
|
@ -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 & {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export const queryKeys = {
|
||||
registries: () => ['registries'] as const,
|
||||
};
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface Repository {
|
||||
Name: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -7,11 +7,6 @@ export type Catalog = {
|
|||
repositories: string[];
|
||||
};
|
||||
|
||||
export type Repository = {
|
||||
name: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export enum RegistryTypes {
|
||||
ANONYMOUS,
|
||||
QUAY,
|
||||
|
|
Loading…
Reference in New Issue