f7cb0f3241
Add a discoverable per-container "Update now" action, shown only when the
image status is `outdated`, plus a bulk "Update selected" action in the
containers list.
Both manual paths share ONE apply primitive (applyContainerUpdate /
useUpdateContainerImage) that also backs the future M4 auto-update job:
- standalone container -> recreate-with-pull (existing recreate endpoint)
- stack-managed -> stack redeploy-with-pull (existing git/file stack
update mutations), so the container stays in its
stack and is never recreated out-of-band
- externally-managed -> refused; the details button is disabled with an
compose explanatory tooltip and the bulk action skips it
Decision logic lives in the pure, unit-tested resolveContainerUpdatePath /
groupContainersForUpdate helpers. The bulk action filters to outdated
containers and redeploys each owning stack exactly once even when several of
its containers are selected, reporting per-item success/failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { Stack, StackId } from '@/react/common/stacks/types';
|
|
|
|
import { resolveContainerUpdatePath } from './resolveContainerUpdatePath';
|
|
import { ContainerUpdateContext } from './types';
|
|
|
|
export interface GroupedContainerUpdates {
|
|
/** Standalone containers, each recreated individually. */
|
|
standalone: ContainerUpdateContext[];
|
|
/**
|
|
* One entry per owning stack, even if several selected containers belong to
|
|
* the same stack: the stack is redeployed ONCE (mirrors M4's "one redeploy
|
|
* per stack per tick"). `context` is a representative container of the stack.
|
|
*/
|
|
stacks: Array<{ stackId: StackId; context: ContainerUpdateContext }>;
|
|
/** Externally-managed compose containers, skipped (never recreated). */
|
|
external: ContainerUpdateContext[];
|
|
}
|
|
|
|
/**
|
|
* Partition a set of containers into the apply paths, de-duplicating stack
|
|
* containers so each stack is redeployed only once regardless of how many of
|
|
* its containers were selected. Pure and unit-testable.
|
|
*/
|
|
export function groupContainersForUpdate(
|
|
contexts: ContainerUpdateContext[],
|
|
stacks: Stack[]
|
|
): GroupedContainerUpdates {
|
|
const standalone: ContainerUpdateContext[] = [];
|
|
const external: ContainerUpdateContext[] = [];
|
|
const stackMap = new Map<
|
|
StackId,
|
|
{ stackId: StackId; context: ContainerUpdateContext }
|
|
>();
|
|
|
|
contexts.forEach((context) => {
|
|
const path = resolveContainerUpdatePath(context, stacks);
|
|
|
|
if (path.kind === 'standalone') {
|
|
standalone.push(context);
|
|
} else if (path.kind === 'external') {
|
|
external.push(context);
|
|
} else if (path.kind === 'stack' && path.stackId != null) {
|
|
if (!stackMap.has(path.stackId)) {
|
|
stackMap.set(path.stackId, { stackId: path.stackId, context });
|
|
}
|
|
}
|
|
});
|
|
|
|
return { standalone, external, stacks: Array.from(stackMap.values()) };
|
|
}
|