import { useCallback, useEffect, useState } from "react"; import { apiFetch, type NodeCard } from "../api"; function pickStrategy(n: NodeCard) { const fleet = (n.fleet || {}) as Record; const health = (n.health || {}) as Record; const strat = (fleet.strategy as Record | undefined) || (health.strategy as Record | undefined) || {}; return { mode: String(fleet.mode || health.mode || "-"), running: strat.running, phase: String(strat.phase ?? "-"), rounds: strat.rounds_done, market: fleet.market_connected ?? health.market_connected, exchange: String(fleet.exchange || health.exchange || "-"), }; } export default function MonitorPage() { const [nodes, setNodes] = useState([]); const [err, setErr] = useState(""); const [busy, setBusy] = useState>({}); const [selected, setSelected] = useState>(new Set()); const [pollSec, setPollSec] = useState(8); const refresh = useCallback(async () => { try { const r = await apiFetch<{ nodes: NodeCard[] }>("/api/nodes/status/all"); setNodes(r.nodes || []); setErr(""); } catch (ex) { setErr(ex instanceof Error ? ex.message : String(ex)); } }, []); useEffect(() => { apiFetch<{ poll_interval_sec?: number }>("/api/auth/me") .then((m) => { if (m.poll_interval_sec) setPollSec(m.poll_interval_sec); }) .catch(() => undefined); void refresh(); const id = window.setInterval(() => void refresh(), pollSec * 1000); return () => window.clearInterval(id); }, [refresh, pollSec]); async function act(id: number, action: "start" | "pause" | "update" | "login") { setBusy((b) => ({ ...b, [id]: action })); setErr(""); try { if (action === "login") { const r = await apiFetch<{ url: string }>(`/api/nodes/${id}/login-url`, { method: "POST", }); window.open(r.url, "_blank", "noopener,noreferrer"); } else { await apiFetch(`/api/nodes/${id}/${action === "pause" ? "pause" : action}`, { method: "POST", }); await refresh(); } } catch (ex) { setErr(ex instanceof Error ? ex.message : String(ex)); } finally { setBusy((b) => { const n = { ...b }; delete n[id]; return n; }); } } async function batchUpdate() { const ids = [...selected]; if (!ids.length) return; setErr(""); try { await apiFetch("/api/nodes/update-batch", { method: "POST", body: JSON.stringify({ ids }), }); await refresh(); } catch (ex) { setErr(ex instanceof Error ? ex.message : String(ex)); } } function toggle(id: number) { setSelected((prev) => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; }); } return (

监控区

{err ?
{err}
: null}
{nodes.map((n) => { const s = pickStrategy(n); const running = s.running === true || s.running === 1; return (
{n.online ? "在线" : "离线"}
{n.base_url}
模式
{s.mode}
交易所
{s.exchange}
策略
{running ? "运行中" : "已停"} · {s.phase}
轮次
{s.rounds ?? "-"}
行情
{s.market ? "已连接" : "断开"}
Token
{n.token_configured ? "已配对" : "未配对"}
{n.error ?
{n.error}
: null}
); })}
{!nodes.length ? (

暂无策略机。请到「系统设置」添加并生成 Token。

) : null}
); }