系统设置改为 Tab 布局,并加大备份区块内边距。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-04 06:54:01 +08:00
parent a983fff6e0
commit 1300dbfd39
4 changed files with 234 additions and 115 deletions
+7 -1
View File
@@ -307,13 +307,19 @@ def _handle_wechat_settings(*, send_test: bool = False) -> None:
def settings_page(): def settings_page():
if request.method == "POST": if request.method == "POST":
action = (request.form.get("form_action") or "account").strip() action = (request.form.get("form_action") or "account").strip()
return_tab = (request.form.get("return_tab") or "").strip()
if action == "wechat": if action == "wechat":
_handle_wechat_settings(send_test=False) _handle_wechat_settings(send_test=False)
return_tab = return_tab or "wechat"
elif action == "wechat_test": elif action == "wechat_test":
_handle_wechat_settings(send_test=True) _handle_wechat_settings(send_test=True)
return_tab = return_tab or "wechat"
else: else:
_handle_account_settings() _handle_account_settings()
return redirect(url_for("settings_page")) return_tab = return_tab or "account"
if return_tab not in ("account", "wechat", "backup"):
return_tab = "account"
return redirect(url_for("settings_page", tab=return_tab))
return render_template( return render_template(
"settings.html", "settings.html",
+100 -5
View File
@@ -586,6 +586,68 @@ button, .btn {
font-weight: 600; font-weight: 600;
} }
.settings-lead { margin: 6px 0 0; max-width: 520px; line-height: 1.45; } .settings-lead { margin: 6px 0 0; max-width: 520px; line-height: 1.45; }
.settings-shell {
max-width: 920px;
margin: 0;
padding: 0;
overflow: hidden;
background:
linear-gradient(165deg, rgba(20, 36, 58, 0.92), rgba(10, 16, 28, 0.96));
border: 1px solid var(--line-strong);
box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.settings-tab-radio {
position: absolute;
opacity: 0;
pointer-events: none;
width: 0;
height: 0;
}
.settings-tabs {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 14px 16px 0;
border-bottom: 1px solid rgba(56, 189, 248, 0.14);
background: rgba(5, 12, 22, 0.35);
}
.settings-tab-btn {
display: inline-flex;
align-items: center;
padding: 9px 14px;
border-radius: 10px 10px 0 0;
border: 1px solid transparent;
border-bottom: 0;
color: var(--muted);
font-size: 0.88rem;
font-weight: 550;
cursor: pointer;
user-select: none;
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.settings-tab-btn:hover {
color: var(--text);
background: rgba(56, 189, 248, 0.08);
}
#hub-set-account:checked ~ .settings-tabs label[for="hub-set-account"],
#hub-set-wechat:checked ~ .settings-tabs label[for="hub-set-wechat"],
#hub-set-backup:checked ~ .settings-tabs label[for="hub-set-backup"] {
color: var(--text);
background: rgba(34, 211, 238, 0.12);
border-color: rgba(56, 189, 248, 0.35);
box-shadow: inset 0 -2px 0 var(--accent);
}
.settings-panels {
padding: 0;
}
.settings-panel {
display: none;
}
#hub-set-account:checked ~ .settings-panels .settings-panel--account,
#hub-set-wechat:checked ~ .settings-panels .settings-panel--wechat,
#hub-set-backup:checked ~ .settings-panels .settings-panel--backup {
display: block;
}
.settings-grid { .settings-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
@@ -604,10 +666,47 @@ button, .btn {
box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.04); box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.04);
} }
.settings-card-head { .settings-card-head {
padding: 18px 20px 14px; padding: 20px 22px 16px;
border-bottom: 1px solid rgba(56, 189, 248, 0.12); border-bottom: 1px solid rgba(56, 189, 248, 0.12);
background: linear-gradient(180deg, rgba(34, 211, 238, 0.06), transparent); background: linear-gradient(180deg, rgba(34, 211, 238, 0.06), transparent);
} }
.settings-backup-body {
padding: 18px 22px 22px;
display: flex;
flex-direction: column;
gap: 20px;
}
.backup-block {
padding: 16px 16px 12px;
border: 1px solid rgba(56, 189, 248, 0.14);
border-radius: 12px;
background: rgba(5, 12, 22, 0.4);
}
.backup-block-head h3 {
margin: 0 0 6px;
font-size: 1rem;
font-weight: 600;
}
.settings-panel--account .settings-form,
.settings-panel--wechat .settings-form {
max-width: 560px;
}
.backup-block-head .field-hint {
margin: 0 0 12px;
}
.settings-field--inline {
margin: 0;
min-width: 12rem;
}
.settings-field--inline select {
margin-top: 6px;
min-width: 12rem;
padding: 9px 10px;
border-radius: 10px;
border: 1px solid var(--line);
background: rgba(5, 10, 18, 0.85);
color: var(--text);
}
.settings-card-head h2 { .settings-card-head h2 {
margin: 8px 0 6px; margin: 8px 0 6px;
font-family: var(--font-display); font-family: var(--font-display);
@@ -777,10 +876,6 @@ button, .btn {
gap: 10px; gap: 10px;
margin-top: 2px; margin-top: 2px;
} }
.backup-block-head h3 {
margin: 0 0 4px;
font-size: 1rem;
}
.backup-table { .backup-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}期权中控{% endblock %}</title> <title>{% block title %}期权中控{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=5"> <link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=6">
</head> </head>
<body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path == '/' %}page-dashboard{% endif %}"> <body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path == '/' %}page-dashboard{% endif %}">
{% if session.get('logged_in') %} {% if session.get('logged_in') %}
+126 -108
View File
@@ -1,129 +1,147 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}系统设置 · 期权中控{% endblock %} {% block title %}系统设置 · 期权中控{% endblock %}
{% block body %} {% block body %}
{% set _tab = (request.args.get('tab') or 'account').strip() %}
{% if _tab not in ('account', 'wechat', 'backup') %}{% set _tab = 'account' %}{% endif %}
<section class="page-head settings-head"> <section class="page-head settings-head">
<div> <div>
<p class="settings-eyebrow">Hub Settings</p> <p class="settings-eyebrow">Hub Settings</p>
<h1>系统设置</h1> <h1>系统设置</h1>
<p class="muted settings-lead">账号与中控告警在此维护,保存后立即生效。</p> <p class="muted settings-lead">账号、告警与备份在此维护;保存后立即生效。</p>
</div> </div>
</section> </section>
<div class="settings-grid"> <div class="settings-shell panel">
<section class="panel settings-card settings-card--account"> <input type="radio" name="hub-settings-tab" id="hub-set-account" class="settings-tab-radio"{% if _tab == 'account' %} checked{% endif %}>
<div class="settings-card-head"> <input type="radio" name="hub-settings-tab" id="hub-set-wechat" class="settings-tab-radio"{% if _tab == 'wechat' %} checked{% endif %}>
<span class="settings-badge">Account</span> <input type="radio" name="hub-settings-tab" id="hub-set-backup" class="settings-tab-radio"{% if _tab == 'backup' %} checked{% endif %}>
<h2>登录账号</h2>
<p class="muted">修改后立即写入 <code>.env</code>,无需重启。</p>
</div>
<form method="post" class="stack settings-form" autocomplete="off" data-lpignore="true" data-1p-ignore="true">
<input type="hidden" name="form_action" value="account">
<div class="settings-field">
<span class="settings-label">当前用户名</span>
<div class="settings-readonly">{{ current_username }}</div>
</div>
<label class="settings-field">当前密码
<input type="password" name="current_password" required autocomplete="new-password" placeholder="验证身份后才能保存">
</label>
<div class="settings-divider"></div>
<label class="settings-field">新用户名
<input type="text" name="new_username" value="{{ current_username }}" required autocomplete="off" minlength="2" maxlength="64">
</label>
<div class="settings-row-2">
<label class="settings-field">新密码
<input type="password" name="new_password" autocomplete="new-password" minlength="6" placeholder="至少 6 位,不改可留空">
</label>
<label class="settings-field">确认新密码
<input type="password" name="confirm_password" autocomplete="new-password" minlength="6" placeholder="再次输入">
</label>
</div>
<button type="submit" class="settings-primary">保存账号</button>
</form>
</section>
<section class="panel settings-card settings-card--wechat"> <div class="settings-tabs" role="tablist" aria-label="系统设置分类">
<div class="settings-card-head"> <label for="hub-set-account" class="settings-tab-btn" role="tab">登录账号</label>
<span class="settings-badge settings-badge--alert">Notify</span> <label for="hub-set-wechat" class="settings-tab-btn" role="tab">企业微信</label>
<h2>企业微信告警</h2> <label for="hub-set-backup" class="settings-tab-btn" role="tab">备份恢复</label>
<p class="muted">中控专用:客户端离线 / 故障 / 恢复时推送,与交易机通知分开。</p> </div>
</div>
<form method="post" class="stack settings-form" autocomplete="off">
<label class="settings-switch">
<input type="checkbox" name="wechat_enabled" value="1"{% if wechat.enabled %} checked{% endif %}>
<span class="settings-switch-ui" aria-hidden="true"></span>
<span class="settings-switch-text">
<strong>启用企业微信推送</strong>
<small>关闭后后台仍巡检,但不发消息</small>
</span>
</label>
<div class="settings-row-2">
<label class="settings-field">告警冷却(秒)
<input type="number" name="alert_cooldown_sec" value="{{ wechat.cooldown_sec }}" min="60" max="86400" step="1">
</label>
<label class="settings-field">连续失败阈值
<input type="number" name="health_fail_threshold" value="{{ wechat.fail_threshold }}" min="1" max="20" step="1">
</label>
</div>
<p class="field-hint settings-hint">连续拉取失败达到阈值后才告警,默认 2 次;同一机器在冷却期内不重复推送。</p>
<label class="settings-field">Webhook 地址
<input type="url" name="wechat_webhook" value="{{ wechat.webhook }}" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." autocomplete="off">
</label>
<div class="settings-actions">
<button type="submit" name="form_action" value="wechat" class="settings-primary">保存告警配置</button>
<button type="submit" name="form_action" value="wechat_test" class="btn-secondary">发送测试消息</button>
</div>
</form>
</section>
<section class="panel settings-card settings-card--backup" style="grid-column:1/-1"> <div class="settings-panels">
<div class="settings-card-head"> <section class="settings-panel settings-panel--account" role="tabpanel">
<span class="settings-badge">Backup</span> <div class="settings-card-head">
<h2>备份恢复</h2> <span class="settings-badge">Account</span>
<p class="muted">每天北京时间 03:00 自动备份,保留最近 10 天。中控可下载本机与各客户端备份;客户端仅能下载自己的。</p> <h2>登录账号</h2>
</div> <p class="muted">修改后立即写入 <code>.env</code>,无需重启。</p>
</div>
<div class="backup-block"> <form method="post" class="stack settings-form" autocomplete="off" data-lpignore="true" data-1p-ignore="true">
<div class="backup-block-head"> <input type="hidden" name="form_action" value="account">
<h3>中控备份</h3> <input type="hidden" name="return_tab" value="account">
<p class="muted field-hint">目录 <code id="hub-backup-dir">/root/crypto_okx_hub_backups</code> · 含 <code>.env</code> 与 <code>hub.db</code></p> <div class="settings-field">
<div class="settings-actions"> <span class="settings-label">当前用户名</span>
<button type="button" class="settings-primary" id="hub-backup-create">立即备份</button> <div class="settings-readonly">{{ current_username }}</div>
<button type="button" class="btn-secondary" id="hub-backup-refresh">刷新</button>
<span class="muted" id="hub-backup-status" style="font-size:.8rem"></span>
</div> </div>
</div> <label class="settings-field">当前密码
<div class="table-wrap"> <input type="password" name="current_password" required autocomplete="new-password" placeholder="验证身份后才能保存">
<table class="backup-table"> </label>
<thead><tr><th>文件</th><th>时间</th><th>大小</th><th>操作</th></tr></thead> <div class="settings-divider"></div>
<tbody id="hub-backup-body"><tr><td colspan="4" class="muted">加载中…</td></tr></tbody> <label class="settings-field">新用户名
</table> <input type="text" name="new_username" value="{{ current_username }}" required autocomplete="off" minlength="2" maxlength="64">
</div> </label>
</div> <div class="settings-row-2">
<label class="settings-field">新密码
<div class="backup-block" style="margin-top:18px"> <input type="password" name="new_password" autocomplete="new-password" minlength="6" placeholder="至少 6 位,不改可留空">
<div class="backup-block-head">
<h3>客户端备份</h3>
<p class="muted field-hint">经 Bridge 拉取各机备份列表,可下载到本地</p>
<div class="settings-actions">
<label class="settings-field" style="margin:0;min-width:12rem">
客户端
<select id="client-backup-instance"></select>
</label> </label>
<button type="button" class="btn-secondary" id="client-backup-refresh">刷新</button> <label class="settings-field">确认新密码
<span class="muted" id="client-backup-status" style="font-size:.8rem"></span> <input type="password" name="confirm_password" autocomplete="new-password" minlength="6" placeholder="再次输入">
</label>
</div>
<button type="submit" class="settings-primary">保存账号</button>
</form>
</section>
<section class="settings-panel settings-panel--wechat" role="tabpanel">
<div class="settings-card-head">
<span class="settings-badge settings-badge--alert">Notify</span>
<h2>企业微信告警</h2>
<p class="muted">中控专用:客户端离线 / 故障 / 恢复时推送,与交易机通知分开。</p>
</div>
<form method="post" class="stack settings-form" autocomplete="off">
<input type="hidden" name="return_tab" value="wechat">
<label class="settings-switch">
<input type="checkbox" name="wechat_enabled" value="1"{% if wechat.enabled %} checked{% endif %}>
<span class="settings-switch-ui" aria-hidden="true"></span>
<span class="settings-switch-text">
<strong>启用企业微信推送</strong>
<small>关闭后后台仍巡检,但不发消息</small>
</span>
</label>
<div class="settings-row-2">
<label class="settings-field">告警冷却(秒)
<input type="number" name="alert_cooldown_sec" value="{{ wechat.cooldown_sec }}" min="60" max="86400" step="1">
</label>
<label class="settings-field">连续失败阈值
<input type="number" name="health_fail_threshold" value="{{ wechat.fail_threshold }}" min="1" max="20" step="1">
</label>
</div>
<p class="field-hint settings-hint">连续拉取失败达到阈值后才告警,默认 2 次;同一机器在冷却期内不重复推送。</p>
<label class="settings-field">Webhook 地址
<input type="url" name="wechat_webhook" value="{{ wechat.webhook }}" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." autocomplete="off">
</label>
<div class="settings-actions">
<button type="submit" name="form_action" value="wechat" class="settings-primary">保存告警配置</button>
<button type="submit" name="form_action" value="wechat_test" class="btn-secondary">发送测试消息</button>
</div>
</form>
</section>
<section class="settings-panel settings-panel--backup" role="tabpanel">
<div class="settings-card-head">
<span class="settings-badge">Backup</span>
<h2>备份恢复</h2>
<p class="muted">每天北京时间 03:00 自动备份,保留最近 10 天。中控可下载本机与各客户端备份;客户端仅能下载自己的。</p>
</div>
<div class="settings-backup-body">
<div class="backup-block">
<div class="backup-block-head">
<h3>中控备份</h3>
<p class="muted field-hint">目录 <code id="hub-backup-dir">/root/crypto_okx_hub_backups</code> · 含 <code>.env</code> 与 <code>hub.db</code></p>
<div class="settings-actions">
<button type="button" class="settings-primary" id="hub-backup-create">立即备份</button>
<button type="button" class="btn-secondary" id="hub-backup-refresh">刷新</button>
<span class="muted" id="hub-backup-status" style="font-size:.8rem"></span>
</div>
</div>
<div class="table-wrap">
<table class="backup-table">
<thead><tr><th>文件</th><th>时间</th><th>大小</th><th>操作</th></tr></thead>
<tbody id="hub-backup-body"><tr><td colspan="4" class="muted">加载中…</td></tr></tbody>
</table>
</div>
</div>
<div class="backup-block">
<div class="backup-block-head">
<h3>客户端备份</h3>
<p class="muted field-hint">经 Bridge 拉取各机备份列表,可下载到本地</p>
<div class="settings-actions">
<label class="settings-field settings-field--inline">
客户端
<select id="client-backup-instance"></select>
</label>
<button type="button" class="btn-secondary" id="client-backup-refresh">刷新</button>
<span class="muted" id="client-backup-status" style="font-size:.8rem"></span>
</div>
</div>
<div class="table-wrap">
<table class="backup-table">
<thead><tr><th>文件</th><th>时间</th><th>大小</th><th>操作</th></tr></thead>
<tbody id="client-backup-body"><tr><td colspan="4" class="muted">请选择客户端</td></tr></tbody>
</table>
</div>
</div> </div>
</div> </div>
<div class="table-wrap"> </section>
<table class="backup-table"> </div>
<thead><tr><th>文件</th><th>时间</th><th>大小</th><th>操作</th></tr></thead>
<tbody id="client-backup-body"><tr><td colspan="4" class="muted">请选择客户端</td></tr></tbody>
</table>
</div>
</div>
</section>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ url_for('static', filename='settings_backup.js') }}?v=1"></script> <script src="{{ url_for('static', filename='settings_backup.js') }}?v=2"></script>
<script>HubSettingsBackup.boot();</script> <script>HubSettingsBackup.boot();</script>
{% endblock %} {% endblock %}