Files
portainer/app/react/docker/containers/queries/useContainerImageStatus.ts
T
claude code agent f69eb3f9eb feat(automation): CE container image update detection endpoint + badge (#9, epic #3 M2)
Add native CE detection of "a newer image is available" for running
containers, surfaced as a read-only HTTP endpoint and a containers-list
badge/column. No applying of updates (M3/M4), no auto-heal (M1).

Backend:
- New CE handler GET /docker/{id}/containers/{containerId}/image_status
  backed by the existing zlib/CE digest engine
  (images.NewClientWithRegistry + ContainerImageStatus). Honors nodeName,
  authz, and routes registry calls through the credential store / SSRF
  AllowList. Engine failures degrade to a 200 {Status:"error"} so the UI
  stays graceful. Response shape: {Status, Message?}.

Frontend (CE-only, no isBE gating; the EE ImageStatus component is left
untouched):
- useContainerImageStatus TanStack Query hook (5min staleTime, no
  refetch-on-focus; backend caches 24h) calling the non-proxied endpoint.
- UpdateStatusBadge component (own assets, neutral on skipped/error).
- "Update available" column in the containers datatable; one cached,
  non-blocking query per visible row.

Tests: Go response-shape unit test; vitest for the badge (all statuses)
and the hook (url + nodeName query param via msw).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:59:54 +03:00

69 lines
2.0 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildDockerUrl } from '../../queries/utils/buildDockerUrl';
import { ContainerId } from '../types';
import { queryKeys } from './query-keys';
/**
* Status of a running container image as reported by the CE detection engine.
*
* - `outdated`: a newer image is available in the registry.
* - `updated`: the local image matches the remote registry digest.
* - `skipped`: detection is not applicable (digest-pinned or local-only image).
* - `processing` / `preparing`: detection is in progress (mostly relevant for services).
* - `error`: detection failed (registry unreachable, auth failure, ...).
*/
export type ContainerImageStatusValue =
| 'outdated'
| 'updated'
| 'skipped'
| 'processing'
| 'preparing'
| 'error';
export interface ContainerImageStatus {
Status: ContainerImageStatusValue;
Message?: string;
}
// The backend caches detection results for 24h, so a generous client-side staleTime
// is enough and avoids hammering the endpoint when many rows are visible at once.
const STALE_TIME = 5 * 60 * 1000; // 5 minutes
async function getContainerImageStatus(
environmentId: EnvironmentId,
containerId: ContainerId,
nodeName?: string
) {
try {
const { data } = await axios.get<ContainerImageStatus>(
buildDockerUrl(environmentId, 'containers', containerId, 'image_status'),
{ params: nodeName ? { nodeName } : undefined }
);
return data;
} catch (err) {
throw parseAxiosError(err as Error, 'Unable to retrieve image status');
}
}
export function useContainerImageStatus(
environmentId: EnvironmentId,
containerId: ContainerId,
nodeName?: string,
enabled = true
) {
return useQuery(
queryKeys.imageStatus(environmentId, containerId),
() => getContainerImageStatus(environmentId, containerId, nodeName),
{
enabled,
staleTime: STALE_TIME,
refetchOnWindowFocus: false,
}
);
}