mirror of https://github.com/portainer/portainer
fix(docker/networks): load containers from target node [EE-5446] (#8927)
parent
a35e18a904
commit
8e785e8bb4
|
@ -27,6 +27,8 @@ axios.interceptors.request.use(async (config) => {
|
||||||
return newConfig;
|
return newConfig;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const agentTargetHeader = 'X-PortainerAgent-Target';
|
||||||
|
|
||||||
export function agentInterceptor(config: AxiosRequestConfig) {
|
export function agentInterceptor(config: AxiosRequestConfig) {
|
||||||
if (!config.url || !config.url.includes('/docker/')) {
|
if (!config.url || !config.url.includes('/docker/')) {
|
||||||
return config;
|
return config;
|
||||||
|
@ -35,7 +37,7 @@ export function agentInterceptor(config: AxiosRequestConfig) {
|
||||||
const newConfig = { headers: config.headers || {}, ...config };
|
const newConfig = { headers: config.headers || {}, ...config };
|
||||||
const target = portainerAgentTargetHeader();
|
const target = portainerAgentTargetHeader();
|
||||||
if (target) {
|
if (target) {
|
||||||
newConfig.headers['X-PortainerAgent-Target'] = target;
|
newConfig.headers[agentTargetHeader] = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (portainerAgentManagerOperation()) {
|
if (portainerAgentManagerOperation()) {
|
||||||
|
|
|
@ -52,12 +52,9 @@ export function ContainersDatatable({
|
||||||
|
|
||||||
const [search, setSearch] = useSearchBarState(storageKey);
|
const [search, setSearch] = useSearchBarState(storageKey);
|
||||||
|
|
||||||
const containersQuery = useContainers(
|
const containersQuery = useContainers(environment.Id, {
|
||||||
environment.Id,
|
autoRefreshRate: settings.autoRefreshRate * 1000,
|
||||||
true,
|
});
|
||||||
undefined,
|
|
||||||
settings.autoRefreshRate * 1000
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowProvider context={{ environment }}>
|
<RowProvider context={{ environment }}>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
import axios, {
|
||||||
|
agentTargetHeader,
|
||||||
|
parseAxiosError,
|
||||||
|
} from '@/portainer/services/axios';
|
||||||
|
import { withGlobalError } from '@/react-tools/react-query';
|
||||||
|
|
||||||
import { urlBuilder } from '../containers.service';
|
import { urlBuilder } from '../containers.service';
|
||||||
import { DockerContainerResponse } from '../types/response';
|
import { DockerContainerResponse } from '../types/response';
|
||||||
|
@ -10,20 +14,27 @@ import { parseViewModel } from '../utils';
|
||||||
import { Filters } from './types';
|
import { Filters } from './types';
|
||||||
import { queryKeys } from './query-keys';
|
import { queryKeys } from './query-keys';
|
||||||
|
|
||||||
|
interface UseContainers {
|
||||||
|
all?: boolean;
|
||||||
|
filters?: Filters;
|
||||||
|
nodeName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function useContainers(
|
export function useContainers(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
all = true,
|
{
|
||||||
filters?: Filters,
|
autoRefreshRate,
|
||||||
autoRefreshRate?: number
|
|
||||||
|
...params
|
||||||
|
}: UseContainers & {
|
||||||
|
autoRefreshRate?: number;
|
||||||
|
} = {}
|
||||||
) {
|
) {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
queryKeys.filters(environmentId, all, filters),
|
queryKeys.filters(environmentId, params),
|
||||||
() => getContainers(environmentId, all, filters),
|
() => getContainers(environmentId, params),
|
||||||
{
|
{
|
||||||
meta: {
|
...withGlobalError('Unable to retrieve containers'),
|
||||||
title: 'Failure',
|
|
||||||
message: 'Unable to retrieve containers',
|
|
||||||
},
|
|
||||||
refetchInterval() {
|
refetchInterval() {
|
||||||
return autoRefreshRate ?? false;
|
return autoRefreshRate ?? false;
|
||||||
},
|
},
|
||||||
|
@ -33,14 +44,18 @@ export function useContainers(
|
||||||
|
|
||||||
async function getContainers(
|
async function getContainers(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
all = true,
|
{ all = true, filters, nodeName }: UseContainers = {}
|
||||||
filters?: Filters
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get<DockerContainerResponse[]>(
|
const { data } = await axios.get<DockerContainerResponse[]>(
|
||||||
urlBuilder(environmentId, undefined, 'json'),
|
urlBuilder(environmentId, undefined, 'json'),
|
||||||
{
|
{
|
||||||
params: { all, filters: filters && JSON.stringify(filters) },
|
params: { all, filters: filters && JSON.stringify(filters) },
|
||||||
|
headers: nodeName
|
||||||
|
? {
|
||||||
|
[agentTargetHeader]: nodeName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return data.map((c) => parseViewModel(c));
|
return data.map((c) => parseViewModel(c));
|
||||||
|
|
|
@ -8,8 +8,10 @@ export const queryKeys = {
|
||||||
list: (environmentId: EnvironmentId) =>
|
list: (environmentId: EnvironmentId) =>
|
||||||
[dockerQueryKeys.root(environmentId), 'containers'] as const,
|
[dockerQueryKeys.root(environmentId), 'containers'] as const,
|
||||||
|
|
||||||
filters: (environmentId: EnvironmentId, all?: boolean, filters?: Filters) =>
|
filters: (
|
||||||
[...queryKeys.list(environmentId), { all, filters }] as const,
|
environmentId: EnvironmentId,
|
||||||
|
params: { all?: boolean; filters?: Filters; nodeName?: string } = {}
|
||||||
|
) => [...queryKeys.list(environmentId), params] as const,
|
||||||
|
|
||||||
container: (environmentId: EnvironmentId, id: string) =>
|
container: (environmentId: EnvironmentId, id: string) =>
|
||||||
[...queryKeys.list(environmentId), id] as const,
|
[...queryKeys.list(environmentId), id] as const,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useRouter, useCurrentStateAndParams } from '@uirouter/react';
|
import { useRouter, useCurrentStateAndParams } from '@uirouter/react';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
|
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
|
||||||
|
@ -15,7 +13,7 @@ import { PageHeader } from '@@/PageHeader';
|
||||||
|
|
||||||
import { useNetwork, useDeleteNetwork } from '../queries';
|
import { useNetwork, useDeleteNetwork } from '../queries';
|
||||||
import { isSystemNetwork } from '../network.helper';
|
import { isSystemNetwork } from '../network.helper';
|
||||||
import { DockerNetwork, NetworkContainer } from '../types';
|
import { NetworkResponseContainers } from '../types';
|
||||||
|
|
||||||
import { NetworkDetailsTable } from './NetworkDetailsTable';
|
import { NetworkDetailsTable } from './NetworkDetailsTable';
|
||||||
import { NetworkOptionsTable } from './NetworkOptionsTable';
|
import { NetworkOptionsTable } from './NetworkOptionsTable';
|
||||||
|
@ -25,28 +23,18 @@ export function ItemView() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [networkContainers, setNetworkContainers] = useState<
|
|
||||||
NetworkContainer[]
|
|
||||||
>([]);
|
|
||||||
const {
|
const {
|
||||||
params: { id: networkId, nodeName },
|
params: { id: networkId, nodeName },
|
||||||
} = useCurrentStateAndParams();
|
} = useCurrentStateAndParams();
|
||||||
const environmentId = useEnvironmentId();
|
const environmentId = useEnvironmentId();
|
||||||
|
const networkQuery = useNetwork(environmentId, networkId, { nodeName });
|
||||||
const networkQuery = useNetwork(environmentId, networkId);
|
|
||||||
const deleteNetworkMutation = useDeleteNetwork();
|
const deleteNetworkMutation = useDeleteNetwork();
|
||||||
const filters = {
|
const containersQuery = useContainers(environmentId, {
|
||||||
network: [networkId],
|
filters: {
|
||||||
};
|
network: [networkId],
|
||||||
const containersQuery = useContainers(environmentId, true, filters);
|
},
|
||||||
|
nodeName,
|
||||||
useEffect(() => {
|
});
|
||||||
if (networkQuery.data && containersQuery.data) {
|
|
||||||
setNetworkContainers(
|
|
||||||
filterContainersInNetwork(networkQuery.data, containersQuery.data)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [networkQuery.data, containersQuery.data]);
|
|
||||||
|
|
||||||
if (!networkQuery.data) {
|
if (!networkQuery.data) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -54,6 +42,10 @@ export function ItemView() {
|
||||||
|
|
||||||
const network = networkQuery.data;
|
const network = networkQuery.data;
|
||||||
|
|
||||||
|
const networkContainers = filterContainersInNetwork(
|
||||||
|
network.Containers,
|
||||||
|
containersQuery.data
|
||||||
|
);
|
||||||
const resourceControl = network.Portainer?.ResourceControl
|
const resourceControl = network.Portainer?.ResourceControl
|
||||||
? new ResourceControlViewModel(network.Portainer.ResourceControl)
|
? new ResourceControlViewModel(network.Portainer.ResourceControl)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -116,24 +108,20 @@ export function ItemView() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function filterContainersInNetwork(
|
|
||||||
network: DockerNetwork,
|
function filterContainersInNetwork(
|
||||||
containers: DockerContainer[]
|
networkContainers?: NetworkResponseContainers,
|
||||||
) {
|
containers: DockerContainer[] = []
|
||||||
const containersInNetwork = _.compact(
|
) {
|
||||||
containers.map((container) => {
|
if (!networkContainers) {
|
||||||
const containerInNetworkResponse = network.Containers[container.Id];
|
return [];
|
||||||
if (containerInNetworkResponse) {
|
}
|
||||||
const containerInNetwork: NetworkContainer = {
|
|
||||||
...containerInNetworkResponse,
|
return containers
|
||||||
Id: container.Id,
|
.filter((container) => networkContainers[container.Id])
|
||||||
};
|
.map((container) => ({
|
||||||
return containerInNetwork;
|
...networkContainers[container.Id],
|
||||||
}
|
Id: container.Id,
|
||||||
return null;
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
return containersInNetwork;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Authorized } from '@/react/hooks/useUser';
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
import { Icon } from '@/react/components/Icon';
|
import { Icon } from '@/react/components/Icon';
|
||||||
|
|
||||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
import { TableContainer, TableTitle } from '@@/datatables';
|
||||||
import { DetailsTable } from '@@/DetailsTable';
|
import { DetailsTable } from '@@/DetailsTable';
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
|
@ -42,53 +42,51 @@ export function NetworkContainersTable({
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<TableTitle label="Containers in network" icon={Server} />
|
<TableTitle label="Containers in network" icon={Server} />
|
||||||
<Table className="nopadding">
|
<DetailsTable
|
||||||
<DetailsTable
|
headers={tableHeaders}
|
||||||
headers={tableHeaders}
|
dataCy="networkDetails-networkContainers"
|
||||||
dataCy="networkDetails-networkContainers"
|
>
|
||||||
>
|
{networkContainers.map((container) => (
|
||||||
{networkContainers.map((container) => (
|
<tr key={container.Id}>
|
||||||
<tr key={container.Id}>
|
<td>
|
||||||
<td>
|
<Link
|
||||||
<Link
|
to="docker.containers.container"
|
||||||
to="docker.containers.container"
|
params={{
|
||||||
params={{
|
id: container.Id,
|
||||||
id: container.Id,
|
nodeName,
|
||||||
nodeName,
|
}}
|
||||||
|
title={container.Name}
|
||||||
|
>
|
||||||
|
{container.Name}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>{container.IPv4Address || '-'}</td>
|
||||||
|
<td>{container.IPv6Address || '-'}</td>
|
||||||
|
<td>{container.MacAddress || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<Authorized authorizations="DockerNetworkDisconnect">
|
||||||
|
<Button
|
||||||
|
data-cy={`networkDetails-disconnect${container.Name}`}
|
||||||
|
size="xsmall"
|
||||||
|
color="dangerlight"
|
||||||
|
onClick={() => {
|
||||||
|
if (container.Id) {
|
||||||
|
disconnectContainer.mutate({
|
||||||
|
containerId: container.Id,
|
||||||
|
environmentId,
|
||||||
|
networkId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
title={container.Name}
|
|
||||||
>
|
>
|
||||||
{container.Name}
|
<Icon icon={Trash2} class-name="icon-secondary icon-md" />
|
||||||
</Link>
|
Leave Network
|
||||||
</td>
|
</Button>
|
||||||
<td>{container.IPv4Address || '-'}</td>
|
</Authorized>
|
||||||
<td>{container.IPv6Address || '-'}</td>
|
</td>
|
||||||
<td>{container.MacAddress || '-'}</td>
|
</tr>
|
||||||
<td>
|
))}
|
||||||
<Authorized authorizations="DockerNetworkDisconnect">
|
</DetailsTable>
|
||||||
<Button
|
|
||||||
data-cy={`networkDetails-disconnect${container.Name}`}
|
|
||||||
size="xsmall"
|
|
||||||
color="dangerlight"
|
|
||||||
onClick={() => {
|
|
||||||
if (container.Id) {
|
|
||||||
disconnectContainer.mutate({
|
|
||||||
containerId: container.Id,
|
|
||||||
environmentId,
|
|
||||||
networkId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon={Trash2} class-name="icon-secondary icon-md" />
|
|
||||||
Leave Network
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</DetailsTable>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Share2, Trash2 } from 'lucide-react';
|
||||||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
import { TableContainer, TableTitle } from '@@/datatables';
|
||||||
import { DetailsTable } from '@@/DetailsTable';
|
import { DetailsTable } from '@@/DetailsTable';
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { Icon } from '@@/Icon';
|
import { Icon } from '@@/Icon';
|
||||||
|
@ -32,76 +32,74 @@ export function NetworkDetailsTable({
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<TableTitle label="Network details" icon={Share2} />
|
<TableTitle label="Network details" icon={Share2} />
|
||||||
<Table className="nopadding">
|
<DetailsTable dataCy="networkDetails-detailsTable">
|
||||||
<DetailsTable dataCy="networkDetails-detailsTable">
|
{/* networkRowContent */}
|
||||||
{/* networkRowContent */}
|
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
|
||||||
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
|
<DetailsTable.Row label="Id">
|
||||||
<DetailsTable.Row label="Id">
|
{network.Id}
|
||||||
{network.Id}
|
{allowRemoveNetwork && (
|
||||||
{allowRemoveNetwork && (
|
<Authorized authorizations="DockerNetworkDelete">
|
||||||
<Authorized authorizations="DockerNetworkDelete">
|
<Button
|
||||||
<Button
|
data-cy="networkDetails-deleteNetwork"
|
||||||
data-cy="networkDetails-deleteNetwork"
|
size="xsmall"
|
||||||
size="xsmall"
|
color="danger"
|
||||||
color="danger"
|
onClick={() => onRemoveNetworkClicked()}
|
||||||
onClick={() => onRemoveNetworkClicked()}
|
>
|
||||||
>
|
<Icon
|
||||||
<Icon
|
icon={Trash2}
|
||||||
icon={Trash2}
|
className="space-right"
|
||||||
className="space-right"
|
aria-hidden="true"
|
||||||
aria-hidden="true"
|
/>
|
||||||
/>
|
Delete this network
|
||||||
Delete this network
|
</Button>
|
||||||
</Button>
|
</Authorized>
|
||||||
</Authorized>
|
)}
|
||||||
)}
|
</DetailsTable.Row>
|
||||||
</DetailsTable.Row>
|
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
||||||
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
|
||||||
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
|
<DetailsTable.Row label="Attachable">
|
||||||
<DetailsTable.Row label="Attachable">
|
{String(network.Attachable)}
|
||||||
{String(network.Attachable)}
|
</DetailsTable.Row>
|
||||||
</DetailsTable.Row>
|
<DetailsTable.Row label="Internal">
|
||||||
<DetailsTable.Row label="Internal">
|
{String(network.Internal)}
|
||||||
{String(network.Internal)}
|
</DetailsTable.Row>
|
||||||
</DetailsTable.Row>
|
|
||||||
|
|
||||||
{/* IPV4 ConfigRowContent */}
|
{/* IPV4 ConfigRowContent */}
|
||||||
{ipv4Configs.map((config) => (
|
{ipv4Configs.map((config) => (
|
||||||
<Fragment key={config.Subnet}>
|
<Fragment key={config.Subnet}>
|
||||||
<DetailsTable.Row
|
<DetailsTable.Row
|
||||||
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
|
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
|
||||||
>
|
>
|
||||||
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
|
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
|
||||||
</DetailsTable.Row>
|
</DetailsTable.Row>
|
||||||
<DetailsTable.Row
|
<DetailsTable.Row
|
||||||
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
|
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
|
||||||
>
|
>
|
||||||
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
|
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
|
||||||
config.AuxiliaryAddresses
|
config.AuxiliaryAddresses
|
||||||
)}`}
|
)}`}
|
||||||
</DetailsTable.Row>
|
</DetailsTable.Row>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* IPV6 ConfigRowContent */}
|
{/* IPV6 ConfigRowContent */}
|
||||||
{ipv6Configs.map((config) => (
|
{ipv6Configs.map((config) => (
|
||||||
<Fragment key={config.Subnet}>
|
<Fragment key={config.Subnet}>
|
||||||
<DetailsTable.Row
|
<DetailsTable.Row
|
||||||
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
|
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
|
||||||
>
|
>
|
||||||
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
|
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
|
||||||
</DetailsTable.Row>
|
</DetailsTable.Row>
|
||||||
<DetailsTable.Row
|
<DetailsTable.Row
|
||||||
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
|
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
|
||||||
>
|
>
|
||||||
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
|
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
|
||||||
config.AuxiliaryAddresses
|
config.AuxiliaryAddresses
|
||||||
)}`}
|
)}`}
|
||||||
</DetailsTable.Row>
|
</DetailsTable.Row>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</DetailsTable>
|
</DetailsTable>
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Share2 } from 'lucide-react';
|
import { Share2 } from 'lucide-react';
|
||||||
|
|
||||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
import { TableContainer, TableTitle } from '@@/datatables';
|
||||||
import { DetailsTable } from '@@/DetailsTable';
|
import { DetailsTable } from '@@/DetailsTable';
|
||||||
|
|
||||||
import { NetworkOptions } from '../types';
|
import { NetworkOptions } from '../types';
|
||||||
|
@ -19,15 +19,13 @@ export function NetworkOptionsTable({ options }: Props) {
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<TableTitle label="Network options" icon={Share2} />
|
<TableTitle label="Network options" icon={Share2} />
|
||||||
<Table className="nopadding">
|
<DetailsTable dataCy="networkDetails-networkOptionsTable">
|
||||||
<DetailsTable dataCy="networkDetails-networkOptionsTable">
|
{networkEntries.map(([key, value]) => (
|
||||||
{networkEntries.map(([key, value]) => (
|
<DetailsTable.Row key={key} label={key}>
|
||||||
<DetailsTable.Row key={key} label={key}>
|
{value}
|
||||||
{value}
|
</DetailsTable.Row>
|
||||||
</DetailsTable.Row>
|
))}
|
||||||
))}
|
</DetailsTable>
|
||||||
</DetailsTable>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { ContainerId } from '@/react/docker/containers/types';
|
import { ContainerId } from '@/react/docker/containers/types';
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
import axios, {
|
||||||
|
agentTargetHeader,
|
||||||
|
parseAxiosError,
|
||||||
|
} from '@/portainer/services/axios';
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
import { NetworkId, DockerNetwork } from './types';
|
import { NetworkId, DockerNetwork } from './types';
|
||||||
|
@ -8,11 +11,19 @@ type NetworkAction = 'connect' | 'disconnect' | 'create';
|
||||||
|
|
||||||
export async function getNetwork(
|
export async function getNetwork(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
networkId: NetworkId
|
networkId: NetworkId,
|
||||||
|
{ nodeName }: { nodeName?: string } = {}
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { data: network } = await axios.get<DockerNetwork>(
|
const { data: network } = await axios.get<DockerNetwork>(
|
||||||
buildUrl(environmentId, networkId)
|
buildUrl(environmentId, networkId),
|
||||||
|
nodeName
|
||||||
|
? {
|
||||||
|
headers: {
|
||||||
|
[agentTargetHeader]: nodeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
);
|
);
|
||||||
return network;
|
return network;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -14,10 +14,21 @@ import {
|
||||||
} from './network.service';
|
} from './network.service';
|
||||||
import { NetworkId } from './types';
|
import { NetworkId } from './types';
|
||||||
|
|
||||||
export function useNetwork(environmentId: EnvironmentId, networkId: NetworkId) {
|
export function useNetwork(
|
||||||
|
environmentId: EnvironmentId,
|
||||||
|
networkId: NetworkId,
|
||||||
|
{ nodeName }: { nodeName?: string } = {}
|
||||||
|
) {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
['environments', environmentId, 'docker', 'networks', networkId],
|
[
|
||||||
() => getNetwork(environmentId, networkId),
|
'environments',
|
||||||
|
environmentId,
|
||||||
|
'docker',
|
||||||
|
'networks',
|
||||||
|
networkId,
|
||||||
|
{ nodeName },
|
||||||
|
],
|
||||||
|
() => getNetwork(environmentId, networkId, { nodeName }),
|
||||||
{
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
notifyError('Failure', err as Error, 'Unable to get network');
|
notifyError('Failure', err as Error, 'Unable to get network');
|
||||||
|
|
|
@ -49,14 +49,12 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
|
||||||
columns.filter((col) => col.canHide).map((col) => col.id)
|
columns.filter((col) => col.canHide).map((col) => col.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const containersQuery = useContainers(
|
const containersQuery = useContainers(environment.Id, {
|
||||||
environment.Id,
|
filters: {
|
||||||
true,
|
|
||||||
{
|
|
||||||
label: [`com.docker.compose.project=${stackName}`],
|
label: [`com.docker.compose.project=${stackName}`],
|
||||||
},
|
},
|
||||||
settings.autoRefreshRate * 1000
|
autoRefreshRate: settings.autoRefreshRate * 1000,
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowProvider context={{ environment }}>
|
<RowProvider context={{ environment }}>
|
||||||
|
|
Loading…
Reference in New Issue