import { useState, useEffect, useCallback, useRef } from 'react' import { useI18n } from '../i18n' const MAX_POINTS = 30 const POLL_INTERVAL = 5000 const MAX_IDLE_POLLS = 3 function MiniGraph({ data, max, color, label, unit }) { if (!data || data.length < 2) return
collecting...
const m = max || Math.max(...data, 1) const w = 100 const h = 32 const points = data.map((v, i) => { const x = (i / (data.length - 1)) * w const y = h - (v / m) * h return `${x},${y}` }).join(' ') const last = data[data.length - 1] return (
{label} {last.toFixed(1)}{unit}
) } export default function Dashboard({ api, refreshRef }) { const { t } = useI18n() const [quota, setQuota] = useState(null) const [recentCmds, setRecentCmds] = useState([]) const [processes, setProcesses] = useState([]) const [metrics, setMetrics] = useState(null) const [copiedIdx, setCopiedIdx] = useState(-1) const cpuRef = useRef([]) const memRef = useRef([]) const netRxRef = useRef([]) const netTxRef = useRef([]) const loadData = useCallback(async () => { try { const [quotaData, cmdData, procData, metricsData] = await Promise.all([ api.getProvidersQuota().catch(() => null), api.getRecentCommands().catch(() => ({ commands: [] })), api.getRunningProcesses().catch(() => ({ processes: [] })), api.getSystemMetrics().catch(() => null), ]) setQuota(quotaData?.providers || []) setRecentCmds(cmdData.commands || []) setProcesses(procData.processes || []) if (metricsData) { setMetrics(metricsData) cpuRef.current = [...cpuRef.current, metricsData.cpu_percent].slice(-MAX_POINTS) memRef.current = [...memRef.current, metricsData.mem_percent].slice(-MAX_POINTS) netRxRef.current = [...netRxRef.current, metricsData.net_rx_kbs].slice(-MAX_POINTS) netTxRef.current = [...netTxRef.current, metricsData.net_tx_kbs].slice(-MAX_POINTS) } } catch (err) { console.error('Dashboard load error:', err) } }, [api]) useEffect(() => { loadData() if (refreshRef) refreshRef.current = loadData let active = true let idleTicks = 0 const iv = setInterval(() => { const hidden = document.querySelector('.dash-grid')?.closest('.tab-hidden') if (hidden) { idleTicks++ if (idleTicks >= MAX_IDLE_POLLS) return } else { idleTicks = 0 } if (active) loadData() }, POLL_INTERVAL) return () => { active = false; clearInterval(iv) } }, [loadData, refreshRef]) const minimax = (quota || []).find(p => p.name === 'minimax') const zai = (quota || []).find(p => p.name === 'zai') const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history', 'cat', 'echo', 'grep', 'export', 'alias', 'unalias', 'set', 'unset', 'source', '.', 'fg', 'bg', 'jobs', 'wait', 'true', 'false', 'yes', 'sleep', 'date', 'whoami', 'id', 'uname', 'hostname', 'uptime', 'df', 'free', 'top', 'htop', 'nano', 'vi', 'vim', 'less', 'more', 'tail', 'head', 'man', 'info', 'which', 'whereis', 'type', 'command', 'hash', 'builtin', 'help'] const topCmds = (() => { const counts = {} for (const c of recentCmds) { const base = c.cmd.split(/\s+/)[0] if (!base || base.length < 2 || EXCLUDE_CMDS.includes(base)) continue if (!/^[a-zA-Z@.\/]/.test(base)) continue counts[base] = (counts[base] || 0) + 1 } return Object.entries(counts) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([cmd, count]) => ({ cmd, count })) })() return (
{/* CPU */}
CPU {metrics ? metrics.cpu_percent.toFixed(0) : '—'}%
{/* RAM */}
RAM {metrics ? `${metrics.mem_used_mb.toFixed(0)}/${metrics.mem_total_mb.toFixed(0)}` : '—'}
{/* Network */}
Network {metrics ? `↓${metrics.net_rx_kbs.toFixed(0)} ↑${metrics.net_tx_kbs.toFixed(0)}` : '—'}
{/* API Quota */}
API Quota
{minimax && minimax.data?.models?.map((m, i) => (
{String(m.model).replace('MiniMax-', '')}
{m.used}/{m.total}
))} {minimax && minimax.data?.models?.length === 0 && (
MiniMax {minimax.error || 'no data'}
)} {zai && zai.data?.models?.map((m, i) => (
{String(m.model)}
{m.used}/{m.total}
))} {zai && !zai.data?.models?.length && (
Z.AI {zai.error || 'no data'}
)} {!minimax && !zai && No providers}
{/* Running Processes */}
Processes {processes.length}
{processes.length === 0 && No relevant processes} {processes.map((p, i) => (
{p.name} cpu {p.cpu}% · mem {p.mem}%
))}
{/* Recent Commands */}
Recent Commands
{topCmds.length > 0 && (
{topCmds.map((c, i) => (
{ navigator.clipboard.writeText(c.cmd); setCopiedIdx(i); setTimeout(() => setCopiedIdx(-1), 1200); }}> {copiedIdx === i ? '✓ Copié' : c.cmd} {c.count}×
))}
)}
{recentCmds.length === 0 && No history} {recentCmds.map((c, i) => (
{c.shell} {c.cmd.length > 45 ? c.cmd.slice(0, 42) + '...' : c.cmd}
))}
) }