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;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,12 +52,9 @@ export function ContainersDatatable({
 | 
			
		|||
 | 
			
		||||
  const [search, setSearch] = useSearchBarState(storageKey);
 | 
			
		||||
 | 
			
		||||
  const containersQuery = useContainers(
 | 
			
		||||
    environment.Id,
 | 
			
		||||
    true,
 | 
			
		||||
    undefined,
 | 
			
		||||
    settings.autoRefreshRate * 1000
 | 
			
		||||
  );
 | 
			
		||||
  const containersQuery = useContainers(environment.Id, {
 | 
			
		||||
    autoRefreshRate: settings.autoRefreshRate * 1000,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <RowProvider context={{ environment }}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = {
 | 
			
		||||
  const containersQuery = useContainers(environmentId, {
 | 
			
		||||
    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]);
 | 
			
		||||
    },
 | 
			
		||||
    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,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import { Authorized } from '@/react/hooks/useUser';
 | 
			
		|||
import { EnvironmentId } from '@/react/portainer/environments/types';
 | 
			
		||||
import { Icon } from '@/react/components/Icon';
 | 
			
		||||
 | 
			
		||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { DetailsTable } from '@@/DetailsTable';
 | 
			
		||||
import { Button } from '@@/buttons';
 | 
			
		||||
import { Link } from '@@/Link';
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,6 @@ export function NetworkContainersTable({
 | 
			
		|||
  return (
 | 
			
		||||
    <TableContainer>
 | 
			
		||||
      <TableTitle label="Containers in network" icon={Server} />
 | 
			
		||||
      <Table className="nopadding">
 | 
			
		||||
      <DetailsTable
 | 
			
		||||
        headers={tableHeaders}
 | 
			
		||||
        dataCy="networkDetails-networkContainers"
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +87,6 @@ export function NetworkContainersTable({
 | 
			
		|||
          </tr>
 | 
			
		||||
        ))}
 | 
			
		||||
      </DetailsTable>
 | 
			
		||||
      </Table>
 | 
			
		||||
    </TableContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import { Share2, Trash2 } from 'lucide-react';
 | 
			
		|||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
 | 
			
		||||
import { Authorized } from '@/react/hooks/useUser';
 | 
			
		||||
 | 
			
		||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { DetailsTable } from '@@/DetailsTable';
 | 
			
		||||
import { Button } from '@@/buttons';
 | 
			
		||||
import { Icon } from '@@/Icon';
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +32,6 @@ export function NetworkDetailsTable({
 | 
			
		|||
  return (
 | 
			
		||||
    <TableContainer>
 | 
			
		||||
      <TableTitle label="Network details" icon={Share2} />
 | 
			
		||||
      <Table className="nopadding">
 | 
			
		||||
      <DetailsTable dataCy="networkDetails-detailsTable">
 | 
			
		||||
        {/* networkRowContent */}
 | 
			
		||||
        <DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +100,6 @@ export function NetworkDetailsTable({
 | 
			
		|||
          </Fragment>
 | 
			
		||||
        ))}
 | 
			
		||||
      </DetailsTable>
 | 
			
		||||
      </Table>
 | 
			
		||||
    </TableContainer>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { Share2 } from 'lucide-react';
 | 
			
		||||
 | 
			
		||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { TableContainer, TableTitle } from '@@/datatables';
 | 
			
		||||
import { DetailsTable } from '@@/DetailsTable';
 | 
			
		||||
 | 
			
		||||
import { NetworkOptions } from '../types';
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,6 @@ export function NetworkOptionsTable({ options }: Props) {
 | 
			
		|||
  return (
 | 
			
		||||
    <TableContainer>
 | 
			
		||||
      <TableTitle label="Network options" icon={Share2} />
 | 
			
		||||
      <Table className="nopadding">
 | 
			
		||||
      <DetailsTable dataCy="networkDetails-networkOptionsTable">
 | 
			
		||||
        {networkEntries.map(([key, value]) => (
 | 
			
		||||
          <DetailsTable.Row key={key} label={key}>
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +26,6 @@ export function NetworkOptionsTable({ options }: Props) {
 | 
			
		|||
          </DetailsTable.Row>
 | 
			
		||||
        ))}
 | 
			
		||||
      </DetailsTable>
 | 
			
		||||
      </Table>
 | 
			
		||||
    </TableContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,14 +49,12 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
 | 
			
		|||
    columns.filter((col) => col.canHide).map((col) => col.id)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const containersQuery = useContainers(
 | 
			
		||||
    environment.Id,
 | 
			
		||||
    true,
 | 
			
		||||
    {
 | 
			
		||||
  const containersQuery = useContainers(environment.Id, {
 | 
			
		||||
    filters: {
 | 
			
		||||
      label: [`com.docker.compose.project=${stackName}`],
 | 
			
		||||
    },
 | 
			
		||||
    settings.autoRefreshRate * 1000
 | 
			
		||||
  );
 | 
			
		||||
    autoRefreshRate: settings.autoRefreshRate * 1000,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <RowProvider context={{ environment }}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue