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() {
+ |