/* ============================================================= ServerClass Admin — Dashboard Client Socket.IO driven; no client-side polling. ============================================================= */ (() => { const socket = io({ transports: ["websocket", "polling"] }); // State let allUsers = []; let lastStatus = null; let lastTotal = 0; // DOM refs const $ = (id) => document.getElementById(id); const connDot = $("connDot"); const connLabel = $("connLabel"); const lastSync = $("lastSync"); const errorBox = $("errorBox"); const statsGrid = $("statsGrid"); const serversC = $("serversContent"); const usersC = $("usersContent"); const userCount = $("userCount"); // ----------------------------------------------------------- // Helpers // ----------------------------------------------------------- const escapeHtml = (s) => { if (s === null || s === undefined) return ""; return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; const fmtDate = (iso) => { if (!iso) return "—"; try { const d = new Date(iso); if (isNaN(d)) return "—"; return d.toLocaleString(undefined, { year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" }); } catch { return "—"; } }; const fmtTime = (ts) => { const d = ts ? new Date(ts * 1000) : new Date(); return d.toLocaleTimeString(undefined, { hour12: false }); }; const setConn = (state, label) => { connDot.dataset.state = state; connLabel.textContent = label; }; const pulse = (el) => { if (!el) return; el.classList.remove("pulse"); // force reflow to restart animation void el.offsetWidth; el.classList.add("pulse"); }; // ----------------------------------------------------------- // Render: errors // ----------------------------------------------------------- const renderErrors = (errors) => { if (!errors || Object.keys(errors).length === 0) { errorBox.innerHTML = ""; return; } errorBox.innerHTML = Object.entries(errors).map(([k, v]) => `
${escapeHtml(k)} ${escapeHtml(v)}
`).join(""); }; // ----------------------------------------------------------- // Render: stats // ----------------------------------------------------------- const renderStats = (status, total) => { const cards = []; cards.push(`
Total Users
${total ?? "—"}
Registered accounts
`); if (status) { const cap = status.total_servers * status.max_per_server; const pct = cap > 0 ? Math.round((total / cap) * 100) : 0; cards.push(`
Servers
${status.total_servers}
Max ${status.max_per_server} ea.
Reservations
${status.total_reservations}
Pending registrations
Capacity
${pct}%
${total} / ${cap}
`); } else { cards.push(`
Servers
Awaiting API key
Reservations
Awaiting API key
Capacity
Awaiting API key
`); } statsGrid.innerHTML = cards.join(""); pulse(statsGrid); }; // ----------------------------------------------------------- // Render: servers // ----------------------------------------------------------- const renderServers = (servers) => { if (!servers || !servers.length) { serversC.innerHTML = `
No server data available.
`; return; } const rows = servers.map((s) => { const pct = s.max > 0 ? (s.effective / s.max) * 100 : 0; const state = pct >= 100 ? "full" : pct >= 75 ? "warn" : "ok"; const statusBadge = s.available ? `Open` : `Full`; return ` № ${escapeHtml(String(s.server_num).padStart(2, "0"))} ${s.users} ${s.reserved} ${s.effective} / ${s.max}
${statusBadge} ${escapeHtml(s.url || "—")} `; }).join(""); serversC.innerHTML = `
${rows}
Users Reserved Capacity Status Endpoint
`; pulse(serversC); }; // ----------------------------------------------------------- // Render: users // ----------------------------------------------------------- const renderUsers = (users) => { userCount.textContent = `${users.length} record${users.length === 1 ? "" : "s"}`; if (!users.length) { usersC.innerHTML = `
No users match the current filter.
`; return; } const rows = users.map((u, i) => { const tokens = u.tokens_count || 0; const tokensBadge = tokens > 0 ? `${tokens} token${tokens > 1 ? "s" : ""}` : `none`; return ` ${String(i + 1).padStart(3, "0")} ${escapeHtml(u.username || "—")} ${escapeHtml(u.telegram_id || "—")} Server ${escapeHtml(String(u.server_num ?? "?"))} ${tokensBadge} ${escapeHtml(fmtDate(u.created_at))} ${escapeHtml(fmtDate(u.last_login))} `; }).join(""); usersC.innerHTML = `
${rows}
# Username Telegram ID Server Tokens Created Last login
`; pulse(usersC); }; // ----------------------------------------------------------- // Filter // ----------------------------------------------------------- window.filterUsers = () => { const q = ($("userSearch").value || "").trim().toLowerCase(); if (!q) return renderUsers(allUsers); const filtered = allUsers.filter((u) => (u.username || "").toLowerCase().includes(q) || (u.telegram_id || "").toLowerCase().includes(q) || String(u.server_num || "").includes(q) ); renderUsers(filtered); }; // ----------------------------------------------------------- // Form events // ----------------------------------------------------------- $("authForm").addEventListener("submit", (e) => { e.preventDefault(); const admin_secret = $("adminSecret").value.trim(); const api_key = $("apiKey").value.trim(); if (!admin_secret && !api_key) { renderErrors({ auth: "Provide at least an Admin Secret or API Key." }); return; } setConn("warn", "AUTHENTICATING…"); socket.emit("authenticate", { admin_secret, api_key }); }); $("refreshBtn").addEventListener("click", () => { setConn("warn", "REFRESHING…"); socket.emit("refresh"); }); // ----------------------------------------------------------- // Socket.IO lifecycle // ----------------------------------------------------------- socket.on("connect", () => { setConn("on", "LIVE"); }); socket.on("connected", (data) => { if (data && data.poll_interval) { const el = $("pollInterval"); if (el) el.textContent = data.poll_interval; } }); socket.on("disconnect", () => { setConn("off", "DISCONNECTED"); }); socket.on("connect_error", () => { setConn("off", "CONNECTION ERROR"); }); socket.on("error", (data) => { renderErrors({ socket: (data && data.message) || "Unknown error" }); }); // ----------------------------------------------------------- // Main payload handler // ----------------------------------------------------------- socket.on("data_update", (payload) => { if (!payload) return; if (payload.errors) renderErrors(payload.errors); allUsers = payload.users || []; lastTotal = payload.total ?? allUsers.length; lastStatus = payload.status || null; renderStats(lastStatus, lastTotal); renderServers(lastStatus ? lastStatus.servers : null); renderUsers(allUsers); lastSync.textContent = fmtTime(payload.timestamp); setConn("on", payload.source === "auto" ? "LIVE · AUTO" : "LIVE"); }); })();