f69eb3f9eb
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>
69 lines
2.0 KiB
TypeScript
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,
|
|
}
|
|
);
|
|
}
|