mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-30 00:27:11 +09:00
refactor(ui): move the log-tail pollers onto usePolling
Plan 76 milestone E2, continuing f9657d38. Ratchet 27 sites / 18 files -> 22 / 15.
Terminal's three tab components (file logs, journal, processes), ServicesTab's
unit log tail, and the GUI extension's synthetic desktop. All were the
auto-refresh tail shape: poll only while the toggle is on, never fetch on mount
from the interval effect.
These are the sites where a hidden tab mattered most — a log tail at 3s left
open in a background tab is the single most wasteful poller in the panel, and
every one of them also re-ran on `lineCount`/`priority`/`sort` changes via a
dependency array with `// eslint-disable-line` on it. That silent restart is now
explicit: the tail's own load effects still handle those, the poll handles the
repeat.
Four `intervalRef`s and one `logIntervalRef` went with them.
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
f9657d38de
commit
89c3896ec4
4 files changed
+44
-35
No files matched your search
@@ -49,7 +49,6 @@ const LEGACY_POLLERS = new Map(Object.entries({
|
||||
'components/monitoring/DoctorPanel.jsx': 1,
|
||||
'components/security/ScannerTab.jsx': 2,
|
||||
'components/server/OnboardingWizard.jsx': 2,
|
||||
'components/serverdetail/ServicesTab.jsx': 1,
|
||||
'hooks/useContainerStatus.js': 1,
|
||||
'hooks/useDeployJobStream.js': 1,
|
||||
'hooks/useMetrics.js': 1,
|
||||
@@ -57,10 +56,8 @@ const LEGACY_POLLERS = new Map(Object.entries({
|
||||
'pages/DeployConsole.jsx': 1,
|
||||
'pages/ImportWizard.jsx': 1,
|
||||
'pages/Monitors.jsx': 1,
|
||||
'pages/Terminal.jsx': 3,
|
||||
'pages/TestSandbox.jsx': 2,
|
||||
'plugins/serverkit-gui/components/ServerGui.jsx': 1,
|
||||
'plugins/serverkit-gui/components/SyntheticDesktop.jsx': 1,
|
||||
}));
|
||||
|
||||
// The polling door itself, plus the query hook that wraps it.
|
||||
|
||||
@@ -13,6 +13,11 @@ import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
||||
import LogToolbar from '../log-viewer/LogToolbar';
|
||||
import LogContent from '../log-viewer/LogContent';
|
||||
import { downloadBlob } from '@/utils/downloadBlob';
|
||||
import { usePolling } from '@/hooks/usePolling';
|
||||
|
||||
// Unit log tail cadence while auto-refresh is on.
|
||||
const LOG_TAIL_MS = 3000;
|
||||
|
||||
|
||||
// Built-in saved views. These are the four buttons that used to sit in the
|
||||
// toolbar (All / Active / Failed / Inactive) — they narrowed the AGENT query
|
||||
@@ -139,7 +144,6 @@ const ServicesTab = ({ serverId = null, serverStatus = 'online' }) => {
|
||||
const [logWrap, setLogWrap] = useState(true);
|
||||
const [logAutoRefresh, setLogAutoRefresh] = useState(false);
|
||||
const logContentRef = useRef(null);
|
||||
const logIntervalRef = useRef(null);
|
||||
|
||||
// One unfiltered fetch. The state buckets are column rules now, so asking
|
||||
// the agent for a subset would only hide rows from the rules — and from the
|
||||
@@ -243,12 +247,11 @@ const ServicesTab = ({ serverId = null, serverStatus = 'online' }) => {
|
||||
}
|
||||
}, [isLocal, serverId, logLineCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logAutoRefresh && logsFor) {
|
||||
logIntervalRef.current = setInterval(() => loadLogs(logsFor.unit, { spinner: false }), 3000);
|
||||
}
|
||||
return () => { if (logIntervalRef.current) clearInterval(logIntervalRef.current); };
|
||||
}, [logAutoRefresh, logsFor, loadLogs]);
|
||||
usePolling(
|
||||
() => loadLogs(logsFor.unit, { spinner: false }),
|
||||
LOG_TAIL_MS,
|
||||
{ enabled: logAutoRefresh && Boolean(logsFor), immediate: false },
|
||||
);
|
||||
|
||||
function openLogs(unit) {
|
||||
setLogsFor({ unit });
|
||||
|
||||
@@ -16,6 +16,13 @@ import SystemdServicesTab from '../components/serverdetail/ServicesTab';
|
||||
import { useTableSort } from '@/hooks/useTableSort';
|
||||
import PageLayout from '../layouts/PageLayout';
|
||||
import { downloadBlob } from '@/utils/downloadBlob';
|
||||
import { usePolling } from '@/hooks/usePolling';
|
||||
|
||||
// Log/journal tail cadence while auto-refresh is on.
|
||||
const LOG_TAIL_MS = 3000;
|
||||
// Process table cadence while auto-refresh is on.
|
||||
const PROCESS_REFRESH_MS = 4000;
|
||||
|
||||
import {
|
||||
FileText, Clock, AlertCircle, Search, X, AlertTriangle, Activity,
|
||||
Terminal as TerminalIcon, Server as ServerIcon,
|
||||
@@ -173,7 +180,6 @@ const LogFilesTab = () => {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
const contentRef = useRef(null);
|
||||
const intervalRef = useRef(null);
|
||||
const selectedLogObj = useMemo(
|
||||
() => logFiles.find((l) => l.path === selectedLog) || null,
|
||||
[logFiles, selectedLog]
|
||||
@@ -191,14 +197,11 @@ const LogFilesTab = () => {
|
||||
loadLogFiles();
|
||||
}, [target.kind, target.server_id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (autoRefresh && selectedLog) {
|
||||
intervalRef.current = setInterval(() => {
|
||||
loadLogContent(selectedLog, false);
|
||||
}, 3000);
|
||||
}
|
||||
return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
|
||||
}, [autoRefresh, selectedLog, lineCount, appliedSearch]); // eslint-disable-line
|
||||
usePolling(
|
||||
() => loadLogContent(selectedLog, false),
|
||||
LOG_TAIL_MS,
|
||||
{ enabled: autoRefresh && Boolean(selectedLog), immediate: false },
|
||||
);
|
||||
|
||||
function ensureSupported(op) {
|
||||
if (isRemote && !REMOTE_LOG_SUPPORTED.has(op)) {
|
||||
@@ -495,7 +498,6 @@ const JournalTab = () => {
|
||||
const [lastUpdated, setLastUpdated] = useState(null);
|
||||
|
||||
const contentRef = useRef(null);
|
||||
const intervalRef = useRef(null);
|
||||
const isJournalctl = source === 'journalctl' || source === '';
|
||||
|
||||
useEffect(() => { localStorage.setItem(JOURNAL_PREFS.showLineNumbers, showLineNumbers); }, [showLineNumbers]);
|
||||
@@ -506,12 +508,10 @@ const JournalTab = () => {
|
||||
loadJournalLogs();
|
||||
}, [target.kind, target.server_id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (autoRefresh) {
|
||||
intervalRef.current = setInterval(() => loadJournalLogs(false), 3000);
|
||||
}
|
||||
return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
|
||||
}, [autoRefresh, unit, lineCount, priority]); // eslint-disable-line
|
||||
usePolling(() => loadJournalLogs(false), LOG_TAIL_MS, {
|
||||
enabled: autoRefresh,
|
||||
immediate: false,
|
||||
});
|
||||
|
||||
async function loadJournalLogs(showSpinner = true) {
|
||||
if (isRemote && !REMOTE_JOURNAL_SUPPORTED.has('read')) {
|
||||
@@ -800,18 +800,15 @@ const ProcessesTab = () => {
|
||||
});
|
||||
const fetchSort = sorts[0]?.key === 'memory' ? 'memory' : 'cpu';
|
||||
|
||||
const intervalRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadProcesses();
|
||||
}, [fetchSort, limit, target.kind, target.server_id]); // eslint-disable-line
|
||||
|
||||
useEffect(() => {
|
||||
if (autoRefresh) {
|
||||
intervalRef.current = setInterval(() => loadProcesses(false), 4000);
|
||||
}
|
||||
return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
|
||||
}, [autoRefresh, fetchSort, limit]); // eslint-disable-line
|
||||
usePolling(() => loadProcesses(false), PROCESS_REFRESH_MS, {
|
||||
enabled: autoRefresh,
|
||||
immediate: false,
|
||||
});
|
||||
|
||||
async function loadProcesses(showSpinner = true) {
|
||||
if (isRemote) {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { usePolling } from '@/hooks/usePolling';
|
||||
|
||||
// Synthetic desktop refresh cadence.
|
||||
const SYNTHETIC_REFRESH_MS = 4000;
|
||||
|
||||
|
||||
/**
|
||||
* Fake desktop UI rendered from agent data, used when the host has no
|
||||
@@ -16,10 +21,17 @@ export default function SyntheticDesktop({ api, serverId, fetchJson }) {
|
||||
.catch(err => { if (!cancelled) setError(err.message); });
|
||||
};
|
||||
load();
|
||||
const id = setInterval(load, 4000);
|
||||
return () => { cancelled = true; clearInterval(id); };
|
||||
return () => { cancelled = true; };
|
||||
}, [serverId, fetchJson]);
|
||||
|
||||
usePolling(
|
||||
() => fetchJson(`/api/v1/server-gui/${serverId}/synthetic`)
|
||||
.then(setData)
|
||||
.catch((err) => setError(err.message)),
|
||||
SYNTHETIC_REFRESH_MS,
|
||||
{ immediate: false },
|
||||
);
|
||||
|
||||
if (error) return <div className="sk-gui__banner sk-gui__banner--error">{error}</div>;
|
||||
if (!data) return <div className="sk-gui__loading">Loading synthetic desktop…</div>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user