diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css
index 36e78c5..e2039a4 100644
--- a/manual_trading_hub/static/app.css
+++ b/manual_trading_hub/static/app.css
@@ -1,102 +1,655 @@
:root {
- --bg: #0f1216;
- --panel: #171b22;
- --text: #e8eaed;
- --muted: #8b929a;
- --border: #2a313c;
+ --bg: #0c0e12;
+ --bg-elevated: #12161d;
+ --panel: #181d26;
+ --panel-hover: #1e2430;
+ --text: #e6edf3;
+ --muted: #8b949e;
+ --border: #30363d;
+ --border-soft: #21262d;
--green: #3fb950;
--red: #f85149;
- --accent: #58a6ff;
+ --accent: #539bf5;
+ --accent-dim: #1f3a5f;
+ --radius: 10px;
+ --shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
+ --font: "Segoe UI", ui-sans-serif, system-ui, -apple-system, sans-serif;
+ --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
}
-* { box-sizing: border-box; }
+
+* {
+ box-sizing: border-box;
+}
+
body {
- font-family: ui-sans-serif, system-ui, "Segoe UI", sans-serif;
+ font-family: var(--font);
background: var(--bg);
color: var(--text);
margin: 0;
- padding: 0;
font-size: 14px;
- line-height: 1.45;
+ line-height: 1.5;
+ min-height: 100vh;
}
-a { color: var(--accent); }
-.top-nav {
+
+a {
+ color: var(--accent);
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+/* —— 顶栏 —— */
+.app-shell {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 0 20px 48px;
+}
+
+.app-header {
display: flex;
- gap: 4px;
- padding: 12px 20px;
- border-bottom: 1px solid var(--border);
- background: #12161c;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 16px 0;
+ border-bottom: 1px solid var(--border-soft);
+ margin-bottom: 8px;
flex-wrap: wrap;
}
+
+.brand {
+ font-size: 15px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--text);
+}
+.brand span {
+ color: var(--muted);
+ font-weight: 400;
+ margin-left: 8px;
+}
+
+.top-nav {
+ display: flex;
+ gap: 6px;
+ background: var(--bg-elevated);
+ padding: 4px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border-soft);
+}
+
.top-nav a {
- padding: 8px 16px;
- border-radius: 6px;
+ padding: 8px 18px;
+ border-radius: 7px;
text-decoration: none;
color: var(--muted);
+ font-size: 13px;
+ font-weight: 500;
+ transition: background 0.15s, color 0.15s;
}
-.top-nav a.active { background: var(--panel); color: var(--text); border: 1px solid var(--border); }
-.page { max-width: 1200px; margin: 0 auto; padding: 16px 20px 40px; }
-.page.hidden { display: none; }
-.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 14px; }
-button, .btn {
+
+.top-nav a:hover {
+ color: var(--text);
+ background: var(--panel-hover);
+ text-decoration: none;
+}
+
+.top-nav a.active {
background: var(--panel);
color: var(--text);
+ box-shadow: var(--shadow);
border: 1px solid var(--border);
- border-radius: 6px;
- padding: 8px 14px;
+}
+
+/* —— 页面 —— */
+.page.hidden {
+ display: none;
+}
+
+.page-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ margin: 20px 0 16px;
+ flex-wrap: wrap;
+}
+
+.page-head h1 {
+ margin: 0;
+ font-size: 22px;
+ font-weight: 600;
+ letter-spacing: -0.02em;
+}
+
+.hint-box {
+ margin-bottom: 16px;
+ border: 1px solid var(--border-soft);
+ border-radius: var(--radius);
+ background: var(--bg-elevated);
+ overflow: hidden;
+}
+
+.hint-box summary {
+ padding: 10px 14px;
cursor: pointer;
font-size: 13px;
+ color: var(--muted);
+ user-select: none;
+ list-style: none;
}
-button:hover { border-color: var(--accent); }
-button.danger { border-color: var(--red); color: var(--red); }
-button:disabled { opacity: 0.45; cursor: not-allowed; }
+.hint-box summary::-webkit-details-marker {
+ display: none;
+}
+.hint-box summary::before {
+ content: "▸ ";
+ color: var(--accent);
+}
+.hint-box[open] summary::before {
+ content: "▾ ";
+}
+
+.hint-box .hint-body {
+ padding: 0 14px 12px;
+ font-size: 12px;
+ color: var(--muted);
+ line-height: 1.6;
+ border-top: 1px solid var(--border-soft);
+}
+.hint-box .hint-body code {
+ font-family: var(--mono);
+ font-size: 11px;
+ background: var(--panel);
+ padding: 1px 5px;
+ border-radius: 4px;
+ color: #b8c4ff;
+}
+
+.toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: center;
+ padding: 12px 14px;
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ margin-bottom: 16px;
+}
+
+.toolbar-spacer {
+ flex: 1;
+ min-width: 8px;
+}
+
+.toolbar-meta {
+ font-size: 12px;
+ color: var(--muted);
+ font-family: var(--mono);
+}
+
+/* —— 按钮 —— */
+button,
+.btn {
+ background: var(--bg-elevated);
+ color: var(--text);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 8px 16px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: border-color 0.15s, background 0.15s;
+}
+
+button:hover:not(:disabled) {
+ border-color: var(--accent);
+ background: var(--panel-hover);
+}
+
+button.primary {
+ background: var(--accent-dim);
+ border-color: var(--accent);
+ color: #fff;
+}
+
+button.danger {
+ border-color: rgba(248, 81, 73, 0.5);
+ color: var(--red);
+ background: rgba(248, 81, 73, 0.08);
+}
+
+button.danger:hover:not(:disabled) {
+ background: rgba(248, 81, 73, 0.15);
+ border-color: var(--red);
+}
+
+button:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.btn-link {
+ background: transparent;
+ border: none;
+ color: var(--accent);
+ padding: 6px 10px;
+ font-size: 12px;
+}
+.btn-link:hover {
+ background: var(--accent-dim);
+ text-decoration: none;
+}
+
+.chk-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ color: var(--muted);
+ cursor: pointer;
+}
+
+/* —— 卡片 —— */
.card {
background: var(--panel);
border: 1px solid var(--border);
- border-radius: 8px;
- margin-bottom: 12px;
+ border-radius: var(--radius);
overflow: hidden;
+ box-shadow: var(--shadow);
+ transition: border-color 0.2s;
}
+
+.card:hover {
+ border-color: #3d444d;
+}
+
.card-head {
- padding: 10px 12px;
- border-bottom: 1px solid var(--border);
+ padding: 14px 16px;
+ border-bottom: 1px solid var(--border-soft);
display: flex;
justify-content: space-between;
- flex-wrap: wrap;
- gap: 8px;
+ align-items: flex-start;
+ gap: 12px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, transparent 100%);
}
-.card-body { padding: 10px 12px; }
-.grid-2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
-.form-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 8px; }
-.form-row input, .form-row select, .form-row textarea {
- background: #0d1117;
- border: 1px solid var(--border);
- color: var(--text);
- border-radius: 6px;
- padding: 7px 10px;
+
+.card-title {
+ font-size: 15px;
+ font-weight: 600;
+ margin: 0 0 4px;
+}
+
+.card-sub {
+ font-size: 11px;
+ color: var(--muted);
+ font-family: var(--mono);
+ word-break: break-all;
+}
+
+.card-actions {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.card-body {
+ padding: 14px 16px;
+}
+
+.grid-monitor {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 16px;
+}
+
+/* 监控统计 */
+.stat-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.stat-box {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-soft);
+ border-radius: 8px;
+ padding: 10px 12px;
+}
+
+.stat-label {
+ font-size: 11px;
+ color: var(--muted);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ margin-bottom: 4px;
+}
+
+.stat-value {
+ font-size: 18px;
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+}
+
+.section-title {
+ font-size: 11px;
+ font-weight: 600;
+ color: #b8c4ff;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ margin: 14px 0 8px;
+ padding-bottom: 6px;
+ border-bottom: 1px solid var(--border-soft);
+}
+
+.section-title:first-child {
+ margin-top: 0;
+}
+
+.data-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 12px;
+}
+
+.data-table th {
+ color: var(--muted);
+ font-weight: 500;
+ font-size: 11px;
+ padding: 6px 8px;
+ text-align: left;
+ border-bottom: 1px solid var(--border);
+}
+
+.data-table td {
+ padding: 8px;
+ border-bottom: 1px solid var(--border-soft);
+ font-variant-numeric: tabular-nums;
+}
+
+.data-table tr:last-child td {
+ border-bottom: none;
+}
+
+.list-line {
+ font-size: 12px;
+ color: var(--muted);
+ padding: 6px 0;
+ border-bottom: 1px dashed var(--border-soft);
+ line-height: 1.45;
+}
+.list-line:last-child {
+ border-bottom: none;
+}
+
+.empty-hint {
+ font-size: 12px;
+ color: var(--muted);
+ padding: 8px 0;
+}
+
+.pnl-pos {
+ color: var(--green);
+}
+.pnl-neg {
+ color: var(--red);
+}
+.err {
+ color: var(--red);
font-size: 13px;
}
-.form-row input { min-width: 100px; }
-.rule-tip { font-size: 12px; color: var(--muted); margin: 8px 0; line-height: 1.5; }
-table { width: 100%; border-collapse: collapse; font-size: 12px; }
-th, td { padding: 6px 8px; border-top: 1px solid var(--border); text-align: left; }
-th { color: var(--muted); }
-.pnl-pos { color: var(--green); }
-.pnl-neg { color: var(--red); }
-.err { color: var(--red); }
-.badge { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: #1f3a5a; color: #8fc8ff; }
-.tabs { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
-.tabs button.active { border-color: var(--accent); color: var(--accent); }
-#toast {
- position: fixed; bottom: 16px; right: 16px;
- max-width: min(480px, 90vw);
+
+.badge {
+ font-size: 10px;
+ padding: 2px 8px;
+ border-radius: 999px;
+ background: var(--accent-dim);
+ color: #8fc8ff;
+ border: 1px solid rgba(83, 155, 245, 0.35);
+ white-space: nowrap;
+}
+
+/* —— 下单区 —— */
+.trade-bar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ align-items: center;
+ margin-bottom: 16px;
+ padding: 14px 16px;
background: var(--panel);
border: 1px solid var(--border);
- padding: 10px 14px;
- border-radius: 8px;
- display: none;
- z-index: 30;
- white-space: pre-wrap;
+ border-radius: var(--radius);
+}
+
+.trade-bar label {
+ font-size: 12px;
+ color: var(--muted);
+ margin-right: 6px;
+}
+
+.trade-bar select {
+ min-width: 220px;
+}
+
+.tabs {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 16px;
+ padding: 4px;
+ background: var(--bg-elevated);
+ border-radius: var(--radius);
+ border: 1px solid var(--border-soft);
+ width: fit-content;
+ max-width: 100%;
+ flex-wrap: wrap;
+}
+
+.tabs button {
+ border: none;
+ background: transparent;
+ padding: 8px 16px;
+ border-radius: 7px;
+}
+
+.tabs button.active {
+ background: var(--panel);
+ color: var(--accent);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+ border: 1px solid var(--border);
+}
+
+.trade-meta {
+ font-size: 12px;
+ color: var(--muted);
+ padding: 10px 14px;
+ background: var(--bg-elevated);
+ border-left: 3px solid var(--accent);
+ border-radius: 0 var(--radius) var(--radius) 0;
+ margin-bottom: 16px;
+ line-height: 1.55;
+}
+
+.form-panel.hidden {
+ display: none;
+}
+
+.form-panel .card-head strong {
+ font-size: 14px;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 12px;
+ align-items: end;
+}
+
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.field label {
+ font-size: 11px;
+ color: var(--muted);
+ font-weight: 500;
+}
+
+.field-wide {
+ grid-column: 1 / -1;
+}
+
+.field input,
+.field select,
+.trade-bar select,
+.form-row input,
+.form-row select {
+ background: var(--bg);
+ border: 1px solid var(--border);
+ color: var(--text);
+ border-radius: 8px;
+ padding: 9px 11px;
+ font-size: 13px;
+ width: 100%;
+}
+
+.field input:focus,
+.field select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px rgba(83, 155, 245, 0.2);
+}
+
+.field-check {
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+ padding-top: 20px;
+}
+
+.field-check label {
+ font-size: 13px;
+ color: var(--text);
+ cursor: pointer;
+}
+
+.form-actions {
+ grid-column: 1 / -1;
+ display: flex;
+ justify-content: flex-end;
+ padding-top: 4px;
+}
+
+/* —— 系统设置 —— */
+.settings-cards {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.settings-card {
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 16px;
+}
+
+.settings-card-head {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 14px;
+ flex-wrap: wrap;
+}
+
+.settings-card-head .ex-name {
+ flex: 1;
+ min-width: 160px;
+ font-size: 15px;
+ font-weight: 600;
+ background: transparent;
+ border: none;
+ border-bottom: 1px dashed var(--border);
+ color: var(--text);
+ padding: 4px 0;
+}
+
+.settings-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 12px;
+}
+
+.settings-grid .field input {
+ font-family: var(--mono);
+ font-size: 12px;
+}
+
+.cap-chips {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ padding: 8px 0;
+}
+
+.cap-chips label {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ color: var(--text);
+ cursor: pointer;
+ padding: 6px 12px;
+ background: var(--bg-elevated);
+ border-radius: 999px;
+ border: 1px solid var(--border-soft);
+}
+
+.settings-card-foot {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border-soft);
+}
+
+.settings-card-foot .field {
+ max-width: 80px;
+}
+
+#toast {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ max-width: min(420px, 92vw);
+ background: var(--panel);
+ border: 1px solid var(--border);
+ padding: 12px 16px;
+ border-radius: var(--radius);
+ display: none;
+ z-index: 50;
+ white-space: pre-wrap;
+ font-size: 13px;
+ box-shadow: var(--shadow);
+}
+
+#toast.show {
+ display: block;
+}
+
+@media (max-width: 640px) {
+ .app-shell {
+ padding: 0 12px 32px;
+ }
+ .grid-monitor {
+ grid-template-columns: 1fr;
+ }
+ .form-grid {
+ grid-template-columns: 1fr;
+ }
}
-#toast.show { display: block; }
-.settings-table input { width: 100%; min-width: 80px; }
-.chk-row { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; }
diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js
index ed4a4e0..ad10989 100644
--- a/manual_trading_hub/static/app.js
+++ b/manual_trading_hub/static/app.js
@@ -112,67 +112,74 @@
inner = `
${esc(row.error || "子代理不可用")}
`;
} else if (!agOk) {
inner = `${esc(agErr || "子代理返回失败")}
`;
- inner += `请检查:PM2 子代理是否在对应 crypto_monitor_* 目录加载了 .env;curl ${esc(row.agent_url || "")}/status
`;
+ inner += `请检查 PM2 子代理与 ${esc(row.agent_url || "")}/status
`;
} else {
- const posRows = pos
- .map(
- (x) =>
- `| ${esc(x.symbol)} | ${esc(x.side)} | ${fmt(x.contracts, 4)} | ${fmt(x.unrealized_pnl, 4)} |
`
- )
- .join("");
- inner = `余额 ${fmt(ag.balance_usdt, 2)} U · 浮盈合计 ${fmt(ag.total_unrealized_pnl, 4)}
`;
- inner += pos.length
- ? ``
- : `交易所无持仓
`;
+ inner = `
+
余额
${fmt(ag.balance_usdt, 2)} U
+
浮盈合计
${fmt(ag.total_unrealized_pnl, 4)}
+
`;
+ inner += `交易所持仓
`;
+ if (pos.length) {
+ const posRows = pos
+ .map(
+ (x) =>
+ `| ${esc(x.symbol)} | ${esc(x.side)} | ${fmt(x.contracts, 4)} | ${fmt(x.unrealized_pnl, 4)} |
`
+ )
+ .join("");
+ inner += ``;
+ } else {
+ inner += `无持仓
`;
+ }
if (orders.length) {
- inner += `机器人持仓 ${orders.length} 笔
`;
+ inner += `机器人单 · ${orders.length}
`;
orders.forEach((o) => {
- inner += `${esc(o.symbol)} ${o.direction} 成交${o.trigger_price}
`;
+ inner += `${esc(o.symbol)} · ${esc(o.direction)} · 触发 ${o.trigger_price}
`;
});
}
if ((row.capabilities || []).includes("key")) {
+ inner += `关键位
`;
if (!flaskOk) {
- inner += `关键位/机器人:策略 Flask 未连通
`;
const fe = row.flask_error || hm.msg || hm.error || "";
const short =
fe ||
(hm.status === 404
- ? "HTTP 404:请 git pull 并重启各 crypto_* Flask(hub_bridge 路由未注册)"
- : "请确认实例已启动,且 HUB_BRIDGE_TOKEN 与实例一致或 APP_AUTH_DISABLED=true");
- inner += `${esc(short)}
`;
+ ? "HTTP 404:请重启各 crypto_* Flask"
+ : "策略 Flask 未连通");
+ inner += `${esc(short)}
`;
} else if (!keys.length) {
- inner += `关键位:当前无记录(在下单区或实例首页添加)
`;
+ inner += `当前无记录
`;
} else {
- inner += `关键位 ${keys.length} 条
`;
keys.slice(0, 8).forEach((k) => {
const kp = kmap[k.id] || kmap[String(k.id)] || {};
const mt = k.monitor_type || k.type || "";
- inner += `${esc(k.symbol)} ${esc(mt)} 上${k.upper}/下${k.lower}`;
+ let line = `${esc(k.symbol)} · ${esc(mt)} · ${k.upper} / ${k.lower}`;
if (kp.price_display != null || kp.price != null) {
- inner += ` · 现价 ${esc(kp.price_display != null ? kp.price_display : kp.price)}`;
+ line += ` · ${esc(kp.price_display != null ? kp.price_display : kp.price)}`;
}
- inner += ` · 门控 ${esc(kp.gate_summary || "-")}
`;
+ line += ` · ${esc(kp.gate_summary || "-")}`;
+ inner += `${line}
`;
});
}
- } else if ((row.capabilities || []).includes("trend")) {
- inner += `该账户为趋势户,无关键位(见趋势计划或下单区)
`;
}
if (trends.length) {
- inner += `趋势计划 ${trends.length} 个运行中
`;
+ inner += `趋势计划 · ${trends.length}
`;
trends.forEach((t) => {
- inner += `#${t.id} ${esc(t.symbol)} ${t.direction} SL${t.stop_loss} TP${t.take_profit}
`;
+ inner += `#${t.id} ${esc(t.symbol)} ${t.direction} · SL ${t.stop_loss} · TP ${t.take_profit}
`;
});
}
}
const review = row.review_url
- ? `交易复盘`
+ ? `复盘`
: "";
return `
-
${esc(row.name)}${esc(row.flask_url_browser || row.flask_url || "")}
-
+
+
${esc(row.name)}
+
${esc(row.flask_url_browser || row.flask_url || "")}
+
+
${review}
-
+
${inner}
@@ -273,16 +280,17 @@
const data = await r.json();
tradeMeta = data.meta?.meta || data.meta || {};
const el = document.getElementById("trade-meta");
- if (tradeMeta.key_gate_rule_text) {
- el.textContent = tradeMeta.key_gate_rule_text;
- } else if (tradeMeta.trend_pullback_preview_ttl) {
- el.textContent =
- `预览有效期 ${tradeMeta.trend_pullback_preview_ttl}s · 补仓档 ${tradeMeta.trend_pullback_dca_legs} · 余额偏差≤${tradeMeta.trend_preview_max_drift_pct}%`;
- } else {
- el.textContent = "";
+ let txt = "";
+ if (tradeMeta.key_gate_rule_text) txt = tradeMeta.key_gate_rule_text;
+ else if (tradeMeta.trend_pullback_preview_ttl) {
+ txt = `预览 ${tradeMeta.trend_pullback_preview_ttl}s · 补仓 ${tradeMeta.trend_pullback_dca_legs} 档 · 余额偏差 ≤${tradeMeta.trend_preview_max_drift_pct}%`;
}
+ el.textContent = txt;
+ el.style.display = txt ? "block" : "none";
} catch (e) {
- document.getElementById("trade-meta").textContent = "";
+ const el = document.getElementById("trade-meta");
+ el.textContent = "";
+ el.style.display = "none";
}
}
@@ -312,9 +320,9 @@
.map((r) => `
| ${r.i} | ${r.price} | ${r.contracts} |
`)
.join("");
box.innerHTML = `
-
预览 #${esc(p.id || trendPreviewId)} 剩余 ${p.expires_in_sec ?? "?"}s
-
${esc(p.symbol)} ${esc(p.direction)} ${p.leverage}x · 快照 ${fmt(p.snapshot_available_usdt, 2)} U
-
+
预览 #${esc(p.id || trendPreviewId)} · ${p.expires_in_sec ?? "?"}s
+
${esc(p.symbol)} ${esc(p.direction)} · ${p.leverage}x · 快照 ${fmt(p.snapshot_available_usdt, 2)} U
+
`;
@@ -355,8 +363,8 @@
const parts = [];
if (m.hub_bridge_token_set) parts.push("中控已配置 HUB_BRIDGE_TOKEN");
else parts.push("中控未设 HUB_BRIDGE_TOKEN(实例需 APP_AUTH_DISABLED 或同令牌)");
- if (m.public_origin) parts.push("复盘外链: " + m.public_origin);
- else parts.push("未设 HUB_PUBLIC_ORIGIN(复盘 127.0.0.1 仅服务器本机可开)");
+ if (m.public_origin) parts.push("浏览器外链基址: " + m.public_origin);
+ else parts.push("未设 HUB_PUBLIC_ORIGIN(复盘链接仅本机可开)");
if ((m.env_disabled_ids || []).length)
parts.push("环境强制关闭 id: " + m.env_disabled_ids.join(", "));
el.textContent = parts.join(" · ");
@@ -366,11 +374,11 @@
function loadSettingsUI() {
loadSettingsMetaLine();
loadSettings().then((data) => {
- const tbody = document.getElementById("settings-tbody");
- tbody.innerHTML = (data.exchanges || [])
- .map((ex, idx) => renderSettingsRow(ex, idx))
+ const list = document.getElementById("settings-list");
+ list.innerHTML = (data.exchanges || [])
+ .map((ex, idx) => renderSettingsCard(ex, idx))
.join("");
- tbody.querySelectorAll(".btn-del-ex").forEach((btn) => {
+ list.querySelectorAll(".btn-del-ex").forEach((btn) => {
btn.onclick = () => {
const i = Number(btn.dataset.idx);
data.exchanges.splice(i, 1);
@@ -381,46 +389,53 @@
});
}
- function renderSettingsRow(ex, idx) {
+ function renderSettingsCard(ex, idx) {
const caps = ex.capabilities || [];
const envOff = ex.env_disabled
- ? '
环境变量强制关'
+ ? '
环境变量强制关'
: "";
- return `
- | ${envOff} |
- |
- |
- |
- |
-
-
-
-
- |
- |
- |
-
`;
+ return `
`;
}
function collectSettingsFromUI() {
- const rows = [...document.querySelectorAll("#settings-tbody tr")];
+ const rows = [...document.querySelectorAll("#settings-list .settings-card")];
return {
version: 1,
- exchanges: rows.map((tr) => {
+ exchanges: rows.map((card) => {
const caps = [];
- if (tr.querySelector(".cap-order").checked) caps.push("order");
- if (tr.querySelector(".cap-key").checked) caps.push("key");
- if (tr.querySelector(".cap-trend").checked) caps.push("trend");
- const id = tr.querySelector(".ex-id").value.trim();
- const stableKey = (tr.dataset.key || id).trim();
+ if (card.querySelector(".cap-order").checked) caps.push("order");
+ if (card.querySelector(".cap-key").checked) caps.push("key");
+ if (card.querySelector(".cap-trend").checked) caps.push("trend");
+ const id = card.querySelector(".ex-id").value.trim();
+ const stableKey = (card.dataset.key || id).trim();
return {
id: id,
key: stableKey,
- name: tr.querySelector(".ex-name").value.trim(),
- flask_url: tr.querySelector(".ex-flask").value.trim(),
- agent_url: tr.querySelector(".ex-agent").value.trim(),
- review_url: tr.querySelector(".ex-review").value.trim(),
- enabled: tr.querySelector(".ex-enabled").checked,
+ name: card.querySelector(".ex-name").value.trim(),
+ flask_url: card.querySelector(".ex-flask").value.trim(),
+ agent_url: card.querySelector(".ex-agent").value.trim(),
+ review_url: card.querySelector(".ex-review").value.trim(),
+ enabled: card.querySelector(".ex-enabled").checked,
capabilities: caps,
};
}),
@@ -471,18 +486,20 @@
};
document.getElementById("order-sltp-mode").onchange = function () {
const pct = this.value === "pct";
- document.getElementById("order-sl").style.display = pct ? "none" : "";
- document.getElementById("order-tp").style.display = pct ? "none" : "";
- document.getElementById("order-sl-pct").style.display = pct ? "" : "none";
- document.getElementById("order-tp-pct").style.display = pct ? "" : "none";
+ const slField = document.getElementById("order-sl").closest(".field");
+ const tpField = document.getElementById("order-tp").closest(".field");
+ if (slField) slField.style.display = pct ? "none" : "";
+ if (tpField) tpField.style.display = pct ? "none" : "";
+ document.getElementById("wrap-sl-pct").style.display = pct ? "" : "none";
+ document.getElementById("wrap-tp-pct").style.display = pct ? "" : "none";
};
document.getElementById("key-sl-tp-mode").onchange = function () {
const manual = this.value === "trend_manual";
- document.getElementById("key-manual-tp").style.display = manual ? "" : "none";
+ document.getElementById("wrap-key-manual-tp").style.display = manual ? "" : "none";
};
document.getElementById("trend-direction").onchange = function () {
- const inp = document.getElementById("trend-add-upper");
- inp.placeholder = this.value === "short" ? "补仓下沿价" : "补仓上沿价";
+ const lbl = document.getElementById("trend-add-label");
+ if (lbl) lbl.textContent = this.value === "short" ? "补仓下沿价" : "补仓上沿价";
};
document.getElementById("btn-settings-save").onclick = saveSettings;
document.getElementById("btn-settings-reload").onclick = loadSettingsUI;
diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html
index 82f50dd..45abc63 100644
--- a/manual_trading_hub/static/index.html
+++ b/manual_trading_hub/static/index.html
@@ -7,156 +7,237 @@
-
+
+
-
-
监控区
-
- 持仓/余额来自子代理;关键位、机器人单来自各实例 Flask(须 PM2 跑着 crypto_*)。
- 「交易复盘」在新标签打开 /records。其它电脑访问中控时,请在 hub 的 .env 设置
- HUB_PUBLIC_ORIGIN=http://Ubuntu内网IP,否则会跳到 127.0.0.1。
-
-
-
-
下单区
-
-
-
-
-
-
-
-
-
-
+
+
+
下单区
+
+
+
+
+
+
+
+
+
+
+
-
-
人工下单
-
-
-
添加关键位
-
-
-
-
系统设置
-
- 配置各交易所 Flask 地址与子代理地址,点击「保存设置」写入本目录
- hub_settings.json(重启 hub 后仍生效)。OKX 默认关闭;环境变量
- HUB_DISABLED_IDS=1 会强制关闭对应 id(勾选框灰掉)。实例须配置与中控一致的
- HUB_BRIDGE_TOKEN,或本机调试时 APP_AUTH_DISABLED=true。
-
-
-
-
-
-
-
-
-
-
-
-
- | 启用 | 显示名 | Flask URL | Agent URL | 复盘链接 |
- 能力 | id | |
-
-
-
-
+
+ 配置说明
+
+ 保存后写入 hub_settings.json。Flask / Agent 填本机地址即可;复盘链接可留空(由 Flask 地址自动生成)。
+ HUB_DISABLED_IDS 可强制关闭账户;HUB_BRIDGE_TOKEN 与实例一致,或实例 APP_AUTH_DISABLED=true。
+
+
+
+
+
+
+
+