import { useState, useEffect } from 'react'; import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle, Eye, Globe, CheckCircle, XCircle } from 'lucide-react'; import { wireguardAPI, peerAPI } from '../services/api'; import QRCode from 'qrcode'; function WireGuard() { const [status, setStatus] = useState(null); const [serverConfig, setServerConfig] = useState(null); const [isRefreshingIp, setIsRefreshingIp] = useState(false); const [peers, setPeers] = useState([]); const [totalPeers, setTotalPeers] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [selectedPeer, setSelectedPeer] = useState(null); const [showPeerConfig, setShowPeerConfig] = useState(false); const [peerConfig, setPeerConfig] = useState(''); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [peerStatuses, setPeerStatuses] = useState({}); const [tunnelMode, setTunnelMode] = useState('full'); // 'split' or 'full' useEffect(() => { fetchWireGuardData(); }, []); const refreshExternalIp = async () => { setIsRefreshingIp(true); try { // Refresh IP first (fast) const ipResp = await fetch('/api/wireguard/refresh-ip', { method: 'POST' }); const ipData = await ipResp.json(); setServerConfig(prev => ({ ...prev, ...ipData, port_open: 'checking' })); // Then check port (slow — external call) const portResp = await fetch('/api/wireguard/check-port', { method: 'POST' }); const portData = await portResp.json(); setServerConfig(prev => ({ ...prev, port_open: portData.port_open })); } catch (e) { console.error('Failed to refresh IP:', e); } finally { setIsRefreshingIp(false); } }; const fetchWireGuardData = async () => { try { const [statusResponse, peersResponse, wireguardResponse, serverConfigResponse] = await Promise.all([ wireguardAPI.getStatus(), peerAPI.getPeers(), wireguardAPI.getPeers(), fetch('/api/wireguard/server-config').then(r => r.json()).catch(() => null), ]); setStatus(statusResponse.data); if (serverConfigResponse) setServerConfig(serverConfigResponse); // Merge peer registry data with WireGuard data (same as Peers page) const peersData = peersResponse.data || []; const wireguardPeers = wireguardResponse.data || []; // Create a map of WireGuard peers by name for quick lookup const wireguardMap = {}; wireguardPeers.forEach(peer => { wireguardMap[peer.name] = peer; }); // Merge the data const mergedPeers = peersData.map(peer => ({ ...peer, ...wireguardMap[peer.peer || peer.name], name: peer.peer || peer.name, status: 'Online', // For now, assume all peers are online type: 'WireGuard', // Preserve important fields that might be overwritten private_key: peer.private_key, server_public_key: peer.server_public_key, server_endpoint: peer.server_endpoint, allowed_ips: peer.allowed_ips || wireguardMap[peer.peer || peer.name]?.AllowedIPs || '0.0.0.0/0', persistent_keepalive: peer.persistent_keepalive || wireguardMap[peer.peer || peer.name]?.PersistentKeepalive || 25 })); // Load all peer statuses in one call (keyed by public_key) let liveStatuses = {}; try { const stResp = await fetch('/api/wireguard/peers/statuses'); if (stResp.ok) liveStatuses = await stResp.json(); } catch (_) {} // Normalize snake_case API fields to camelCase for UI const normalizeStatus = (st) => ({ online: st.online ?? null, lastHandshake: st.last_handshake || st.lastHandshake || null, lastHandshakeSecondsAgo: st.last_handshake_seconds_ago ?? null, transferRx: st.transfer_rx ?? st.transferRx ?? 0, transferTx: st.transfer_tx ?? st.transferTx ?? 0, endpoint: st.endpoint || null, }); // Build name→status map and annotate peers const statusMap = {}; const annotated = mergedPeers.map(peer => { const raw = liveStatuses[peer.public_key] || { online: null }; const st = normalizeStatus(raw); statusMap[peer.name] = st; return { ...peer, _liveStatus: st }; }); setPeerStatuses(statusMap); setTotalPeers(annotated.length); // Show all peers; live ones bubble up via status indicator setPeers(annotated); } catch (error) { console.error('Failed to fetch WireGuard data:', error); } finally { setIsLoading(false); setIsRefreshing(false); } }; const refreshData = async () => { setIsRefreshing(true); await fetchWireGuardData(); }; const handleViewPeerConfig = async (peer, mode = tunnelMode) => { setSelectedPeer(peer); try { const sc = await getServerConfig(); const peerWithServerConfig = { ...peer, server_public_key: sc.public_key, server_endpoint: sc.endpoint }; const config = generateWireGuardConfig(peerWithServerConfig, mode); setPeerConfig(config); // Generate QR code for the config try { const qrDataUrl = await generateQRCode(config); setQrCodeDataUrl(qrDataUrl); } catch (qrError) { console.error('Failed to generate QR code:', qrError); setQrCodeDataUrl(''); } } catch (error) { console.error('Failed to get peer config:', error); setPeerConfig('Failed to load configuration'); setQrCodeDataUrl(''); } setShowPeerConfig(true); }; const copyToClipboard = async (text) => { try { await navigator.clipboard.writeText(text); alert('Configuration copied to clipboard!'); } catch (error) { console.error('Failed to copy to clipboard:', error); alert('Failed to copy to clipboard. Please copy manually.'); } }; const downloadConfig = (peerName, config) => { const blob = new Blob([config], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${peerName}.conf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; const getServerConfig = async () => { if (serverConfig?.public_key) return serverConfig; try { const response = await fetch('/api/wireguard/server-config'); if (response.ok) { const config = await response.json(); setServerConfig(config); return config; } } catch (error) { console.warn('Could not get server config:', error); } return { public_key: '', endpoint: ':51820' }; }; const CELL_DNS = '172.20.0.3'; const SPLIT_TUNNEL_IPS = '10.0.0.0/24, 172.20.0.0/16'; const FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0'; const generateWireGuardConfig = (peer, mode = tunnelMode) => { const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER"; const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820"; const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; const peerAddress = peer.ip?.includes('/') ? peer.ip : `${peer.ip}/32`; const allowedIPs = mode === 'full' ? FULL_TUNNEL_IPS : SPLIT_TUNNEL_IPS; return `[Interface] PrivateKey = ${privateKey} Address = ${peerAddress} DNS = ${CELL_DNS} [Peer] PublicKey = ${serverPublicKey} Endpoint = ${serverEndpoint} AllowedIPs = ${allowedIPs} PersistentKeepalive = ${peer.persistent_keepalive || 25}`; }; const generateQRCode = async (text) => { try { const qrDataUrl = await QRCode.toDataURL(text, { width: 256, margin: 2, color: { dark: '#000000', light: '#FFFFFF' }, errorCorrectionLevel: 'M' }); return qrDataUrl; } catch (error) { console.error('QR Code generation error:', error); throw error; } }; const formatBytes = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const getPeerStatus = async (peer) => { try { // Get real peer status from the API const response = await fetch('http://localhost:3000/api/wireguard/peers/status', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ public_key: peer.public_key }) }); if (response.ok) { const status = await response.json(); return { online: status.online, lastHandshake: status.latest_handshake, transferRx: status.transfer_rx || 0, transferTx: status.transfer_tx || 0 }; } else { console.error('Failed to get peer status:', response.status); return { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 }; } } catch (error) { console.error('Error getting peer status:', error); return { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 }; } }; if (isLoading) { return (
); } return (

WireGuard VPN

Manage WireGuard VPN configuration and monitor active peers

{/* Status Overview */}

Service Status

{status?.running ? 'Online' : 'Offline'}

Total Peers

{totalPeers}

p._liveStatus?.online) ? 'bg-green-100' : 'bg-gray-100'}`}> p._liveStatus?.online) ? 'text-green-600' : 'text-gray-400'}`} />

