diff --git a/cmd/api/main_entry.go b/cmd/api/main_entry.go index 3d31f47..1b249da 100644 --- a/cmd/api/main_entry.go +++ b/cmd/api/main_entry.go @@ -139,8 +139,7 @@ func registerUserRoutes(v1 *gin.RouterGroup) { user.GET("/real-name-verification/status", handler.PluginRealNameStatus) user.POST("/real-name-verification/submit", handler.PluginRealNameSubmit) user.GET("/user-online-devices/get-ip", handler.PluginUserOnlineDevicesGetIP) - user.POST("/user-add-ipv6-subscription/enable", handler.PluginUserAddIPv6Enable) - user.POST("/user-add-ipv6-subscription/sync-password", handler.PluginUserAddIPv6SyncPassword) + // User IPv6 subscription - read-only status check (enable/disable is admin-only) user.GET("/user-add-ipv6-subscription/check", handler.PluginUserAddIPv6Check) } @@ -295,6 +294,7 @@ func registerAdminRoutesV2(v2 *gin.RouterGroup) { admin.GET("/user-add-ipv6-subscription/config", handler.AdminIPv6SubscriptionConfigFetch) admin.POST("/user-add-ipv6-subscription/config", handler.AdminIPv6SubscriptionConfigSave) admin.POST("/user-add-ipv6-subscription/enable/:userId", handler.AdminIPv6SubscriptionEnable) + admin.POST("/user-add-ipv6-subscription/disable/:userId", handler.AdminIPv6SubscriptionDisable) admin.POST("/user-add-ipv6-subscription/sync-password/:userId", handler.AdminIPv6SubscriptionSyncPassword) } diff --git a/frontend/admin/app.js b/frontend/admin/app.js index e056ef8..1e6342b 100644 --- a/frontend/admin/app.js +++ b/frontend/admin/app.js @@ -173,8 +173,12 @@ 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`); + 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)); } @@ -337,18 +341,20 @@ } if (action === "node-copy") { - await adminPost(`${cfg.api.adminBase}/server/manage/copy`, { - id: Number(actionEl.getAttribute("data-id")) - }); + 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") { if (confirm("确认删除该节点吗?")) { - await adminPost(`${cfg.api.adminBase}/server/manage/drop`, { - id: Number(actionEl.getAttribute("data-id")) - }); + 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; @@ -356,15 +362,20 @@ if (action === "node-toggle-visible") { event.preventDefault(); + event.stopPropagation(); + const nodeId = Number(actionEl.getAttribute("data-id")); 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 - }); + 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; } @@ -436,10 +447,20 @@ return; } - if (action === "ipv6-enable") { + if (action === "ipv6-enable-modal") { const userId = actionEl.getAttribute("data-user-id"); - await adminPost(`${cfg.api.ipv6Base}/enable/${userId}`, {}); - await hydrateRoute(); + 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; } @@ -505,6 +526,14 @@ } 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; } @@ -953,23 +982,34 @@ } function renderIPv6Manage() { - const rows = state.ipv6.list.map((item) => [ - renderRelationChip(item.id, item.shadow_user_id || "-"), - [ - `${escapeHtml(item.email || "-")}`, - `
${item.id}`,
+ [
+ `${escapeHtml(item.email || "-")}`,
+ `用户: ${escapeHtml(data.email || data.userId)}
`, + '" + ].join(""); + } + function setBusy(value) { state.busy = value; render(); @@ -1293,9 +1347,14 @@ } async function adminPost(url, body) { - const response = await request(url, { method: "POST", body }); - show("操作成功", "success"); - return response; + 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) { @@ -1382,7 +1441,7 @@ return; } - if (["id", "group_id", "transfer_enable", "speed_limit", "balance", "plan_id", "device_limit", "server_port", "value"].includes(key)) { + 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; } @@ -1492,6 +1551,8 @@ type = "ok"; } else if (/(pending|warn|unverified|eligible|ready)/.test(normalized)) { type = "warn"; + } else if (/(disabled)/.test(normalized)) { + type = "warn"; } return `${escapeHtml(text)}`; } diff --git a/frontend/templates/admin_plugin_panel.html b/frontend/templates/admin_plugin_panel.html index 2f32d56..e074bce 100644 --- a/frontend/templates/admin_plugin_panel.html +++ b/frontend/templates/admin_plugin_panel.html @@ -761,10 +761,9 @@ const list = payload.list || []; document.getElementById("thead").innerHTML = `用户: ${email || userId}
+ + +