mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 18:56:53 +00:00
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:
@@ -53,6 +53,7 @@ interface StackStatusInfo {
|
||||
mainPort?: number;
|
||||
running?: number;
|
||||
total?: number;
|
||||
isSelf?: boolean;
|
||||
}
|
||||
|
||||
export interface RemoteResult {
|
||||
@@ -92,6 +93,7 @@ export function useStackListState() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [stackStatuses, setStackStatuses] = useState<StackStatus>({});
|
||||
const [stackPorts, setStackPorts] = useState<Record<string, number | undefined>>({});
|
||||
const [stackSelfFlags, setStackSelfFlags] = useState<Record<string, boolean>>({});
|
||||
const [stackCounts, setStackCounts] = useState<StackCounts>({});
|
||||
const [labels, setLabels] = useState<StackLabel[]>([]);
|
||||
const [stackLabelMap, setStackLabelMap] = useState<Record<string, StackLabel[]>>({});
|
||||
@@ -206,6 +208,7 @@ export function useStackListState() {
|
||||
if (stale()) return fileList;
|
||||
let bulkStatuses: Record<string, StackRowStatus> = {};
|
||||
const bulkPorts: Record<string, number | undefined> = {};
|
||||
const bulkSelf: Record<string, boolean> = {};
|
||||
const bulkCounts: StackCounts = {};
|
||||
|
||||
const raw: unknown = statusRes.ok ? await statusRes.json() : null;
|
||||
@@ -213,6 +216,7 @@ export function useStackListState() {
|
||||
for (const [key, val] of Object.entries(raw as Record<string, StackStatusInfo>)) {
|
||||
bulkStatuses[key] = val.status;
|
||||
if (val.mainPort) bulkPorts[key] = val.mainPort;
|
||||
if (val.isSelf) bulkSelf[key] = true;
|
||||
if (val.running !== undefined && val.total !== undefined) {
|
||||
bulkCounts[key] = { running: val.running, total: val.total };
|
||||
}
|
||||
@@ -233,6 +237,7 @@ export function useStackListState() {
|
||||
if (keys.length === Object.keys(prev).length && keys.every(k => prev[k] === bulkPorts[k])) return prev;
|
||||
return bulkPorts;
|
||||
});
|
||||
setStackSelfFlags(bulkSelf);
|
||||
setStackCounts(bulkCounts);
|
||||
refreshLabels();
|
||||
return fileList;
|
||||
@@ -403,6 +408,7 @@ export function useStackListState() {
|
||||
searchQuery, setSearchQuery,
|
||||
stackStatuses, setStackStatuses,
|
||||
stackPorts, setStackPorts,
|
||||
stackSelfFlags,
|
||||
stackCounts,
|
||||
labels,
|
||||
stackLabelMap,
|
||||
|
||||
Reference in New Issue
Block a user