${escapeHtml(hint || "")}
`, "(function () { "use strict"; const cfg = window.ADMIN_APP_CONFIG || {}; const root = document.getElementById("admin-app"); if (!root) { return; } const state = { token: readToken(), user: null, route: "overview", busy: false, message: "", messageType: "info", modal: null, configTab: "site", sidebarGroups: { dashboard: true, infrastructure: true, business: true, operations: true, system: true }, pagination: {}, dashboard: null, system: null, config: null, nodes: [], groups: [], routes: [], plans: [], orders: { list: [], pagination: null }, coupons: [], users: { list: [], pagination: null }, tickets: { list: [], pagination: null }, realname: { list: [], pagination: null }, devices: { list: [], pagination: null }, ipv6: { list: [], pagination: null }, expandedNodes: new Set(), prevUser: null, prevRoute: null }; const ROUTE_META = { overview: { title: '总览', description: '查看收入、用户和流量概况。' }, 'dashboard-node': { title: '节点状态', description: '查看节点在线状态、负载和推送情况。' }, 'node-manage': { title: '节点管理', description: '管理服务节点、可见性以及父子节点关系。' }, 'node-group': { title: '权限组', description: '管理节点权限组和用户分组映射。' }, 'node-route': { title: '路由规则', description: '维护节点路由匹配规则。' }, 'plan-manage': { title: '套餐管理', description: '维护套餐、流量和价格配置。' }, 'order-manage': { title: '订单管理', description: '处理待支付和已支付订单。' }, 'coupon-manage': { title: '优惠券', description: '创建和维护优惠券信息。' }, 'user-manage': { title: '用户管理', description: '查看用户订阅、流量和封禁状态。' }, 'ticket-manage': { title: '工单中心', description: '查看用户工单和处理状态。' }, realname: { title: '实名认证', description: '审核实名记录和同步状态。' }, 'user-online-devices': { title: '在线设备', description: '查看用户在线 IP 和设备分布。' }, 'user-ipv6-subscription': { title: 'IPv6 子账号', description: '管理 IPv6 阴影账号与密码同步。' }, 'system-config': { title: '系统设置', description: '编辑站点、订阅和安全参数。' } }; state.route = normalizeRoute(readRoute()); boot(); async function boot() { window.addEventListener('hashchange', async function () { state.route = normalizeRoute(readRoute()); state.modal = null; await hydrateRoute(); }); root.addEventListener('click', onClick); root.addEventListener('submit', onSubmit); // Initialize shell and modal containers root.innerHTML = '
'; if (state.token) { await loadBootstrap(); } else { render(); } if (state.user) { await hydrateRoute(); } else { render(); } } async function loadBootstrap() { try { setBusy(true); const loginCheck = unwrap(await request('/api/v1/user/checkLogin', { method: 'GET' })); if (!loginCheck || !loginCheck.is_admin) { clearSession(); return; } state.user = loginCheck; const [config, system] = await Promise.all([ request(cfg.api.adminConfig, { method: 'GET' }), request(cfg.api.systemStatus, { method: 'GET' }) ]); state.config = unwrap(config) || {}; state.system = unwrap(system) || {}; } catch (error) { console.error('bootstrap failed', error); clearSession(); show(error.message || '管理端初始化失败', 'error'); } finally { setBusy(false); render(); } } async function hydrateRoute() { if (!state.user) { render(); return; } try { setBusy(true); const page = getCurrentPage(); if (state.route === 'overview') { state.dashboard = unwrap(await request(cfg.api.dashboardSummary)); } else if (state.route === 'dashboard-node') { const [dashboard, nodes] = await Promise.all([ request(cfg.api.dashboardSummary), request(cfg.api.serverNodes) ]); state.dashboard = unwrap(dashboard) || {}; state.nodes = toArray(unwrap(nodes)); } else if (state.route === 'node-manage') { const [nodes, groups] = await Promise.all([ request(cfg.api.serverNodes), request(cfg.api.serverGroups) ]); state.nodes = toArray(unwrap(nodes)); state.groups = toArray(unwrap(groups)); } else if (state.route === 'node-group') { state.groups = toArray(unwrap(await request(cfg.api.serverGroups))); } else if (state.route === "node-route") { state.routes = toArray(unwrap(await request(cfg.api.serverRoutes))); } else if (state.route === "plan-manage") { const [plans, groups] = await Promise.all([ request(cfg.api.plans), request(cfg.api.serverGroups) ]); state.plans = toArray(unwrap(plans)); state.groups = toArray(unwrap(groups)); } else if (state.route === "order-manage") { const payload = await request(`${cfg.api.orders}?page=${page}&per_page=20`); state.orders = normalizeListPayload(payload); } else if (state.route === "coupon-manage") { state.coupons = toArray(unwrap(await request(cfg.api.coupons))); } else if (state.route === "user-manage") { const [users, plans, groups] = await Promise.all([ request(`${cfg.api.users}?page=${page}&per_page=20`), request(cfg.api.plans), request(cfg.api.serverGroups) ]); state.users = normalizeListPayload(users); state.plans = toArray(unwrap(plans)); state.groups = toArray(unwrap(groups)); } else if (state.route === "ticket-manage") { const payload = await request(`${cfg.api.tickets}?page=${page}&per_page=20`); state.tickets = normalizeListPayload(payload); } else if (state.route === "realname") { const payload = await request(`${cfg.api.realnameBase}/records?page=${page}&per_page=20`); state.realname = normalizeListPayload(payload, "data"); } else if (state.route === "user-online-devices") { const payload = await request(`${cfg.api.onlineDevices}?page=${page}&per_page=20`); state.devices = normalizeListPayload(payload); } else if (state.route === "user-ipv6-subscription") { const [payload, plans] = await Promise.all([ request(`${cfg.api.ipv6Base}/users?page=${page}&per_page=20`), request(cfg.api.plans) ]); state.ipv6 = normalizeListPayload(payload); state.plans = toArray(unwrap(plans)); } else if (state.route === "system-config") { state.config = unwrap(await request(cfg.api.adminConfig)); } } catch (error) { console.error("route hydrate failed", error); show(error.message || "页面数据加载失败", "error"); } finally { setBusy(false); render(); } } async function onClick(event) { const actionEl = event.target.closest("[data-action]"); if (!actionEl || state.busy) { return; } const action = actionEl.getAttribute("data-action"); if (action === "nav") { window.location.hash = actionEl.getAttribute("data-route") || "overview"; return; } if (action === "logout") { clearSession(); render(); return; } if (action === "refresh") { await loadBootstrap(); await hydrateRoute(); return; } if (action === "modal-close") { state.modal = null; render(); return; } if (action === "sidebar-toggle") { const group = actionEl.getAttribute("data-id"); state.sidebarGroups[group] = state.sidebarGroups[group] === false; render(); return; } if (action === "config-tab") { state.configTab = actionEl.getAttribute("data-tab") || "site"; render(); return; } if (action === "page") { setCurrentPage(Number(actionEl.getAttribute("data-page")) || 1); await hydrateRoute(); return; } if (action === "node-expand") { const id = String(actionEl.getAttribute("data-id")); if (state.expandedNodes.has(id)) { state.expandedNodes.delete(id); } else { state.expandedNodes.add(id); } render(); return; } if (action === "plan-add") { state.modal = { type: "plan", data: {} }; render(); return; } if (action === "plan-edit") { const item = state.plans.find((row) => String(row.id) === actionEl.getAttribute("data-id")); state.modal = { type: "plan", data: item || {} }; render(); return; } if (action === "plan-delete") { event.preventDefault(); event.stopPropagation(); if (confirm("确认删除该套餐吗?")) { await adminPost(`${cfg.api.adminBase}/plan/drop`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); } return; } if (action === "coupon-add") { state.modal = { type: "coupon", data: {} }; render(); return; } if (action === "coupon-delete") { event.preventDefault(); event.stopPropagation(); if (confirm("确认删除该优惠券吗?")) { await adminPost(`${cfg.api.adminBase}/coupon/drop`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); } return; } if (action === "user-edit") { const item = state.users.list.find((row) => String(row.id) === actionEl.getAttribute("data-id")); state.modal = { type: "user", data: item || {} }; render(); return; } if (action === "user-ban" || action === "user-unban") { await adminPost(`${cfg.api.adminBase}/user/ban`, { id: Number(actionEl.getAttribute("data-id")), banned: action === "user-ban" }); await hydrateRoute(); return; } if (action === "user-reset-traffic") { await adminPost(`${cfg.api.adminBase}/user/resetTraffic`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); return; } if (action === "order-paid") { event.preventDefault(); event.stopPropagation(); await adminPost(`${cfg.api.adminBase}/order/paid`, { trade_no: actionEl.getAttribute("data-trade") }); await hydrateRoute(); return; } if (action === "order-cancel") { event.preventDefault(); event.stopPropagation(); await adminPost(`${cfg.api.adminBase}/order/cancel`, { trade_no: actionEl.getAttribute("data-trade") }); await hydrateRoute(); return; } if (action === "node-add") { state.modal = { type: "node", data: {} }; render(); return; } if (action === "node-edit") { event.preventDefault(); event.stopPropagation(); const nodeId = String(actionEl.getAttribute("data-id")); const item = state.nodes.find((row) => String(row.id) === nodeId); state.modal = { type: "node", data: item || {} }; render(); return; } if (action === "node-copy") { event.preventDefault(); event.stopPropagation(); const nodeId = Number(actionEl.getAttribute("data-id")); try { await adminPost(`${cfg.api.adminBase}/server/manage/copy`, { id: nodeId }); } catch (e) { /* adminPost already shows error */ } await hydrateRoute(); return; } if (action === "node-delete") { event.preventDefault(); event.stopPropagation(); if (confirm("确认删除该节点吗?")) { const nodeId = Number(actionEl.getAttribute("data-id")); try { await adminPost(`${cfg.api.adminBase}/server/manage/drop`, { id: nodeId }); } catch (e) { /* adminPost already shows error */ } await hydrateRoute(); } return; } if (action === "node-toggle-visible") { event.preventDefault(); event.stopPropagation(); const nodeId = Number(actionEl.getAttribute("data-id")); const input = actionEl.querySelector("input"); if (!input) { return; } const newShow = !input.checked; input.checked = newShow; try { await adminPost(`${cfg.api.adminBase}/server/manage/update`, { id: nodeId, show: newShow ? 1 : 0 }); } catch (e) { /* adminPost already shows error */ } await hydrateRoute(); return; } if (action === "group-add") { state.modal = { type: "group", data: {} }; render(); return; } if (action === "group-edit") { const item = state.groups.find((row) => String(row.id) === actionEl.getAttribute("data-id")); state.modal = { type: "group", data: item || {} }; render(); return; } if (action === "group-delete") { if (confirm("确认删除该权限组吗?")) { await adminPost(`${cfg.api.adminBase}/server/group/drop`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); } return; } if (action === "route-add") { state.modal = { type: "route", data: {} }; render(); return; } if (action === "route-edit") { const item = state.routes.find((row) => String(row.id) === actionEl.getAttribute("data-id")); state.modal = { type: "route", data: item || {} }; render(); return; } if (action === "route-delete") { if (confirm("确认删除该路由规则吗?")) { await adminPost(`${cfg.api.adminBase}/server/route/drop`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); } return; } if (action === "realname-review") { const userId = actionEl.getAttribute("data-user-id"); const status = actionEl.getAttribute("data-status"); const reason = status === "rejected" ? prompt("请输入驳回原因,可留空:", "") || "" : ""; await adminPost(`${cfg.api.realnameBase}/review/${userId}`, { status, reason }); await hydrateRoute(); return; } if (action === "approve-all") { await adminPost(`${cfg.api.realnameBase}/approve-all`, {}); await hydrateRoute(); return; } if (action === "sync-all") { await adminPost(`${cfg.api.realnameBase}/sync-all`, {}); await hydrateRoute(); return; } if (action === "ipv6-enable-modal") { const userId = actionEl.getAttribute("data-user-id"); const userEmail = actionEl.getAttribute("data-email") || ""; state.modal = { type: "ipv6-enable", data: { userId, email: userEmail } }; render(); return; } if (action === "ipv6-disable") { const userId = actionEl.getAttribute("data-user-id"); if (confirm("确认关闭该用户的 IPv6 子账号?(软禁用,可恢复)")) { await adminPost(`${cfg.api.ipv6Base}/disable/${userId}`, {}); await hydrateRoute(); } return; } if (action === "ipv6-sync-password") { const userId = actionEl.getAttribute("data-user-id"); await adminPost(`${cfg.api.ipv6Base}/sync-password/${userId}`, {}); await hydrateRoute(); return; } } async function onSubmit(event) { const form = event.target; const action = form.getAttribute("data-form"); if (!action) { return; } event.preventDefault(); if (action === "login") { try { setBusy(true); const payload = unwrap(await request("/api/v2/passport/auth/login", { method: "POST", auth: false, body: serializeForm(form) })); if (!payload || !payload.auth_data || !payload.is_admin) { throw new Error("当前账号没有管理员权限"); } saveToken(payload.auth_data); state.token = readToken(); await loadBootstrap(); await hydrateRoute(); show("登录成功", "success"); } catch (error) { show(error.message || "登录失败", "error"); render(); } finally { setBusy(false); } return; } const body = serializeForm(form); let target = ""; if (action === "plan-save") { target = `${cfg.api.adminBase}/plan/save`; } else if (action === "coupon-save") { target = `${cfg.api.adminBase}/coupon/save`; } else if (action === "user-save") { target = `${cfg.api.adminBase}/user/update`; } else if (action === "node-save") { target = `${cfg.api.adminBase}/server/manage/save`; } else if (action === "group-save") { target = `${cfg.api.adminBase}/server/group/save`; } else if (action === "route-save") { target = `${cfg.api.adminBase}/server/route/save`; } else if (action === "config-save") { target = `${cfg.api.adminBase}/config/save`; } if (!target) { if (action === "ipv6-enable-save") { const userId = body.user_id; const planId = Number(body.plan_id) || 0; await adminPost(`${cfg.api.ipv6Base}/enable/${userId}`, { plan_id: planId }); state.modal = null; await hydrateRoute(); return; } return; } await adminPost(target, body); if (action !== "config-save") { state.modal = null; } await hydrateRoute(); } function render(force) { const shellContainer = document.getElementById("admin-shell-container"); const modalContainer = document.getElementById("admin-modal-container"); const busyContainer = document.getElementById("admin-busy-container"); if (!shellContainer || !modalContainer || !busyContainer) return; // Re-render the shell if user or route changed, or if forced (e.g., data updated) if (force || state.user !== state.prevUser || state.route !== state.prevRoute) { shellContainer.innerHTML = state.user ? renderDashboard() : renderLogin(); state.prevUser = state.user; state.prevRoute = state.route; } // Handle Modal independently if (state.modal) { modalContainer.innerHTML = ``; } else { modalContainer.innerHTML = ""; } // Handle Busy overlay independently if (state.busy) { busyContainer.innerHTML = ''; } else { busyContainer.innerHTML = ""; } } function renderLogin() { return [ '使用管理员账号登录 Go 重构后的控制台
', "${node.id}`,
escapeHtml(node.name || "-"),
escapeHtml(node.type || "-"),
renderStatus(node.available_status || "offline"),
escapeHtml(`${formatTraffic(node.u)} / ${formatTraffic(node.d)}`),
escapeHtml(formatDate(node.last_push_at || node.last_check_at))
]);
return wrapTable(
["ID", "节点", "类型", "状态", "上下行", "最后心跳"],
rows
);
}
function renderNodeManage() {
const isRoot = (node) => !node.parent_id || String(node.parent_id) === "0";
const roots = state.nodes.filter(isRoot);
const childrenByParent = {};
state.nodes.forEach((node) => {
if (node.parent_id) {
childrenByParent[node.parent_id] = childrenByParent[node.parent_id] || [];
childrenByParent[node.parent_id].push(node);
}
});
const rows = [];
roots.forEach((node) => {
const children = childrenByParent[node.id] || [];
const expanded = state.expandedNodes.has(String(node.id));
rows.push(renderNodeRow(node, false, children.length > 0, expanded));
if (expanded) {
children.forEach((child) => rows.push(renderNodeRow(child, true, false, false)));
}
});
return [
'",
wrapTable(["ID", "名称 / 主机", "类型", "倍率", "显示", "操作"], rows)
].join("");
}
function renderNodeRow(node, isChild, hasChildren, expanded) {
const toggle = hasChildren
? ``
: '';
const relationCell = isChild && node.parent_id
? `${escapeHtml(node.id)}→${escapeHtml(node.parent_id)}`
: `${node.id}`;
return {
className: isChild ? "node-child" : "",
cells: [
relationCell,
[
'${group.id}`,
escapeHtml(group.name || "-"),
`${group.server_count || 0}`,
`${group.user_count || 0}`,
renderActionRow([
buttonAction("编辑", "group-edit", group.id),
buttonAction("删除", "group-delete", group.id)
])
]);
return [
'',
wrapTable(["ID", "名称", "节点数", "用户数", "操作"], rows)
].join("");
}
function renderNodeRoute() {
const rows = state.routes.map((route) => [
`${route.id}`,
escapeHtml(route.remarks || "-"),
escapeHtml((route.match || []).join(", ")),
escapeHtml(route.action || "-"),
renderActionRow([
buttonAction("编辑", "route-edit", route.id),
buttonAction("删除", "route-delete", route.id)
])
]);
return [
'',
wrapTable(["ID", "备注", "匹配规则", "动作", "操作"], rows)
].join("");
}
function renderPlanManage() {
const rows = state.plans.map((plan) => [
`${plan.id}`,
escapeHtml(plan.name || "-"),
escapeHtml(plan.group_name || "-"),
escapeHtml(formatTraffic(plan.transfer_enable)),
`${escapeHtml(plan.prices || "{}")}`,
renderStatus(plan.show ? "visible" : "hidden"),
renderActionRow([
buttonAction("编辑", "plan-edit", plan.id),
buttonAction("删除", "plan-delete", plan.id)
])
]);
return [
'',
wrapTable(["ID", "套餐名称", "权限组", "流量", "价格", "状态", "操作"], rows)
].join("");
}
function renderOrderManage() {
const rows = state.orders.list.map((order) => [
`${escapeHtml(order.trade_no)}`,
escapeHtml(order.user_email || `#${order.user_id}`),
escapeHtml((order.plan && order.plan.name) || "-"),
formatMoney(order.total_amount),
renderStatus(orderStatusText(order.status)),
renderActionRow(
order.status === 0
? [
buttonAction("标记已支付", "order-paid", null, `data-trade="${escapeHtml(order.trade_no)}"`),
buttonAction("取消", "order-cancel", null, `data-trade="${escapeHtml(order.trade_no)}"`)
]
: ["-"]
)
]);
return [
wrapTable(["订单号", "用户", "套餐", "金额", "状态", "操作"], rows),
renderPagination(state.orders.pagination)
].join("");
}
function renderCouponManage() {
const rows = state.coupons.map((coupon) => [
`${coupon.id}`,
escapeHtml(coupon.name || "-"),
`${escapeHtml(coupon.code || "-")}`,
escapeHtml(coupon.type === 1 ? "固定金额" : "比例折扣"),
escapeHtml(coupon.type === 1 ? formatMoney(coupon.value) : `${coupon.value || 0}%`),
renderActionRow([buttonAction("删除", "coupon-delete", coupon.id)])
]);
return [
'',
wrapTable(["ID", "名称", "编码", "类型", "面额", "操作"], rows)
].join("");
}
function renderUserManage() {
const rows = state.users.list.map((user) => [
user.parent_id ? renderRelationChip(user.parent_id, user.id) : `${user.id}`,
[
`${escapeHtml(user.email || "-")}`,
user.parent_id ? `${ticket.id}`,
escapeHtml(ticket.user_email || `#${ticket.user_id}`),
escapeHtml(ticket.subject || "-"),
renderStatus(ticketStatusText(ticket.status)),
escapeHtml(formatDate(ticket.updated_at || ticket.created_at)),
`${ticket.message_count || 0}`
]);
return [
wrapTable(["ID", "用户", "主题", "状态", "更新时间", "消息数"], rows),
renderPagination(state.tickets.pagination)
].join("");
}
function renderRealnameManage() {
const rows = state.realname.list.map((record) => [
`${record.id}`,
escapeHtml(record.email || "-"),
escapeHtml(record.real_name || "-"),
`${escapeHtml(record.identity_no_masked || "-")}`,
renderStatus(record.status_label || record.status || "unverified"),
escapeHtml(formatDate(record.submitted_at)),
renderActionRow([
buttonAction("通过", "realname-review", null, `data-user-id="${record.id}" data-status="approved"`),
buttonAction("驳回", "realname-review", null, `data-user-id="${record.id}" data-status="rejected"`)
])
]);
return [
'",
wrapTable(["用户ID", "邮箱", "姓名", "证件号", "状态", "提交时间", "操作"], rows),
renderPagination(state.realname.pagination)
].join("");
}
function renderOnlineDevices() {
const rows = state.devices.list.map((item) => [
`${item.id}`,
escapeHtml(item.email || "-"),
escapeHtml(item.subscription_name || "-"),
escapeHtml((item.online_devices || []).join(", ") || "-"),
`${item.online_count || 0}`,
escapeHtml(item.last_online_text || "-")
]);
return [
wrapTable(["用户ID", "邮箱", "套餐", "在线 IP", "设备数", "最后在线"], rows),
renderPagination(state.devices.pagination)
].join("");
}
function renderIPv6Manage() {
const rows = state.ipv6.list.map((item) => {
const isActive = item.is_active || item.status === "active";
const isDisabled = item.status === "disabled";
const actions = [];
if (isActive) {
actions.push(buttonAction("关闭", "ipv6-disable", null, `data-user-id="${item.id}"`));
actions.push(buttonAction("同步密码", "ipv6-sync-password", null, `data-user-id="${item.id}"`));
} else {
actions.push(buttonAction("开通", "ipv6-enable-modal", null, `data-user-id="${item.id}" data-email="${escapeHtml(item.email || "")}"`));
}
if (isDisabled) {
actions.unshift(buttonAction("重新开通", "ipv6-enable-modal", null, `data-user-id="${item.id}" data-email="${escapeHtml(item.email || "")}"`));
}
return [
`${item.id}`,
[
`${escapeHtml(item.email || "-")}`,
`| ${escapeHtml(header)} | `).join(""), "
|---|
| ${cell} | `).join("")}
${escapeHtml(hint || "")}
`, "${escapeHtml(fromId)}→${escapeHtml(toId)}`;
}
function hiddenField(name, value) {
return ``;
}
function inputField(label, name, value, type, required, extraAttrs) {
return [
'用户: ${escapeHtml(data.email || data.userId)}
`, '" ].join(""); } function setBusy(value) { state.busy = value; render(true); } function show(message, type) { state.message = message; state.messageType = type || "info"; render(true); window.clearTimeout(show._timer); show._timer = window.setTimeout(function () { state.message = ""; render(true); }, 3000); } async function adminPost(url, body) { try { const response = await request(url, { method: "POST", body }); show("操作成功", "success"); return response; } catch (error) { show(error.message || "操作失败", "error"); throw error; } } async function request(url, options) { const opt = options || {}; const headers = { "Content-Type": "application/json" }; if (opt.auth !== false && state.token) { headers.Authorization = state.token; } const response = await fetch(url, { method: opt.method || "GET", headers, body: opt.body ? JSON.stringify(opt.body) : undefined }); const payload = await response.json().catch(() => null); if (!response.ok) { if (response.status === 401) { clearSession(); render(); } throw new Error((payload && (payload.message || payload.msg)) || "请求失败"); } return payload; } function normalizeListPayload(payload, listKey) { const key = listKey || "list"; return { list: toArray(payload, key), pagination: payload && payload.pagination ? payload.pagination : null }; } function unwrap(payload) { return payload && typeof payload.data !== "undefined" ? payload.data : payload; } function toArray(value, key) { if (Array.isArray(value)) { return value; } if (!value || typeof value !== "object") { return []; } if (Array.isArray(value[key || "data"])) { return value[key || "data"]; } if (Array.isArray(value.list)) { return value.list; } if (Array.isArray(value.data)) { return value.data; } return []; } function serializeForm(form) { const values = {}; const section = form.querySelector('input[name="__section"]'); const formData = new window.FormData(form); formData.forEach((rawValue, key) => { if (key === "__section") { return; } if (key === "show" || key === "renew" || key === "sell") { values[key] = String(rawValue) === "1"; return; } if (key === "expired_at") { values[key] = rawValue ? Math.floor(new Date(rawValue).getTime() / 1000) : null; return; } if (key === "match") { values[key] = String(rawValue) .split(/\r?\n/) .map((item) => item.trim()) .filter(Boolean); return; } if (["id", "group_id", "transfer_enable", "speed_limit", "balance", "plan_id", "device_limit", "server_port", "value", "user_id"].includes(key)) { values[key] = rawValue === "" ? null : Number(rawValue); return; } values[key] = rawValue; }); if (section) { return values; } return values; } function getCurrentPage() { return state.pagination[state.route] || 1; } function setCurrentPage(page) { state.pagination[state.route] = page > 0 ? page : 1; } function readToken() { return window.localStorage.getItem("__gopanel_admin_auth__") || ""; } function saveToken(token) { window.localStorage.setItem("__gopanel_admin_auth__", /^Bearer /.test(token) ? token : `Bearer ${token}`); } function clearSession() { state.token = ""; state.user = null; state.modal = null; window.localStorage.removeItem("__gopanel_admin_auth__"); } function readRoute() { return (window.location.hash || "#overview").slice(1); } function normalizeRoute(route) { return ROUTE_META[route] ? route : "overview"; } function getSecurePath() { if (state.config && state.config.safe && state.config.safe.secure_path) { return String(state.config.safe.secure_path).replace(/^\//, ""); } return String(cfg.securePath || "admin").replace(/^\//, ""); } function formatTraffic(bytes) { const value = Number(bytes || 0); if (!value) { return "0 B"; } const units = ["B", "KB", "MB", "GB", "TB"]; let index = 0; let size = value; while (size >= 1024 && index < units.length - 1) { size /= 1024; index += 1; } return `${size.toFixed(size >= 100 ? 0 : 2)} ${units[index]}`; } function formatMoney(amount) { return `¥${((Number(amount) || 0) / 100).toFixed(2)}`; } function formatDate(value) { if (!value) { return "-"; } const date = new Date(typeof value === "number" ? value * 1000 : value); if (Number.isNaN(date.getTime())) { return String(value); } return date.toLocaleString(); } function formatSignedPercent(value) { const num = Number(value || 0); return `${num >= 0 ? "+" : ""}${num.toFixed(2)}%`; } function orderStatusText(status) { if (status === 0) return "pending"; if (status === 3) return "paid"; if (status === 2) return "cancelled"; return String(status); } function ticketStatusText(status) { if (status === 0) return "pending"; if (status === 1) return "answered"; if (status === 2) return "closed"; return String(status); } function renderStatus(status) { const text = String(status || "-"); const normalized = text.toLowerCase(); let type = "danger"; if (/(ok|active|visible|approved|paid|answered|online|enabled)/.test(normalized)) { type = "ok"; } else if (/(pending|warn|unverified|eligible|ready)/.test(normalized)) { type = "warn"; } else if (/(disabled)/.test(normalized)) { type = "warn"; } return `${escapeHtml(text)}`; } function escapeHtml(value) { return String(value == null ? "" : value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function getRouteTitle(route) { return route.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()); } })();