mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +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';
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue