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:
# 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()

View File

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

View File

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

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

View File

@@ -1,7 +1,8 @@
import { useState, useEffect } from 'react'
import './App.css'
import sendRequest from "./Api.jsx";
window.sendRequest = sendRequest;
const TextTile = ({ label }) => (
<div className="tile-content">
<span className="text-purple-400 text-lg font-semibold">{label}</span>
@@ -9,9 +10,9 @@ const TextTile = ({ label }) => (
);
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} />
<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>
);
@@ -39,7 +40,7 @@ const ClockTile = ({ id }) => {
return (
<div className="tile-content">
<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>
);
};
@@ -50,7 +51,7 @@ const GaugeTile = ({ gauges }) => (
{gauges.map((g, index) => (
<div key={index} className="flex items-center gap-4">
<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>
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
</div>
@@ -59,10 +60,10 @@ const GaugeTile = ({ gauges }) => (
</div>
);
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => {
return (
<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">
{sliders.map((s, index) => (
<div key={index} className="flex flex-col items-start">
@@ -88,40 +89,32 @@ const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
const NumberTile = ({ value, label }) => (
<div className="tile-content">
<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>
);
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" }
{ id: 0, type: "break", label: "Система" },
{ id: 1, type: "clock", mini_icon:"/icons/mini/clock-five.png" },
{ 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" },
{ 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" },
{ id: 5, type: "break", label: "Сайты" },
{ 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 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;
})
);
};
const updateSliderValue = (tileId, sliderIndex, newValue) => {
setTiles(prevTiles =>
@@ -139,16 +132,7 @@ const App = () => {
);
};
const updateNumberValue = (tileId) => {
setTiles(prevTiles =>
prevTiles.map(tile => {
if (tile.id === tileId && tile.type === "number") {
return { ...tile, value: tile.value + 1 };
}
return tile;
})
);
};
const toggleImageEnabled = (tileId) => {
setTiles(prevTiles =>
@@ -160,18 +144,95 @@ const App = () => {
})
);
};
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 (
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-4">
<div className="grid grid-cols-2 lg:grid-cols-6 flex-wrap gap-4">
{tiles.map(tile => (
<div key={tile.id}>
<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"></div>
<div className="w-full mt-2 text-purple-400 text-2xl font-bold">{tile.label}</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" : ""}`}
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
@@ -183,38 +244,17 @@ const App = () => {
)}
{tile.type === "clock" && <ClockTile id={tile.id} />}
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
{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.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}
onClick={() => updateGaugeValue(tile.id, index)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Update Gauge {index + 1}
</button>
))}
{tile.type === "slider" && tile.sliders.map((_, index) => (
<button
key={index}
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Increment Slider {index + 1}
</button>
))}
{tile.type === "number" && (
<button
onClick={() => updateNumberValue(tile.id)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Increment Number
</button>
)}
{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>

View File

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

View File

@@ -4,7 +4,7 @@ import './index.css'
import App from './App.jsx'
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 />
</body>
)

View File

@@ -2,18 +2,37 @@ 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 = "<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("/")
@@ -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