feat: hub setting to hide account balances and PnL in monitor

Persist show_account_pnl in hub_settings.json; refine key monitor panel layout with right-aligned live stats and scrollable history (max 8 rows).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-11 10:15:57 +08:00
parent b671c05b1b
commit a45a3b18e2
6 changed files with 134 additions and 20 deletions
+11 -1
View File
@@ -67,6 +67,7 @@ from settings_store import (
enabled_exchanges,
env_force_disabled_ids,
load_settings,
normalize_display_prefs,
save_settings,
)
from hub_web_auth import (
@@ -676,8 +677,13 @@ def api_get_settings():
return load_settings()
class SettingsDisplayBody(BaseModel):
show_account_pnl: bool = True
class SettingsBody(BaseModel):
exchanges: list[dict] = Field(default_factory=list)
display: SettingsDisplayBody | None = None
@app.post("/api/settings")
@@ -691,7 +697,11 @@ def api_save_settings(body: SettingsBody):
row["enabled"] = False
row.pop("env_disabled", None)
to_save.append(row)
save_settings({"version": 1, "exchanges": to_save})
existing = load_settings()
display = normalize_display_prefs(existing.get("display"))
if body.display is not None:
display = normalize_display_prefs(body.display.model_dump())
save_settings({"version": 1, "exchanges": to_save, "display": display})
return {"ok": True, "settings": load_settings()}
+18 -1
View File
@@ -9,6 +9,10 @@ from pathlib import Path
DIR = Path(__file__).resolve().parent
SETTINGS_PATH = DIR / "hub_settings.json"
DEFAULT_DISPLAY = {
"show_account_pnl": True,
}
DEFAULT_EXCHANGES = [
{
"id": "0",
@@ -65,8 +69,20 @@ def env_force_disabled_ids() -> set[str]:
return _ids_from_csv(raw)
def normalize_display_prefs(raw: dict | None) -> dict:
out = dict(DEFAULT_DISPLAY)
if isinstance(raw, dict):
if "show_account_pnl" in raw:
out["show_account_pnl"] = bool(raw.get("show_account_pnl"))
return out
def load_settings() -> dict:
data = {"exchanges": [dict(x) for x in DEFAULT_EXCHANGES], "version": 1}
data = {
"exchanges": [dict(x) for x in DEFAULT_EXCHANGES],
"version": 1,
"display": dict(DEFAULT_DISPLAY),
}
if SETTINGS_PATH.is_file():
try:
loaded = json.loads(SETTINGS_PATH.read_text(encoding="utf-8"))
@@ -74,6 +90,7 @@ def load_settings() -> dict:
data = loaded
except Exception:
pass
data["display"] = normalize_display_prefs(data.get("display"))
force_off = env_force_disabled_ids()
for ex in data.get("exchanges") or []:
if str(ex.get("id")) in force_off:
+22
View File
@@ -2164,6 +2164,28 @@ button.btn-sm {
text-transform: none;
}
.settings-display-panel {
margin-bottom: 16px;
padding: 14px 16px;
}
.settings-display-title {
margin: 0 0 10px;
font-size: 0.95rem;
color: var(--text);
}
.settings-display-chk {
font-size: 0.88rem;
}
.settings-display-hint {
margin: 8px 0 0;
font-size: 0.78rem;
color: var(--muted);
line-height: 1.45;
}
.settings-card {
background: var(--panel);
border: 1px solid var(--border);
+51 -9
View File
@@ -2,6 +2,25 @@
const toast = document.getElementById("toast");
let settingsCache = null;
let authState = { required: false, logged_in: true };
function showAccountPnlPref() {
const d = settingsCache && settingsCache.display;
if (!d || d.show_account_pnl === undefined) return true;
return !!d.show_account_pnl;
}
function syncDisplayPrefsUI(data) {
const cb = document.getElementById("pref-show-account-pnl");
if (!cb) return;
const show = data && data.display ? data.display.show_account_pnl : true;
cb.checked = show !== false;
}
function positionTableHeadHtml(compact) {
const pnlTh = showAccountPnlPref() ? "<th>浮盈</th>" : "";
const cls = compact ? " data-table data-table-positions" : "";
return `<table class="data-table${cls}"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>标记价</th><th>张数</th>${pnlTh}<th>操作</th></tr></thead><tbody>`;
}
let tpslPending = null;
let lastMonitorRows = [];
let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || "";
@@ -2218,7 +2237,11 @@
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value${tpMonitored ? " pos-tp-program" : ""}">${formatTpCellValue(tp, tpMonitored, symbol, tickMap)}</span></div>
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${rr != null ? fmt(rr, 2) + ":1" : ""}</span></div>
<div class="pos-cell"><span class="pos-label">张数</span><span class="pos-value">${fmt(pos.contracts, 4)}</span></div>
<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ${pnlFmt.cls}">${pnlText}</span></div>
${
showAccountPnlPref()
? `<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ${pnlFmt.cls}">${pnlText}</span></div>`
: ""
}
</div>
<div class="pos-footer">
<span>杠杆: ${sizingFoot.leverage != null && sizingFoot.leverage !== "" ? esc(sizingFoot.leverage) + "x" : "—"}</span>
@@ -2324,13 +2347,16 @@
<button type="button" class="btn-place-tpsl btn-sm ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}" data-contracts="${contractsAttr}" data-sl="${slAttr}" data-tp="${tpAttr}">委托</button>
<button type="button" class="btn-close-pos btn-sm danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>
</div>`;
const pnlTd = showAccountPnlPref()
? `<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>`
: "";
return `<tr>
<td class="td-symbol"><button type="button" class="btn-open-market sym-link" ${mktAttrs} title="打开行情区(含入场/止盈止损)">${esc(x.symbol)}</button>${symBeBadge}</td>
<td class="${sideDirCls(x.side)}">${renderDirectionHtml(x.side)}</td>
<td class="td-entry">${fmtEntryPrice(x, tickMap)}</td>
<td>${fmtMarkPrice(x, tickMap)}</td>
<td>${fmt(x.contracts, 4)}</td>
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>
${pnlTd}
<td class="td-actions">${actionCell}</td>
</tr>`;
}
@@ -2354,7 +2380,7 @@
);
return `<div class="pos-block">
<div class="table-scroll">
<table class="data-table"><thead><tr><th>合约</th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody>
${positionTableHeadHtml(false)}
${rowHtml}
</tbody></table>
</div>
@@ -2442,13 +2468,14 @@
)
.join("");
return `<div class="pos-table-wrap table-scroll">
<table class="data-table data-table-positions"><thead><tr><th>合约</th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody>
${positionTableHeadHtml(true)}
${rows}
</tbody></table>
</div>`;
}
function renderAccountStatRow(row, ag) {
if (!showAccountPnlPref()) return "";
const upnl = ag.total_unrealized_pnl;
return `<div class="stat-row">
<div class="stat-box"><div class="stat-label">资金账户</div><div class="stat-value">${fmt(row.funding_usdt, 2)} <small style="font-size:12px;color:var(--muted)">U</small></div></div>
@@ -2763,7 +2790,11 @@
<span class="status-dot ${dotCls}" aria-hidden="true"></span>
<span class="hub-tile-name">${esc(row.name)}</span>
</div>
<div class="hub-tile-pnl ${pnlCls(upnl)}">${fmt(upnl, 2)} <small>U</small></div>
${
showAccountPnlPref()
? `<div class="hub-tile-pnl ${pnlCls(upnl)}">${fmt(upnl, 2)} <small>U</small></div>`
: ""
}
<div class="hub-tile-meta">${esc(posLine)}</div>
${strategyStats}
<div class="hub-tile-foot">UPD ${esc(tsShort)}</div>
@@ -2977,6 +3008,7 @@
function loadSettingsUI() {
loadSettingsMetaLine();
loadSettings().then((data) => {
syncDisplayPrefsUI(data);
renderSettingsList(data);
});
}
@@ -3010,8 +3042,12 @@
function collectSettingsFromUI() {
const rows = [...document.querySelectorAll("#settings-list .settings-card")];
const pnlCb = document.getElementById("pref-show-account-pnl");
return {
version: 1,
display: {
show_account_pnl: pnlCb ? !!pnlCb.checked : true,
},
exchanges: rows.map((card) => {
const caps = [];
if (card.querySelector(".cap-key").checked) caps.push("key");
@@ -3045,11 +3081,13 @@
showToast("设置已保存(已写入 hub_settings.json");
if (j.settings) {
settingsCache = j.settings;
syncDisplayPrefsUI(j.settings);
renderSettingsList(j.settings);
loadSettingsMetaLine();
} else {
await loadSettingsUI();
}
if (lastMonitorRows.length) renderMonitorGrid(lastMonitorRows);
} else showToast("保存失败", true);
} catch (e) {
showToast(String(e), true);
@@ -3526,9 +3564,13 @@
initAuth().then((ok) => {
if (!ok) return;
initShellNav();
setActiveNav();
if (currentPage() === "settings") {
loadSettings().catch(() => {});
}
loadSettings()
.then((data) => {
syncDisplayPrefsUI(data);
})
.catch(() => {})
.finally(() => {
setActiveNav();
});
});
})();
+8
View File
@@ -466,6 +466,14 @@
</div>
</details>
<p id="settings-meta-line" class="settings-meta-line"></p>
<div class="settings-display-panel card">
<h3 class="settings-display-title">监控区显示</h3>
<label class="chk-label settings-display-chk">
<input type="checkbox" id="pref-show-account-pnl" checked />
显示资金账户、交易账户与浮动盈亏
</label>
<p class="settings-display-hint">关闭后监控区不显示上述数值;保存至 hub_settings.json,换浏览器同样生效。</p>
</div>
<div class="toolbar">
<button type="button" id="btn-settings-save" class="primary">保存设置</button>
<button type="button" id="btn-settings-add">添加交易所</button>