diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx index 2770474..1162ccb 100644 --- a/frontend/src/NetworkConfigManager.jsx +++ b/frontend/src/NetworkConfigManager.jsx @@ -111,6 +111,7 @@ function NetworkConfigManager() { const [typeFilter, setTypeFilter] = useState(''); const [gatewayTypeFilter, setGatewayTypeFilter] = useState(''); // Для фильтрации gateway по типу const [viewMode, setViewMode] = useState('cards'); // 'cards' | 'table' + const [ipRegistrySearch, setIpRegistrySearch] = useState(''); // Поиск в реестре IP // === Modals === const [gatewayModalOpen, setGatewayModalOpen] = useState(false); @@ -434,7 +435,126 @@ function NetworkConfigManager() { setInterfaceModalOpen(true); }; + // === Проверка пересечений IP адресов для интерфейсов === + const checkInterfaceIpConflict = (ifaceData, excludeId = null) => { + const conflicts = []; + const allInterfaces = config.tunnelInterfaces || []; + + // Проверяем localIp + if (ifaceData.localIp && ifaceData.localIp.trim()) { + const conflictingLocal = allInterfaces.find(i => + i.id !== excludeId && + (i.localIp === ifaceData.localIp.trim() || i.remoteIp === ifaceData.localIp.trim()) + ); + if (conflictingLocal) { + conflicts.push({ + ip: ifaceData.localIp.trim(), + type: 'localIp', + conflictingInterface: conflictingLocal + }); + } + } + + // Проверяем remoteIp + if (ifaceData.remoteIp && ifaceData.remoteIp.trim()) { + const conflictingRemote = allInterfaces.find(i => + i.id !== excludeId && + (i.localIp === ifaceData.remoteIp.trim() || i.remoteIp === ifaceData.remoteIp.trim()) + ); + if (conflictingRemote) { + conflicts.push({ + ip: ifaceData.remoteIp.trim(), + type: 'remoteIp', + conflictingInterface: conflictingRemote + }); + } + } + + return conflicts; + }; + + // === Получение реестра всех используемых IP адресов === + const ipRegistry = useMemo(() => { + const registry = []; + + // Собираем IP из интерфейсов + (config.tunnelInterfaces || []).forEach(iface => { + if (iface.localIp && iface.localIp.trim()) { + registry.push({ + ip: iface.localIp.trim(), + type: 'localIp', + interface: iface, + interfaceName: iface.name || iface.id + }); + } + if (iface.remoteIp && iface.remoteIp.trim()) { + registry.push({ + ip: iface.remoteIp.trim(), + type: 'remoteIp', + interface: iface, + interfaceName: iface.name || iface.id + }); + } + }); + + // Группируем по IP адресам + const groupedByIp = {}; + registry.forEach(item => { + if (!groupedByIp[item.ip]) { + groupedByIp[item.ip] = { + ip: item.ip, + interfaces: [] + }; + } + groupedByIp[item.ip].interfaces.push({ + type: item.type, + interface: item.interface, + interfaceName: item.interfaceName + }); + }); + + // Преобразуем в массив и сортируем + return Object.values(groupedByIp) + .map(item => ({ + ...item, + usageCount: item.interfaces.length, + hasConflict: item.interfaces.length > 1 // Конфликт если IP используется в нескольких интерфейсах + })) + .sort((a, b) => { + // Сначала конфликты, потом по IP + if (a.hasConflict !== b.hasConflict) { + return a.hasConflict ? -1 : 1; + } + return a.ip.localeCompare(b.ip); + }); + }, [config.tunnelInterfaces]); + + // === Фильтрация реестра IP === + const filteredIpRegistry = useMemo(() => { + if (!ipRegistrySearch) return ipRegistry; + const term = ipRegistrySearch.toLowerCase(); + return ipRegistry.filter(item => + item.ip.toLowerCase().includes(term) || + item.interfaces.some(i => i.interfaceName.toLowerCase().includes(term)) + ); + }, [ipRegistry, ipRegistrySearch]); + const handleSaveInterface = (ifaceData) => { + // Валидация: проверка пересечений IP адресов + const conflicts = checkInterfaceIpConflict( + ifaceData, + interfaceModalMode === 'edit' ? ifaceData.id : null + ); + + if (conflicts.length > 0) { + const conflictMessages = conflicts.map(c => { + const conflictName = c.conflictingInterface.name || c.conflictingInterface.id; + return `${c.type === 'localIp' ? 'Local IP' : 'Remote IP'} ${c.ip} уже используется в интерфейсе "${conflictName}"`; + }); + notify.error(`Обнаружено пересечение IP адресов:\n${conflictMessages.join('\n')}`); + return; + } + if (interfaceModalMode === 'add') { setConfig(prev => ({ ...prev, @@ -1255,6 +1375,21 @@ function NetworkConfigManager() { {config.ipPools?.length || 0} +