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:
@@ -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()}
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user