diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index b452537..b9d4b50 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -509,6 +509,133 @@ function NetworkConfigManager() { // === Создание интерфейса из шаблона === const handleCreateInterfaceFromTemplate = () => { + const tunnelCount = Math.max(1, Math.min(100, parseInt(templateTunnelCount) || 1)); // Ограничиваем от 1 до 100 + + // Если количество туннелей больше 1, создаем туннели между разными парами серверов + if (tunnelCount > 1) { + // Получаем список всех серверов + const availableServers = servers.filter(s => s.id && s.ip); + + if (availableServers.length < 2) { + notify.error('Недостаточно серверов для создания нескольких туннелей (нужно минимум 2)'); + return; + } + + // Генерируем все возможные пары серверов + const serverPairs = []; + for (let i = 0; i < availableServers.length; i++) { + for (let j = i + 1; j < availableServers.length; j++) { + serverPairs.push([availableServers[i].id, availableServers[j].id]); + } + } + + if (serverPairs.length < tunnelCount) { + notify.error(`Недостаточно пар серверов для создания ${tunnelCount} туннелей. Доступно пар: ${serverPairs.length}`); + return; + } + + // Берем первые N пар + const pairsToUse = serverPairs.slice(0, tunnelCount); + + const newInterfaces = []; + const usedIps = new Set(getAllUsedIpsSet); + + // Создаем туннели для каждой пары + pairsToUse.forEach((pair, index) => { + const server1Id = pair[0]; + const server2Id = pair[1]; + const server1 = getServerInfo(server1Id); + const server2 = getServerInfo(server2Id); + + if (!server1 || !server2) { + notify.error(`Не удалось найти информацию о серверах для пары ${index + 1}`); + return; + } + + // Генерируем имена интерфейсов + const baseName1 = generateInterfaceName(server1, server2, templateType, templateNamePrefix); + const baseName2 = generateInterfaceName(server2, server1, templateType, templateNamePrefix); + const name1 = templateName1.trim() ? `${templateName1.trim()}-${index + 1}` : baseName1; + const name2 = templateName2.trim() ? `${templateName2.trim()}-${index + 1}` : baseName2; + + // Генерируем IP адреса (учитываем уже использованные в этой итерации) + // Важно: Local IP и Remote IP должны быть разными и в одной сети /30 + let localIp = null; + let remoteIp = null; + let attempts = 0; + const maxAttempts = 1000; + + while ((!localIp || !remoteIp || localIp === remoteIp) && attempts < maxAttempts) { + const candidateLocal = generateFreePrivateIp(null, false, null, templateIpPoolId || null); + if (candidateLocal && !usedIps.has(candidateLocal)) { + const candidateRemote = generateFreePrivateIp(null, true, candidateLocal, templateIpPoolId || null); + if (candidateRemote && !usedIps.has(candidateRemote) && candidateRemote !== candidateLocal) { + localIp = candidateLocal; + remoteIp = candidateRemote; + usedIps.add(localIp); + usedIps.add(remoteIp); + break; + } + } + attempts++; + } + + if (!localIp || !remoteIp || localIp === remoteIp) { + notify.error(`Не удалось найти свободные IP адреса для туннеля ${index + 1} (${server1.dns || server1.ip} ↔ ${server2.dns || server2.ip})`); + return; + } + + // Создаем интерфейс + const newInterface = { + ...getEmptyInterface(), + name: name1, + name2: name2, + type: templateType, + localIp: localIp, + remoteIp: remoteIp, + serverId: server1Id, + serverId2: server2Id, + ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : '', + ipPoolId: templateIpPoolId || '' + }; + + // Проверяем конфликты с существующими интерфейсами + const conflicts = checkInterfaceIpConflict(newInterface); + if (conflicts.length > 0) { + notify.error(`Обнаружены конфликты IP адресов для туннеля ${index + 1}. Попробуйте еще раз.`); + return; + } + + newInterfaces.push(newInterface); + }); + + if (newInterfaces.length === 0) { + return; // Ошибки уже показаны выше + } + + // Добавляем все созданные интерфейсы + setConfig(prev => ({ + ...prev, + tunnelInterfaces: [...prev.tunnelInterfaces, ...newInterfaces], + })); + + notify.success(`Создано туннелей: ${newInterfaces.length} между разными парами серверов`); + + // Закрываем модал и сбрасываем значения + setInterfaceTemplateModalOpen(false); + setTemplateServer1(''); + setTemplateServer2(''); + setTemplateType('GRE'); + setTemplateNamePrefix(''); + setTemplateName1(''); + setTemplateName2(''); + setTemplateIpsecPasswordId(''); + setTemplateIpPoolId(''); + setTemplateTunnelCount(1); + return; + } + + // Если количество туннелей = 1, используем старую логику с выбором двух серверов if (!templateServer1 || !templateServer2) { notify.error('Выберите оба сервера'); return; @@ -519,8 +646,6 @@ function NetworkConfigManager() { return; } - const tunnelCount = Math.max(1, Math.min(100, parseInt(templateTunnelCount) || 1)); // Ограничиваем от 1 до 100 - const server1 = getServerInfo(templateServer1); const server2 = getServerInfo(templateServer2); @@ -529,93 +654,50 @@ function NetworkConfigManager() { return; } - const newInterfaces = []; - const usedIps = new Set(getAllUsedIpsSet); + // Генерируем имена интерфейсов (используем опциональные имена если указаны) + const name1 = templateName1.trim() || generateInterfaceName(server1, server2, templateType, templateNamePrefix); + const name2 = templateName2.trim() || generateInterfaceName(server2, server1, templateType, templateNamePrefix); - // Создаем указанное количество туннелей - for (let i = 0; i < tunnelCount; i++) { - // Генерируем имена интерфейсов - // Если указаны имена вручную, используем их (для первого туннеля) или добавляем номер - let name1, name2; - if (templateName1.trim() && i === 0) { - name1 = templateName1.trim(); - } else if (templateName1.trim()) { - name1 = `${templateName1.trim()}-${i + 1}`; - } else { - const baseName = generateInterfaceName(server1, server2, templateType, templateNamePrefix); - name1 = tunnelCount > 1 ? `${baseName}-${i + 1}` : baseName; - } - - if (templateName2.trim() && i === 0) { - name2 = templateName2.trim(); - } else if (templateName2.trim()) { - name2 = `${templateName2.trim()}-${i + 1}`; - } else { - const baseName = generateInterfaceName(server2, server1, templateType, templateNamePrefix); - name2 = tunnelCount > 1 ? `${baseName}-${i + 1}` : baseName; - } - - // Генерируем IP адреса (учитываем уже использованные в этой итерации) - let localIp = null; - let remoteIp = null; - let attempts = 0; - const maxAttempts = 1000; - - while (!localIp && attempts < maxAttempts) { - const candidateLocal = generateFreePrivateIp(null, false, null, templateIpPoolId || null); - if (candidateLocal && !usedIps.has(candidateLocal)) { - const candidateRemote = generateFreePrivateIp(null, true, candidateLocal, templateIpPoolId || null); - if (candidateRemote && !usedIps.has(candidateRemote)) { - localIp = candidateLocal; - remoteIp = candidateRemote; - usedIps.add(localIp); - usedIps.add(remoteIp); - break; - } - } - attempts++; - } - - if (!localIp || !remoteIp) { - notify.error(`Не удалось найти свободные IP адреса для туннеля ${i + 1}`); - return; - } - - // Создаем интерфейс - const newInterface = { - ...getEmptyInterface(), - name: name1, - name2: name2, - type: templateType, - localIp: localIp, - remoteIp: remoteIp, - serverId: templateServer1, - serverId2: templateServer2, - ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : '', - ipPoolId: templateIpPoolId || '' - }; - - // Проверяем конфликты с существующими интерфейсами - const conflicts = checkInterfaceIpConflict(newInterface); - if (conflicts.length > 0) { - notify.error(`Обнаружены конфликты IP адресов для туннеля ${i + 1}. Попробуйте еще раз.`); - return; - } - - newInterfaces.push(newInterface); + // Генерируем IP адреса + const localIp = generateFreePrivateIp(null, false, null, templateIpPoolId || null); + if (!localIp) { + notify.error('Не удалось найти свободный Local IP адрес'); + return; } - // Добавляем все созданные интерфейсы + const remoteIp = generateFreePrivateIp(null, true, localIp, templateIpPoolId || null); + if (!remoteIp || remoteIp === localIp) { + notify.error('Не удалось найти свободный Remote IP адрес (отличающийся от Local IP)'); + return; + } + + // Проверяем конфликты + const newInterface = { + ...getEmptyInterface(), + name: name1, + name2: name2, + type: templateType, + localIp: localIp, + remoteIp: remoteIp, + serverId: templateServer1, + serverId2: templateServer2, + ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : '', + ipPoolId: templateIpPoolId || '' + }; + + const conflicts = checkInterfaceIpConflict(newInterface); + if (conflicts.length > 0) { + notify.error('Обнаружены конфликты IP адресов. Попробуйте еще раз.'); + return; + } + + // Создаем интерфейс setConfig(prev => ({ ...prev, - tunnelInterfaces: [...prev.tunnelInterfaces, ...newInterfaces], + tunnelInterfaces: [...prev.tunnelInterfaces, newInterface], })); - if (tunnelCount === 1) { - notify.success(`Интерфейс создан: ${newInterfaces[0].name} (${newInterfaces[0].localIp} ↔ ${newInterfaces[0].remoteIp})`); - } else { - notify.success(`Создано туннелей: ${tunnelCount}`); - } + notify.success(`Интерфейс создан: ${name1} (${localIp} ↔ ${remoteIp})`); // Закрываем модал и сбрасываем значения setInterfaceTemplateModalOpen(false); @@ -3236,7 +3318,11 @@ function NetworkConfigManager() { min="1" max="100" /> -
Сколько туннелей создать между выбранными серверами (1-100)
+
+ {templateTunnelCount > 1 + ? `Будет создано ${templateTunnelCount} туннелей между разными парами серверов (каждый с каждым)` + : 'Сколько туннелей создать между выбранными серверами (1-100)'} +
@@ -3259,42 +3345,57 @@ function NetworkConfigManager() { />
Будет добавлен перед именем интерфейса
-
- - setTemplateServer1(val)} - servers={servers.filter(s => s.id !== templateServer2 && s.ip !== templateServer2)} - placeholder="Выберите первый сервер..." - /> - {templateServer1 && (() => { - const server1 = getServerInfo(templateServer1); - return server1 ? ( -
- {server1.dns || server1.ip} - {server1.country && ` • ${countryToFlag(server1.country)} ${server1.country}`} -
- ) : null; - })()} -
-
- - setTemplateServer2(val)} - servers={servers.filter(s => s.id !== templateServer1 && s.ip !== templateServer1)} - placeholder="Выберите второй сервер..." - /> - {templateServer2 && (() => { - const server2 = getServerInfo(templateServer2); - return server2 ? ( -
- {server2.dns || server2.ip} - {server2.country && ` • ${countryToFlag(server2.country)} ${server2.country}`} -
- ) : null; - })()} -
+ {templateTunnelCount === 1 && ( + <> +
+ + setTemplateServer1(val)} + servers={servers.filter(s => s.id !== templateServer2 && s.ip !== templateServer2)} + placeholder="Выберите первый сервер..." + /> + {templateServer1 && (() => { + const server1 = getServerInfo(templateServer1); + return server1 ? ( +
+ {server1.dns || server1.ip} + {server1.country && ` • ${countryToFlag(server1.country)} ${server1.country}`} +
+ ) : null; + })()} +
+
+ + setTemplateServer2(val)} + servers={servers.filter(s => s.id !== templateServer1 && s.ip !== templateServer1)} + placeholder="Выберите второй сервер..." + /> + {templateServer2 && (() => { + const server2 = getServerInfo(templateServer2); + return server2 ? ( +
+ {server2.dns || server2.ip} + {server2.country && ` • ${countryToFlag(server2.country)} ${server2.country}`} +
+ ) : null; + })()} +
+ + )} + {templateTunnelCount > 1 && ( +
+
+ Режим множественного создания: Будет создано {templateTunnelCount} туннелей между разными парами серверов. +
+ Туннели будут созданы между всеми возможными парами серверов (каждый с каждым). +
+ Доступно серверов: {servers.filter(s => s.id && s.ip).length}. Максимум пар: {Math.floor((servers.filter(s => s.id && s.ip).length * (servers.filter(s => s.id && s.ip).length - 1)) / 2)} +
+
+ )} {templateServer1 && templateServer2 && templateServer1 !== templateServer2 && (() => { const server1 = getServerInfo(templateServer1); const server2 = getServerInfo(templateServer2);