import { IconPlus, IconDownload, IconQrcode, IconPlayerPlay, IconPlayerPause, IconTrash, IconNetwork, } 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, useWgInterface, useCreateWgClient, useDeleteWgClient, useToggleWgClient, } from "src/hooks/useWireGuard"; import WireGuardClientModal from "src/modals/WireGuardClientModal"; 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 { data: clients, isLoading: clientsLoading } = useWgClients(); const { data: wgInterface, isLoading: ifaceLoading } = useWgInterface(); const createClient = useCreateWgClient(); const deleteClient = useDeleteWgClient(); const toggleClient = useToggleWgClient(); const [filter, setFilter] = useState(""); if (clientsLoading || ifaceLoading) { return ; } const filteredClients = clients?.filter( (c) => !filter || c.name.toLowerCase().includes(filter.toLowerCase()) || c.ipv4Address.includes(filter), ); const handleNewClient = async () => { const result = (await EasyModal.show(WireGuardClientModal)) as any; if (result && result.name) { createClient.mutate({ name: result.name }); } }; const handleDelete = async (id: number, name: string) => { if (window.confirm(`Are you sure you want to delete client "${name}"?`)) { deleteClient.mutate(id); } }; const handleToggle = (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 (
{/* Interface Info Card */}

WireGuard VPN

{wgInterface && (
Interface
{wgInterface.name}
Public Key
{wgInterface.publicKey}
Address
{wgInterface.ipv4Cidr}
Port
{wgInterface.listenPort}
DNS
{wgInterface.dns}
)} {/* Clients Card */}

Clients ({clients?.length || 0})

setFilter(e.target.value)} style={{ width: 200 }} />
{filteredClients?.map((client) => { const isConnected = client.latestHandshakeAt && Date.now() - new Date(client.latestHandshakeAt).getTime() < 3 * 60 * 1000; return ( ); })} {(!filteredClients || filteredClients.length === 0) && ( )}
Status Name IP Address Last Handshake Transfer ↓ Transfer ↑ Actions
{!client.enabled ? "Disabled" : isConnected ? "Connected" : "Idle"} {client.name} {client.ipv4Address} {timeAgo(client.latestHandshakeAt)} {formatBytes(client.transferRx)} {formatBytes(client.transferTx)}
{filter ? "No clients match your filter" : "No WireGuard clients yet. Click 'New Client' to create one."}
); } export default WireGuard;