feat: Replace react-vis-network-graph with react-sigma for enhanced graph visualization and update GraphView component to utilize SVG for rendering connections and nodes
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 8m11s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 8m11s
This commit is contained in:
Generated
+14
-157
@@ -17,7 +17,7 @@
|
|||||||
"react-bootstrap": "^2.10.10",
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^6.30.1",
|
"react-router-dom": "^6.30.1",
|
||||||
"react-vis-network-graph": "^3.0.1"
|
"react-sigma": "^1.2.35"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
@@ -296,19 +296,6 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@egjs/hammerjs": {
|
|
||||||
"version": "2.0.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
|
||||||
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/hammerjs": "^2.0.36"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
@@ -772,13 +759,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/hammerjs": {
|
|
||||||
"version": "2.0.46",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
|
|
||||||
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -1063,16 +1043,6 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/component-emitter": {
|
|
||||||
"version": "1.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
|
||||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2188,13 +2158,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/keycharm": {
|
|
||||||
"version": "0.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.3.1.tgz",
|
|
||||||
"integrity": "sha512-zn47Ti4FJT9zdF+YBBLWJsfKF/fYQHkrYlBeB5Ez5e2PjW7SoIxr43yehAne2HruulIoid4NKZZxO0dHBygCtQ==",
|
|
||||||
"license": "(Apache-2.0 OR MIT)",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2229,12 +2192,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2295,16 +2252,6 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/moment": {
|
|
||||||
"version": "2.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
|
||||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2604,6 +2551,19 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-sigma": {
|
||||||
|
"version": "1.2.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-sigma/-/react-sigma-1.2.35.tgz",
|
||||||
|
"integrity": "sha512-amjKjaQusefDp8dD9er3wE/lfubSQT5fJSRF4SsS0LjxRue4bpSapAQ1WpmSM6nQEgawbrKel+bQBmatl7MAIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15.3",
|
||||||
|
"react-dom": ">=15.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
@@ -2620,41 +2580,6 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-vis-network-graph": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-vis-network-graph/-/react-vis-network-graph-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-gxUOJDPb+w7sZxvSzIrV7YpOqoJiTF6YrPsXS3RGnrmZnWyGhpCpUcaPli4PDsqpI74Q4f5AICg0cdzAEVbmIQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^4.17.15",
|
|
||||||
"prop-types": "^15.5.10",
|
|
||||||
"react": "16.x.x",
|
|
||||||
"uuid": "^2.0.1",
|
|
||||||
"vis-data": "^6.5.1",
|
|
||||||
"vis-network": "^7.6.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-vis-network-graph/node_modules/react": {
|
|
||||||
"version": "16.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
|
||||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-vis-network-graph/node_modules/uuid": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==",
|
|
||||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2780,13 +2705,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/timsort": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2871,67 +2789,6 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "8.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vis-data": {
|
|
||||||
"version": "6.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-6.6.1.tgz",
|
|
||||||
"integrity": "sha512-xmujDB2Dzf8T04rGFJ9OP4OA6zRVrz8R9hb0CVKryBrZRCljCga9JjSfgctA8S7wdZu7otDtUIwX4ZOgfV/57w==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "(Apache-2.0 OR MIT)",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/visjs"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"moment": "^2.24.0",
|
|
||||||
"uuid": "^7.0.0 || ^8.0.0",
|
|
||||||
"vis-util": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vis-network": {
|
|
||||||
"version": "7.10.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-7.10.2.tgz",
|
|
||||||
"integrity": "sha512-KDx2agbDnaiE0Bye4AcCRqTn5mxzDKhdUNpKkzSn0AOLBmdhNtPGjxAFluAmvFVyiSK5R6Q5KIWdLjeIMu/PAQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "(Apache-2.0 OR MIT)",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/visjs"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@egjs/hammerjs": "^2.0.0",
|
|
||||||
"component-emitter": "^1.3.0",
|
|
||||||
"keycharm": "^0.2.0 || ^0.3.0",
|
|
||||||
"moment": "^2.24.0",
|
|
||||||
"timsort": "^0.3.0",
|
|
||||||
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0",
|
|
||||||
"vis-data": "^6.2.1",
|
|
||||||
"vis-util": "^3.0.0 || ^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vis-util": {
|
|
||||||
"version": "4.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-4.3.4.tgz",
|
|
||||||
"integrity": "sha512-hJIZNrwf4ML7FYjs+m+zjJfaNvhjk3/1hbMdQZVnwwpOFJS/8dMG8rdbOHXcKoIEM6U5VOh3HNpaDXxGkOZGpw==",
|
|
||||||
"license": "(Apache-2.0 OR MIT)",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/visjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"react-bootstrap": "^2.10.10",
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^6.30.1",
|
"react-router-dom": "^6.30.1",
|
||||||
"react-vis-network-graph": "^3.0.1"
|
"react-sigma": "^1.2.35"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
|
|||||||
+157
-122
@@ -1,124 +1,43 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Network } from 'react-vis-network-graph';
|
|
||||||
|
|
||||||
function GraphView({ servers, connections }) {
|
function GraphView({ servers, connections }) {
|
||||||
const graph = useMemo(() => {
|
const [positions, setPositions] = useState({});
|
||||||
const nodes = servers.map(server => ({
|
const [dimensions, setDimensions] = useState({ width: 800, height: 500 });
|
||||||
id: server.ip,
|
const svgRef = useRef(null);
|
||||||
label: `${server.dns}\n${server.ip}`,
|
|
||||||
title: `${server.dns} (${server.ip})\n${server.country} - ${server.provider}`,
|
|
||||||
color: {
|
|
||||||
background: '#3b82f6',
|
|
||||||
border: '#1e40af',
|
|
||||||
highlight: {
|
|
||||||
background: '#60a5fa',
|
|
||||||
border: '#3b82f6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
color: '#ffffff',
|
|
||||||
size: 14,
|
|
||||||
face: 'Arial'
|
|
||||||
},
|
|
||||||
shape: 'box',
|
|
||||||
borderWidth: 2,
|
|
||||||
shadow: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const edges = connections.map((connection, index) => ({
|
// Вычисляем позиции узлов
|
||||||
id: index,
|
useEffect(() => {
|
||||||
from: connection.from,
|
if (servers.length === 0) return;
|
||||||
to: connection.to,
|
|
||||||
label: `${connection.tunnelType}\n${connection.ipA} ⇄ ${connection.ipB}`,
|
|
||||||
title: `${connection.tunnelType} туннель\n${connection.ipA} ⇄ ${connection.ipB}`,
|
|
||||||
color: {
|
|
||||||
color: '#64748b',
|
|
||||||
highlight: '#3b82f6',
|
|
||||||
hover: '#3b82f6'
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
color: '#374151',
|
|
||||||
size: 12,
|
|
||||||
face: 'Arial',
|
|
||||||
align: 'middle'
|
|
||||||
},
|
|
||||||
width: 2,
|
|
||||||
arrows: {
|
|
||||||
to: {
|
|
||||||
enabled: true,
|
|
||||||
scaleFactor: 0.8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
smooth: {
|
|
||||||
type: 'curvedCW',
|
|
||||||
roundness: 0.2
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return { nodes, edges };
|
const newPositions = {};
|
||||||
}, [servers, connections]);
|
const centerX = dimensions.width / 2;
|
||||||
|
const centerY = dimensions.height / 2;
|
||||||
|
const radius = Math.min(dimensions.width, dimensions.height) * 0.3;
|
||||||
|
|
||||||
const options = {
|
servers.forEach((server, index) => {
|
||||||
nodes: {
|
const angle = (index / servers.length) * 2 * Math.PI;
|
||||||
shape: 'box',
|
newPositions[server.ip] = {
|
||||||
margin: 10,
|
x: centerX + radius * Math.cos(angle),
|
||||||
shadow: true,
|
y: centerY + radius * Math.sin(angle)
|
||||||
font: {
|
};
|
||||||
size: 14,
|
});
|
||||||
face: 'Arial'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edges: {
|
|
||||||
font: {
|
|
||||||
size: 12,
|
|
||||||
align: 'middle'
|
|
||||||
},
|
|
||||||
smooth: {
|
|
||||||
type: 'curvedCW',
|
|
||||||
roundness: 0.2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
physics: {
|
|
||||||
enabled: true,
|
|
||||||
solver: 'forceAtlas2Based',
|
|
||||||
forceAtlas2Based: {
|
|
||||||
gravitationalConstant: -50,
|
|
||||||
centralGravity: 0.01,
|
|
||||||
springLength: 200,
|
|
||||||
springConstant: 0.08,
|
|
||||||
damping: 0.4
|
|
||||||
},
|
|
||||||
stabilization: {
|
|
||||||
enabled: true,
|
|
||||||
iterations: 1000,
|
|
||||||
updateInterval: 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
hover: true,
|
|
||||||
tooltipDelay: 200,
|
|
||||||
zoomView: true,
|
|
||||||
dragView: true
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
improvedLayout: true,
|
|
||||||
hierarchical: {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const events = {
|
setPositions(newPositions);
|
||||||
select: function(event) {
|
}, [servers, dimensions]);
|
||||||
const { nodes, edges } = event;
|
|
||||||
console.log('Selected nodes:', nodes);
|
// Обработчик изменения размера
|
||||||
console.log('Selected edges:', edges);
|
useEffect(() => {
|
||||||
},
|
const handleResize = () => {
|
||||||
hoverNode: function(event) {
|
if (svgRef.current) {
|
||||||
const { node } = event;
|
const rect = svgRef.current.getBoundingClientRect();
|
||||||
console.log('Hovered node:', node);
|
setDimensions({ width: rect.width, height: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (servers.length === 0) {
|
if (servers.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -132,13 +51,129 @@ function GraphView({ servers, connections }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: '500px', border: '1px solid #e5e7eb', borderRadius: '8px' }}>
|
<div style={{ width: '100%', height: '500px', border: '1px solid #e5e7eb', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<Network
|
<svg
|
||||||
graph={graph}
|
ref={svgRef}
|
||||||
options={options}
|
width="100%"
|
||||||
events={events}
|
height="100%"
|
||||||
style={{ width: '100%', height: '100%' }}
|
viewBox={`0 0 ${dimensions.width} ${dimensions.height}`}
|
||||||
/>
|
style={{ background: '#f8fafc' }}
|
||||||
|
>
|
||||||
|
{/* Связи */}
|
||||||
|
{connections.map((connection, index) => {
|
||||||
|
const fromPos = positions[connection.from];
|
||||||
|
const toPos = positions[connection.to];
|
||||||
|
|
||||||
|
if (!fromPos || !toPos) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g key={`link-${index}`}>
|
||||||
|
{/* Линия связи */}
|
||||||
|
<line
|
||||||
|
x1={fromPos.x}
|
||||||
|
y1={fromPos.y}
|
||||||
|
x2={toPos.x}
|
||||||
|
y2={toPos.y}
|
||||||
|
stroke="#64748b"
|
||||||
|
strokeWidth="2"
|
||||||
|
markerEnd="url(#arrowhead)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Подпись связи */}
|
||||||
|
<text
|
||||||
|
x={(fromPos.x + toPos.x) / 2}
|
||||||
|
y={(fromPos.y + toPos.y) / 2 - 10}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="12"
|
||||||
|
fill="#374151"
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
{connection.tunnelType}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={(fromPos.x + toPos.x) / 2}
|
||||||
|
y={(fromPos.y + toPos.y) / 2 + 5}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="10"
|
||||||
|
fill="#6b7280"
|
||||||
|
>
|
||||||
|
{connection.ipA} ⇄ {connection.ipB}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Узлы (серверы) */}
|
||||||
|
{servers.map(server => {
|
||||||
|
const pos = positions[server.ip];
|
||||||
|
if (!pos) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g key={server.ip}>
|
||||||
|
{/* Круг узла */}
|
||||||
|
<circle
|
||||||
|
cx={pos.x}
|
||||||
|
cy={pos.y}
|
||||||
|
r="25"
|
||||||
|
fill="#3b82f6"
|
||||||
|
stroke="#1e40af"
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* DNS имя */}
|
||||||
|
<text
|
||||||
|
x={pos.x}
|
||||||
|
y={pos.y - 5}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="11"
|
||||||
|
fill="white"
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
{server.dns}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* IP адрес */}
|
||||||
|
<text
|
||||||
|
x={pos.x}
|
||||||
|
y={pos.y + 10}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="10"
|
||||||
|
fill="white"
|
||||||
|
>
|
||||||
|
{server.ip}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* Страна */}
|
||||||
|
<text
|
||||||
|
x={pos.x}
|
||||||
|
y={pos.y + 25}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="9"
|
||||||
|
fill="#6b7280"
|
||||||
|
>
|
||||||
|
{server.country}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Определение стрелки */}
|
||||||
|
<defs>
|
||||||
|
<marker
|
||||||
|
id="arrowhead"
|
||||||
|
markerWidth="10"
|
||||||
|
markerHeight="7"
|
||||||
|
refX="9"
|
||||||
|
refY="3.5"
|
||||||
|
orient="auto"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
points="0 0, 10 3.5, 0 7"
|
||||||
|
fill="#64748b"
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconLink
|
IconLink
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import GraphView from './GraphView';
|
// import GraphView from './GraphView';
|
||||||
|
|
||||||
const API_URL = '/api';
|
const API_URL = '/api';
|
||||||
|
|
||||||
@@ -668,14 +668,15 @@ function ServerManager() {
|
|||||||
error={error}
|
error={error}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="card w-100 mb-4">
|
{/* Временно отключаем граф для отладки */}
|
||||||
|
{/* <div className="card w-100 mb-4">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title mb-0">Граф связей между серверами</h3>
|
<h3 className="card-title mb-0">Граф связей между серверами</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<GraphView servers={servers} connections={connections} />
|
<GraphView servers={servers} connections={connections} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="card w-100 mb-4">
|
<div className="card w-100 mb-4">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user