diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index 8a34ca1..4bf10e3 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -145,6 +145,7 @@ function NetworkConfigManager() { const [templateName2, setTemplateName2] = useState(''); // Опциональное имя для сервера 2 const [templateIpsecPasswordId, setTemplateIpsecPasswordId] = useState(''); // Выбранный IPSec пароль const [templateIpPoolId, setTemplateIpPoolId] = useState(''); // Выбранный IP пул + const [templateTunnelCount, setTemplateTunnelCount] = useState(1); // Количество туннелей для создания // === IPSec Passwords Management === const [ipsecPasswords, setIpsecPasswords] = useState([]); @@ -518,6 +519,8 @@ function NetworkConfigManager() { return; } + const tunnelCount = Math.max(1, Math.min(100, parseInt(templateTunnelCount) || 1)); // Ограничиваем от 1 до 100 + const server1 = getServerInfo(templateServer1); const server2 = getServerInfo(templateServer2); @@ -526,50 +529,93 @@ function NetworkConfigManager() { return; } - // Генерируем имена интерфейсов (используем опциональные имена если указаны) - const name1 = templateName1.trim() || generateInterfaceName(server1, server2, templateType, templateNamePrefix); - const name2 = templateName2.trim() || generateInterfaceName(server2, server1, templateType, templateNamePrefix); + const newInterfaces = []; + const usedIps = new Set(getAllUsedIpsSet); - // Генерируем IP адреса - const localIp = generateFreePrivateIp(null, false, null, templateIpPoolId || null); - if (!localIp) { - notify.error('Не удалось найти свободный Local IP адрес'); - return; + // Создаем указанное количество туннелей + 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); } - const remoteIp = generateFreePrivateIp(null, true, localIp, templateIpPoolId || null); - if (!remoteIp) { - notify.error('Не удалось найти свободный Remote 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, newInterface], + tunnelInterfaces: [...prev.tunnelInterfaces, ...newInterfaces], })); - notify.success(`Интерфейс создан: ${name1} (${localIp} ↔ ${remoteIp})`); + if (tunnelCount === 1) { + notify.success(`Интерфейс создан: ${newInterfaces[0].name} (${newInterfaces[0].localIp} ↔ ${newInterfaces[0].remoteIp})`); + } else { + notify.success(`Создано туннелей: ${tunnelCount}`); + } // Закрываем модал и сбрасываем значения setInterfaceTemplateModalOpen(false); @@ -581,6 +627,7 @@ function NetworkConfigManager() { setTemplateName2(''); setTemplateIpsecPasswordId(''); setTemplateIpPoolId(''); + setTemplateTunnelCount(1); }; // === CRUD для Interfaces === @@ -3303,14 +3350,31 @@ function NetworkConfigManager() { setTemplateName1(''); setTemplateName2(''); setTemplateIpsecPasswordId(''); + setTemplateIpPoolId(''); + setTemplateTunnelCount(1); }} onSubmit={handleCreateInterfaceFromTemplate} title="Создать интерфейс из шаблона" - submitLabel="Создать интерфейс" + submitLabel={templateTunnelCount > 1 ? `Создать ${templateTunnelCount} туннелей` : "Создать интерфейс"} submitIcon={IconSparkles} size="lg" >