6aecdfbe46
Make the container image-status badge actionable, matching native Portainer: - Clicking "Update available" opens the update confirm dialog and runs the existing update flow (standalone recreate-with-pull / stack redeploy), gated and disabled while in flight to avoid a double submit. The confirm+apply logic is extracted from UpdateNowButton into a shared useApplyContainerImageUpdate hook so the details button and the list badge share one implementation. - Clicking "Up to date" re-queries the registry. Because the server caches image status (statusCache 5m + remoteDigestCache 5s), a plain refetch was a no-op, so the endpoint gains an optional ?force=true that bypasses BOTH caches for a manual re-check while still repopulating them; the default (auto badges + the auto-update daemon) keeps using the caches unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
|
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
|
|
import { withError } from '@/react-tools/react-query';
|
|
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;
|
|
}
|
|
|
|
// Client-side staleTime for image-status badges: long enough to avoid hammering
|
|
// the endpoint when many rows are visible at once, short enough that a freshly
|
|
// pushed upstream image surfaces reasonably soon. Exported as the single source of
|
|
// truth so the bulk-update action reuses the same window instead of redefining it.
|
|
export const STALE_TIME = 5 * 60 * 1000; // 5 minutes
|
|
|
|
export async function getContainerImageStatus(
|
|
environmentId: EnvironmentId,
|
|
containerId: ContainerId,
|
|
nodeName?: string,
|
|
// When true, ask the backend to bypass its short-lived status cache and
|
|
// recompute against the registry (manual re-check). Off by default so the
|
|
// per-row auto-badges keep using the cache.
|
|
force = false
|
|
) {
|
|
try {
|
|
const params: { nodeName?: string; force?: boolean } = {};
|
|
if (nodeName) {
|
|
params.nodeName = nodeName;
|
|
}
|
|
if (force) {
|
|
params.force = true;
|
|
}
|
|
const { data } = await axios.get<ContainerImageStatus>(
|
|
buildDockerUrl(environmentId, 'containers', containerId, 'image_status'),
|
|
{ params: Object.keys(params).length > 0 ? params : 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, nodeName),
|
|
() => getContainerImageStatus(environmentId, containerId, nodeName),
|
|
{
|
|
enabled,
|
|
staleTime: STALE_TIME,
|
|
refetchOnWindowFocus: false,
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Manual "re-check" for a single container: forces a cache-bypassing registry
|
|
* comparison and writes the fresh result into the shared image-status query cache,
|
|
* so the badge flips in place if the status changed. Used by the interactive
|
|
* "Up to date" list badge; the background per-row query keeps using the cache.
|
|
*/
|
|
export function useRecheckContainerImageStatus(
|
|
environmentId: EnvironmentId,
|
|
containerId: ContainerId,
|
|
nodeName?: string
|
|
) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation(
|
|
() => getContainerImageStatus(environmentId, containerId, nodeName, true),
|
|
{
|
|
onSuccess: (data) => {
|
|
queryClient.setQueryData(
|
|
queryKeys.imageStatus(environmentId, containerId, nodeName),
|
|
data
|
|
);
|
|
},
|
|
...withError('Unable to refresh image status'),
|
|
}
|
|
);
|
|
}
|