From ec55362d15b806cfe4050ed2230eef4e07bc7da0 Mon Sep 17 00:00:00 2001 From: xtcnet Date: Sun, 8 Mar 2026 14:17:18 +0700 Subject: [PATCH] feat: fix audit log display, add dashboard counts, restructure WireGuard page, add translations --- backend/internal/report.js | 17 +- frontend/src/components/SiteFooter.tsx | 38 +- .../Table/Formatter/EventFormatter.tsx | 15 +- frontend/src/pages/Dashboard/index.tsx | 69 ++- frontend/src/pages/WireGuard/index.tsx | 522 +++++++++--------- 5 files changed, 373 insertions(+), 288 deletions(-) diff --git a/backend/internal/report.js b/backend/internal/report.js index 59f13fe..5c188b7 100644 --- a/backend/internal/report.js +++ b/backend/internal/report.js @@ -1,3 +1,4 @@ +import db from "../db.js"; import internalDeadHost from "./dead-host.js"; import internalProxyHost from "./proxy-host.js"; import internalRedirectionHost from "./redirection-host.js"; @@ -23,15 +24,29 @@ const internalReport = { return Promise.all(promises); }) - .then((counts) => { + .then(async (counts) => { + const knex = db(); + let wgServers = 0; + let wgClients = 0; + try { + const srvResult = await knex("wg_interface").count("id as count").first(); + wgServers = srvResult?.count || 0; + const cliResult = await knex("wg_client").count("id as count").first(); + wgClients = cliResult?.count || 0; + } catch (_) { + // WireGuard tables may not exist yet + } return { proxy: counts.shift(), redirection: counts.shift(), stream: counts.shift(), dead: counts.shift(), + wgServers: Number(wgServers), + wgClients: Number(wgClients), }; }); }, }; export default internalReport; + diff --git a/frontend/src/components/SiteFooter.tsx b/frontend/src/components/SiteFooter.tsx index 57bb219..35c7387 100644 --- a/frontend/src/components/SiteFooter.tsx +++ b/frontend/src/components/SiteFooter.tsx @@ -34,41 +34,11 @@ export function SiteFooter() {
diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx index 1220fa0..4e2e44b 100644 --- a/frontend/src/components/Table/Formatter/EventFormatter.tsx +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -1,4 +1,4 @@ -import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconNetwork, IconServer, IconShield, IconUser } from "@tabler/icons-react"; import cn from "classnames"; import type { AuditLog } from "src/api/backend"; import { useLocaleState } from "src/context"; @@ -17,6 +17,12 @@ const getEventValue = (event: AuditLog) => { return event.meta?.incomingPort || "N/A"; case "certificate": return event.meta?.domainNames?.join(", ") || event.meta?.niceName || "N/A"; + case "wireguard-server": + return event.meta?.name || `Server #${event.objectId}`; + case "wireguard-client": + return event.meta?.name || `Client #${event.objectId}`; + case "wireguard-server-links": + return `Server #${event.objectId}`; default: return `UNKNOWN EVENT TYPE: ${event.objectType}`; } @@ -58,6 +64,13 @@ const getIcon = (row: AuditLog) => { case "certificate": ico = ; break; + case "wireguard-server": + case "wireguard-server-links": + ico = ; + break; + case "wireguard-client": + ico = ; + break; } return ico; diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index 5cb6486..0fcd985 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -1,9 +1,22 @@ -import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react"; +import { + IconArrowsCross, + IconBolt, + IconBoltOff, + IconDisc, + IconNetwork, + IconServer, +} from "@tabler/icons-react"; import { useNavigate } from "react-router-dom"; import { HasPermission } from "src/components"; import { useHostReport } from "src/hooks"; import { T } from "src/locale"; -import { DEAD_HOSTS, PROXY_HOSTS, REDIRECTION_HOSTS, STREAMS, VIEW } from "src/modules/Permissions"; +import { + DEAD_HOSTS, + PROXY_HOSTS, + REDIRECTION_HOSTS, + STREAMS, + VIEW, +} from "src/modules/Permissions"; const Dashboard = () => { const { data: hostReport } = useHostReport(); @@ -122,6 +135,58 @@ const Dashboard = () => { + {/* WireGuard Servers */} +
+ { + e.preventDefault(); + navigate("/wireguard"); + }} + > +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+ {/* WireGuard Clients */} +
+ { + e.preventDefault(); + navigate("/wireguard"); + }} + > +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
diff --git a/frontend/src/pages/WireGuard/index.tsx b/frontend/src/pages/WireGuard/index.tsx index 9e1fd7d..16e096e 100644 --- a/frontend/src/pages/WireGuard/index.tsx +++ b/frontend/src/pages/WireGuard/index.tsx @@ -48,11 +48,9 @@ function timeAgo(date: string | null): string { } function WireGuard() { - const [activeTab, setActiveTab] = useState<"servers" | "clients">("servers"); - const { data: clients, isLoading: clientsLoading } = useWgClients(); const { data: interfaces, isLoading: ifacesLoading } = useWgInterfaces(); - + const createServer = useCreateWgInterface(); const updateServer = useUpdateWgInterface(); const deleteServer = useDeleteWgInterface(); @@ -61,22 +59,15 @@ function WireGuard() { const createClient = useCreateWgClient(); const deleteClient = useDeleteWgClient(); const toggleClient = useToggleWgClient(); - + const [clientFilter, setClientFilter] = useState(""); const [serverFilter, setServerFilter] = useState(""); + const [selectedServerId, setSelectedServerId] = useState("all"); if (clientsLoading || ifacesLoading) { return ; } - const filteredClients = clients?.filter( - (c) => - !clientFilter || - c.name.toLowerCase().includes(clientFilter.toLowerCase()) || - c.ipv4Address.includes(clientFilter) || - c.interfaceName?.toLowerCase().includes(clientFilter.toLowerCase()), - ); - const filteredInterfaces = interfaces?.filter( (i) => !serverFilter || @@ -85,6 +76,23 @@ function WireGuard() { (i.host && i.host.toLowerCase().includes(serverFilter.toLowerCase())) ); + const filteredClients = clients?.filter((c) => { + // Filter by selected server + if (selectedServerId !== "all" && c.interfaceId !== selectedServerId) { + return false; + } + // Filter by search text + if ( + clientFilter && + !c.name.toLowerCase().includes(clientFilter.toLowerCase()) && + !c.ipv4Address.includes(clientFilter) && + !c.interfaceName?.toLowerCase().includes(clientFilter.toLowerCase()) + ) { + return false; + } + return true; + }); + // Server Handlers const handleNewServer = async () => { const result = (await EasyModal.show(WireGuardServerModal)) as any; @@ -159,235 +167,225 @@ function WireGuard() { - {/* Tabs */} + {/* ================== SERVERS TABLE ================== */} +
+
+

+ + WireGuard Servers + {interfaces?.length || 0} +

+
+
+
+
+
+ Listing WireGuard Servers +
+
+ setServerFilter(e.target.value)} + style={{ width: 250 }} + /> + +
+
+
+ + + + + + + + + + + + + + {filteredInterfaces?.map((iface) => ( + + + + + + + + + + ))} + {(!filteredInterfaces || filteredInterfaces.length === 0) && ( + + + + )} + +
InterfaceSubnetPortEndpoint HostIsolationLinksActions
{iface.name} + {iface.ipv4Cidr} + {iface.listenPort}{iface.host || "None"} + {iface.isolateClients ? ( + Enabled + ) : ( + Disabled + )} + +
+ {iface.linkedServers?.length || 0} + {iface.linkedServers?.length > 0 && interfaces && ( + + ({interfaces.filter(i => iface.linkedServers.includes(i.id)).map(i => i.name).join(", ")}) + + )} +
+
+
+ + + +
+
+ {serverFilter + ? "No servers match your filter" + : "No WireGuard servers configured. Click 'New Server' to create one."} +
+
+
+ + {/* ================== CLIENTS TABLE ================== */}
- - {/* Tab Content */} - {activeTab === "clients" && ( -
-
-
-
- Listing WireGuard Clients -
-
- setClientFilter(e.target.value)} - style={{ width: 250 }} - /> - -
+
+
+
+
+ Listing WireGuard Clients +
+
+ {/* Server filter dropdown */} + + setClientFilter(e.target.value)} + style={{ width: 250 }} + /> +
- - - - - - - - - - - - - - {filteredClients?.map((client) => { - const isConnected = - client.latestHandshakeAt && - Date.now() - new Date(client.latestHandshakeAt).getTime() < - 3 * 60 * 1000; - return ( - - - - - - - - - - ); - })} - {(!filteredClients || filteredClients.length === 0) && ( - - - - )} - -
StatusNameServerIP AddressLast HandshakeTransfer ↓ / ↑Actions
- - {!client.enabled - ? "Disabled" - : isConnected - ? "Connected" - : "Idle"} - - {client.name} -
{client.interfaceName}
-
- {client.ipv4Address} - {timeAgo(client.latestHandshakeAt)} -
- ↓ {formatBytes(client.transferRx)} - ↑ {formatBytes(client.transferTx)} -
-
-
- - - - -
-
- {clientFilter - ? "No clients match your filter" - : "No WireGuard clients yet. Click 'New Client' to create one."} -
- )} - - {activeTab === "servers" && ( -
-
-
-
- Listing WireGuard Servers -
-
- setServerFilter(e.target.value)} - style={{ width: 250 }} - /> - -
-
-
- - - - - - - - - - - - - - {filteredInterfaces?.map((iface) => ( - - +
InterfaceSubnetPortEndpoint HostIsolationLinksActions
{iface.name}
+ + + + + + + + + + + + + {filteredClients?.map((client) => { + const isConnected = + client.latestHandshakeAt && + Date.now() - new Date(client.latestHandshakeAt).getTime() < + 3 * 60 * 1000; + return ( + - - + + + - ))} - {(!interfaces || interfaces.length === 0) && ( - - - - )} - -
StatusNameServerIP AddressLast HandshakeTransfer ↓ / ↑Actions
- {iface.ipv4Cidr} + + {!client.enabled + ? "Disabled" + : isConnected + ? "Connected" + : "Idle"} + {iface.listenPort}{iface.host || "None"}{client.name} - {iface.isolateClients ? ( - Enabled - ) : ( - Disabled - )} +
{client.interfaceName || "—"}
-
- {iface.linkedServers?.length || 0} - {iface.linkedServers?.length > 0 && interfaces && ( - - ({interfaces.filter(i => iface.linkedServers.includes(i.id)).map(i => i.name).join(", ")}) - - )} + {client.ipv4Address} +
{timeAgo(client.latestHandshakeAt)} +
+ ↓ {formatBytes(client.transferRx)} + ↑ {formatBytes(client.transferTx)}
@@ -395,42 +393,66 @@ function WireGuard() { +
- No WireGuard servers configured. -
-
- )} + ); + })} + {(!filteredClients || filteredClients.length === 0) && ( + + + {clientFilter || selectedServerId !== "all" + ? "No clients match your filter" + : "No WireGuard clients yet. Click 'New Client' to create one."} + + + )} + + +
);