diff --git a/__pycache__/web_server.cpython-311.pyc b/__pycache__/web_server.cpython-311.pyc new file mode 100644 index 0000000..dfc2891 Binary files /dev/null and b/__pycache__/web_server.cpython-311.pyc differ diff --git a/callbacks/__pycache__/system.cpython-311.pyc b/callbacks/__pycache__/system.cpython-311.pyc index 6e7f879..44ffdb7 100644 Binary files a/callbacks/__pycache__/system.cpython-311.pyc and b/callbacks/__pycache__/system.cpython-311.pyc differ diff --git a/callbacks/__pycache__/v2ray.cpython-311.pyc b/callbacks/__pycache__/v2ray.cpython-311.pyc index 2d605c6..a0ec7ac 100644 Binary files a/callbacks/__pycache__/v2ray.cpython-311.pyc and b/callbacks/__pycache__/v2ray.cpython-311.pyc differ diff --git a/callbacks/system.py b/callbacks/system.py index 61a1bc3..8bfc2ed 100644 --- a/callbacks/system.py +++ b/callbacks/system.py @@ -21,7 +21,7 @@ async def get_system_metrics(args: Dict[str, Any]) -> Dict[str, Any]: """ try: # 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 memory = psutil.virtual_memory() diff --git a/static/frontend/package-lock.json b/static/frontend/package-lock.json index b9975cc..71d65bd 100644 --- a/static/frontend/package-lock.json +++ b/static/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.1.7", + "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "tailwindcss": "^4.1.7" @@ -1683,6 +1684,23 @@ "dev": true, "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": { "version": "1.0.2", "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_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": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1811,6 +1842,18 @@ "dev": true, "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": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1872,6 +1915,15 @@ "dev": true, "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": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -1881,6 +1933,20 @@ "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": { "version": "1.5.155", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", @@ -1901,6 +1967,51 @@ "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": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -2228,6 +2339,41 @@ "dev": true, "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": { "version": "2.3.3", "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_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": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2252,6 +2407,43 @@ "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": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2278,6 +2470,18 @@ "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": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2294,6 +2498,45 @@ "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": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2731,6 +2974,36 @@ "@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": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2958,6 +3231,12 @@ "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": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/static/frontend/package.json b/static/frontend/package.json index b844061..0b74ea6 100644 --- a/static/frontend/package.json +++ b/static/frontend/package.json @@ -4,13 +4,14 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "@tailwindcss/vite": "^4.1.7", + "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "tailwindcss": "^4.1.7" diff --git a/static/icons/cloud.png b/static/frontend/public/icons/cloud.png similarity index 100% rename from static/icons/cloud.png rename to static/frontend/public/icons/cloud.png diff --git a/static/frontend/public/icons/mini/arrow-up-right-from-square.png b/static/frontend/public/icons/mini/arrow-up-right-from-square.png new file mode 100644 index 0000000..31ff339 Binary files /dev/null and b/static/frontend/public/icons/mini/arrow-up-right-from-square.png differ diff --git a/static/frontend/public/icons/mini/chart-line-up.png b/static/frontend/public/icons/mini/chart-line-up.png new file mode 100644 index 0000000..53ca856 Binary files /dev/null and b/static/frontend/public/icons/mini/chart-line-up.png differ diff --git a/static/frontend/public/icons/mini/clock-five.png b/static/frontend/public/icons/mini/clock-five.png new file mode 100644 index 0000000..cb9aeb4 Binary files /dev/null and b/static/frontend/public/icons/mini/clock-five.png differ diff --git a/static/frontend/public/icons/mini/settings.png b/static/frontend/public/icons/mini/settings.png new file mode 100644 index 0000000..9ec119d Binary files /dev/null and b/static/frontend/public/icons/mini/settings.png differ diff --git a/static/frontend/public/icons/mini/wifi.png b/static/frontend/public/icons/mini/wifi.png new file mode 100644 index 0000000..4c931f9 Binary files /dev/null and b/static/frontend/public/icons/mini/wifi.png differ diff --git a/static/icons/steam-svgrepo-com.svg b/static/frontend/public/icons/steam-svgrepo-com.svg similarity index 100% rename from static/icons/steam-svgrepo-com.svg rename to static/frontend/public/icons/steam-svgrepo-com.svg diff --git a/static/icons/telegram-logo-svgrepo-com.svg b/static/frontend/public/icons/telegram-logo-svgrepo-com.svg similarity index 100% rename from static/icons/telegram-logo-svgrepo-com.svg rename to static/frontend/public/icons/telegram-logo-svgrepo-com.svg diff --git a/static/icons/vpn.png b/static/frontend/public/icons/vpn.png similarity index 100% rename from static/icons/vpn.png rename to static/frontend/public/icons/vpn.png diff --git a/static/frontend/src/Api.jsx b/static/frontend/src/Api.jsx new file mode 100644 index 0000000..b50174d --- /dev/null +++ b/static/frontend/src/Api.jsx @@ -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(""+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; \ No newline at end of file diff --git a/static/frontend/src/App.css b/static/frontend/src/App.css index 2d6ca43..f8635fb 100644 --- a/static/frontend/src/App.css +++ b/static/frontend/src/App.css @@ -13,6 +13,27 @@ 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 { background: rgba(30, 240, 135, 0.2); border-color: rgba(51, 234, 127, 0.7); @@ -42,7 +63,7 @@ .gauge span { color: #d8b4fe; - font-size: 12px; + font-size: 13px; font-weight: bold; z-index: 1; } diff --git a/static/frontend/src/App.jsx b/static/frontend/src/App.jsx index 5ea8fd5..5b8bdfe 100644 --- a/static/frontend/src/App.jsx +++ b/static/frontend/src/App.jsx @@ -1,226 +1,266 @@ import { useState, useEffect } from 'react' import './App.css' +import sendRequest from "./Api.jsx"; - +window.sendRequest = sendRequest; const TextTile = ({ label }) => ( -
- {label} -
+
+ {label} +
); const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => ( -
- {label} - {label} -
+
+ {label} + {label} +
); const ClockTile = ({ id }) => { - const [time, setTime] = useState(""); - const [date, setDate] = useState(""); + const [time, setTime] = useState(""); + const [date, setDate] = useState(""); - useEffect(() => { - const updateClock = () => { - const now = new Date(); - const time = now.toLocaleTimeString('ru-RU', { hour12: false }); - const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' }; - const date = now.toLocaleDateString('ru-RU', options) - .replace(/,/, '') - .replace(/(\d+)\s(\S+)/, '$1 $2') - .replace(/г\./, ''); - setTime(time); - setDate(date.charAt(0).toUpperCase() + date.slice(1)); - }; - updateClock(); - const interval = setInterval(updateClock, 1000); - return () => clearInterval(interval); - }, []); + useEffect(() => { + const updateClock = () => { + const now = new Date(); + const time = now.toLocaleTimeString('ru-RU', { hour12: false }); + const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' }; + const date = now.toLocaleDateString('ru-RU', options) + .replace(/,/, '') + .replace(/(\d+)\s(\S+)/, '$1 $2') + .replace(/г\./, ''); + setTime(time); + setDate(date.charAt(0).toUpperCase() + date.slice(1)); + }; + updateClock(); + const interval = setInterval(updateClock, 1000); + return () => clearInterval(interval); + }, []); - return ( -
- {time} - {date} -
- ); + return ( +
+ {time} + {date} +
+ ); }; const GaugeTile = ({ gauges }) => ( -
-
- {gauges.map((g, index) => ( -
-
- {Math.round((g.value / 360) * 100)}% -
- {g.label} -
- ))} -
-
+
+
+ {gauges.map((g, index) => ( +
+
+ {Math.round((g.value / 360) * 100)}% +
+ {g.label} +
+ ))} +
+
); -const SliderTile = ({ sliders, tileId, updateSliderValue }) => { - return ( -
- Slider Controls -
- {sliders.map((s, index) => ( -
-
- {s.label} - {s.value} -
- updateSliderValue(tileId, index, e.target.value)} - className="w-full" - /> -
- ))} -
-
- ); +const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => { + return ( +
+ {label} +
+ {sliders.map((s, index) => ( +
+
+ {s.label} + {s.value} +
+ updateSliderValue(tileId, index, e.target.value)} + className="w-full" + /> +
+ ))} +
+
+ ); }; const NumberTile = ({ value, label }) => ( -
- {value} - {label} -
+
+ {value} + {label} +
); const App = () => { - const [tiles, setTiles] = useState([ - { id: 1, type: "text", label: "Tile 1" }, - { 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 [tiles, setTiles] = useState([ + { id: 0, type: "break", label: "Система" }, - const updateGaugeValue = (tileId, gaugeIndex) => { - 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; - }) - ); - }; + { id: 1, type: "clock", mini_icon:"/icons/mini/clock-five.png" }, - const updateSliderValue = (tileId, sliderIndex, newValue) => { - 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; - }) - ); - }; + { 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" }, - const updateNumberValue = (tileId) => { - setTiles(prevTiles => - prevTiles.map(tile => { - if (tile.id === tileId && tile.type === "number") { - return { ...tile, value: tile.value + 1 }; - } - return tile; - }) - ); - }; + { id: 3, type: "number", value: 0, label: "Трафик Mbit/s", tag: "net_traffic", mini_icon:"/icons/mini/wifi.png" }, + { 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" }, - const toggleImageEnabled = (tileId) => { - setTiles(prevTiles => - prevTiles.map(tile => { - if (tile.id === tileId && tile.type === "image") { - return { ...tile, enabled: !tile.enabled }; - } - return tile; - }) - ); - }; + { id: 5, type: "break", label: "Сайты" }, - return ( -
-
- {tiles.map(tile => ( -
- {tile.type === "break" ? ( -
- ) : ( -
- {tile.type === "text" && } - {tile.type === "image" && ( - toggleImageEnabled(tile.id)} - /> - )} - {tile.type === "clock" && } - {tile.type === "gauge" && } - {tile.type === "slider" && } - {tile.type === "number" && } - {(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && ( -
- {tile.type === "gauge" && tile.gauges.map((_, index) => ( - - ))} - {tile.type === "slider" && tile.sliders.map((_, index) => ( - - ))} - {tile.type === "number" && ( - - )} -
- )} -
- )} -
- ))} -
-
- ); + { 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" }, + { 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" }, + { 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" }, + + { id: 9, type: "break", label: "Управление ПК" }, + { id: 10, type: "image", imageUrl: "/icons/vpn.png", label: "VPN", action: "v2ray.enable_vpn", action_args: {} }, + ]); + + + + const updateSliderValue = (tileId, sliderIndex, newValue) => { + 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 toggleImageEnabled = (tileId) => { + setTiles(prevTiles => + prevTiles.map(tile => { + if (tile.id === tileId && tile.type === "image") { + return { ...tile, enabled: !tile.enabled }; + } + return tile; + }) + ); + }; + const setTileProps = (tileId, props) => { + setTiles(prevTiles => + prevTiles.map(tile => { + if (tile.id === tileId) { + return { ...tile, ...props }; + } + return tile; + }) + ); + }; + const setTilePropsByTag = (tag, props) => { + setTiles(prevTiles => + prevTiles.map(tile => { + if (tile.tag === tag) { + return { ...tile, ...props }; + } + return tile; + }) + ); + }; + + + 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 ( +
+
+ {tiles.map(tile => ( +
+ {tile.type === "break" ? ( +
{tile.label}
+ ) : ( +
{ 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 && +
+
+
+ } + {!tile.loading && + <> + {tile.type === "text" && } + {tile.type === "image" && ( + toggleImageEnabled(tile.id)} + /> + )} + {tile.type === "clock" && } + {tile.type === "gauge" && } + {tile.type === "slider" && } + {tile.type === "number" && } + {tile.mini_icon && ( +
+ + + +
+ )} + + } +
+ )} +
+ ))} +
+
+ ); }; export default App diff --git a/static/frontend/src/index.css b/static/frontend/src/index.css index a461c50..bf9f16f 100644 --- a/static/frontend/src/index.css +++ b/static/frontend/src/index.css @@ -1 +1,4 @@ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; +#root{ + width:100%; +} \ No newline at end of file diff --git a/static/frontend/src/main.jsx b/static/frontend/src/main.jsx index 0311944..ffc71ea 100644 --- a/static/frontend/src/main.jsx +++ b/static/frontend/src/main.jsx @@ -4,7 +4,7 @@ import './index.css' import App from './App.jsx' createRoot(document.getElementById('root')).render( - + ) diff --git a/web_server.py b/web_server.py index 2a749ca..44526bf 100644 --- a/web_server.py +++ b/web_server.py @@ -2,19 +2,38 @@ from fastapi import FastAPI, HTTPException from pydantic import BaseModel from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse +from fastapi.middleware.cors import CORSMiddleware from typing import Optional, Dict, Any import uvicorn from callback import call_action from fastapi.responses import JSONResponse +import hashlib +import json +import time + +PASSWORD = "" + + 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 app.mount("/static", StaticFiles(directory="static"), name="static") -class ActionArgs(BaseModel): - args: Optional[Dict[str, Any]] = None - +# Pydantic model for request payload +class CommandModel(BaseModel): + args: Dict[str, Any] + hash: str # Mandatory hash field + # Show main page @app.get("/") async def read_index(): @@ -24,10 +43,36 @@ async def read_index(): # Process actions via POST @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: # Вызываем функцию из 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}) except ValueError as e: # Если функция не найдена, возвращаем 404