mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-27 07:14:08 +09:00
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:
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();
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user