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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-08 21:31:14 +08:00
parent 12e44ab26d
commit 1ee9eb02c3
14 changed files with 169 additions and 85 deletions
+3 -1
View File
@@ -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,
+3 -1
View File
@@ -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,
+3 -1
View File
@@ -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,
+17 -3
View File
@@ -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 || "";
}
}
+47
View File
@@ -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;
}
+4 -4
View File
@@ -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":
+1
View File
@@ -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"],
}
+19
View File
@@ -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
+2 -1
View File
@@ -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
@@ -1,5 +1,5 @@
{# 系统设置 · 导航显示开关(SSR 预渲染,保存仍走 API) #}
<div class="card settings-card settings-card--standalone" id="display-prefs-card">
<div class="settings-tab-inner" id="display-prefs-card">
<h2>导航显示</h2>
<p class="settings-env-hint">以下开关控制顶栏导航与系统设置内区块是否显示,保存后立即生效。关键位监控、实盘下单、系统设置为固定项。</p>
<div id="display-prefs-form" class="display-prefs-form" data-prefs-ssr="1">
+2 -2
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
<link rel="stylesheet" href="/static/instance_page.css?v=4">
<link rel="stylesheet" href="/static/instance_theme.css?v=74">
<link rel="stylesheet" href="/static/instance_theme.css?v=75">
<script src="/static/account_risk_badge.js?v=4"></script>
<meta name="theme-color" content="#0b0d14">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
@@ -103,7 +103,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
<script>
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
</script>
<script src="/static/instance_settings_prefs.js?v=9"></script>
<script src="/static/instance_settings_prefs.js?v=10"></script>
<script src="/static/instance_live.js?v=4"></script>
<script src="/static/instance_embed.js?v=20"></script>
</body>
+13 -5
View File
@@ -47,17 +47,25 @@
<option value="true"{% if cur in ('true', '1', 'yes', 'on') %} selected{% endif %}>开启</option>
<option value="false"{% if cur not in ('true', '1', 'yes', 'on') %} selected{% endif %}>关闭</option>
</select>
{% elif field.sensitive %}
{% if field.has_value %}
<div class="env-sensitive-current muted">已配置 <span class="env-masked-value">{{ field.masked }}</span></div>
{% endif %}
<input
class="env-field-input"
id="env-f-{{ field.key }}"
type="password"
data-env-key="{{ field.key }}"
placeholder="{% if field.has_value %}修改时填写新值,留空不修改{% else %}请输入{% endif %}"
autocomplete="off"
>
{% else %}
<input
class="env-field-input"
id="env-f-{{ field.key }}"
type="{% if field.sensitive %}password{% else %}text{% endif %}"
type="text"
data-env-key="{{ field.key }}"
{% if field.sensitive and field.has_value %}
placeholder="{{ field.masked or '留空不修改' }}"
{% else %}
value="{{ field.current or field.default or '' }}"
{% endif %}
>
{% endif %}
</div>
+2 -2
View File
@@ -17,7 +17,7 @@
<link rel="manifest" href="/static/icons/manifest.webmanifest">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
<link rel="stylesheet" href="/static/instance_page.css?v=1">
<link rel="stylesheet" href="/static/instance_theme.css?v=74">
<link rel="stylesheet" href="/static/instance_theme.css?v=75">
</head>
<body
@@ -2038,6 +2038,6 @@ setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }})
<script>
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
</script>
<script src="/static/instance_settings_prefs.js?v=9"></script>
<script src="/static/instance_settings_prefs.js?v=10"></script>
</body>
</html>
+52 -64
View File
@@ -1,66 +1,54 @@
{# 系统设置:2 列独立卡片 #}
<div class="settings-page settings-page--grid">
<p class="settings-env-hint muted" style="margin:0 0 10px">各区块说明见 <code>docs/系统设置说明.md</code></p>
<div class="settings-grid-2col">
<div class="settings-grid-cell">
{% include 'display_prefs_panel.html' %}
</div>
{% if display.show_settings_password %}
<div class="settings-grid-cell">
<div class="card settings-card settings-card--standalone">
{% include 'password_settings_panel.html' %}
</div>
</div>
{% endif %}
{% if instance_settings.show_transfer and display.show_settings_transfer %}
<div class="settings-grid-cell">
<div class="card settings-card settings-card--standalone">
<h2>永续资金划转</h2>
<p class="settings-subcard-desc">子账户永续:资金账户与交易账户之间划转 USDT。</p>
{% include 'instance_transfer_panel.html' %}
</div>
</div>
{% endif %}
{% if display.show_settings_export %}
<div class="settings-grid-cell">
<div class="card settings-card settings-card--standalone settings-card--export">
<h2>数据导出</h2>
<p class="settings-subcard-desc muted">CSV · v{{ instance_settings.data_export_version }}</p>
<div class="settings-export-links-inline settings-export-links-block">
<a href="/export/trade_records">交易记录</a>
<a href="/export/journal_entries">复盘记录</a>
<a href="/export/key_monitors">关键位(当前)</a>
<a href="/export/key_monitor_history">关键位历史</a>
</div>
</div>
</div>
{% endif %}
{% if instance_settings.options_settings_enabled and display.show_settings_options_swap %}
<div class="settings-grid-cell">
<div class="card settings-card settings-card--standalone">
<h2>币种兑换</h2>
{% include 'options_settings_swap.html' %}
</div>
</div>
{% endif %}
{% if instance_settings.options_settings_enabled and display.show_settings_options_transfer %}
<div class="settings-grid-cell">
<div class="card settings-card settings-card--standalone">
<h2>期权资金划转</h2>
{% include 'options_settings_transfer.html' %}
</div>
</div>
{% endif %}
{% if instance_settings.options_settings_enabled %}
<div class="settings-grid-cell settings-grid-cell--full">
{% include 'options_settings_panel.html' %}
</div>
{% endif %}
{# 系统设置:CSS Tab(与 env 配置同方案) #}
<div class="settings-page">
<div class="env-config-head card">
<h2>系统设置</h2>
<p class="settings-env-hint env-config-head-hint">各区块说明见 <code>docs/系统设置说明.md</code></p>
</div>
{% if settings_tabs %}
<div class="env-config-body card settings-config-body">
{% for tab in settings_tabs %}
<input type="radio" name="settings-section" id="settings-sec-{{ loop.index0 }}" class="env-tab-radio"{% if loop.first %} checked{% endif %}>
{% endfor %}
<div class="env-config-tabs" role="tablist" aria-label="系统设置分类">
{% for tab in settings_tabs %}
<label for="settings-sec-{{ loop.index0 }}" class="env-tab-btn" role="tab">{{ tab.title }}</label>
{% endfor %}
</div>
<div class="env-config-panels">
{% for tab in settings_tabs %}
<section class="env-panel env-panel--{{ loop.index0 }} settings-tab-panel" role="tabpanel">
{% if tab.key == 'nav' %}
{% include 'display_prefs_panel.html' %}
{% elif tab.key == 'password' %}
{% include 'password_settings_panel.html' %}
{% elif tab.key == 'transfer' %}
<h2>永续资金划转</h2>
<p class="settings-subcard-desc">子账户永续:资金账户与交易账户之间划转 USDT。</p>
{% include 'instance_transfer_panel.html' %}
{% elif tab.key == 'export' %}
<h2>数据导出</h2>
<p class="settings-subcard-desc muted">CSV · v{{ instance_settings.data_export_version }}</p>
<div class="settings-export-links-inline settings-export-links-block">
<a href="/export/trade_records">交易记录</a>
<a href="/export/journal_entries">复盘记录</a>
<a href="/export/key_monitors">关键位(当前)</a>
<a href="/export/key_monitor_history">关键位历史</a>
</div>
{% elif tab.key == 'options_swap' %}
<h2>币种兑换</h2>
{% include 'options_settings_swap.html' %}
{% elif tab.key == 'options_transfer' %}
<h2>期权资金划转</h2>
{% include 'options_settings_transfer.html' %}
{% endif %}
</section>
{% endfor %}
</div>
</div>
{% endif %}
{% if instance_settings.options_settings_enabled %}
{% include 'options_settings_panel.html' %}
{% endif %}
</div>