wip: peer make work with qr code

This commit is contained in:
Cloud
2025-09-13 12:08:28 -05:00
parent 4f65f95ac9
commit 5bd7443681
5 changed files with 105 additions and 200 deletions
-1
View File
@@ -171,7 +171,6 @@ services:
- "8082:8080" - "8082:8080"
environment: environment:
- FG_PUBLIC_PATH=/files-ui - FG_PUBLIC_PATH=/files-ui
platform: linux/amd64
networks: networks:
cell-network: cell-network:
+2 -1
View File
@@ -18,7 +18,8 @@
"clsx": "^2.0.0", "clsx": "^2.0.0",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"postcss": "^8.4.32" "postcss": "^8.4.32",
"qrcode": "^1.5.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.30.1", "@eslint/js": "^9.30.1",
+76 -195
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Plus, Trash2, Edit, Eye, Wifi, Shield, Copy, Download, Key, Smartphone, QrCode } from 'lucide-react'; import { Plus, Trash2, Edit, Eye, Wifi, Shield, Copy, Download, Key, Smartphone } from 'lucide-react';
import { peerAPI, wireguardAPI } from '../services/api'; import { peerAPI, wireguardAPI } from '../services/api';
import QRCode from 'qrcode';
function Peers() { function Peers() {
const [peers, setPeers] = useState([]); const [peers, setPeers] = useState([]);
@@ -27,22 +28,6 @@ function Peers() {
useEffect(() => { useEffect(() => {
fetchPeers(); 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 () => { const fetchPeers = async () => {
@@ -172,19 +157,29 @@ function Peers() {
const getServerConfig = async () => { const getServerConfig = async () => {
try { try {
// Try to get server configuration from API // Try to get server configuration from API
console.log('Fetching server config from:', '/api/wireguard/server-config');
const response = await fetch('/api/wireguard/server-config'); const response = await fetch('/api/wireguard/server-config');
console.log('Server config response status:', response.status);
console.log('Server config response ok:', response.ok);
if (response.ok) { if (response.ok) {
const config = await response.json(); const config = await response.json();
console.log('Server config from API:', config);
return { return {
public_key: config.public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER", public_key: config.public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER",
endpoint: config.endpoint || "YOUR_SERVER_IP:51820" endpoint: config.endpoint || "YOUR_SERVER_IP:51820"
}; };
} else {
console.error('Failed to get server config, status:', response.status);
const errorText = await response.text();
console.error('Error response:', errorText);
} }
} catch (error) { } catch (error) {
console.warn('Could not get server config:', error); console.warn('Could not get server config:', error);
} }
// Return default values // Return default values
console.log('Using fallback server config');
return { return {
public_key: "SERVER_PUBLIC_KEY_PLACEHOLDER", public_key: "SERVER_PUBLIC_KEY_PLACEHOLDER",
endpoint: "YOUR_SERVER_IP:51820" endpoint: "YOUR_SERVER_IP:51820"
@@ -210,7 +205,9 @@ function Peers() {
setSelectedPeer(peer); setSelectedPeer(peer);
try { try {
// Get server configuration first // Get server configuration first
console.log('Getting server config for peer:', peer.name);
const serverConfig = await getServerConfig(); const serverConfig = await getServerConfig();
console.log('Server config received:', serverConfig);
// Create peer with server config // Create peer with server config
const peerWithServerConfig = { const peerWithServerConfig = {
@@ -218,6 +215,7 @@ function Peers() {
server_public_key: serverConfig.public_key, server_public_key: serverConfig.public_key,
server_endpoint: serverConfig.endpoint server_endpoint: serverConfig.endpoint
}; };
console.log('Peer with server config:', peerWithServerConfig);
// Try to get existing config first // Try to get existing config first
const response = await wireguardAPI.getPeerConfig({ name: peer.name }); const response = await wireguardAPI.getPeerConfig({ name: peer.name });
@@ -232,10 +230,12 @@ function Peers() {
// Generate QR code for the config using QR-specific format // Generate QR code for the config using QR-specific format
try { try {
const qrConfig = generateQRConfig(peerWithServerConfig); console.log('Generating QR config for peer:', peer.name);
console.log('QR Config for peer:', peer.name); console.log('Using config from API for QR code:', config);
console.log('QR Config content:', qrConfig);
const qrDataUrl = await generateQRCode(qrConfig); // Use the same config string that works for the text area
// It already has the correct server data from the API
const qrDataUrl = await generateQRCode(config);
setQrCodeDataUrl(qrDataUrl); setQrCodeDataUrl(qrDataUrl);
} catch (qrError) { } catch (qrError) {
console.error('Failed to generate QR code:', qrError); console.error('Failed to generate QR code:', qrError);
@@ -315,176 +315,24 @@ AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25`; PersistentKeepalive = 25`;
}; };
const generateQRCode = (text) => { const generateQRCode = async (text) => {
return new Promise((resolve, reject) => { try {
const tryGenerate = (attempts = 0) => { const qrDataUrl = await QRCode.toDataURL(text, {
try { width: 256,
// Check if QRCode library is available margin: 2,
if (typeof QRCode !== 'undefined') { color: {
console.log('Using QRCode library for QR generation'); dark: '#000000',
QRCode.toDataURL(text, { light: '#FFFFFF'
width: 256, },
margin: 2, errorCorrectionLevel: 'M'
color: { });
dark: '#000000', return qrDataUrl;
light: '#FFFFFF' } catch (error) {
}, console.error('QR Code generation error:', error);
errorCorrectionLevel: 'M' throw error;
}, (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) => { const handleEditPeer = (peer) => {
setSelectedPeer(peer); setSelectedPeer(peer);
@@ -669,7 +517,9 @@ PersistentKeepalive = 25`;
className="text-green-600 hover:text-green-900" className="text-green-600 hover:text-green-900"
title="Show QR Code" title="Show QR Code"
> >
<QrCode className="h-4 w-4" /> <div className="h-4 w-4 border-2 border-current rounded-sm flex items-center justify-center">
<div className="w-2 h-2 bg-current rounded-sm"></div>
</div>
</button> </button>
<button <button
onClick={() => handleEditPeer(peer)} onClick={() => handleEditPeer(peer)}
@@ -697,7 +547,17 @@ PersistentKeepalive = 25`;
{/* Add Peer Modal */} {/* Add Peer Modal */}
{showAddModal && ( {showAddModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowAddModal(false);
setGeneratedKeys(null);
setShowAdvanced(false);
}
}}
>
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> <div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
@@ -850,7 +710,15 @@ PersistentKeepalive = 25`;
{/* View Peer Modal */} {/* View Peer Modal */}
{showViewModal && selectedPeer && ( {showViewModal && selectedPeer && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowViewModal(false);
}
}}
>
<div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white"> <div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
@@ -935,7 +803,9 @@ PersistentKeepalive = 25`;
{/* QR Code Section */} {/* QR Code Section */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2 flex items-center"> <label className="block text-sm font-medium text-gray-700 mb-2 flex items-center">
<QrCode className="h-5 w-5 mr-2" /> <div className="h-5 w-5 mr-2 border-2 border-gray-400 rounded-sm flex items-center justify-center">
<div className="w-2 h-2 bg-gray-400 rounded-sm"></div>
</div>
QR Code QR Code
</label> </label>
<div className="text-center"> <div className="text-center">
@@ -971,7 +841,9 @@ PersistentKeepalive = 25`;
</div> </div>
) : ( ) : (
<div className="p-8 bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg"> <div className="p-8 bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg">
<QrCode className="h-12 w-12 text-gray-400 mx-auto mb-2" /> <div className="h-12 w-12 border-2 border-gray-400 rounded-sm flex items-center justify-center mx-auto mb-2">
<div className="w-6 h-6 bg-gray-400 rounded-sm"></div>
</div>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
QR Code will appear here QR Code will appear here
</p> </p>
@@ -1007,7 +879,16 @@ PersistentKeepalive = 25`;
{/* Edit Peer Modal */} {/* Edit Peer Modal */}
{showEditModal && selectedPeer && ( {showEditModal && selectedPeer && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowEditModal(false);
setSelectedPeer(null);
}
}}
>
<div className="relative top-10 mx-auto p-5 border w-full max-w-2xl shadow-lg rounded-md bg-white"> <div className="relative top-10 mx-auto p-5 border w-full max-w-2xl shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
+18 -2
View File
@@ -291,7 +291,15 @@ function Vault() {
{/* Generate Certificate Modal */} {/* Generate Certificate Modal */}
{showAddCertModal && ( {showAddCertModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowAddCertModal(false);
}
}}
>
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> <div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<h3 className="text-lg font-medium text-gray-900 mb-4">Generate Certificate</h3> <h3 className="text-lg font-medium text-gray-900 mb-4">Generate Certificate</h3>
@@ -375,7 +383,15 @@ function Vault() {
{/* Add Trusted Key Modal */} {/* Add Trusted Key Modal */}
{showAddKeyModal && ( {showAddKeyModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowAddKeyModal(false);
}
}}
>
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> <div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<h3 className="text-lg font-medium text-gray-900 mb-4">Add Trusted Key</h3> <h3 className="text-lg font-medium text-gray-900 mb-4">Add Trusted Key</h3>
+9 -1
View File
@@ -329,7 +329,15 @@ function WireGuard() {
{/* Peer Configuration Modal */} {/* Peer Configuration Modal */}
{showPeerConfig && selectedPeer && ( {showPeerConfig && selectedPeer && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
onClick={(e) => {
// Close modal when clicking on backdrop
if (e.target === e.currentTarget) {
setShowPeerConfig(false);
}
}}
>
<div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white"> <div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white">
<div className="mt-3"> <div className="mt-3">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">