美化系统设置页,登录页抑制浏览器自动填充账号密码。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+225
-25
@@ -174,6 +174,18 @@ body.page-login .main {
|
|||||||
filter: brightness(1.05);
|
filter: brightness(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-card .login-honeypot {
|
||||||
|
position: absolute !important;
|
||||||
|
left: -10000px !important;
|
||||||
|
top: auto !important;
|
||||||
|
width: 1px !important;
|
||||||
|
height: 1px !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
.login-card .login-form { position: relative; }
|
||||||
|
|
||||||
.stack label, .form-grid label { display: block; margin: 8px 0; }
|
.stack label, .form-grid label { display: block; margin: 8px 0; }
|
||||||
input[type="text"], input[type="password"], input[type="number"], input:not([type]), select {
|
input[type="text"], input[type="password"], input[type="number"], input:not([type]), select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -399,43 +411,231 @@ button, .btn {
|
|||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
.empty { padding: 40px; color: var(--muted); }
|
.empty { padding: 40px; color: var(--muted); }
|
||||||
|
|
||||||
|
/* —— 系统设置 —— */
|
||||||
|
.settings-head {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.settings-eyebrow {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.settings-lead { margin: 6px 0 0; max-width: 520px; line-height: 1.45; }
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 480px));
|
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||||||
gap: 20px;
|
gap: 18px;
|
||||||
align-items: start;
|
align-items: stretch;
|
||||||
|
max-width: 980px;
|
||||||
}
|
}
|
||||||
.settings-card { max-width: 480px; }
|
.settings-card {
|
||||||
.settings-form { max-width: 420px; }
|
max-width: none;
|
||||||
.settings-form .check-row {
|
margin: 0;
|
||||||
display: flex;
|
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-card-head {
|
||||||
|
padding: 18px 20px 14px;
|
||||||
|
border-bottom: 1px solid rgba(56, 189, 248, 0.12);
|
||||||
|
background: linear-gradient(180deg, rgba(34, 211, 238, 0.06), transparent);
|
||||||
|
}
|
||||||
|
.settings-card-head h2 {
|
||||||
|
margin: 8px 0 6px;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.settings-card-head .muted {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.45;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
.settings-badge {
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #041018;
|
||||||
|
background: linear-gradient(90deg, var(--accent-deep), var(--accent));
|
||||||
|
}
|
||||||
|
.settings-badge--alert {
|
||||||
|
background: linear-gradient(90deg, #0d9488, #34d399);
|
||||||
|
}
|
||||||
|
.settings-form {
|
||||||
|
max-width: none;
|
||||||
|
padding: 16px 20px 20px;
|
||||||
|
}
|
||||||
|
.settings-field {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #b6c7da;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.settings-form .check-row input { width: auto; margin: 0; }
|
.settings-label {
|
||||||
.settings-form .field-hint {
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #b6c7da;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.settings-form input[type="text"],
|
||||||
|
.settings-form input[type="password"],
|
||||||
|
.settings-form input[type="number"],
|
||||||
|
.settings-form input[type="url"] {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: rgba(5, 10, 18, 0.85);
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.settings-form input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15);
|
||||||
|
}
|
||||||
|
.settings-readonly {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.14);
|
||||||
|
background: rgba(8, 14, 24, 0.55);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.settings-divider {
|
||||||
|
height: 1px;
|
||||||
|
margin: 4px 0 16px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(56, 189, 248, 0.28), transparent);
|
||||||
|
}
|
||||||
|
.settings-row-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.settings-hint {
|
||||||
|
margin: -4px 0 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.settings-switch {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 0 0 16px !important;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.16);
|
||||||
|
background: rgba(5, 12, 22, 0.55);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.settings-switch input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.settings-switch-ui {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 42px;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #1a2738;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
position: relative;
|
||||||
|
transition: background 0.18s ease, border-color 0.18s ease;
|
||||||
|
}
|
||||||
|
.settings-switch-ui::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #8aa0b8;
|
||||||
|
transition: transform 0.18s ease, background 0.18s ease;
|
||||||
|
}
|
||||||
|
.settings-switch input:checked + .settings-switch-ui {
|
||||||
|
background: rgba(34, 211, 238, 0.25);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.settings-switch input:checked + .settings-switch-ui::after {
|
||||||
|
transform: translateX(18px);
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
.settings-switch-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.settings-switch-text strong {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.settings-switch-text small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.settings-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.settings-primary,
|
||||||
|
.settings-form button.settings-primary {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 11px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
background: linear-gradient(90deg, var(--accent-deep), var(--accent));
|
||||||
|
color: #041018;
|
||||||
|
box-shadow: 0 8px 22px rgba(14, 165, 233, 0.22);
|
||||||
|
}
|
||||||
|
.settings-actions .btn-secondary,
|
||||||
|
button.btn-secondary {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.settings-actions .btn-secondary:hover,
|
||||||
|
button.btn-secondary:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
.field-hint {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.settings-actions {
|
@media (max-width: 720px) {
|
||||||
display: flex;
|
.settings-row-2 { grid-template-columns: 1fr; }
|
||||||
flex-wrap: wrap;
|
.settings-grid { grid-template-columns: 1fr; }
|
||||||
gap: 10px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
.settings-actions .btn-secondary,
|
|
||||||
button.btn-secondary {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text);
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
}
|
|
||||||
.settings-actions .btn-secondary:hover,
|
|
||||||
button.btn-secondary:hover {
|
|
||||||
border-color: var(--accent, #60a5fa);
|
|
||||||
}
|
}
|
||||||
input.readonly {
|
input.readonly {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|||||||
+47
-3
@@ -5,15 +5,59 @@
|
|||||||
<section class="login-card">
|
<section class="login-card">
|
||||||
<h1>期权中控</h1>
|
<h1>期权中控</h1>
|
||||||
<p class="login-sub">多机期权持仓总览 · 内嵌打开客户端 · 本仓不下单</p>
|
<p class="login-sub">多机期权持仓总览 · 内嵌打开客户端 · 本仓不下单</p>
|
||||||
<form method="post" class="stack" autocomplete="on">
|
{# 诱饵字段 + readonly/聚焦解锁,避免浏览器自动填入用户名密码 #}
|
||||||
|
<form method="post" class="stack login-form" autocomplete="off" data-lpignore="true" data-1p-ignore="true">
|
||||||
|
<input type="text" name="username_trap" tabindex="-1" aria-hidden="true" autocomplete="username" class="login-honeypot" value="">
|
||||||
|
<input type="password" name="password_trap" tabindex="-1" aria-hidden="true" autocomplete="current-password" class="login-honeypot" value="">
|
||||||
<label>用户名
|
<label>用户名
|
||||||
<input name="username" autocomplete="username" required placeholder="admin">
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
value=""
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
readonly
|
||||||
|
data-login-field="1"
|
||||||
|
>
|
||||||
</label>
|
</label>
|
||||||
<label>密码
|
<label>密码
|
||||||
<input type="password" name="password" autocomplete="current-password" required placeholder="••••••••">
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
value=""
|
||||||
|
placeholder="请输入密码"
|
||||||
|
autocomplete="new-password"
|
||||||
|
readonly
|
||||||
|
data-login-field="1"
|
||||||
|
>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit">登 录</button>
|
<button type="submit">登 录</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
function unlock(el) {
|
||||||
|
if (!el) return;
|
||||||
|
el.removeAttribute("readonly");
|
||||||
|
}
|
||||||
|
document.querySelectorAll("[data-login-field]").forEach(function (el) {
|
||||||
|
el.addEventListener("focus", function () { unlock(el); }, { once: true });
|
||||||
|
el.addEventListener("pointerdown", function () { unlock(el); }, { once: true });
|
||||||
|
});
|
||||||
|
// 延迟清一次,挡住部分密码管理器首屏写入
|
||||||
|
setTimeout(function () {
|
||||||
|
document.querySelectorAll("[data-login-field]").forEach(function (el) {
|
||||||
|
if (document.activeElement !== el) el.value = "";
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
+47
-25
@@ -1,53 +1,75 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}系统设置 · 期权中控{% endblock %}
|
{% block title %}系统设置 · 期权中控{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<section class="page-head"><h1>系统设置</h1></section>
|
<section class="page-head settings-head">
|
||||||
|
<div>
|
||||||
|
<p class="settings-eyebrow">Hub Settings</p>
|
||||||
|
<h1>系统设置</h1>
|
||||||
|
<p class="muted settings-lead">账号与中控告警在此维护,保存后立即生效。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
<section class="panel settings-card">
|
<section class="panel settings-card settings-card--account">
|
||||||
|
<div class="settings-card-head">
|
||||||
|
<span class="settings-badge">Account</span>
|
||||||
<h2>登录账号</h2>
|
<h2>登录账号</h2>
|
||||||
<p class="muted">修改后立即生效,并写入服务器 <code>.env</code>(<code>HUB_USERNAME</code> / <code>HUB_PASSWORD</code>).</p>
|
<p class="muted">修改后立即写入 <code>.env</code>,无需重启。</p>
|
||||||
<form method="post" class="stack settings-form" autocomplete="off">
|
</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">
|
<input type="hidden" name="form_action" value="account">
|
||||||
<label>当前用户名
|
<div class="settings-field">
|
||||||
<input type="text" value="{{ current_username }}" readonly class="readonly">
|
<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>
|
</label>
|
||||||
<label>当前密码
|
<div class="settings-divider"></div>
|
||||||
<input type="password" name="current_password" required autocomplete="current-password" placeholder="验证身份">
|
<label class="settings-field">新用户名
|
||||||
|
<input type="text" name="new_username" value="{{ current_username }}" required autocomplete="off" minlength="2" maxlength="64">
|
||||||
</label>
|
</label>
|
||||||
<label>新用户名
|
<div class="settings-row-2">
|
||||||
<input type="text" name="new_username" value="{{ current_username }}" required autocomplete="username" minlength="2" maxlength="64">
|
<label class="settings-field">新密码
|
||||||
|
<input type="password" name="new_password" autocomplete="new-password" minlength="6" placeholder="至少 6 位,不改可留空">
|
||||||
</label>
|
</label>
|
||||||
<label>新密码
|
<label class="settings-field">确认新密码
|
||||||
<input type="password" name="new_password" autocomplete="new-password" minlength="6" placeholder="至少 6 位;不改密码可留空">
|
<input type="password" name="confirm_password" autocomplete="new-password" minlength="6" placeholder="再次输入">
|
||||||
</label>
|
</label>
|
||||||
<label>确认新密码
|
</div>
|
||||||
<input type="password" name="confirm_password" autocomplete="new-password" minlength="6" placeholder="与新密码一致;不改密码可留空">
|
<button type="submit" class="settings-primary">保存账号</button>
|
||||||
</label>
|
|
||||||
<button type="submit">保存账号</button>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-card">
|
<section class="panel settings-card settings-card--wechat">
|
||||||
|
<div class="settings-card-head">
|
||||||
|
<span class="settings-badge settings-badge--alert">Notify</span>
|
||||||
<h2>企业微信告警</h2>
|
<h2>企业微信告警</h2>
|
||||||
<p class="muted">中控专用:客户端离线/故障与恢复时推送。与各交易机自身通知分开配置。</p>
|
<p class="muted">中控专用:客户端离线 / 故障 / 恢复时推送,与交易机通知分开。</p>
|
||||||
|
</div>
|
||||||
<form method="post" class="stack settings-form" autocomplete="off">
|
<form method="post" class="stack settings-form" autocomplete="off">
|
||||||
<label class="check-row">
|
<label class="settings-switch">
|
||||||
<input type="checkbox" name="wechat_enabled" value="1"{% if wechat.enabled %} checked{% endif %}>
|
<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>
|
</label>
|
||||||
<label>Webhook 地址
|
<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">
|
<input type="url" name="wechat_webhook" value="{{ wechat.webhook }}" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." autocomplete="off">
|
||||||
</label>
|
</label>
|
||||||
<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">
|
<input type="number" name="alert_cooldown_sec" value="{{ wechat.cooldown_sec }}" min="60" max="86400" step="1">
|
||||||
</label>
|
</label>
|
||||||
<label>连续失败阈值
|
<label class="settings-field">连续失败阈值
|
||||||
<input type="number" name="health_fail_threshold" value="{{ wechat.fail_threshold }}" min="1" max="20" step="1">
|
<input type="number" name="health_fail_threshold" value="{{ wechat.fail_threshold }}" min="1" max="20" step="1">
|
||||||
<span class="field-hint">连续拉失败达到次数后才告警,默认 2</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="field-hint settings-hint">连续拉取失败达到阈值后才告警,默认 2 次;同一机器在冷却期内不重复推送。</p>
|
||||||
<div class="settings-actions">
|
<div class="settings-actions">
|
||||||
<button type="submit" name="form_action" value="wechat">保存告警配置</button>
|
<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>
|
<button type="submit" name="form_action" value="wechat_test" class="btn-secondary">发送测试消息</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user