settings: add Exchange API tab for instance system settings.

Allow configuring perpetual API keys and live trading from Settings, reusing the existing env save API.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 17:09:53 +08:00
parent 82d16a73e8
commit fd1bf9982c
8 changed files with 150 additions and 4 deletions
@@ -21,6 +21,7 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
"show_settings_transfer": True,
"show_settings_export": True,
"show_settings_password": True,
"show_settings_exchange_api": True,
"show_settings_options_swap": True,
"show_settings_options_transfer": True,
}
@@ -39,6 +40,7 @@ DISPLAY_LABELS: dict[str, str] = {
"show_settings_transfer": "资金划转",
"show_settings_export": "数据导出",
"show_settings_password": "账户密码修改",
"show_settings_exchange_api": "交易所 API",
"show_settings_options_swap": "期权币种兑换",
"show_settings_options_transfer": "期权资金划转",
}
@@ -118,6 +120,7 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
"show_nav_hedge_plan",
]
settings_keys = [
"show_settings_exchange_api",
"show_settings_transfer",
"show_settings_export",
"show_settings_password",
+11 -1
View File
@@ -187,6 +187,8 @@ def build_settings_tabs(display: dict[str, Any] | None, instance_settings: dict[
disp = display or {}
inst = instance_settings or {}
tabs: list[dict[str, str]] = [{"key": "nav", "title": "导航显示"}]
if disp.get("show_settings_exchange_api", True):
tabs.append({"key": "exchange_api", "title": "交易所 API"})
if disp.get("show_settings_password", True):
tabs.append({"key": "password", "title": "账户密码"})
if inst.get("show_transfer") and disp.get("show_settings_transfer", True):
@@ -205,13 +207,21 @@ def settings_page_context(page: str, *, instance_base_dir: str | None = None, **
if p not in ("settings", "risk_policy", "env_config"):
return {}
display = kwargs.pop("display", None)
exchange_key = str(kwargs.get("exchange_key") or "")
ctx: dict[str, Any] = {"instance_settings": build_instance_settings_view(**kwargs)}
if p == "settings":
ctx["settings_tabs"] = build_settings_tabs(display, ctx["instance_settings"])
if instance_base_dir:
from lib.env.env_ui_manifest import build_exchange_api_group
env_path = os.path.join(instance_base_dir, ".env")
example_path = os.path.join(instance_base_dir, ".env.example")
ctx["exchange_api_group"] = build_exchange_api_group(
exchange_key, example_path, env_path
)
if p == "env_config" and instance_base_dir:
from lib.env.env_ui_manifest import build_env_ui_payload
exchange_key = str(kwargs.get("exchange_key") or "")
env_path = os.path.join(instance_base_dir, ".env")
example_path = os.path.join(instance_base_dir, ".env.example")
ctx["env_config_groups"] = build_env_ui_payload(exchange_key, example_path, env_path)
+1 -1
View File
@@ -115,7 +115,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=14"></script>
<script src="/static/instance_settings_prefs.js?v=15"></script>
<script src="/static/instance_live.js?v=6"></script>
<script src="/static/instance_embed.js?v=25"></script>
</body>
@@ -0,0 +1,63 @@
{# 系统设置 · 交易所 API(写入实例 .env,与 env 配置同源) #}
<h2>交易所 API</h2>
<p class="settings-subcard-desc">配置本所永续 API 与实盘开关,保存写入 <code>.env</code>.含「需重启」项请用「保存并重启」.</p>
{% if exchange_api_group %}
<div id="settings-exchange-api-panel" class="settings-exchange-api" data-exchange-api="1">
{% if exchange_api_group.has_restart %}
<p class="env-panel-hint">本组含需重启项,修改后请点「保存并重启」.</p>
{% endif %}
<div class="env-form-grid">
{% for field in exchange_api_group.fields %}
<div class="env-field-row{% if field.restart_required %} env-field-row--restart{% endif %}">
<label class="env-field-label" for="set-api-{{ field.key }}">
{{ field.label or field.key }}
{% if field.restart_required %}<span class="env-restart-mark" title="需重启">*</span>{% endif %}
</label>
{% if field.note %}
<div class="env-field-note muted">{{ field.note }}</div>
{% endif %}
{% if field.type == 'bool' %}
<select class="env-field-input" id="set-api-{{ field.key }}" data-env-key="{{ field.key }}">
{% set cur = (field.current or field.default or 'false')|lower %}
<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="set-api-{{ field.key }}"
type="password"
data-env-key="{{ field.key }}"
data-env-sensitive="1"
data-env-dirty="0"
data-lpignore="true"
data-1p-ignore="true"
data-form-type="other"
autocomplete="new-password"
placeholder="{% if field.has_value %}修改时填写新值,留空不修改{% else %}请输入{% endif %}"
oninput="this.dataset.envDirty='1'"
>
{% else %}
<input
class="env-field-input"
id="set-api-{{ field.key }}"
type="text"
data-env-key="{{ field.key }}"
value="{{ field.current or field.default or '' }}"
>
{% endif %}
</div>
{% endfor %}
</div>
<div class="settings-actions-row" style="margin-top:1rem;">
<button type="button" class="btn-primary btn-sm" id="settings-exchange-api-save">保存</button>
<button type="button" class="btn-secondary btn-sm" id="settings-exchange-api-save-restart">保存并重启</button>
<span class="settings-status-line" id="settings-exchange-api-status"></span>
</div>
</div>
{% else %}
<p class="muted">未能加载交易所 API 字段.</p>
{% endif %}
+1 -1
View File
@@ -2020,6 +2020,6 @@ document.addEventListener("DOMContentLoaded", function () {
});
{% endif %}
</script>
<script src="/static/instance_settings_prefs.js?v=14"></script>
<script src="/static/instance_settings_prefs.js?v=15"></script>
</body>
</html>
+3 -1
View File
@@ -2,7 +2,7 @@
<div class="settings-page full">
<div class="env-config-head card">
<h2>系统设置</h2>
<p class="settings-env-hint env-config-head-hint">各区块说明见 <code>docs/系统设置说明.md</code>.</p>
<p class="settings-env-hint env-config-head-hint">交易所 API、账户密码、划转与显示开关等在此维护.</p>
</div>
{% if settings_tabs %}
@@ -20,6 +20,8 @@
<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 == 'exchange_api' %}
{% include 'exchange_api_settings_panel.html' %}
{% elif tab.key == 'password' %}
{% include 'password_settings_panel.html' %}
{% elif tab.key == 'transfer' %}