diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index 39cb9ad..9865e06 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -547,9 +547,17 @@ function NetworkConfigManager() { setSelectedTemplate(template); // Инициализируем gateways из шаблона const initializedGateways = template.gateways.map((gw, index) => { - // Генерируем IP из шаблона, если есть базовый IP - let ip = gw.ipTemplate || ''; - if (templateBaseIp && gw.ipTemplate) { + let ip = ''; + + // Для прямых gateways используем IP сервера (если выбран сервер) + if (gw.type === 'direct' && templateServerId) { + const server = getServerInfo(templateServerId); + if (server && server.ip) { + ip = server.ip; + } + } + // Для рекурсивных gateways генерируем из базового IP + else if (gw.type === 'recursive' && templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && gw.ipTemplate) { ip = gw.ipTemplate.replace('{baseIp}', templateBaseIp.trim()); } @@ -565,12 +573,21 @@ function NetworkConfigManager() { setTemplateGateways(initializedGateways); }; - // Обновление IP адресов при изменении базового IP + // Обновление IP адресов при изменении базового IP или сервера useEffect(() => { - if (selectedTemplate && templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && templateGateways.length > 0) { + if (selectedTemplate && templateGateways.length > 0) { const updatedGateways = templateGateways.map((gw, index) => { const originalTemplate = selectedTemplate.gateways[index]; - if (originalTemplate && originalTemplate.ipTemplate) { + + // Для прямых gateways используем IP сервера + if (gw.type === 'direct' && templateServerId) { + const server = getServerInfo(templateServerId); + if (server && server.ip) { + return { ...gw, ip: server.ip }; + } + } + // Для рекурсивных gateways генерируем из базового IP + else if (gw.type === 'recursive' && templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && originalTemplate && originalTemplate.ipTemplate) { const templateIp = originalTemplate.ipTemplate.replace('{baseIp}', templateBaseIp.trim()); // Обновляем IP только если он соответствует шаблону (последний октет совпадает) или пустой const currentIpParts = gw.ip ? gw.ip.split('.') : []; @@ -591,7 +608,7 @@ function NetworkConfigManager() { } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [templateBaseIp, selectedTemplate?.id]); + }, [templateBaseIp, templateServerId, selectedTemplate?.id]); const handleCreateGatewaysFromTemplate = () => { if (!selectedTemplate) { @@ -618,13 +635,22 @@ function NetworkConfigManager() { const newGateways = []; const usedIps = new Set(config.gateways.map(g => g.ip)); + + // Создаем маппинг между ID из шаблона и реальными ID после создания + const templateIdToRealId = new Map(); templateGateways.forEach((gwTemplate, index) => { - // Используем IP из редактируемого gateway (уже сгенерированный из шаблона) let ip = gwTemplate.ip || ''; - // Если IP пустой, пытаемся сгенерировать из шаблона - if (!ip && selectedTemplate.gateways[index]?.ipTemplate) { + // Для прямых gateways используем IP сервера + if (gwTemplate.type === 'direct' && templateServerId) { + const server = getServerInfo(templateServerId); + if (server && server.ip) { + ip = server.ip; + } + } + // Для рекурсивных gateways генерируем из базового IP + else if (gwTemplate.type === 'recursive' && !ip && selectedTemplate.gateways[index]?.ipTemplate) { ip = selectedTemplate.gateways[index].ipTemplate.replace('{baseIp}', templateBaseIp.trim()); } @@ -642,15 +668,24 @@ function NetworkConfigManager() { return; } + const newGatewayId = `gw-${Date.now()}-${index}-${Math.random().toString(16).slice(2, 6)}`; + + // Сохраняем маппинг ID + templateIdToRealId.set(gwTemplate.id, newGatewayId); + const newGateway = { - id: `gw-${Date.now()}-${index}-${Math.random().toString(16).slice(2, 6)}`, + id: newGatewayId, ip: ip, description: gwTemplate.description || '', serverId: templateServerId, country: gwTemplate.country || '', type: gwTemplate.type, parentGateways: gwTemplate.type === 'recursive' - ? (gwTemplate.parentGateways || []).filter(p => p && p.id) + ? (gwTemplate.parentGateways || []).filter(p => p && p.id).map(p => { + // Преобразуем ID родительского gateway из шаблона в реальный ID + const realParentId = templateIdToRealId.get(p.id) || p.id; + return { ...p, id: realParentId }; + }) : [], }; @@ -2569,9 +2604,21 @@ function NetworkConfigManager() { }; // === Получение родительского gateway или интерфейса === - const getParentGateway = (parentId) => { + const getParentGateway = (parentId, templateGatewaysList = null) => { if (!parentId) return null; + // Если передан список templateGateways, сначала ищем там + if (templateGatewaysList) { + const templateGateway = templateGatewaysList.find(g => g.id === parentId); + if (templateGateway) { + return { + parentType: 'gateway', + ...templateGateway, + ip: templateGateway.ip + }; + } + } + // Сначала ищем в gateway const gateway = config.gateways?.find(g => g.id === parentId); if (gateway) { @@ -2598,7 +2645,8 @@ function NetworkConfigManager() { // Если не найден ни gateway, ни интерфейс - логируем для отладки console.warn('Parent gateway/interface not found:', parentId, { availableGateways: config.gateways?.map(g => g.id), - availableInterfaces: config.tunnelInterfaces?.map(i => i.id) + availableInterfaces: config.tunnelInterfaces?.map(i => i.id), + templateGateways: templateGatewaysList?.map(g => g.id) }); return null; @@ -5051,15 +5099,21 @@ function NetworkConfigManager() {
{GATEWAY_TEMPLATES.map(template => { - // Генерируем предпросмотр IP адресов если есть базовый IP - const previewIps = templateBaseIp && templateBaseIp.trim().split('.').length >= 3 - ? template.gateways.map(gw => { - if (gw.ipTemplate) { - return gw.ipTemplate.replace('{baseIp}', templateBaseIp.trim()); - } - return '—'; - }) - : template.gateways.map(() => '—'); + // Генерируем предпросмотр IP адресов если есть базовый IP и сервер + const previewIps = template.gateways.map((gw, idx) => { + // Для прямых gateways показываем IP сервера, если выбран + if (gw.type === 'direct' && templateServerId) { + const server = getServerInfo(templateServerId); + if (server && server.ip) { + return server.ip; + } + } + // Для рекурсивных gateways генерируем из базового IP + if (gw.type === 'recursive' && templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && gw.ipTemplate) { + return gw.ipTemplate.replace('{baseIp}', templateBaseIp.trim()); + } + return '—'; + }); return (
@@ -5085,7 +5139,7 @@ function NetworkConfigManager() { {template.gateways.filter(g => g.type === 'direct').length} прямых, {template.gateways.filter(g => g.type === 'recursive').length} рекурсивных
- {templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && ( + {templateBaseIp && templateBaseIp.trim().split('.').length >= 3 && templateServerId && (
Предпросмотр IP:
@@ -5138,7 +5192,7 @@ function NetworkConfigManager() { placeholder="10.9.9 или 45.8.248 (минимум 3 октета)" />
- Базовый IP для генерации адресов gateways. Например, для "10.9.9" будут созданы IP: 10.9.9.1, 10.9.9.2, 10.9.9.3 и т.д. + Базовый IP для генерации адресов рекурсивных gateways. Например, для "10.9.9" будут созданы IP: 10.9.9.1, 10.9.9.2, 10.9.9.3 и т.д. Прямые gateways будут использовать IP адрес выбранного сервера.
@@ -5188,10 +5242,17 @@ function NetworkConfigManager() { newGateways[index].ip = e.target.value; setTemplateGateways(newGateways); }} - placeholder={gw.ipTemplate ? gw.ipTemplate.replace('{baseIp}', templateBaseIp || 'X.X.X') : 'IP адрес'} + placeholder={ + gw.type === 'direct' && templateServerId + ? (getServerInfo(templateServerId)?.ip || 'IP сервера') + : (gw.ipTemplate ? gw.ipTemplate.replace('{baseIp}', templateBaseIp || 'X.X.X') : 'IP адрес') + } />
- Шаблон: {gw.ipTemplate || '—'} + {gw.type === 'direct' + ? (templateServerId ? `IP сервера: ${getServerInfo(templateServerId)?.ip || '—'}` : 'Выберите сервер для автоматической подстановки IP') + : `Шаблон: ${gw.ipTemplate || '—'}` + }
@@ -5214,7 +5275,7 @@ function NetworkConfigManager() {
{(gw.parentGateways || []).map((parent, parentIndex) => { - const parentInfo = getParentGateway(parent.id); + const parentInfo = getParentGateway(parent.id, templateGateways); return (
@@ -5230,6 +5291,8 @@ function NetworkConfigManager() { }} gateways={config.gateways} interfaces={config.tunnelInterfaces} + templateGateways={templateGateways} + excludeGatewayId={gw.id} placeholder="Выберите родительский gateway или интерфейс..." />
diff --git a/frontend/src/components/GatewayAutocompleteInput.jsx b/frontend/src/components/GatewayAutocompleteInput.jsx index 665c19c..2f51ab4 100644 --- a/frontend/src/components/GatewayAutocompleteInput.jsx +++ b/frontend/src/components/GatewayAutocompleteInput.jsx @@ -10,6 +10,7 @@ function GatewayAutocompleteInput({ onChange, gateways = [], interfaces = [], + templateGateways = [], // Gateways из шаблона, которые еще не созданы excludeGatewayId = null, // ID gateway, который нужно исключить (текущий редактируемый) placeholder = '', className = 'form-control', @@ -25,7 +26,23 @@ function GatewayAutocompleteInput({ const allItems = useMemo(() => { const items = []; - // Добавляем прямые gateway + // Добавляем прямые gateway из шаблона (которые еще не созданы) + templateGateways + .filter(g => g.id !== excludeGatewayId && g.type === 'direct' && g.ip) + .forEach(g => { + items.push({ + type: 'gateway', + id: g.id, + value: g.id, + label: g.ip, + description: g.description || '', + ip: g.ip, + isTemplate: true, // Помечаем как шаблонный gateway + search: [g.ip, g.description, 'gateway', 'template'].filter(Boolean).join(' ').toLowerCase(), + }); + }); + + // Добавляем прямые gateway (уже созданные) gateways .filter(g => g.id !== excludeGatewayId && g.type === 'direct' && g.ip) .forEach(g => { @@ -36,6 +53,7 @@ function GatewayAutocompleteInput({ label: g.ip, description: g.description || '', ip: g.ip, + isTemplate: false, search: [g.ip, g.description, 'gateway'].filter(Boolean).join(' ').toLowerCase(), }); }); @@ -57,7 +75,7 @@ function GatewayAutocompleteInput({ }); return items; - }, [gateways, interfaces, excludeGatewayId]); + }, [gateways, interfaces, templateGateways, excludeGatewayId]); const suggestions = useMemo(() => { const q = String(value || '').toLowerCase(); @@ -182,16 +200,17 @@ function GatewayAutocompleteInput({ onMouseEnter={() => setActiveIndex(globalIdx)} >
- +
{s.label} {s.description && ({s.description})} + {s.isTemplate && Шаблон}
- Gateway + Gateway {s.isTemplate ? '(из шаблона)' : ''}