chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:08:39 +08:00
commit a0df89c693
5252 changed files with 523444 additions and 0 deletions
@@ -0,0 +1,85 @@
import { Form, Formik } from 'formik';
import { SchemaOf, object, string } from 'yup';
import { useAuthorizations } from '@/react/hooks/useUser';
import { useConnectContainerMutation } from '@/react/docker/networks/queries/useConnectContainerMutation';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { notifySuccess } from '@/portainer/services/notifications';
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);
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..."
data-cy="connect-network-button"
isLoading={connectMutation.isLoading}
>
Join Network
</LoadingButton>
</div>
</FormControl>
</Form>
)}
</Formik>
);
function handleSubmit({ networkId }: { networkId: string }) {
connectMutation.mutate(
{ containerId, networkId, nodeName },
{
onSuccess() {
notifySuccess('Success', `Connected container to ${networkId}`);
},
}
);
}
}
function validation(): SchemaOf<FormValues> {
return object({
networkId: string().required('Please select a network'),
});
}
@@ -0,0 +1,79 @@
import { useMemo } from 'react';
import { Network } from 'lucide-react';
import { EndpointSettings, NetworkSettings } from 'docker-types';
import { createPersistedStore } from '@@/datatables/types';
import { useTableState } from '@@/datatables/useTableState';
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
import { withMeta } from '@@/datatables/extend-options/withMeta';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { ContainerListViewModel } from '../../types';
import { TableNetwork } from './types';
import { buildColumns } from './columns';
import { ConnectNetworkForm } from './ConnectNetworkForm';
const storageKey = 'container-networks';
const store = createPersistedStore(storageKey, 'name');
export function ContainerNetworksDatatable({
dataset,
containerId,
nodeName,
}: {
dataset: NetworkSettings['Networks'];
containerId: ContainerListViewModel['Id'];
nodeName?: string;
}) {
const tableState = useTableState(store, storageKey);
const columns = useMemo(() => buildColumns({ nodeName }), [nodeName]);
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={Network}
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={containerId}
nodeName={nodeName}
selectedNetworks={networks.map((n) => n.id)}
/>
}
extendTableOptions={mergeOptions(
withMeta({
table: 'container-networks',
containerId,
})
)}
data-cy="container-networks-datatable"
/>
);
}
function isNetworkDefined(
value: [string, EndpointSettings | undefined]
): value is [string, EndpointSettings] {
return value.length > 1 && !!value[1];
}
@@ -0,0 +1,66 @@
import { CellContext } from '@tanstack/react-table';
import { Authorized } from '@/react/hooks/useUser';
import { useDisconnectContainer } from '@/react/docker/networks/queries/useDisconnectContainerMutation';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { notifySuccess } from '@/portainer/services/notifications';
import { LoadingButton } from '@@/buttons';
import { TableNetwork, isContainerNetworkTableMeta } from './types';
import { columnHelper } from './helper';
export function buildActions({ nodeName }: { nodeName?: string } = {}) {
return columnHelper.display({
header: 'Actions',
cell: Cell,
});
function Cell({
row: {
original: { id: networkId },
},
table: {
options: { meta },
},
}: CellContext<TableNetwork, unknown>) {
const environmentId = useEnvironmentId();
const disconnectMutation = useDisconnectContainer({
environmentId,
networkId,
});
return (
<Authorized authorizations="DockerNetworkDisconnect">
<LoadingButton
color="dangerlight"
data-cy="disconnect-network-button"
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(
{
containerId: meta.containerId,
nodeName,
},
{
onSuccess() {
notifySuccess('Container successfully disconnected', networkId);
},
}
);
}
}
}
@@ -0,0 +1,37 @@
import { buildExpandColumn } from '@@/datatables/expand-column';
import { buildNameColumnFromObject } from '@@/datatables/buildNameColumn';
import { TableNetwork } from './types';
import { columnHelper } from './helper';
import { buildActions } from './actions';
export function buildColumns({ nodeName }: { nodeName?: string } = {}) {
return [
buildExpandColumn<TableNetwork>(),
{
...buildNameColumnFromObject<TableNetwork>({
nameKey: 'name',
path: 'docker.networks.network',
dataCy: 'docker-networks-name',
linkParamsBuilder: () => ({ nodeName }),
}),
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,
}),
buildActions({ nodeName }),
];
}
@@ -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';
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 && 'table' in meta && meta.table === 'container-networks';
}