feat: Replace ForceGraph with react-vis-network-graph for improved visualization and enhance ServerManager with connection validation and deletion functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 7m50s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 7m50s
This commit is contained in:
Generated
+153
-186
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
+133
-63
@@ -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 (
|
||||
<div className="text-center py-5">
|
||||
<div className="text-muted">
|
||||
<h4>Нет серверов для отображения</h4>
|
||||
<p>Добавьте серверы, чтобы увидеть граф связей</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 style={{ width: '100%', height: '500px', border: '1px solid #e5e7eb', borderRadius: '8px' }}>
|
||||
<Network
|
||||
graph={graph}
|
||||
options={options}
|
||||
events={events}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -398,9 +398,22 @@ function ServerManager() {
|
||||
setError('Все поля для связи должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
if (newConnection.from === newConnection.to) {
|
||||
setError('Сервер A и сервер B не могут быть одинаковыми.');
|
||||
return;
|
||||
}
|
||||
setConnections([...connections, newConnection]);
|
||||
setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' });
|
||||
setError('');
|
||||
setSuccess('Связь успешно добавлена!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
};
|
||||
|
||||
// Удаление связи
|
||||
const handleDeleteConnection = (index) => {
|
||||
setConnections(connections.filter((_, i) => i !== index));
|
||||
setSuccess('Связь удалена!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -668,22 +681,22 @@ function ServerManager() {
|
||||
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row g-2 align-items-end">
|
||||
<div className="col-md-3">
|
||||
<label className="form-label">Сервер A (IP)</label>
|
||||
<div className="row g-3">
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">Сервер A (IP/DNS)</label>
|
||||
<select className="form-select" value={newConnection.from} onChange={e => setNewConnection({ ...newConnection, from: e.target.value })}>
|
||||
<option value="">Выберите</option>
|
||||
<option value="">Выберите сервер A</option>
|
||||
{servers.map(s => <option key={s.ip} value={s.ip}>{s.ip} ({s.dns})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<label className="form-label">Сервер B (IP)</label>
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">Сервер B (IP/DNS)</label>
|
||||
<select className="form-select" value={newConnection.to} onChange={e => setNewConnection({ ...newConnection, to: e.target.value })}>
|
||||
<option value="">Выберите</option>
|
||||
<option value="">Выберите сервер B</option>
|
||||
{servers.map(s => <option key={s.ip} value={s.ip}>{s.ip} ({s.dns})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">Тип туннеля</label>
|
||||
<select className="form-select" value={newConnection.tunnelType} onChange={e => setNewConnection({ ...newConnection, tunnelType: e.target.value })}>
|
||||
<option value="GRE">GRE</option>
|
||||
@@ -692,20 +705,84 @@ function ServerManager() {
|
||||
<option value="OpenVPN">OpenVPN</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<label className="form-label">IP сервера A</label>
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">IP адрес сервера A</label>
|
||||
<input className="form-control" value={newConnection.ipA} onChange={e => setNewConnection({ ...newConnection, ipA: e.target.value })} placeholder="10.10.100.1" />
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<label className="form-label">IP сервера B</label>
|
||||
<div className="col-md-4">
|
||||
<label className="form-label">IP адрес сервера B</label>
|
||||
<input className="form-control" value={newConnection.ipB} onChange={e => setNewConnection({ ...newConnection, ipB: e.target.value })} placeholder="10.10.100.2" />
|
||||
</div>
|
||||
<div className="col-md-12 mt-2">
|
||||
<button className="btn btn-primary" type="button" onClick={handleAddConnection}>Добавить связь</button>
|
||||
<div className="col-12">
|
||||
<button className="btn btn-primary d-flex align-items-center" type="button" onClick={handleAddConnection}>
|
||||
<span className="me-2 d-flex align-items-center"><IconPlus className="icon" /></span>
|
||||
<span className="fw-bold">Добавить связь</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список существующих связей */}
|
||||
{connections.length > 0 && (
|
||||
<div className="card w-100 mb-4">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title mb-0">Существующие связи ({connections.length})</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="table-responsive">
|
||||
<table className="table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Сервер A</th>
|
||||
<th>Сервер B</th>
|
||||
<th>Тип туннеля</th>
|
||||
<th>IP адреса туннеля</th>
|
||||
<th className="text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{connections.map((connection, index) => {
|
||||
const serverA = servers.find(s => s.ip === connection.from);
|
||||
const serverB = servers.find(s => s.ip === connection.to);
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<div>
|
||||
<div className="fw-bold">{connection.from}</div>
|
||||
<div className="text-muted">{serverA?.dns || 'Неизвестный сервер'}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<div className="fw-bold">{connection.to}</div>
|
||||
<div className="text-muted">{serverB?.dns || 'Неизвестный сервер'}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className="badge bg-green-lt text-green">{connection.tunnelType}</span>
|
||||
</td>
|
||||
<td>
|
||||
<code>{connection.ipA} ⇄ {connection.ipB}</code>
|
||||
</td>
|
||||
<td className="text-end">
|
||||
<button
|
||||
className="btn btn-icon btn-sm btn-outline-danger rounded"
|
||||
title="Удалить связь"
|
||||
onClick={() => handleDeleteConnection(index)}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user