feat: Enhance GraphView and ServerManager components by adding node highlighting and a modal for quick connection creation, improving user interaction and visual feedback in the graph interface.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m37s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m37s
This commit is contained in:
+114
-13
@@ -95,6 +95,7 @@ function ServerManager() {
|
||||
// { from: '192.168.1.1', to: '192.168.1.2', tunnelType: 'GRE', ipA: '10.10.100.1', ipB: '10.10.100.2' }
|
||||
]);
|
||||
const [newConnection, setNewConnection] = useState({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
const [graphConnectionModalOpen, setGraphConnectionModalOpen] = useState(false);
|
||||
|
||||
// Фильтры графа
|
||||
const [graphTunnelFilters, setGraphTunnelFilters] = useState({ GRE: true, IPSec: true, WireGuard: true, OpenVPN: true });
|
||||
@@ -475,21 +476,38 @@ function ServerManager() {
|
||||
);
|
||||
}
|
||||
|
||||
// Добавление новой связи
|
||||
const handleAddConnection = () => {
|
||||
if (!newConnection.from || !newConnection.to || !newConnection.tunnelType || !newConnection.ipA || !newConnection.ipB) {
|
||||
// Валидация и добавление связи (общая функция для формы и модального окна графа)
|
||||
const addConnectionInternal = (draft) => {
|
||||
if (!draft.from || !draft.to || !draft.tunnelType || !draft.ipA || !draft.ipB) {
|
||||
setError('Все поля для связи должны быть заполнены.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (newConnection.from === newConnection.to) {
|
||||
if (draft.from === draft.to) {
|
||||
setError('Сервер A и сервер B не могут быть одинаковыми.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
setConnections([...connections, newConnection]);
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
const next = { ...draft };
|
||||
setConnections((prev) => [...prev, next]);
|
||||
setSuccess('Связь успешно добавлена!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Добавление новой связи из нижней формы
|
||||
const handleAddConnection = () => {
|
||||
const ok = addConnectionInternal(newConnection);
|
||||
if (!ok) return;
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
};
|
||||
|
||||
// Подтверждение связи, созданной через граф (модальное окно)
|
||||
const handleConfirmGraphConnection = () => {
|
||||
const ok = addConnectionInternal(newConnection);
|
||||
if (!ok) return;
|
||||
setGraphConnectionModalOpen(false);
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
};
|
||||
|
||||
// Удаление связи
|
||||
@@ -961,10 +979,19 @@ function ServerManager() {
|
||||
servers={servers.filter(s => (!graphCountryFilter || s.country === graphCountryFilter) && (!graphProviderFilter || s.provider === graphProviderFilter))}
|
||||
connections={connections.filter(c => graphTunnelFilters[c.tunnelType])}
|
||||
onCreateConnection={({ from, to }) => {
|
||||
// Открываем модал добавления связи и проставляем выбранные узлы
|
||||
setNewConnection(prev => ({ ...prev, from, to }));
|
||||
const el = document.querySelector('#add-connection-anchor');
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Открываем современный флоу создания связи прямо поверх графа
|
||||
setNewConnection(prev => ({
|
||||
...prev,
|
||||
from,
|
||||
to,
|
||||
// Если серверы уже имеют тип туннеля, предлагаем его по умолчанию
|
||||
tunnelType:
|
||||
prev.tunnelType ||
|
||||
servers.find(s => s.ip === from)?.tunnel ||
|
||||
servers.find(s => s.ip === to)?.tunnel ||
|
||||
'GRE',
|
||||
}));
|
||||
setGraphConnectionModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<div className="mt-3">
|
||||
@@ -977,6 +1004,80 @@ function ServerManager() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Модальное окно для быстрого создания связи после соединения узлов на графе */}
|
||||
<FormModal
|
||||
show={graphConnectionModalOpen}
|
||||
onClose={() => {
|
||||
setGraphConnectionModalOpen(false);
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
}}
|
||||
onSubmit={handleConfirmGraphConnection}
|
||||
title="Создать связь между серверами"
|
||||
submitLabel="Создать связь"
|
||||
submitIcon={IconPlus}
|
||||
size="md"
|
||||
>
|
||||
{error && (
|
||||
<div className="alert alert-danger mb-3" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<div className="text-muted small mb-1">Вы выбрали связь между:</div>
|
||||
<div className="d-flex flex-wrap align-items-center gap-2">
|
||||
<span className="badge bg-blue-lt text-blue">
|
||||
{newConnection.from || 'Сервер A'}
|
||||
</span>
|
||||
<span className="text-muted">⇄</span>
|
||||
<span className="badge bg-blue-lt text-blue">
|
||||
{newConnection.to || 'Сервер B'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3">
|
||||
<div className="col-md-4">
|
||||
<FormField
|
||||
label="Тип туннеля"
|
||||
name="tunnelType"
|
||||
type="select"
|
||||
value={newConnection.tunnelType}
|
||||
onChange={(val) => setNewConnection({ ...newConnection, tunnelType: val })}
|
||||
required
|
||||
options={[
|
||||
{ value: 'GRE', label: 'GRE' },
|
||||
{ value: 'IPSec', label: 'IPSec' },
|
||||
{ value: 'WireGuard', label: 'WireGuard' },
|
||||
{ value: 'OpenVPN', label: 'OpenVPN' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<FormField
|
||||
label="IP адрес сервера A"
|
||||
name="ipA"
|
||||
value={newConnection.ipA}
|
||||
onChange={(val) => setNewConnection({ ...newConnection, ipA: val })}
|
||||
placeholder="10.10.100.1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<FormField
|
||||
label="IP адрес сервера B"
|
||||
name="ipB"
|
||||
value={newConnection.ipB}
|
||||
onChange={(val) => setNewConnection({ ...newConnection, ipB: val })}
|
||||
placeholder="10.10.100.2"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 text-muted small">
|
||||
Протяните линию между серверами в графе — здесь вы только уточняете параметры туннеля. Это делает
|
||||
построение схемы максимально быстрым и наглядным.
|
||||
</div>
|
||||
</FormModal>
|
||||
<div id="add-connection-anchor" className="card w-100 mb-4">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
||||
|
||||
Reference in New Issue
Block a user