diff --git a/frontend/src/NetworkMapUnifi.jsx b/frontend/src/NetworkMapUnifi.jsx index cd0917e..01f7c63 100644 --- a/frontend/src/NetworkMapUnifi.jsx +++ b/frontend/src/NetworkMapUnifi.jsx @@ -63,7 +63,9 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa const [size, setSize] = useState({ w: 800, h: 520 }); const [positions, setPositions] = useState({}); const [drag, setDrag] = useState(null); - const [isFullscreen, setIsFullscreen] = useState(false); + const [isFullscreen, setIsFullscreen] = useState(false); + const [hoveredEdgeKey, setHoveredEdgeKey] = useState(null); + const [selectedEdge, setSelectedEdge] = useState(null); useEffect(() => { const el = containerRef.current; @@ -183,12 +185,14 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa if (typeof rev === 'number') parts.push(`← ${rev} ms`); const pingLabel = parts.length ? parts.join(' ') : null; return { + edgeKey: `${from}-${to}-${c.tunnelType || ''}`, from, to, p1, p2, pingLabel, tunnelType: c.tunnelType, + raw: c, }; }).filter((e) => e.p1 && e.p2); }, [connections, positions, pingMap]); @@ -281,14 +285,27 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa const LABEL_W = 64; const LABEL_H = 20; const LABEL_GAP = 4; - return edgesWithPing.map((e, idx) => ( - + return edgesWithPing.map((e, idx) => { + const dimFactor = + hoveredEdgeKey && hoveredEdgeKey !== e.edgeKey ? 0.25 : 1; + const strokeWidth = hoveredEdgeKey === e.edgeKey ? 3.2 : 2.5; + return ( + setHoveredEdgeKey(e.edgeKey)} + onMouseLeave={() => + setHoveredEdgeKey((prev) => (prev === e.edgeKey ? null : prev)) + } + onClick={() => setSelectedEdge(e)} + style={{ cursor: 'pointer' }} + > {e.pingLabel && (() => { const base = labelPoint(e); @@ -332,7 +349,8 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa ); })()} - )); + ); + }); })()} @@ -389,6 +407,107 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa ); })} + {selectedEdge && (() => { + const fromServer = servers.find((s) => String(s.ip) === selectedEdge.from); + const toServer = servers.find((s) => String(s.ip) === selectedEdge.to); + const fwd = pingMap[`${selectedEdge.from}:${selectedEdge.to}`]; + const rev = pingMap[`${selectedEdge.to}:${selectedEdge.from}`]; + return ( +
setSelectedEdge(null)} + > +
e.stopPropagation()} + > +
+
Детали соединения
+
+
+
+
+ От +
+
+ {fromServer?.dns || fromServer?.ip || selectedEdge.from}{' '} + ({fromServer?.ip || '—'}){' '} + + {String(fromServer?.type || '').toUpperCase() || 'SERVER'} + +
+
+
+
+ К +
+
+ {toServer?.dns || toServer?.ip || selectedEdge.to}{' '} + ({toServer?.ip || '—'}){' '} + + {String(toServer?.type || '').toUpperCase() || 'SERVER'} + +
+
+
+
+ Туннель +
+
+ + {selectedEdge.tunnelType || 'TUNNEL'} + + {selectedEdge.raw?.internalFromTo && ( + + {selectedEdge.raw.internalToFrom} ⇄ {selectedEdge.raw.internalFromTo} + + )} +
+
+
+
+ Пинг +
+
+
+ + {typeof fwd === 'number' ? `${fwd} ms` : 'нет данных'} +
+
+ + {typeof rev === 'number' ? `${rev} ms` : 'нет данных'} +
+
+
+
+
+
+ ); + })()}