mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-28 04:38:59 +00:00
refactor(frontend): extract useImageUpdates hook from EditorLayout (#831)
EditorLayout owned the stack-image-update state plus a 5-minute polling interval as part of a 30-line useEffect that already juggled six other concerns (selected file, active view, stacks refresh, auto-update settings, git-source pending, …). The image- update slice has clean boundaries: it depends on activeNode.id, mutates one state object, and is otherwise unrelated to the rest of the effect. Move it into a dedicated hook at frontend/src/hooks/useImageUpdates.ts. The hook owns the stackUpdates state, runs an initial fetch on activeNode.id change, schedules the 5-minute poll, and exposes a refresh() callback for the four manual-trigger sites (deploy success, image-update action, manual registry-refresh poll). The hook destructure aliases refresh to fetchImageUpdates so existing call sites in EditorLayout don't need to be renamed. This is the first slice of audit finding 1.6 (EditorLayout 3129-line refactor); the next slice is useFleetNotifications.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useMemo, useCallback, Suspense } from 'rea
|
||||
|
||||
type Theme = 'light' | 'dark' | 'auto';
|
||||
import { Editor } from '@/lib/monacoLoader';
|
||||
import { useImageUpdates } from '@/hooks/useImageUpdates';
|
||||
import TerminalComponent from './Terminal';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import HomeDashboard from './HomeDashboard';
|
||||
@@ -354,7 +355,7 @@ export default function EditorLayout() {
|
||||
|
||||
|
||||
// Image update checker state
|
||||
const [stackUpdates, setStackUpdates] = useState<Record<string, boolean>>({});
|
||||
const { stackUpdates, refresh: fetchImageUpdates } = useImageUpdates(activeNode?.id);
|
||||
const [autoUpdateSettings, setAutoUpdateSettings] = useState<Record<string, boolean>>({});
|
||||
const isAdmiral = license?.variant === 'admiral';
|
||||
|
||||
@@ -824,13 +825,10 @@ export default function EditorLayout() {
|
||||
}
|
||||
|
||||
refreshStacks();
|
||||
fetchImageUpdates();
|
||||
// Image-update fetching + 5-minute poll are owned by useImageUpdates,
|
||||
// which mirrors this effect's activeNode.id dependency.
|
||||
fetchAutoUpdateSettings();
|
||||
refreshGitSourcePending();
|
||||
|
||||
// Poll for image update results every 5 minutes so background checks are picked up
|
||||
const imageUpdateInterval = setInterval(fetchImageUpdates, 5 * 60 * 1000);
|
||||
return () => clearInterval(imageUpdateInterval);
|
||||
}, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const fetchNotifications = async () => {
|
||||
@@ -878,18 +876,6 @@ export default function EditorLayout() {
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const fetchImageUpdates = async () => {
|
||||
try {
|
||||
const res = await apiFetch('/image-updates');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setStackUpdates(data);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
console.error('[ImageUpdates] fetch failed:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAutoUpdateSettings = async () => {
|
||||
try {
|
||||
const res = await apiFetch('/stacks/auto-update-settings');
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
|
||||
const IMAGE_UPDATE_POLL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Owns the stack-image-update state and its 5-minute background poll.
|
||||
* Re-fetches whenever `activeNodeId` changes; consumers can also call
|
||||
* `refresh()` to force a refetch (e.g. after a deploy or a manual
|
||||
* registry-check trigger).
|
||||
*
|
||||
* Extracted from EditorLayout so the polling lifecycle and its state
|
||||
* live next to each other instead of being spread across a 3000-line
|
||||
* component. The dependency on `apiFetch` keeps the call routed
|
||||
* through the active-node header just like before.
|
||||
*/
|
||||
export function useImageUpdates(activeNodeId: number | undefined) {
|
||||
const [stackUpdates, setStackUpdates] = useState<Record<string, boolean>>({});
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
const res = await apiFetch('/image-updates');
|
||||
if (res.ok) {
|
||||
const data = await res.json() as Record<string, boolean>;
|
||||
setStackUpdates(data);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
console.error('[ImageUpdates] fetch failed:', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Pin the interval to the latest closure without retriggering it on
|
||||
// every render the way putting `refresh` into the deps array would.
|
||||
const refreshRef = useRef(refresh);
|
||||
refreshRef.current = refresh;
|
||||
|
||||
useEffect(() => {
|
||||
void refreshRef.current();
|
||||
const id = setInterval(() => { void refreshRef.current(); }, IMAGE_UPDATE_POLL_MS);
|
||||
return () => clearInterval(id);
|
||||
}, [activeNodeId]);
|
||||
|
||||
return { stackUpdates, refresh };
|
||||
}
|
||||
Reference in New Issue
Block a user