From 1ee9eb02c3dd49217fd0f3e58930ffdcfe8f8e93 Mon Sep 17 00:00:00 2001
From: dekun
Date: Wed, 8 Jul 2026 21:31:14 +0800
Subject: [PATCH] Add settings page tabs and show env API keys with last four
digits.
System settings now use the same CSS tab layout as env config; sensitive env fields display masked values from .env while keeping save-on-empty behavior.
Co-authored-by: Cursor
---
crypto_monitor_binance/app.py | 4 +-
crypto_monitor_gate/app.py | 4 +-
crypto_monitor_okx/app.py | 4 +-
lib/common/static/instance_settings_prefs.js | 20 ++-
lib/common/static/instance_theme.css | 47 +++++++
lib/env/env_schema.py | 8 +-
lib/env/env_ui_manifest.py | 1 +
lib/instance/instance_settings_lib.py | 19 +++
lib/instance/instance_settings_register.py | 3 +-
.../templates/display_prefs_panel.html | 2 +-
lib/instance/templates/embed_shell.html | 4 +-
lib/instance/templates/env_config_panel.html | 18 ++-
lib/instance/templates/index.html | 4 +-
lib/instance/templates/settings_panel.html | 116 ++++++++----------
14 files changed, 169 insertions(+), 85 deletions(-)
diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py
index f7f7202..6bccd58 100644
--- a/crypto_monitor_binance/app.py
+++ b/crypto_monitor_binance/app.py
@@ -7259,6 +7259,7 @@ def render_main_page(page="trade", embed_mode=None):
from lib.instance.instance_settings_lib import settings_page_context
from lib.instance.instance_display_prefs_lib import display_prefs_template_context
+ _display_ctx = display_prefs_template_context(get_db)
template_ctx = dict(
page=page,
key=key_list,
@@ -7340,9 +7341,10 @@ def render_main_page(page="trade", embed_mode=None):
kline_timeframe=KLINE_TIMEFRAME,
**strategy_extra,
**embed_context_extras("binance"),
- **display_prefs_template_context(get_db),
+ **_display_ctx,
**settings_page_context(
page,
+ display=_display_ctx["display"],
instance_base_dir=BASE_DIR,
exchange_key="binance",
exchange_display=EXCHANGE_DISPLAY_NAME,
diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py
index ac864a7..bbba42b 100644
--- a/crypto_monitor_gate/app.py
+++ b/crypto_monitor_gate/app.py
@@ -7053,6 +7053,7 @@ def render_main_page(page="trade", embed_mode=None):
from lib.instance.instance_settings_lib import settings_page_context
from lib.instance.instance_display_prefs_lib import display_prefs_template_context
+ _display_ctx = display_prefs_template_context(get_db)
template_ctx = dict(
page=page,
key=key_list,
@@ -7143,9 +7144,10 @@ def render_main_page(page="trade", embed_mode=None):
has_active_positions=bool(order_list),
),
**embed_context_extras("gate"),
- **display_prefs_template_context(get_db),
+ **_display_ctx,
**settings_page_context(
page,
+ display=_display_ctx["display"],
instance_base_dir=BASE_DIR,
exchange_key="gate",
exchange_display=EXCHANGE_DISPLAY_NAME,
diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py
index dded600..efb3db3 100644
--- a/crypto_monitor_okx/app.py
+++ b/crypto_monitor_okx/app.py
@@ -6647,6 +6647,7 @@ def render_main_page(page="trade", embed_mode=None):
from lib.instance.instance_settings_lib import settings_page_context
from lib.instance.instance_display_prefs_lib import display_prefs_template_context
+ _display_ctx = display_prefs_template_context(get_db)
template_ctx = dict(
page=page,
key=key_list,
@@ -6744,9 +6745,10 @@ def render_main_page(page="trade", embed_mode=None):
exchange_pnl_sync=exchange_pnl_sync,
**strategy_extra,
**embed_context_extras("okx"),
- **display_prefs_template_context(get_db),
+ **_display_ctx,
**settings_page_context(
page,
+ display=_display_ctx["display"],
instance_base_dir=BASE_DIR,
exchange_key="okx",
exchange_display=EXCHANGE_DISPLAY_NAME,
diff --git a/lib/common/static/instance_settings_prefs.js b/lib/common/static/instance_settings_prefs.js
index 71bda56..22f76d1 100644
--- a/lib/common/static/instance_settings_prefs.js
+++ b/lib/common/static/instance_settings_prefs.js
@@ -179,10 +179,24 @@
} else {
input = document.createElement("input");
input.id = "env-f-" + field.key;
- input.type = field.sensitive ? "password" : "text";
- if (field.sensitive && field.has_value) {
- input.placeholder = field.masked || "留空不修改";
+ input.type = "password";
+ input.autocomplete = "off";
+ if (field.sensitive) {
+ if (field.has_value) {
+ const cur = document.createElement("div");
+ cur.className = "env-sensitive-current muted";
+ const labelSpan = document.createElement("span");
+ labelSpan.textContent = "已配置 ";
+ const masked = document.createElement("span");
+ masked.className = "env-masked-value";
+ masked.textContent = field.masked || "";
+ cur.appendChild(labelSpan);
+ cur.appendChild(masked);
+ row.appendChild(cur);
+ }
+ input.placeholder = field.has_value ? "修改时填写新值,留空不修改" : "请输入";
} else {
+ input.type = "text";
input.value = field.current || field.default || "";
}
}
diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index 37944a9..b9c7e3a 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -2306,6 +2306,53 @@ html[data-theme="light"] .journal-detail-img-thumb {
display: block;
}
+#settings-sec-0:checked ~ .env-config-tabs label[for="settings-sec-0"],
+#settings-sec-1:checked ~ .env-config-tabs label[for="settings-sec-1"],
+#settings-sec-2:checked ~ .env-config-tabs label[for="settings-sec-2"],
+#settings-sec-3:checked ~ .env-config-tabs label[for="settings-sec-3"],
+#settings-sec-4:checked ~ .env-config-tabs label[for="settings-sec-4"],
+#settings-sec-5:checked ~ .env-config-tabs label[for="settings-sec-5"],
+#settings-sec-6:checked ~ .env-config-tabs label[for="settings-sec-6"] {
+ color: #e8ecff;
+ border-bottom-color: var(--accent, #7c6cf0);
+ font-weight: 600;
+}
+
+#settings-sec-0:checked ~ .env-config-panels .env-panel--0,
+#settings-sec-1:checked ~ .env-config-panels .env-panel--1,
+#settings-sec-2:checked ~ .env-config-panels .env-panel--2,
+#settings-sec-3:checked ~ .env-config-panels .env-panel--3,
+#settings-sec-4:checked ~ .env-config-panels .env-panel--4,
+#settings-sec-5:checked ~ .env-config-panels .env-panel--5,
+#settings-sec-6:checked ~ .env-config-panels .env-panel--6 {
+ display: block;
+}
+
+.env-sensitive-current {
+ margin: 0 0 6px;
+ font-size: 0.75rem;
+}
+
+.env-masked-value {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ letter-spacing: 0.04em;
+ color: #c5cae0;
+}
+
+.settings-tab-panel h2 {
+ margin: 0 0 8px;
+ font-size: 1rem;
+}
+
+.settings-tab-inner h2 {
+ margin: 0 0 8px;
+ font-size: 1rem;
+}
+
+.settings-config-body {
+ margin-top: 10px;
+}
+
.env-config-panels {
padding: 14px 16px 16px;
}
diff --git a/lib/env/env_schema.py b/lib/env/env_schema.py
index f351dae..21f48fd 100644
--- a/lib/env/env_schema.py
+++ b/lib/env/env_schema.py
@@ -135,11 +135,11 @@ def _field_type(key: str, value: str) -> str:
def _mask_value(key: str, value: Optional[str]) -> dict[str, Any]:
if value is None or value == "":
- return {"value": "", "masked": "", "has_value": False}
+ return {"value": "", "masked": "", "tail": "", "has_value": False}
if not _is_sensitive(key):
- return {"value": value, "masked": value, "has_value": True}
+ return {"value": value, "masked": value, "tail": "", "has_value": True}
tail = value[-4:] if len(value) >= 4 else value
- return {"value": "", "masked": f"****{tail}", "has_value": True}
+ return {"value": "", "masked": f"****{tail}", "tail": tail, "has_value": True}
def parse_env_example_schema(example_path: str) -> list[dict[str, Any]]:
@@ -264,7 +264,7 @@ def validate_env_updates(groups: list[dict], updates: dict[str, str]) -> tuple[d
if value is None:
continue
val = str(value).strip()
- if allowed[key].get("sensitive") and val == "":
+ if allowed[key].get("sensitive") and (val == "" or (val.startswith("****") and len(val) <= 8)):
continue
ftype = allowed[key].get("type")
if ftype == "bool":
diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py
index fcd4819..146e402 100644
--- a/lib/env/env_ui_manifest.py
+++ b/lib/env/env_ui_manifest.py
@@ -170,6 +170,7 @@ def _build_field(
"hot_reload": meta.get("hot_reload", _hot_reload(key)),
"current": masked["value"] if not _is_sensitive(key) else "",
"masked": masked["masked"],
+ "tail": masked.get("tail") or "",
"has_value": masked["has_value"],
}
diff --git a/lib/instance/instance_settings_lib.py b/lib/instance/instance_settings_lib.py
index 5e15e48..b3c26c9 100644
--- a/lib/instance/instance_settings_lib.py
+++ b/lib/instance/instance_settings_lib.py
@@ -183,11 +183,30 @@ def build_instance_settings_view(
}
+def build_settings_tabs(display: dict[str, Any] | None, instance_settings: dict[str, Any]) -> list[dict[str, str]]:
+ disp = display or {}
+ inst = instance_settings or {}
+ tabs: list[dict[str, str]] = [{"key": "nav", "title": "导航显示"}]
+ if disp.get("show_settings_password", True):
+ tabs.append({"key": "password", "title": "账户密码"})
+ if inst.get("show_transfer") and disp.get("show_settings_transfer", True):
+ tabs.append({"key": "transfer", "title": "永续划转"})
+ if disp.get("show_settings_export", True):
+ tabs.append({"key": "export", "title": "数据导出"})
+ if inst.get("options_settings_enabled") and disp.get("show_settings_options_swap", True):
+ tabs.append({"key": "options_swap", "title": "币种兑换"})
+ if inst.get("options_settings_enabled") and disp.get("show_settings_options_transfer", True):
+ tabs.append({"key": "options_transfer", "title": "期权划转"})
+ return tabs
+
+
def settings_page_context(page: str, *, instance_base_dir: str | None = None, **kwargs: Any) -> dict[str, Any]:
p = (page or "").strip()
if p not in ("settings", "risk_policy", "env_config"):
return {}
ctx: dict[str, Any] = {"instance_settings": build_instance_settings_view(**kwargs)}
+ if p == "settings":
+ ctx["settings_tabs"] = build_settings_tabs(kwargs.get("display"), ctx["instance_settings"])
if p == "env_config" and instance_base_dir:
from lib.env.env_ui_manifest import build_env_ui_payload
diff --git a/lib/instance/instance_settings_register.py b/lib/instance/instance_settings_register.py
index 0e423a2..001d83d 100644
--- a/lib/instance/instance_settings_register.py
+++ b/lib/instance/instance_settings_register.py
@@ -168,6 +168,7 @@ def merge_ui_template_context(page: str, get_db: Callable, **settings_kwargs: An
prefs = get_display_prefs(get_db)
ctx = {
"display": prefs,
- **settings_page_context(page, **settings_kwargs),
+ "display_meta": display_meta_for_ui(),
+ **settings_page_context(page, display=prefs, **settings_kwargs),
}
return ctx
diff --git a/lib/instance/templates/display_prefs_panel.html b/lib/instance/templates/display_prefs_panel.html
index 4272cbe..5aab200 100644
--- a/lib/instance/templates/display_prefs_panel.html
+++ b/lib/instance/templates/display_prefs_panel.html
@@ -1,5 +1,5 @@
{# 系统设置 · 导航显示开关(SSR 预渲染,保存仍走 API) #}
-
+
导航显示
以下开关控制顶栏导航与系统设置内区块是否显示,保存后立即生效。关键位监控、实盘下单、系统设置为固定项。
diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html
index 9b4b5ba..4882e0a 100644
--- a/lib/instance/templates/embed_shell.html
+++ b/lib/instance/templates/embed_shell.html
@@ -7,7 +7,7 @@
-
+
{{ exchange_display }} · 加密货币 | 交易监控复盘系统
@@ -103,7 +103,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
-
+
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
-
+