${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() }; 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); 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 = await request(`${cfg.api.ipv6Base}/users?page=${page}&per_page=20`); state.ipv6 = normalizeListPayload(payload); } 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") { 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") { 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") { await adminPost(`${cfg.api.adminBase}/order/paid`, { trade_no: actionEl.getAttribute("data-trade") }); await hydrateRoute(); return; } if (action === "order-cancel") { 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(); const item = state.nodes.find((row) => String(row.id) === actionEl.getAttribute("data-id")); state.modal = { type: "node", data: item || {} }; render(); return; } if (action === "node-copy") { event.preventDefault(); await adminPost(`${cfg.api.adminBase}/server/manage/copy`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); return; } if (action === "node-delete") { event.preventDefault(); if (confirm("确认删除该节点吗?")) { await adminPost(`${cfg.api.adminBase}/server/manage/drop`, { id: Number(actionEl.getAttribute("data-id")) }); await hydrateRoute(); } return; } if (action === "ipv6-enable") { const id = actionEl.getAttribute("data-user-id"); // Use prompt with plan selection for simplicity in this minimal framework const planNames = state.plans.map(p => `${p.id}: ${p.name}`).join("\n"); const planID = prompt(`请输入要分配给该用户的 IPv6 套餐 ID:\n\n${planNames}`, ""); if (planID === null) return; await adminPost(`${cfg.api.adminBase}/user-add-ipv6-subscription/enable/${id}`, { plan_id: Number(planID) || 0 }); await hydrateRoute(); return; } if (action === "ipv6-disable") { const id = actionEl.getAttribute("data-user-id"); if (confirm("确认要关闭该用户的 IPv6 订阅吗?(将封禁从属账号)")) { await adminPost(`${cfg.api.adminBase}/user-add-ipv6-subscription/disable/${id}`, {}); await hydrateRoute(); } return; } if (action === "ipv6-sync-password") { await adminPost(`${cfg.api.adminBase}/user-add-ipv6-subscription/sync-password/${actionEl.getAttribute("data-user-id")}`, {}); return; } if (action === "node-toggle-visible") { event.preventDefault(); const input = actionEl.querySelector("input"); if (!input) { return; } input.checked = !input.checked; await adminPost(`${cfg.api.adminBase}/server/manage/update`, { id: Number(actionEl.getAttribute("data-id")), show: input.checked ? 1 : 0 }); 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") { const userId = actionEl.getAttribute("data-user-id"); await adminPost(`${cfg.api.ipv6Base}/enable/${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) { return; } await adminPost(target, body); if (action !== "config-save") { state.modal = null; } await hydrateRoute(); } function render() { root.innerHTML = state.user ? renderDashboard() : renderLogin(); if (state.modal) { const modalShell = document.createElement("div"); modalShell.className = "modal-overlay"; modalShell.innerHTML = renderModalContent(); root.appendChild(modalShell); } if (state.busy) { const overlay = document.createElement("div"); overlay.className = "busy-overlay"; overlay.innerHTML = '
使用管理员账号登录 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 roots = state.nodes.filter((node) => !node.parent_id);
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) => [
renderRelationChip(item.id, item.shadow_user_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 [
'