feat: Refactor EditServerModal to enhance gateway management with improved state handling, including dedicated functions for updating, adding, and removing gateways, ensuring robust server configuration management.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m12s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m12s
This commit is contained in:
+109
-74
@@ -1446,24 +1446,30 @@ function ServerManager() {
|
|||||||
function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
||||||
const modalRef = useRef(null);
|
const modalRef = useRef(null);
|
||||||
// Локальное состояние для формы
|
// Локальное состояние для формы
|
||||||
const [localServer, setLocalServer] = useState(server || {});
|
const [localServer, setLocalServer] = useState({});
|
||||||
|
|
||||||
|
// Функция для создания сервера с гарантированными gateways
|
||||||
const ensureGateways = (srv) => {
|
const ensureGateways = (srv) => {
|
||||||
if (!srv) return {};
|
if (!srv) return { gateways: [] };
|
||||||
const needs = NEEDS_GATEWAYS(srv.type);
|
const needs = NEEDS_GATEWAYS(srv.type);
|
||||||
let gateways = needs ? normalizeGateways(srv.gateways, srv.gateway || srv.dns || srv.ip) : [];
|
let gateways = [];
|
||||||
if (needs && gateways.length === 0) {
|
if (needs) {
|
||||||
gateways = [makeGateway({ primary: true })];
|
gateways = normalizeGateways(srv.gateways, srv.gateway || srv.dns || srv.ip);
|
||||||
|
if (gateways.length === 0) {
|
||||||
|
gateways = [makeGateway({ primary: true })];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { ...srv, gateways };
|
return { ...srv, gateways };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Синхронизируем локальное состояние с пропсами
|
// Синхронизируем локальное состояние с пропсами при открытии модалки
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalServer(ensureGateways(server || {}));
|
if (show && server) {
|
||||||
}, [server]);
|
const prepared = ensureGateways(server);
|
||||||
|
console.log('[EditServerModal] Инициализация с сервером:', prepared);
|
||||||
const gateways = Array.isArray(localServer?.gateways) ? localServer.gateways : [];
|
setLocalServer(prepared);
|
||||||
|
}
|
||||||
|
}, [show, server]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
||||||
@@ -1484,12 +1490,80 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
}
|
}
|
||||||
}, [show, onClose]);
|
}, [show, onClose]);
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
// Универсальная функция обновления поля
|
||||||
const updated = { ...localServer, [field]: value };
|
const handleFieldChange = (field, value) => {
|
||||||
setLocalServer(updated);
|
setLocalServer(prev => {
|
||||||
onChange && onChange(updated);
|
const updated = { ...prev, [field]: value };
|
||||||
|
onChange && onChange(updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция обновления шлюза по индексу
|
||||||
|
const handleGatewayChange = (idx, field, value) => {
|
||||||
|
setLocalServer(prev => {
|
||||||
|
const currentGateways = Array.isArray(prev.gateways) ? prev.gateways : [];
|
||||||
|
const updatedGateways = currentGateways.map((gw, i) =>
|
||||||
|
i === idx ? { ...gw, [field]: value } : gw
|
||||||
|
);
|
||||||
|
const updated = { ...prev, gateways: updatedGateways };
|
||||||
|
onChange && onChange(updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция переключения primary шлюза
|
||||||
|
const handlePrimaryChange = (idx, checked) => {
|
||||||
|
setLocalServer(prev => {
|
||||||
|
const currentGateways = Array.isArray(prev.gateways) ? prev.gateways : [];
|
||||||
|
const updatedGateways = currentGateways.map((gw, i) => ({
|
||||||
|
...gw,
|
||||||
|
primary: i === idx ? checked : false
|
||||||
|
}));
|
||||||
|
// Убедимся, что хотя бы один primary
|
||||||
|
if (!updatedGateways.some(g => g.primary) && updatedGateways.length > 0) {
|
||||||
|
updatedGateways[idx].primary = true;
|
||||||
|
}
|
||||||
|
const updated = { ...prev, gateways: updatedGateways };
|
||||||
|
onChange && onChange(updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция добавления нового шлюза
|
||||||
|
const handleAddGateway = () => {
|
||||||
|
console.log('[EditServerModal] handleAddGateway вызвана, текущий localServer:', localServer);
|
||||||
|
setLocalServer(prev => {
|
||||||
|
const currentGateways = Array.isArray(prev.gateways) ? prev.gateways : [];
|
||||||
|
const newGw = makeGateway({ primary: currentGateways.length === 0 });
|
||||||
|
const updatedGateways = [...currentGateways, newGw];
|
||||||
|
const updated = { ...prev, gateways: updatedGateways };
|
||||||
|
console.log('[EditServerModal] Новый localServer после добавления шлюза:', updated);
|
||||||
|
onChange && onChange(updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция удаления шлюза
|
||||||
|
const handleRemoveGateway = (idx) => {
|
||||||
|
setLocalServer(prev => {
|
||||||
|
const currentGateways = Array.isArray(prev.gateways) ? prev.gateways : [];
|
||||||
|
if (currentGateways.length <= 1) return prev; // Не удаляем последний
|
||||||
|
const updatedGateways = currentGateways.filter((_, i) => i !== idx);
|
||||||
|
// Убедимся, что есть primary
|
||||||
|
if (!updatedGateways.some(g => g.primary) && updatedGateways.length > 0) {
|
||||||
|
updatedGateways[0].primary = true;
|
||||||
|
}
|
||||||
|
const updated = { ...prev, gateways: updatedGateways };
|
||||||
|
onChange && onChange(updated);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Получаем текущие шлюзы для рендера
|
||||||
|
const currentGateways = Array.isArray(localServer.gateways) ? localServer.gateways : [];
|
||||||
|
const showGateways = NEEDS_GATEWAYS(localServer.type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal" tabIndex="-1" ref={modalRef}>
|
<div className="modal" tabIndex="-1" ref={modalRef}>
|
||||||
<div className="modal-dialog">
|
<div className="modal-dialog">
|
||||||
@@ -1502,35 +1576,35 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
<form onSubmit={e => { e.preventDefault(); onSave && onSave(localServer); }}>
|
<form onSubmit={e => { e.preventDefault(); onSave && onSave(localServer); }}>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">ID (опционально)</label>
|
<label className="form-label">ID (опционально)</label>
|
||||||
<input type="text" className="form-control" value={localServer.id || ''} onChange={e => handleChange('id', e.target.value)} placeholder="srv-1" />
|
<input type="text" className="form-control" value={localServer.id || ''} onChange={e => handleFieldChange('id', e.target.value)} placeholder="srv-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">IP адрес</label>
|
<label className="form-label">IP адрес</label>
|
||||||
<input type="text" className="form-control" value={localServer.ip || ''} onChange={e => handleChange('ip', e.target.value)} />
|
<input type="text" className="form-control" value={localServer.ip || ''} onChange={e => handleFieldChange('ip', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Внешний IP (опционально)</label>
|
<label className="form-label">Внешний IP (опционально)</label>
|
||||||
<input type="text" className="form-control" value={localServer.extIp || ''} onChange={e => handleChange('extIp', e.target.value)} placeholder="публичный IP, если отличается" />
|
<input type="text" className="form-control" value={localServer.extIp || ''} onChange={e => handleFieldChange('extIp', e.target.value)} placeholder="публичный IP, если отличается" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Внутренний IP (опционально)</label>
|
<label className="form-label">Внутренний IP (опционально)</label>
|
||||||
<input type="text" className="form-control" value={localServer.internalIp || ''} onChange={e => handleChange('internalIp', e.target.value)} placeholder="10.x.x.x / 192.168.x.x" />
|
<input type="text" className="form-control" value={localServer.internalIp || ''} onChange={e => handleFieldChange('internalIp', e.target.value)} placeholder="10.x.x.x / 192.168.x.x" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">DNS имя</label>
|
<label className="form-label">DNS имя</label>
|
||||||
<input type="text" className="form-control" value={localServer.dns || ''} onChange={e => handleChange('dns', e.target.value)} />
|
<input type="text" className="form-control" value={localServer.dns || ''} onChange={e => handleFieldChange('dns', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Страна</label>
|
<label className="form-label">Страна</label>
|
||||||
<input type="text" className="form-control" value={localServer.country || ''} onChange={e => handleChange('country', e.target.value)} />
|
<input type="text" className="form-control" value={localServer.country || ''} onChange={e => handleFieldChange('country', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Провайдер</label>
|
<label className="form-label">Провайдер</label>
|
||||||
<input type="text" className="form-control" value={localServer.provider || ''} onChange={e => handleChange('provider', e.target.value)} />
|
<input type="text" className="form-control" value={localServer.provider || ''} onChange={e => handleFieldChange('provider', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Тип сервера</label>
|
<label className="form-label">Тип сервера</label>
|
||||||
<select className="form-select" value={localServer.type || 'jumphost'} onChange={e => handleChange('type', e.target.value)}>
|
<select className="form-select" value={localServer.type || 'jumphost'} onChange={e => handleFieldChange('type', e.target.value)}>
|
||||||
{SERVER_TYPE_OPTIONS.map((opt) => (
|
{SERVER_TYPE_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
))}
|
))}
|
||||||
@@ -1538,14 +1612,14 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Тип туннеля</label>
|
<label className="form-label">Тип туннеля</label>
|
||||||
<input type="text" className="form-control" value={localServer.tunnel || ''} onChange={e => handleChange('tunnel', e.target.value)} />
|
<input type="text" className="form-control" value={localServer.tunnel || ''} onChange={e => handleFieldChange('tunnel', e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
{NEEDS_GATEWAYS(localServer.type) && (
|
{showGateways && (
|
||||||
<>
|
<>
|
||||||
<div className="form-label">Шлюзы</div>
|
<div className="form-label">Шлюзы ({currentGateways.length})</div>
|
||||||
<div className="list-group list-group-flush border rounded mb-2">
|
<div className="list-group list-group-flush border rounded mb-2">
|
||||||
{(localServer.gateways || []).map((gw, idx) => (
|
{currentGateways.map((gw, idx) => (
|
||||||
<div key={gw.id || idx} className="list-group-item">
|
<div key={gw.id || `gw-${idx}`} className="list-group-item">
|
||||||
<div className="row g-2 align-items-end">
|
<div className="row g-2 align-items-end">
|
||||||
<div className="col-12 col-md-4">
|
<div className="col-12 col-md-4">
|
||||||
<label className="form-label small mb-1">Имя *</label>
|
<label className="form-label small mb-1">Имя *</label>
|
||||||
@@ -1553,12 +1627,7 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={gw.name || ''}
|
value={gw.name || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleGatewayChange(idx, 'name', e.target.value)}
|
||||||
const list = (localServer.gateways || []).map((g, i) => i === idx ? { ...g, name: e.target.value } : g);
|
|
||||||
const next = { ...localServer, gateways: list };
|
|
||||||
setLocalServer(next);
|
|
||||||
onChange && onChange(next);
|
|
||||||
}}
|
|
||||||
placeholder="GW-MSK"
|
placeholder="GW-MSK"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -1569,12 +1638,7 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={gw.ip || ''}
|
value={gw.ip || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleGatewayChange(idx, 'ip', e.target.value)}
|
||||||
const list = (localServer.gateways || []).map((g, i) => i === idx ? { ...g, ip: e.target.value } : g);
|
|
||||||
const next = { ...localServer, gateways: list };
|
|
||||||
setLocalServer(next);
|
|
||||||
onChange && onChange(next);
|
|
||||||
}}
|
|
||||||
placeholder="10.0.0.1"
|
placeholder="10.0.0.1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1584,12 +1648,7 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={gw.comment || ''}
|
value={gw.comment || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleGatewayChange(idx, 'comment', e.target.value)}
|
||||||
const list = (localServer.gateways || []).map((g, i) => i === idx ? { ...g, comment: e.target.value } : g);
|
|
||||||
const next = { ...localServer, gateways: list };
|
|
||||||
setLocalServer(next);
|
|
||||||
onChange && onChange(next);
|
|
||||||
}}
|
|
||||||
placeholder="Основной шлюз"
|
placeholder="Основной шлюз"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1598,14 +1657,8 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
<input
|
<input
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={gw.primary}
|
checked={!!gw.primary}
|
||||||
onChange={(e) => {
|
onChange={(e) => handlePrimaryChange(idx, e.target.checked)}
|
||||||
const list = (localServer.gateways || []).map((g, i) => ({ ...g, primary: i === idx ? e.target.checked : false }));
|
|
||||||
if (!list.some(g => g.primary)) list[idx].primary = true;
|
|
||||||
const next = { ...localServer, gateways: list };
|
|
||||||
setLocalServer(next);
|
|
||||||
onChange && onChange(next);
|
|
||||||
}}
|
|
||||||
title="Основной шлюз"
|
title="Основной шлюз"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1613,17 +1666,11 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="d-flex justify-content-between align-items-center mt-2">
|
<div className="d-flex justify-content-between align-items-center mt-2">
|
||||||
<div className="text-muted small">{gw.primary ? 'Основной шлюз' : 'Резервный шлюз'}</div>
|
<div className="text-muted small">{gw.primary ? 'Основной шлюз' : 'Резервный шлюз'}</div>
|
||||||
{(localServer.gateways || []).length > 1 && (
|
{currentGateways.length > 1 && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-link text-danger px-0"
|
className="btn btn-link text-danger px-0"
|
||||||
onClick={() => {
|
onClick={() => handleRemoveGateway(idx)}
|
||||||
const list = (localServer.gateways || []).filter((_, i) => i !== idx);
|
|
||||||
if (!list.some(g => g.primary) && list.length > 0) list[0].primary = true;
|
|
||||||
const next = { ...localServer, gateways: list };
|
|
||||||
setLocalServer(next);
|
|
||||||
onChange && onChange(next);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Удалить
|
Удалить
|
||||||
</button>
|
</button>
|
||||||
@@ -1635,19 +1682,7 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-outline-primary btn-sm"
|
className="btn btn-outline-primary btn-sm"
|
||||||
onClick={() => {
|
onClick={handleAddGateway}
|
||||||
try {
|
|
||||||
setLocalServer((prev) => {
|
|
||||||
const list = Array.isArray(prev?.gateways) ? prev.gateways : [];
|
|
||||||
const nextList = [...list, makeGateway({ primary: list.length === 0 })];
|
|
||||||
const next = { ...(prev || {}), gateways: nextList };
|
|
||||||
onChange && onChange(next);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Ошибка при добавлении шлюза в модалке редактирования', e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<IconPlus className="icon" />
|
<IconPlus className="icon" />
|
||||||
Добавить шлюз
|
Добавить шлюз
|
||||||
|
|||||||
Reference in New Issue
Block a user