(function () { "use strict"; var cfg = window.ADMIN_APP_CONFIG || {}; var api = cfg.api || {}; var root = document.getElementById("admin-app"); if (!root) { return; } var state = { token: readToken(), user: null, route: normalizeRoute(readRoute()), message: "", messageType: "", config: null, system: null, plugins: [], integration: {}, realname: null, devices: null, busy: false }; boot(); async function boot() { window.addEventListener("hashchange", function () { state.route = normalizeRoute(readRoute()); render(); hydrateRoute(); }); root.addEventListener("click", onClick); root.addEventListener("submit", onSubmit); if (state.token) { await loadBootstrap(); } render(); hydrateRoute(); } async function loadBootstrap() { try { state.busy = true; var loginCheck = unwrap(await request("/api/v1/user/checkLogin", { method: "GET" })); if (!loginCheck || !loginCheck.is_login || !loginCheck.is_admin) { clearSession(); return; } state.user = loginCheck; var results = await Promise.all([ request(api.adminConfig, { method: "GET" }), request(api.systemStatus, { method: "GET" }), request(api.plugins, { method: "GET" }), request(api.integration, { method: "GET" }) ]); state.config = unwrap(results[0]) || {}; state.system = unwrap(results[1]) || {}; state.plugins = toArray(unwrap(results[2])); state.integration = unwrap(results[3]) || {}; } catch (error) { clearSession(); show(error.message || "Failed to load admin data.", "error"); } finally { state.busy = false; } } async function hydrateRoute() { if (!state.user) { return; } try { if (state.route === "realname") { state.realname = unwrap(await request(api.realnameBase + "/records?per_page=50", { method: "GET" })) || {}; render(); return; } if (state.route === "user-online-devices") { state.devices = unwrap(await request(api.onlineDevices + "?per_page=50", { method: "GET" })) || {}; render(); } } catch (error) { show(error.message || "Failed to load page data.", "error"); render(); } } function onClick(event) { var actionEl = event.target.closest("[data-action]"); if (!actionEl) { return; } var action = actionEl.getAttribute("data-action"); if (action === "logout") { clearSession(); render(); return; } if (action === "nav") { window.location.hash = actionEl.getAttribute("data-route") || "overview"; return; } if (action === "refresh") { refreshAll(); return; } if (action === "approve-all") { adminPost(api.realnameBase + "/approve-all", {}, "Approved all pending records.").then(hydrateRoute); return; } if (action === "sync-all") { adminPost(api.realnameBase + "/sync-all", {}, "Triggered real-name sync.").then(hydrateRoute); return; } if (action === "clear-cache") { adminPost(api.realnameBase + "/clear-cache", {}, "Cleared plugin cache."); return; } if (action === "review") { var userId = actionEl.getAttribute("data-user-id"); var status = actionEl.getAttribute("data-status") || "approved"; var reason = ""; if (status === "rejected") { reason = window.prompt("Reject reason", "") || ""; } adminPost( api.realnameBase + "/review/" + encodeURIComponent(userId), { status: status, reason: reason }, "Updated review result." ).then(hydrateRoute); return; } if (action === "reset-record") { var resetUserId = actionEl.getAttribute("data-user-id"); adminPost( api.realnameBase + "/reset/" + encodeURIComponent(resetUserId), {}, "Reset the verification record." ).then(hydrateRoute); } } function onSubmit(event) { var form = event.target; if (form.getAttribute("data-form") !== "login") { return; } event.preventDefault(); var formData = serializeForm(form); request("/api/v1/passport/auth/login", { method: "POST", auth: false, body: formData }).then(function (response) { var payload = unwrap(response); if (!payload || !payload.auth_data || !payload.is_admin) { throw new Error("This account does not have admin access."); } saveToken(payload.auth_data); state.token = readToken(); return loadBootstrap(); }).then(function () { show("Admin login successful.", "success"); render(); hydrateRoute(); }).catch(function (error) { show(error.message || "Login failed.", "error"); render(); }); } function refreshAll() { state.realname = null; state.devices = null; state.busy = true; render(); loadBootstrap().then(function () { render(); return hydrateRoute(); }); } function clearSession() { clearToken(); state.user = null; state.config = null; state.system = null; state.plugins = []; state.integration = {}; state.realname = null; state.devices = null; state.route = "overview"; state.busy = false; } function render() { root.innerHTML = state.user ? renderDashboard() : renderLogin(); } function renderLogin() { return [ '
', '
', '

' + escapeHtml(cfg.title || "Admin") + '

Use an administrator account to sign in and open the rebuilt backend workspace.

', state.message ? renderNotice() : "", '
', '
', '
', '', '
', '
The backend now uses a single Go-rendered admin shell with integrated plugin pages.
', '
', '
' ].join(""); } function renderDashboard() { return [ '
', renderSidebar(), '
', renderTopbar(), '
', state.message ? renderNotice() : "", renderMainContent(), '
', '
', '
' ].join(""); } function renderSidebar() { var items = [ navItem("overview", "Overview", "System status, plugin visibility, and entry summary"), navItem("realname", "Real Name", "Review and operate the verification workflow"), navItem("user-online-devices", "Online Devices", "Inspect user sessions and online device data"), navItem("ipv6-subscription", "IPv6 Subscription", "Check the IPv6 shadow-account integration state"), navItem("plugin-status", "Plugin Status", "Compare current backend integrations by module") ]; return [ '' ].join(""); } function renderTopbar() { return [ '
', '
', 'Admin Workspace', '

' + escapeHtml(getRouteTitle(state.route)) + '

', '

' + escapeHtml(getRouteDescription(state.route)) + '

', '
', 'Secure Path /' + escapeHtml(getSecurePath()) + '', 'Server Time ' + escapeHtml(formatDate(state.system && state.system.server_time)) + '', state.busy ? 'Refreshing' : "", '
', '
', '
', '
' ].join(""); } function renderMainContent() { if (state.route === "realname") { return renderRealName(); } if (state.route === "user-online-devices") { return renderOnlineDevices(); } if (state.route === "ipv6-subscription") { return renderIPv6Integration(); } if (state.route === "plugin-status") { return renderPluginStatus(); } return renderOverview(); } function renderOverview() { var enabledCount = countEnabledPlugins(); return [ '
', '
', 'Overview', '

Classic backend structure, rebuilt with the current Go APIs.

', '

The page keeps the familiar admin navigation pattern while exposing plugin data, system state, and backend integration results in one place.

', '
', '
', '
Enabled Plugins' + escapeHtml(String(enabledCount)) + '
', '
Secure Path/' + escapeHtml(getSecurePath()) + '
', '
', '
', '
', statCard("Server Time", formatDate(state.system && state.system.server_time), "Source: /system/getSystemStatus"), statCard("Admin Path", "/" + getSecurePath(), "Synced from current backend settings"), statCard("Plugin Count", String((state.plugins || []).length), "Read from the integrated plugin list"), '
', '
Plugins

Integrated plugin list

Each entry reflects the current Go backend response and the copied integration status.

' + renderPluginsTable() + '
' ].join(""); } function renderRealName() { var rows = toArray(state.realname, "data"); var loading = state.realname === null; return [ '
', '
Workflow

Real-name verification

Batch actions stay in the toolbar while the review table keeps the classic admin operating rhythm.

', '
', '
', loading ? '' : rows.length ? rows.map(function (row) { return [ '', '', '', '', '', '', '', '', '' ].join(""); }).join("") : '', '
IDEmailStatusReal NameID NumberSubmitted AtActions
Loading verification records...
' + escapeHtml(String(row.id || "")) + '' + escapeHtml(row.email || "-") + '' + renderStatus(row.status) + '' + escapeHtml(row.real_name || "-") + '' + escapeHtml(row.identity_no_masked || "-") + '' + escapeHtml(formatDate(row.submitted_at)) + '
No verification records available.
', '
' ].join(""); } function renderOnlineDevices() { var rows = toArray(state.devices, "list"); var loading = state.devices === null; return [ '
', '
Monitoring

Online devices

This table keeps the admin-friendly scan pattern for sessions, subscription names, IPs, and recent activity.

', '
', loading ? '' : rows.length ? rows.map(function (row) { return [ '', '', '', '', '', '', '', '' ].join(""); }).join("") : '', '
UserSubscriptionOnline CountOnline IPsLast SeenCreated At
Loading device records...
' + escapeHtml(row.email || "-") + '' + escapeHtml(row.subscription_name || "-") + '' + escapeHtml(String(row.online_count || 0)) + '' + escapeHtml(formatDeviceList(row.online_devices)) + '' + escapeHtml(row.last_online_text || "-") + '' + escapeHtml(row.created_text || "-") + '
No online device records available.
', '
' ].join(""); } function renderIPv6Integration() { var integration = (state.integration && state.integration.user_add_ipv6_subscription) || {}; return [ '
', '
Integration

IPv6 shadow subscription

' + escapeHtml(buildSummary(integration, "This panel shows the current runtime status of the IPv6 shadow-account integration.")) + '

', '
' + renderStatus(integration.status || "unknown") + '
', '
' + escapeHtml(stringifyJSON(integration)) + '
', '
' ].join(""); } function renderPluginStatus() { var cards = [ sectionCard("Real-name verification", state.integration && state.integration.real_name_verification), sectionCard("Online devices", state.integration && state.integration.user_online_devices), sectionCard("IPv6 shadow subscription", state.integration && state.integration.user_add_ipv6_subscription) ]; return '
' + cards.join("") + '
'; } function renderPluginsTable() { var rows = toArray(state.plugins); return '' + (rows.length ? rows.map(function (row) { return ''; }).join("") : '') + '
IDCodeStatusConfig
' + escapeHtml(String(row.id || "")) + '' + escapeHtml(row.code || row.name || "-") + '' + renderStatus(row.is_enabled ? "enabled" : "disabled") + '
' + escapeHtml(formatPluginConfig(row.config)) + '
No plugin data available.
'; } function sectionCard(title, data) { data = data || {}; return [ '
', '
Module

' + escapeHtml(title) + '

', '

' + escapeHtml(buildSummary(data, "No summary has been returned by the backend for this module.")) + '

', renderStatus(data.status || "unknown"), '
' + escapeHtml(stringifyJSON(data)) + '
', '
' ].join(""); } function navItem(route, title, desc) { return '' + escapeHtml(title) + '' + escapeHtml(desc) + ''; } function statCard(title, value, hint) { return '
' + escapeHtml(title) + '' + escapeHtml(value || "-") + '

' + escapeHtml(hint || "") + '

'; } function renderNotice() { return '
' + escapeHtml(state.message || "") + '
'; } function countEnabledPlugins() { return toArray(state.plugins).filter(function (item) { return !!item.is_enabled; }).length; } function buildSummary(data, fallback) { if (!data) { return fallback; } if (typeof data.summary === "string" && data.summary.trim()) { return data.summary.trim(); } if (typeof data.message === "string" && data.message.trim()) { return data.message.trim(); } return fallback; } function formatPluginConfig(value) { if (typeof value === "string" && value.trim()) { return value; } return stringifyJSON(value || {}); } function stringifyJSON(value) { try { return JSON.stringify(value == null ? {} : value, null, 2); } catch (error) { return String(value == null ? "" : value); } } function formatDeviceList(value) { if (Array.isArray(value)) { return value.join(", ") || "-"; } if (typeof value === "string" && value.trim()) { return value; } return "-"; } function toArray(value, preferredKey) { if (Array.isArray(value)) { return value; } if (!value || typeof value !== "object") { return []; } if (preferredKey && Array.isArray(value[preferredKey])) { return value[preferredKey]; } if (Array.isArray(value.data)) { return value.data; } if (Array.isArray(value.list)) { return value.list; } return []; } function request(url, options) { options = options || {}; var headers = { "Content-Type": "application/json" }; if (options.auth !== false && state.token) { headers.Authorization = state.token; } return fetch(url, { method: options.method || "GET", headers: headers, credentials: "same-origin", body: options.body ? JSON.stringify(options.body) : undefined }).then(async function (response) { var payload = null; try { payload = await response.json(); } catch (error) { payload = null; } if (!response.ok) { if (response.status === 401) { clearSession(); render(); } throw new Error(getErrorMessage(payload) || "Request failed."); } return payload; }); } function adminPost(url, body, successMessage) { return request(url, { method: "POST", body: body || {} }).then(function (payload) { show(successMessage || "Operation completed.", "success"); render(); return payload; }).catch(function (error) { show(error.message || "Operation failed.", "error"); render(); throw error; }); } function unwrap(payload) { if (!payload) { return null; } return typeof payload.data !== "undefined" ? payload.data : payload; } function getErrorMessage(payload) { if (!payload) { return ""; } return payload.message || payload.msg || payload.error || ""; } function saveToken(token) { var normalized = /^Bearer /.test(token) ? token : "Bearer " + token; window.localStorage.setItem("__gopanel_admin_auth__", normalized); } function serializeForm(form) { var result = {}; var formData = new FormData(form); formData.forEach(function (value, key) { result[key] = value; }); return result; } function readToken() { var token = window.localStorage.getItem("__gopanel_admin_auth__") || window.localStorage.getItem("__nebula_auth_data__") || window.localStorage.getItem("auth_data") || ""; if (!token) { return ""; } return /^Bearer /.test(token) ? token : "Bearer " + token; } function clearToken() { window.localStorage.removeItem("__gopanel_admin_auth__"); state.token = ""; } function readRoute() { return (window.location.hash || "#overview").slice(1) || "overview"; } function normalizeRoute(route) { var allowed = { overview: true, realname: true, "user-online-devices": true, "ipv6-subscription": true, "plugin-status": true }; return allowed[route] ? route : "overview"; } function getSecurePath() { return (state.config && state.config.secure_path) || cfg.securePath || "admin"; } function getRouteTitle(route) { if (route === "realname") { return "Real-name Verification"; } if (route === "user-online-devices") { return "Online Devices"; } if (route === "ipv6-subscription") { return "IPv6 Subscription"; } if (route === "plugin-status") { return "Plugin Status"; } return "Overview"; } function getRouteDescription(route) { if (route === "realname") { return "Review records, run batch actions, and keep the original backend workflow readable."; } if (route === "user-online-devices") { return "Inspect active users, current devices, and recent online activity in one table."; } if (route === "ipv6-subscription") { return "Check the replicated backend integration output for the IPv6 shadow-account module."; } if (route === "plugin-status") { return "Compare plugin integration payloads and runtime summaries side by side."; } return "A familiar backend workspace with sidebar navigation, top control bar, and focused content cards."; } function show(message, type) { state.message = message || ""; state.messageType = type || ""; } function formatDate(value) { if (!value) { return "-"; } var numeric = Number(value); if (!Number.isNaN(numeric) && numeric > 0) { return new Date(numeric * 1000).toLocaleString(); } var parsed = Date.parse(value); if (!Number.isNaN(parsed)) { return new Date(parsed).toLocaleString(); } return String(value); } function renderStatus(status) { var raw = String(status || "unknown"); var normalized = raw.toLowerCase(); var klass = "status-pill status-ok"; if (normalized.indexOf("warn") !== -1 || normalized.indexOf("pending") !== -1 || normalized.indexOf("runtime") !== -1) { klass = "status-pill status-warn"; } if (normalized.indexOf("disabled") !== -1 || normalized.indexOf("fail") !== -1 || normalized.indexOf("error") !== -1 || normalized.indexOf("reject") !== -1 || normalized.indexOf("unknown") !== -1) { klass = "status-pill status-danger"; } return '' + escapeHtml(raw) + ''; } function escapeHtml(value) { return String(value == null ? "" : value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } })();