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>
This commit is contained in:
+55
-2
@@ -1,5 +1,13 @@
|
||||
import { useRouter } from '@uirouter/react';
|
||||
import { Pause, Play, RefreshCw, Slash, Square, Trash2 } from 'lucide-react';
|
||||
import {
|
||||
Download,
|
||||
Pause,
|
||||
Play,
|
||||
RefreshCw,
|
||||
Slash,
|
||||
Square,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
|
||||
@@ -20,8 +28,13 @@ import {
|
||||
stopContainer,
|
||||
} from '@/react/docker/containers/containers.service';
|
||||
import type { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { useStacks } from '@/react/common/stacks/queries/useStacks';
|
||||
import {
|
||||
ContainerUpdateContext,
|
||||
useBulkUpdateContainerImages,
|
||||
} from '@/react/docker/containers/update';
|
||||
|
||||
import { ButtonGroup, Button, AddButton } from '@@/buttons';
|
||||
import { ButtonGroup, Button, LoadingButton, AddButton } from '@@/buttons';
|
||||
|
||||
type ContainerServiceAction = (
|
||||
endpointId: EnvironmentId,
|
||||
@@ -71,6 +84,8 @@ export function ContainersDatatableActions({
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const stacksQuery = useStacks();
|
||||
const bulkUpdateMutation = useBulkUpdateContainerImages();
|
||||
|
||||
if (!authorized) {
|
||||
return null;
|
||||
@@ -162,6 +177,20 @@ export function ContainersDatatableActions({
|
||||
Remove
|
||||
</Button>
|
||||
</Authorized>
|
||||
|
||||
<Authorized authorizations="DockerContainerCreate">
|
||||
<LoadingButton
|
||||
color="light"
|
||||
data-cy="update-selected-docker-container-button"
|
||||
onClick={() => onUpdateClick(selectedItems)}
|
||||
disabled={selectedItemCount === 0 || stacksQuery.isLoading}
|
||||
isLoading={bulkUpdateMutation.isLoading}
|
||||
loadingText="Updating..."
|
||||
icon={Download}
|
||||
>
|
||||
Update
|
||||
</LoadingButton>
|
||||
</Authorized>
|
||||
</ButtonGroup>
|
||||
{isAddActionVisible && (
|
||||
<div className="space-left">
|
||||
@@ -241,6 +270,30 @@ export function ContainersDatatableActions({
|
||||
);
|
||||
}
|
||||
|
||||
function onUpdateClick(selectedItems: ContainerListViewModel[]) {
|
||||
// Apply the shared image-update primitive to every outdated container,
|
||||
// skipping up-to-date/unknown ones and redeploying each owning stack once.
|
||||
const contexts: ContainerUpdateContext[] = selectedItems.map(
|
||||
(container) => ({
|
||||
id: container.Id,
|
||||
name: container.Names[0],
|
||||
image: container.Image,
|
||||
labels: container.Labels,
|
||||
environmentId: endpointId,
|
||||
nodeName: container.NodeName,
|
||||
})
|
||||
);
|
||||
|
||||
bulkUpdateMutation.mutate(
|
||||
{ contexts, stacks: stacksQuery.data ?? [] },
|
||||
{
|
||||
onSettled: () => {
|
||||
router.stateService.reload();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function onRemoveClick(selectedItems: ContainerListViewModel[]) {
|
||||
const isOneContainerRunning = selectedItems.some(
|
||||
(container) => container.State === 'running'
|
||||
|
||||
Reference in New Issue
Block a user