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>
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
import { Download } from 'lucide-react';
|
|
import { useRouter } from '@uirouter/react';
|
|
|
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
import { useContainerImageStatus } from '@/react/docker/containers/queries/useContainerImageStatus';
|
|
import { useApplyContainerImageUpdate } from '@/react/docker/containers/update';
|
|
|
|
import { ButtonGroup, LoadingButton } from '@@/buttons';
|
|
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
|
|
|
|
import { ContainerId } from '../../../types';
|
|
|
|
interface UpdateNowButtonProps {
|
|
environmentId: EnvironmentId;
|
|
containerId: ContainerId;
|
|
nodeName?: string;
|
|
containerImage: string;
|
|
containerName: string;
|
|
labels?: Record<string, string>;
|
|
isPortainer: boolean;
|
|
}
|
|
|
|
/**
|
|
* "Update now" surfaces a discoverable per-container apply action ONLY when the
|
|
* image is `outdated`. It routes through the shared update primitive:
|
|
* standalone -> recreate-with-pull, stack-managed -> stack redeploy-with-pull
|
|
* (container stays in its stack). Externally-managed compose containers are
|
|
* shown disabled with an explanatory tooltip, never recreated out-of-band.
|
|
*
|
|
* A stack redeploy is gated by `PortainerStackUpdate` (as everywhere else in the
|
|
* app): a user with container-create but without stack-update rights sees the
|
|
* button disabled with a tooltip rather than getting a 403 on click.
|
|
*/
|
|
export function UpdateNowButton({
|
|
environmentId,
|
|
containerId,
|
|
nodeName,
|
|
containerImage,
|
|
containerName,
|
|
labels,
|
|
isPortainer,
|
|
}: UpdateNowButtonProps) {
|
|
const router = useRouter();
|
|
const statusQuery = useContainerImageStatus(
|
|
environmentId,
|
|
containerId,
|
|
nodeName
|
|
);
|
|
const { apply, isLoading, isExternal, stackUpdateForbidden, canApply } =
|
|
useApplyContainerImageUpdate({
|
|
environmentId,
|
|
containerId,
|
|
nodeName,
|
|
containerImage,
|
|
containerName,
|
|
labels,
|
|
isPortainer,
|
|
// The details view reloads to reflect the recreated/redeployed container.
|
|
onSuccess: () =>
|
|
router.stateService.go('docker.containers', {}, { reload: true }),
|
|
});
|
|
|
|
// Only meaningful when a newer image is actually available.
|
|
if (statusQuery.data?.Status !== 'outdated') {
|
|
return null;
|
|
}
|
|
|
|
const button = (
|
|
<LoadingButton
|
|
color="primary"
|
|
size="small"
|
|
onClick={apply}
|
|
disabled={!canApply}
|
|
isLoading={isLoading}
|
|
loadingText="Updating..."
|
|
data-cy="update-now-button"
|
|
icon={Download}
|
|
>
|
|
Update now
|
|
</LoadingButton>
|
|
);
|
|
|
|
if (isExternal) {
|
|
return (
|
|
<ButtonGroup>
|
|
<TooltipWithChildren message="This container belongs to a compose project that is managed outside Portainer, so it can't be updated from here.">
|
|
{button}
|
|
</TooltipWithChildren>
|
|
</ButtonGroup>
|
|
);
|
|
}
|
|
|
|
if (stackUpdateForbidden) {
|
|
return (
|
|
<ButtonGroup>
|
|
<TooltipWithChildren message="Updating this container redeploys its stack, which requires stack update permission you don't have.">
|
|
{button}
|
|
</TooltipWithChildren>
|
|
</ButtonGroup>
|
|
);
|
|
}
|
|
|
|
return <ButtonGroup>{button}</ButtonGroup>;
|
|
}
|