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 - + diff --git a/lib/instance/templates/env_config_panel.html b/lib/instance/templates/env_config_panel.html index 2e84b4b..a49ba41 100644 --- a/lib/instance/templates/env_config_panel.html +++ b/lib/instance/templates/env_config_panel.html @@ -47,17 +47,25 @@ + {% elif field.sensitive %} + {% if field.has_value %} +
已配置 {{ field.masked }}
+ {% endif %} + {% else %} {% endif %}
diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 5629327..9447b79 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -17,7 +17,7 @@ {{ exchange_display }} · 加密货币 | 交易监控复盘系统 - + window.__INSTANCE_DISPLAY__ = {{ display | tojson }}; - + \ No newline at end of file diff --git a/lib/instance/templates/settings_panel.html b/lib/instance/templates/settings_panel.html index 49f222d..763f4fd 100644 --- a/lib/instance/templates/settings_panel.html +++ b/lib/instance/templates/settings_panel.html @@ -1,66 +1,54 @@ -{# 系统设置:2 列独立卡片 #} -
-

各区块说明见 docs/系统设置说明.md

-
-
- {% include 'display_prefs_panel.html' %} -
- - {% if display.show_settings_password %} -
-
- {% include 'password_settings_panel.html' %} -
-
- {% endif %} - - {% if instance_settings.show_transfer and display.show_settings_transfer %} -
-
-

永续资金划转

-

子账户永续:资金账户与交易账户之间划转 USDT。

- {% include 'instance_transfer_panel.html' %} -
-
- {% endif %} - - {% if display.show_settings_export %} -
-
-

数据导出

-

CSV · v{{ instance_settings.data_export_version }}

- -
-
- {% endif %} - - {% if instance_settings.options_settings_enabled and display.show_settings_options_swap %} -
-
-

币种兑换

- {% include 'options_settings_swap.html' %} -
-
- {% endif %} - - {% if instance_settings.options_settings_enabled and display.show_settings_options_transfer %} -
-
-

期权资金划转

- {% include 'options_settings_transfer.html' %} -
-
- {% endif %} - - {% if instance_settings.options_settings_enabled %} -
- {% include 'options_settings_panel.html' %} -
- {% endif %} +{# 系统设置:CSS Tab(与 env 配置同方案) #} +
+
+

系统设置

+

各区块说明见 docs/系统设置说明.md

+ + {% if settings_tabs %} +
+ {% for tab in settings_tabs %} + + {% endfor %} +
+ {% for tab in settings_tabs %} + + {% endfor %} +
+
+ {% for tab in settings_tabs %} +
+ {% if tab.key == 'nav' %} + {% include 'display_prefs_panel.html' %} + {% elif tab.key == 'password' %} + {% include 'password_settings_panel.html' %} + {% elif tab.key == 'transfer' %} +

永续资金划转

+

子账户永续:资金账户与交易账户之间划转 USDT。

+ {% include 'instance_transfer_panel.html' %} + {% elif tab.key == 'export' %} +

数据导出

+

CSV · v{{ instance_settings.data_export_version }}

+ + {% elif tab.key == 'options_swap' %} +

币种兑换

+ {% include 'options_settings_swap.html' %} + {% elif tab.key == 'options_transfer' %} +

期权资金划转

+ {% include 'options_settings_transfer.html' %} + {% endif %} +
+ {% endfor %} +
+
+ {% endif %} + + {% if instance_settings.options_settings_enabled %} + {% include 'options_settings_panel.html' %} + {% endif %}