Add wireguard-ui-multi core: multi-server DB, WireGuard manager, REST API, UI, installers
Implements the from-scratch multi-server WireGuard management fork per CLAUDE.md spec: sqlite schema (servers/peers/audit_log/users), Curve25519 key generation, per-interface config rendering + wg-quick/systemd control, nftables hook scaffolding, session+CSRF-protected REST API with QR code and config download endpoints, a minimal vanilla-JS web UI, legacy wg0.conf migration, and both a native installer and a Proxmox LXC provisioning script (with auto-detected latest Debian template). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3d6608ef80
commit
3b3ffd8ebf
@@ -0,0 +1,106 @@
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
|
||||
return match ? decodeURIComponent(match[1]) : "";
|
||||
}
|
||||
|
||||
async function apiFetch(url, options) {
|
||||
options = options || {};
|
||||
options.headers = options.headers || {};
|
||||
if (options.method && options.method !== "GET") {
|
||||
options.headers["X-CSRF-Token"] = getCookie("wgm_csrf");
|
||||
}
|
||||
const res = await fetch(url, options);
|
||||
if (res.status === 401) {
|
||||
window.location.href = "/login";
|
||||
throw new Error("unauthenticated");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async function loadServers() {
|
||||
const tbody = document.querySelector("#servers tbody");
|
||||
tbody.innerHTML = "";
|
||||
const res = await apiFetch("/api/servers");
|
||||
if (!res.ok) return;
|
||||
const servers = await res.json();
|
||||
|
||||
for (const s of servers) {
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
const nameTd = document.createElement("td");
|
||||
const link = document.createElement("a");
|
||||
link.href = "/servers/" + s.ID;
|
||||
link.textContent = s.Name;
|
||||
nameTd.appendChild(link);
|
||||
|
||||
const ifaceTd = document.createElement("td");
|
||||
ifaceTd.textContent = s.InterfaceName;
|
||||
|
||||
const portTd = document.createElement("td");
|
||||
portTd.textContent = s.ListenPort;
|
||||
|
||||
const statusTd = document.createElement("td");
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "badge " + (s.status === "UP" ? "up" : "down");
|
||||
badge.textContent = s.status;
|
||||
statusTd.appendChild(badge);
|
||||
|
||||
const actionsTd = document.createElement("td");
|
||||
actionsTd.appendChild(makeActionButton("Start", () => serverAction(s.ID, "start")));
|
||||
actionsTd.appendChild(makeActionButton("Stop", () => serverAction(s.ID, "stop")));
|
||||
actionsTd.appendChild(makeActionButton("Reload", () => serverAction(s.ID, "reload")));
|
||||
|
||||
tr.appendChild(nameTd);
|
||||
tr.appendChild(ifaceTd);
|
||||
tr.appendChild(portTd);
|
||||
tr.appendChild(statusTd);
|
||||
tr.appendChild(actionsTd);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function makeActionButton(label, onClick) {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = label;
|
||||
btn.className = "secondary";
|
||||
btn.addEventListener("click", onClick);
|
||||
return btn;
|
||||
}
|
||||
|
||||
async function serverAction(id, action) {
|
||||
await apiFetch("/api/servers/" + id + "/" + action, { method: "POST" });
|
||||
loadServers();
|
||||
}
|
||||
|
||||
document.getElementById("new-server").addEventListener("click", async () => {
|
||||
const name = prompt("Name des Servers (z.B. WGhome):");
|
||||
if (!name) return;
|
||||
const interfaceName = prompt("Interface (z.B. wg-home):");
|
||||
if (!interfaceName) return;
|
||||
const listenPort = parseInt(prompt("Listen Port (z.B. 51822):"), 10);
|
||||
if (!listenPort) return;
|
||||
const addressRange = prompt("Address Range (z.B. 10.20.22.0/24):");
|
||||
if (!addressRange) return;
|
||||
const dns = prompt("DNS (optional):") || "";
|
||||
|
||||
const res = await apiFetch("/api/servers", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
interface_name: interfaceName,
|
||||
listen_port: listenPort,
|
||||
address_range: addressRange,
|
||||
dns: dns,
|
||||
mtu: 1420,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
alert(data.error || "Server konnte nicht erstellt werden.");
|
||||
return;
|
||||
}
|
||||
loadServers();
|
||||
});
|
||||
|
||||
loadServers();
|
||||
Reference in New Issue
Block a user