mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 06:23:18 +00:00
refactor(sidebar): rebuild footer as priority-driven Ops Pulse strip (#1178)
* refactor(sidebar): rebuild footer as priority-driven Ops Pulse strip Replace the simple notification ticker with a derived activity summary that picks one of six states (active-op, failure, automation, recent-event, quiet-live, disconnected) and routes per-state clicks to logs, schedules, or activity. The hook owns the cascade; the component is pure presentation; EditorLayout owns wiring. Failure detection covers unread errors in the last 24h; recent-event is limited to non-error stack notifications in the last hour; automation reads the next /scheduled-tasks?action=update run and a debounced state-invalidate listener; the deploy-panel composite key is used for elapsed-time tracking so close-then-immediately-reopen counts as a new session. * refactor(sidebar): apply Ops Pulse audit fixes - countEnabledAutoUpdates now defaults missing autoUpdateSettings entries to enabled, matching the backend's getStackAutoUpdateSettingsForNode contract. Previously the automation state could not render even with the documented per-row default-true. - findFailure now requires a stack_name so the sidebar does not select a system-level error whose click would no-op through navigateToNotification. System errors continue to surface via the top-bar NotificationPanel. - DeployPanelState gains a monotonic sessionId sourced from the existing internal counter, and the new usePanelSessionStartedAt hook keys the elapsed-time tracker off it so a same-stack rerun always resets even when isOpen stays true across succeeded then preparing. - buildConfig splits quiet-live out of the default and adds an exhaustiveness guard so future SidebarActivitySummary variants fail to compile. - New unit tests cover the default-true aggregation, the same-stack session reset, the non-stack failure guard, and the useNextAutoUpdateRun debounce and cleanup paths. Frontend suite: 276 / 276 pass.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import type { ScheduledTask } from '@/types/scheduling';
|
||||
|
||||
const POLL_INTERVAL_MS = 60_000;
|
||||
const INVALIDATE_DEBOUNCE_MS = 250;
|
||||
|
||||
async function fetchNextRun(signal: AbortSignal): Promise<number | null> {
|
||||
const res = await apiFetch('/scheduled-tasks?action=update', { localOnly: true, signal });
|
||||
if (!res.ok) return null;
|
||||
const tasks = (await res.json()) as ScheduledTask[];
|
||||
let earliest: number | null = null;
|
||||
for (const t of tasks) {
|
||||
if (!t.enabled) continue;
|
||||
if (t.next_run_at == null) continue;
|
||||
if (earliest == null || t.next_run_at < earliest) earliest = t.next_run_at;
|
||||
}
|
||||
return earliest;
|
||||
}
|
||||
|
||||
export function useNextAutoUpdateRun(): number | null {
|
||||
const [nextRunAt, setNextRunAt] = useState<number | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
let invalidateTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const run = () => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
fetchNextRun(ctrl.signal)
|
||||
.then((v) => { if (active) setNextRunAt(v); })
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return;
|
||||
console.error('[useNextAutoUpdateRun] fetch failed:', err);
|
||||
});
|
||||
};
|
||||
|
||||
run();
|
||||
|
||||
const onInvalidate = (e: Event) => {
|
||||
const detail = (e as CustomEvent<{ action?: string; scope?: string }>).detail;
|
||||
if (detail?.action !== 'auto-update-settings-changed' && detail?.scope !== 'scheduled-tasks') return;
|
||||
if (invalidateTimer) clearTimeout(invalidateTimer);
|
||||
invalidateTimer = setTimeout(() => { invalidateTimer = null; run(); }, INVALIDATE_DEBOUNCE_MS);
|
||||
};
|
||||
window.addEventListener('sencho:state-invalidate', onInvalidate);
|
||||
|
||||
const interval = setInterval(run, POLL_INTERVAL_MS);
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
window.removeEventListener('sencho:state-invalidate', onInvalidate);
|
||||
if (invalidateTimer) clearTimeout(invalidateTimer);
|
||||
clearInterval(interval);
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return nextRunAt;
|
||||
}
|
||||
Reference in New Issue
Block a user