feat: Add GraphView component for visualizing server connections and implement connection management in ServerManager
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 8m19s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 8m19s
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import ForceGraph2D from 'react-force-graph-2d';
|
||||
|
||||
function GraphView({ servers, connections }) {
|
||||
// Преобразуем данные в формат, подходящий для ForceGraph
|
||||
const nodes = servers.map(s => ({ id: s.ip, label: s.dns || s.ip }));
|
||||
const links = connections.map(c => ({
|
||||
source: c.from,
|
||||
target: c.to,
|
||||
tunnelType: c.tunnelType,
|
||||
ipA: c.ipA,
|
||||
ipB: c.ipB
|
||||
}));
|
||||
|
||||
// Кастомный рендер связей с подписями
|
||||
const fgRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (fgRef.current) {
|
||||
fgRef.current.d3ReheatSimulation();
|
||||
}
|
||||
}, [nodes.length, links.length]);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: 400 }}>
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
graphData={{ nodes, links }}
|
||||
nodeLabel={node => node.label}
|
||||
nodeAutoColorBy="id"
|
||||
linkDirectionalArrowLength={6}
|
||||
linkDirectionalArrowRelPos={1}
|
||||
linkLabel={l => `${l.tunnelType}: ${l.ipA} ⇄ ${l.ipB}`}
|
||||
linkWidth={2}
|
||||
linkColor={() => '#888'}
|
||||
nodeCanvasObject={(node, ctx, globalScale) => {
|
||||
const label = node.label;
|
||||
const fontSize = 14/globalScale;
|
||||
ctx.font = `${fontSize}px sans-serif`;
|
||||
ctx.fillStyle = '#1e293b';
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 10, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, node.x, node.y - 18);
|
||||
}}
|
||||
linkCanvasObjectMode={() => 'after'}
|
||||
linkCanvasObject={(link, ctx, globalScale) => {
|
||||
const label = `${link.tunnelType}: ${link.ipA} ⇄ ${link.ipB}`;
|
||||
if (!label) return;
|
||||
const start = link.source;
|
||||
const end = link.target;
|
||||
if (typeof start !== 'object' || typeof end !== 'object') return;
|
||||
const textPos = {
|
||||
x: (start.x + end.x) / 2,
|
||||
y: (start.y + end.y) / 2
|
||||
};
|
||||
ctx.save();
|
||||
ctx.font = `${12/globalScale}px sans-serif`;
|
||||
ctx.fillStyle = '#2563eb';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, textPos.x, textPos.y);
|
||||
ctx.restore();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GraphView;
|
||||
Reference in New Issue
Block a user