ui updates
BIN
__pycache__/web_server.cpython-311.pyc
Normal file
@@ -21,7 +21,7 @@ async def get_system_metrics(args: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get CPU usage (averaged over 0.1 seconds for responsiveness)
|
# Get CPU usage (averaged over 0.1 seconds for responsiveness)
|
||||||
cpu_usage = await asyncio.to_thread(psutil.cpu_percent, interval=0.1)
|
cpu_usage = await asyncio.to_thread(psutil.cpu_percent, interval=1)
|
||||||
|
|
||||||
# Get RAM usage
|
# Get RAM usage
|
||||||
memory = psutil.virtual_memory()
|
memory = psutil.virtual_memory()
|
||||||
|
|||||||
279
static/frontend/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"tailwindcss": "^4.1.7"
|
"tailwindcss": "^4.1.7"
|
||||||
@@ -1683,6 +1684,23 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1734,6 +1752,19 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@@ -1811,6 +1842,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -1872,6 +1915,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||||
@@ -1881,6 +1933,20 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.155",
|
"version": "1.5.155",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
||||||
@@ -1901,6 +1967,51 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.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",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
||||||
@@ -2228,6 +2339,41 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -2242,6 +2388,15 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@@ -2252,6 +2407,43 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -2278,6 +2470,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -2294,6 +2498,45 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -2731,6 +2974,36 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@@ -2958,6 +3231,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"tailwindcss": "^4.1.7"
|
"tailwindcss": "^4.1.7"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
BIN
static/frontend/public/icons/mini/arrow-up-right-from-square.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
static/frontend/public/icons/mini/chart-line-up.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
static/frontend/public/icons/mini/clock-five.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
static/frontend/public/icons/mini/settings.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
static/frontend/public/icons/mini/wifi.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
43
static/frontend/src/Api.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = "http://192.168.2.151:8000"
|
||||||
|
|
||||||
|
|
||||||
|
const createSha256Hash = async (input) => {
|
||||||
|
try {
|
||||||
|
// Convert string to array buffer
|
||||||
|
const msgBuffer = new TextEncoder().encode(input);
|
||||||
|
|
||||||
|
// Generate hash
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||||
|
|
||||||
|
// Convert buffer to hex string
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
|
return hashHex;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating SHA-256 hash:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const sendRequest = async (action,args) => {
|
||||||
|
try {
|
||||||
|
const TS = Math.round(Date.now()/1000);
|
||||||
|
const hash = await createSha256Hash("<password>"+JSON.stringify(args)+TS);
|
||||||
|
const response = await axios.post(API_URL+"/action/"+action, {args:args,hash:hash+"."+TS}, {
|
||||||
|
timeout: 4000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in POST request:', error);
|
||||||
|
throw error.response?.data || error.message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sendRequest;
|
||||||
@@ -13,6 +13,27 @@
|
|||||||
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
|
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes flipRight {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass:hover>.absolute>img {
|
||||||
|
animation: flipRight 0.5s forwards;
|
||||||
|
animation-delay: 0s;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.glass_enabled {
|
.glass_enabled {
|
||||||
background: rgba(30, 240, 135, 0.2);
|
background: rgba(30, 240, 135, 0.2);
|
||||||
border-color: rgba(51, 234, 127, 0.7);
|
border-color: rgba(51, 234, 127, 0.7);
|
||||||
@@ -42,7 +63,7 @@
|
|||||||
|
|
||||||
.gauge span {
|
.gauge span {
|
||||||
color: #d8b4fe;
|
color: #d8b4fe;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +1,266 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
import sendRequest from "./Api.jsx";
|
||||||
|
|
||||||
|
window.sendRequest = sendRequest;
|
||||||
const TextTile = ({ label }) => (
|
const TextTile = ({ label }) => (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
|
const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
|
||||||
<div className="tile-content cursor-pointer" onClick={toggleEnabled}>
|
<div className="tile-content cursor-pointer">
|
||||||
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold mt-2">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ClockTile = ({ id }) => {
|
const ClockTile = ({ id }) => {
|
||||||
const [time, setTime] = useState("");
|
const [time, setTime] = useState("");
|
||||||
const [date, setDate] = useState("");
|
const [date, setDate] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateClock = () => {
|
const updateClock = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
||||||
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
||||||
const date = now.toLocaleDateString('ru-RU', options)
|
const date = now.toLocaleDateString('ru-RU', options)
|
||||||
.replace(/,/, '')
|
.replace(/,/, '')
|
||||||
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
||||||
.replace(/г\./, '');
|
.replace(/г\./, '');
|
||||||
setTime(time);
|
setTime(time);
|
||||||
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
||||||
};
|
};
|
||||||
updateClock();
|
updateClock();
|
||||||
const interval = setInterval(updateClock, 1000);
|
const interval = setInterval(updateClock, 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{date}</span>
|
<span className="text-purple-400 text-lg font-semibold mt-2">{date}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaugeTile = ({ gauges }) => (
|
const GaugeTile = ({ gauges }) => (
|
||||||
<div className="tile-content gauge-tile-content">
|
<div className="tile-content gauge-tile-content">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{gauges.map((g, index) => (
|
{gauges.map((g, index) => (
|
||||||
<div key={index} className="flex items-center gap-4">
|
<div key={index} className="flex items-center gap-4">
|
||||||
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
||||||
<span>{Math.round((g.value / 360) * 100)}%</span>
|
<span className='font-bold'>{Math.round((g.value / 360) * 100)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
|
const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-purple-400 text-lg font-semibold">Slider Controls</span>
|
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||||
<div className="flex flex-col w-full gap-4">
|
<div className="flex flex-col w-full gap-4">
|
||||||
{sliders.map((s, index) => (
|
{sliders.map((s, index) => (
|
||||||
<div key={index} className="flex flex-col items-start">
|
<div key={index} className="flex flex-col items-start">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
||||||
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={s.value}
|
value={s.value}
|
||||||
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NumberTile = ({ value, label }) => (
|
const NumberTile = ({ value, label }) => (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold mt-2">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [tiles, setTiles] = useState([
|
const [tiles, setTiles] = useState([
|
||||||
{ id: 1, type: "text", label: "Tile 1" },
|
{ id: 0, type: "break", label: "Система" },
|
||||||
{ id: 2, type: "gauge", gauges: [{ value: 75, label: "Metric 1" }, { value: 120, label: "Metric 2" }] },
|
|
||||||
{ id: 3, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 1", enabled: true },
|
|
||||||
{ id: 4, type: "break" },
|
|
||||||
{ id: 5, type: "text", label: "Tile 4" },
|
|
||||||
{ id: 6, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 2", enabled: true },
|
|
||||||
{ id: 7, type: "clock" },
|
|
||||||
{ id: 8, type: "slider", sliders: [{ value: 50, label: "Slider 1" }, { value: 75, label: "Slider 2" }] },
|
|
||||||
{ id: 9, type: "text", label: "Tile 5 (Double)" },
|
|
||||||
{ id: 10, type: "text", label: "Tile 6" },
|
|
||||||
{ id: 11, type: "number", value: 42, label: "Data Point" }
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updateGaugeValue = (tileId, gaugeIndex) => {
|
{ id: 1, type: "clock", mini_icon:"/icons/mini/clock-five.png" },
|
||||||
setTiles(prevTiles =>
|
|
||||||
prevTiles.map(tile => {
|
|
||||||
if (tile.id === tileId && tile.type === "gauge") {
|
|
||||||
const newGauges = [...tile.gauges];
|
|
||||||
newGauges[gaugeIndex] = {
|
|
||||||
...newGauges[gaugeIndex],
|
|
||||||
value: (newGauges[gaugeIndex].value + 10) % 360
|
|
||||||
};
|
|
||||||
return { ...tile, gauges: newGauges };
|
|
||||||
}
|
|
||||||
return tile;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
{ id: 2, type: "gauge", gauges: [{ value: 3.60 * 0, label: "Загрузка CPU" }, { value: 3.60 * 0, label: "Занято RAM" }], tag: "system_load", mini_icon:"/icons/mini/chart-line-up.png" },
|
||||||
setTiles(prevTiles =>
|
|
||||||
prevTiles.map(tile => {
|
|
||||||
if (tile.id === tileId && tile.type === "slider") {
|
|
||||||
const newSliders = [...tile.sliders];
|
|
||||||
newSliders[sliderIndex] = {
|
|
||||||
...newSliders[sliderIndex],
|
|
||||||
value: parseInt(newValue)
|
|
||||||
};
|
|
||||||
return { ...tile, sliders: newSliders };
|
|
||||||
}
|
|
||||||
return tile;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateNumberValue = (tileId) => {
|
{ id: 3, type: "number", value: 0, label: "Трафик Mbit/s", tag: "net_traffic", mini_icon:"/icons/mini/wifi.png" },
|
||||||
setTiles(prevTiles =>
|
{ id: 4, type: "slider", label: "Настройки", sliders: [{ value: 50, label: "Яроксть экрана", action: "screen.chenge_brightness" }, { value: 75, label: "Громкость звука", action: "media.set_volume" }], mini_icon:"/icons/mini/settings.png" },
|
||||||
prevTiles.map(tile => {
|
|
||||||
if (tile.id === tileId && tile.type === "number") {
|
|
||||||
return { ...tile, value: tile.value + 1 };
|
|
||||||
}
|
|
||||||
return tile;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleImageEnabled = (tileId) => {
|
{ id: 5, type: "break", label: "Сайты" },
|
||||||
setTiles(prevTiles =>
|
|
||||||
prevTiles.map(tile => {
|
|
||||||
if (tile.id === tileId && tile.type === "image") {
|
|
||||||
return { ...tile, enabled: !tile.enabled };
|
|
||||||
}
|
|
||||||
return tile;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
{ id: 6, type: "image", imageUrl: "/icons/steam-svgrepo-com.svg", label: "Steam", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||||
<div className="flex flex-col gap-4">
|
{ id: 7, type: "image", imageUrl: "/icons/telegram-logo-svgrepo-com.svg", label: "Telegram", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||||
<div className="flex flex-wrap gap-4">
|
{ id: 8, type: "image", imageUrl: "/icons/cloud.png", label: "Cloud", action: "web.open_url", action_args: { url: "https://cloud.viadev.su" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
||||||
{tiles.map(tile => (
|
|
||||||
<div key={tile.id}>
|
{ id: 9, type: "break", label: "Управление ПК" },
|
||||||
{tile.type === "break" ? (
|
{ id: 10, type: "image", imageUrl: "/icons/vpn.png", label: "VPN", action: "v2ray.enable_vpn", action_args: {} },
|
||||||
<div className="w-full"></div>
|
]);
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-96 h-48" : "w-48 h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
|
||||||
>
|
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
||||||
{tile.type === "text" && <TextTile label={tile.label} />}
|
setTiles(prevTiles =>
|
||||||
{tile.type === "image" && (
|
prevTiles.map(tile => {
|
||||||
<ImageTile
|
if (tile.id === tileId && tile.type === "slider") {
|
||||||
imageUrl={tile.imageUrl}
|
const newSliders = [...tile.sliders];
|
||||||
label={tile.label}
|
newSliders[sliderIndex] = {
|
||||||
enabled={tile.enabled}
|
...newSliders[sliderIndex],
|
||||||
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
value: parseInt(newValue)
|
||||||
/>
|
};
|
||||||
)}
|
return { ...tile, sliders: newSliders };
|
||||||
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
}
|
||||||
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
return tile;
|
||||||
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
})
|
||||||
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
);
|
||||||
{(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && (
|
};
|
||||||
<div className="absolute top-2 right-2 flex gap-2">
|
|
||||||
{tile.type === "gauge" && tile.gauges.map((_, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
const toggleImageEnabled = (tileId) => {
|
||||||
onClick={() => updateGaugeValue(tile.id, index)}
|
setTiles(prevTiles =>
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
prevTiles.map(tile => {
|
||||||
>
|
if (tile.id === tileId && tile.type === "image") {
|
||||||
Update Gauge {index + 1}
|
return { ...tile, enabled: !tile.enabled };
|
||||||
</button>
|
}
|
||||||
))}
|
return tile;
|
||||||
{tile.type === "slider" && tile.sliders.map((_, index) => (
|
})
|
||||||
<button
|
);
|
||||||
key={index}
|
};
|
||||||
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
|
const setTileProps = (tileId, props) => {
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
setTiles(prevTiles =>
|
||||||
>
|
prevTiles.map(tile => {
|
||||||
Increment Slider {index + 1}
|
if (tile.id === tileId) {
|
||||||
</button>
|
return { ...tile, ...props };
|
||||||
))}
|
}
|
||||||
{tile.type === "number" && (
|
return tile;
|
||||||
<button
|
})
|
||||||
onClick={() => updateNumberValue(tile.id)}
|
);
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
};
|
||||||
>
|
const setTilePropsByTag = (tag, props) => {
|
||||||
Increment Number
|
setTiles(prevTiles =>
|
||||||
</button>
|
prevTiles.map(tile => {
|
||||||
)}
|
if (tile.tag === tag) {
|
||||||
</div>
|
return { ...tile, ...props };
|
||||||
)}
|
}
|
||||||
</div>
|
return tile;
|
||||||
)}
|
})
|
||||||
</div>
|
);
|
||||||
))}
|
};
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
useEffect(() => {
|
||||||
|
const updateSystemStats = async () => {
|
||||||
|
try {
|
||||||
|
const data = await sendRequest("system.get_system_metrics", {});
|
||||||
|
setTilePropsByTag("system_load", {
|
||||||
|
gauges: [
|
||||||
|
{ value: (3.60 * data.result.metrics.cpu_usage_percent).toFixed(0), label: "Загрузка CPU" },
|
||||||
|
{ value: (3.60 * (data.result.metrics.ram_used_gb / data.result.metrics.ram_total_gb) * 100).toFixed(0), label: "Занято RAM" }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
setTilePropsByTag("net_traffic", {
|
||||||
|
value: data.result.metrics.network_traffic_mbps
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateSystemStats();
|
||||||
|
const interval = setInterval(updateSystemStats, 2000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const onTileClick = async (tile) => {
|
||||||
|
console.log("click", tile)
|
||||||
|
|
||||||
|
if (tile.action) {
|
||||||
|
try {
|
||||||
|
setTileProps(tile.id, { loading: true })
|
||||||
|
await sendRequest(tile.action, tile.action_args);
|
||||||
|
|
||||||
|
if (tile.action.includes("enable")) {
|
||||||
|
tile.enabled = true;
|
||||||
|
tile.action = tile.action.replace("enable", "disable");
|
||||||
|
} else if (tile.action.includes("disable")) {
|
||||||
|
tile.enabled = false;
|
||||||
|
tile.action = tile.action.replace("disable", "enable");
|
||||||
|
}
|
||||||
|
tile.loading = false;
|
||||||
|
|
||||||
|
setTileProps(tile.id, tile)
|
||||||
|
} catch (e) {
|
||||||
|
setTileProps(tile.id, { loading: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-6 flex-wrap gap-4">
|
||||||
|
{tiles.map(tile => (
|
||||||
|
<div key={tile.id} className={tile.type === "break" ? "col-span-full" : (tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" || tile.type === "number" ? "w-full sm:w-auto col-span-2" : "col-span-1")}>
|
||||||
|
{tile.type === "break" ? (
|
||||||
|
<div className="w-full mt-2 text-purple-400 text-2xl font-bold">{tile.label}</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onClick={() => { onTileClick(tile) }}
|
||||||
|
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-full h-45 lg:h-48" : "w-full h-45 lg:h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
||||||
|
>
|
||||||
|
{tile.loading &&
|
||||||
|
<div className={"flex animate-[fadeIn_0.5s_ease-out] absolute inset-0 bg-black/10 backdrop-blur-[3px] flex-col items-center justify-center z-10 rounded-xl"}>
|
||||||
|
<div className="w-10 h-10 border-4 border-purple-500 border-t-transparent rounded-full animate-spin" style={{ animationDuration: "0.5s" }} ></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{!tile.loading &&
|
||||||
|
<>
|
||||||
|
{tile.type === "text" && <TextTile label={tile.label} />}
|
||||||
|
{tile.type === "image" && (
|
||||||
|
<ImageTile
|
||||||
|
imageUrl={tile.imageUrl}
|
||||||
|
label={tile.label}
|
||||||
|
enabled={tile.enabled}
|
||||||
|
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
||||||
|
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
||||||
|
{tile.type === "slider" && <SliderTile label={tile.label} sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
||||||
|
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
||||||
|
{tile.mini_icon && (
|
||||||
|
<div className="absolute top-3 right-3 flex gap-2">
|
||||||
|
|
||||||
|
<img src={tile.mini_icon} className='h-5 w-5'/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
#root{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import './index.css'
|
|||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<body className='bg-gray-900 min-h-screen p-4'>
|
<body className='bg-gray-900 min-h-screen py-4 flex justify-center px-3 2xl:px-[15%]'>
|
||||||
<App />
|
<App />
|
||||||
</body>
|
</body>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,19 +2,38 @@ from fastapi import FastAPI, HTTPException
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from callback import call_action
|
from callback import call_action
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
PASSWORD = "<password>"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Add CORS middleware
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # Allows all origins
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"], # Allows all methods (GET, POST, etc.)
|
||||||
|
allow_headers=["*"], # Allows all headers
|
||||||
|
)
|
||||||
|
|
||||||
# Static files & UI server
|
# Static files & UI server
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
class ActionArgs(BaseModel):
|
# Pydantic model for request payload
|
||||||
args: Optional[Dict[str, Any]] = None
|
class CommandModel(BaseModel):
|
||||||
|
args: Dict[str, Any]
|
||||||
|
hash: str # Mandatory hash field
|
||||||
|
|
||||||
# Show main page
|
# Show main page
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def read_index():
|
async def read_index():
|
||||||
@@ -24,10 +43,36 @@ async def read_index():
|
|||||||
|
|
||||||
# Process actions via POST
|
# Process actions via POST
|
||||||
@app.post("/action/{name:path}")
|
@app.post("/action/{name:path}")
|
||||||
async def handle_action(name: str, action_args: ActionArgs):
|
async def handle_action(name: str, payload: CommandModel):
|
||||||
|
|
||||||
|
action_args = payload.args
|
||||||
|
hash_with_ts = payload.hash
|
||||||
|
if not hash_with_ts:
|
||||||
|
raise HTTPException(status_code=400, detail="Missing hash")
|
||||||
|
|
||||||
|
# Split hash and timestamp
|
||||||
|
try:
|
||||||
|
received_hash, received_ts = hash_with_ts.split(".")
|
||||||
|
received_ts = int(received_ts)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid hash format")
|
||||||
|
|
||||||
|
# Check if token is within 5-second window
|
||||||
|
current_ts = int(time.time())
|
||||||
|
if abs(current_ts - received_ts) > 5:
|
||||||
|
raise HTTPException(status_code=401, detail="Token expired")
|
||||||
|
|
||||||
|
# Reconstruct hash
|
||||||
|
data_to_hash = PASSWORD + json.dumps(action_args, separators=(",", ":")) + str(received_ts)
|
||||||
|
computed_hash = hashlib.sha256(data_to_hash.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
# Verify hash
|
||||||
|
if computed_hash != received_hash:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid hash")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Вызываем функцию из callback.py, передавая имя действия и аргументы
|
# Вызываем функцию из callback.py, передавая имя действия и аргументы
|
||||||
result = await call_action(name, action_args.args or {})
|
result = await call_action(name, action_args or {})
|
||||||
return JSONResponse(content={"status": "success", "result": result, "command": name})
|
return JSONResponse(content={"status": "success", "result": result, "command": name})
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# Если функция не найдена, возвращаем 404
|
# Если функция не найдена, возвращаем 404
|
||||||
|
|||||||