diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7731cd1..ae15b3d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,8 +16,8 @@ "react": "^19.1.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.1.0", - "react-force-graph-2d": "^1.28.0", - "react-router-dom": "^6.30.1" + "react-router-dom": "^6.30.1", + "react-vis-network-graph": "^3.0.1" }, "devDependencies": { "@eslint/js": "^9.29.0", @@ -296,6 +296,19 @@ "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": { "version": "0.25.5", "cpu": [ @@ -717,12 +730,6 @@ "react": ">= 16" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -765,6 +772,13 @@ "dev": true, "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": { "version": "7.0.15", "dev": true, @@ -825,15 +839,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/acorn": { "version": "8.15.0", "dev": true, @@ -905,16 +910,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/bootstrap": { "version": "5.3.7", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", @@ -1012,18 +1007,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -1080,6 +1063,16 @@ "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": { "version": "0.0.1", "dev": true, @@ -1169,12 +1162,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -1318,22 +1305,6 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -1376,12 +1347,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -1933,20 +1898,6 @@ "dev": true, "license": "ISC" }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/follow-redirects": { "version": "1.15.9", "funding": [ @@ -1965,32 +1916,6 @@ } } }, - "node_modules/force-graph": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.50.1.tgz", - "integrity": "sha512-CtldBdsUHLmlnerVYe09V9Bxi5iz8GZce1WdBSkwGAFgNFTYn6cW90NQ1lOh/UVm0NhktMRHKugXrS9Sl8Bl3A==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/form-data": { "version": "4.0.3", "license": "MIT", @@ -2169,15 +2094,6 @@ "node": ">=0.8.19" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2220,15 +2136,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jerrypick": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.2.tgz", - "integrity": "sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -2281,17 +2188,12 @@ "node": ">=6" } }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", - "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } + "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": { "version": "4.5.4", @@ -2327,10 +2229,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash-es": { + "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -2393,6 +2295,16 @@ "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": { "version": "2.1.3", "dev": true, @@ -2548,16 +2460,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/preact": { - "version": "10.26.9", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", - "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -2650,44 +2552,12 @@ "react": "^19.1.0" } }, - "node_modules/react-force-graph-2d": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/react-force-graph-2d/-/react-force-graph-2d-1.28.0.tgz", - "integrity": "sha512-NYA8GLxJnoZyLWjob8xea38B1cZqSGdcA8lDpvTc1hcJrpzFyBEHkeJ4xtFoJp66tsM4PAlj5af4HWnU0OQ3Sg==", - "license": "MIT", - "dependencies": { - "force-graph": "^1.50", - "prop-types": "15", - "react-kapsule": "^2.5" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/react-kapsule": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.5.7.tgz", - "integrity": "sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==", - "license": "MIT", - "dependencies": { - "jerrypick": "^1.1.1" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -2750,6 +2620,41 @@ "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": { "version": "4.0.0", "dev": true, @@ -2875,11 +2780,12 @@ "node": ">=8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" + "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": { "version": "0.2.14", @@ -2965,6 +2871,67 @@ "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": { "version": "7.0.2", "dev": true, diff --git a/frontend/package.json b/frontend/package.json index c3fac37..2a67432 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,8 +18,8 @@ "react": "^19.1.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.1.0", - "react-force-graph-2d": "^1.28.0", - "react-router-dom": "^6.30.1" + "react-router-dom": "^6.30.1", + "react-vis-network-graph": "^3.0.1" }, "devDependencies": { "@eslint/js": "^9.29.0", diff --git a/frontend/src/GraphView.jsx b/frontend/src/GraphView.jsx index 35ad6b4..b36d8b4 100644 --- a/frontend/src/GraphView.jsx +++ b/frontend/src/GraphView.jsx @@ -1,73 +1,143 @@ -import React, { useRef, useEffect } from 'react'; -import ForceGraph2D from 'react-force-graph-2d'; +import React, { useMemo } from 'react'; +import { Network } from 'react-vis-network-graph'; 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 graph = useMemo(() => { + const nodes = servers.map(server => ({ + id: server.ip, + 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 fgRef = useRef(); + const edges = connections.map((connection, index) => ({ + id: index, + from: connection.from, + 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 + } + })); - useEffect(() => { - if (fgRef.current) { - fgRef.current.d3ReheatSimulation(); + return { nodes, edges }; + }, [servers, connections]); + + const options = { + nodes: { + shape: 'box', + margin: 10, + shadow: true, + 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 + } } - }, [nodes.length, links.length]); + }; + + const events = { + select: function(event) { + const { nodes, edges } = event; + console.log('Selected nodes:', nodes); + console.log('Selected edges:', edges); + }, + hoverNode: function(event) { + const { node } = event; + console.log('Hovered node:', node); + } + }; + + if (servers.length === 0) { + return ( +
Добавьте серверы, чтобы увидеть граф связей
+| Сервер A | +Сервер B | +Тип туннеля | +IP адреса туннеля | +Действия | +
|---|---|---|---|---|
|
+
+
+ {connection.from}
+ {serverA?.dns || 'Неизвестный сервер'}
+ |
+
+
+
+ {connection.to}
+ {serverB?.dns || 'Неизвестный сервер'}
+ |
+ + {connection.tunnelType} + | +
+ {connection.ipA} ⇄ {connection.ipB}
+ |
+ + + | +