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
+13
View File
@@ -48,6 +48,7 @@ import { useTopNavMode } from '@/hooks/use-top-nav-mode';
import { useTopNavQuickLinks } from '@/hooks/use-top-nav-quick-links';
import { getAppNavItem } from '@/lib/navigation/appNavRegistry';
import { useStackMuteActions } from '@/hooks/useMuteRuleActions';
import { useServiceUpdateStatus } from '@/hooks/useServiceUpdateStatus';
import { toast } from '@/components/ui/toast-store';
import { useIsMobile } from '@/hooks/use-is-mobile';
import { MobileTabBar } from './MobileTabBar';
@@ -112,6 +113,8 @@ export default function EditorLayout() {
backupInfo,
isEditing,
editingCompose, setEditingCompose,
effectiveServices,
serviceUpdateInProgress,
} = editorState;
const stackListState = useStackListState();
@@ -258,6 +261,8 @@ export default function EditorLayout() {
activeNode?.id ?? null,
);
const serviceUpdateStatuses = useServiceUpdateStatus(stackUpdates, selectedFile);
const stackActions = useStackActions({
editorState,
stackListState,
@@ -271,6 +276,7 @@ export default function EditorLayout() {
diffPreviewEnabled,
hasUpdateGuard: hasCapability('update-guard'),
hasGuidedExternalNetworkPreflight: hasCapability('guided-external-network-preflight'),
hasServiceScopedUpdate: hasCapability('service-scoped-update'),
canEditStack: (stackNameOrFilename) => {
const stackName = stackNameOrFilename.replace(/\.(ya?ml)$/, '');
return can('stack:edit', 'stack', stackName);
@@ -639,6 +645,13 @@ export default function EditorLayout() {
openLogViewer={stackActions.openLogViewer}
openBashModal={stackActions.openBashModal}
serviceAction={stackActions.serviceAction}
effectiveServices={effectiveServices}
serviceUpdateStatuses={serviceUpdateStatuses}
serviceUpdateInProgress={serviceUpdateInProgress}
onRequestServiceUpdate={(serviceName, mode) => {
if (!selectedFile) return;
void stackActions.requestServiceUpdate(selectedFile, serviceName, mode);
}}
setActiveTab={setActiveTab}
setLogsMode={setLogsMode}
setEditingCompose={setEditingCompose}