feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)

* feat: block self-stack lifecycle ops with UI and preflight guardrails

Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project.

Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions.

Add SelfStackProtectedDialog and self-managed-stack preflight warning.

Closes #1564

* fix: add missing stackSelfFlags mock to useSidebarContextMenu test

The production hook now reads stackListState.stackSelfFlags[file], but the
test mock did not include it, causing 6 tests to fail with TypeError:
Cannot read properties of undefined (reading 'web.yml').

* fix: harden self-stack protection during startup

Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR.

Align status decoration and route guards on Docker label fallback detection.

Block rollback and service-level stop on the protected self stack.

* fix: add self_stack_location to diagnostics-route expected check IDs
This commit is contained in:
Anso
2026-07-06 02:08:16 -04:00
committed by GitHub
parent f30a65ee08
commit 0f9925e04f
30 changed files with 905 additions and 31 deletions
+11
View File
@@ -113,6 +113,7 @@ export default function EditorLayout() {
isScanning,
searchQuery, setSearchQuery,
stackStatuses,
stackSelfFlags,
stackCounts,
stackLabelMap,
filterChip, setFilterChip,
@@ -494,6 +495,7 @@ export default function EditorLayout() {
setGitSourceOpen={setGitSourceOpen}
setCopiedDigest={setCopiedDigest}
requestDeleteStack={stackActions.requestDeleteStack}
isSelfStack={selectedFile ? stackSelfFlags[selectedFile] === true : false}
recoveryResult={selectedFile ? lastActionResult[selectedFile] : undefined}
onRefreshState={async () => {
if (!selectedFile) return;
@@ -796,6 +798,15 @@ export default function EditorLayout() {
stackName={stackName}
gitSourceOpen={gitSourceOpen}
setGitSourceOpen={setGitSourceOpen}
canSelfUpdate={hasCapability('self-update')}
onOpenFleetNodeUpdates={() => {
if (isMobile) {
navigateMobileAware('fleet');
} else {
setFleetUpdatesIntent({ tab: 'nodes' });
handleNavigate('fleet');
}
}}
/>
);