init
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, Trash2, Edit, Eye, Wifi, Shield } from 'lucide-react';
|
||||
import { peerAPI } from '../services/api';
|
||||
|
||||
function Peers() {
|
||||
const [peers, setPeers] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [newPeer, setNewPeer] = useState({
|
||||
name: '',
|
||||
ip: '',
|
||||
public_key: '',
|
||||
allowed_ips: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchPeers();
|
||||
}, []);
|
||||
|
||||
const fetchPeers = async () => {
|
||||
try {
|
||||
const response = await peerAPI.getPeers();
|
||||
setPeers(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch peers:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddPeer = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await peerAPI.addPeer(newPeer);
|
||||
setShowAddModal(false);
|
||||
setNewPeer({ name: '', ip: '', public_key: '', allowed_ips: '', description: '' });
|
||||
fetchPeers();
|
||||
} catch (error) {
|
||||
console.error('Failed to add peer:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemovePeer = async (peerName) => {
|
||||
if (window.confirm(`Are you sure you want to remove peer "${peerName}"?`)) {
|
||||
try {
|
||||
await peerAPI.removePeer(peerName);
|
||||
fetchPeers();
|
||||
} catch (error) {
|
||||
console.error('Failed to remove peer:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Peers</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Manage peer connections and WireGuard configurations
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="btn btn-primary flex items-center"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Peer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Peers List */}
|
||||
<div className="card">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
IP Address
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Type
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{peers.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="5" className="px-6 py-4 text-center text-gray-500">
|
||||
No peers configured. Add your first peer to get started.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
peers.map((peer) => (
|
||||
<tr key={peer.name} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">{peer.name}</div>
|
||||
{peer.description && (
|
||||
<div className="text-sm text-gray-500">{peer.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{peer.ip}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="status-indicator status-online">
|
||||
Online
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<Shield className="h-4 w-4 text-primary-500 mr-2" />
|
||||
<span className="text-sm text-gray-900">WireGuard</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
title="View Details"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
title="Edit Peer"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleRemovePeer(peer.name)}
|
||||
className="text-danger-600 hover:text-danger-900"
|
||||
title="Remove Peer"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add Peer Modal */}
|
||||
{showAddModal && (
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div className="mt-3">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Add New Peer</h3>
|
||||
<form onSubmit={handleAddPeer}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Peer Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newPeer.name}
|
||||
onChange={(e) => setNewPeer({ ...newPeer, name: e.target.value })}
|
||||
className="input"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
IP Address
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newPeer.ip}
|
||||
onChange={(e) => setNewPeer({ ...newPeer, ip: e.target.value })}
|
||||
className="input"
|
||||
placeholder="10.0.0.1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Public Key
|
||||
</label>
|
||||
<textarea
|
||||
value={newPeer.public_key}
|
||||
onChange={(e) => setNewPeer({ ...newPeer, public_key: e.target.value })}
|
||||
className="input"
|
||||
rows="3"
|
||||
placeholder="Enter WireGuard public key"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Allowed IPs
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newPeer.allowed_ips}
|
||||
onChange={(e) => setNewPeer({ ...newPeer, allowed_ips: e.target.value })}
|
||||
className="input"
|
||||
placeholder="192.168.1.0/24"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newPeer.description}
|
||||
onChange={(e) => setNewPeer({ ...newPeer, description: e.target.value })}
|
||||
className="input"
|
||||
placeholder="Optional description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddModal(false)}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Add Peer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Peers;
|
||||
Reference in New Issue
Block a user