Live Connections

p._liveStatus?.online) ? 'text-green-600' : 'text-gray-900'}`}> {peers.filter(p => p._liveStatus?.online).length} / {totalPeers}

Interface

{status?.interface || 'wg0'}

{/* External IP & Port Status */}

Server Endpoint

External IP

{serverConfig?.external_ip || Detecting…}

WireGuard Endpoint

{serverConfig?.endpoint || `:${serverConfig?.port || 51820}`}

UDP Port {serverConfig?.port || 51820}

{serverConfig ? ( {serverConfig.port_open === true ? 'Open' : serverConfig.port_open === false ? 'Blocked' : serverConfig.port_open === 'checking' ? 'Checking…' : 'Click Refresh IP to check'} ) : '—'}

Server Public Key

{serverConfig?.public_key || '—'}

{serverConfig && !serverConfig.external_ip && (
External IP could not be detected. Check internet connectivity, then click Refresh IP.
)} {serverConfig && serverConfig.port_open === false && (
UDP port {serverConfig.port || 51820} appears closed. Check your router/firewall and forward this port to this machine.
)}
{/* Traffic Stats */} {status?.total_traffic && (

Traffic Statistics

Data Received

{formatBytes(status.total_traffic.bytes_received || 0)}

Data Sent

{formatBytes(status.total_traffic.bytes_sent || 0)}

)} {/* Peers Table */}

Live Connected Peers

{peers.length} peer{peers.length !== 1 ? 's' : ''} currently connected
{peers.length === 0 ? (

No active connections

No peers are currently connected to the WireGuard VPN

Configured peers will appear here when they connect

) : (
{peers.map((peer, index) => { const peerStatus = peerStatuses[peer.name] || { online: null, lastHandshake: null, transferRx: 0, transferTx: 0, endpoint: null }; return ( ); })}
Peer IP Address Status Last Handshake Traffic Actions
{peer.name}
{peer.description && (
{peer.description}
)}
{peer.ip || peer.AllowedIPs || 'N/A'}
{peerStatus.online === true ? 'Online' : peerStatus.online === false ? 'Offline' : 'Unknown'}
{peerStatus.lastHandshake ? new Date(peerStatus.lastHandshake).toLocaleString() : 'Unknown' }
{peerStatus.transferRx > 0 ? formatBytes(peerStatus.transferRx) : 'No data'}
{peerStatus.transferTx > 0 ? formatBytes(peerStatus.transferTx) : 'No data'}
)}
{/* Peer Configuration Modal */} {showPeerConfig && selectedPeer && (
{ // Close modal when clicking on backdrop if (e.target === e.currentTarget) { setShowPeerConfig(false); } }} >

{selectedPeer.name} Configuration

{tunnelMode === 'split' ? 'Split tunnel: only cell services (10.0.0.0/24, 172.20.0.0/16) route through VPN — local network & internet traffic stay direct.' : 'Full tunnel: all traffic (internet + local) routes through VPN server.'}

{selectedPeer.name}

{selectedPeer.ip || selectedPeer.AllowedIPs}

{selectedPeer.public_key || selectedPeer.PublicKey}

{selectedPeer.allowed_ips || selectedPeer.AllowedIPs}