FIx mult panel server
This commit is contained in:
@@ -402,3 +402,192 @@ body.drawer-open .drawer-backdrop,body.sidebar-open .drawer-backdrop{display:blo
|
||||
table{font-size:.76rem;}
|
||||
th,td{padding:9px 10px;}
|
||||
}
|
||||
|
||||
/* --- UI polish fixes for servers page / sidebar / language selector --- */
|
||||
@media(min-width:901px){
|
||||
.panel-layout{align-items:start;}
|
||||
.sidebar{align-self:start; position:sticky; top:18px;}
|
||||
}
|
||||
|
||||
/* Keep the sidebar visible while long pages scroll */
|
||||
.sidebar{
|
||||
overflow:hidden;
|
||||
}
|
||||
.side-nav{
|
||||
overscroll-behavior:contain;
|
||||
}
|
||||
|
||||
/* Better top alignment for paired cards */
|
||||
.grid2,
|
||||
.servers-grid{
|
||||
align-items:start;
|
||||
}
|
||||
.grid2 > .card,
|
||||
.grid2 > div,
|
||||
.servers-grid > .card,
|
||||
.servers-grid > div{
|
||||
align-self:start;
|
||||
margin-top:0 !important;
|
||||
}
|
||||
|
||||
/* Server form checkbox rows should align visually with the input fields */
|
||||
.server-form-grid{
|
||||
align-items:start;
|
||||
}
|
||||
.server-form-grid > .toggle-field{
|
||||
min-height:44px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
padding:0 12px;
|
||||
border:1px solid var(--line);
|
||||
border-radius:14px;
|
||||
background:linear-gradient(180deg,var(--input-bg),#06090f);
|
||||
color:var(--text-2);
|
||||
font-size:.8rem;
|
||||
font-weight:800;
|
||||
cursor:pointer;
|
||||
}
|
||||
.server-form-grid > .toggle-field input{
|
||||
flex:0 0 auto;
|
||||
}
|
||||
|
||||
/* Language selector dark theme fix */
|
||||
.language-select{
|
||||
color:var(--text);
|
||||
background:linear-gradient(180deg,rgba(17,23,32,.94),rgba(10,14,21,.98));
|
||||
border-color:rgba(148,163,184,.18);
|
||||
}
|
||||
.language-select:hover,
|
||||
.language-select:focus{
|
||||
border-color:rgba(34,211,238,.42);
|
||||
box-shadow:0 0 0 3px rgba(34,211,238,.10), 0 0 22px rgba(34,211,238,.08);
|
||||
}
|
||||
.language-select option,
|
||||
.language-select optgroup{
|
||||
background:#0d1118;
|
||||
color:#f3f7ff;
|
||||
}
|
||||
|
||||
/* Small visual consistency improvements */
|
||||
.topbar-actions{
|
||||
align-items:center;
|
||||
}
|
||||
.card-hdr{
|
||||
align-items:flex-start;
|
||||
}
|
||||
.card-hdr > .card-actions{
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
|
||||
/* --- sidebar follow-scroll fix --- */
|
||||
@media(min-width:901px){
|
||||
.panel-layout{
|
||||
display:block;
|
||||
padding:18px;
|
||||
}
|
||||
.sidebar{
|
||||
position:fixed !important;
|
||||
top:18px !important;
|
||||
left:18px !important;
|
||||
bottom:auto !important;
|
||||
width:300px !important;
|
||||
height:calc(100vh - 36px) !important;
|
||||
max-height:calc(100vh - 36px) !important;
|
||||
z-index:30;
|
||||
}
|
||||
@supports (height:100dvh){
|
||||
.sidebar{
|
||||
height:calc(100dvh - 36px) !important;
|
||||
max-height:calc(100dvh - 36px) !important;
|
||||
}
|
||||
}
|
||||
.workspace{
|
||||
margin-left:336px !important;
|
||||
min-height:calc(100vh - 36px);
|
||||
}
|
||||
@supports (min-height:100dvh){
|
||||
.workspace{min-height:calc(100dvh - 36px);}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- Servers status page --- */
|
||||
.servers-status-toolbar{margin-bottom:16px;}
|
||||
.servers-status-grid{
|
||||
display:grid;
|
||||
grid-template-columns:repeat(auto-fit,minmax(330px,1fr));
|
||||
gap:16px;
|
||||
align-items:start;
|
||||
}
|
||||
.server-status-card{
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
border:1px solid rgba(148,163,184,.12);
|
||||
border-radius:24px;
|
||||
padding:16px;
|
||||
background:
|
||||
radial-gradient(circle at 90% 0%,rgba(255,255,255,.08),transparent 34%),
|
||||
linear-gradient(180deg,rgba(16,22,32,.95),rgba(8,12,18,.98));
|
||||
box-shadow:0 20px 58px rgba(0,0,0,.26),inset 0 1px 0 rgba(255,255,255,.025);
|
||||
}
|
||||
.server-status-card::after{
|
||||
content:"";
|
||||
position:absolute;
|
||||
right:-38px;
|
||||
bottom:-58px;
|
||||
width:150px;
|
||||
height:150px;
|
||||
border-radius:999px;
|
||||
background:rgba(34,211,238,.12);
|
||||
pointer-events:none;
|
||||
}
|
||||
.server-status-offline{opacity:.72;}
|
||||
.server-status-offline::after{background:rgba(255,91,105,.11);}
|
||||
.server-status-head{
|
||||
position:relative;
|
||||
z-index:1;
|
||||
display:flex;
|
||||
align-items:flex-start;
|
||||
justify-content:space-between;
|
||||
gap:12px;
|
||||
margin-bottom:12px;
|
||||
}
|
||||
.server-status-title{font-size:1rem;font-weight:950;color:var(--text);line-height:1.1;}
|
||||
.server-status-url{margin-top:5px;color:var(--muted);font-size:.72rem;font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace;word-break:break-all;}
|
||||
.server-status-badges{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap;}
|
||||
.server-status-error{position:relative;z-index:1;margin-bottom:10px;color:#ffc6cc;font-size:.76rem;}
|
||||
.server-mini-grid{
|
||||
position:relative;
|
||||
z-index:1;
|
||||
display:grid;
|
||||
grid-template-columns:repeat(2,minmax(0,1fr));
|
||||
gap:10px;
|
||||
}
|
||||
.server-mini-metric{
|
||||
min-width:0;
|
||||
border:1px solid rgba(148,163,184,.11);
|
||||
border-radius:18px;
|
||||
padding:12px;
|
||||
background:rgba(255,255,255,.035);
|
||||
}
|
||||
.server-mini-label{color:var(--muted);font-size:.66rem;text-transform:uppercase;letter-spacing:.14em;font-weight:900;}
|
||||
.server-mini-value{margin-top:6px;font-size:1.28rem;line-height:1.05;font-weight:950;color:var(--text);letter-spacing:-.04em;}
|
||||
.server-mini-note{margin-top:5px;min-height:15px;color:var(--muted);font-size:.7rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.server-mini-bar{height:7px;margin-top:9px;border-radius:999px;background:rgba(148,163,184,.12);overflow:hidden;}
|
||||
.server-mini-bar span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--accent),var(--accent-3));box-shadow:0 0 18px rgba(34,211,238,.24);transition:width .25s ease;}
|
||||
.server-status-footer{
|
||||
position:relative;
|
||||
z-index:1;
|
||||
display:grid;
|
||||
gap:5px;
|
||||
margin-top:12px;
|
||||
color:var(--muted);
|
||||
font-size:.72rem;
|
||||
line-height:1.35;
|
||||
}
|
||||
@media(max-width:640px){
|
||||
.servers-status-grid{grid-template-columns:1fr;}
|
||||
.server-mini-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
@@ -308,6 +308,9 @@ const serverConfigSubpage = document.getElementById("serverConfigSubpage");
|
||||
const cfgServerName = document.getElementById("cfgServerName");
|
||||
const managedConfigEditor = document.getElementById("managedConfigEditor");
|
||||
const managedConfigStatus = document.getElementById("managedConfigStatus");
|
||||
const serversStatusGrid = document.getElementById("serversStatusGrid");
|
||||
const serversStatusPageStatus = document.getElementById("serversStatusPageStatus");
|
||||
const serversStatusCountChip = document.getElementById("serversStatusCountChip");
|
||||
|
||||
// Stats
|
||||
const cpuVal = document.getElementById("cpuVal");
|
||||
@@ -491,6 +494,7 @@ const tabTitles = {
|
||||
xray: ["Accounts", "Xray Users"],
|
||||
resellers: ["Administration", "Resellers"],
|
||||
servers: ["Administration", "Servers"],
|
||||
"servers-status": ["Administration", "Servers Status"],
|
||||
stats: ["Server", "Monitoring"],
|
||||
vnstat: ["Traffic", "VnStat"],
|
||||
logs: ["System", "Logs"],
|
||||
@@ -521,6 +525,7 @@ function selectTab(tab) {
|
||||
if (currentRole === "superadmin") loadWizardFromConfig();
|
||||
}
|
||||
if (tab === "stats" && currentRole === "superadmin") loadStats();
|
||||
if (tab === "servers-status" && currentRole === "superadmin") loadServersStatus();
|
||||
if (tab === "resellers" && currentRole === "superadmin") loadResellers();
|
||||
if (tab === "servers" && currentRole === "superadmin") loadServers();
|
||||
}
|
||||
@@ -612,6 +617,7 @@ function initAfterLogin() {
|
||||
statsTimer = setInterval(() => {
|
||||
loadDashboardStats();
|
||||
if (currentTab === "stats") loadStats();
|
||||
if (currentTab === "servers-status") loadServersStatus({ silent: true });
|
||||
}, 2000);
|
||||
} else {
|
||||
loadMe();
|
||||
@@ -1382,7 +1388,9 @@ xrayServerSelect?.addEventListener("change", () => {
|
||||
document.getElementById("reloadServersBtn")?.addEventListener("click", loadServers);
|
||||
document.getElementById("reloadServersBtn2")?.addEventListener("click", loadServers);
|
||||
document.getElementById("refreshServersBtn")?.addEventListener("click", loadServers);
|
||||
document.getElementById("refreshServersStatusBtn")?.addEventListener("click", () => loadServersStatus());
|
||||
document.querySelector("[data-tab='servers']")?.addEventListener("click", loadServers);
|
||||
document.querySelector("[data-tab='servers-status']")?.addEventListener("click", () => loadServersStatus());
|
||||
document.getElementById("clearServerFormBtn")?.addEventListener("click", clearServerForm);
|
||||
document.getElementById("testServerBtn")?.addEventListener("click", testServerForm);
|
||||
document.getElementById("backToServersBtn")?.addEventListener("click", () => showServerListView());
|
||||
@@ -1409,6 +1417,135 @@ async function loadServers() {
|
||||
renderServersTable();
|
||||
}
|
||||
|
||||
|
||||
async function loadServersStatus(options = {}) {
|
||||
const silent = !!options.silent;
|
||||
if (!serversStatusGrid) return;
|
||||
try {
|
||||
if (!Array.isArray(serversCache) || serversCache.length === 0) await loadServers();
|
||||
const nodes = (serversCache || []).filter(s => s && s.is_active !== false);
|
||||
if (serversStatusCountChip) serversStatusCountChip.textContent = String(nodes.length);
|
||||
if (!silent) {
|
||||
serversStatusPageStatus && (serversStatusPageStatus.textContent = "Loading server usage...");
|
||||
serversStatusGrid.innerHTML = `<div class="hint">Loading nodes...</div>`;
|
||||
}
|
||||
const rows = await Promise.all(nodes.map(loadSingleServerStatus));
|
||||
renderServersStatusCards(rows);
|
||||
if (serversStatusPageStatus) {
|
||||
const online = rows.filter(r => r.ok).length;
|
||||
serversStatusPageStatus.textContent = `${online}/${rows.length} nodes online - Updated ${new Date().toLocaleTimeString()}`;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message === "auth") doAuthError();
|
||||
else {
|
||||
serversStatusPageStatus && (serversStatusPageStatus.textContent = "Error loading server status: " + e.message);
|
||||
if (!silent) serversStatusGrid.innerHTML = `<div class="hint">Error loading server status.</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJSONForServer(path, serverID) {
|
||||
const res = await api(withServerParam(path, serverID));
|
||||
if (!res.ok) throw new Error((await res.text()).trim() || `HTTP ${res.status}`);
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async function loadSingleServerStatus(server) {
|
||||
const id = String(server.id || "local");
|
||||
const out = { server, ok: true, error: "", stats: null, users: [], inbounds: [], xray: null };
|
||||
try {
|
||||
out.stats = await fetchJSONForServer("/api/stats", id);
|
||||
} catch (e) {
|
||||
out.ok = false;
|
||||
out.error = e.message || "stats failed";
|
||||
}
|
||||
if (server.enable_ssh || server.is_local) {
|
||||
try { out.users = await fetchJSONForServer("/api/users", id) || []; }
|
||||
catch (e) { out.usersError = e.message || "users failed"; }
|
||||
}
|
||||
if (server.enable_xray || server.is_local) {
|
||||
try { out.xray = await fetchJSONForServer("/api/xray/status", id); }
|
||||
catch (e) { out.xrayError = e.message || "xray status failed"; }
|
||||
try { out.inbounds = await fetchJSONForServer("/api/xray/inbounds", id) || []; }
|
||||
catch (e) { out.inboundsError = e.message || "xray clients failed"; }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderServersStatusCards(rows = []) {
|
||||
if (!serversStatusGrid) return;
|
||||
if (!rows.length) {
|
||||
serversStatusGrid.innerHTML = `<div class="hint">No active servers configured.</div>`;
|
||||
return;
|
||||
}
|
||||
serversStatusGrid.innerHTML = rows.map(serverStatusCardHTML).join("");
|
||||
}
|
||||
|
||||
function serverStatusCardHTML(row) {
|
||||
const s = row.server || {};
|
||||
const stats = row.stats || {};
|
||||
const ifaces = Array.isArray(stats.interfaces) ? stats.interfaces : [];
|
||||
let rx = 0, tx = 0, rxTotal = 0, txTotal = 0;
|
||||
ifaces.forEach(it => {
|
||||
rx += Number(it.rx_mbps || 0);
|
||||
tx += Number(it.tx_mbps || 0);
|
||||
rxTotal += Number(it.rx_bytes || 0);
|
||||
txTotal += Number(it.tx_bytes || 0);
|
||||
});
|
||||
const cpu = Number(stats.cpu_percent || 0);
|
||||
const mem = stats.mem_percent == null ? 0 : Number(stats.mem_percent || 0);
|
||||
const users = Array.isArray(row.users) ? row.users : [];
|
||||
const now = Date.now();
|
||||
const sshActive = users.filter(u => !u.expires_at || new Date(u.expires_at).getTime() > now).length;
|
||||
const sshExpired = Math.max(0, users.length - sshActive);
|
||||
const sshConns = users.reduce((sum, u) => sum + Number(u.active_conns || 0), 0);
|
||||
const clients = [];
|
||||
(Array.isArray(row.inbounds) ? row.inbounds : []).forEach(ib => (ib.clients || []).forEach(c => clients.push(c)));
|
||||
const xrayOnline = clients.filter(c => !!c.online).length;
|
||||
const xrayActive = clients.filter(c => !c.expired && (!c.expires_at || new Date(c.expires_at).getTime() > now)).length;
|
||||
const xrayExpired = Math.max(0, clients.length - xrayActive);
|
||||
const netNow = rx + tx;
|
||||
const running = row.xray ? !!row.xray.running : false;
|
||||
const nodeStatus = row.ok ? `<span class="badge-on">online</span>` : `<span class="badge-off">offline</span>`;
|
||||
const options = `${s.enable_ssh ? "SSH" : ""}${s.enable_ssh && s.enable_xray ? " / " : ""}${s.enable_xray ? "Xray" : ""}` || "disabled";
|
||||
const err = row.ok ? "" : `<div class="server-status-error">${escapeHTML(row.error || "connection failed")}</div>`;
|
||||
return `
|
||||
<article class="server-status-card ${row.ok ? "" : "server-status-offline"}">
|
||||
<div class="server-status-head">
|
||||
<div>
|
||||
<div class="server-status-title">${escapeHTML(s.name || "Server")}</div>
|
||||
<div class="server-status-url">${escapeHTML(s.base_url || "local")}</div>
|
||||
</div>
|
||||
<div class="server-status-badges">
|
||||
${nodeStatus}
|
||||
<span class="chip">${escapeHTML(options)}</span>
|
||||
</div>
|
||||
</div>
|
||||
${err}
|
||||
<div class="server-mini-grid">
|
||||
${miniMetricHTML("CPU", fmtPct(cpu), cpu, cpu >= 85 ? "High load" : cpu >= 60 ? "Moderate load" : "Normal load")}
|
||||
${miniMetricHTML("RAM", fmtPct(mem), mem, stats.mem_used_bytes && stats.mem_total_bytes ? `${fmtBytes(stats.mem_used_bytes)} / ${fmtBytes(stats.mem_total_bytes)}` : "Memory used")}
|
||||
${miniMetricHTML("Network", `${fmtMbps(netNow)} Mb/s`, Math.min(100, netNow / 20), `RX ${fmtMbps(rx)} - TX ${fmtMbps(tx)}`)}
|
||||
${miniMetricHTML("Accounts", String(users.length + clients.length), Math.min(100, (users.length + clients.length) * 3), `SSH ${users.length} - Xray ${clients.length}`)}
|
||||
</div>
|
||||
<div class="server-status-footer">
|
||||
<span>SSH: ${sshConns} online - ${sshActive} active - ${sshExpired} expired</span>
|
||||
<span>Xray: ${xrayOnline} online - ${xrayActive} active - ${xrayExpired} expired - Core ${running ? "running" : "stopped"}</span>
|
||||
<span>Total traffic: ${fmtBytes(rxTotal + txTotal)}</span>
|
||||
</div>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
function miniMetricHTML(label, value, pct, note) {
|
||||
const width = Math.min(100, Math.max(0, Number(pct) || 0));
|
||||
return `<div class="server-mini-metric">
|
||||
<div class="server-mini-label">${escapeHTML(label)}</div>
|
||||
<div class="server-mini-value">${escapeHTML(value)}</div>
|
||||
<div class="server-mini-note">${escapeHTML(note || "")}</div>
|
||||
<div class="server-mini-bar"><span style="width:${width}%"></span></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderServerSelectors() {
|
||||
const active = serversCache.filter(s => s.is_active !== false);
|
||||
const sshServers = active.filter(s => s.enable_ssh || s.is_local);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="assets/app.css?v=20260510black3"/>
|
||||
<link rel="stylesheet" href="assets/app.css?v=20260511serverstatus1"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
@@ -53,6 +53,7 @@
|
||||
<div class="nav-group-label superadmin-only hidden">Administração</div>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="resellers"><span class="nav-icon">🏪</span><span>Revendedores</span></button>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="servers"><span class="nav-icon">▣</span><span>Servidores</span></button>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="servers-status"><span class="nav-icon">▥</span><span>Status Servidores</span></button>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="stats"><span class="nav-icon">📊</span><span>Servidor</span></button>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="vnstat"><span class="nav-icon">⇅</span><span>Tráfego</span></button>
|
||||
<button class="tab-btn superadmin-only hidden" data-tab="logs"><span class="nav-icon">☰</span><span>Logs</span></button>
|
||||
@@ -580,7 +581,7 @@
|
||||
<!-- ═══════════ Servers Tab (superadmin only) ═══════════ -->
|
||||
<div class="tab-pane" id="tab-servers">
|
||||
<div id="serversListView">
|
||||
<div class="grid2">
|
||||
<div class="grid2 servers-grid">
|
||||
<div class="card">
|
||||
<div class="card-hdr">
|
||||
<div class="card-title">Managed servers <span class="chip" id="serversCountChip">0</span></div>
|
||||
@@ -599,14 +600,14 @@
|
||||
<div class="card-hdr"><div class="card-title" id="serverFormTitle">Add / edit server</div></div>
|
||||
<form id="serverForm">
|
||||
<input type="hidden" id="srvID"/>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid server-form-grid">
|
||||
<div class="field"><label>Name</label><input id="srvName" placeholder="Brazil Node 01"/></div>
|
||||
<div class="field"><label>Base URL</label><input id="srvBaseURL" placeholder="https://node.example.com:8080"/></div>
|
||||
<div class="field"><label>Admin username</label><input id="srvAdminUser" placeholder="admin"/></div>
|
||||
<div class="field"><label>Admin key / password</label><input id="srvAdminKey" type="password" placeholder="leave blank to keep saved key"/></div>
|
||||
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;"><input type="checkbox" id="srvEnableSSH" checked/> SSH enabled</label>
|
||||
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;"><input type="checkbox" id="srvEnableXray" checked/> Xray enabled</label>
|
||||
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;"><input type="checkbox" id="srvIsActive" checked/> Active</label>
|
||||
<label class="toggle-field"><input type="checkbox" id="srvEnableSSH" checked/> SSH enabled</label>
|
||||
<label class="toggle-field"><input type="checkbox" id="srvEnableXray" checked/> Xray enabled</label>
|
||||
<label class="toggle-field"><input type="checkbox" id="srvIsActive" checked/> Active</label>
|
||||
</div>
|
||||
<div class="form-actions" style="margin-top:8px;">
|
||||
<button class="btn" type="submit" id="saveServerBtn">Save server</button>
|
||||
@@ -778,6 +779,23 @@
|
||||
</div>
|
||||
</div><!-- /tab-servers -->
|
||||
|
||||
<!-- ═══════════ Servers Status Tab (superadmin only) ═══════════ -->
|
||||
<div class="tab-pane" id="tab-servers-status">
|
||||
<div class="card servers-status-toolbar">
|
||||
<div class="card-hdr">
|
||||
<div class="card-title">Servers Status <span class="chip" id="serversStatusCountChip">0</span></div>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-ghost btn-sm" id="refreshServersStatusBtn" type="button">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statusbar">
|
||||
<span id="serversStatusPageStatus">Small usage graphs for every active master/slave node.</span>
|
||||
<span class="hint">Auto-refreshes while this page is open.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="servers-status-grid" id="serversStatusGrid"></div>
|
||||
</div><!-- /tab-servers-status -->
|
||||
|
||||
<!-- ═══════════ Stats Tab (superadmin only) ═══════════ -->
|
||||
<div class="tab-pane" id="tab-stats">
|
||||
<div class="grid2">
|
||||
|
||||
3
main.go
3
main.go
@@ -1698,6 +1698,9 @@ func handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if proxyManagedServerFromRequest(w, r, statsStore, "/api/stats", nil, "") {
|
||||
return
|
||||
}
|
||||
stats := getCurrentStats()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(stats)
|
||||
|
||||
Reference in New Issue
Block a user