import { IconPlus, IconDownload, IconQrcode, IconPlayerPlay, IconPlayerPause, IconTrash, IconNetwork, IconServer, IconEdit, IconLink, } from "@tabler/icons-react"; import EasyModal from "ez-modal-react"; import { useState } from "react"; import { downloadWgConfig } from "src/api/backend/wireguard"; import { Loading } from "src/components"; import { useWgClients, useWgInterfaces, useCreateWgInterface, useUpdateWgInterface, useDeleteWgInterface, useUpdateWgInterfaceLinks, useCreateWgClient, useDeleteWgClient, useToggleWgClient, } from "src/hooks/useWireGuard"; import WireGuardClientModal from "src/modals/WireGuardClientModal"; import WireGuardServerModal from "src/modals/WireGuardServerModal"; import WireGuardLinkedServersModal from "src/modals/WireGuardLinkedServersModal"; import WireGuardQRModal from "src/modals/WireGuardQRModal"; function formatBytes(bytes: number | null): string { if (bytes === null || bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; } function timeAgo(date: string | null): string { if (!date) return "Never"; const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000); if (seconds < 60) return `${seconds}s ago`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; return `${Math.floor(seconds / 86400)}d ago`; } 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(); const updateLinks = useUpdateWgInterfaceLinks(); const createClient = useCreateWgClient(); const deleteClient = useDeleteWgClient(); const toggleClient = useToggleWgClient(); const [clientFilter, setClientFilter] = useState(""); const [serverFilter, setServerFilter] = useState(""); 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 || 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; if (result) { createServer.mutate(result); } }; const handleEditServer = async (wgInterface: any) => { const result = (await EasyModal.show(WireGuardServerModal, { wgInterface })) as any; if (result) { updateServer.mutate({ id: wgInterface.id, data: result }); } }; const handleManageLinks = async (wgInterface: any) => { if (!interfaces) return; const result = (await EasyModal.show(WireGuardLinkedServersModal, { wgInterface, allInterfaces: interfaces })) as any; if (result) { updateLinks.mutate({ id: wgInterface.id, data: result }); } }; const handleDeleteServer = async (id: number, name: string) => { if (window.confirm(`Are you absolutely sure you want to delete server "${name}"? This will also delete all associated clients and peering links.`)) { deleteServer.mutate(id); } }; // Client Handlers const handleNewClient = async () => { if (!interfaces || interfaces.length === 0) { alert("Bạn phải tạo một WireGuard Server trước khi tạo Client."); return; } const result = (await EasyModal.show(WireGuardClientModal, { interfaces: interfaces || [] })) as any; if (result && result.name && result.interface_id) { createClient.mutate({ name: result.name, interface_id: result.interface_id }); } }; const handleDeleteClient = async (id: number, name: string) => { if (window.confirm(`Are you sure you want to delete client "${name}"?`)) { deleteClient.mutate(id); } }; const handleToggleClient = (id: number, currentlyEnabled: boolean) => { toggleClient.mutate({ id, enabled: !currentlyEnabled }); }; const handleQR = (id: number, name: string) => { EasyModal.show(WireGuardQRModal, { clientId: id, clientName: name }); }; const handleDownload = (id: number, name: string) => { const cleanName = name.replace(/[^a-zA-Z0-9_.-]/g, "-").substring(0, 32); downloadWgConfig(id, cleanName); }; return (
{/* Page Header */}

WireGuard VPN

{/* Tabs */}
{/* Tab Content */} {activeTab === "clients" && (
Listing WireGuard Clients
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) && ( )}
Status Name Server IP Address Last Handshake Transfer ↓ / ↑ 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) => ( ))} {(!interfaces || interfaces.length === 0) && ( )}
Interface Subnet Port Endpoint Host Isolation Links Actions
{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(", ")}) )}
No WireGuard servers configured.
)}
); } export default WireGuard;