fix(ui): объединить параллельные связи OSPF в читаемый граф
This commit is contained in:
1 parent
086f118204
commit
9f74cfebfe
8 files changed
+43
-13
No files matched your search
@@ -406,6 +406,14 @@ function TopologyGraph({ graphNodes, graphEdges, nodeStates, highlightId, select
|
||||
return () => observer.disconnect()
|
||||
}, [graphNodes, selectedId])
|
||||
const nodeMap = Object.fromEntries(graphNodes.map(n => [n.id, n]))
|
||||
const visualLinks = useMemo(() => {
|
||||
const groups = new Map<string, GraphEdge[]>()
|
||||
for (const edge of graphEdges) {
|
||||
const key = JSON.stringify([edge.from, edge.to].sort())
|
||||
const links = groups.get(key) ?? []; links.push(edge); groups.set(key, links)
|
||||
}
|
||||
return [...groups.values()]
|
||||
}, [graphEdges])
|
||||
|
||||
function nodeState(id: string): keyof typeof NODE_THEME {
|
||||
if (nodeStates?.has(id)) return nodeStates.get(id)!
|
||||
@@ -427,34 +435,39 @@ function TopologyGraph({ graphNodes, graphEdges, nodeStates, highlightId, select
|
||||
<rect width={TOPO_W} height={TOPO_H} fill="var(--background)" />
|
||||
<rect width={TOPO_W} height={TOPO_H} fill="url(#ospf-dots)" />
|
||||
|
||||
{graphEdges.map((e, i) => {
|
||||
{visualLinks.map((links, i) => {
|
||||
const e = links[0]
|
||||
const mixed = links.some(link => link.state !== e.state)
|
||||
const grouped = links.length > 1
|
||||
const badgeWidth = grouped ? 90 : 30
|
||||
const a = nodeMap[e.from]
|
||||
const b = nodeMap[e.to]
|
||||
if (!a || !b) return null
|
||||
const color = EDGE_COLOR[e.state] ?? "var(--muted-foreground)"
|
||||
const isDashed = e.state !== "Full"
|
||||
const color = mixed ? "var(--muted-foreground)" : EDGE_COLOR[e.state] ?? "var(--muted-foreground)"
|
||||
const isDashed = mixed || e.state !== "Full"
|
||||
const active = highlightId === e.from || highlightId === e.to
|
||||
|| selectedId === e.from || selectedId === e.to
|
||||
const opacity = active ? 1 : 0.38
|
||||
const opacity = active || grouped ? 1 : 0.38
|
||||
const strokeW = active ? 2.4 : 1.4
|
||||
const mx = (a.x + b.x) / 2
|
||||
const my = (a.y + b.y) / 2
|
||||
return (
|
||||
<g key={i} style={{ transition: showDots ? "opacity 0.2s" : "none" }} opacity={opacity}>
|
||||
<g key={i} role={grouped ? "img" : undefined} data-ospf-links={links.length} aria-label={grouped ? `Параллельные связи: ${links.length}. ${links.map(link => `${link.state}, cost ${link.cost ?? "—"}`).join("; ")}` : undefined} style={{ transition: showDots ? "opacity 0.2s" : "none" }} opacity={opacity}>
|
||||
{grouped && <title>{links.map(link => `${link.state}, cost ${link.cost ?? "—"}`).join("; ")}</title>}
|
||||
<line x1={a.x} y1={a.y} x2={b.x} y2={b.y}
|
||||
stroke={color} strokeWidth={strokeW}
|
||||
strokeDasharray={isDashed ? "7 4" : "none"} />
|
||||
{showDots && e.state === "Full" && (
|
||||
{showDots && !mixed && e.state === "Full" && (
|
||||
<circle r="3.5" fill={color} opacity="0.9">
|
||||
<animateMotion dur={`${2.2 + (i % 5) * 0.38}s`} repeatCount="indefinite"
|
||||
path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`} />
|
||||
</circle>
|
||||
)}
|
||||
<g transform={`translate(${mx},${my})`}>
|
||||
<rect x="-15" y="-10" width="30" height="18" rx="4"
|
||||
<rect x={-badgeWidth / 2} y="-10" width={badgeWidth} height="18" rx="4"
|
||||
fill="var(--background)" stroke={color} strokeWidth="0.8" opacity="0.95" />
|
||||
<text textAnchor="middle" y="4" fontSize="12" fontWeight="700"
|
||||
fill={color} fontFamily="ui-monospace,monospace">{e.cost ?? "—"}</text>
|
||||
fill={color} fontFamily="ui-monospace,monospace">{grouped ? `${links.length} ${links.length < 5 ? "связи" : "связей"}` : e.cost ?? "—"}</text>
|
||||
</g>
|
||||
</g>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user