refactor(ServerModal): simplify country selection and remove unused gateway handling logic
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m52s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m52s
This commit is contained in:
@@ -11,20 +11,8 @@ import {
|
||||
} from '../../utils/serverUtils.js';
|
||||
|
||||
/**
|
||||
* Списки стран и провайдеров
|
||||
* Список провайдеров
|
||||
*/
|
||||
const COUNTRY_OPTIONS = [
|
||||
{ value: '', label: 'Выберите страну', disabled: true },
|
||||
{ value: 'RU', label: 'Россия (RU)' },
|
||||
{ value: 'SWE', label: 'Швеция (SWE)' },
|
||||
{ value: 'FI', label: 'Финляндия (FI)' },
|
||||
{ value: 'US', label: 'США (US)' },
|
||||
{ value: 'DE', label: 'Германия (DE)' },
|
||||
{ value: 'NL', label: 'Нидерланды (NL)' },
|
||||
{ value: 'SE', label: 'Швеция (SE)' },
|
||||
{ value: 'SG', label: 'Сингапур (SG)' },
|
||||
];
|
||||
|
||||
const PROVIDER_OPTIONS = [
|
||||
{ value: '', label: 'Выберите провайдера', disabled: true },
|
||||
{ value: 'Yandex', label: 'Yandex Cloud' },
|
||||
@@ -154,53 +142,6 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
|
||||
setLocalServer(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Обработчики для gateways
|
||||
*/
|
||||
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
|
||||
);
|
||||
return { ...prev, gateways: updatedGateways };
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
}));
|
||||
if (!updatedGateways.some(g => g.primary) && updatedGateways.length > 0) {
|
||||
updatedGateways[idx].primary = true;
|
||||
}
|
||||
return { ...prev, gateways: updatedGateways };
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddGateway = () => {
|
||||
setLocalServer(prev => {
|
||||
const currentGateways = Array.isArray(prev.gateways) ? prev.gateways : [];
|
||||
const newGw = makeGateway({ primary: currentGateways.length === 0 });
|
||||
return { ...prev, gateways: [...currentGateways, newGw] };
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
if (!updatedGateways.some(g => g.primary) && updatedGateways.length > 0) {
|
||||
updatedGateways[0].primary = true;
|
||||
}
|
||||
return { ...prev, gateways: updatedGateways };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Валидация и сохранение
|
||||
*/
|
||||
@@ -236,14 +177,13 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
|
||||
return;
|
||||
}
|
||||
|
||||
// Валидация gateways для jumphost/exit
|
||||
// Шлюзы для jumphost/exit задаются в другом месте (network-config); при сохранении передаём существующие или один по умолчанию
|
||||
const needsGws = NEEDS_GATEWAYS(localServer.type);
|
||||
let finalGateways = [];
|
||||
if (needsGws) {
|
||||
finalGateways = normalizeGateways(localServer.gateways, localServer.gateway || localServer.dns || localServer.ip);
|
||||
if (finalGateways.length === 0) {
|
||||
setInternalError('Добавьте хотя бы один шлюз');
|
||||
return;
|
||||
finalGateways = [makeGateway({ primary: true, name: localServer.dns?.split('.')[0] || 'main' })];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,9 +196,6 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
|
||||
onSave && onSave(serverToSave);
|
||||
};
|
||||
|
||||
const currentGateways = Array.isArray(localServer.gateways) ? localServer.gateways : [];
|
||||
const showGateways = NEEDS_GATEWAYS(localServer.type);
|
||||
|
||||
return (
|
||||
<div className="modal" tabIndex="-1" ref={modalRef}>
|
||||
<div className="modal-dialog modal-lg">
|
||||
@@ -367,26 +304,24 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Страна */}
|
||||
{/* Страна — 2-буквенный код как в интерфейсах */}
|
||||
<div className="col-md-6">
|
||||
<label className="form-label required">Страна</label>
|
||||
<div className="input-icon">
|
||||
<span className="input-icon-addon">
|
||||
<IconMapPin size={16} />
|
||||
</span>
|
||||
<select
|
||||
className="form-select ps-8"
|
||||
<input
|
||||
type="text"
|
||||
className="form-control ps-8"
|
||||
value={localServer.country || ''}
|
||||
onChange={(e) => handleFieldChange('country', e.target.value)}
|
||||
onChange={(e) => handleFieldChange('country', e.target.value.toUpperCase().slice(0, 2))}
|
||||
placeholder="RU, SE, DE…"
|
||||
maxLength={2}
|
||||
required
|
||||
>
|
||||
{COUNTRY_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value} disabled={opt.disabled}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</div>
|
||||
<div className="form-text">2-буквенный код страны (ISO 3166-1 alpha-2)</div>
|
||||
</div>
|
||||
|
||||
{/* Провайдер */}
|
||||
@@ -477,116 +412,8 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Основной шлюз (текстовое поле для совместимости) */}
|
||||
<div className="col-12">
|
||||
<label className="form-label">
|
||||
Основной шлюз <span className="text-muted">(устаревшее поле)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={localServer.gateway || ''}
|
||||
onChange={(e) => handleFieldChange('gateway', e.target.value)}
|
||||
placeholder="Cloudflare, Hetzner и т.д."
|
||||
/>
|
||||
<div className="form-text">
|
||||
Используйте секцию "Шлюзы" ниже для более детальной настройки
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Секция шлюзов */}
|
||||
{showGateways && (
|
||||
<div className="mt-4">
|
||||
<div className="d-flex align-items-center justify-content-between mb-2">
|
||||
<label className="form-label mb-0">
|
||||
Шлюзы ({currentGateways.length})
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={handleAddGateway}
|
||||
>
|
||||
<IconPlus size={14} className="me-1" />
|
||||
Добавить шлюз
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="list-group list-group-flush border rounded">
|
||||
{currentGateways.map((gw, idx) => (
|
||||
<div key={gw.id || `gw-${idx}`} className="list-group-item">
|
||||
<div className="row g-2 align-items-end">
|
||||
<div className="col-12 col-md-3">
|
||||
<label className="form-label small mb-1">Имя *</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={gw.name || ''}
|
||||
onChange={(e) => handleGatewayChange(idx, 'name', e.target.value)}
|
||||
placeholder="GW-MSK"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-3">
|
||||
<label className="form-label small mb-1">IP</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={gw.ip || ''}
|
||||
onChange={(e) => handleGatewayChange(idx, 'ip', e.target.value)}
|
||||
placeholder="10.0.0.1"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-3">
|
||||
<label className="form-label small mb-1">Комментарий</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={gw.comment || ''}
|
||||
onChange={(e) => handleGatewayChange(idx, 'comment', e.target.value)}
|
||||
placeholder="Описание"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-md-2">
|
||||
<div className="form-check form-switch">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={!!gw.primary}
|
||||
onChange={(e) => handlePrimaryChange(idx, e.target.checked)}
|
||||
id={`primary-${gw.id || idx}`}
|
||||
/>
|
||||
<label className="form-check-label small" htmlFor={`primary-${gw.id || idx}`}>
|
||||
Основной
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6 col-md-1 text-end">
|
||||
{currentGateways.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost-danger btn-icon btn-sm"
|
||||
onClick={() => handleRemoveGateway(idx)}
|
||||
title="Удалить шлюз"
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-muted small mt-1">
|
||||
{gw.primary ? 'Основной шлюз' : 'Резервный шлюз'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="form-text mt-2">
|
||||
Для типов jumphost/exit должен быть минимум один шлюз с признаком "Основной"
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* MikroTik RouterOS API — для jumphost и домашних (входных) роутеров */}
|
||||
{(localServer.type === 'jumphost' || localServer.type === 'home') && (
|
||||
<div className="mt-4 pt-3 border-top">
|
||||
|
||||
Reference in New Issue
Block a user