mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(docker/containers): migrate networks table to react [EE-4665] (#10069)
This commit is contained in:
parent
776f6a62c3
commit
b15812a74d
28 changed files with 632 additions and 259 deletions
|
@ -0,0 +1,85 @@
|
|||
import { Form, Formik } from 'formik';
|
||||
import { SchemaOf, object, string } from 'yup';
|
||||
import { useRouter } from '@uirouter/react';
|
||||
|
||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
import { useConnectContainerMutation } from '@/react/docker/networks/queries/useConnectContainer';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { LoadingButton } from '@@/buttons';
|
||||
|
||||
import { NetworkSelector } from '../../components/NetworkSelector';
|
||||
|
||||
interface FormValues {
|
||||
networkId: string;
|
||||
}
|
||||
|
||||
export function ConnectNetworkForm({
|
||||
nodeName,
|
||||
containerId,
|
||||
selectedNetworks,
|
||||
}: {
|
||||
nodeName?: string;
|
||||
containerId: string;
|
||||
selectedNetworks: string[];
|
||||
}) {
|
||||
const environmentId = useEnvironmentId();
|
||||
const authorized = useAuthorizations('DockerNetworkConnect');
|
||||
const connectMutation = useConnectContainerMutation(environmentId);
|
||||
const router = useRouter();
|
||||
if (!authorized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik<FormValues>
|
||||
initialValues={{ networkId: '' }}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validation}
|
||||
>
|
||||
{({ values, errors, setFieldValue }) => (
|
||||
<Form className="form-horizontal w-full">
|
||||
<FormControl
|
||||
label="Join a network"
|
||||
className="!mb-0"
|
||||
errors={errors.networkId}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-full">
|
||||
<NetworkSelector
|
||||
value={values.networkId}
|
||||
onChange={(value) => setFieldValue('networkId', value)}
|
||||
hiddenNetworks={selectedNetworks}
|
||||
/>
|
||||
</div>
|
||||
<LoadingButton
|
||||
loadingText="Joining network..."
|
||||
isLoading={connectMutation.isLoading}
|
||||
>
|
||||
Join Network
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</FormControl>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
function handleSubmit({ networkId }: { networkId: string }) {
|
||||
connectMutation.mutate(
|
||||
{ containerId, networkId, nodeName },
|
||||
{
|
||||
onSuccess() {
|
||||
router.stateService.reload();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
networkId: string().required('Please select a network'),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { Share2 } from 'lucide-react';
|
||||
import { EndpointSettings, NetworkSettings } from 'docker-types/generated/1.41';
|
||||
|
||||
import { createPersistedStore } from '@@/datatables/types';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
|
||||
import { withMeta } from '@@/datatables/extend-options/withMeta';
|
||||
|
||||
import { DockerContainer } from '../../types';
|
||||
|
||||
import { TableNetwork } from './types';
|
||||
import { columns } from './columns';
|
||||
import { ConnectNetworkForm } from './ConnectNetworkForm';
|
||||
|
||||
const storageKey = 'container-networks';
|
||||
const store = createPersistedStore(storageKey, 'name');
|
||||
|
||||
export function ContainerNetworksDatatable({
|
||||
dataset,
|
||||
container,
|
||||
nodeName,
|
||||
}: {
|
||||
dataset: NetworkSettings['Networks'];
|
||||
container: DockerContainer;
|
||||
nodeName?: string;
|
||||
}) {
|
||||
const tableState = useTableState(store, storageKey);
|
||||
|
||||
const networks: Array<TableNetwork> = Object.entries(dataset || {})
|
||||
.filter(isNetworkDefined)
|
||||
.map(([id, network]) => ({
|
||||
...network,
|
||||
id,
|
||||
name: id,
|
||||
}));
|
||||
|
||||
return (
|
||||
<ExpandableDatatable<TableNetwork>
|
||||
columns={columns}
|
||||
dataset={networks}
|
||||
settingsManager={tableState}
|
||||
title="Connected Networks"
|
||||
titleIcon={Share2}
|
||||
disableSelect
|
||||
getRowCanExpand={(row) => !!row.original.GlobalIPv6Address}
|
||||
isLoading={!dataset}
|
||||
renderSubRow={({ original: item }) => (
|
||||
<tr className="datatable-highlighted">
|
||||
<td colSpan={2} />
|
||||
<td>{item.GlobalIPv6Address}</td>
|
||||
<td colSpan={3}>{item.IPv6Gateway || '-'}</td>
|
||||
</tr>
|
||||
)}
|
||||
description={
|
||||
<ConnectNetworkForm
|
||||
containerId={container.Id}
|
||||
nodeName={nodeName}
|
||||
selectedNetworks={networks.map((n) => n.id)}
|
||||
/>
|
||||
}
|
||||
extendTableOptions={withMeta({
|
||||
table: 'container-networks',
|
||||
containerId: container.Id,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function isNetworkDefined(
|
||||
value: [string, EndpointSettings | undefined]
|
||||
): value is [string, EndpointSettings] {
|
||||
return value.length > 1 && !!value[1];
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { CellContext } from '@tanstack/react-table';
|
||||
import { useRouter } from '@uirouter/react';
|
||||
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
import { useDisconnectContainer } from '@/react/docker/networks/queries';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { LoadingButton } from '@@/buttons';
|
||||
|
||||
import { TableNetwork, isContainerNetworkTableMeta } from './types';
|
||||
import { columnHelper } from './helper';
|
||||
|
||||
export const actions = columnHelper.display({
|
||||
header: 'Actions',
|
||||
cell: Cell,
|
||||
});
|
||||
|
||||
function Cell({
|
||||
row,
|
||||
table: {
|
||||
options: { meta },
|
||||
},
|
||||
}: CellContext<TableNetwork, unknown>) {
|
||||
const router = useRouter();
|
||||
const environmentId = useEnvironmentId();
|
||||
const disconnectMutation = useDisconnectContainer();
|
||||
|
||||
return (
|
||||
<Authorized authorizations="DockerNetworkDisconnect">
|
||||
<LoadingButton
|
||||
color="dangerlight"
|
||||
isLoading={disconnectMutation.isLoading}
|
||||
loadingText="Leaving network..."
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Leave network
|
||||
</LoadingButton>
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
function handleSubmit() {
|
||||
if (!isContainerNetworkTableMeta(meta)) {
|
||||
throw new Error('Invalid row meta');
|
||||
}
|
||||
|
||||
disconnectMutation.mutate(
|
||||
{
|
||||
environmentId,
|
||||
networkId: row.original.id,
|
||||
containerId: meta.containerId,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
router.stateService.reload();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { buildExpandColumn } from '@@/datatables/expand-column';
|
||||
import { buildNameColumn } from '@@/datatables/buildNameColumn';
|
||||
|
||||
import { TableNetwork } from './types';
|
||||
import { columnHelper } from './helper';
|
||||
import { actions } from './actions';
|
||||
|
||||
export const columns = [
|
||||
buildExpandColumn<TableNetwork>(),
|
||||
{
|
||||
...buildNameColumn<TableNetwork>('name', 'docker.networks.network'),
|
||||
header: 'Network',
|
||||
},
|
||||
columnHelper.accessor((item) => item.IPAddress || '-', {
|
||||
header: 'IP Address',
|
||||
id: 'ip',
|
||||
enableSorting: false,
|
||||
}),
|
||||
columnHelper.accessor((item) => item.Gateway || '-', {
|
||||
header: 'Gateway',
|
||||
id: 'gateway',
|
||||
enableSorting: false,
|
||||
}),
|
||||
columnHelper.accessor((item) => item.MacAddress || '-', {
|
||||
header: 'MAC Address',
|
||||
id: 'macAddress',
|
||||
enableSorting: false,
|
||||
}),
|
||||
actions,
|
||||
];
|
|
@ -0,0 +1,5 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { TableNetwork } from './types';
|
||||
|
||||
export const columnHelper = createColumnHelper<TableNetwork>();
|
|
@ -0,0 +1 @@
|
|||
export { ContainerNetworksDatatable } from './ContainerNetworksDatatable';
|
|
@ -0,0 +1,15 @@
|
|||
import { TableMeta } from '@tanstack/react-table';
|
||||
import { EndpointSettings } from 'docker-types/generated/1.41';
|
||||
|
||||
export type TableNetwork = EndpointSettings & { id: string; name: string };
|
||||
|
||||
export type ContainerNetworkTableMeta = TableMeta<TableNetwork> & {
|
||||
table: 'container-networks';
|
||||
containerId: string;
|
||||
};
|
||||
|
||||
export function isContainerNetworkTableMeta(
|
||||
meta?: TableMeta<TableNetwork>
|
||||
): meta is ContainerNetworkTableMeta {
|
||||
return !!meta && meta.table === 'container-networks';
|
||||
}
|
71
app/react/docker/containers/components/NetworkSelector.tsx
Normal file
71
app/react/docker/containers/components/NetworkSelector.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { useNetworks } from '@/react/docker/networks/queries/useNetworks';
|
||||
import { DockerNetwork } from '@/react/docker/networks/types';
|
||||
import { useIsSwarm } from '@/react/docker/proxy/queries/useInfo';
|
||||
import { useApiVersion } from '@/react/docker/proxy/queries/useVersion';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||
|
||||
export function NetworkSelector({
|
||||
onChange,
|
||||
additionalOptions = [],
|
||||
value,
|
||||
hiddenNetworks = [],
|
||||
}: {
|
||||
value: string;
|
||||
additionalOptions?: Array<Option<string>>;
|
||||
onChange: (value: string) => void;
|
||||
hiddenNetworks?: string[];
|
||||
}) {
|
||||
const networksQuery = useNetworksForSelector({
|
||||
select(networks) {
|
||||
return networks.map((n) => ({ label: n.Name, value: n.Name }));
|
||||
},
|
||||
});
|
||||
|
||||
const networks = networksQuery.data;
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
(networks || [])
|
||||
.concat(additionalOptions)
|
||||
.filter((n) => !hiddenNetworks.includes(n.value))
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
[additionalOptions, hiddenNetworks, networks]
|
||||
);
|
||||
|
||||
return (
|
||||
<PortainerSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
isLoading={networksQuery.isLoading}
|
||||
bindToBody
|
||||
placeholder="Select a network"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNetworksForSelector<T = DockerNetwork[]>({
|
||||
select,
|
||||
}: {
|
||||
select?(networks: Array<DockerNetwork>): T;
|
||||
} = {}) {
|
||||
const environmentId = useEnvironmentId();
|
||||
|
||||
const isSwarmQuery = useIsSwarm(environmentId);
|
||||
const dockerApiVersion = useApiVersion(environmentId);
|
||||
|
||||
return useNetworks(
|
||||
environmentId,
|
||||
{
|
||||
local: true,
|
||||
swarmAttachable: isSwarmQuery && dockerApiVersion >= 1.25,
|
||||
},
|
||||
{
|
||||
select,
|
||||
}
|
||||
);
|
||||
}
|
20
app/react/docker/networks/queries/buildUrl.ts
Normal file
20
app/react/docker/networks/queries/buildUrl.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildUrl as buildDockerUrl } from '../../proxy/queries/build-url';
|
||||
import { NetworkId } from '../types';
|
||||
|
||||
export function buildUrl(
|
||||
environmentId: EnvironmentId,
|
||||
{ id, action }: { id?: NetworkId; action?: string } = {}
|
||||
) {
|
||||
let baseUrl = 'networks';
|
||||
if (id) {
|
||||
baseUrl += `/${id}`;
|
||||
}
|
||||
|
||||
if (action) {
|
||||
baseUrl += `/${action}`;
|
||||
}
|
||||
|
||||
return buildDockerUrl(environmentId, baseUrl);
|
||||
}
|
12
app/react/docker/networks/queries/queryKeys.ts
Normal file
12
app/react/docker/networks/queries/queryKeys.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { queryKeys as dockerQueryKeys } from '../../queries/utils';
|
||||
|
||||
import { NetworksQuery } from './types';
|
||||
|
||||
export const queryKeys = {
|
||||
base: (environmentId: EnvironmentId) =>
|
||||
[...dockerQueryKeys.root(environmentId), 'networks'] as const,
|
||||
list: (environmentId: EnvironmentId, query: NetworksQuery) =>
|
||||
[...queryKeys.base(environmentId), 'list', query] as const,
|
||||
};
|
23
app/react/docker/networks/queries/types.ts
Normal file
23
app/react/docker/networks/queries/types.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
interface Filters {
|
||||
/* dangling=<boolean> When set to true (or 1), returns all networks that are not in use by a container. When set to false (or 0), only networks that are in use by one or more containers are returned. */
|
||||
dangling?: boolean[];
|
||||
// Matches a network's driver
|
||||
driver?: string[];
|
||||
// Matches all or part of a network ID
|
||||
id?: string[];
|
||||
// `label=<key>` or `label=<key>=<value>` of a network label.
|
||||
label?: string[];
|
||||
// Matches all or part of a network name.
|
||||
name?: string[];
|
||||
// Filters networks by scope (swarm, global, or local).
|
||||
scope?: ('swarm' | 'global' | 'local')[];
|
||||
// Filters networks by type. The custom keyword returns all user-defined networks.
|
||||
type?: ('custom' | 'builtin')[];
|
||||
}
|
||||
|
||||
export interface NetworksQuery {
|
||||
local?: boolean;
|
||||
swarm?: boolean;
|
||||
swarmAttachable?: boolean;
|
||||
filters?: Filters;
|
||||
}
|
73
app/react/docker/networks/queries/useConnectContainer.ts
Normal file
73
app/react/docker/networks/queries/useConnectContainer.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { EndpointSettings } from 'docker-types/generated/1.41';
|
||||
import { AxiosRequestHeaders } from 'axios';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
mutationOptions,
|
||||
withError,
|
||||
withInvalidate,
|
||||
} from '@/react-tools/react-query';
|
||||
|
||||
import { queryKeys as dockerQueryKeys } from '../../queries/utils';
|
||||
|
||||
import { buildUrl } from './buildUrl';
|
||||
|
||||
interface ConnectContainerPayload {
|
||||
Container: string;
|
||||
EndpointConfig?: EndpointSettings;
|
||||
}
|
||||
|
||||
export function useConnectContainerMutation(environmentId: EnvironmentId) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
(params: Omit<ConnectContainer, 'environmentId'>) =>
|
||||
connectContainer({ ...params, environmentId }),
|
||||
mutationOptions(
|
||||
withError('Failed connecting container to network'),
|
||||
withInvalidate(queryClient, [dockerQueryKeys.containers(environmentId)])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface ConnectContainer {
|
||||
environmentId: EnvironmentId;
|
||||
networkId: string;
|
||||
containerId: string;
|
||||
aliases?: EndpointSettings['Aliases'];
|
||||
nodeName?: string;
|
||||
}
|
||||
|
||||
export async function connectContainer({
|
||||
environmentId,
|
||||
containerId,
|
||||
networkId,
|
||||
aliases,
|
||||
nodeName,
|
||||
}: ConnectContainer) {
|
||||
const payload: ConnectContainerPayload = {
|
||||
Container: containerId,
|
||||
};
|
||||
if (aliases) {
|
||||
payload.EndpointConfig = {
|
||||
Aliases: aliases,
|
||||
};
|
||||
}
|
||||
|
||||
const headers: AxiosRequestHeaders = {};
|
||||
|
||||
if (nodeName) {
|
||||
headers['X-PortainerAgent-Target'] = nodeName;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
buildUrl(environmentId, { id: networkId, action: 'connect' }),
|
||||
payload
|
||||
);
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err as Error, 'Unable to connect container');
|
||||
}
|
||||
}
|
59
app/react/docker/networks/queries/useNetworks.ts
Normal file
59
app/react/docker/networks/queries/useNetworks.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildUrl } from '../../proxy/queries/build-url';
|
||||
import { DockerNetwork } from '../types';
|
||||
|
||||
import { queryKeys } from './queryKeys';
|
||||
import { NetworksQuery } from './types';
|
||||
|
||||
export function useNetworks<T = Array<DockerNetwork>>(
|
||||
environmentId: EnvironmentId,
|
||||
query: NetworksQuery,
|
||||
{
|
||||
enabled = true,
|
||||
onSuccess,
|
||||
select,
|
||||
}: {
|
||||
enabled?: boolean;
|
||||
onSuccess?(networks: T): void;
|
||||
select?(networks: Array<DockerNetwork>): T;
|
||||
} = {}
|
||||
) {
|
||||
return useQuery(
|
||||
queryKeys.list(environmentId, query),
|
||||
() => getNetworks(environmentId, query),
|
||||
{ enabled, onSuccess, select }
|
||||
);
|
||||
}
|
||||
|
||||
export async function getNetworks(
|
||||
environmentId: EnvironmentId,
|
||||
{ local, swarm, swarmAttachable, filters }: NetworksQuery
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.get<Array<DockerNetwork>>(
|
||||
buildUrl(environmentId, 'networks'),
|
||||
filters && {
|
||||
params: {
|
||||
filters,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return !local && !swarm && !swarmAttachable
|
||||
? data
|
||||
: data.filter(
|
||||
(network) =>
|
||||
(local && network.Scope === 'local') ||
|
||||
(swarm && network.Scope === 'swarm') ||
|
||||
(swarmAttachable &&
|
||||
network.Scope === 'swarm' &&
|
||||
network.Attachable === true)
|
||||
);
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err as Error, 'Unable to retrieve networks');
|
||||
}
|
||||
}
|
|
@ -41,3 +41,14 @@ export function useIsSwarm(environmentId: EnvironmentId) {
|
|||
|
||||
return !!query.data;
|
||||
}
|
||||
|
||||
export function useSystemLimits(environmentId: EnvironmentId) {
|
||||
const infoQuery = useInfo(environmentId);
|
||||
|
||||
const maxCpu = infoQuery.data?.NCPU || 32;
|
||||
const maxMemory = infoQuery.data?.MemTotal
|
||||
? Math.floor(infoQuery.data.MemTotal / 1000 / 1000)
|
||||
: 32768;
|
||||
|
||||
return { maxCpu, maxMemory };
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import { useQuery } from 'react-query';
|
||||
import { SystemVersion } from 'docker-types/generated/1.41';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildUrl } from './build-url';
|
||||
|
||||
export interface VersionResponse {
|
||||
ApiVersion: string;
|
||||
}
|
||||
|
||||
export async function getVersion(environmentId: EnvironmentId) {
|
||||
try {
|
||||
const { data } = await axios.get<VersionResponse>(
|
||||
const { data } = await axios.get<SystemVersion>(
|
||||
buildUrl(environmentId, 'version')
|
||||
);
|
||||
return data;
|
||||
|
@ -20,9 +17,9 @@ export async function getVersion(environmentId: EnvironmentId) {
|
|||
}
|
||||
}
|
||||
|
||||
export function useVersion<TSelect = VersionResponse>(
|
||||
export function useVersion<TSelect = SystemVersion>(
|
||||
environmentId: EnvironmentId,
|
||||
select?: (info: VersionResponse) => TSelect
|
||||
select?: (info: SystemVersion) => TSelect
|
||||
) {
|
||||
return useQuery(
|
||||
['environment', environmentId, 'docker', 'version'],
|
||||
|
@ -32,3 +29,8 @@ export function useVersion<TSelect = VersionResponse>(
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useApiVersion(environmentId: EnvironmentId) {
|
||||
const query = useVersion(environmentId, (info) => info.ApiVersion);
|
||||
return query.data ? parseFloat(query.data) : 0;
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
export const queryKeys = {
|
||||
root: (environmentId: EnvironmentId) => ['docker', environmentId] as const,
|
||||
};
|
36
app/react/docker/queries/utils/container.ts
Normal file
36
app/react/docker/queries/utils/container.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { DockerContainer } from '@/react/docker/containers/types';
|
||||
import { EdgeStack } from '@/react/edge/edge-stacks/types';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildDockerSnapshotUrl, queryKeys as rootQueryKeys } from './root';
|
||||
|
||||
export interface ContainersQueryParams {
|
||||
edgeStackId?: EdgeStack['Id'];
|
||||
}
|
||||
|
||||
export const queryKeys = {
|
||||
...rootQueryKeys,
|
||||
containers: (environmentId: EnvironmentId) =>
|
||||
[...queryKeys.snapshot(environmentId), 'containers'] as const,
|
||||
containersQuery: (
|
||||
environmentId: EnvironmentId,
|
||||
params: ContainersQueryParams
|
||||
) => [...queryKeys.containers(environmentId), params] as const,
|
||||
container: (
|
||||
environmentId: EnvironmentId,
|
||||
containerId: DockerContainer['Id']
|
||||
) => [...queryKeys.containers(environmentId), containerId] as const,
|
||||
};
|
||||
|
||||
export function buildDockerSnapshotContainersUrl(
|
||||
environmentId: EnvironmentId,
|
||||
containerId?: DockerContainer['Id']
|
||||
) {
|
||||
let url = `${buildDockerSnapshotUrl(environmentId)}/containers`;
|
||||
|
||||
if (containerId) {
|
||||
url += `/${containerId}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
14
app/react/docker/queries/utils/index.ts
Normal file
14
app/react/docker/queries/utils/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { queryKeys as containerQueryKeys } from './container';
|
||||
import { queryKeys as rootQueryKeys } from './root';
|
||||
|
||||
export const queryKeys = {
|
||||
...rootQueryKeys,
|
||||
...containerQueryKeys,
|
||||
};
|
||||
|
||||
export {
|
||||
buildDockerSnapshotContainersUrl,
|
||||
type ContainersQueryParams,
|
||||
} from './container';
|
||||
|
||||
export { buildDockerSnapshotUrl } from './root';
|
Loading…
Add table
Add a link
Reference in a new issue