139 lines
5.0 KiB
TypeScript
139 lines
5.0 KiB
TypeScript
"use server";
|
||
|
||
import {
|
||
CustomProxyValidationError,
|
||
parseCustomProxyYaml,
|
||
} from "@/lib/custom-proxy";
|
||
import { revalidatePath } from "next/cache";
|
||
|
||
import { ensureDb } from "@/lib/db";
|
||
import { requireAdminAccess } from "@/lib/session";
|
||
import {
|
||
createNode,
|
||
deleteNode,
|
||
normalizeNodeType,
|
||
updateNode,
|
||
} from "@/lib/store";
|
||
|
||
function boolFromForm(value: FormDataEntryValue | null) {
|
||
return value === "on";
|
||
}
|
||
|
||
function validateCustomYaml(nodeType: string, formData: FormData) {
|
||
if (nodeType !== "custom") return;
|
||
try {
|
||
parseCustomProxyYaml(String(formData.get("custom_config_yaml") ?? ""));
|
||
} catch (error) {
|
||
if (error instanceof CustomProxyValidationError)
|
||
return { error: error.message };
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
export async function createNodeAction(formData: FormData) {
|
||
await requireAdminAccess();
|
||
const nodeType = normalizeNodeType(
|
||
String(formData.get("node_type") ?? "hy2"),
|
||
);
|
||
const validation = validateCustomYaml(nodeType, formData);
|
||
if (validation) return validation;
|
||
const name = String(formData.get("name") ?? "").trim();
|
||
const clientName = String(formData.get("client_name") ?? "").trim();
|
||
|
||
await createNode({
|
||
nodeType,
|
||
slug: String(formData.get("slug") ?? "").trim(),
|
||
name,
|
||
authToken: String(formData.get("auth_token") ?? "").trim() || null,
|
||
hy2Listen: String(formData.get("hy2_listen") ?? "").trim() || "",
|
||
serverHost: String(formData.get("server_host") ?? "").trim(),
|
||
serverPort: Number(formData.get("server_port") ?? 443),
|
||
clientName: clientName || (nodeType === "hy2" ? name : ""),
|
||
bandwidthUpMbps: Number(formData.get("bandwidth_up_mbps") ?? 30),
|
||
bandwidthDownMbps: Number(formData.get("bandwidth_down_mbps") ?? 30),
|
||
skipCertVerify: boolFromForm(formData.get("skip_cert_verify")),
|
||
sni: String(formData.get("sni") ?? "").trim() || null,
|
||
customConfigYaml: String(formData.get("custom_config_yaml") ?? ""),
|
||
trafficStatsUrl: String(formData.get("traffic_stats_url") ?? "").trim(),
|
||
trafficStatsListen:
|
||
String(formData.get("traffic_stats_listen") ?? "").trim() || ":9999",
|
||
trafficStatsSecret: String(
|
||
formData.get("traffic_stats_secret") ?? "",
|
||
).trim(),
|
||
enabled: boolFromForm(formData.get("enabled")),
|
||
pollIntervalSeconds: Number(formData.get("poll_interval_seconds") ?? 15),
|
||
});
|
||
|
||
revalidatePath("/admin/nodes");
|
||
revalidatePath("/admin/subscriptions");
|
||
revalidatePath("/admin/dashboard");
|
||
}
|
||
|
||
export async function updateNodeAction(formData: FormData) {
|
||
await requireAdminAccess();
|
||
const nodeType = normalizeNodeType(
|
||
String(formData.get("node_type") ?? "hy2"),
|
||
);
|
||
const validation = validateCustomYaml(nodeType, formData);
|
||
if (validation) return validation;
|
||
const name = String(formData.get("name") ?? "").trim();
|
||
const clientName = String(formData.get("client_name") ?? "").trim();
|
||
|
||
await updateNode({
|
||
id: Number(formData.get("id")),
|
||
nodeType,
|
||
slug: String(formData.get("slug") ?? "").trim(),
|
||
name,
|
||
authToken: String(formData.get("auth_token") ?? "").trim(),
|
||
hy2Listen: String(formData.get("hy2_listen") ?? "").trim() || "",
|
||
serverHost: String(formData.get("server_host") ?? "").trim(),
|
||
serverPort: Number(formData.get("server_port") ?? 443),
|
||
clientName: clientName || (nodeType === "hy2" ? name : ""),
|
||
bandwidthUpMbps: Number(formData.get("bandwidth_up_mbps") ?? 30),
|
||
bandwidthDownMbps: Number(formData.get("bandwidth_down_mbps") ?? 30),
|
||
skipCertVerify: boolFromForm(formData.get("skip_cert_verify")),
|
||
sni: String(formData.get("sni") ?? "").trim() || null,
|
||
customConfigYaml: String(formData.get("custom_config_yaml") ?? ""),
|
||
trafficStatsUrl: String(formData.get("traffic_stats_url") ?? "").trim(),
|
||
trafficStatsListen:
|
||
String(formData.get("traffic_stats_listen") ?? "").trim() || ":9999",
|
||
trafficStatsSecret: String(
|
||
formData.get("traffic_stats_secret") ?? "",
|
||
).trim(),
|
||
enabled: boolFromForm(formData.get("enabled")),
|
||
pollIntervalSeconds: Number(formData.get("poll_interval_seconds") ?? 15),
|
||
});
|
||
|
||
revalidatePath("/admin/nodes");
|
||
revalidatePath("/admin/subscriptions");
|
||
revalidatePath("/admin/dashboard");
|
||
}
|
||
|
||
export async function deleteNodeAction(formData: FormData) {
|
||
await requireAdminAccess();
|
||
await deleteNode(Number(formData.get("id")));
|
||
revalidatePath("/admin/nodes");
|
||
revalidatePath("/admin/subscriptions");
|
||
revalidatePath("/admin/dashboard");
|
||
}
|
||
|
||
export async function updateAnyTLSNodeAction(data: FormData) {
|
||
await requireAdminAccess();
|
||
const name = String(data.get("name") || "").trim();
|
||
if (!name || name.length > 100)
|
||
return { error: "请填写 1–100 字的节点名称。" };
|
||
const db = await ensureDb();
|
||
db.prepare(
|
||
"UPDATE hy2_nodes SET name=?,client_name=?,enabled=?,updated_at=? WHERE id=? AND node_type='anytls'",
|
||
).run(
|
||
name,
|
||
name,
|
||
data.get("enabled") === "on" ? 1 : 0,
|
||
new Date().toISOString(),
|
||
Number(data.get("id")),
|
||
);
|
||
revalidatePath("/admin/nodes");
|
||
revalidatePath("/admin/subscriptions");
|
||
revalidatePath("/admin/dashboard");
|
||
}
|