refactor(ui): move four multi-request pollers onto usePolling

Plan 76 milestone E2, continuing 3ac566cb. Ratchet 31 sites / 22 files -> 27 / 18.

QueueDetail and QueueOperations each fired two requests per tick. Their ticks
now return a single awaited Promise.all, because the in-flight guard only knows
a tick has finished when the promise it was handed settles — a bare `.then()`
inside the callback reports done immediately and lets the next tick start on top
of a request still in flight, which is the exact overlap the hook exists to
prevent.

ServerDetail and LinkPanelForm keep their mount-time load as its own effect;
the poll is `immediate: false` so nothing fetches twice.

Gate: npm run build clean, npm run lint 0 errors / 953 warnings, npm test 72
passed, boundary check green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Juan DenisandClaude Opus 5 committed 2026-08-18 21:46:31 -04:00
1 parent 1503c493f4
commit f9657d38de
5 files changed
+40 -36

No files matched your search

@@ -50,7 +50,6 @@ const LEGACY_POLLERS = new Map(Object.entries({
'components/security/ScannerTab.jsx': 2,
'components/server/OnboardingWizard.jsx': 2,
'components/serverdetail/ServicesTab.jsx': 1,
'components/servers/LinkPanelForm.jsx': 1,
'hooks/useContainerStatus.js': 1,
'hooks/useDeployJobStream.js': 1,
'hooks/useMetrics.js': 1,
@@ -58,9 +57,6 @@ const LEGACY_POLLERS = new Map(Object.entries({
'pages/DeployConsole.jsx': 1,
'pages/ImportWizard.jsx': 1,
'pages/Monitors.jsx': 1,
'pages/QueueDetail.jsx': 1,
'pages/QueueOperations.jsx': 1,
'pages/ServerDetail.jsx': 1,
'pages/Terminal.jsx': 3,
'pages/TestSandbox.jsx': 2,
'plugins/serverkit-gui/components/ServerGui.jsx': 1,
@@ -7,6 +7,7 @@ import StatusBadge from '../StatusBadge';
import { InfoList, InfoItem } from '../InfoList';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { usePolling } from '@/hooks/usePolling';
const POLL_INTERVAL = 5000;
@@ -40,11 +41,8 @@ const LinkPanelForm = ({ onClose }) => {
}
}
useEffect(() => {
loadStatus();
const interval = setInterval(() => loadStatus({ silent: true }), POLL_INTERVAL);
return () => clearInterval(interval);
}, []);
useEffect(() => { loadStatus(); }, []);
usePolling(() => loadStatus({ silent: true }), POLL_INTERVAL, { immediate: false });
async function handleLink(e) {
e.preventDefault();
+15 -11
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import {
ArrowLeft,
@@ -27,6 +27,7 @@ import {
} from '@/components/ds/grid';
import { useTableSort } from '@/hooks/useTableSort';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { usePolling } from '@/hooks/usePolling';
const STATUS_KINDS = {
pending: 'blue',
@@ -136,7 +137,6 @@ const QueueDetail = () => {
if (saved.statusFilter !== undefined) setStatusFilter(saved.statusFilter);
}, []);
const pollRef = useRef(null);
const viewOnly = group?.owner_type === 'system';
@@ -172,18 +172,22 @@ const QueueDetail = () => {
loadMeta();
}, [loadMeta]);
// Reload when the status filter or the queue changes; poll on top of that.
useEffect(() => {
loadMessages(statusFilter);
pollRef.current = setInterval(() => {
loadMessages(statusFilter);
}, [statusFilter, loadMessages]);
usePolling(async () => {
// Awaited together so the in-flight guard covers BOTH requests: the
// guard only knows a tick is done when the promise it was handed
// settles, and a bare .then() would report done before the queue call.
await Promise.all([
loadMessages(statusFilter),
api.getQueue(groupSlug, queueSlug)
.then(r => setQueue(r.queue || null))
.catch(() => {});
}, POLL_INTERVAL);
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
}, [statusFilter, loadMessages, groupSlug, queueSlug]);
.then((r) => setQueue(r.queue || null))
.catch(() => {}),
]);
}, POLL_INTERVAL, { immediate: false });
const stats = queue?.stats || {};
+10 -10
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Layers,
@@ -31,6 +31,7 @@ import {
import { useTableSort } from '@/hooks/useTableSort';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { formatCompact, formatFull } from '../utils/formatNumber';
import { usePolling } from '@/hooks/usePolling';
const STATUS_KINDS = {
pending: 'blue',
@@ -142,7 +143,6 @@ const QueueOperations = () => {
if (saved.searchTerm !== undefined) setSearchTerm(saved.searchTerm);
}, []);
const pollRef = useRef(null);
const navigate = useNavigate();
const loadData = useCallback(async () => {
@@ -181,16 +181,16 @@ const QueueOperations = () => {
loadData();
}, [loadData]);
// Reload when the selected group changes; poll on top of that.
useEffect(() => {
loadQueues(selectedGroup);
pollRef.current = setInterval(() => {
loadData();
loadQueues(selectedGroup);
}, POLL_INTERVAL);
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
}, [selectedGroup, loadData, loadQueues]);
}, [selectedGroup, loadQueues]);
usePolling(
() => Promise.all([loadData(), loadQueues(selectedGroup)]),
POLL_INTERVAL,
{ immediate: false },
);
const totalQueues = useMemo(
() => groups.reduce((acc, g) => acc + (g.stats?.queues || 0), 0),
+12 -6
View File
@@ -27,6 +27,10 @@ import {
import ProxyStackPanel from '../components/proxy/ProxyStackPanel';
import RemoteAccess from '../pages/RemoteAccess';
import EmptyState from '../components/EmptyState';
import { usePolling } from '@/hooks/usePolling';
// Live host metrics cadence while the server is online.
const METRICS_POLL_MS = 10000;
const ServerDetail = () => {
const { id, tab } = useParams();
@@ -118,14 +122,16 @@ const ServerDetail = () => {
}
useEffect(() => {
if (server?.status === 'online') {
loadMetrics();
loadSystemInfo();
const interval = setInterval(loadMetrics, 10000);
return () => clearInterval(interval);
}
if (server?.status !== 'online') return;
loadMetrics();
loadSystemInfo();
}, [server, loadMetrics, loadSystemInfo]);
usePolling(loadMetrics, METRICS_POLL_MS, {
enabled: server?.status === 'online',
immediate: false,
});
async function handleDeleteServer() {
const confirmed = await confirm({ title: 'Remove Server', message: 'Are you sure you want to remove this server? This action cannot be undone.' });
if (!confirmed) return;