mirror of
https://github.com/portainer/portainer.git
synced 2025-08-10 08:15:25 +02:00
feat(docker/networks): show sub networks
This commit is contained in:
parent
24341cd1ac
commit
8143fab676
8 changed files with 134 additions and 64 deletions
|
@ -1,33 +0,0 @@
|
|||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
export function NetworkViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Scope = data.Scope;
|
||||
this.Driver = data.Driver;
|
||||
this.Attachable = data.Attachable;
|
||||
this.Internal = data.Internal;
|
||||
this.IPAM = data.IPAM;
|
||||
this.Containers = data.Containers;
|
||||
this.Options = data.Options;
|
||||
this.Ingress = data.Ingress;
|
||||
|
||||
this.Labels = data.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
if (data.Portainer.Agent && data.Portainer.Agent.NodeName) {
|
||||
this.NodeName = data.Portainer.Agent.NodeName;
|
||||
}
|
||||
}
|
||||
|
||||
this.ConfigFrom = data.ConfigFrom;
|
||||
this.ConfigOnly = data.ConfigOnly;
|
||||
}
|
79
app/docker/models/network.ts
Normal file
79
app/docker/models/network.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { IPAM, Network, NetworkContainer } from 'docker-types/generated/1.41';
|
||||
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
|
||||
export class NetworkViewModel implements IResource {
|
||||
Id: string;
|
||||
|
||||
Name: string;
|
||||
|
||||
Scope: string;
|
||||
|
||||
Driver: string;
|
||||
|
||||
Attachable: boolean;
|
||||
|
||||
Internal: boolean;
|
||||
|
||||
IPAM?: IPAM;
|
||||
|
||||
Containers?: Record<string, NetworkContainer>;
|
||||
|
||||
Options?: Record<string, string>;
|
||||
|
||||
Ingress: boolean;
|
||||
|
||||
Labels: Record<string, string>;
|
||||
|
||||
StackName?: string;
|
||||
|
||||
NodeName?: string;
|
||||
|
||||
ConfigFrom?: { Network: string };
|
||||
|
||||
ConfigOnly?: boolean;
|
||||
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(
|
||||
data: Network & {
|
||||
Portainer?: PortainerMetadata;
|
||||
ConfigFrom?: { Network: string };
|
||||
ConfigOnly?: boolean;
|
||||
}
|
||||
) {
|
||||
this.Id = data.Id || '';
|
||||
this.Name = data.Name || '';
|
||||
this.Scope = data.Scope || '';
|
||||
this.Driver = data.Driver || '';
|
||||
this.Attachable = data.Attachable || false;
|
||||
this.Internal = data.Internal || false;
|
||||
this.IPAM = data.IPAM;
|
||||
this.Containers = data.Containers;
|
||||
this.Options = data.Options;
|
||||
this.Ingress = data.Ingress || false;
|
||||
|
||||
this.Labels = data.Labels || {};
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(
|
||||
data.Portainer.ResourceControl
|
||||
);
|
||||
}
|
||||
if (data.Portainer.Agent && data.Portainer.Agent.NodeName) {
|
||||
this.NodeName = data.Portainer.Agent.NodeName;
|
||||
}
|
||||
}
|
||||
|
||||
this.ConfigFrom = data.ConfigFrom;
|
||||
this.ConfigOnly = data.ConfigOnly;
|
||||
}
|
||||
}
|
|
@ -10,9 +10,7 @@ import {
|
|||
ResourceObject,
|
||||
} from 'docker-types/generated/1.41';
|
||||
|
||||
type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
|
||||
[Property in Key]-?: Type[Property];
|
||||
};
|
||||
import { WithRequiredProperty } from '@/types';
|
||||
|
||||
export class NodeViewModel {
|
||||
Model: Node;
|
||||
|
|
20
app/react/docker/networks/ListView/NestedNetwordsTable.tsx
Normal file
20
app/react/docker/networks/ListView/NestedNetwordsTable.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { NestedDatatable } from '@@/datatables/NestedDatatable';
|
||||
|
||||
import { useIsSwarm } from '../../proxy/queries/useInfo';
|
||||
|
||||
import { useColumns } from './columns';
|
||||
import { DecoratedNetwork } from './types';
|
||||
|
||||
export function NestedNetworksDatatable({
|
||||
dataset,
|
||||
}: {
|
||||
dataset: Array<DecoratedNetwork>;
|
||||
}) {
|
||||
const environmentId = useEnvironmentId();
|
||||
const isSwarm = useIsSwarm(environmentId);
|
||||
|
||||
const columns = useColumns(isSwarm);
|
||||
return <NestedDatatable columns={columns} dataset={dataset} />;
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { Plus, Share2, Trash2 } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
@ -12,7 +11,7 @@ import {
|
|||
RefreshableTableSettings,
|
||||
} from '@@/datatables/types';
|
||||
import { Button } from '@@/buttons';
|
||||
import { TableRow, TableSettingsMenu } from '@@/datatables';
|
||||
import { TableSettingsMenu } from '@@/datatables';
|
||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
|
@ -20,8 +19,9 @@ import { Link } from '@@/Link';
|
|||
|
||||
import { useIsSwarm } from '../../proxy/queries/useInfo';
|
||||
|
||||
import { DockerNetworkViewModel } from './types';
|
||||
import { useColumns } from './columns';
|
||||
import { DecoratedNetwork } from './types';
|
||||
import { NestedNetworksDatatable } from './NestedNetwordsTable';
|
||||
|
||||
const storageKey = 'docker.networks';
|
||||
|
||||
|
@ -35,7 +35,7 @@ const settingsStore = createPersistedStore<TableSettings>(
|
|||
})
|
||||
);
|
||||
|
||||
type DatasetType = Array<DockerNetworkViewModel>;
|
||||
type DatasetType = Array<DecoratedNetwork>;
|
||||
interface Props {
|
||||
dataset: DatasetType;
|
||||
onRemove(selectedItems: DatasetType): void;
|
||||
|
@ -43,14 +43,17 @@ interface Props {
|
|||
}
|
||||
|
||||
export function NetworksDatatable({ dataset, onRemove, onRefresh }: Props) {
|
||||
const environmentId = useEnvironmentId();
|
||||
const settings = useTableState(settingsStore, storageKey);
|
||||
|
||||
const environmentId = useEnvironmentId();
|
||||
const isSwarm = useIsSwarm(environmentId);
|
||||
|
||||
const columns = useColumns(isSwarm);
|
||||
|
||||
useRepeater(settings.autoRefreshRate, onRefresh);
|
||||
|
||||
return (
|
||||
<ExpandableDatatable<DockerNetworkViewModel>
|
||||
<ExpandableDatatable<DecoratedNetwork>
|
||||
settingsManager={settings}
|
||||
title="Networks"
|
||||
titleIcon={Share2}
|
||||
|
@ -61,10 +64,13 @@ export function NetworksDatatable({ dataset, onRemove, onRefresh }: Props) {
|
|||
}
|
||||
renderSubRow={(row) => (
|
||||
<>
|
||||
{row.original.Subs &&
|
||||
row.original.Subs.map((network, idx) => (
|
||||
<TableRow<D> cells={cells} />
|
||||
))}
|
||||
{row.original.Subs && (
|
||||
<tr>
|
||||
<td colSpan={Number.MAX_SAFE_INTEGER}>
|
||||
<NestedNetworksDatatable dataset={row.original.Subs} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
emptyContentLabel="No networks available."
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { DockerNetworkViewModel } from '../types';
|
||||
import { DecoratedNetwork } from '../types';
|
||||
|
||||
export const columnHelper = createColumnHelper<DockerNetworkViewModel>();
|
||||
export const columnHelper = createColumnHelper<DecoratedNetwork>();
|
||||
|
|
|
@ -6,7 +6,7 @@ import { createOwnershipColumn } from '@/react/docker/components/datatable/creat
|
|||
import { buildExpandColumn } from '@@/datatables/expand-column';
|
||||
import { buildNameColumn } from '@@/datatables/buildNameColumn';
|
||||
|
||||
import { DockerNetworkViewModel } from '../types';
|
||||
import { DecoratedNetwork } from '../types';
|
||||
|
||||
import { columnHelper } from './helper';
|
||||
|
||||
|
@ -14,8 +14,8 @@ export function useColumns(isHostColumnVisible?: boolean) {
|
|||
return useMemo(
|
||||
() =>
|
||||
_.compact([
|
||||
buildExpandColumn<DockerNetworkViewModel>(),
|
||||
buildNameColumn<DockerNetworkViewModel>('Name', '.network'),
|
||||
buildExpandColumn<DecoratedNetwork>(),
|
||||
buildNameColumn<DecoratedNetwork>('Name', '.network'),
|
||||
columnHelper.accessor((item) => item.StackName || '-', {
|
||||
header: 'Stack',
|
||||
}),
|
||||
|
@ -29,25 +29,25 @@ export function useColumns(isHostColumnVisible?: boolean) {
|
|||
header: 'IPAM Driver',
|
||||
}),
|
||||
columnHelper.accessor(
|
||||
(item) => item.IPAM?.IPV4Configs[0]?.Subnet ?? '-',
|
||||
(item) => item.IPAM?.IPV4Configs?.[0]?.Subnet ?? '-',
|
||||
{
|
||||
header: 'IPV4 IPAM Subnet',
|
||||
}
|
||||
),
|
||||
columnHelper.accessor(
|
||||
(item) => item.IPAM?.IPV4Configs[0]?.Gateway ?? '-',
|
||||
(item) => item.IPAM?.IPV4Configs?.[0]?.Gateway ?? '-',
|
||||
{
|
||||
header: 'IPV4 IPAM Gateway',
|
||||
}
|
||||
),
|
||||
columnHelper.accessor(
|
||||
(item) => item.IPAM?.IPV6Configs[0]?.Subnet ?? '-',
|
||||
(item) => item.IPAM?.IPV6Configs?.[0]?.Subnet ?? '-',
|
||||
{
|
||||
header: 'IPV6 IPAM Subnet',
|
||||
}
|
||||
),
|
||||
columnHelper.accessor(
|
||||
(item) => item.IPAM?.IPV6Configs[0]?.Gateway ?? '-',
|
||||
(item) => item.IPAM?.IPV6Configs?.[0]?.Gateway ?? '-',
|
||||
{
|
||||
header: 'IPV6 IPAM Gateway',
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export function useColumns(isHostColumnVisible?: boolean) {
|
|||
columnHelper.accessor('NodeName', {
|
||||
header: 'Node',
|
||||
}),
|
||||
createOwnershipColumn<DockerNetworkViewModel>(),
|
||||
createOwnershipColumn<DecoratedNetwork>(),
|
||||
]),
|
||||
[isHostColumnVisible]
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { IPAMConfig } from 'docker-types/generated/1.41';
|
||||
|
||||
import { DockerNetwork } from '../types';
|
||||
import { NetworkViewModel } from '@/docker/models/network';
|
||||
|
||||
export type DockerNetworkViewModel = DockerNetwork & {
|
||||
StackName?: string;
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
NodeName?: string;
|
||||
Subs?: DockerNetworkViewModel[];
|
||||
Highlighted: boolean;
|
||||
export type DecoratedNetwork = NetworkViewModel & {
|
||||
Subs?: DecoratedNetwork[];
|
||||
IPAM: NetworkViewModel['IPAM'] & {
|
||||
IPV4Configs?: Array<IPAMConfig>;
|
||||
IPV6Configs?: Array<IPAMConfig>;
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue