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:
|
async def _fetch_agent_status(client: httpx.AsyncClient, ex: dict) -> dict:
|
||||||
url = f"{ex['agent_url'].rstrip('/')}/status"
|
url = f"{ex['agent_url'].rstrip('/')}/status"
|
||||||
try:
|
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;
|
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 {
|
.settings-section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4168,6 +4168,8 @@
|
|||||||
const parts = [];
|
const parts = [];
|
||||||
if (m.password_required) parts.push("已启用用户名+密码登录");
|
if (m.password_required) parts.push("已启用用户名+密码登录");
|
||||||
else parts.push("未设 HUB_PASSWORD(反代公网暴露时建议设置 HUB_USERNAME + HUB_PASSWORD)");
|
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");
|
if (m.hub_bridge_token_set) parts.push("中控已配置 HUB_BRIDGE_TOKEN");
|
||||||
else parts.push("中控未设 HUB_BRIDGE_TOKEN(实例需 APP_AUTH_DISABLED 或同令牌)");
|
else parts.push("中控未设 HUB_BRIDGE_TOKEN(实例需 APP_AUTH_DISABLED 或同令牌)");
|
||||||
if (m.public_origin) parts.push("浏览器外链基址: " + m.public_origin);
|
if (m.public_origin) parts.push("浏览器外链基址: " + m.public_origin);
|
||||||
@@ -4258,21 +4260,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initSettingsSectionFolds() {
|
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) => {
|
document.querySelectorAll(".settings-section-save").forEach((btn) => {
|
||||||
if (btn.dataset.saveBound === "1") return;
|
if (btn.dataset.saveBound === "1") return;
|
||||||
btn.dataset.saveBound = "1";
|
btn.dataset.saveBound = "1";
|
||||||
@@ -4290,12 +4277,76 @@
|
|||||||
? "交易监管"
|
? "交易监管"
|
||||||
: section === "exchanges"
|
: section === "exchanges"
|
||||||
? "交易所账户"
|
? "交易所账户"
|
||||||
|
: section === "backup"
|
||||||
|
? "备份设置"
|
||||||
: "设置";
|
: "设置";
|
||||||
|
if (section === "backup") return;
|
||||||
void saveSettingsSection(section, { label });
|
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) {
|
function macroDatetimeLocalToApi(v) {
|
||||||
if (!v) return "";
|
if (!v) return "";
|
||||||
return String(v).trim().replace("T", " ").slice(0, 16);
|
return String(v).trim().replace("T", " ").slice(0, 16);
|
||||||
@@ -4439,6 +4490,7 @@
|
|||||||
function loadSettingsUI() {
|
function loadSettingsUI() {
|
||||||
loadSettingsMetaLine();
|
loadSettingsMetaLine();
|
||||||
initMacroCalendarSettings();
|
initMacroCalendarSettings();
|
||||||
|
initHubPasswordSettings();
|
||||||
loadMacroCalendarUI();
|
loadMacroCalendarUI();
|
||||||
loadSettings().then((data) => {
|
loadSettings().then((data) => {
|
||||||
syncDisplayPrefsUI(data);
|
syncDisplayPrefsUI(data);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<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'" />
|
<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>
|
<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/trade_stats_calendar.css?v=3" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
@@ -885,24 +885,56 @@
|
|||||||
<div id="page-settings" class="page hidden">
|
<div id="page-settings" class="page hidden">
|
||||||
<div class="page-head">
|
<div class="page-head">
|
||||||
<h1><span class="head-tag">CFG</span> 系统设置</h1>
|
<h1><span class="head-tag">CFG</span> 系统设置</h1>
|
||||||
<p class="page-desc">交易所地址、启用状态与监控能力</p>
|
<p class="page-desc">登录账号、导航显示、交易所地址与监控能力</p>
|
||||||
</div>
|
</div>
|
||||||
<details class="hint-box">
|
<details class="hint-box">
|
||||||
<summary>配置说明</summary>
|
<summary>配置说明</summary>
|
||||||
<div class="hint-body">
|
<div class="hint-body">
|
||||||
保存后写入 <code>hub_settings.json</code>。Flask / Agent 填本机地址即可;复盘链接可留空(由 Flask 地址自动生成)。<br />
|
交易所等配置保存后写入 <code>hub_settings.json</code>;登录账号密码写入 <code>manual_trading_hub/.env</code>。<br />
|
||||||
<code>HUB_DISABLED_IDS</code> 可强制关闭账户;<code>HUB_BRIDGE_TOKEN</code> 与实例一致,或实例 <code>APP_AUTH_DISABLED=true</code>。<br />
|
Flask / Agent 填本机地址即可;复盘链接可留空(由 Flask 地址自动生成)。<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_DISABLED_IDS</code> 可强制关闭账户;<code>HUB_BRIDGE_TOKEN</code> 与实例一致,或实例 <code>APP_AUTH_DISABLED=true</code>。
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<p id="settings-meta-line" class="settings-meta-line"></p>
|
<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">
|
<div class="hub-config-body card">
|
||||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
<input type="radio" name="hub-settings-section" id="hub-sec-0" class="hub-tab-radio" checked>
|
||||||
<h3 class="settings-display-title">显示与导航</h3>
|
<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="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>
|
||||||
|
<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>
|
<button type="button" class="primary settings-section-save" data-settings-section="display">保存</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-body">
|
|
||||||
<label class="chk-label settings-display-chk">
|
<label class="chk-label settings-display-chk">
|
||||||
<input type="checkbox" id="pref-show-account-pnl" checked />
|
<input type="checkbox" id="pref-show-account-pnl" checked />
|
||||||
监控区显示资金账户、交易账户与浮动盈亏
|
监控区显示资金账户、交易账户与浮动盈亏
|
||||||
@@ -936,15 +968,13 @@
|
|||||||
顶栏显示「策略说明」
|
顶栏显示「策略说明」
|
||||||
</label>
|
</label>
|
||||||
<p class="settings-display-hint">保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。</p>
|
<p class="settings-display-hint">保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。</p>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section card settings-macro-panel" data-settings-section="macro">
|
|
||||||
<div class="settings-section-head">
|
<section class="hub-panel hub-panel--2 hub-settings-tab-panel settings-macro-panel" data-settings-section="macro" role="tabpanel">
|
||||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
<div class="hub-settings-tab-head">
|
||||||
<h3 class="settings-display-title">宏观关键数据(风控前置)</h3>
|
<h3 class="hub-settings-tab-title">宏观关键数据(风控前置)</h3>
|
||||||
<button type="button" class="ghost settings-section-save" data-settings-section="macro">保存表单</button>
|
<button type="button" class="ghost settings-section-save" data-settings-section="macro">保存表单</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-body">
|
|
||||||
<p class="settings-display-hint">
|
<p class="settings-display-hint">
|
||||||
手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。
|
手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。
|
||||||
</p>
|
</p>
|
||||||
@@ -971,15 +1001,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id="macro-event-list" class="macro-event-list"></div>
|
<div id="macro-event-list" class="macro-event-list"></div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section card settings-supervisor-panel" data-settings-section="supervisor">
|
|
||||||
<div class="settings-section-head">
|
<section class="hub-panel hub-panel--3 hub-settings-tab-panel settings-supervisor-panel" data-settings-section="supervisor" role="tabpanel">
|
||||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
<div class="hub-settings-tab-head">
|
||||||
<h3 class="settings-display-title">交易监管 · 企业微信</h3>
|
<h3 class="hub-settings-tab-title">交易监管 · 企业微信</h3>
|
||||||
<button type="button" class="primary settings-section-save" data-settings-section="supervisor">保存</button>
|
<button type="button" class="primary settings-section-save" data-settings-section="supervisor">保存</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-body">
|
|
||||||
<p class="settings-display-hint">
|
<p class="settings-display-hint">
|
||||||
与三所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。
|
与三所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。
|
||||||
</p>
|
</p>
|
||||||
@@ -1021,15 +1049,13 @@
|
|||||||
<input id="supervisor-reopen-min" type="number" min="1" step="1" value="30" />
|
<input id="supervisor-reopen-min" type="number" min="1" step="1" value="30" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section card settings-backup-panel" data-settings-section="backup">
|
|
||||||
<div class="settings-section-head">
|
<section class="hub-panel hub-panel--4 hub-settings-tab-panel settings-backup-panel" data-settings-section="backup" role="tabpanel">
|
||||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
<div class="hub-settings-tab-head">
|
||||||
<h3 class="settings-display-title">备份与恢复</h3>
|
<h3 class="hub-settings-tab-title">备份与恢复</h3>
|
||||||
<button type="button" class="primary settings-section-save" data-settings-section="backup">保存</button>
|
<button type="button" class="primary settings-section-save" data-settings-section="backup">保存</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-body">
|
|
||||||
<p class="settings-display-hint">
|
<p class="settings-display-hint">
|
||||||
打包三所 <code>crypto.db</code>、中控 K 线/归档等 SQLite、<code>hub_settings.json</code> 与 <code>.env</code>(可选)。
|
打包三所 <code>crypto.db</code>、中控 K 线/归档等 SQLite、<code>hub_settings.json</code> 与 <code>.env</code>(可选)。
|
||||||
恢复前会自动做一次 pre-restore 快照,并尝试 <code>pm2 restart all</code>。
|
恢复前会自动做一次 pre-restore 快照,并尝试 <code>pm2 restart all</code>。
|
||||||
@@ -1072,21 +1098,21 @@
|
|||||||
<button type="button" id="backup-restore-upload-btn" class="danger">上传并恢复</button>
|
<button type="button" id="backup-restore-upload-btn" class="danger">上传并恢复</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="backup-list" class="backup-list"></div>
|
<div id="backup-list" class="backup-list"></div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section card" data-settings-section="exchanges">
|
|
||||||
<div class="settings-section-head">
|
<section class="hub-panel hub-panel--5 hub-settings-tab-panel" data-settings-section="exchanges" role="tabpanel">
|
||||||
<button type="button" class="settings-section-fold" aria-expanded="true" aria-label="折叠"></button>
|
<div class="hub-settings-tab-head">
|
||||||
<h3 class="settings-display-title">交易所账户</h3>
|
<h3 class="hub-settings-tab-title">交易所账户</h3>
|
||||||
<div class="settings-section-head-actions">
|
<div class="hub-settings-tab-head-actions">
|
||||||
<button type="button" id="btn-settings-add" class="ghost">添加交易所</button>
|
<button type="button" id="btn-settings-add" class="ghost">添加交易所</button>
|
||||||
<button type="button" class="primary settings-section-save" data-settings-section="exchanges">全部保存</button>
|
<button type="button" class="primary settings-section-save" data-settings-section="exchanges">全部保存</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-body">
|
|
||||||
<div id="settings-list" class="settings-grid-wrap"></div>
|
<div id="settings-list" class="settings-grid-wrap"></div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toolbar settings-page-toolbar">
|
<div class="toolbar settings-page-toolbar">
|
||||||
<button type="button" id="btn-settings-save" class="primary">保存全部</button>
|
<button type="button" id="btn-settings-save" class="primary">保存全部</button>
|
||||||
<button type="button" id="btn-settings-reload" class="ghost">重新加载</button>
|
<button type="button" id="btn-settings-reload" class="ghost">重新加载</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user