修复节点无法编辑的错误
Some checks failed
build / build (api, amd64, linux) (push) Failing after -50s
build / build (api, arm64, linux) (push) Failing after -52s
build / build (api.exe, amd64, windows) (push) Failing after -51s

This commit is contained in:
CN-JS-HuiBai
2026-04-18 10:31:31 +08:00
parent 6e75b7d7d5
commit 98379b21f4
6 changed files with 275 additions and 130 deletions

View File

@@ -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 || "-"),
[
`<strong>${escapeHtml(item.email || "-")}</strong>`,
`<div class="subtle-text">${escapeHtml(item.ipv6_email || "-")}</div>`
].join(""),
escapeHtml(item.plan_name || "-"),
renderStatus(item.status_label || item.status || "-"),
escapeHtml(formatDate(item.updated_at)),
renderActionRow([
buttonAction("开通/同步", "ipv6-enable", null, `data-user-id="${item.id}"`),
buttonAction("同步密码", "ipv6-sync-password", null, `data-user-id="${item.id}"`)
])
]);
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 [
`<code>${item.id}</code>`,
[
`<strong>${escapeHtml(item.email || "-")}</strong>`,
`<div class="subtle-text">${escapeHtml(item.ipv6_email || "-")}</div>`
].join(""),
escapeHtml(item.plan_name || "-"),
renderStatus(item.status_label || item.status || "-"),
escapeHtml(formatDate(item.updated_at)),
renderActionRow(actions)
];
});
return [
wrapTable(["主从关系", "账号", "套餐", "状态", "更新时间", "操作"], rows),
wrapTable(["用户ID", "账号", "IPv6 套餐", "状态", "更新时间", "操作"], rows),
renderPagination(state.ipv6.pagination)
].join("");
}
@@ -1034,7 +1074,9 @@
? renderGroupForm(modal.data)
: modal.type === "route"
? renderRouteForm(modal.data)
: "";
: modal.type === "ipv6-enable"
? renderIPv6EnableForm(modal.data)
: "";
return [
'<div class="modal-card">',
@@ -1276,6 +1318,18 @@
return `<div class="toolbar toolbar-spaced"><button class="btn btn-primary" type="submit">${escapeHtml(label)}</button></div>`;
}
function renderIPv6EnableForm(data) {
return [
`<h2>开通 IPv6 子账号</h2>`,
`<p>用户: <strong>${escapeHtml(data.email || data.userId)}</strong></p>`,
'<form data-form="ipv6-enable-save">',
hiddenField("user_id", data.userId),
selectField("IPv6 套餐", "plan_id", state.plans, 0, true),
submitRow("确认开通"),
"</form>"
].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 `<span class="status-pill status-${type}">${escapeHtml(text)}</span>`;
}