import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { HttpUtil } from '@/utils'; import { Sparkline } from '@/components/viz'; import './NodeHistoryPanel.css'; interface NodeRef { id: number; } interface NodeHistoryPanelProps { node: NodeRef; bucket?: number; } interface SeriesPoint { t: number; v: number; } interface ApiMsg { success?: boolean; obj?: T; } const REFRESH_MS = 15000; export default function NodeHistoryPanel({ node, bucket = 30 }: NodeHistoryPanelProps) { const { t } = useTranslation(); const [cpuPoints, setCpuPoints] = useState([]); const [cpuLabels, setCpuLabels] = useState([]); const [memPoints, setMemPoints] = useState([]); const [memLabels, setMemLabels] = useState([]); const [netUpPoints, setNetUpPoints] = useState([]); const [netUpLabels, setNetUpLabels] = useState([]); const [netDownPoints, setNetDownPoints] = useState([]); const [netDownLabels, setNetDownLabels] = useState([]); const lastNodeId = useRef(node.id); useEffect(() => { let cancelled = false; const bucketLabel = (unixSec: number) => { const d = new Date(unixSec * 1000); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); if (bucket >= 60) return `${hh}:${mm}`; const ss = String(d.getSeconds()).padStart(2, '0'); return `${hh}:${mm}:${ss}`; }; // cpu/mem are percentages (clamp 0-100); net throughput is bytes/sec shown // as KB/s (no upper clamp, the sparkline auto-scales). const fetchSeries = async (metric: string, kind: 'pct' | 'rate') => { try { const url = `/panel/api/nodes/history/${node.id}/${metric}/${bucket}`; const msg = await HttpUtil.get(url) as ApiMsg; if (msg?.success && Array.isArray(msg.obj)) { const vals: number[] = []; const labs: string[] = []; for (const p of msg.obj) { labs.push(bucketLabel(p.t)); const n = Number(p.v) || 0; vals.push(kind === 'pct' ? Math.max(0, Math.min(100, n)) : Math.max(0, n / 1024)); } return { vals, labs }; } } catch (e) { console.error('node history fetch failed', metric, e); } return { vals: [] as number[], labs: [] as string[] }; }; const refresh = async () => { const [cpu, mem, netUp, netDown] = await Promise.all([ fetchSeries('cpu', 'pct'), fetchSeries('mem', 'pct'), fetchSeries('netUp', 'rate'), fetchSeries('netDown', 'rate'), ]); if (cancelled) return; setCpuPoints(cpu.vals); setCpuLabels(cpu.labs); setMemPoints(mem.vals); setMemLabels(mem.labs); setNetUpPoints(netUp.vals); setNetUpLabels(netUp.labs); setNetDownPoints(netDown.vals); setNetDownLabels(netDown.labs); }; refresh(); const timer = window.setInterval(refresh, REFRESH_MS); lastNodeId.current = node.id; return () => { cancelled = true; window.clearInterval(timer); }; }, [node.id, bucket]); return (
{t('pages.nodes.cpu')}
{t('pages.nodes.mem')}
{t('pages.nodes.netUp')}
{t('pages.nodes.netDown')}
); }