refactor(NetworkConfigManager): migrate parentGatewayId to parentGateways array for recursive gateways; enhance data handling and UI updates for improved clarity and functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
This commit is contained in:
@@ -67,7 +67,7 @@ const getEmptyGateway = () => ({
|
||||
serverId: '',
|
||||
country: '',
|
||||
type: 'direct',
|
||||
parentGatewayId: '', // Для рекурсивных - ID родительского gateway
|
||||
parentGateways: [], // Для рекурсивных - массив родительских gateway с distance [{id, distance}]
|
||||
});
|
||||
|
||||
const getEmptyInterface = () => ({
|
||||
@@ -144,8 +144,21 @@ function NetworkConfigManager() {
|
||||
try {
|
||||
const response = await api.get('/network-config');
|
||||
const data = response.data || getDefaultConfig();
|
||||
|
||||
// Миграция старых данных: преобразуем parentGatewayId в parentGateways
|
||||
const migratedGateways = (data.gateways || []).map(gw => {
|
||||
if (gw.type === 'recursive' && gw.parentGatewayId && (!gw.parentGateways || gw.parentGateways.length === 0)) {
|
||||
return {
|
||||
...gw,
|
||||
parentGateways: [{ id: gw.parentGatewayId, distance: undefined }],
|
||||
parentGatewayId: undefined // Удаляем старое поле
|
||||
};
|
||||
}
|
||||
return gw;
|
||||
});
|
||||
|
||||
setConfig({
|
||||
gateways: Array.isArray(data.gateways) ? data.gateways : [],
|
||||
gateways: Array.isArray(migratedGateways) ? migratedGateways : [],
|
||||
tunnelInterfaces: Array.isArray(data.tunnelInterfaces) ? data.tunnelInterfaces : [],
|
||||
ipPools: Array.isArray(data.ipPools) ? data.ipPools : [],
|
||||
});
|
||||
@@ -334,7 +347,17 @@ function NetworkConfigManager() {
|
||||
};
|
||||
|
||||
const handleEditGateway = (gateway) => {
|
||||
setEditingGateway({ ...gateway });
|
||||
// Миграция старых данных при редактировании
|
||||
const migratedGateway = gateway.type === 'recursive' && gateway.parentGatewayId && (!gateway.parentGateways || gateway.parentGateways.length === 0)
|
||||
? {
|
||||
...gateway,
|
||||
parentGateways: [{ id: gateway.parentGatewayId, distance: undefined }]
|
||||
}
|
||||
: {
|
||||
...gateway,
|
||||
parentGateways: gateway.parentGateways || []
|
||||
};
|
||||
setEditingGateway(migratedGateway);
|
||||
setGatewayModalMode('edit');
|
||||
setGatewayModalOpen(true);
|
||||
};
|
||||
@@ -346,36 +369,45 @@ function NetworkConfigManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем пустые parentGateways и удаляем старое поле parentGatewayId
|
||||
const cleanedData = {
|
||||
...gatewayData,
|
||||
parentGateways: gatewayData.type === 'recursive'
|
||||
? (gatewayData.parentGateways || []).filter(p => p && p.id)
|
||||
: [],
|
||||
parentGatewayId: undefined // Удаляем старое поле
|
||||
};
|
||||
|
||||
if (gatewayModalMode === 'add') {
|
||||
// Проверка на дубликат IP
|
||||
const existingGateway = config.gateways.find(g => g.ip === gatewayData.ip.trim());
|
||||
const existingGateway = config.gateways.find(g => g.ip === cleanedData.ip.trim());
|
||||
if (existingGateway) {
|
||||
notify.error(`Gateway с IP ${gatewayData.ip} уже существует`);
|
||||
notify.error(`Gateway с IP ${cleanedData.ip} уже существует`);
|
||||
return;
|
||||
}
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
gateways: [...prev.gateways, gatewayData],
|
||||
gateways: [...prev.gateways, cleanedData],
|
||||
}));
|
||||
notify.success('Gateway добавлен');
|
||||
} else {
|
||||
// При редактировании ищем по ID, но проверяем, не занят ли новый IP другим gateway
|
||||
const currentGateway = config.gateways.find(g => g.id === gatewayData.id);
|
||||
const currentGateway = config.gateways.find(g => g.id === cleanedData.id);
|
||||
if (!currentGateway) {
|
||||
notify.error('Gateway не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
// Если IP изменился, проверяем, не занят ли он другим gateway
|
||||
if (gatewayData.ip.trim() !== currentGateway.ip &&
|
||||
config.gateways.some(g => g.id !== gatewayData.id && g.ip === gatewayData.ip.trim())) {
|
||||
notify.error(`Gateway с IP ${gatewayData.ip} уже существует`);
|
||||
if (cleanedData.ip.trim() !== currentGateway.ip &&
|
||||
config.gateways.some(g => g.id !== cleanedData.id && g.ip === cleanedData.ip.trim())) {
|
||||
notify.error(`Gateway с IP ${cleanedData.ip} уже существует`);
|
||||
return;
|
||||
}
|
||||
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
gateways: prev.gateways.map(g => g.id === gatewayData.id ? gatewayData : g),
|
||||
gateways: prev.gateways.map(g => g.id === cleanedData.id ? cleanedData : g),
|
||||
}));
|
||||
notify.success('Gateway обновлён');
|
||||
}
|
||||
@@ -586,7 +618,7 @@ function NetworkConfigManager() {
|
||||
code += `# Связь с сервером: ${iface.server2Name}\n`;
|
||||
code += `# Local IP: ${iface.localIp}\n`;
|
||||
code += `# Remote IP: ${iface.remoteIp}\n`;
|
||||
code += `/ip address add address=${iface.localIp}/32 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`;
|
||||
code += `/ip address add address=${iface.localIp}/30 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`;
|
||||
code += '\n';
|
||||
});
|
||||
|
||||
@@ -641,10 +673,13 @@ function NetworkConfigManager() {
|
||||
let validRoutesCount = 0;
|
||||
|
||||
gateways.forEach(gw => {
|
||||
const parent = getParentGateway(gw.parentGatewayId);
|
||||
// Поддержка старого формата (parentGatewayId) и нового (parentGateways)
|
||||
const parentGatewaysList = gw.parentGateways && Array.isArray(gw.parentGateways) && gw.parentGateways.length > 0
|
||||
? gw.parentGateways
|
||||
: (gw.parentGatewayId ? [{ id: gw.parentGatewayId, distance: undefined }] : []);
|
||||
|
||||
if (!parent) {
|
||||
code += `# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`;
|
||||
if (parentGatewaysList.length === 0) {
|
||||
code += `# Gateway "${gw.ip || 'без IP'}" - родительские gateway/интерфейсы не указаны, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -653,43 +688,57 @@ function NetworkConfigManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем IP родителя в зависимости от типа
|
||||
const parentIp = parent.parentType === 'interface' ? parent.remoteIp : parent.ip;
|
||||
|
||||
if (!parentIp) {
|
||||
const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway';
|
||||
code += `# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
validRoutesCount++;
|
||||
|
||||
// Создаём маршрут для рекурсивного gateway
|
||||
const parentLabel = parent.parentType === 'interface'
|
||||
? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})`
|
||||
: `gateway ${parent.ip}`;
|
||||
|
||||
code += `# Рекурсивный gateway: ${gw.ip}\n`;
|
||||
code += `# Родительский: ${parentLabel}\n`;
|
||||
|
||||
// Формируем gateway: для интерфейсов добавляем имя через %, для gateway - только IP
|
||||
const gatewayValue = parent.parentType === 'interface'
|
||||
? `${parentIp}%${parent.name || parent.interfaceType || 'interface'}`
|
||||
: parentIp;
|
||||
|
||||
// Создаём маршрут к IP рекурсивного gateway через родительский
|
||||
const comment = gw.description
|
||||
? `${gw.description} -> ${parentIp}`
|
||||
: `Recursive: ${gw.ip} -> ${parentIp}`;
|
||||
code += `/ip route add dst-address=${gw.ip}/32 gateway=${gatewayValue} comment="${comment}"\n`;
|
||||
|
||||
// Если у рекурсивного gateway есть описание с указанием на default route
|
||||
if (gw.description && (gw.description.toLowerCase().includes('default') || gw.description.toLowerCase().includes('0.0.0.0'))) {
|
||||
code += `# Дополнительный default route через рекурсивный gateway\n`;
|
||||
const defaultComment = gw.description ? `${gw.description} (default)` : `Recursive default: ${gw.ip}`;
|
||||
code += `/ip route add dst-address=0.0.0.0/0 gateway=${gatewayValue} comment="${defaultComment}"\n`;
|
||||
}
|
||||
code += '\n';
|
||||
// Обрабатываем каждый родительский gateway
|
||||
parentGatewaysList.forEach((parentRef, parentIndex) => {
|
||||
const parent = getParentGateway(parentRef.id);
|
||||
|
||||
if (!parent) {
|
||||
code += `# Gateway "${gw.ip}" - родительский gateway/интерфейс "${parentRef.id}" не найден, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем IP родителя в зависимости от типа
|
||||
const parentIp = parent.parentType === 'interface' ? parent.remoteIp : parent.ip;
|
||||
|
||||
if (!parentIp) {
|
||||
const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway';
|
||||
code += `# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
validRoutesCount++;
|
||||
|
||||
// Создаём маршрут для рекурсивного gateway
|
||||
const parentLabel = parent.parentType === 'interface'
|
||||
? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})`
|
||||
: `gateway ${parent.ip}`;
|
||||
|
||||
const distanceSuffix = parentRef.distance !== undefined && parentRef.distance !== null ? ` (distance: ${parentRef.distance})` : '';
|
||||
code += `# Рекурсивный gateway: ${gw.ip}${distanceSuffix}\n`;
|
||||
code += `# Родительский: ${parentLabel}\n`;
|
||||
|
||||
// Формируем gateway: для интерфейсов добавляем имя через %, для gateway - только IP
|
||||
const gatewayValue = parent.parentType === 'interface'
|
||||
? `${parentIp}%${parent.name || parent.interfaceType || 'interface'}`
|
||||
: parentIp;
|
||||
|
||||
// Создаём маршрут к IP рекурсивного gateway через родительский
|
||||
const comment = gw.description
|
||||
? `Recursive: ${gw.description} -> ${parentIp}${distanceSuffix}`
|
||||
: `Recursive: ${gw.ip} -> ${parentIp}${distanceSuffix}`;
|
||||
|
||||
// Формируем команду с distance, если указан
|
||||
const distanceParam = parentRef.distance !== undefined && parentRef.distance !== null ? ` distance=${parentRef.distance}` : '';
|
||||
code += `/ip route add dst-address=${gw.ip}/32 gateway=${gatewayValue}${distanceParam} comment="${comment}"\n`;
|
||||
|
||||
// Если у рекурсивного gateway есть описание с указанием на default route
|
||||
if (gw.description && (gw.description.toLowerCase().includes('default') || gw.description.toLowerCase().includes('0.0.0.0'))) {
|
||||
code += `# Дополнительный default route через рекурсивный gateway${distanceSuffix}\n`;
|
||||
const defaultComment = gw.description ? `Recursive: ${gw.description} (default)${distanceSuffix}` : `Recursive: ${gw.ip} (default)${distanceSuffix}`;
|
||||
code += `/ip route add dst-address=0.0.0.0/0 gateway=${gatewayValue}${distanceParam} comment="${defaultComment}"\n`;
|
||||
}
|
||||
code += '\n';
|
||||
});
|
||||
});
|
||||
|
||||
code += '# Проверка созданных маршрутов:\n';
|
||||
@@ -921,9 +970,16 @@ function NetworkConfigManager() {
|
||||
const gatewayCountry = gateway.country || '';
|
||||
const displayCountry = gatewayCountry || serverCountry;
|
||||
const gatewayType = GATEWAY_TYPES.find(t => t.value === gateway.type) || GATEWAY_TYPES[0];
|
||||
const parentGateway = gateway.type === 'recursive' && gateway.parentGatewayId
|
||||
? getParentGateway(gateway.parentGatewayId)
|
||||
: null;
|
||||
|
||||
// Поддержка старого формата (parentGatewayId) и нового (parentGateways)
|
||||
const parentGatewaysList = gateway.type === 'recursive'
|
||||
? (gateway.parentGateways && Array.isArray(gateway.parentGateways) && gateway.parentGateways.length > 0
|
||||
? gateway.parentGateways
|
||||
: (gateway.parentGatewayId ? [{ id: gateway.parentGatewayId, distance: undefined }] : []))
|
||||
: [];
|
||||
|
||||
// Получаем информацию о первом родительском gateway для отображения
|
||||
const firstParent = parentGatewaysList.length > 0 ? getParentGateway(parentGatewaysList[0].id) : null;
|
||||
|
||||
// Определяем тип индикатора (как в ServerCard)
|
||||
const indicatorType = gateway.type === 'recursive' ? 'EXIT' : 'JUMP';
|
||||
@@ -935,12 +991,16 @@ function NetworkConfigManager() {
|
||||
if (gateway.type === 'direct') {
|
||||
// Для прямого gateway: 0.0.0.0/0 -> IP адрес
|
||||
displayText = gateway.ip ? `0.0.0.0/0 → ${gateway.ip}` : '';
|
||||
} else if (gateway.type === 'recursive' && parentGateway) {
|
||||
// Для рекурсивного: IP адрес -> IP адрес родительского gateway
|
||||
const parentIp = parentGateway.parentType === 'interface'
|
||||
? parentGateway.remoteIp
|
||||
: parentGateway.ip;
|
||||
} else if (gateway.type === 'recursive' && firstParent) {
|
||||
// Для рекурсивного: IP адрес -> IP адрес первого родительского gateway
|
||||
const parentIp = firstParent.parentType === 'interface'
|
||||
? firstParent.remoteIp
|
||||
: firstParent.ip;
|
||||
displayText = gateway.ip && parentIp ? `${gateway.ip} → ${parentIp}` : '';
|
||||
// Если несколько родительских gateway, добавляем информацию
|
||||
if (parentGatewaysList.length > 1) {
|
||||
displayText += ` (+${parentGatewaysList.length - 1})`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -967,36 +1027,32 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
|
||||
{/* Центральная часть: информация */}
|
||||
<div className="flex-grow-1 py-2 px-3" style={{ minWidth: 0, flex: '1 1 auto' }}>
|
||||
{/* Верхняя строка: флаг, название, провайдер */}
|
||||
<div className="d-flex align-items-center mb-1 flex-wrap">
|
||||
<div className="d-flex align-items-center me-3" style={{ flex: '0 0 auto' }}>
|
||||
<div className="flex-grow-1 py-2 px-3" style={{ minWidth: 0, flex: '1 1 auto', overflow: 'hidden' }}>
|
||||
{/* Верхняя строка: флаг, название */}
|
||||
<div className="d-flex align-items-center mb-1">
|
||||
<div className="d-flex align-items-center" style={{ flex: '0 0 auto', minWidth: 0 }}>
|
||||
{displayCountry && (
|
||||
<span className="me-2" style={{ fontSize: '1.25rem', flexShrink: 0 }}>{countryToFlag(displayCountry)}</span>
|
||||
)}
|
||||
<div style={{ whiteSpace: 'nowrap' }}>
|
||||
<span className="fw-semibold">{gateway.description || provider || gateway.ip || '—'}</span>
|
||||
{provider && (
|
||||
<>
|
||||
<span className="text-muted mx-2">•</span>
|
||||
<span className="text-muted">{provider}</span>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<span className="fw-semibold">{gateway.description || gateway.ip || '—'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Зеленый блок с маршрутом */}
|
||||
{displayText && (
|
||||
<span className="badge bg-success-lt text-success ms-auto" style={{ whiteSpace: 'nowrap', flexShrink: 0, fontSize: '0.875rem' }}>
|
||||
</div>
|
||||
|
||||
{/* Маршрут ip->ip сразу под названием */}
|
||||
{displayText && (
|
||||
<div className="mb-1">
|
||||
<span className="badge bg-success-lt text-success" style={{ whiteSpace: 'nowrap', fontSize: '0.875rem' }}>
|
||||
{displayText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Нижняя строка: DNS + badges */}
|
||||
<div className="d-flex align-items-center gap-2 flex-wrap">
|
||||
{server && (
|
||||
<code className="text-muted" style={{ fontSize: '0.8rem', wordBreak: 'break-all' }}>
|
||||
<code className="text-muted" style={{ fontSize: '0.8rem', wordBreak: 'break-all', overflowWrap: 'break-word' }}>
|
||||
{server.dns || server.ip || gateway.serverId}
|
||||
</code>
|
||||
)}
|
||||
@@ -1102,30 +1158,32 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
|
||||
{/* Центральная часть: информация */}
|
||||
<div className="flex-grow-1 py-2 px-3" style={{ minWidth: 0, flex: '1 1 auto' }}>
|
||||
<div className="flex-grow-1 py-2 px-3" style={{ minWidth: 0, flex: '1 1 auto', overflow: 'hidden' }}>
|
||||
{/* Верхняя строка: флаг, название */}
|
||||
<div className="d-flex align-items-center mb-1 flex-wrap">
|
||||
<div className="d-flex align-items-center me-3" style={{ flex: '0 0 auto' }}>
|
||||
<div className="d-flex align-items-center mb-1">
|
||||
<div className="d-flex align-items-center" style={{ flex: '0 0 auto', minWidth: 0 }}>
|
||||
{displayCountry && (
|
||||
<span className="me-2" style={{ fontSize: '1.25rem', flexShrink: 0 }}>{countryToFlag(displayCountry)}</span>
|
||||
)}
|
||||
<div style={{ whiteSpace: 'nowrap' }}>
|
||||
<div>
|
||||
<span className="fw-semibold">{interfaceName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Зеленый блок с IP */}
|
||||
{displayIp && displayIp !== '—' && (
|
||||
<span className="badge bg-success-lt text-success ms-auto" style={{ whiteSpace: 'nowrap', flexShrink: 0, fontSize: '0.875rem' }}>
|
||||
→ {displayIp}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Маршрут между Local IP и Remote IP сразу под названием */}
|
||||
{displayLocalIp !== '—' && displayRemoteIp !== '—' && (
|
||||
<div className="mb-1">
|
||||
<span className="badge bg-success-lt text-success" style={{ whiteSpace: 'nowrap', fontSize: '0.875rem' }}>
|
||||
{displayLocalIp} → {displayRemoteIp}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Нижняя строка: DNS + badges */}
|
||||
<div className="d-flex align-items-center gap-2 flex-wrap">
|
||||
{displayServerName && (
|
||||
<code className="text-muted" style={{ fontSize: '0.8rem', wordBreak: 'break-all' }}>
|
||||
<code className="text-muted" style={{ fontSize: '0.8rem', wordBreak: 'break-all', overflowWrap: 'break-word' }}>
|
||||
{displayServerName}
|
||||
</code>
|
||||
)}
|
||||
@@ -1447,9 +1505,13 @@ function NetworkConfigManager() {
|
||||
const gatewayCountry = gateway.country || '';
|
||||
const displayCountry = gatewayCountry || serverCountry;
|
||||
const gatewayType = GATEWAY_TYPES.find(t => t.value === gateway.type) || GATEWAY_TYPES[0];
|
||||
const parentGateway = gateway.type === 'recursive' && gateway.parentGatewayId
|
||||
? getParentGateway(gateway.parentGatewayId)
|
||||
: null;
|
||||
// Поддержка старого формата (parentGatewayId) и нового (parentGateways)
|
||||
const parentGatewaysList = gateway.type === 'recursive'
|
||||
? (gateway.parentGateways && Array.isArray(gateway.parentGateways) && gateway.parentGateways.length > 0
|
||||
? gateway.parentGateways
|
||||
: (gateway.parentGatewayId ? [{ id: gateway.parentGatewayId, distance: undefined }] : []))
|
||||
: [];
|
||||
const firstParent = parentGatewaysList.length > 0 ? getParentGateway(parentGatewaysList[0].id) : null;
|
||||
|
||||
return (
|
||||
<tr key={gateway.id}>
|
||||
@@ -1482,14 +1544,17 @@ function NetworkConfigManager() {
|
||||
<span className={`badge bg-${gatewayType.color}-lt text-${gatewayType.color}`}>
|
||||
{gatewayType.label}
|
||||
</span>
|
||||
{parentGateway && (
|
||||
{firstParent && (
|
||||
<div className="text-muted small mt-1">
|
||||
→ {parentGateway.parentType === 'interface'
|
||||
? `Интерфейс ${parentGateway.name || parentGateway.interfaceType || 'interface'} (${parentGateway.remoteIp || '—'})`
|
||||
: parentGateway.ip
|
||||
? `Gateway ${parentGateway.ip}`
|
||||
→ {firstParent.parentType === 'interface'
|
||||
? `Интерфейс ${firstParent.name || firstParent.interfaceType || 'interface'} (${firstParent.remoteIp || '—'})`
|
||||
: firstParent.ip
|
||||
? `Gateway ${firstParent.ip}`
|
||||
: 'Gateway (не указан IP)'
|
||||
}
|
||||
{parentGatewaysList.length > 1 && (
|
||||
<span className="ms-1">(+{parentGatewaysList.length - 1})</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
@@ -1826,7 +1891,7 @@ function NetworkConfigManager() {
|
||||
name="type"
|
||||
type="select"
|
||||
value={editingGateway.type}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, type: val, parentGatewayId: val === 'direct' ? '' : editingGateway.parentGatewayId })}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, type: val, parentGateways: val === 'direct' ? [] : (editingGateway.parentGateways || []) })}
|
||||
options={GATEWAY_TYPES.map(t => ({ value: t.value, label: `${t.label} - ${t.description}` }))}
|
||||
required
|
||||
/>
|
||||
@@ -1843,17 +1908,82 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
{editingGateway.type === 'recursive' && (
|
||||
<div className="col-12">
|
||||
<label className="form-label">Родительский gateway или интерфейс</label>
|
||||
<GatewayAutocompleteInput
|
||||
value={editingGateway.parentGatewayId}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, parentGatewayId: val })}
|
||||
gateways={config.gateways}
|
||||
interfaces={config.tunnelInterfaces}
|
||||
excludeGatewayId={editingGateway.id}
|
||||
placeholder="Выберите родительский gateway или интерфейс..."
|
||||
/>
|
||||
<label className="form-label">Родительские gateway или интерфейсы</label>
|
||||
<div className="d-flex flex-column gap-2">
|
||||
{(editingGateway.parentGateways || []).map((parent, index) => {
|
||||
const parentInfo = getParentGateway(parent.id);
|
||||
return (
|
||||
<div key={index} className="card card-sm border">
|
||||
<div className="card-body p-2">
|
||||
<div className="row g-2 align-items-center">
|
||||
<div className="col-md-8">
|
||||
<GatewayAutocompleteInput
|
||||
value={parent.id}
|
||||
onChange={(val) => {
|
||||
const newParents = [...(editingGateway.parentGateways || [])];
|
||||
newParents[index] = { ...newParents[index], id: val };
|
||||
setEditingGateway({ ...editingGateway, parentGateways: newParents });
|
||||
}}
|
||||
gateways={config.gateways}
|
||||
interfaces={config.tunnelInterfaces}
|
||||
excludeGatewayId={editingGateway.id}
|
||||
placeholder="Выберите родительский gateway или интерфейс..."
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<FormField
|
||||
label="Distance"
|
||||
name="distance"
|
||||
type="number"
|
||||
value={parent.distance || ''}
|
||||
onChange={(val) => {
|
||||
const newParents = [...(editingGateway.parentGateways || [])];
|
||||
newParents[index] = { ...newParents[index], distance: val ? parseInt(val) : undefined };
|
||||
setEditingGateway({ ...editingGateway, parentGateways: newParents });
|
||||
}}
|
||||
placeholder="1"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-1 d-flex align-items-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-danger btn-sm"
|
||||
onClick={() => {
|
||||
const newParents = (editingGateway.parentGateways || []).filter((_, i) => i !== index);
|
||||
setEditingGateway({ ...editingGateway, parentGateways: newParents });
|
||||
}}
|
||||
title="Удалить"
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{parentInfo && (
|
||||
<div className="mt-2 small text-muted">
|
||||
{parentInfo.parentType === 'interface'
|
||||
? `Интерфейс ${parentInfo.name || parentInfo.interfaceType || 'interface'} (${parentInfo.remoteIp || '—'})`
|
||||
: `Gateway ${parentInfo.ip || '—'}`
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => {
|
||||
const newParents = [...(editingGateway.parentGateways || []), { id: '', distance: undefined }];
|
||||
setEditingGateway({ ...editingGateway, parentGateways: newParents });
|
||||
}}
|
||||
>
|
||||
<IconPlus size={16} className="me-1" />
|
||||
Добавить родительский gateway
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-text">
|
||||
Рекурсивный gateway может ссылаться на прямой gateway или на интерфейс (по его remote IP)
|
||||
Рекурсивный gateway может ссылаться на несколько прямых gateway или интерфейсов. Distance определяет приоритет маршрута (меньше = выше приоритет).
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user