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
+16
View File
@@ -0,0 +1,16 @@
/** Tooltip / badge copy naming outdated services when the breakdown is known. */
export function updateAvailableLabel(outdatedServices?: string[]): string {
const names = (outdatedServices ?? []).filter(Boolean);
if (names.length === 0) return 'Update available';
if (names.length === 1) return `Update available: ${names[0]}`;
if (names.length <= 3) return `Update available: ${names.join(', ')}`;
return `Update available: ${names.length} services`;
}
/** Compact Stack Health badge; full detail stays in the title attribute. */
export function updateAvailableBadge(outdatedServices?: string[]): string {
const names = (outdatedServices ?? []).filter(Boolean);
if (names.length === 0) return 'Update available';
if (names.length === 1) return `Update: ${names[0]}`;
return `${names.length} updates`;
}