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 - ? `${posRows}
合约方向张数浮盈
` - : `
交易所无持仓
`; + 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 += `${posRows}
合约方向张数浮盈
`; + } 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
- ${levels}
#补仓价张数
+
预览 #${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
+ ${levels}
#补仓价张数
`; @@ -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 `
+
+ + ${envOff} + +
+
+
+
+
+
+
+ + + +
+
+
+ +
+
`; } 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 @@ - +
+
+
交易中控 manual_trading_hub
+ +
-
-

监控区

-

- 持仓/余额来自子代理;关键位、机器人单来自各实例 Flask(须 PM2 跑着 crypto_*)。 - 「交易复盘」在新标签打开 /records。其它电脑访问中控时,请在 hub 的 .env 设置 - HUB_PUBLIC_ORIGIN=http://Ubuntu内网IP,否则会跳到 127.0.0.1。 -

-
- - - - +
+
+

监控区

+
+
+ 说明:数据来源与复盘链接 +
+ 持仓与余额来自子代理;关键位、机器人单、趋势计划来自各实例 Flask(须 PM2 运行 crypto_*)。
+ 「交易复盘」在新标签打开该实例 /records。其它电脑访问中控时,请在 hub 的 .env 设置 + HUB_PUBLIC_ORIGIN=http://服务器内网IP。 +
+
+
+ + + + + +
+
-
-
-