diff --git a/admin/assets/app.js b/admin/assets/app.js index 88020b4..eae2a53 100644 --- a/admin/assets/app.js +++ b/admin/assets/app.js @@ -6,6 +6,7 @@ let currentUser = ""; let statsTimer = null, usersTimer = null, xrayTimer = null; let formCollapsed = true; let tlsForwardersState = []; +let managedTlsForwardersState = []; let editingXrayClientId = null; let wzInbounds = []; let dashboardCache = { sshUsers: [], xrayInbounds: [], me: null }; @@ -1387,6 +1388,8 @@ document.getElementById("testServerBtn")?.addEventListener("click", testServerFo document.getElementById("backToServersBtn")?.addEventListener("click", () => showServerListView()); document.getElementById("loadManagedConfigBtn")?.addEventListener("click", () => loadManagedServerConfig(configuringServerID)); document.getElementById("saveManagedConfigBtn")?.addEventListener("click", saveManagedServerConfig); +document.getElementById("saveManagedConfigBottomBtn")?.addEventListener("click", saveManagedServerConfig); +document.getElementById("reloadManagedConfigBottomBtn")?.addEventListener("click", () => loadManagedServerConfig(configuringServerID)); serverForm?.addEventListener("submit", async e => { e.preventDefault(); await saveServerForm(); @@ -1576,34 +1579,278 @@ function openManagedServerConfig(id) { loadManagedServerConfig(configuringServerID); } +function toggleManagedDnsttFields(on) { + const el = document.getElementById("managedDnsttFields"); + if (!el) return; + el.style.opacity = on ? "1" : ".4"; + el.style.pointerEvents = on ? "" : "none"; +} +function toggleManagedUdpgwFields(on) { + const el = document.getElementById("managedUdpgwFields"); + if (!el) return; + el.style.opacity = on ? "1" : ".4"; + el.style.pointerEvents = on ? "" : "none"; +} + async function loadManagedServerConfig(id) { if (!id) return; - managedConfigStatus.textContent = "Loading config…"; + const st = document.getElementById("managedConfigStatus"); + if (st) st.textContent = "Loading config…"; try { const res = await api(withServerParam("/api/servers/config", id)); if (!res.ok) throw new Error(await res.text()); - const text = await res.text(); - try { managedConfigEditor.value = JSON.stringify(JSON.parse(text), null, 2); } - catch { managedConfigEditor.value = text; } - managedConfigStatus.textContent = "Config loaded."; + const c = await res.json(); + + document.getElementById("managedCfgListen").value = c.listen || ""; + document.getElementById("managedCfgExtraListen").value = (c.extra_listen || []).join("\n"); + + document.getElementById("managedCfgLimitUp").value = c.default_limit_mbps_up || 0; + document.getElementById("managedCfgLimitDown").value = c.default_limit_mbps_down || 0; + document.getElementById("managedCfgQuiet").checked = !!c.quiet; + document.getElementById("managedCfgUserCount").checked = !!c.user_count; + document.getElementById("managedCfgBanner").value = c.banner || ""; + + const hasDnstt = !!c.dnstt; + document.getElementById("managedCfgDnsttEnabled").checked = hasDnstt; + toggleManagedDnsttFields(hasDnstt); + const d = c.dnstt || {}; + document.getElementById("managedCfgDnsttDomain").value = d.domain || ""; + document.getElementById("managedCfgDnsttUDP").value = d.udp_listen || ""; + document.getElementById("managedCfgDnsttKey").value = d.privkey_file || "/opt/sshpanel/dnstt.key"; + document.getElementById("managedCfgDnsttNoStats").checked = !!d.disable_stats_log; + document.getElementById("managedCfgDnsttNoConsole").checked = !!d.disable_console_log; + + const hasUdpgw = !!c.udpgw; + document.getElementById("managedCfgUdpgwEnabled").checked = hasUdpgw; + toggleManagedUdpgwFields(hasUdpgw); + const u = c.udpgw || {}; + document.getElementById("managedCfgUdpgwListen").value = u.listen || ""; + document.getElementById("managedCfgUdpgwMaxConns").value = u.max_client_conns || 0; + document.getElementById("managedCfgUdpgwIdle").value = u.idle_timeout || ""; + document.getElementById("managedCfgUdpgwMapTTL").value = u.map_ttl || ""; + document.getElementById("managedCfgUdpgwDebug").checked = !!u.debug; + + managedTlsForwardersState = c.tls_forwarders || []; + renderManagedTLSForwarders(); + + const x = c.xray || {}; + document.getElementById("managedCfgXrayEnabled").checked = !!x.enabled; + + document.getElementById("managedDnsttPubkeyWrap")?.classList.add("hidden"); + if (st) st.textContent = "Config loaded."; } catch (e) { if (e.message === "auth") doAuthError(); - else managedConfigStatus.textContent = "Error: " + e.message; + else if (st) st.textContent = "Error: " + e.message; } } +function managedConfigFromForm() { + const extraLines = document.getElementById("managedCfgExtraListen").value + .split("\n").map(s => s.trim()).filter(Boolean); + return { + listen: document.getElementById("managedCfgListen").value.trim(), + extra_listen: extraLines, + host_key_file: "/opt/sshpanel/ssh_host_rsa_key", + admin_dir: "/opt/sshpanel/admin", + default_limit_mbps_up: parseInt(document.getElementById("managedCfgLimitUp").value || "0", 10), + default_limit_mbps_down: parseInt(document.getElementById("managedCfgLimitDown").value || "0", 10), + quiet: document.getElementById("managedCfgQuiet").checked, + user_count: document.getElementById("managedCfgUserCount").checked, + banner: document.getElementById("managedCfgBanner").value, + banner_file: "/opt/sshpanel/banner.txt", + dnstt: document.getElementById("managedCfgDnsttEnabled").checked ? { + domain: document.getElementById("managedCfgDnsttDomain").value.trim(), + udp_listen: document.getElementById("managedCfgDnsttUDP").value.trim(), + privkey_file: document.getElementById("managedCfgDnsttKey").value.trim(), + disable_stats_log: document.getElementById("managedCfgDnsttNoStats").checked, + disable_console_log: document.getElementById("managedCfgDnsttNoConsole").checked, + } : null, + udpgw: document.getElementById("managedCfgUdpgwEnabled").checked ? { + listen: document.getElementById("managedCfgUdpgwListen").value.trim(), + max_client_conns: parseInt(document.getElementById("managedCfgUdpgwMaxConns").value || "0", 10), + idle_timeout: document.getElementById("managedCfgUdpgwIdle").value.trim(), + map_ttl: document.getElementById("managedCfgUdpgwMapTTL").value.trim(), + debug: document.getElementById("managedCfgUdpgwDebug").checked, + } : null, + tls_forwarders: managedTlsForwardersState, + xray: { + enabled: document.getElementById("managedCfgXrayEnabled").checked, + bin_path: "/opt/sshpanel/xray", + config_file: "/opt/sshpanel/xray_config.json", + api_server: "127.0.0.1:10085", + online_window_seconds: 90, + stats_poll_seconds: 15, + }, + }; +} + async function saveManagedServerConfig() { if (!configuringServerID) return; - const text = managedConfigEditor.value.trim(); - try { JSON.parse(text); } catch (e) { managedConfigStatus.textContent = "Invalid JSON: " + e.message; return; } - managedConfigStatus.textContent = "Saving config…"; + const st = document.getElementById("managedConfigStatus"); + if (st) st.textContent = "Saving config…"; try { - const res = await api(withServerParam("/api/servers/config", configuringServerID), { method:"POST", body: text }); + const cfg = managedConfigFromForm(); + const res = await api(withServerParam("/api/servers/config", configuringServerID), { method:"POST", body: JSON.stringify(cfg) }); if (!res.ok) throw new Error(await res.text()); - managedConfigStatus.textContent = "Saved and applied."; + const report = await res.json().catch(() => null); + const warnings = report?.warnings || []; + const bad = Object.entries(report?.services || {}).filter(([_, v]) => v?.enabled && !v?.running); + if (warnings.length || bad.length) { + const badText = bad.map(([name, v]) => `${name}: ${v.error || "not running"}`).join(" | "); + if (st) st.textContent = "Saved live with warnings: " + [...warnings, badText].filter(Boolean).join(" | "); + } else if (st) { + st.textContent = "Saved and applied live."; + } } catch (e) { if (e.message === "auth") doAuthError(); - else managedConfigStatus.textContent = "Error: " + e.message; + else if (st) st.textContent = "Error: " + e.message; + } +} + +function renderManagedTLSForwarders() { + const list = document.getElementById("managedTlsForwardersList"); + const chip = document.getElementById("managedTlsCountChip"); + if (!list) return; + if (chip) chip.textContent = managedTlsForwardersState.length; + if (!managedTlsForwardersState.length) { + list.innerHTML = '