From 5c2edac25a16ee96826c0f03f15c8501553ec7da Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 22 Jan 2026 17:40:14 +0700 Subject: [PATCH] feat(NetworkConfigManager): implement IP suggestion feature for local and remote IPs; add functionality to generate free private IPs from defined ranges --- frontend/src/NetworkConfigManager.jsx | 133 +++++++++++++++++++++++--- 1 file changed, 119 insertions(+), 14 deletions(-) diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index 2205d94..22d93b1 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -19,6 +19,7 @@ import { IconCopy, IconSearch, IconRefresh, + IconWand, IconFilter, IconX, IconCircleFilled, @@ -473,6 +474,86 @@ function NetworkConfigManager() { return conflicts; }; + // === Получение множества всех используемых IP адресов === + const getAllUsedIpsSet = useMemo(() => { + const usedIps = new Set(); + (config.tunnelInterfaces || []).forEach(iface => { + if (iface.localIp && iface.localIp.trim()) { + usedIps.add(iface.localIp.trim()); + } + if (iface.remoteIp && iface.remoteIp.trim()) { + usedIps.add(iface.remoteIp.trim()); + } + }); + return usedIps; + }, [config.tunnelInterfaces]); + + // === Генерация свободного IP адреса из приватных диапазонов === + const generateFreePrivateIp = (excludeIp = null) => { + const usedIps = new Set(getAllUsedIpsSet); + if (excludeIp) { + usedIps.delete(excludeIp.trim()); + } + + // Приватные диапазоны для туннелей (RFC 1918) + // Начинаем с 10.10.0.0/16 для удобства + const ranges = [ + { start: [10, 10, 0, 1], end: [10, 10, 255, 254] }, // 10.10.0.0/16 + { start: [10, 0, 0, 1], end: [10, 255, 255, 254] }, // 10.0.0.0/8 (весь диапазон) + { start: [172, 16, 0, 1], end: [172, 31, 255, 254] }, // 172.16.0.0/12 + { start: [192, 168, 0, 1], end: [192, 168, 255, 254] }, // 192.168.0.0/16 + ]; + + for (const range of ranges) { + const [a, b, c, d] = range.start; + const [aEnd, bEnd, cEnd, dEnd] = range.end; + + // Перебираем IP в диапазоне + for (let aVal = a; aVal <= aEnd; aVal++) { + const bStart = (aVal === a) ? b : 0; + const bEndVal = (aVal === aEnd) ? bEnd : 255; + for (let bVal = bStart; bVal <= bEndVal; bVal++) { + const cStart = (aVal === a && bVal === b) ? c : 0; + const cEndVal = (aVal === aEnd && bVal === bEnd) ? cEnd : 255; + for (let cVal = cStart; cVal <= cEndVal; cVal++) { + const dStart = (aVal === a && bVal === b && cVal === c) ? d : 1; + const dEndVal = (aVal === aEnd && bVal === bEnd && cVal === cEnd) ? dEnd : 254; + for (let dVal = dStart; dVal <= dEndVal; dVal++) { + const ip = `${aVal}.${bVal}.${cVal}.${dVal}`; + if (!usedIps.has(ip)) { + return ip; + } + } + } + } + } + } + + // Если все IP заняты (маловероятно), возвращаем null + return null; + }; + + // === Подбор IP для интерфейса === + const handleSuggestLocalIp = () => { + const freeIp = generateFreePrivateIp(editingInterface?.localIp); + if (freeIp) { + setEditingInterface({ ...editingInterface, localIp: freeIp }); + notify.success(`Подобран Local IP: ${freeIp}`); + } else { + notify.error('Не удалось найти свободный IP адрес'); + } + }; + + const handleSuggestRemoteIp = () => { + const freeIp = generateFreePrivateIp(editingInterface?.remoteIp); + if (freeIp) { + setEditingInterface({ ...editingInterface, remoteIp: freeIp }); + notify.success(`Подобран Remote IP: ${freeIp}`); + } else { + notify.error('Не удалось найти свободный IP адрес'); + } + }; + // === Получение реестра всех используемых IP адресов === const ipRegistry = useMemo(() => { const registry = []; @@ -2285,13 +2366,25 @@ function NetworkConfigManager() { />
- setEditingInterface({ ...editingInterface, localIp: val })} - placeholder="10.10.0.1" - /> +
+
+ setEditingInterface({ ...editingInterface, localIp: val })} + placeholder="10.10.0.1" + /> +
+ +
{editingInterface.localIp && (() => { const conflicts = checkInterfaceIpConflict( { localIp: editingInterface.localIp }, @@ -2308,13 +2401,25 @@ function NetworkConfigManager() { })()}
- setEditingInterface({ ...editingInterface, remoteIp: val })} - placeholder="10.10.0.2" - /> +
+
+ setEditingInterface({ ...editingInterface, remoteIp: val })} + placeholder="10.10.0.2" + /> +
+ +
{editingInterface.remoteIp && (() => { const conflicts = checkInterfaceIpConflict( { remoteIp: editingInterface.remoteIp },