feat: add service-scoped Compose update and restore (#1648)

* feat: add service-scoped Compose update and restore

Allow updating or rebuilding one declared Compose service on multi-service
stacks without recreating siblings, with recovery snapshots, health-gate
observation, and prune holds for rollback images. Full-stack update paths
and single-service UX stay unchanged.

* fix: sanitize service-scoped update log messages for CodeQL

* fix: address service-scoped update audit findings B-01 through B-07

* fix: complete service-scoped update audit metadata and surfaces

* test: wrap Updates readiness tests for deploy-feedback context

* fix: keep service recovery reachable without Deploy Progress

Make failed service-gate recovery discoverable when Deploy Progress is
disabled or dismissed, suppress stale image-scan notification side
effects, normalize ComposeService line endings, and add focused
regression coverage.

* fix: resurface ContainersHealth density and expand on multi-service stacks

Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks.
This commit is contained in:
Anso
2026-07-19 02:42:29 -04:00
committed by GitHub
parent 31d4e4669b
commit 63213c0960
89 changed files with 7608 additions and 331 deletions
@@ -7,6 +7,7 @@ import type { StackStatusEntry, MetricPoint, StackCpuSeries } from './types';
import type { StackUpdateInfo } from '@/types/imageUpdates';
import { aggregateCurrentUsage } from './aggregateCurrentUsage';
import { classifyRow, type RowState } from './classifyRow';
import { updateAvailableBadge, updateAvailableLabel } from '@/lib/updateAvailableLabel';
interface StackHealthTableProps {
stackStatuses: Record<string, StackStatusEntry>;
@@ -125,6 +126,9 @@ export function StackHealthTable({
source: entry.source ?? 'local',
mainPort: entry.mainPort ?? null,
hasUpdate: stackUpdates[file]?.hasUpdate ?? false,
outdatedServices: (stackUpdates[file]?.services ?? [])
.filter((s) => s.hasUpdate)
.map((s) => s.service),
};
});
}, [stackStatuses, stackAggregates, stackCpuSeries, stackUpdates]);
@@ -245,8 +249,11 @@ export function StackHealthTable({
<span className="flex items-center gap-1.5 min-w-0">
<span className="min-w-0 truncate font-mono text-sm text-stat-value">{row.name}</span>
{row.hasUpdate && (
<span className="shrink-0 rounded-full bg-brand/15 px-2 py-0.5 font-mono text-[10px] leading-none text-brand tracking-wide">
Update available
<span
className="shrink-0 rounded-full bg-brand/15 px-2 py-0.5 font-mono text-[10px] leading-none text-brand tracking-wide"
title={updateAvailableLabel(row.outdatedServices)}
>
{updateAvailableBadge(row.outdatedServices)}
</span>
)}
</span>