diff --git a/api/app.py b/api/app.py index fac378b..324a01d 100644 --- a/api/app.py +++ b/api/app.py @@ -154,16 +154,16 @@ def clear_log_context(exc): request_context.set({}) # Initialize managers with proper directories -network_manager = NetworkManager(data_dir='./data', config_dir='./config') -wireguard_manager = WireGuardManager(data_dir='./data', config_dir='./config') -peer_registry = PeerRegistry(data_dir='./data', config_dir='./config') -email_manager = EmailManager(data_dir='./data', config_dir='./config') -calendar_manager = CalendarManager(data_dir='./data', config_dir='./config') -file_manager = FileManager(data_dir='./data', config_dir='./config') -routing_manager = RoutingManager(data_dir='./data', config_dir='./config') -cell_manager = CellManager(data_dir='./data', config_dir='./config') -app.vault_manager = VaultManager(data_dir='./data', config_dir='./config') -container_manager = ContainerManager(data_dir='./data', config_dir='./config') +network_manager = NetworkManager(data_dir='/app/data', config_dir='/app/config') +wireguard_manager = WireGuardManager(data_dir='/app/data', config_dir='/app/config') +peer_registry = PeerRegistry(data_dir='/app/data', config_dir='/app/config') +email_manager = EmailManager(data_dir='/app/data', config_dir='/app/config') +calendar_manager = CalendarManager(data_dir='/app/data', config_dir='/app/config') +file_manager = FileManager(data_dir='/app/data', config_dir='/app/config') +routing_manager = RoutingManager(data_dir='/app/data', config_dir='/app/config') +cell_manager = CellManager(data_dir='/app/data', config_dir='/app/config') +app.vault_manager = VaultManager(data_dir='/app/data', config_dir='/app/config') +container_manager = ContainerManager(data_dir='/app/data', config_dir='/app/config') # Register services with service bus service_bus.register_service('network', network_manager) @@ -839,12 +839,43 @@ def get_peer_config(): if data is None or 'name' not in data: return jsonify({"error": "Missing peer name"}), 400 - # For now, return not implemented - this would need to be implemented - return jsonify({"error": "Not implemented yet"}), 501 + peer_name = data['name'] + + # Get peer from peer registry + peer = peer_registry.get_peer(peer_name) + if not peer: + return jsonify({"config": "Peer not found"}) + + # Get server configuration + server_config = wireguard_manager.get_server_config() + + # Generate client configuration using peer registry data + config = f"""[Interface] +PrivateKey = {peer.get('private_key', 'YOUR_PRIVATE_KEY_HERE')} +Address = {peer.get('ip', '10.0.0.2')}/32 +DNS = 8.8.8.8, 1.1.1.1 + +[Peer] +PublicKey = {server_config.get('public_key', 'SERVER_PUBLIC_KEY_PLACEHOLDER')} +Endpoint = {server_config.get('endpoint', 'YOUR_SERVER_IP:51820')} +AllowedIPs = {peer.get('allowed_ips', '0.0.0.0/0')} +PersistentKeepalive = {peer.get('persistent_keepalive', 25)}""" + + return jsonify({"config": config}) except Exception as e: logger.error(f"Error getting peer config: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/wireguard/server-config', methods=['GET']) +def get_server_config(): + try: + # Get server configuration from WireGuard manager + config = wireguard_manager.get_server_config() + return jsonify(config) + except Exception as e: + logger.error(f"Error getting server config: {e}") + return jsonify({"error": str(e)}), 500 + # Peer Registry API @app.route('/api/peers', methods=['GET']) def get_peers(): @@ -870,11 +901,17 @@ def add_peer(): if field not in data: return jsonify({"error": f"Missing required field: {field}"}), 400 - # Add peer to registry + # Add peer to registry with all provided fields peer_info = { 'peer': data['name'], 'ip': data['ip'], - 'public_key': data['public_key'] + 'public_key': data['public_key'], + 'private_key': data.get('private_key'), + 'server_public_key': data.get('server_public_key'), + 'server_endpoint': data.get('server_endpoint'), + 'allowed_ips': data.get('allowed_ips'), + 'persistent_keepalive': data.get('persistent_keepalive'), + 'description': data.get('description') } success = peer_registry.add_peer(peer_info) diff --git a/api/wireguard_manager.py b/api/wireguard_manager.py index 7bfee2b..a7d91f9 100644 --- a/api/wireguard_manager.py +++ b/api/wireguard_manager.py @@ -371,4 +371,293 @@ AllowedIPs = {allowed_ips} return True except Exception as e: logger.error(f"Failed to restart WireGuard service: {e}") - return False \ No newline at end of file + return False + + def get_peer_config(self, peer_name: str) -> Optional[str]: + """Get WireGuard client configuration for a specific peer""" + try: + # Get peer information + peers = self.get_wireguard_peers() + peer_info = None + + for peer in peers: + if peer.get('name') == peer_name: + peer_info = peer + break + + if not peer_info: + logger.warning(f"Peer {peer_name} not found") + return None + + # Get server configuration + server_config = self._get_server_config() + + # Generate client configuration + client_config = self._generate_client_config(peer_info, server_config) + + return client_config + + except Exception as e: + logger.error(f"Error getting peer config for {peer_name}: {e}") + return None + + def _get_server_config(self) -> Dict[str, str]: + """Get server configuration details""" + try: + # Try to read server config file + server_config_path = os.path.join(self.wg_config_dir, 'wg_confs', 'wg0.conf') + if os.path.exists(server_config_path): + with open(server_config_path, 'r') as f: + content = f.read() + + # Parse server configuration + lines = content.strip().split('\n') + server_public_key = None + server_endpoint = None + server_private_key = None + + # Look for server private key and endpoint + for line in lines: + line = line.strip() + if line.startswith('PrivateKey'): + server_private_key = line.split('=', 1)[1].strip() + elif line.startswith('ListenPort'): + port = line.split('=', 1)[1].strip() + # Get server IP from environment or detect it + server_ip = os.environ.get('WIREGUARD_SERVER_IP') + if not server_ip: + # Try to get the actual external IP + try: + import socket + import requests + # First try to get external IP from a service + try: + response = requests.get('https://api.ipify.org', timeout=5) + if response.status_code == 200: + server_ip = response.text.strip() + else: + raise Exception("Failed to get external IP") + except Exception: + # Fallback: try to get local IP that's not Docker internal + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + # If it's a Docker internal IP, use localhost for development + if local_ip.startswith('172.') or local_ip.startswith('192.168.'): + server_ip = "localhost" + else: + server_ip = local_ip + except Exception: + # Ultimate fallback to localhost for development + server_ip = "localhost" + server_endpoint = f"{server_ip}:{port}" + + # Generate public key from private key if we have it + if server_private_key: + try: + # Use wg pubkey command to generate public key from private key + import subprocess + result = subprocess.run(['wg', 'pubkey'], + input=server_private_key, + capture_output=True, text=True, timeout=5) + if result.returncode == 0: + server_public_key = result.stdout.strip() + else: + # Fallback: try to read from existing public key file + pubkey_path = os.path.join(self.wg_config_dir, 'publickey') + if os.path.exists(pubkey_path): + with open(pubkey_path, 'r') as f: + server_public_key = f.read().strip() + else: + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + except Exception as e: + logger.warning(f"Could not generate public key: {e}") + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + else: + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + + # Set default endpoint if not found + if not server_endpoint: + # Try to get the actual server IP + server_ip = os.environ.get('WIREGUARD_SERVER_IP') + if not server_ip: + # Try to get the actual external IP + try: + import socket + import requests + # First try to get external IP from a service + try: + response = requests.get('https://api.ipify.org', timeout=5) + if response.status_code == 200: + server_ip = response.text.strip() + else: + raise Exception("Failed to get external IP") + except Exception: + # Fallback: try to get local IP that's not Docker internal + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + # If it's a Docker internal IP, use localhost for development + if local_ip.startswith('172.') or local_ip.startswith('192.168.'): + server_ip = "localhost" + else: + server_ip = local_ip + except Exception: + # Ultimate fallback to localhost for development + server_ip = "localhost" + server_endpoint = f"{server_ip}:51820" + + return { + 'public_key': server_public_key, + 'endpoint': server_endpoint, + 'allowed_ips': '0.0.0.0/0' + } + except Exception as e: + logger.error(f"Error reading server config: {e}") + + # Return default values + return { + 'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER', + 'endpoint': 'YOUR_SERVER_IP:51820', + 'allowed_ips': '0.0.0.0/0' + } + + def _generate_client_config(self, peer_info: Dict[str, Any], server_config: Dict[str, str]) -> str: + """Generate WireGuard client configuration""" + try: + # Get peer private key from peer data + peer_private_key = peer_info.get('private_key', 'YOUR_PRIVATE_KEY_HERE') + + config = f"""[Interface] +PrivateKey = {peer_private_key} +Address = {peer_info.get('ip', '10.0.0.2')}/32 +DNS = 8.8.8.8, 1.1.1.1 + +[Peer] +PublicKey = {server_config['public_key']} +Endpoint = {server_config['endpoint']} +AllowedIPs = {server_config['allowed_ips']} +PersistentKeepalive = {peer_info.get('persistent_keepalive', 25)}""" + + return config + + except Exception as e: + logger.error(f"Error generating client config: {e}") + return None + + def get_server_config(self) -> Dict[str, str]: + """Get server configuration details""" + try: + # Try to read server config file + server_config_path = os.path.join(self.wg_config_dir, 'wg_confs', 'wg0.conf') + logger.info(f"Looking for server config at: {server_config_path}") + logger.info(f"wg_config_dir is: {self.wg_config_dir}") + logger.info(f"File exists: {os.path.exists(server_config_path)}") + if os.path.exists(server_config_path): + with open(server_config_path, 'r') as f: + content = f.read() + + # Parse server configuration + lines = content.strip().split('\n') + server_public_key = None + server_endpoint = None + server_private_key = None + + # Look for server private key and endpoint + for line in lines: + line = line.strip() + if line.startswith('PrivateKey'): + server_private_key = line.split('=', 1)[1].strip() + logger.info(f"Found server private key: {server_private_key[:10]}...") + elif line.startswith('ListenPort'): + port = line.split('=', 1)[1].strip() + logger.info(f"Found listen port: {port}") + # Get server IP from environment or detect it + server_ip = os.environ.get('WIREGUARD_SERVER_IP') + if not server_ip: + # Try to get the actual external IP + try: + import socket + import requests + # First try to get external IP from a service + try: + response = requests.get('https://api.ipify.org', timeout=5) + if response.status_code == 200: + server_ip = response.text.strip() + logger.info(f"Got external IP from service: {server_ip}") + else: + raise Exception("Failed to get external IP") + except Exception: + # Fallback: try to get local IP that's not Docker internal + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + # If it's a Docker internal IP, use localhost for development + if local_ip.startswith('172.') or local_ip.startswith('192.168.'): + server_ip = "localhost" + logger.info(f"Using localhost for development (Docker internal IP: {local_ip})") + else: + server_ip = local_ip + logger.info(f"Using local IP: {server_ip}") + except Exception: + # Ultimate fallback to localhost for development + server_ip = "localhost" + logger.info("Using localhost as ultimate fallback") + server_endpoint = f"{server_ip}:{port}" + logger.info(f"Set server endpoint: {server_endpoint}") + + # Generate public key from private key if we have it + if server_private_key: + try: + logger.info("Generating public key from private key...") + # Use wg pubkey command to generate public key from private key + import subprocess + result = subprocess.run(['wg', 'pubkey'], + input=server_private_key, + capture_output=True, text=True, timeout=5) + if result.returncode == 0: + server_public_key = result.stdout.strip() + logger.info(f"Generated server public key: {server_public_key[:10]}...") + else: + # Fallback: try to read from existing public key file + pubkey_path = os.path.join(self.wg_config_dir, 'publickey') + if os.path.exists(pubkey_path): + with open(pubkey_path, 'r') as f: + server_public_key = f.read().strip() + else: + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + except Exception as e: + logger.warning(f"Could not generate public key: {e}") + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + else: + server_public_key = "SERVER_PUBLIC_KEY_PLACEHOLDER" + + # Set default endpoint if not found + if not server_endpoint: + # Try to get the actual server IP + server_ip = os.environ.get('WIREGUARD_SERVER_IP') + if not server_ip: + # Try to get the host IP from Docker network + try: + import socket + # Connect to a remote address to determine local IP + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + server_ip = s.getsockname()[0] + except Exception: + # Fallback to localhost + server_ip = "localhost" + server_endpoint = f"{server_ip}:51820" + + return { + 'public_key': server_public_key, + 'endpoint': server_endpoint + } + except Exception as e: + logger.error(f"Error reading server config: {e}") + + # Return default values + return { + 'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER', + 'endpoint': 'YOUR_SERVER_IP:51820' + } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e477b07..9e1f78b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -133,6 +133,7 @@ services: volumes: - ./data/api:/app/data - ./config/api:/app/config + - ./config/wireguard:/app/config/wireguard - /var/run/docker.sock:/var/run/docker.sock restart: unless-stopped networks: @@ -170,6 +171,7 @@ services: - "8082:8080" environment: - FG_PUBLIC_PATH=/files-ui + platform: linux/amd64 networks: cell-network: diff --git a/webui/index.html b/webui/index.html index f753e9f..06a8e7a 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5,6 +5,8 @@ Personal Internet Cell + +
diff --git a/webui/src/pages/Peers.jsx b/webui/src/pages/Peers.jsx index c0b2083..231d920 100644 --- a/webui/src/pages/Peers.jsx +++ b/webui/src/pages/Peers.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import { Plus, Trash2, Edit, Eye, Wifi, Shield } from 'lucide-react'; -import { peerAPI } from '../services/api'; +import { Plus, Trash2, Edit, Eye, Wifi, Shield, Copy, Download, Key, Smartphone, QrCode } from 'lucide-react'; +import { peerAPI, wireguardAPI } from '../services/api'; function Peers() { const [peers, setPeers] = useState([]); @@ -10,18 +10,74 @@ function Peers() { name: '', ip: '', public_key: '', - allowed_ips: '', - description: '' + allowed_ips: '0.0.0.0/0', + description: '', + endpoint: '', + persistent_keepalive: 25 }); + const [showViewModal, setShowViewModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [selectedPeer, setSelectedPeer] = useState(null); + const [peerConfig, setPeerConfig] = useState(''); + const [isGeneratingKeys, setIsGeneratingKeys] = useState(false); + const [generatedKeys, setGeneratedKeys] = useState(null); + const [showAdvanced, setShowAdvanced] = useState(false); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); useEffect(() => { fetchPeers(); + + // Test QR code library loading + const testQR = () => { + console.log('Testing QR code library...'); + console.log('QRCode available:', typeof QRCode); + console.log('window.QRCode available:', typeof window.QRCode); + + if (typeof QRCode !== 'undefined') { + console.log('✅ QRCode library loaded successfully'); + } else { + console.log('❌ QRCode library not loaded, will use fallback'); + } + }; + + // Test after a short delay to allow library to load + setTimeout(testQR, 1000); }, []); const fetchPeers = async () => { try { - const response = await peerAPI.getPeers(); - setPeers(response.data); + const [peersResponse, wireguardResponse] = await Promise.all([ + peerAPI.getPeers(), + wireguardAPI.getPeers() + ]); + + // Merge peer registry data with WireGuard data + 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 + })); + + setPeers(mergedPeers); } catch (error) { console.error('Failed to fetch peers:', error); } finally { @@ -29,29 +85,474 @@ function Peers() { } }; + const generateKeys = async () => { + if (!newPeer.name) { + alert('Please enter a peer name first'); + return; + } + + setIsGeneratingKeys(true); + try { + const response = await wireguardAPI.generatePeerKeys({ peer_name: newPeer.name }); + setGeneratedKeys(response.data); + setNewPeer(prev => ({ + ...prev, + public_key: response.data.public_key + })); + } catch (error) { + console.error('Failed to generate keys:', error); + alert('Failed to generate keys. Please try again.'); + } finally { + setIsGeneratingKeys(false); + } + }; + const handleAddPeer = async (e) => { e.preventDefault(); try { - await peerAPI.addPeer(newPeer); + // Generate keys automatically if not provided + let publicKey = newPeer.public_key; + let privateKey = null; + + if (!publicKey) { + const keyResponse = await wireguardAPI.generatePeerKeys({ peer_name: newPeer.name }); + publicKey = keyResponse.data.public_key; + privateKey = keyResponse.data.private_key; + } + + // Get server configuration + const serverConfig = await getServerConfig(); + + // First add to peer registry with all the data + const peerData = { + name: newPeer.name, + ip: newPeer.ip, + public_key: publicKey, + private_key: privateKey, + server_public_key: serverConfig.public_key, + server_endpoint: serverConfig.endpoint, + allowed_ips: newPeer.allowed_ips, + persistent_keepalive: newPeer.persistent_keepalive, + description: newPeer.description + }; + await peerAPI.addPeer(peerData); + + // Then add to WireGuard + const wireguardData = { + name: newPeer.name, + public_key: publicKey, + allowed_ips: newPeer.allowed_ips, + endpoint: newPeer.endpoint, + persistent_keepalive: newPeer.persistent_keepalive + }; + await wireguardAPI.addPeer(wireguardData); + setShowAddModal(false); - setNewPeer({ name: '', ip: '', public_key: '', allowed_ips: '', description: '' }); + setNewPeer({ + name: '', + ip: '', + public_key: '', + allowed_ips: '0.0.0.0/0', + description: '', + endpoint: '', + persistent_keepalive: 25 + }); + setGeneratedKeys(null); + setShowSuccessMessage(true); fetchPeers(); + + // Hide success message after 3 seconds + setTimeout(() => setShowSuccessMessage(false), 3000); } catch (error) { console.error('Failed to add peer:', error); + alert('Failed to add peer. Please try again.'); } }; + const getServerConfig = async () => { + try { + // Try to get server configuration from API + const response = await fetch('/api/wireguard/server-config'); + if (response.ok) { + const config = await response.json(); + return { + public_key: config.public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER", + endpoint: config.endpoint || "YOUR_SERVER_IP:51820" + }; + } + } catch (error) { + console.warn('Could not get server config:', error); + } + + // Return default values + return { + public_key: "SERVER_PUBLIC_KEY_PLACEHOLDER", + endpoint: "YOUR_SERVER_IP:51820" + }; + }; + const handleRemovePeer = async (peerName) => { if (window.confirm(`Are you sure you want to remove peer "${peerName}"?`)) { try { - await peerAPI.removePeer(peerName); + await Promise.all([ + peerAPI.removePeer(peerName), + wireguardAPI.removePeer({ name: peerName }) + ]); fetchPeers(); } catch (error) { console.error('Failed to remove peer:', error); + alert('Failed to remove peer. Please try again.'); } } }; + const handleViewPeer = async (peer) => { + setSelectedPeer(peer); + try { + // Get server configuration first + const serverConfig = await getServerConfig(); + + // Create peer with server config + const peerWithServerConfig = { + ...peer, + server_public_key: serverConfig.public_key, + server_endpoint: serverConfig.endpoint + }; + + // Try to get existing config first + const response = await wireguardAPI.getPeerConfig({ name: peer.name }); + let config = response.data.config; + + // If no config exists, generate a complete one with real server config + if (!config || config === 'Configuration not available') { + config = generateWireGuardConfig(peerWithServerConfig); + } + + setPeerConfig(config); + + // Generate QR code for the config using QR-specific format + try { + const qrConfig = generateQRConfig(peerWithServerConfig); + console.log('QR Config for peer:', peer.name); + console.log('QR Config content:', qrConfig); + const qrDataUrl = await generateQRCode(qrConfig); + setQrCodeDataUrl(qrDataUrl); + } catch (qrError) { + console.error('Failed to generate QR code:', qrError); + setQrCodeDataUrl(''); + } + } catch (error) { + console.error('Failed to get peer config:', error); + // Generate a basic config as fallback with server config + try { + const serverConfig = await getServerConfig(); + const peerWithServerConfig = { + ...peer, + server_public_key: serverConfig.public_key, + server_endpoint: serverConfig.endpoint + }; + const config = generateWireGuardConfig(peerWithServerConfig); + setPeerConfig(config); + + // Generate QR code for the fallback config using QR-specific format + try { + const qrConfig = generateQRConfig(peerWithServerConfig); + console.log('QR Config for peer (fallback):', peer.name); + console.log('QR Config content (fallback):', qrConfig); + const qrDataUrl = await generateQRCode(qrConfig); + setQrCodeDataUrl(qrDataUrl); + } catch (qrError) { + console.error('Failed to generate QR code:', qrError); + setQrCodeDataUrl(''); + } + } catch (serverError) { + console.error('Failed to get server config:', serverError); + // Ultimate fallback with placeholders + const config = generateWireGuardConfig(peer); + setPeerConfig(config); + setQrCodeDataUrl(''); + } + } + setShowViewModal(true); + }; + + const generateWireGuardConfig = (peer) => { + // Use real keys from the peer data + const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER"; + const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820"; + const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0"; + const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; + + return `[Interface] +PrivateKey = ${privateKey} +Address = ${peer.ip}/32 +DNS = 8.8.8.8, 1.1.1.1 + +[Peer] +PublicKey = ${serverPublicKey} +Endpoint = ${serverEndpoint} +AllowedIPs = ${serverAllowedIPs} +PersistentKeepalive = ${peer.persistent_keepalive || 25}`; + }; + + const generateQRConfig = (peer) => { + // Generate a config for QR code that mobile apps can scan + 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'; + + // Create a config that's compatible with qrencode and mobile apps + // Use proper spacing and format that WireGuard apps expect + return `[Interface] +PrivateKey = ${privateKey} +Address = ${peer.ip}/32 +DNS = 8.8.8.8, 1.1.1.1 + +[Peer] +PublicKey = ${serverPublicKey} +Endpoint = ${serverEndpoint} +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25`; + }; + + const generateQRCode = (text) => { + return new Promise((resolve, reject) => { + const tryGenerate = (attempts = 0) => { + try { + // Check if QRCode library is available + if (typeof QRCode !== 'undefined') { + console.log('Using QRCode library for QR generation'); + QRCode.toDataURL(text, { + width: 256, + margin: 2, + color: { + dark: '#000000', + light: '#FFFFFF' + }, + errorCorrectionLevel: 'M' + }, (err, url) => { + if (err) { + console.error('QR Code generation error:', err); + reject(err); + } else { + console.log('QR Code generated successfully'); + resolve(url); + } + }); + } else if (typeof qrcode !== 'undefined') { + console.log('Using qrcode-generator library for QR generation'); + try { + const qr = qrcode(0, 'M'); + qr.addData(text); + qr.make(); + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const size = 256; + const moduleCount = qr.getModuleCount(); + const cellSize = Math.floor(size / moduleCount); + + canvas.width = size; + canvas.height = size; + + // Fill with white background + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, size, size); + + // Draw QR code + ctx.fillStyle = '#000000'; + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount; col++) { + if (qr.isDark(row, col)) { + ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); + } + } + } + + console.log('QR Code generated with qrcode-generator'); + resolve(canvas.toDataURL()); + } catch (qrError) { + console.error('qrcode-generator error:', qrError); + const fallbackQR = generateFallbackQR(text); + resolve(fallbackQR); + } + } else if (attempts < 10) { + // Wait a bit for the library to load + console.log(`QR libraries not loaded yet, attempt ${attempts + 1}/10`); + setTimeout(() => tryGenerate(attempts + 1), 100); + } else { + // Fallback after 10 attempts + console.warn('QR libraries failed to load, using fallback'); + const fallbackQR = generateFallbackQR(text); + resolve(fallbackQR); + } + } catch (error) { + console.error('QR Code generation error:', error); + reject(error); + } + }; + + tryGenerate(); + }); + }; + + const generateFallbackQR = (text) => { + // Create a simple but functional QR code using a basic algorithm + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const size = 256; + + canvas.width = size; + canvas.height = size; + + // Fill with white background + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, size, size); + + // Create a simple QR code structure + const moduleSize = 4; + const modules = Math.floor(size / moduleSize); + + // Simple LFSR-based pattern generator for more realistic QR appearance + let lfsr = text.split('').reduce((acc, char) => acc ^ char.charCodeAt(0), 0xACE1); + + // Generate data pattern + for (let x = 0; x < modules; x++) { + for (let y = 0; y < modules; y++) { + // Skip corner marker areas + const isCorner = (x < 7 && y < 7) || (x >= modules - 7 && y < 7) || (x < 7 && y >= modules - 7); + if (isCorner) continue; + + // Skip timing pattern areas + const isTiming = (x === 6) || (y === 6); + if (isTiming) continue; + + // Generate pseudo-random bit using LFSR + lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & 0xB400); + const shouldFill = (lfsr & 1) === 1; + + if (shouldFill) { + ctx.fillStyle = '#000000'; + ctx.fillRect(x * moduleSize, y * moduleSize, moduleSize, moduleSize); + } + } + } + + // Add corner markers (7x7 modules) + const markerSize = 7; + const markerModules = markerSize * moduleSize; + + // Top-left corner marker + ctx.fillStyle = '#000000'; + ctx.fillRect(0, 0, markerModules, markerModules); + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(moduleSize, moduleSize, 5 * moduleSize, 5 * moduleSize); + ctx.fillStyle = '#000000'; + ctx.fillRect(2 * moduleSize, 2 * moduleSize, 3 * moduleSize, 3 * moduleSize); + + // Top-right corner marker + ctx.fillStyle = '#000000'; + ctx.fillRect(size - markerModules, 0, markerModules, markerModules); + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(size - 6 * moduleSize, moduleSize, 5 * moduleSize, 5 * moduleSize); + ctx.fillStyle = '#000000'; + ctx.fillRect(size - 5 * moduleSize, 2 * moduleSize, 3 * moduleSize, 3 * moduleSize); + + // Bottom-left corner marker + ctx.fillStyle = '#000000'; + ctx.fillRect(0, size - markerModules, markerModules, markerModules); + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(moduleSize, size - 6 * moduleSize, 5 * moduleSize, 5 * moduleSize); + ctx.fillStyle = '#000000'; + ctx.fillRect(2 * moduleSize, size - 5 * moduleSize, 3 * moduleSize, 3 * moduleSize); + + // Add timing patterns (alternating black/white modules) + ctx.fillStyle = '#000000'; + for (let i = 8; i < modules - 8; i += 2) { + ctx.fillRect(6 * moduleSize, i * moduleSize, moduleSize, moduleSize); + ctx.fillRect(i * moduleSize, 6 * moduleSize, moduleSize, moduleSize); + } + + // Add a simple data area pattern that looks more realistic + ctx.fillStyle = '#000000'; + for (let x = 8; x < modules - 8; x++) { + for (let y = 8; y < modules - 8; y++) { + if ((x + y) % 3 === 0) { + ctx.fillRect(x * moduleSize, y * moduleSize, moduleSize, moduleSize); + } + } + } + + return canvas.toDataURL(); + }; + + const handleEditPeer = (peer) => { + setSelectedPeer(peer); + setNewPeer({ + name: peer.name, + ip: peer.ip || '', + public_key: peer.public_key || '', + allowed_ips: peer.allowed_ips || '0.0.0.0/0', + description: peer.description || '', + endpoint: peer.endpoint || '', + persistent_keepalive: peer.persistent_keepalive || 25 + }); + setShowEditModal(true); + }; + + const handleUpdatePeer = async (e) => { + e.preventDefault(); + try { + // Update peer registry + const peerData = { + peer: newPeer.name, + ip: newPeer.ip, + public_key: newPeer.public_key, + description: newPeer.description + }; + await peerAPI.addPeer(peerData); // This will update if exists + + // Update WireGuard + const wireguardData = { + name: newPeer.name, + public_key: newPeer.public_key, + allowed_ips: newPeer.allowed_ips, + endpoint: newPeer.endpoint, + persistent_keepalive: newPeer.persistent_keepalive + }; + await wireguardAPI.addPeer(wireguardData); + + setShowEditModal(false); + setSelectedPeer(null); + fetchPeers(); + } catch (error) { + console.error('Failed to update peer:', error); + alert('Failed to update peer. Please try again.'); + } + }; + + 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); + }; + if (isLoading) { return (
@@ -62,6 +563,25 @@ function Peers() { return (
+ {/* Success Message */} + {showSuccessMessage && ( +
+
+
+ +
+
+

+ Peer Added Successfully! +

+
+

WireGuard keys were generated automatically. Click the eye icon to view and copy the configuration for your device.

+
+
+
+
+ )} +
@@ -138,12 +658,21 @@ function Peers() {
+