/** * 实例:导航显示,env 配置,改密,PM2 重启. */ (function (global) { const DISPLAY = () => global.__INSTANCE_DISPLAY__ || {}; function setStatus(el, text, isErr) { if (!el) return; el.textContent = text || ""; el.classList.toggle("err", !!isErr); } async function fetchJson(url, opts) { const res = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {})); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.msg || res.statusText || "请求失败"); } return data; } /** 默认关闭的导航开关:缺失时按 false,不能用 !== false */ const NAV_DEFAULT_OFF = { show_nav_dashboard: true, show_nav_system_guide: true }; function navPrefShow(display, key) { if (!key) return true; if (NAV_DEFAULT_OFF[key]) return display[key] === true; return display[key] !== false; } function applyDisplayToNav(display) { const map = { dashboard: "show_nav_dashboard", strategy: "show_nav_strategy", strategy_records: "show_nav_strategy_records", records: "show_nav_records", stats: "show_nav_stats", options: "show_nav_options", "options-review": "show_nav_options_review", options_review: "show_nav_options_review", "hedge-plan": "show_nav_hedge_plan", hedge_plan: "show_nav_hedge_plan", risk_policy: "show_nav_risk_policy", system_guide: "show_nav_system_guide", env_config: "show_nav_env_config", }; document.querySelectorAll(".embed-top-nav [data-embed-tab], .top-nav a[href^='/']").forEach((a) => { const tab = a.getAttribute("data-embed-tab") || (a.getAttribute("href") || "").replace(/^\//, "").split("?")[0]; const key = map[tab]; if (!key) return; const show = navPrefShow(display, key); a.classList.toggle("nav-hidden", !show); a.style.display = show ? "" : "none"; }); global.__INSTANCE_DISPLAY__ = display; } function pageNavAllowed(tab) { const d = DISPLAY(); const map = { dashboard: "show_nav_dashboard", strategy: "show_nav_strategy", strategy_records: "show_nav_strategy_records", records: "show_nav_records", stats: "show_nav_stats", options: "show_nav_options", "options-review": "show_nav_options_review", options_review: "show_nav_options_review", "hedge-plan": "show_nav_hedge_plan", hedge_plan: "show_nav_hedge_plan", risk_policy: "show_nav_risk_policy", system_guide: "show_nav_system_guide", env_config: "show_nav_env_config", }; const key = map[tab]; if (!key) return true; return navPrefShow(d, key); } function displayPrefsRoot() { const settingsPane = document.querySelector('.embed-tab-pane[data-embed-pane="settings"]'); if (settingsPane) { const inSettings = settingsPane.querySelector("#display-prefs-form"); if (inSettings) return inSettings; } const pane = document.querySelector(".embed-tab-pane.is-active-pane"); if (pane) { const inPane = pane.querySelector("#display-prefs-form"); if (inPane) return inPane; } return document.getElementById("display-prefs-form"); } function displayPrefsStatusEl() { const card = document.getElementById("display-prefs-card"); if (card) { const el = card.querySelector("#display-prefs-status"); if (el) return el; } return document.getElementById("display-prefs-status"); } function envConfigRoot() { const activePane = document.querySelector(".embed-tab-pane.is-active-pane"); if (activePane) { return activePane.querySelector(".env-config-page"); } return document.querySelector(".env-config-page"); } function bindEnvTabs() { /* Tab 切换由 CSS radio+label 实现 */ } async function loadDisplayPrefsForm(force) { const root = displayPrefsRoot(); if (!root) return; if (!force && root.getAttribute("data-prefs-ssr") === "1" && root.querySelector("[data-pref-key]")) { return; } return loadDisplayPrefsFormIn(root); } async function loadDisplayPrefsFormIn(root) { try { const data = await fetchJson("/api/settings/display"); const display = data.display || {}; const meta = data.meta || []; root.innerHTML = ""; meta.forEach((group) => { const section = document.createElement("div"); section.className = "display-prefs-group"; const title = document.createElement("h3"); title.className = "settings-subcard-title"; title.textContent = group.group; section.appendChild(title); const grid = document.createElement("div"); grid.className = "display-prefs-checks"; (group.entries || []).forEach((item) => { const label = document.createElement("label"); label.className = "chk-label"; const cb = document.createElement("input"); cb.type = "checkbox"; cb.dataset.prefKey = item.key; cb.checked = NAV_DEFAULT_OFF[item.key] ? display[item.key] === true : display[item.key] !== false; label.appendChild(cb); label.appendChild(document.createTextNode(" " + item.label)); grid.appendChild(label); }); section.appendChild(grid); root.appendChild(section); }); root.setAttribute("data-prefs-ssr", "1"); } catch (e) { root.innerHTML = '' + (e.message || "加载失败") + ""; } } async function saveDisplayPrefs() { const status = displayPrefsStatusEl(); const root = displayPrefsRoot(); if (!root) { setStatus(status, "未找到导航设置表单", true); return; } const display = {}; root.querySelectorAll("input[data-pref-key]").forEach((cb) => { display[cb.dataset.prefKey] = !!cb.checked; }); try { const data = await fetchJson("/api/settings/display", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ display }), }); applyDisplayToNav(data.display || display); setStatus(status, "已保存,导航已更新"); } catch (e) { setStatus(status, e.message || "保存失败", true); } } let envSchemaGroups = []; function renderEnvFieldRow(field) { const row = document.createElement("div"); row.className = "env-field-row" + (field.restart_required ? " env-field-row--restart" : ""); const label = document.createElement("label"); label.className = "env-field-label"; label.htmlFor = "env-f-" + field.key; label.textContent = field.label || field.key; if (field.restart_required) { const mark = document.createElement("span"); mark.className = "env-restart-mark"; mark.title = "需重启"; mark.textContent = "*"; label.appendChild(mark); } row.appendChild(label); if (field.note) { const note = document.createElement("div"); note.className = "env-field-note muted"; note.textContent = field.note; row.appendChild(note); } let input; if (field.type === "bool") { input = document.createElement("select"); input.id = "env-f-" + field.key; [["true", "开启"], ["false", "关闭"]].forEach(([v, text]) => { const o = document.createElement("option"); o.value = v; o.textContent = text; input.appendChild(o); }); const cur = (field.current || field.default || "false").toLowerCase(); input.value = cur === "true" || cur === "1" ? "true" : "false"; } else if (field.type === "select" && Array.isArray(field.options) && field.options.length) { input = document.createElement("select"); input.id = "env-f-" + field.key; const cur = String(field.current || field.default || ""); const seen = new Set(); field.options.forEach((opt) => { const v = String(opt.value != null ? opt.value : ""); if (seen.has(v)) return; seen.add(v); const o = document.createElement("option"); o.value = v; o.textContent = opt.label || v; input.appendChild(o); }); if (cur && !seen.has(cur)) { const o = document.createElement("option"); o.value = cur; o.textContent = cur; input.insertBefore(o, input.firstChild); } input.value = cur || (field.options[0] && field.options[0].value) || ""; } else { input = document.createElement("input"); input.id = "env-f-" + field.key; input.type = "password"; // 防止浏览器把登录密码自动填进 API Key/Secret(保存对冲开关时曾误写入密钥) input.autocomplete = "new-password"; input.setAttribute("data-lpignore", "true"); input.setAttribute("data-1p-ignore", "true"); input.setAttribute("data-form-type", "other"); input.readOnly = true; input.addEventListener("focus", function () { input.readOnly = false; }); if (field.sensitive) { input.dataset.envSensitive = "1"; input.dataset.envDirty = "0"; input.addEventListener("input", function () { input.dataset.envDirty = "1"; }); if (field.has_value) { const cur = document.createElement("div"); cur.className = "env-sensitive-current muted"; const labelSpan = document.createElement("span"); labelSpan.textContent = "已配置 "; const masked = document.createElement("span"); masked.className = "env-masked-value"; masked.textContent = field.masked || ""; cur.appendChild(labelSpan); cur.appendChild(masked); row.appendChild(cur); } input.placeholder = field.has_value ? "修改时填写新值,留空不修改" : "请输入"; } else { input.type = "text"; input.autocomplete = "off"; input.value = field.current || field.default || ""; } } input.dataset.envKey = field.key; input.className = "env-field-input"; row.appendChild(input); return row; } function renderEnvConfigBody(groups) { const body = document.createElement("div"); body.className = "env-config-body card"; body.id = "env-config-body"; body.setAttribute("data-env-ssr", "1"); groups.forEach((_group, idx) => { const radio = document.createElement("input"); radio.type = "radio"; radio.name = "env-section"; radio.id = "env-sec-" + idx; radio.className = "env-tab-radio"; if (idx === 0) radio.checked = true; body.appendChild(radio); }); const tabBar = document.createElement("div"); tabBar.className = "env-config-tabs"; tabBar.setAttribute("role", "tablist"); const panelsWrap = document.createElement("div"); panelsWrap.className = "env-config-panels"; panelsWrap.id = "env-config-grid"; groups.forEach((group, idx) => { const label = document.createElement("label"); label.className = "env-tab-btn"; label.htmlFor = "env-sec-" + idx; label.setAttribute("role", "tab"); label.textContent = group.title || "其他"; tabBar.appendChild(label); const panel = document.createElement("section"); panel.className = "env-panel env-panel--" + idx; panel.setAttribute("role", "tabpanel"); if (group.has_restart) { const hint = document.createElement("p"); hint.className = "env-panel-hint"; hint.textContent = "本组含需重启项,修改后请点「保存并重启」."; panel.appendChild(hint); } const grid = document.createElement("div"); grid.className = "env-form-grid"; (group.fields || []).forEach((field) => grid.appendChild(renderEnvFieldRow(field))); panel.appendChild(grid); panelsWrap.appendChild(panel); }); body.appendChild(tabBar); body.appendChild(panelsWrap); return body; } async function loadEnvConfig(force) { const root = envConfigRoot(); const body = root && root.querySelector("#env-config-body"); if (!force && body && body.getAttribute("data-env-ssr") === "1" && body.querySelector("[data-env-key]")) { return; } return loadEnvConfigIn(root); } async function loadEnvConfigIn(root) { const page = root || envConfigRoot() || document.querySelector(".env-config-page"); if (!page) return; const loading = document.createElement("div"); loading.className = "env-config-loading-wrap card"; loading.id = "env-config-body"; loading.innerHTML = '