feat: Enhance server validation in server.js and improve gateway management in ServerManager and FilterManager, ensuring proper gateway configuration for jumphost and exit server types.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m2s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m2s
This commit is contained in:
@@ -31,6 +31,43 @@ const SERVER_TYPE_OPTIONS = [
|
||||
{ value: 'jumphost', label: 'Jumphost' },
|
||||
{ value: 'exit', label: 'Выходная нода' },
|
||||
];
|
||||
const NEEDS_GATEWAYS = (type) => ['jumphost', 'exit'].includes(String(type || '').toLowerCase());
|
||||
|
||||
const makeGateway = (over = {}) => ({
|
||||
id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
||||
name: '',
|
||||
ip: '',
|
||||
comment: '',
|
||||
primary: false,
|
||||
...over,
|
||||
});
|
||||
|
||||
const normalizeGateways = (gateways = [], fallbackName = '') => {
|
||||
if (!Array.isArray(gateways)) return [];
|
||||
const cleaned = gateways
|
||||
.map((g, idx) => ({
|
||||
id: g?.id || `${Date.now()}-${idx}`,
|
||||
name: String(g?.name || '').trim(),
|
||||
ip: String(g?.ip || '').trim(),
|
||||
comment: String(g?.comment || '').trim(),
|
||||
primary: Boolean(g?.primary),
|
||||
}))
|
||||
.filter(g => g.name || g.ip || g.comment);
|
||||
if (cleaned.length === 0) {
|
||||
return [{ id: `${Date.now()}-primary`, name: fallbackName || 'gateway', ip: '', comment: '', primary: true }];
|
||||
}
|
||||
// ensure single primary
|
||||
let hasPrimary = false;
|
||||
cleaned.forEach((g, i) => {
|
||||
if (g.primary && !hasPrimary) {
|
||||
hasPrimary = true;
|
||||
} else {
|
||||
g.primary = false;
|
||||
}
|
||||
});
|
||||
if (!hasPrimary) cleaned[0].primary = true;
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
function ServerManager() {
|
||||
const [servers, setServers] = useState([]);
|
||||
@@ -44,7 +81,8 @@ function ServerManager() {
|
||||
provider: '',
|
||||
tunnel: 'GRE',
|
||||
type: 'jumphost',
|
||||
gateway: ''
|
||||
gateway: '',
|
||||
gateways: [makeGateway({ primary: true })],
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
@@ -136,6 +174,7 @@ function ServerManager() {
|
||||
const normalized = (Array.isArray(response.data) ? response.data : []).map((srv) => ({
|
||||
...srv,
|
||||
type: normalizeServerType(srv.type),
|
||||
gateways: NEEDS_GATEWAYS(srv.type) ? normalizeGateways(srv.gateways, srv.gateway || srv.dns || srv.ip) : (Array.isArray(srv.gateways) ? srv.gateways : []),
|
||||
}));
|
||||
setServers(normalized);
|
||||
setError('');
|
||||
@@ -212,10 +251,20 @@ function ServerManager() {
|
||||
};
|
||||
|
||||
const handleAddServerFromModal = () => {
|
||||
if (!newServer.ip.trim() || !newServer.dns.trim() || !newServer.country.trim() || !newServer.provider.trim() || !newServer.tunnel.trim() || !newServer.gateway.trim() || !newServer.type) {
|
||||
if (!newServer.ip.trim() || !newServer.dns.trim() || !newServer.country.trim() || !newServer.provider.trim() || !newServer.tunnel.trim() || !newServer.type) {
|
||||
setError('Все обязательные поля должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
if (NEEDS_GATEWAYS(newServer.type)) {
|
||||
const normalizedGws = normalizeGateways(newServer.gateways, newServer.gateway || newServer.dns || newServer.ip);
|
||||
if (!normalizedGws.length) {
|
||||
setError('Добавьте хотя бы один gateway и отметьте основной.');
|
||||
return;
|
||||
}
|
||||
newServer.gateways = normalizedGws;
|
||||
} else {
|
||||
newServer.gateways = [];
|
||||
}
|
||||
|
||||
// Проверяем, если выбран "Другой" провайдер, то customProvider должен быть заполнен
|
||||
if (newServer.provider === 'Другой' && !customProvider.trim()) {
|
||||
@@ -246,7 +295,8 @@ function ServerManager() {
|
||||
provider: '',
|
||||
tunnel: 'GRE',
|
||||
type: 'jumphost',
|
||||
gateway: ''
|
||||
gateway: '',
|
||||
gateways: [makeGateway({ primary: true })],
|
||||
});
|
||||
setCustomProvider('');
|
||||
setAddServerModalOpen(false);
|
||||
@@ -264,7 +314,11 @@ function ServerManager() {
|
||||
};
|
||||
|
||||
const handleEditModalSave = (updatedServer) => {
|
||||
const normalizedServer = { ...updatedServer, type: normalizeServerType(updatedServer.type) };
|
||||
const normalizedServer = {
|
||||
...updatedServer,
|
||||
type: normalizeServerType(updatedServer.type),
|
||||
gateways: NEEDS_GATEWAYS(updatedServer.type) ? normalizeGateways(updatedServer.gateways, updatedServer.gateway || updatedServer.dns || updatedServer.ip) : [],
|
||||
};
|
||||
setServers(servers.map(s => {
|
||||
const sameId = s.id && updatedServer.id ? s.id === updatedServer.id : false;
|
||||
const sameIp = s.ip === updatedServer.ip;
|
||||
@@ -1022,6 +1076,100 @@ function ServerManager() {
|
||||
required
|
||||
helpText="Имя основного шлюза для маршрутизации"
|
||||
/>
|
||||
{NEEDS_GATEWAYS(newServer.type) && (
|
||||
<>
|
||||
<div className="form-label">Шлюзы</div>
|
||||
<div className="list-group list-group-flush border rounded mb-2">
|
||||
{newServer.gateways.map((gw, idx) => (
|
||||
<div key={gw.id || idx} className="list-group-item">
|
||||
<div className="row g-2 align-items-end">
|
||||
<div className="col-12 col-md-4">
|
||||
<label className="form-label small mb-1">Имя *</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={gw.name}
|
||||
onChange={(e) => {
|
||||
const list = newServer.gateways.map((g, i) => i === idx ? { ...g, name: e.target.value } : g);
|
||||
setNewServer({ ...newServer, gateways: list });
|
||||
}}
|
||||
placeholder="GW-MSK"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-4">
|
||||
<label className="form-label small mb-1">IP</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={gw.ip || ''}
|
||||
onChange={(e) => {
|
||||
const list = newServer.gateways.map((g, i) => i === idx ? { ...g, ip: e.target.value } : g);
|
||||
setNewServer({ ...newServer, gateways: list });
|
||||
}}
|
||||
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"
|
||||
value={gw.comment || ''}
|
||||
onChange={(e) => {
|
||||
const list = newServer.gateways.map((g, i) => i === idx ? { ...g, comment: e.target.value } : g);
|
||||
setNewServer({ ...newServer, gateways: list });
|
||||
}}
|
||||
placeholder="Основной шлюз"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-1 d-flex align-items-center justify-content-center">
|
||||
<div className="form-check form-switch m-0">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={gw.primary}
|
||||
onChange={(e) => {
|
||||
const list = newServer.gateways.map((g, i) => ({ ...g, primary: i === idx ? e.target.checked : false }));
|
||||
if (!list.some(g => g.primary)) list[idx].primary = true;
|
||||
setNewServer({ ...newServer, gateways: list });
|
||||
}}
|
||||
title="Основной шлюз"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center mt-2">
|
||||
<div className="text-muted small">{gw.primary ? 'Основной шлюз' : 'Резервный шлюз'}</div>
|
||||
{newServer.gateways.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link text-danger px-0"
|
||||
onClick={() => {
|
||||
if (newServer.gateways.length === 1) return;
|
||||
const list = newServer.gateways.filter((_, i) => i !== idx);
|
||||
if (!list.some(g => g.primary) && list.length > 0) list[0].primary = true;
|
||||
setNewServer({ ...newServer, gateways: list });
|
||||
}}
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => setNewServer({ ...newServer, gateways: [...newServer.gateways, makeGateway({ primary: newServer.gateways.length === 0 })] })}
|
||||
>
|
||||
<IconPlus className="icon" />
|
||||
Добавить шлюз
|
||||
</button>
|
||||
<div className="form-text">Для jumphost/exit должен быть один основной gateway.</div>
|
||||
</>
|
||||
)}
|
||||
</FormModal>
|
||||
|
||||
<div className="card w-100 mb-4">
|
||||
|
||||
Reference in New Issue
Block a user