diff --git a/internal/topology/builder.go b/internal/topology/builder.go index f9f2ed5..328b44a 100644 --- a/internal/topology/builder.go +++ b/internal/topology/builder.go @@ -157,49 +157,62 @@ func (b *Builder) Build() models.TopologyGraph { } } - // 策略3b: 通过MAC前缀匹配(改进:排除歧义前缀) - // 当精确MAC匹配失败时,尝试通过MAC前缀匹配 - // 但如果同一前缀匹配到多台设备,则跳过(避免错误连接) - if targetIP == "" && neighbor.RemoteMAC != "" { + // 策略3b: MAC前缀匹配已禁用 - 同品牌设备MAC前缀相同,极易产生误匹配 + // 如果需要连接,请使用精确MAC匹配或确保邻居设备在设备列表中 + if false && targetIP == "" && neighbor.RemoteMAC != "" { neighborMACPrefix := getMACPrefix(neighbor.RemoteMAC) - fmt.Printf(" Trying MAC prefix match: %s (prefix: %s)\n", neighbor.RemoteMAC, neighborMACPrefix) + fmt.Printf(" ⚠ MAC prefix match disabled (too unreliable): %s (prefix: %s)\n", neighbor.RemoteMAC, neighborMACPrefix) + } - // 先统计有多少台设备匹配此MAC前缀 - type prefixMatch struct { - ip string - matchingMACs int - } - var matches []prefixMatch + // 策略3c: LLDP对称性匹配(改进 - 需要双向验证) + // 设备A的接口X连接到设备B的接口Y + // 同时设备B的接口Y也应该连接到设备A的接口X + if targetIP == "" && neighbor.RemoteMAC != "" && neighbor.RemoteInterface != "" { + fmt.Printf(" Trying LLDP symmetric match: looking for device with interface %s\n", neighbor.RemoteInterface) for _, d := range b.devices { if d.IP == device.IP { continue // 跳过自己 } - matchingMACs := 0 - for _, mac := range d.MACAddresses { - if getMACPrefix(mac) == neighborMACPrefix { - matchingMACs++ + // 检查设备d是否有这个接口 + hasInterface := false + for _, iface := range d.Interfaces { + if iface.Name == neighbor.RemoteInterface { + hasInterface = true + break } } - if matchingMACs >= 3 { - matches = append(matches, prefixMatch{ip: d.IP, matchingMACs: matchingMACs}) + if !hasInterface { + continue } - } - // 只在唯一匹配时使用前缀匹配 - if len(matches) == 1 && getSubnet(matches[0].ip) == getSubnet(device.IP) { - targetIP = matches[0].ip - matchMethod = fmt.Sprintf("MAC-prefix(%s)", neighborMACPrefix) - fmt.Printf(" ✓ Matched by MAC prefix: %s (device has %d MACs with prefix %s, same subnet) -> %s\n", - neighbor.RemoteMAC, matches[0].matchingMACs, neighborMACPrefix, targetIP) - } else if len(matches) > 1 { - fmt.Printf(" ✗ Skipping MAC prefix match: %d devices share prefix %s (ambiguous)\n", len(matches), neighborMACPrefix) + // 双向验证:检查设备d的邻居信息中,这个接口是否连接回当前设备 + hasReverseLink := false + for _, dNeighbor := range d.Neighbors { + if dNeighbor.RemoteInterface == neighbor.LocalInterface && dNeighbor.LocalInterface == neighbor.RemoteInterface { + hasReverseLink = true + break + } + } + + if hasReverseLink { + // 双向验证通过! + targetIP = d.IP + matchMethod = fmt.Sprintf("LLDP-symmetric(%s<->%s)", neighbor.LocalInterface, neighbor.RemoteInterface) + fmt.Printf(" ✓ Matched by LLDP symmetric (bidirectional): %s %s <-> %s %s -> %s\n", + device.IP, neighbor.LocalInterface, d.IP, neighbor.RemoteInterface, targetIP) + break + } else { + fmt.Printf(" ✗ Device %s has interface %s but no reverse link to %s %s\n", + d.IP, neighbor.RemoteInterface, device.IP, neighbor.LocalInterface) + } } } // 策略4: 通过本地接口IP网段匹配(新增) + // 注意:此策略容易产生误匹配,仅在必要时使用 if targetIP == "" { // 查找本地接口的IP地址 localInterfaceIP := "" @@ -211,7 +224,7 @@ func (b *Builder) Build() models.TopologyGraph { } if localInterfaceIP != "" { - fmt.Printf(" Trying subnet match: local interface %s has IP %s\n", + fmt.Printf(" ⚠ Trying subnet match (risky): local interface %s has IP %s\n", neighbor.LocalInterface, localInterfaceIP) // 计算本地接口的网段 @@ -227,7 +240,7 @@ func (b *Builder) Build() models.TopologyGraph { if getSubnet(d.IP) == localSubnet { targetIP = d.IP matchMethod = "subnet(management IP)" - fmt.Printf(" ✓ Matched by subnet: %s in %s\n", d.IP, localSubnet) + fmt.Printf(" ⚠ Matched by subnet (risky): %s in %s\n", d.IP, localSubnet) break } @@ -236,7 +249,7 @@ func (b *Builder) Build() models.TopologyGraph { if iface.IP != "" && getSubnet(iface.IP) == localSubnet { targetIP = d.IP matchMethod = fmt.Sprintf("subnet(interface %s)", iface.Name) - fmt.Printf(" ✓ Matched by subnet: %s (%s) in %s\n", + fmt.Printf(" ⚠ Matched by subnet (risky): %s (%s) in %s\n", d.IP, iface.Name, localSubnet) break } @@ -246,27 +259,9 @@ func (b *Builder) Build() models.TopologyGraph { } } } else { - // 策略4b: 本地接口没有IP,尝试使用设备管理IP进行子网匹配(新增) - // 注意:只有当该网段只有2台设备时才使用此策略(点对点连接) - fmt.Printf(" Trying subnet match with management IP: %s\n", device.IP) - localSubnet := getSubnet(device.IP) - - // 统计在该网段的设备数量 - var devicesInSubnet []string - for _, d := range b.devices { - if d.IP != device.IP && getSubnet(d.IP) == localSubnet { - devicesInSubnet = append(devicesInSubnet, d.IP) - } - } - - // 只有当网段中恰好有1台其他设备时才匹配(点对点) - if len(devicesInSubnet) == 1 { - targetIP = devicesInSubnet[0] - matchMethod = "subnet(management IP both sides)" - fmt.Printf(" ✓ Matched by management subnet: %s in %s (only device in subnet)\n", targetIP, localSubnet) - } else if len(devicesInSubnet) > 1 { - fmt.Printf(" ✗ Skipping subnet match: %d devices in subnet %s (ambiguous)\n", len(devicesInSubnet)+1, localSubnet) - } + // 策略4b: 本地接口没有IP,尝试使用设备管理IP进行子网匹配(高风险策略 - 已禁用) + // 此策略容易产生错误连接,暂时禁用 + fmt.Printf(" ✗ Skipping subnet match with management IP (disabled - too risky)\n") } } diff --git a/web/css/style.css b/web/css/style.css index eea6f91..208b7cb 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -100,6 +100,8 @@ header h1 { .sidebar { width: 300px; + min-width: 200px; + max-width: 600px; background: white; padding: 20px; overflow-y: auto; @@ -190,6 +192,7 @@ header h1 { .content { flex: 1; position: relative; + min-width: 400px; } #cy { @@ -200,17 +203,47 @@ header h1 { .detail-panel { width: 600px; + min-width: 300px; + max-width: 1000px; background: white; padding: 20px; overflow-y: auto; box-shadow: -2px 0 5px rgba(0,0,0,0.1); display: none; + position: relative; } .detail-panel.active { display: block; } +/* 拖拽手柄样式 */ +.resizer { + width: 5px; + cursor: col-resize; + background: #e0e0e0; + position: relative; + z-index: 10; + transition: background 0.2s; +} + +.resizer:hover, +.resizer.resizing { + background: #667eea; +} + +.resizer::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 3px; + height: 30px; + background: rgba(0, 0, 0, 0.2); + border-radius: 2px; +} + .detail-panel h3 { margin-bottom: 15px; color: #667eea; diff --git a/web/index.html b/web/index.html index a76535f..54350dc 100644 --- a/web/index.html +++ b/web/index.html @@ -29,7 +29,7 @@