From b51071fad2238115d9e9aa922075a3388c89cb3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 07:41:36 +0000 Subject: [PATCH] Add frontend real-time monitoring, snapshots, and configuration management Co-authored-by: Ospab <189454929+Ospab@users.noreply.github.com> --- ospabhost/frontend/package-lock.json | 487 +++++++++++++++++- ospabhost/frontend/package.json | 4 +- ospabhost/frontend/src/hooks/useSocket.ts | 76 +++ .../src/pages/dashboard/serverpanel.tsx | 415 ++++++++++++++- 4 files changed, 967 insertions(+), 15 deletions(-) create mode 100644 ospabhost/frontend/src/hooks/useSocket.ts diff --git a/ospabhost/frontend/package-lock.json b/ospabhost/frontend/package-lock.json index 164e297..a835f85 100644 --- a/ospabhost/frontend/package-lock.json +++ b/ospabhost/frontend/package-lock.json @@ -12,7 +12,9 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-icons": "^5.5.0", - "react-qr-code": "^2.0.18" + "react-qr-code": "^2.0.18", + "recharts": "^2.15.0", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -279,6 +281,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -1393,6 +1404,12 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1438,6 +1455,69 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2120,6 +2200,15 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2218,9 +2307,129 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2239,6 +2448,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2269,6 +2484,16 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2304,6 +2529,45 @@ "dev": true, "license": "MIT" }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2592,6 +2856,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2599,6 +2869,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -3033,6 +3312,15 @@ "node": ">=0.8.19" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3264,6 +3552,12 @@ "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": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3374,7 +3668,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -3941,6 +4234,37 @@ "react-dom": ">=18" } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -3964,6 +4288,44 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz", + "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4130,6 +4492,68 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4367,6 +4791,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4547,6 +4977,28 @@ "dev": true, "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", @@ -4774,6 +5226,35 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/ospabhost/frontend/package.json b/ospabhost/frontend/package.json index 744aef5..180bc64 100644 --- a/ospabhost/frontend/package.json +++ b/ospabhost/frontend/package.json @@ -14,7 +14,9 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-icons": "^5.5.0", - "react-qr-code": "^2.0.18" + "react-qr-code": "^2.0.18", + "recharts": "^2.15.0", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/ospabhost/frontend/src/hooks/useSocket.ts b/ospabhost/frontend/src/hooks/useSocket.ts new file mode 100644 index 0000000..bf1647c --- /dev/null +++ b/ospabhost/frontend/src/hooks/useSocket.ts @@ -0,0 +1,76 @@ +import { useEffect, useState } from 'react'; +import { io, Socket } from 'socket.io-client'; + +const SOCKET_URL = 'http://localhost:5000'; + +export function useSocket() { + const [socket, setSocket] = useState(null); + const [connected, setConnected] = useState(false); + + useEffect(() => { + const socketInstance = io(SOCKET_URL, { + transports: ['websocket', 'polling'], + reconnection: true, + reconnectionDelay: 1000, + reconnectionAttempts: 5 + }); + + socketInstance.on('connect', () => { + console.log('WebSocket connected'); + setConnected(true); + }); + + socketInstance.on('disconnect', () => { + console.log('WebSocket disconnected'); + setConnected(false); + }); + + socketInstance.on('connect_error', (error) => { + console.error('WebSocket connection error:', error); + }); + + setSocket(socketInstance); + + return () => { + socketInstance.close(); + }; + }, []); + + return { socket, connected }; +} + +export function useServerStats(serverId: number | null) { + const { socket, connected } = useSocket(); + const [stats, setStats] = useState(null); + const [alerts, setAlerts] = useState([]); + + useEffect(() => { + if (!socket || !connected || !serverId) return; + + // Подписываемся на обновления сервера + socket.emit('subscribe-server', serverId); + + // Обработчик обновлений статистики + socket.on('server-stats', (data: any) => { + if (data.serverId === serverId) { + setStats(data.stats); + } + }); + + // Обработчик алертов + socket.on('server-alerts', (data: any) => { + if (data.serverId === serverId) { + setAlerts(data.alerts); + } + }); + + // Отписываемся при размонтировании + return () => { + socket.emit('unsubscribe-server', serverId); + socket.off('server-stats'); + socket.off('server-alerts'); + }; + }, [socket, connected, serverId]); + + return { stats, alerts, connected }; +} diff --git a/ospabhost/frontend/src/pages/dashboard/serverpanel.tsx b/ospabhost/frontend/src/pages/dashboard/serverpanel.tsx index fcc9c4a..eb346ba 100644 --- a/ospabhost/frontend/src/pages/dashboard/serverpanel.tsx +++ b/ospabhost/frontend/src/pages/dashboard/serverpanel.tsx @@ -1,5 +1,9 @@ import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import axios, { AxiosError } from 'axios'; +import { useServerStats } from '../../hooks/useSocket'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; // Встроенная секция консоли function ConsoleSection({ serverId }: { serverId: number }) { @@ -47,8 +51,264 @@ function ConsoleSection({ serverId }: { serverId: number }) { ); } -import { useParams } from 'react-router-dom'; -import axios, { AxiosError } from 'axios'; + +// Модальное окно для изменения конфигурации +function ResizeModal({ serverId, onClose, onSuccess }: { serverId: number; onClose: () => void; onSuccess: () => void }) { + const [cores, setCores] = useState(''); + const [memory, setMemory] = useState(''); + const [disk, setDisk] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const handleResize = async () => { + setLoading(true); + setError(''); + try { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + const data: any = {}; + if (cores) data.cores = Number(cores); + if (memory) data.memory = Number(memory); + if (disk) data.disk = Number(disk); + + const res = await axios.put(`http://localhost:5000/api/server/${serverId}/resize`, data, { headers }); + if (res.data?.status === 'success') { + onSuccess(); + onClose(); + } else { + setError('Ошибка изменения конфигурации'); + } + } catch (err) { + setError('Ошибка изменения конфигурации'); + console.error(err); + } finally { + setLoading(false); + } + }; + + return ( +
+
e.stopPropagation()}> +

Изменить конфигурацию

+
+
+ + setCores(e.target.value)} + className="w-full px-4 py-2 border rounded-lg" + placeholder="Оставьте пустым, чтобы не менять" + min="1" + /> +
+
+ + setMemory(e.target.value)} + className="w-full px-4 py-2 border rounded-lg" + placeholder="Например: 2048" + min="512" + /> +
+
+ + setDisk(e.target.value)} + className="w-full px-4 py-2 border rounded-lg" + placeholder="Например: 40" + min="10" + /> +
+ {error &&
{error}
} +
+ + +
+
+
+
+ ); +} + +// Компонент для управления снэпшотами +function SnapshotsSection({ serverId }: { serverId: number }) { + const [snapshots, setSnapshots] = useState([]); + const [loading, setLoading] = useState(false); + const [snapName, setSnapName] = useState(''); + const [snapDesc, setSnapDesc] = useState(''); + const [error, setError] = useState(''); + + useEffect(() => { + loadSnapshots(); + }, [serverId]); + + const loadSnapshots = async () => { + setLoading(true); + try { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + const res = await axios.get(`http://localhost:5000/api/server/${serverId}/snapshots`, { headers }); + if (res.data?.status === 'success') { + setSnapshots(res.data.data || []); + } + } catch (err) { + console.error('Error loading snapshots:', err); + } finally { + setLoading(false); + } + }; + + const handleCreateSnapshot = async () => { + if (!snapName) { + setError('Введите имя снэпшота'); + return; + } + setLoading(true); + setError(''); + try { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + const res = await axios.post( + `http://localhost:5000/api/server/${serverId}/snapshots`, + { snapname: snapName, description: snapDesc }, + { headers } + ); + if (res.data?.status === 'success') { + setSnapName(''); + setSnapDesc(''); + loadSnapshots(); + } else { + setError('Ошибка создания снэпшота'); + } + } catch (err) { + setError('Ошибка создания снэпшота'); + console.error(err); + } finally { + setLoading(false); + } + }; + + const handleRollback = async (snapname: string) => { + if (!confirm(`Восстановить из снэпшота ${snapname}? Текущее состояние будет потеряно.`)) return; + setLoading(true); + try { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + await axios.post( + `http://localhost:5000/api/server/${serverId}/snapshots/rollback`, + { snapname }, + { headers } + ); + alert('Снэпшот восстановлен'); + } catch (err) { + alert('Ошибка восстановления снэпшота'); + console.error(err); + } finally { + setLoading(false); + } + }; + + const handleDelete = async (snapname: string) => { + if (!confirm(`Удалить снэпшот ${snapname}?`)) return; + setLoading(true); + try { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + await axios.delete( + `http://localhost:5000/api/server/${serverId}/snapshots`, + { data: { snapname }, headers } + ); + loadSnapshots(); + } catch (err) { + alert('Ошибка удаления снэпшота'); + console.error(err); + } finally { + setLoading(false); + } + }; + + return ( +
+

