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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user