Replace control monitor cards with status table layout.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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<string, unknown>): 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<string, unknown>): 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, unknown>): 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<string, unknown> }) {
|
||||
const r = riskLines(strat);
|
||||
return (
|
||||
@@ -479,129 +508,126 @@ export default function MonitorPage() {
|
||||
</div>
|
||||
</div>
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
<div className="card-grid">
|
||||
{nodes.map((n) => {
|
||||
const s = pickStrategy(n);
|
||||
const running = s.running === true || s.running === 1;
|
||||
const inPos = hasOpenPosition(s.position);
|
||||
return (
|
||||
<article
|
||||
key={n.id}
|
||||
className={`node-card ${n.online ? "online" : "offline"} ${
|
||||
running ? "running" : ""
|
||||
}`}
|
||||
onClick={() => setDetailId(n.id)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setDetailId(n.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<header className="node-card-head">
|
||||
<label
|
||||
className="check"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.has(n.id)}
|
||||
onChange={() => toggle(n.id)}
|
||||
/>
|
||||
<strong>{n.name}</strong>
|
||||
</label>
|
||||
<span className={`pill ${n.online ? "ok" : "bad"}`}>
|
||||
{n.online ? "在线" : "离线"}
|
||||
</span>
|
||||
</header>
|
||||
<div className="node-meta mono">{n.base_url}</div>
|
||||
<dl className="kv">
|
||||
<div>
|
||||
<dt>模式</dt>
|
||||
<dd>{s.mode}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>交易所</dt>
|
||||
<dd>{s.exchange}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>策略</dt>
|
||||
<dd>
|
||||
{running ? "运行中" : "已停"} · {s.phase}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>持仓</dt>
|
||||
<dd className={inPos ? "pnl-pos" : "pnl-neg"}>
|
||||
{inPos ? "持仓中" : "无持仓"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>轮次</dt>
|
||||
<dd>{s.rounds ?? "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>行情</dt>
|
||||
<dd>{s.market ? "已连接" : "断开"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Token</dt>
|
||||
<dd>
|
||||
{n.token_configured
|
||||
? n.fleet_ok === false
|
||||
? "配对失败"
|
||||
: "已配对"
|
||||
: "未配对"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<RiskParamsBox strat={s.strat} />
|
||||
{n.fleet_error ? <div className="err soft">{n.fleet_error}</div> : null}
|
||||
{n.error ? <div className="err soft">{n.error}</div> : null}
|
||||
<div className="node-actions" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn ${running ? "btn-running" : ""}`}
|
||||
disabled={!!busy[n.id] || !n.token_configured || running}
|
||||
onClick={() => void act(n.id, "start")}
|
||||
>
|
||||
{running ? "运行中" : "启动"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn ghost"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "pause")}
|
||||
>
|
||||
停止
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "login")}
|
||||
>
|
||||
登录策略机
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn ghost"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "update")}
|
||||
>
|
||||
更新代码
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!nodes.length ? (
|
||||
{nodes.length ? (
|
||||
<div className="monitor-table-wrap">
|
||||
<table className="monitor-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-check">
|
||||
<span className="sr-only">选择</span>
|
||||
</th>
|
||||
<th>机器名字</th>
|
||||
<th>模式</th>
|
||||
<th>交易所</th>
|
||||
<th>持仓</th>
|
||||
<th>轮次</th>
|
||||
<th>风险/开仓</th>
|
||||
<th>出场</th>
|
||||
<th>杠杆</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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 (
|
||||
<tr
|
||||
key={n.id}
|
||||
className={`monitor-row ${running ? "is-running" : "is-stopped"}`}
|
||||
onClick={() => setDetailId(n.id)}
|
||||
>
|
||||
<td
|
||||
className="col-check"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.has(n.id)}
|
||||
onChange={() => toggle(n.id)}
|
||||
aria-label={`选择 ${n.name}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="machine-cell">
|
||||
<span
|
||||
className={`dot ${n.online ? "ok" : "bad"}`}
|
||||
title={n.online ? "在线" : "离线"}
|
||||
/>
|
||||
<span
|
||||
className={tokenDotClass(n)}
|
||||
title={tokenDotTitle(n)}
|
||||
/>
|
||||
<span
|
||||
className={`run-dot ${running ? "ok" : "bad"}`}
|
||||
title={running ? "运行中" : "已停"}
|
||||
/>
|
||||
<strong className="machine-name">{n.name}</strong>
|
||||
</div>
|
||||
</td>
|
||||
<td className="mono">{modeLabel(s.mode, s.strat)}</td>
|
||||
<td className="mono">{s.exchange}</td>
|
||||
<td className={inPos ? "pnl-pos" : "pnl-neg"}>
|
||||
{inPos ? "持仓中" : "无持仓"}
|
||||
</td>
|
||||
<td className="mono">{s.rounds ?? "—"}</td>
|
||||
<td className="mono" title={risk.riskBased ? "风险比例" : "开仓比例"}>
|
||||
{risk.riskOrOpen}
|
||||
</td>
|
||||
<td className="mono">{risk.exit}</td>
|
||||
<td className="mono">{risk.leverage}</td>
|
||||
<td
|
||||
className="col-actions"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="row-actions">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${running ? "btn-running" : ""}`}
|
||||
disabled={
|
||||
!!busy[n.id] || !n.token_configured || running
|
||||
}
|
||||
onClick={() => void act(n.id, "start")}
|
||||
>
|
||||
{running ? "运行中" : "启动"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm ghost"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "pause")}
|
||||
>
|
||||
停止
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "login")}
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm ghost"
|
||||
disabled={!!busy[n.id] || !n.token_configured}
|
||||
onClick={() => void act(n.id, "update")}
|
||||
>
|
||||
更新
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p className="meta">暂无策略机。请到「系统设置」添加并生成 Token。</p>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
{detailNode && detail ? (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user