Управление снэпшотами

+ +
+

Создать новый снэпшот

+
+ setSnapName(e.target.value)} + placeholder="Имя снэпшота (например: backup-2024)" + className="w-full px-4 py-2 border rounded-lg" + /> + setSnapDesc(e.target.value)} + placeholder="Описание (опционально)" + className="w-full px-4 py-2 border rounded-lg" + /> + {error &&
{error}
} + +
+
+ +
+

Существующие снэпшоты

+ {snapshots.length === 0 ? ( +

Снэпшотов пока нет

+ ) : ( +
+ {snapshots.map((snap) => ( +
+
+
{snap.name}
+
{snap.description || 'Без описания'}
+
+
+ + +
+
+ ))} +
+ )} +
+
+ ); +} interface Server { id: number; @@ -73,6 +333,8 @@ const TABS = [ { key: 'console', label: 'Консоль' }, { key: 'stats', label: 'Статистика' }, { key: 'manage', label: 'Управление' }, + { key: 'snapshots', label: 'Снэпшоты' }, + { key: 'resize', label: 'Конфигурация' }, { key: 'security', label: 'Безопасность' }, ]; @@ -86,6 +348,10 @@ const ServerPanel: React.FC = () => { const [newRoot, setNewRoot] = useState(null); const [showRoot, setShowRoot] = useState(false); const [stats, setStats] = useState(null); + const [showResizeModal, setShowResizeModal] = useState(false); + + // Real-time WebSocket stats + const { stats: realtimeStats, alerts, connected } = useServerStats(server?.id || null); useEffect(() => { const fetchServer = async () => { @@ -210,16 +476,105 @@ const ServerPanel: React.FC = () => { )} {activeTab === 'stats' && ( -
-
Графики нагрузки
-
-
-
CPU
-
{stats?.data?.cpu ? (stats.data.cpu * 100).toFixed(1) : '—'}%
+
+ {/* WebSocket connection status */} +
+
+ + {connected ? 'Подключено к live-мониторингу' : 'Нет подключения к мониторингу'} + +
+ + {/* Alerts */} + {alerts.length > 0 && ( +
+

⚠️ Предупреждения

+
+ {alerts.map((alert, idx) => ( +
+ {alert.message} +
+ ))} +
-
-
RAM
-
{stats?.data?.memory?.usage ? stats.data.memory.usage.toFixed(1) : '—'}%
+ )} + + {/* Real-time stats cards */} +
+
+
CPU
+
+ {realtimeStats?.data?.cpu ? (realtimeStats.data.cpu * 100).toFixed(1) : stats?.data?.cpu ? (stats.data.cpu * 100).toFixed(1) : '—'}% +
+
+
+
RAM
+
+ {realtimeStats?.data?.memory?.usage?.toFixed(1) || stats?.data?.memory?.usage?.toFixed(1) || '—'}% +
+
+
+
Disk
+
+ {realtimeStats?.data?.disk?.usage?.toFixed(1) || '—'}% +
+
+
+ + {/* Charts */} + {realtimeStats?.data?.rrdData && realtimeStats.data.rrdData.length > 0 && ( +
+

История использования (последний час)

+ + + + + + + + + + + +
+ )} + + {/* Detailed stats */} +
+
Детальная статистика
+
+
+
Memory Used
+
+ {realtimeStats?.data?.memory?.used + ? `${(realtimeStats.data.memory.used / (1024 * 1024 * 1024)).toFixed(2)} GB` + : '—'} +
+
+
+
Memory Max
+
+ {realtimeStats?.data?.memory?.max + ? `${(realtimeStats.data.memory.max / (1024 * 1024 * 1024)).toFixed(2)} GB` + : '—'} +
+
+
+
Network In
+
+ {realtimeStats?.data?.network?.in + ? `${(realtimeStats.data.network.in / (1024 * 1024)).toFixed(2)} MB` + : '—'} +
+
+
+
Network Out
+
+ {realtimeStats?.data?.network?.out + ? `${(realtimeStats.data.network.out / (1024 * 1024)).toFixed(2)} MB` + : '—'} +
+
@@ -233,6 +588,26 @@ const ServerPanel: React.FC = () => {
)} + {activeTab === 'snapshots' && ( + + )} + + {activeTab === 'resize' && ( +
+

Изменение конфигурации сервера

+

+ Вы можете увеличить или уменьшить ресурсы вашего сервера (CPU, RAM, диск). + Изменения вступят в силу после перезапуска сервера. +

+ +
+ )} + {activeTab === 'security' && (
@@ -246,6 +621,24 @@ const ServerPanel: React.FC = () => {
)}
+ + {/* Resize Modal */} + {showResizeModal && ( + setShowResizeModal(false)} + onSuccess={() => { + // Reload server data after resize + const fetchServer = async () => { + const token = localStorage.getItem('access_token'); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + const res = await axios.get(`http://localhost:5000/api/server/${id}`, { headers }); + setServer(res.data); + }; + fetchServer(); + }} + /> + )}
); };