Add hub settings tabs and account password change UI.
Restructure manual-trading-hub settings with CSS tabs matching instance layout; add password API writing HUB_USERNAME/HUB_PASSWORD to hub .env. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1650,6 +1650,48 @@ def api_settings_meta():
|
||||
}
|
||||
|
||||
|
||||
class HubPasswordBody(BaseModel):
|
||||
old_password: str = ""
|
||||
new_username: str = ""
|
||||
new_password: str = ""
|
||||
confirm_password: str = ""
|
||||
|
||||
|
||||
@app.post("/api/settings/password")
|
||||
def api_change_hub_password(body: HubPasswordBody):
|
||||
from hub_env_lib import update_hub_credentials
|
||||
|
||||
if not verify_credentials(expected_username(), body.old_password):
|
||||
raise HTTPException(status_code=400, detail="当前密码错误")
|
||||
if len(body.new_password or "") < 6:
|
||||
raise HTTPException(status_code=400, detail="新密码至少 6 位")
|
||||
if body.new_password != body.confirm_password:
|
||||
raise HTTPException(status_code=400, detail="两次输入的新密码不一致")
|
||||
new_user = (body.new_username or "").strip()
|
||||
changed = update_hub_credentials(
|
||||
new_password=body.new_password,
|
||||
new_username=new_user or None,
|
||||
)
|
||||
if not changed:
|
||||
return {"ok": True, "changed_keys": [], "restart_required": False}
|
||||
return {"ok": True, "changed_keys": changed, "restart_required": True}
|
||||
|
||||
|
||||
@app.get("/api/admin/health")
|
||||
def api_admin_health():
|
||||
return {"ok": True, "status": "up"}
|
||||
|
||||
|
||||
@app.post("/api/admin/restart")
|
||||
def api_admin_restart():
|
||||
from hub_env_lib import restart_hub_pm2
|
||||
|
||||
result = restart_hub_pm2()
|
||||
if not result.get("ok"):
|
||||
raise HTTPException(status_code=500, detail=result.get("msg") or "restart failed")
|
||||
return result
|
||||
|
||||
|
||||
async def _fetch_agent_status(client: httpx.AsyncClient, ex: dict) -> dict:
|
||||
url = f"{ex['agent_url'].rstrip('/')}/status"
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""中控 .env 读写与 PM2 重启。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from lib.env.env_file_lib import apply_env_updates, load_env_file_into_environ
|
||||
|
||||
HUB_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def hub_env_path() -> str:
|
||||
return str(HUB_DIR / ".env")
|
||||
|
||||
|
||||
def update_hub_credentials(*, new_password: str, new_username: str | None = None) -> list[str]:
|
||||
updates: dict[str, str] = {"HUB_PASSWORD": new_password}
|
||||
if new_username:
|
||||
updates["HUB_USERNAME"] = new_username
|
||||
path = hub_env_path()
|
||||
changed = apply_env_updates(path, updates)
|
||||
if changed:
|
||||
load_env_file_into_environ(path)
|
||||
return changed
|
||||
|
||||
|
||||
def restart_hub_pm2() -> dict[str, Any]:
|
||||
app_name = (os.getenv("PM2_APP_NAME") or "").strip() or "manual-trading-hub"
|
||||
if not sys.platform.startswith("linux"):
|
||||
return {"ok": False, "msg": "仅 Linux 服务器支持 PM2 重启", "app": app_name}
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["pm2", "restart", app_name, "--update-env"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
ok = proc.returncode == 0
|
||||
return {
|
||||
"ok": ok,
|
||||
"app": app_name,
|
||||
"msg": (proc.stdout or proc.stderr or "").strip()[:500],
|
||||
"returncode": proc.returncode,
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {"ok": False, "msg": "未找到 pm2 命令", "app": app_name}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"ok": False, "msg": "pm2 restart 超时", "app": app_name}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e), "app": app_name}
|
||||
@@ -2989,6 +2989,125 @@ button.btn-sm {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 中控系统设置 · CSS Tab(与实例 env 配置同方案) */
|
||||
.hub-config-body {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hub-tab-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.hub-config-tabs {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0;
|
||||
overflow-x: auto;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
padding: 0 8px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.hub-tab-btn {
|
||||
flex: 0 0 auto;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
white-space: nowrap;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hub-tab-btn:hover {
|
||||
color: color-mix(in srgb, var(--text) 80%, var(--muted));
|
||||
}
|
||||
|
||||
.hub-config-panels .hub-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#hub-sec-0:checked ~ .hub-config-tabs label[for="hub-sec-0"],
|
||||
#hub-sec-1:checked ~ .hub-config-tabs label[for="hub-sec-1"],
|
||||
#hub-sec-2:checked ~ .hub-config-tabs label[for="hub-sec-2"],
|
||||
#hub-sec-3:checked ~ .hub-config-tabs label[for="hub-sec-3"],
|
||||
#hub-sec-4:checked ~ .hub-config-tabs label[for="hub-sec-4"],
|
||||
#hub-sec-5:checked ~ .hub-config-tabs label[for="hub-sec-5"] {
|
||||
color: var(--text);
|
||||
border-bottom-color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#hub-sec-0:checked ~ .hub-config-panels .hub-panel--0,
|
||||
#hub-sec-1:checked ~ .hub-config-panels .hub-panel--1,
|
||||
#hub-sec-2:checked ~ .hub-config-panels .hub-panel--2,
|
||||
#hub-sec-3:checked ~ .hub-config-panels .hub-panel--3,
|
||||
#hub-sec-4:checked ~ .hub-config-panels .hub-panel--4,
|
||||
#hub-sec-5:checked ~ .hub-config-panels .hub-panel--5 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hub-config-panels {
|
||||
padding: 14px 16px 16px;
|
||||
}
|
||||
|
||||
.hub-settings-tab-panel {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hub-settings-tab-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hub-settings-tab-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hub-settings-tab-head-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hub-settings-tab-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hub-password-grid {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.settings-status-line {
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.settings-status-line.is-err {
|
||||
color: var(--danger, #f87171);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -4168,6 +4168,8 @@
|
||||
const parts = [];
|
||||
if (m.password_required) parts.push("已启用用户名+密码登录");
|
||||
else parts.push("未设 HUB_PASSWORD(反代公网暴露时建议设置 HUB_USERNAME + HUB_PASSWORD)");
|
||||
const userEl = document.getElementById("hub-pwd-current-user");
|
||||
if (userEl && m.default_username) userEl.textContent = m.default_username;
|
||||
if (m.hub_bridge_token_set) parts.push("中控已配置 HUB_BRIDGE_TOKEN");
|
||||
else parts.push("中控未设 HUB_BRIDGE_TOKEN(实例需 APP_AUTH_DISABLED 或同令牌)");
|
||||
if (m.public_origin) parts.push("浏览器外链基址: " + m.public_origin);
|
||||
@@ -4258,21 +4260,6 @@
|
||||
}
|
||||
|
||||
function initSettingsSectionFolds() {
|
||||
document.querySelectorAll(".settings-section[data-settings-section]").forEach((el) => {
|
||||
applySettingsSectionFold(el);
|
||||
if (el.dataset.foldBound === "1") return;
|
||||
el.dataset.foldBound = "1";
|
||||
const foldBtn = el.querySelector(":scope > .settings-section-head > .settings-section-fold");
|
||||
if (foldBtn) {
|
||||
foldBtn.addEventListener("click", () => {
|
||||
const section = el.dataset.settingsSection;
|
||||
const collapsed = !el.classList.contains("is-collapsed");
|
||||
el.classList.toggle("is-collapsed", collapsed);
|
||||
foldBtn.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
setSettingsFoldState(section, collapsed);
|
||||
});
|
||||
}
|
||||
});
|
||||
document.querySelectorAll(".settings-section-save").forEach((btn) => {
|
||||
if (btn.dataset.saveBound === "1") return;
|
||||
btn.dataset.saveBound = "1";
|
||||
@@ -4290,12 +4277,76 @@
|
||||
? "交易监管"
|
||||
: section === "exchanges"
|
||||
? "交易所账户"
|
||||
: "设置";
|
||||
: section === "backup"
|
||||
? "备份设置"
|
||||
: "设置";
|
||||
if (section === "backup") return;
|
||||
void saveSettingsSection(section, { label });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function waitHubHealth() {
|
||||
const deadline = Date.now() + 90000;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
try {
|
||||
const r = await fetch("/api/admin/health", { credentials: "same-origin" });
|
||||
if (r.ok) return;
|
||||
} catch (_) {}
|
||||
}
|
||||
throw new Error("重启后中控未在预期时间内恢复");
|
||||
}
|
||||
|
||||
async function saveHubPassword() {
|
||||
const status = document.getElementById("hub-pwd-save-status");
|
||||
const setStatus = (msg, err) => {
|
||||
if (!status) return;
|
||||
status.textContent = msg || "";
|
||||
status.classList.toggle("is-err", !!err);
|
||||
};
|
||||
setStatus("保存中…");
|
||||
try {
|
||||
const r = await apiFetch("/api/settings/password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
old_password: (document.getElementById("hub-pwd-old") || {}).value || "",
|
||||
new_username: (document.getElementById("hub-pwd-new-username") || {}).value || "",
|
||||
new_password: (document.getElementById("hub-pwd-new") || {}).value || "",
|
||||
confirm_password: (document.getElementById("hub-pwd-confirm") || {}).value || "",
|
||||
}),
|
||||
});
|
||||
const j = await r.json();
|
||||
if (!r.ok) throw new Error(j.detail || j.msg || "保存失败");
|
||||
if (!j.restart_required) {
|
||||
setStatus("未修改(与当前配置相同)");
|
||||
showToast("未修改");
|
||||
return;
|
||||
}
|
||||
setStatus("密码已保存,正在重启中控…");
|
||||
await apiFetch("/api/admin/restart", { method: "POST" });
|
||||
await waitHubHealth();
|
||||
setStatus("密码已更新,请用新密码重新登录");
|
||||
showToast("密码已更新,请重新登录");
|
||||
setTimeout(() => {
|
||||
location.href = "/login";
|
||||
}, 1200);
|
||||
} catch (e) {
|
||||
setStatus(String(e.message || e), true);
|
||||
showToast(String(e.message || e), true);
|
||||
}
|
||||
}
|
||||
|
||||
function initHubPasswordSettings() {
|
||||
const btn = document.getElementById("hub-pwd-save-btn");
|
||||
if (!btn || btn.dataset.bound === "1") return;
|
||||
btn.dataset.bound = "1";
|
||||
btn.addEventListener("click", () => {
|
||||
void saveHubPassword();
|
||||
});
|
||||
}
|
||||
|
||||
function macroDatetimeLocalToApi(v) {
|
||||
if (!v) return "";
|
||||
return String(v).trim().replace("T", " ").slice(0, 16);
|
||||
@@ -4439,6 +4490,7 @@
|
||||
function loadSettingsUI() {
|
||||
loadSettingsMetaLine();
|
||||
initMacroCalendarSettings();
|
||||
initHubPasswordSettings();
|
||||
loadMacroCalendarUI();
|
||||
loadSettings().then((data) => {
|
||||
syncDisplayPrefsUI(data);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260707-stats-bar-center" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260708-hub-settings-tabs" />
|
||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=3" />
|
||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||
@@ -885,208 +885,234 @@
|
||||
<div id="page-settings" class="page hidden">
|
||||
<div class="page-head">
|
||||
<h1><span class="head-tag">CFG</span> 系统设置</h1>
|
||||
<p class="page-desc">交易所地址、启用状态与监控能力</p>
|
||||
<p class="page-desc">登录账号、导航显示、交易所地址与监控能力</p>
|
||||
</div>
|
||||
<details class="hint-box">
|
||||
<summary>配置说明</summary>
|
||||
<div class="hint-body">
|
||||
保存后写入 <code>hub_settings.json</code>。Flask / Agent 填本机地址即可;复盘链接可留空(由 Flask 地址自动生成)。<br />
|
||||
<code>HUB_DISABLED_IDS</code> 可强制关闭账户;<code>HUB_BRIDGE_TOKEN</code> 与实例一致,或实例 <code>APP_AUTH_DISABLED=true</code>。<br />
|
||||
公网反代请在 hub <code>.env</code> 设置 <code>HUB_USERNAME</code> 与 <code>HUB_PASSWORD</code>(默认 <code>admin</code> / <code>admin123</code>);HTTPS 反代建议 <code>HUB_COOKIE_SECURE=true</code>。
|
||||
交易所等配置保存后写入 <code>hub_settings.json</code>;登录账号密码写入 <code>manual_trading_hub/.env</code>。<br />
|
||||
Flask / Agent 填本机地址即可;复盘链接可留空(由 Flask 地址自动生成)。<br />
|
||||
<code>HUB_DISABLED_IDS</code> 可强制关闭账户;<code>HUB_BRIDGE_TOKEN</code> 与实例一致,或实例 <code>APP_AUTH_DISABLED=true</code>。
|
||||
</div>
|
||||
</details>
|
||||
<p id="settings-meta-line" class="settings-meta-line"></p>
|
||||
<section class="settings-section card settings-display-panel" data-settings-section="display">
|
||||
<div class="settings-section-head">
|
||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
||||
<h3 class="settings-display-title">显示与导航</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="display">保存</button>
|
||||
|
||||
<div class="hub-config-body card">
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-0" class="hub-tab-radio" checked>
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-1" class="hub-tab-radio">
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-2" class="hub-tab-radio">
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-3" class="hub-tab-radio">
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-4" class="hub-tab-radio">
|
||||
<input type="radio" name="hub-settings-section" id="hub-sec-5" class="hub-tab-radio">
|
||||
<div class="hub-config-tabs" role="tablist" aria-label="系统设置分类">
|
||||
<label for="hub-sec-0" class="hub-tab-btn" role="tab">账户密码</label>
|
||||
<label for="hub-sec-1" class="hub-tab-btn" role="tab">显示与导航</label>
|
||||
<label for="hub-sec-2" class="hub-tab-btn" role="tab">宏观数据</label>
|
||||
<label for="hub-sec-3" class="hub-tab-btn" role="tab">交易监管</label>
|
||||
<label for="hub-sec-4" class="hub-tab-btn" role="tab">备份恢复</label>
|
||||
<label for="hub-sec-5" class="hub-tab-btn" role="tab">交易所账户</label>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-account-pnl" checked />
|
||||
监控区显示资金账户、交易账户与浮动盈亏
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-funds" checked />
|
||||
顶栏显示「资金概况」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-dashboard" checked />
|
||||
顶栏显示「数据看板」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-plan" checked />
|
||||
顶栏显示「开仓计划」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-archive" checked />
|
||||
顶栏显示「内照明心」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-ai" checked />
|
||||
顶栏显示「AI 教练」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-calculator" checked />
|
||||
顶栏显示「计算器」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-strategy" checked />
|
||||
顶栏显示「策略说明」
|
||||
</label>
|
||||
<p class="settings-display-hint">保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section card settings-macro-panel" data-settings-section="macro">
|
||||
<div class="settings-section-head">
|
||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
||||
<h3 class="settings-display-title">宏观关键数据(风控前置)</h3>
|
||||
<button type="button" class="ghost settings-section-save" data-settings-section="macro">保存表单</button>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-display-hint">
|
||||
手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。
|
||||
</p>
|
||||
<form id="macro-event-form" class="macro-event-form">
|
||||
<label class="macro-event-field">
|
||||
<span>数据名称</span>
|
||||
<select id="macro-event-type" required>
|
||||
<option value="fomc">FOMC 联邦基金利率</option>
|
||||
<option value="cpi">美国 CPI 通胀</option>
|
||||
<option value="employment">就业与劳工数据</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="macro-event-field">
|
||||
<span>发布时间(北京)</span>
|
||||
<input id="macro-event-at" type="datetime-local" required />
|
||||
</label>
|
||||
<label class="macro-event-field macro-event-field-wide">
|
||||
<span>备注(可选)</span>
|
||||
<input id="macro-event-note" type="text" maxlength="500" placeholder="如:仅关注核心 CPI" autocomplete="off" />
|
||||
</label>
|
||||
<div class="macro-event-actions">
|
||||
<button type="submit" id="macro-event-submit" class="primary">添加</button>
|
||||
<button type="button" id="macro-event-cancel" class="ghost hidden">取消编辑</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="macro-event-list" class="macro-event-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section card settings-supervisor-panel" data-settings-section="supervisor">
|
||||
<div class="settings-section-head">
|
||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
||||
<h3 class="settings-display-title">交易监管 · 企业微信</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="supervisor">保存</button>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-display-hint">
|
||||
与三所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。
|
||||
</p>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="supervisor-enabled" checked />
|
||||
启用交易监管推送
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="supervisor-wechat-program" checked />
|
||||
程序止盈/止损也发微信(鼓励向)
|
||||
</label>
|
||||
<div class="settings-grid supervisor-settings-grid">
|
||||
<div class="field field-wide">
|
||||
<label>企业微信 Webhook</label>
|
||||
<input id="supervisor-wechat-webhook" type="text" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." autocomplete="off" />
|
||||
</div>
|
||||
<div class="field field-wide">
|
||||
<label>微信消息跳转链接(可改)</label>
|
||||
<input id="supervisor-wechat-link" type="text" placeholder="https://你的域名/ai?mode=supervisor" autocomplete="off" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>消息前缀</label>
|
||||
<input id="supervisor-wechat-prefix" type="text" value="【交易监管】" autocomplete="off" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>日手动平警告阈值</label>
|
||||
<input id="supervisor-daily-warn" type="number" min="1" step="1" value="2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>最短两笔间隔(分钟)</label>
|
||||
<input id="supervisor-interval-warn" type="number" min="1" step="1" value="15" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>30 分钟内笔数阈值</label>
|
||||
<input id="supervisor-freq-30m" type="number" min="1" step="1" value="2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>平仓后再开仓(分钟)</label>
|
||||
<input id="supervisor-reopen-min" type="number" min="1" step="1" value="30" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section card settings-backup-panel" data-settings-section="backup">
|
||||
<div class="settings-section-head">
|
||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
||||
<h3 class="settings-display-title">备份与恢复</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="backup">保存</button>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<p class="settings-display-hint">
|
||||
打包三所 <code>crypto.db</code>、中控 K 线/归档等 SQLite、<code>hub_settings.json</code> 与 <code>.env</code>(可选)。
|
||||
恢复前会自动做一次 pre-restore 快照,并尝试 <code>pm2 restart all</code>。
|
||||
</p>
|
||||
<div class="settings-grid backup-settings-grid">
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-auto-enabled" checked />
|
||||
每日自动备份(北京时间)
|
||||
</label>
|
||||
<div class="field">
|
||||
<label>自动备份时刻(时,0–23)</label>
|
||||
<input id="backup-auto-hour" type="number" min="0" max="23" step="1" value="0" />
|
||||
<div class="hub-config-panels">
|
||||
<section class="hub-panel hub-panel--0 hub-settings-tab-panel" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">账户密码</h3>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>保留天数</label>
|
||||
<input id="backup-retention-days" type="number" min="1" max="365" step="1" value="30" />
|
||||
<p class="settings-display-hint">修改中控网页登录账号密码,写入 <code>manual_trading_hub/.env</code> 后需重启中控生效。当前用户:<code id="hub-pwd-current-user">admin</code></p>
|
||||
<div class="settings-grid hub-password-grid">
|
||||
<div class="field"><label>当前密码</label><input type="password" id="hub-pwd-old" autocomplete="current-password"></div>
|
||||
<div class="field"><label>新用户名(可选)</label><input type="text" id="hub-pwd-new-username" autocomplete="username"></div>
|
||||
<div class="field"><label>新密码</label><input type="password" id="hub-pwd-new" autocomplete="new-password"></div>
|
||||
<div class="field"><label>确认新密码</label><input type="password" id="hub-pwd-confirm" autocomplete="new-password"></div>
|
||||
</div>
|
||||
<div class="hub-settings-tab-actions">
|
||||
<button type="button" class="primary" id="hub-pwd-save-btn">保存密码</button>
|
||||
<span id="hub-pwd-save-status" class="settings-status-line"></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-panel hub-panel--1 hub-settings-tab-panel settings-display-panel" data-settings-section="display" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">显示与导航</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="display">保存</button>
|
||||
</div>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-include-env" checked />
|
||||
包含 .env 配置文件
|
||||
<input type="checkbox" id="pref-show-account-pnl" checked />
|
||||
监控区显示资金账户、交易账户与浮动盈亏
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-include-images" />
|
||||
包含三所 static/images 截图
|
||||
<input type="checkbox" id="pref-show-nav-funds" checked />
|
||||
顶栏显示「资金概况」
|
||||
</label>
|
||||
<div class="field field-wide">
|
||||
<label>备份目录(留空默认 /root/backups/crypto_monitor_portal)</label>
|
||||
<input id="backup-root" type="text" placeholder="/root/backups/crypto_monitor_portal" autocomplete="off" />
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-dashboard" checked />
|
||||
顶栏显示「数据看板」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-plan" checked />
|
||||
顶栏显示「开仓计划」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-archive" checked />
|
||||
顶栏显示「内照明心」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-ai" checked />
|
||||
顶栏显示「AI 教练」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-calculator" checked />
|
||||
顶栏显示「计算器」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-strategy" checked />
|
||||
顶栏显示「策略说明」
|
||||
</label>
|
||||
<p class="settings-display-hint">保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。</p>
|
||||
</section>
|
||||
|
||||
<section class="hub-panel hub-panel--2 hub-settings-tab-panel settings-macro-panel" data-settings-section="macro" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">宏观关键数据(风控前置)</h3>
|
||||
<button type="button" class="ghost settings-section-save" data-settings-section="macro">保存表单</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-actions">
|
||||
<button type="button" id="backup-run-now" class="primary">立即备份</button>
|
||||
<span id="backup-status-line" class="backup-status-line"></span>
|
||||
</div>
|
||||
<div class="backup-restore-upload">
|
||||
<label class="backup-upload-label">
|
||||
<span>上传备份包恢复(.zip)</span>
|
||||
<input id="backup-restore-file" type="file" accept=".zip,application/zip" />
|
||||
<p class="settings-display-hint">
|
||||
手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。
|
||||
</p>
|
||||
<form id="macro-event-form" class="macro-event-form">
|
||||
<label class="macro-event-field">
|
||||
<span>数据名称</span>
|
||||
<select id="macro-event-type" required>
|
||||
<option value="fomc">FOMC 联邦基金利率</option>
|
||||
<option value="cpi">美国 CPI 通胀</option>
|
||||
<option value="employment">就业与劳工数据</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="macro-event-field">
|
||||
<span>发布时间(北京)</span>
|
||||
<input id="macro-event-at" type="datetime-local" required />
|
||||
</label>
|
||||
<label class="macro-event-field macro-event-field-wide">
|
||||
<span>备注(可选)</span>
|
||||
<input id="macro-event-note" type="text" maxlength="500" placeholder="如:仅关注核心 CPI" autocomplete="off" />
|
||||
</label>
|
||||
<div class="macro-event-actions">
|
||||
<button type="submit" id="macro-event-submit" class="primary">添加</button>
|
||||
<button type="button" id="macro-event-cancel" class="ghost hidden">取消编辑</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="macro-event-list" class="macro-event-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="hub-panel hub-panel--3 hub-settings-tab-panel settings-supervisor-panel" data-settings-section="supervisor" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">交易监管 · 企业微信</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="supervisor">保存</button>
|
||||
</div>
|
||||
<p class="settings-display-hint">
|
||||
与三所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。
|
||||
</p>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="supervisor-enabled" checked />
|
||||
启用交易监管推送
|
||||
</label>
|
||||
<button type="button" id="backup-restore-upload-btn" class="danger">上传并恢复</button>
|
||||
</div>
|
||||
<div id="backup-list" class="backup-list"></div>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="supervisor-wechat-program" checked />
|
||||
程序止盈/止损也发微信(鼓励向)
|
||||
</label>
|
||||
<div class="settings-grid supervisor-settings-grid">
|
||||
<div class="field field-wide">
|
||||
<label>企业微信 Webhook</label>
|
||||
<input id="supervisor-wechat-webhook" type="text" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." autocomplete="off" />
|
||||
</div>
|
||||
<div class="field field-wide">
|
||||
<label>微信消息跳转链接(可改)</label>
|
||||
<input id="supervisor-wechat-link" type="text" placeholder="https://你的域名/ai?mode=supervisor" autocomplete="off" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>消息前缀</label>
|
||||
<input id="supervisor-wechat-prefix" type="text" value="【交易监管】" autocomplete="off" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>日手动平警告阈值</label>
|
||||
<input id="supervisor-daily-warn" type="number" min="1" step="1" value="2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>最短两笔间隔(分钟)</label>
|
||||
<input id="supervisor-interval-warn" type="number" min="1" step="1" value="15" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>30 分钟内笔数阈值</label>
|
||||
<input id="supervisor-freq-30m" type="number" min="1" step="1" value="2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>平仓后再开仓(分钟)</label>
|
||||
<input id="supervisor-reopen-min" type="number" min="1" step="1" value="30" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-panel hub-panel--4 hub-settings-tab-panel settings-backup-panel" data-settings-section="backup" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">备份与恢复</h3>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="backup">保存</button>
|
||||
</div>
|
||||
<p class="settings-display-hint">
|
||||
打包三所 <code>crypto.db</code>、中控 K 线/归档等 SQLite、<code>hub_settings.json</code> 与 <code>.env</code>(可选)。
|
||||
恢复前会自动做一次 pre-restore 快照,并尝试 <code>pm2 restart all</code>。
|
||||
</p>
|
||||
<div class="settings-grid backup-settings-grid">
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-auto-enabled" checked />
|
||||
每日自动备份(北京时间)
|
||||
</label>
|
||||
<div class="field">
|
||||
<label>自动备份时刻(时,0–23)</label>
|
||||
<input id="backup-auto-hour" type="number" min="0" max="23" step="1" value="0" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>保留天数</label>
|
||||
<input id="backup-retention-days" type="number" min="1" max="365" step="1" value="30" />
|
||||
</div>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-include-env" checked />
|
||||
包含 .env 配置文件
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="backup-include-images" />
|
||||
包含三所 static/images 截图
|
||||
</label>
|
||||
<div class="field field-wide">
|
||||
<label>备份目录(留空默认 /root/backups/crypto_monitor_portal)</label>
|
||||
<input id="backup-root" type="text" placeholder="/root/backups/crypto_monitor_portal" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-actions">
|
||||
<button type="button" id="backup-run-now" class="primary">立即备份</button>
|
||||
<span id="backup-status-line" class="backup-status-line"></span>
|
||||
</div>
|
||||
<div class="backup-restore-upload">
|
||||
<label class="backup-upload-label">
|
||||
<span>上传备份包恢复(.zip)</span>
|
||||
<input id="backup-restore-file" type="file" accept=".zip,application/zip" />
|
||||
</label>
|
||||
<button type="button" id="backup-restore-upload-btn" class="danger">上传并恢复</button>
|
||||
</div>
|
||||
<div id="backup-list" class="backup-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="hub-panel hub-panel--5 hub-settings-tab-panel" data-settings-section="exchanges" role="tabpanel">
|
||||
<div class="hub-settings-tab-head">
|
||||
<h3 class="hub-settings-tab-title">交易所账户</h3>
|
||||
<div class="hub-settings-tab-head-actions">
|
||||
<button type="button" id="btn-settings-add" class="ghost">添加交易所</button>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="exchanges">全部保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-list" class="settings-grid-wrap"></div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section card" data-settings-section="exchanges">
|
||||
<div class="settings-section-head">
|
||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
||||
<h3 class="settings-display-title">交易所账户</h3>
|
||||
<div class="settings-section-head-actions">
|
||||
<button type="button" id="btn-settings-add" class="ghost">添加交易所</button>
|
||||
<button type="button" class="primary settings-section-save" data-settings-section="exchanges">全部保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<div id="settings-list" class="settings-grid-wrap"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toolbar settings-page-toolbar">
|
||||
<button type="button" id="btn-settings-save" class="primary">保存全部</button>
|
||||
<button type="button" id="btn-settings-reload" class="ghost">重新加载</button>
|
||||
|
||||
Reference in New Issue
Block a user