fix(docker/networks): load containers from target node [EE-5446] (#8928)

pull/8539/head^2
Chaim Lev-Ari 2023-05-18 12:53:34 +07:00 committed by GitHub
parent 14fa60f6e6
commit 881fa01eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 97 additions and 73 deletions

View File

@ -27,6 +27,8 @@ axios.interceptors.request.use(async (config) => {
return newConfig;
});
export const agentTargetHeader = 'X-PortainerAgent-Target';
export function agentInterceptor(config: AxiosRequestConfig) {
if (!config.url || !config.url.includes('/docker/')) {
return config;
@ -35,7 +37,7 @@ export function agentInterceptor(config: AxiosRequestConfig) {
const newConfig = { headers: config.headers || {}, ...config };
const target = portainerAgentTargetHeader();
if (target) {
newConfig.headers['X-PortainerAgent-Target'] = target;
newConfig.headers[agentTargetHeader] = target;
}
if (portainerAgentManagerOperation()) {

View File

@ -45,12 +45,9 @@ export function ContainersDatatable({
const columns = useColumns(isHostColumnVisible, isGPUsColumnVisible);
const tableState = useTableState(settingsStore, storageKey);
const containersQuery = useContainers(
environment.Id,
true,
undefined,
tableState.autoRefreshRate * 1000
);
const containersQuery = useContainers(environment.Id, {
autoRefreshRate: tableState.autoRefreshRate * 1000,
});
return (
<RowProvider context={{ environment }}>

View File

@ -1,7 +1,11 @@
import { useQuery } from 'react-query';
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 { DockerContainerResponse } from '../types/response';
@ -10,20 +14,27 @@ import { parseViewModel } from '../utils';
import { Filters } from './types';
import { queryKeys } from './query-keys';
interface UseContainers {
all?: boolean;
filters?: Filters;
nodeName?: string;
}
export function useContainers(
environmentId: EnvironmentId,
all = true,
filters?: Filters,
autoRefreshRate?: number
{
autoRefreshRate,
...params
}: UseContainers & {
autoRefreshRate?: number;
} = {}
) {
return useQuery(
queryKeys.filters(environmentId, all, filters),
() => getContainers(environmentId, all, filters),
queryKeys.filters(environmentId, params),
() => getContainers(environmentId, params),
{
meta: {
title: 'Failure',
message: 'Unable to retrieve containers',
},
...withGlobalError('Unable to retrieve containers'),
refetchInterval() {
return autoRefreshRate ?? false;
},
@ -33,14 +44,18 @@ export function useContainers(
async function getContainers(
environmentId: EnvironmentId,
all = true,
filters?: Filters
{ all = true, filters, nodeName }: UseContainers = {}
) {
try {
const { data } = await axios.get<DockerContainerResponse[]>(
urlBuilder(environmentId, undefined, 'json'),
{
params: { all, filters: filters && JSON.stringify(filters) },
headers: nodeName
? {
[agentTargetHeader]: nodeName,
}
: undefined,
}
);
return data.map((c) => parseViewModel(c));

View File

@ -8,8 +8,10 @@ export const queryKeys = {
list: (environmentId: EnvironmentId) =>
[dockerQueryKeys.root(environmentId), 'containers'] as const,
filters: (environmentId: EnvironmentId, all?: boolean, filters?: Filters) =>
[...queryKeys.list(environmentId), { all, filters }] as const,
filters: (
environmentId: EnvironmentId,
params: { all?: boolean; filters?: Filters; nodeName?: string } = {}
) => [...queryKeys.list(environmentId), params] as const,
container: (environmentId: EnvironmentId, id: string) =>
[...queryKeys.list(environmentId), id] as const,

View File

@ -1,7 +1,5 @@
import { useState, useEffect } from 'react';
import { useRouter, useCurrentStateAndParams } from '@uirouter/react';
import { useQueryClient } from 'react-query';
import _ from 'lodash';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
@ -15,7 +13,7 @@ import { PageHeader } from '@@/PageHeader';
import { useNetwork, useDeleteNetwork } from '../queries';
import { isSystemNetwork } from '../network.helper';
import { DockerNetwork, NetworkContainer } from '../types';
import { NetworkResponseContainers } from '../types';
import { NetworkDetailsTable } from './NetworkDetailsTable';
import { NetworkOptionsTable } from './NetworkOptionsTable';
@ -25,28 +23,18 @@ export function ItemView() {
const router = useRouter();
const queryClient = useQueryClient();
const [networkContainers, setNetworkContainers] = useState<
NetworkContainer[]
>([]);
const {
params: { id: networkId, nodeName },
} = useCurrentStateAndParams();
const environmentId = useEnvironmentId();
const networkQuery = useNetwork(environmentId, networkId);
const networkQuery = useNetwork(environmentId, networkId, { nodeName });
const deleteNetworkMutation = useDeleteNetwork();
const filters = {
network: [networkId],
};
const containersQuery = useContainers(environmentId, true, filters);
useEffect(() => {
if (networkQuery.data && containersQuery.data) {
setNetworkContainers(
filterContainersInNetwork(networkQuery.data, containersQuery.data)
);
}
}, [networkQuery.data, containersQuery.data]);
const containersQuery = useContainers(environmentId, {
filters: {
network: [networkId],
},
nodeName,
});
if (!networkQuery.data) {
return null;
@ -54,6 +42,10 @@ export function ItemView() {
const network = networkQuery.data;
const networkContainers = filterContainersInNetwork(
network.Containers,
containersQuery.data
);
const resourceControl = network.Portainer?.ResourceControl
? new ResourceControlViewModel(network.Portainer.ResourceControl)
: undefined;
@ -116,24 +108,20 @@ export function ItemView() {
);
}
}
function filterContainersInNetwork(
network: DockerNetwork,
containers: DockerContainer[]
) {
const containersInNetwork = _.compact(
containers.map((container) => {
const containerInNetworkResponse = network.Containers[container.Id];
if (containerInNetworkResponse) {
const containerInNetwork: NetworkContainer = {
...containerInNetworkResponse,
Id: container.Id,
};
return containerInNetwork;
}
return null;
})
);
return containersInNetwork;
}
}
function filterContainersInNetwork(
networkContainers?: NetworkResponseContainers,
containers: DockerContainer[] = []
) {
if (!networkContainers) {
return [];
}
return containers
.filter((container) => networkContainers[container.Id])
.map((container) => ({
...networkContainers[container.Id],
Id: container.Id,
}));
}

View File

@ -1,5 +1,8 @@
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 { NetworkId, DockerNetwork } from './types';
@ -8,11 +11,19 @@ type NetworkAction = 'connect' | 'disconnect' | 'create';
export async function getNetwork(
environmentId: EnvironmentId,
networkId: NetworkId
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
try {
const { data: network } = await axios.get<DockerNetwork>(
buildUrl(environmentId, networkId)
buildUrl(environmentId, networkId),
nodeName
? {
headers: {
[agentTargetHeader]: nodeName,
},
}
: undefined
);
return network;
} catch (e) {

View File

@ -14,10 +14,21 @@ import {
} from './network.service';
import { NetworkId } from './types';
export function useNetwork(environmentId: EnvironmentId, networkId: NetworkId) {
export function useNetwork(
environmentId: EnvironmentId,
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
return useQuery(
['environments', environmentId, 'docker', 'networks', networkId],
() => getNetwork(environmentId, networkId),
[
'environments',
environmentId,
'docker',
'networks',
networkId,
{ nodeName },
],
() => getNetwork(environmentId, networkId, { nodeName }),
{
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to get network');

View File

@ -42,14 +42,12 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
const isGPUsColumnVisible = useShowGPUsColumn(environment.Id);
const columns = useColumns(false, isGPUsColumnVisible);
const containersQuery = useContainers(
environment.Id,
true,
{
const containersQuery = useContainers(environment.Id, {
filters: {
label: [`com.docker.compose.project=${stackName}`],
},
tableState.autoRefreshRate * 1000
);
autoRefreshRate: tableState.autoRefreshRate * 1000,
});
return (
<RowProvider context={{ environment }}>