From 46a861d311db5cd466a8bd03ec878591872e43a0 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 2 Aug 2026 12:01:49 +0800 Subject: [PATCH] Replace control monitor cards with status table layout. Co-authored-by: Cursor --- control/frontend/src/pages/Monitor.tsx | 274 ++++++++++++++----------- control/frontend/src/styles.css | 121 +++++++++++ 2 files changed, 271 insertions(+), 124 deletions(-) diff --git a/control/frontend/src/pages/Monitor.tsx b/control/frontend/src/pages/Monitor.tsx index a2f5ec3..8e03898 100644 --- a/control/frontend/src/pages/Monitor.tsx +++ b/control/frontend/src/pages/Monitor.tsx @@ -102,9 +102,12 @@ function closeReasonZh(v: string): string { type RiskLines = { riskBased: boolean; sizing: string; + sizingShort: string; lossPct: string | null; exit: string; openRatio: string | null; + /** 表格「风险/开仓」列:以损显示风险%;手动显示名义比例 */ + riskOrOpen: string; leverage: string; }; @@ -131,7 +134,6 @@ function riskLines(strat: Record): RiskLines { ? `固定 ${fmt(t, 2)}U` : "固定 —"; } - // 以损定仓必显风险比例(risk_loss_pct);手动隐藏 let lossPct: string | null = null; if (riskBased) { const lossN = Number(strat.risk_loss_pct); @@ -141,17 +143,44 @@ function riskLines(strat: Record): RiskLines { } const openRatio = riskBased ? `${unitLabel(strat.risk_perp_unit)}:${unitLabel(strat.risk_option_unit)}` - : null; + : `${unitLabel(strat.perp_qty_eth)}:${unitLabel(strat.option_qty_eth)}`; + const riskOrOpen = riskBased ? lossPct || "—" : openRatio; return { riskBased, sizing: riskBased ? "以损定仓" : "手动仓位", + sizingShort: riskBased ? "以损" : "手动", lossPct, exit, openRatio, + riskOrOpen, leverage: leveragePair(strat), }; } +function modeLabel(mode: string, strat: Record): string { + const m = String(mode || "-").toUpperCase(); + const short = m.includes("LIVE") ? "LIVE" : m.includes("SIM") ? "SIM" : m || "-"; + return `${short}/${riskLines(strat).sizingShort}`; +} + +function tokenDotClass(n: { + token_configured?: boolean; + fleet_ok?: boolean | null; +}): string { + if (!n.token_configured) return "dot muted"; + if (n.fleet_ok === false) return "dot warn"; + return "dot ok"; +} + +function tokenDotTitle(n: { + token_configured?: boolean; + fleet_ok?: boolean | null; +}): string { + if (!n.token_configured) return "Token 未配对"; + if (n.fleet_ok === false) return "Token 配对失败"; + return "Token 已配对"; +} + function RiskParamsBox({ strat }: { strat: Record }) { const r = riskLines(strat); return ( @@ -479,129 +508,126 @@ export default function MonitorPage() { {err ?
{err}
: null} -
- {nodes.map((n) => { - const s = pickStrategy(n); - const running = s.running === true || s.running === 1; - const inPos = hasOpenPosition(s.position); - return ( -
setDetailId(n.id)} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - setDetailId(n.id); - } - }} - > -
- - - {n.online ? "在线" : "离线"} - -
-
{n.base_url}
-
-
-
模式
-
{s.mode}
-
-
-
交易所
-
{s.exchange}
-
-
-
策略
-
- {running ? "运行中" : "已停"} · {s.phase} -
-
-
-
持仓
-
- {inPos ? "持仓中" : "无持仓"} -
-
-
-
轮次
-
{s.rounds ?? "-"}
-
-
-
行情
-
{s.market ? "已连接" : "断开"}
-
-
-
Token
-
- {n.token_configured - ? n.fleet_ok === false - ? "配对失败" - : "已配对" - : "未配对"} -
-
-
- - {n.fleet_error ?
{n.fleet_error}
: null} - {n.error ?
{n.error}
: null} -
e.stopPropagation()}> - - - - -
-
- ); - })} -
- {!nodes.length ? ( + {nodes.length ? ( +
+ + + + + + + + + + + + + + + + + {nodes.map((n) => { + const s = pickStrategy(n); + const running = s.running === true || s.running === 1; + const inPos = hasOpenPosition(s.position); + const risk = riskLines(s.strat); + return ( + setDetailId(n.id)} + > + + + + + + + + + + + + ); + })} + +
+ 选择 + 机器名字模式交易所持仓轮次风险/开仓出场杠杆操作
e.stopPropagation()} + > + toggle(n.id)} + aria-label={`选择 ${n.name}`} + /> + +
+ + + + {n.name} +
+
{modeLabel(s.mode, s.strat)}{s.exchange} + {inPos ? "持仓中" : "无持仓"} + {s.rounds ?? "—"} + {risk.riskOrOpen} + {risk.exit}{risk.leverage} e.stopPropagation()} + > +
+ + + + +
+
+
+ ) : (

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

- ) : null} + )} {detailNode && detail ? (