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>
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
import { COMPOSE_STACK_NAME_LABEL } from '@/react/constants';
|
|
import { Stack } from '@/react/common/stacks/types';
|
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
|
|
import { ContainerUpdatePath } from './types';
|
|
|
|
/**
|
|
* Decide how a container's image update must be applied, given the list of
|
|
* Portainer-managed stacks. Pure and side-effect free so it can be unit-tested
|
|
* and reused by the details button, the bulk action and the M4 auto-update job.
|
|
*
|
|
* - No compose project label -> `standalone` (recreate-with-pull).
|
|
* - Compose project that matches a Portainer `Stack` (same name + endpoint) ->
|
|
* `stack` (redeploy-with-pull, keeps the container in its stack).
|
|
* - Compose project with no matching Portainer stack -> `external`: managed
|
|
* outside Portainer, so we must not recreate it (would detach it / drift).
|
|
*/
|
|
export function resolveContainerUpdatePath(
|
|
context: { labels?: Record<string, string>; environmentId: EnvironmentId },
|
|
stacks: Stack[]
|
|
): ContainerUpdatePath {
|
|
const projectName = context.labels?.[COMPOSE_STACK_NAME_LABEL];
|
|
|
|
if (!projectName) {
|
|
return { kind: 'standalone' };
|
|
}
|
|
|
|
const stack = stacks.find(
|
|
(s) => s.Name === projectName && s.EndpointId === context.environmentId
|
|
);
|
|
|
|
if (!stack) {
|
|
return { kind: 'external' };
|
|
}
|
|
|
|
return {
|
|
kind: 'stack',
|
|
stackId: stack.Id,
|
|
isGitStack: !!stack.GitConfig,
|
|
};
|
|
}
|