From 5d47f71e0a757dbac0b69f377e97b035a78c44ce Mon Sep 17 00:00:00 2001 From: MoonDev Date: Thu, 22 May 2025 01:54:24 +0300 Subject: [PATCH] ui updates --- __pycache__/web_server.cpython-311.pyc | Bin 0 -> 4430 bytes callbacks/__pycache__/system.cpython-311.pyc | Bin 3731 -> 3722 bytes callbacks/__pycache__/v2ray.cpython-311.pyc | Bin 8385 -> 8745 bytes callbacks/system.py | 2 +- static/frontend/package-lock.json | 279 +++++++++++ static/frontend/package.json | 3 +- static/{ => frontend/public}/icons/cloud.png | Bin .../icons/mini/arrow-up-right-from-square.png | Bin 0 -> 2837 bytes .../public/icons/mini/chart-line-up.png | Bin 0 -> 3097 bytes .../frontend/public/icons/mini/clock-five.png | Bin 0 -> 5187 bytes .../frontend/public/icons/mini/settings.png | Bin 0 -> 6448 bytes static/frontend/public/icons/mini/wifi.png | Bin 0 -> 3434 bytes .../public}/icons/steam-svgrepo-com.svg | 0 .../icons/telegram-logo-svgrepo-com.svg | 0 static/{ => frontend/public}/icons/vpn.png | Bin static/frontend/src/Api.jsx | 43 ++ static/frontend/src/App.css | 23 +- static/frontend/src/App.jsx | 432 ++++++++++-------- static/frontend/src/index.css | 5 +- static/frontend/src/main.jsx | 2 +- web_server.py | 55 ++- 21 files changed, 638 insertions(+), 206 deletions(-) create mode 100644 __pycache__/web_server.cpython-311.pyc rename static/{ => frontend/public}/icons/cloud.png (100%) create mode 100644 static/frontend/public/icons/mini/arrow-up-right-from-square.png create mode 100644 static/frontend/public/icons/mini/chart-line-up.png create mode 100644 static/frontend/public/icons/mini/clock-five.png create mode 100644 static/frontend/public/icons/mini/settings.png create mode 100644 static/frontend/public/icons/mini/wifi.png rename static/{ => frontend/public}/icons/steam-svgrepo-com.svg (100%) rename static/{ => frontend/public}/icons/telegram-logo-svgrepo-com.svg (100%) rename static/{ => frontend/public}/icons/vpn.png (100%) create mode 100644 static/frontend/src/Api.jsx diff --git a/__pycache__/web_server.cpython-311.pyc b/__pycache__/web_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfc289179b4915a7b050abe84bfcd91f5ac3d681 GIT binary patch literal 4430 zcmaJETWs6b^jS<@=X2u7E-=}?c7 zRQ#wksA;hJV;I~aMFOk^{IO*j>W>cChWrfZfVJ3w6(9ov5eNhrFa%h!eJDr@1peB& zq-9BVw#&lT=4zQw={yl%nBgMNTW z%QZwIGTXS8*bDqStC;mIY2CrM?Hbx~t1h5@ww|WiC+$0EqjERtkoV5A!BKju*$@hqpMc#j5n1_;7=We6a8VypMhb;3UEzCS%)d zKnR$42(z)nKwd!wlt=4zctE^W8Wp(!0;kU{4;UZ3Zow!W>_smjon7=SG4G&7jL|!= zH%7*`kt3AH#Jk~Zq=ZkxO|3M$3UF9S6M3nB-bkr7);7I4WA??@KoNL3oy-s=J@?ID zVLxgZ$xPY+C9{K+FsF;KOck5WWaYHYYM@{m^`C%>XYCMW2q+GTP7fW6X;kiZfMSZy zu3A4#9DrBq`Q$!E^VN>W?|K8_O(|^?RA1(QhRuJ~a?)>&3i zJjks?4!c%*_&utWgIUR#FKBImWpi0+S3zM(aN?nh zwI68^gLa(5yZaoA$W^&|?nbz_(*iXKr!=baj{SW`Qxy+X)tC3Kz*!n`i0?8>akNJ1 zaD?A|Dq~NLn-Qt?7q)Mh_YnU^pi!zTfM`(PuvKiV}EUb(7OXW zxHglP|Ht>2=+mdzN0oO~KjV=pWeu!sC3H&Bb?{~}8g=3xEe?CzJ|Y{EqHf~;mB@Jd zili#US&z+Tw3K9gL;a}o5hV|w&0Ln#F?lHqv693+oA`xIJP2+wpvzfFlOTHPS|6aV zgz6dDoSsW2WnBk9=>*)IZu>M@CWr%p_aW9=Z_=Q`}8Jf@pP$H8lAX-SQ_u`4EP zT1Fd;#UmO`^)#x98Z`>rt7laOatQ_8a^NjV&BT5*F(cpkC}$QRr*Xr}>(#&sQo_jBV(#fKt$ufI?{S%zk) zI9U!xX#^O>_l3^e%;!_4Fk%TKC1J$mM=CzndlHxXUb@%!>fOFq?=V*1P^oXo;-6a$ zuf2D_{h6DGto8$?_5-WI)!>u+!8Xe2B(8@JS94(86ecWTq9jb1`~;0vCozPcZ+XgRtIketzfUk?Jsfr*WbO%9W=Rv?)j!oVbl^vOTwth zkACYztzDmzPZn->ezDi=p0v6rO8_I2U+d<~kF9C3G%Z>Yu{e3Zwe$Ma`p~`JBX@g` ze7eR8gw;A(YMm^e0_pH=H^X+`+#a`~8?H9J7RDCE=LK9|J)FdMm|z8)RF94uv`J*4Ih%%*@|r|2_+ST7`3# zaIPerGx>9rw&JBE(9x-W{`xy7!syT8t`l+QFE1Z@qZ@tM9T+>xe0i{Y%*TA?3q${_ zNYB^+^VNVC`YYUb`<+KQzR$^J*!hfZ*laeV8Sx&Qn}xpv37i1+%dQRb*<~U@?w!=A zyRai$82xT~HA{aGG#U-<=IRPK0OvnJck(4WTAfdA|1b0>sXw6^gzYnyv*5Qh8pfS8 z*v){s+!Y0o(l$^3A1+DB%QZ{YMkwwH3Wg~-4gj1%HfswKA!2PC+ndVd(uR{0+svG7 zI0+fOEEqoZ5RO6FjFwAlAPwYZz&+60Cf*_LvoeXXzDzSnK(pb|tl@(}s3=P?Bp2ebgqhyJVn)%e=^wez=M zGusBNwt-UHz@68MehZD3(5Q(apd$utvQ}8DdDstC? Vt0Qa5I)Tq*p+lv*kL4)Ce*u8_IwSx9 literal 0 HcmV?d00001 diff --git a/callbacks/__pycache__/system.cpython-311.pyc b/callbacks/__pycache__/system.cpython-311.pyc index 6e7f879ba96e4ccb4331bed4a34b068e8323a205..44ffdb7e4dfe50d1e2eba70d08bb89c6adcd6dd1 100644 GIT binary patch delta 191 zcmbO%+a=4poR^o20SICO^fHV#@|H6(GEQz~n#|5w#mc~t!nIkRnUhhGyH+5QA&;e; zp@KP*p`0;=r$iT|2LfssQ+OwPvdA)mNNHvkAg`509>SAkOW|F{#K5o`h#??_Zw^}x zLkj=on=Gn~0+auCLmo>x zLj`jrLpfs#Z;38Q4+PXOrtnSnWRYbAkVkrAN?*3rhvJj}KWUR<6#qTvJKy}5 zbNOb@t`2>k_g_|3iGY2P?mYH*wCr!D!{zpbmkHBiNtkz)=QGq*emkWC$27EyO?+=c z<9-?xm{|Tck@1y$CAt+qMNQ-3=Zg~N&y;+ZD69j0!zf{dxfbL_WlC|ldqG|FPx&bk z$U(w9X9;V%BtQl94Vwxu?^!YxWIn(UlK{hvPLK}y4@@XLU74pJQ5va8;$asxDo2}6 z3POv(r-O|5OEYvo|3FGcnKbQL4&0v-1|~BfQ}~Zkj!HZzzqK#86?>GJZEv`j#Vrq0 zSKRND4~2i8WjRv$RPLp8lK-rv#OppcaDG6H3T>$E z51{MrP|^dR4Cu1lrML;vF{Q2p)u1gpE~@IU+v= z%tGPQ>9mPgp)RvIYx<>yQt{L)C(IG>tnhW?3cs3U{B~SDEr90?^t;Uf&k{=BBaAKs ze_(TXMTJ6LhWcXZ1Wv^|sEKbFh66BI_@{7xHUFH23q43QpMlUh%)#k5FNJE{jPGiQ zf+D3FL~u)H7@{2!Q-`50TCZAlVaalq;5$=G-Kd1Kb!R$r03Wf!IpAO3fyE_ua-lS6 zPJ$2jVo8*LpKA4a$PU9Mp@xUwPQ7;gc}m+G<; IgR`-}0UtX|0RR91 delta 1509 zcmcgsOK1~87@pZpOm{cC*}PxvCb7k;NQuT8v9(yYQp5++h=*JxcH*O?jqIis1*w8y z4}ydr#G5xoL=mG!@M4>Tco0&kD0)y&J`OdaLhvBYq*`BXMG$A%Z@&5G|G$}?`Pj=n z6Wz)sSr!p(H^x^TeCa7F0ldE0+Qel!*d|6TS#E$YZjWKUI23W(E@FhP(08^Gu6Vb} z-;iy{;sO3!a0DA6Hil7)R=lQe$A}=p=fpuNE!DXtXCIW)GDaNgMZ|gvktG*7Dofwl zmR5-E6iPct14WhK6j~v?W>Y&$!}tQmu9BDUv*2Lqplz7rK4Ew!+`}DkNz_~g`Kt!) zh)4t?!BbJkc8EwP-OBg57G!viTMN$8BdHyOEHA^mAt9%*0HYMN~N=$>BD z=LCB0aKm~fMU~DdTEr&Ds%T+=`$`nvDs7{}PdV>c$%jSi=2!h_N8V5+)L-dKPxoaQ z3H48T$h3!4JmhgWT6HY6%IIIOwb)nvw!2!bws<~Y`>4VW!Jk<(SX+>&}_61If9 ztea{5{?b_Z1&24nfEG&33NqFD&392|GB-q{QA4Hu6Nwx$Gy8Jcy(5Q*j2;>}0%PIp za3Dg|6;8v?Qh4*I`Gm`xFcQtTu@&lhMh9aq22AKvL+4 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 0000000000000000000000000000000000000000..31ff33927502d96f857828b1055cd7a49d42ff83 GIT binary patch literal 2837 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cb9aeb4b87a9526a3c311b5c725c97803c3b725e GIT binary patch literal 5187 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�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 literal 0 HcmV?d00001 diff --git a/static/frontend/public/icons/mini/settings.png b/static/frontend/public/icons/mini/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..9ec119deec85864882bfc7fad52d557131ce5dc3 GIT binary patch literal 6448 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 KMNUMnLSTYHv2`{8 literal 0 HcmV?d00001 diff --git a/static/frontend/public/icons/mini/wifi.png b/static/frontend/public/icons/mini/wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..4c931f92cbec3855348b2850d6eece23f4d7fc76 GIT binary patch literal 3434 zcmZu!c{J1y_x=o7vn2aoDKQvZ43VWQDbbk05TWd39qU*_qC(lqIt{{%vQ}UFM1{dv zvwSnMHcR$x?7iOqe(yQIbMAAWbMC$8{`1^>&$%ggEljyN#W(=~;5NHyWOc%E{{=hi ziSN{N={+HifSY!~0Kn7vUx2#&D_l>4q9|h|3hwWY3Udi^1H!_>AYOhx!4F*m+#voz z9{DS}VgSI!Ff%elgcmH6qq7j((Ov5d25IVz+oYREDC{xsy1dErPjoZfcH?(l>JYo& zd8zy}*@V8wKj3iw{L#MM6Tgq}dRw=1LdHv@`egcW=1{y+29JR8a!L&W%b4X?rY zQY2)kswP|P2w9yA$zrHFf~#{!(YMQ>m)G=j;|tL-)9(i<=b}xv9gPm|MyNmbwI<%{ z>59#~p8D~)ou$l$Im2w_xB?d}rd7^<*oG}T9X{3eyzyWA zHiUUr?0F_kg`%a91*^6tw}(s+gv#f__y~=6QqaW{O;V`!wBy~_B=Si3=p#Ta*$dy| zbQQ0-&teFv{>-h%^OL2>VEbi-I6BI&m@APycsyzi*qD#)JZMyrT)dSrz*Jj<-bhkQ z?0$%K-#%Eb-G;VZYVP!-F~4QQu!X#n?G;wo7epv8b}D=IdmGH0FA4I$Dt**=PRw2X zEz8V@l&fVDN{%l)IkjY~^set*1uJ|tvMpsTResLuEfr@Tw97y&{vr70Eh@Fy6EisR z4ZeQ&R0cQfm6AW$0ct&S+PL51=W<7brdS^uKt)Xb90#QjjFniqqHb)33JoAie(`JR zQ)}~5aC%R=KQ{%}I zuKQ7kQKt=f>E<*!6923XrnkDOL{9E-n2%gR{jS3E4MLO9wR#?$ClkbDLNeSHm|@ky zwY^x|Mrek%J2j`bopK(49-!ka_h9}4)y(Nv!RbwV)bBnSZ-Fk{#Ih+hF@?D-_yzS6 zQ!dcOqBW&1vT3caCxqiP;1xT%iAAYXozC__$nJc0?-w`z#pp37)HOs?6K`HryEmi| zZ+S}P>$$c5u%MMWsjuNmbJ{?!PO_zyOlwUV4WAXn??J`ocdb7cDPEuV zs|!e~D2)q_R&0e^Q$i^7K48aMEKv2jYWbU0py=A=Y@REe%uR;Rqo5zvcUmkp#5*>O zW)Vr^f*(4{eMC;h7~%6R9%X*v91|^`Ce2zpFPxtVwP!=VMwM|Q=jn(Att*+G~I+mdW`o}-jLP7$p`?TBGnrlxJTZ#I#$hWFd#e1>_${hi=A>+ox&UJ?^Vstv@ z*apZsna!6E)&pmJSD9;XeQhPR(T_eh+{jFWJJ|eauA}m=`}*!rpWpopxblqHNGg6{ zgRCJOM6ds&UHI&1FgRiCbtHe1HbJdLD)Hy#Q%o|K)$GFrr9qG_n@rn_*!tg$uD>I2 zxQ+2i6+G@^xUQ^Bx8& zY^`c930IDDeU;>Aof9d4F~C%fGTqSbpKG%gIY=EfTIWgnqbqo(^;TqNk}b@lBEULf zt(@lD2MiYH6CoQZo1|TgBFg#Ssb2t7jb3!r98D zo9k>~iEd70m}ZRrTps{#T<+5%gs1nfnz%)RSM^{KXd9){`XO$t8nc^2Zm;}dvJ5U3 zn&y@qxUhkmKC;WcW)Hf(NWL6U@P@F4>`kQ?T7adUdgFJPvd(|)yTxcuKbWi#EI?^C zfmG|Y2wm|VWLngrA=|m^u<9E~_9b+}oGk3PTWZ8#m*(3mW1=k_9=aN9>zWUytvL8f zEd@iIxy5>AZ$G)0?Ws9)TF1c;bGI4pL0c9Ii_9Skt+^k?Yqjy7mZ1>^3YbX#ffd~TP77X zsPn==yfUb#=3vrP2zkk>O)0x@Ex#sTwE79Vfo$VJiHVF8zgvvtRK0!sjo3qQ;f!sOv0QXrd}3=j6)jxIoqOnz-VyugnD6`?}>x7>XFX zJrA{V2QrNrbp&$E#rBb310&bNldL3vBGagcNjh`YiRA8SZ|$UB&~n6&Sfx3we6O|_ zya5)a7nOHpQ=wpcsB%k_r)$soe}&|;R}4dJCR#_I=sE;ogAa>VyWp2ypfxSg)Op#t zDrO5#OcY1BUDh~aLC|^=oVMAHZR)KMRgo=X3n-XPtI*0sl-_xv?U2N8ykG=-N4x1{ zXl~dp`Vhj4{+)T3IfEt_V_`fFNb2Q~71u7#9V&{Y=)#v3$!|(SF-fNlAc$RwwZP*2 zQ{5$M3WZ_U9Lax@2bpSg?V2V;>Ph8C`-&a*s;-aQ4#FcSYi1^UYT)2?kEUEE>Jkca zT*LbjEOZM{OPLi-)iu~Y*ipvQ!wRF8ioK6SaGb|;U4 ztqyvyI!`<*yN?ji^66f}qe5-Qam}ikNMm-h3jF2jIdeP~Av%N~ncM~c$`en{V|8}z z@QmhM71u8(_S0ILTc&@Di8Pv(pc1g30sd<_^0;mmvK>QPAFY#-$KpIt%9d}fH3SE} z{CGBHI)k41Iohw^w)bpvx=CT#g`nE5v&!#+Roly_#jX39YJ9C`QGsh}{1Y?X4$L2; zeX4By>oQFCKxe8^j~uH8NKBce;lY(wZH3} zkET6taZOkThmWtkV%X2GP~z9gh?0Ga3zRkqUt4LM9k8Pde($k#DJ1&5bUw^7-3C8a zu)GnYOSzv)e^Q%cU0R;g{7K`Q_-oQPA!dD%5RhAl2Sl@8RQX#Qt0=v%>lm3GEAmJ* zUZ-iZ)?Dd;lXBC!4wV86&Lq5fF2Ntpfh-_4GRA%$xBe2yHaB&Vz2G$?VDkHRjNyy= zf6(Oz11=0(__d09R_=Y5fgCrx|FIC2(?=wX$`sW2@(M&CP4I##Ah-DW^_ic;Pt~Ik ze=wYc;je>kJTI#s$FRPx8K@3By-~{7h}RK!ls1WV(UN1zpov_y_-5CmRXmPgYrr+` zIEUlBM%XY=m@Y3$GTZXX5SyBMVQ?`AwmNcX6oeesh{OEYJ z{}f-$y_8Qz#JVE-imsnyfW| zKcY_mjwBG*7_Bz=<{4>f({n1UtCkp=xU#%AQC3;Ckg%fcdnZCO;L2^DA { + 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