From 3960d6025f42b94e3d284beb26c9780fe1767207 Mon Sep 17 00:00:00 2001 From: xtcnet Date: Sun, 8 Mar 2026 10:39:17 +0700 Subject: [PATCH] fix: resolve 404 on server creation and 500 on client creation and reposition buttons to tables --- backend/internal/wireguard.js | 123 ++++++++++++++++++++++++- backend/routes/wireguard.js | 56 +++++++++++ frontend/src/pages/WireGuard/index.tsx | 95 ++++++++++++------- 3 files changed, 237 insertions(+), 37 deletions(-) diff --git a/backend/internal/wireguard.js b/backend/internal/wireguard.js index d856ec8..9b0ee19 100644 --- a/backend/internal/wireguard.js +++ b/backend/internal/wireguard.js @@ -233,7 +233,9 @@ const internalWireguard = { * Create a new WireGuard client */ async createClient(knex, data) { - const iface = await this.getOrCreateInterface(knex); + const iface = data.interface_id + ? await knex("wg_interface").where("id", data.interface_id).first() + : await this.getOrCreateInterface(knex); // Generate keys const privateKey = await wgHelpers.generatePrivateKey(); @@ -241,7 +243,7 @@ const internalWireguard = { const preSharedKey = await wgHelpers.generatePreSharedKey(); // Allocate IP - const existingClients = await knex("wg_client").select("ipv4_address"); + const existingClients = await knex("wg_client").select("ipv4_address").where("interface_id", iface.id); const allocatedIPs = existingClients.map((c) => c.ipv4_address); const ipv4Address = wgHelpers.findNextAvailableIP(iface.ipv4_cidr, allocatedIPs); @@ -255,6 +257,7 @@ const internalWireguard = { allowed_ips: data.allowed_ips || WG_DEFAULT_ALLOWED_IPS, persistent_keepalive: data.persistent_keepalive || WG_DEFAULT_PERSISTENT_KEEPALIVE, expires_at: data.expires_at || null, + interface_id: iface.id, created_on: knex.fn.now(), modified_on: knex.fn.now(), }; @@ -356,6 +359,122 @@ const internalWireguard = { return wgHelpers.generateQRCodeSVG(config); }, + /** + * Create a new WireGuard Interface Endpoint + */ + async createInterface(knex, data) { + const existingIfaces = await knex("wg_interface").select("name", "listen_port"); + const newIndex = existingIfaces.length; + + const name = `wg${newIndex}`; + const listen_port = 51820 + newIndex; + + // Attempt to grab /24 subnets, ex 10.8.0.0/24 -> 10.8.1.0/24 + const ipv4_cidr = `10.8.${newIndex}.1/24`; + + // Generate keys + const privateKey = await wgHelpers.generatePrivateKey(); + const publicKey = await wgHelpers.getPublicKey(privateKey); + + const insertData = { + name, + private_key: privateKey, + public_key: publicKey, + listen_port, + ipv4_cidr, + ipv6_cidr: null, + mtu: data.mtu || WG_DEFAULT_MTU, + dns: data.dns || WG_DEFAULT_DNS, + host: data.host || WG_HOST, + isolate_clients: data.isolate_clients || false, + post_up: "iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE", + post_down: "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE", + created_on: knex.fn.now(), + modified_on: knex.fn.now(), + }; + + const [id] = await knex("wg_interface").insert(insertData); + + const newIface = await knex("wg_interface").where("id", id).first(); + + // Regenerate config and restart the new interface seamlessly + const parsed = wgHelpers.parseCIDR(newIface.ipv4_cidr); + let configContent = wgHelpers.generateServerInterface({ + privateKey: newIface.private_key, + address: `${parsed.firstHost}/${parsed.prefix}`, + listenPort: newIface.listen_port, + mtu: newIface.mtu, + dns: null, + postUp: newIface.post_up, + postDown: newIface.post_down, + }); + + fs.writeFileSync(`${WG_CONFIG_DIR}/${name}.conf`, configContent, { mode: 0o600 }); + await wgHelpers.wgUp(name); + + return newIface; + }, + + /** + * Update an existing Interface + */ + async updateInterface(knex, id, data) { + const iface = await knex("wg_interface").where("id", id).first(); + if (!iface) throw new Error("Interface not found"); + + const updateData = { modified_on: knex.fn.now() }; + if (data.host !== undefined) updateData.host = data.host; + if (data.dns !== undefined) updateData.dns = data.dns; + if (data.mtu !== undefined) updateData.mtu = data.mtu; + if (data.isolate_clients !== undefined) updateData.isolate_clients = data.isolate_clients; + + await knex("wg_interface").where("id", id).update(updateData); + + await this.saveConfig(knex); // This will re-render IPTables and sync + return knex("wg_interface").where("id", id).first(); + }, + + /** + * Delete an interface + */ + async deleteInterface(knex, id) { + const iface = await knex("wg_interface").where("id", id).first(); + if (!iface) throw new Error("Interface not found"); + + try { + await wgHelpers.wgDown(iface.name); + if (fs.existsSync(`${WG_CONFIG_DIR}/${iface.name}.conf`)) { + fs.unlinkSync(`${WG_CONFIG_DIR}/${iface.name}.conf`); + } + } catch (e) { + logger.warn(`Failed to teardown WG interface ${iface.name}: ${e.message}`); + } + + // Cascading deletion handles clients and links in DB schema + await knex("wg_interface").where("id", id).del(); + return { success: true }; + }, + + /** + * Update Peering Links between WireGuard Interfaces + */ + async updateInterfaceLinks(knex, id, linkedServers) { + // Clean up existing links where this interface is involved + await knex("wg_server_link").where("interface_id_1", id).orWhere("interface_id_2", id).del(); + + // Insert new ones + for (const peerId of linkedServers) { + if (peerId !== Number(id)) { + await knex("wg_server_link").insert({ + interface_id_1: id, + interface_id_2: peerId + }); + } + } + await this.saveConfig(knex); + return { success: true }; + }, + /** * Get the WireGuard interfaces info */ diff --git a/backend/routes/wireguard.js b/backend/routes/wireguard.js index 45abcae..a685c1c 100644 --- a/backend/routes/wireguard.js +++ b/backend/routes/wireguard.js @@ -22,6 +22,62 @@ router.get("/", async (_req, res, next) => { } }); +/** + * POST /api/wireguard + * Create a new WireGuard interface + */ +router.post("/", async (req, res, next) => { + try { + const knex = db(); + const iface = await internalWireguard.createInterface(knex, req.body); + res.status(201).json(iface); + } catch (err) { + next(err); + } +}); + +/** + * PUT /api/wireguard/:id + * Update a WireGuard interface + */ +router.put("/:id", async (req, res, next) => { + try { + const knex = db(); + const iface = await internalWireguard.updateInterface(knex, req.params.id, req.body); + res.status(200).json(iface); + } catch (err) { + next(err); + } +}); + +/** + * DELETE /api/wireguard/:id + * Delete a WireGuard interface + */ +router.delete("/:id", async (req, res, next) => { + try { + const knex = db(); + const result = await internalWireguard.deleteInterface(knex, req.params.id); + res.status(200).json(result); + } catch (err) { + next(err); + } +}); + +/** + * POST /api/wireguard/:id/links + * Update peering links for a WireGuard interface + */ +router.post("/:id/links", async (req, res, next) => { + try { + const knex = db(); + const result = await internalWireguard.updateInterfaceLinks(knex, req.params.id, req.body.linked_servers || []); + res.status(200).json(result); + } catch (err) { + next(err); + } +}); + /** * GET /api/wireguard/client * List all WireGuard clients with live status diff --git a/frontend/src/pages/WireGuard/index.tsx b/frontend/src/pages/WireGuard/index.tsx index 2961b99..9e1fd7d 100644 --- a/frontend/src/pages/WireGuard/index.tsx +++ b/frontend/src/pages/WireGuard/index.tsx @@ -63,6 +63,7 @@ function WireGuard() { const toggleClient = useToggleWgClient(); const [clientFilter, setClientFilter] = useState(""); + const [serverFilter, setServerFilter] = useState(""); if (clientsLoading || ifacesLoading) { return ; @@ -76,6 +77,14 @@ function WireGuard() { c.interfaceName?.toLowerCase().includes(clientFilter.toLowerCase()), ); + const filteredInterfaces = interfaces?.filter( + (i) => + !serverFilter || + i.name.toLowerCase().includes(serverFilter.toLowerCase()) || + i.ipv4Cidr.includes(serverFilter) || + (i.host && i.host.toLowerCase().includes(serverFilter.toLowerCase())) + ); + // Server Handlers const handleNewServer = async () => { const result = (await EasyModal.show(WireGuardServerModal)) as any; @@ -147,30 +156,6 @@ function WireGuard() { WireGuard VPN -
-
- {activeTab === "servers" ? ( - - ) : ( - - )} -
-
@@ -197,15 +182,30 @@ function WireGuard() { {activeTab === "clients" && (
-

Clients

- setClientFilter(e.target.value)} - style={{ width: 250 }} - /> +
+
+ Listing WireGuard Clients +
+
+ setClientFilter(e.target.value)} + style={{ width: 250 }} + /> + +
+
@@ -327,6 +327,31 @@ function WireGuard() { {activeTab === "servers" && (
+
+
+
+ Listing WireGuard Servers +
+
+ setServerFilter(e.target.value)} + style={{ width: 250 }} + /> + +
+
+
@@ -340,7 +365,7 @@ function WireGuard() { - {interfaces?.map((iface) => ( + {filteredInterfaces?.map((iface) => (
{iface.name} @@ -358,7 +383,7 @@ function WireGuard() {
{iface.linkedServers?.length || 0} - {iface.linkedServers?.length > 0 && ( + {iface.linkedServers?.length > 0 && interfaces && ( ({interfaces.filter(i => iface.linkedServers.includes(i.id)).map(i => i.name).join(", ")})