ui updates

This commit is contained in:
MoonDev
2025-05-22 01:54:24 +03:00
parent 127fe617ec
commit 5d47f71e0a
21 changed files with 638 additions and 206 deletions

Binary file not shown.

View 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()

View File

@@ -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",

View File

@@ -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"

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View 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;

View File

@@ -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;
} }

View File

@@ -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

View File

@@ -1 +1,4 @@
@import "tailwindcss"; @import "tailwindcss";
#root{
width:100%;
}

View File

@@ -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>
) )

View File

@@ -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