fix(ui): связывать OSPF узлы по подтверждённым ID
This commit is contained in:
1 parent
7a68ae8184
commit
259c5c3014
16 files changed
+123
-41
No files matched your search
+20
-25
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { deriveOspfRouterIdentity } from "@/lib/ospf-router-identity"
|
||||
import { buildOspfCostIndex, ospfCostKey } from "@/lib/ospf-cost"
|
||||
import { normalizeOspfState, normalizeBfdState, ospfNodeState, type OspfNeighborState, type BfdState } from "@/lib/ospf-state"
|
||||
import { useMemo, useState, useEffect, useRef, Suspense } from "react"
|
||||
@@ -76,7 +77,7 @@ interface OspfNeighbor {
|
||||
interface OspfRoute {
|
||||
id: string
|
||||
destination: string
|
||||
type: "O" | "O IA" | "O E1" | "O E2"
|
||||
type: "O" | "O IA" | "O E1" | "O E2" | "Unknown"
|
||||
cost: number | null
|
||||
nextHop: string
|
||||
via: string
|
||||
@@ -97,7 +98,7 @@ interface BfdSession {
|
||||
multiplier: number | null
|
||||
iface: string
|
||||
uptime: string | null
|
||||
multihop: boolean
|
||||
multihop: boolean | null
|
||||
rxInterval: number | null // ms
|
||||
holdTime: number | null // ms
|
||||
packetsRx: number | null
|
||||
@@ -121,7 +122,7 @@ function isRefInterfaceName(name: string): boolean {
|
||||
|
||||
// ─── backend → frontend mappers ───────────────────────────────────────────────
|
||||
|
||||
function backendToNeighbor(b: BackendNeighbor, ifaceMap: Map<string, number | null>): OspfNeighbor {
|
||||
function backendToNeighbor(b: BackendNeighbor, ifaceMap: Map<string, number | null>, serverByRouterId: Record<string, string>, labels: Record<string, string>): OspfNeighbor {
|
||||
const state = normalizeOspfState(b.state)
|
||||
const costKey = ospfCostKey(b.serverId, b.interface, b.areaId)
|
||||
return {
|
||||
@@ -129,8 +130,8 @@ function backendToNeighbor(b: BackendNeighbor, ifaceMap: Map<string, number | nu
|
||||
localRouter: String(b.serverId),
|
||||
localLabel: b.serverName,
|
||||
localIface: b.interface,
|
||||
remoteRouter: b.routerId,
|
||||
remoteLabel: b.routerId, // no server name for remote peers
|
||||
remoteRouter: serverByRouterId[b.routerId] ?? b.routerId,
|
||||
remoteLabel: labels[serverByRouterId[b.routerId]] ?? b.routerId,
|
||||
remoteRouterId: b.routerId,
|
||||
area: b.areaId !== b.area ? `${b.area} (${b.areaId})` : b.area,
|
||||
state,
|
||||
@@ -180,8 +181,8 @@ function backendToBfdSession(b: BackendBfdSession): BfdSession {
|
||||
}
|
||||
|
||||
function backendToRoute(b: BackendOspfRoute): OspfRoute {
|
||||
const allowed: OspfRoute["type"][] = ["O", "O IA", "O E1", "O E2"]
|
||||
const type = allowed.includes(b.type) ? b.type : "O"
|
||||
const allowed: OspfRoute["type"][] = ["O", "O IA", "O E1", "O E2", "Unknown"]
|
||||
const type = allowed.includes(b.type) ? b.type : "Unknown"
|
||||
return {
|
||||
id: `${b.serverId}-${b.id}`,
|
||||
destination: b.destination,
|
||||
@@ -320,9 +321,9 @@ function buildLiveGraph(
|
||||
// Collect unique remote peers (that are NOT already local)
|
||||
const remoteMap = new Map<string, { label: string }>()
|
||||
for (const n of neighbors) {
|
||||
const remId = `remote-${n.remoteRouterId}`
|
||||
const remId = n.remoteRouter !== n.remoteRouterId ? n.remoteRouter : `remote-${n.remoteRouterId}`
|
||||
if (!localMap.has(n.remoteRouter) && !remoteMap.has(remId)) {
|
||||
remoteMap.set(remId, { label: n.remoteRouterId })
|
||||
remoteMap.set(remId, { label: n.remoteLabel })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +343,7 @@ function buildLiveGraph(
|
||||
|
||||
const edges: GraphEdge[] = neighbors.map(n => ({
|
||||
from: n.localRouter,
|
||||
to: localMap.has(n.remoteRouter) ? n.remoteRouter : `remote-${n.remoteRouterId}`,
|
||||
to: n.remoteRouter !== n.remoteRouterId || localMap.has(n.remoteRouter) ? n.remoteRouter : `remote-${n.remoteRouterId}`,
|
||||
state: n.state,
|
||||
cost: n.cost,
|
||||
}))
|
||||
@@ -460,7 +461,7 @@ function TopologyGraph({ graphNodes, graphEdges, highlightId, selectedId, showDo
|
||||
(e.from === highlightId && e.to === node.id) ||
|
||||
(e.to === highlightId && e.from === node.id))
|
||||
return (
|
||||
<g key={node.id} transform={`translate(${node.x},${node.y})`}
|
||||
<g data-ospf-node={node.id} key={node.id} transform={`translate(${node.x},${node.y})`}
|
||||
style={{ cursor: "pointer", transition: "opacity 0.2s" }}
|
||||
opacity={dimmed ? 0.25 : 1}
|
||||
onClick={e => { e.stopPropagation(); onNodeClick(isSel ? null : node.id) }}>
|
||||
@@ -537,12 +538,12 @@ function NodeDetailPanel({
|
||||
const node = graphNodes.find(n => n.id === nodeId)
|
||||
if (!node) return null
|
||||
|
||||
const routerId = routerIds[nodeId] ?? "—"
|
||||
const adjacencies = neighbors.filter(n => n.localRouter === nodeId || n.remoteRouter === nodeId)
|
||||
const routerId = routerIds[nodeId] ?? (nodeId.startsWith("remote-") ? nodeId.slice(7) : "—")
|
||||
const adjacencies = neighbors.filter(n => n.localRouter === nodeId || n.remoteRouter === nodeId || nodeId === `remote-${n.remoteRouterId}`)
|
||||
.map(n => {
|
||||
if (n.localRouter === nodeId)
|
||||
return { iface: n.localIface, peerLabel: n.remoteLabel, peerId: n.remoteRouterId, state: n.state, cost: n.cost, uptime: n.uptime }
|
||||
return { iface: n.localIface, peerLabel: n.localLabel, peerId: routerId, state: n.state, cost: n.cost, uptime: n.uptime }
|
||||
return { iface: n.localIface, peerLabel: n.localLabel, peerId: routerIds[n.localRouter] ?? "—", state: n.state, cost: n.cost, uptime: n.uptime }
|
||||
})
|
||||
const ifaces = items.filter(i => i.routerKey === nodeId)
|
||||
const fullCnt = adjacencies.filter(a => a.state === "Full").length
|
||||
@@ -1010,7 +1011,7 @@ function RoutesTab({ routes, hasSearch }: { routes: OspfRoute[]; hasSearch: bool
|
||||
<span className="text-sm font-medium">{routes.some(r => r.dataUnknown) ? "Последний снимок: " : ""}{routes.length} маршрутов</span>
|
||||
{typeCount.map(([type, count]) => (
|
||||
<span key={type} className={cn("inline-flex items-center rounded border px-2 py-0.5 text-[11px] font-medium", routeTypeClass(type as OspfRoute["type"]))}>
|
||||
{type}: {count}
|
||||
{type === "Unknown" ? "Тип неизвестен" : type}: {count}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -1190,21 +1191,15 @@ function OspfPageContent({ search, selectedNodeId, onSearch, onSelectNode, filte
|
||||
// Build interface→cost map for neighbor cost lookup
|
||||
const ifaceMap = buildOspfCostIndex(liveData.interfaces)
|
||||
|
||||
const { routerIds, serverByRouterId } = deriveOspfRouterIdentity(liveData.instances)
|
||||
const labels = Object.fromEntries(liveServers.map(server => [String(server.id), server.name]))
|
||||
const items = liveData.interfaces
|
||||
.map(b => ({ ...backendToItem(b), dataUnknown: unknownIds.has(b.serverId) }))
|
||||
.filter((item) => !isRefInterfaceName(item.interfaceName))
|
||||
const neighbors = liveData.neighbors.map(b => ({ ...backendToNeighbor(b, ifaceMap), dataUnknown: unknownIds.has(b.serverId) }))
|
||||
const neighbors = liveData.neighbors.map(b => ({ ...backendToNeighbor(b, ifaceMap, serverByRouterId, labels), dataUnknown: unknownIds.has(b.serverId) }))
|
||||
const bfdSessions = liveData.bfdSessions.map(b => ({ ...backendToBfdSession(b), dataUnknown: unknownIds.has(b.serverId) }))
|
||||
const routes = liveData.routes.map(b => ({ ...backendToRoute(b), dataUnknown: unknownIds.has(b.serverId) }))
|
||||
|
||||
// Build routerIds from instances
|
||||
const routerIds: Record<string, string> = {}
|
||||
for (const inst of liveData.instances) {
|
||||
if (inst.routerId && !routerIds[String(inst.serverId)]) {
|
||||
routerIds[String(inst.serverId)] = inst.routerId
|
||||
}
|
||||
}
|
||||
|
||||
const { nodes: graphNodes, edges: graphEdges } = buildLiveGraph(neighbors)
|
||||
return { items, neighbors, graphNodes, graphEdges, routerIds, bfdSessions, routes }
|
||||
}
|
||||
@@ -1228,7 +1223,7 @@ function OspfPageContent({ search, selectedNodeId, onSearch, onSelectNode, filte
|
||||
bfdSessions: MOCK_BFD,
|
||||
routes: MOCK_ROUTES,
|
||||
}
|
||||
}, [isLive, liveData, unknownIds])
|
||||
}, [isLive, liveData, liveServers, unknownIds])
|
||||
|
||||
// ── server list for chips bar ────────────────────────────────────────────────
|
||||
const ospfServers = useMemo((): OspfServerInfo[] => {
|
||||
|
||||
Reference in new issue
Block a user