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,
|
DefaultRegistryDomain,
|
||||||
DefaultRegistryName,
|
DefaultRegistryName,
|
||||||
} from '@/react/portainer/registries/ListView/DefaultRegistry';
|
} 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
|
export const registriesModule = angular
|
||||||
.module('portainer.app.react.components.registries', [])
|
.module('portainer.app.react.components.registries', [])
|
||||||
|
@ -21,4 +23,8 @@ export const registriesModule = angular
|
||||||
.component(
|
.component(
|
||||||
'defaultRegistryDomain',
|
'defaultRegistryDomain',
|
||||||
r2a(withReactQuery(DefaultRegistryDomain), [])
|
r2a(withReactQuery(DefaultRegistryDomain), [])
|
||||||
|
)
|
||||||
|
.component(
|
||||||
|
'registryRepositoriesDatatable',
|
||||||
|
r2a(withUIRouter(withReactQuery(RepositoriesDatatable)), ['dataset'])
|
||||||
).name;
|
).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(
|
export function useTableStateWithoutStorage(
|
||||||
defaultSortKey?: string
|
defaultSortKey?: string
|
||||||
): BasicTableSettings & {
|
): BasicTableSettings & {
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const queryKeys = {
|
|
||||||
registries: () => ['registries'] as const,
|
|
||||||
};
|
|
|
@ -1,6 +1,6 @@
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
|
|
||||||
import { Catalog, Repository } from './types/registry';
|
import { Catalog } from './types/registry';
|
||||||
|
|
||||||
export async function listRegistryCatalogs(registryId: number) {
|
export async function listRegistryCatalogs(registryId: number) {
|
||||||
try {
|
try {
|
||||||
|
@ -12,21 +12,3 @@ export async function listRegistryCatalogs(registryId: number) {
|
||||||
throw parseAxiosError(err as Error, 'Failed to get catalog of registry');
|
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[];
|
repositories: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Repository = {
|
|
||||||
name: string;
|
|
||||||
tags: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum RegistryTypes {
|
export enum RegistryTypes {
|
||||||
ANONYMOUS,
|
ANONYMOUS,
|
||||||
QUAY,
|
QUAY,
|
||||||
|
|
Loading…
Reference in New Issue