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>
58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import { renderHook } from '@testing-library/react-hooks';
|
|
import { http, HttpResponse } from 'msw';
|
|
|
|
import { server } from '@/setup-tests/server';
|
|
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
|
|
|
|
import {
|
|
ContainerImageStatus,
|
|
useContainerImageStatus,
|
|
} from './useContainerImageStatus';
|
|
|
|
const environmentId = 3;
|
|
const containerId = 'abc123';
|
|
|
|
function renderImageStatusHook(nodeName?: string) {
|
|
return renderHook(
|
|
() => useContainerImageStatus(environmentId, containerId, nodeName),
|
|
{ wrapper: withTestQueryProvider(({ children }) => <>{children}</>) }
|
|
);
|
|
}
|
|
|
|
describe('useContainerImageStatus', () => {
|
|
it('calls the non-proxied image_status endpoint and returns the status', async () => {
|
|
const payload: ContainerImageStatus = { Status: 'outdated' };
|
|
|
|
server.use(
|
|
http.get(
|
|
`/api/docker/${environmentId}/containers/${containerId}/image_status`,
|
|
() => HttpResponse.json(payload)
|
|
)
|
|
);
|
|
|
|
const { result, waitFor } = renderImageStatusHook();
|
|
|
|
await waitFor(() => result.current.isSuccess);
|
|
expect(result.current.data).toEqual(payload);
|
|
});
|
|
|
|
it('forwards nodeName as a query parameter', async () => {
|
|
let receivedNodeName: string | null = null;
|
|
|
|
server.use(
|
|
http.get(
|
|
`/api/docker/${environmentId}/containers/${containerId}/image_status`,
|
|
({ request }) => {
|
|
receivedNodeName = new URL(request.url).searchParams.get('nodeName');
|
|
return HttpResponse.json<ContainerImageStatus>({ Status: 'updated' });
|
|
}
|
|
)
|
|
);
|
|
|
|
const { result, waitFor } = renderImageStatusHook('node-2');
|
|
|
|
await waitFor(() => result.current.isSuccess);
|
|
expect(receivedNodeName).toBe('node-2');
|
|
});
|
|
});
|