feat: Replace ForceGraph with react-vis-network-graph for improved visualization and enhance ServerManager with connection validation and deletion functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 7m50s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 7m50s
This commit is contained in:
@@ -398,9 +398,22 @@ function ServerManager() {
|
||||
setError('Все поля для связи должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
if (newConnection.from === newConnection.to) {
|
||||
setError('Сервер A и сервер B не могут быть одинаковыми.');
|
||||
return;
|
||||
}
|
||||
setConnections([...connections, newConnection]);
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
setSuccess('Связь успешно добавлена!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
};
|
||||
|
||||
// Удаление связи
|
||||
const handleDeleteConnection = (index) => {
|
||||
setConnections(connections.filter((_, i) => i !== index));
|
||||
setSuccess('Связь удалена!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -668,22 +681,22 @@ function ServerManager() {
|
||||
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row g-2 align-items-end">
|
||||
<div className="col-md-3">
|
||||
<label className="form-label">Сервер A (IP)</label>
|
||||
<div className="row g-3">
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">Сервер A (IP/DNS)</label>
|
||||
<select className="form-select" value={newConnection.from} onChange={e => setNewConnection({ ...newConnection, from: e.target.value })}>
|
||||
<option value="">Выберите</option>
|
||||
<option value="">Выберите сервер A</option>
|
||||
{servers.map(s => <option key={s.ip} value={s.ip}>{s.ip} ({s.dns})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<label className="form-label">Сервер B (IP)</label>
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">Сервер B (IP/DNS)</label>
|
||||
<select className="form-select" value={newConnection.to} onChange={e => setNewConnection({ ...newConnection, to: e.target.value })}>
|
||||
<option value="">Выберите</option>
|
||||
<option value="">Выберите сервер B</option>
|
||||
{servers.map(s => <option key={s.ip} value={s.ip}>{s.ip} ({s.dns})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">Тип туннеля</label>
|
||||
<select className="form-select" value={newConnection.tunnelType} onChange={e => setNewConnection({ ...newConnection, tunnelType: e.target.value })}>
|
||||
<option value="GRE">GRE</option>
|
||||
@@ -692,20 +705,84 @@ function ServerManager() {
|
||||
<option value="OpenVPN">OpenVPN</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<label className="form-label">IP сервера A</label>
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">IP адрес сервера A</label>
|
||||
<input className="form-control" value={newConnection.ipA} onChange={e => setNewConnection({ ...newConnection, ipA: e.target.value })} placeholder="10.10.100.1" />
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<label className="form-label">IP сервера B</label>
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">IP адрес сервера B</label>
|
||||
<input className="form-control" value={newConnection.ipB} onChange={e => setNewConnection({ ...newConnection, ipB: e.target.value })} placeholder="10.10.100.2" />
|
||||
</div>
|
||||
<div className="col-md-12 mt-2">
|
||||
<button className="btn btn-primary" type="button" onClick={handleAddConnection}>Добавить связь</button>
|
||||
<div className="col-12">
|
||||
<button className="btn btn-primary d-flex align-items-center" type="button" onClick={handleAddConnection}>
|
||||
<span className="me-2 d-flex align-items-center"><IconPlus className="icon" /></span>
|
||||
<span className="fw-bold">Добавить связь</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список существующих связей */}
|
||||
{connections.length > 0 && (
|
||||
<div className="card w-100 mb-4">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title mb-0">Существующие связи ({connections.length})</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="table-responsive">
|
||||
<table className="table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Сервер A</th>
|
||||
<th>Сервер B</th>
|
||||
<th>Тип туннеля</th>
|
||||
<th>IP адреса туннеля</th>
|
||||
<th className="text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{connections.map((connection, index) => {
|
||||
const serverA = servers.find(s => s.ip === connection.from);
|
||||
const serverB = servers.find(s => s.ip === connection.to);
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<div>
|
||||
<div className="fw-bold">{connection.from}</div>
|
||||
<div className="text-muted">{serverA?.dns || 'Неизвестный сервер'}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<div className="fw-bold">{connection.to}</div>
|
||||
<div className="text-muted">{serverB?.dns || 'Неизвестный сервер'}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className="badge bg-green-lt text-green">{connection.tunnelType}</span>
|
||||
</td>
|
||||
<td>
|
||||
<code>{connection.ipA} ⇄ {connection.ipB}</code>
|
||||
</td>
|
||||
<td className="text-end">
|
||||
<button
|
||||
className="btn btn-icon btn-sm btn-outline-danger rounded"
|
||||
title="Удалить связь"
|
||||
onClick={() => handleDeleteConnection(index)}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user