mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 08:58:05 +00:00
d369b03a38
* feat: detect stalled stack updates and add in-app recovery actions Add a backend idle-output backstop that stops a deploy/update compose step that has gone silent (SENCHO_COMPOSE_STALL_TIMEOUT_MS, default 10m), so a hung image pull surfaces a fast failure instead of spinning indefinitely. Surface failed, timed-out, and stalled operations with recovery actions on the stack page: a desktop chip plus popover menu and an inline mobile card offering retry, restart, roll back (when a backup exists), refresh state, and copy diagnostics, all gated by deploy permission. The streaming deploy/update progress modal is now on by default and warns when output goes quiet. Container state is refreshed after a failed or stalled operation, and the UI never sits in an indefinite spinner. * fix: harden rollback against policy-blocked file mutation and refine recovery Address review findings on the stalled-update recovery work: - The rollback route restored backup files before running the policy gate, so a policy-blocked rollback could leave the on-disk config rolled back while the deployed containers were unchanged. Snapshot the current files first and revert them when the gate blocks; if that revert itself fails, escalate it on the persistent alert feed since the 409 is already sent. - Refresh container state after a successful manual rollback (rollback redeploys), without mis-recording a refetch failure as a rollback failure. - Suppress the stalled-output warning once live progress is unavailable. * test: mock snapshotStackFiles in the atomic-deploy rollback route tests The rollback route now snapshots stack files before restoring a backup, so its FileSystemService mock needs snapshotStackFiles. Without it the mocked call threw and the route returned 500, failing the success-path rollback assertions.
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import { SENCHO_SETTINGS_CHANGED } from '@/lib/events';
|
|
|
|
export const DEPLOY_FEEDBACK_KEY = 'sencho.deploy-feedback.enabled';
|
|
|
|
// Default on (opt-out): the live deploy/update output panel shows unless the
|
|
// user has explicitly turned it off, so update progress and the stalled-output
|
|
// warning are visible without a setting hunt. Only a stored 'false' disables it.
|
|
function readStored(): boolean {
|
|
if (typeof window === 'undefined') return true;
|
|
try {
|
|
return window.localStorage.getItem(DEPLOY_FEEDBACK_KEY) !== 'false';
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export function useDeployFeedbackEnabled(): [boolean, (next: boolean) => void] {
|
|
const [enabled, setEnabledState] = useState<boolean>(readStored);
|
|
|
|
useEffect(() => {
|
|
function onSettingsChanged() {
|
|
setEnabledState(readStored());
|
|
}
|
|
window.addEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
|
|
return () => window.removeEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
function onStorage(event: StorageEvent) {
|
|
if (event.key !== DEPLOY_FEEDBACK_KEY) return;
|
|
// Opt-out: anything other than an explicit 'false' (including a
|
|
// cleared key) means enabled.
|
|
setEnabledState(event.newValue !== 'false');
|
|
}
|
|
window.addEventListener('storage', onStorage);
|
|
return () => window.removeEventListener('storage', onStorage);
|
|
}, []);
|
|
|
|
const setEnabled = useCallback((next: boolean) => {
|
|
try {
|
|
window.localStorage.setItem(DEPLOY_FEEDBACK_KEY, next ? 'true' : 'false');
|
|
} catch {
|
|
// ignore; localStorage may be unavailable (private mode, quota)
|
|
}
|
|
setEnabledState(next);
|
|
window.dispatchEvent(new CustomEvent(SENCHO_SETTINGS_CHANGED));
|
|
}, []);
|
|
|
|
return [enabled, setEnabled];
|
|
}
|