diff --git a/__pycache__/web_server.cpython-311.pyc b/__pycache__/web_server.cpython-311.pyc index 7fc74e7..47f615c 100644 Binary files a/__pycache__/web_server.cpython-311.pyc and b/__pycache__/web_server.cpython-311.pyc differ diff --git a/callbacks/__pycache__/media.cpython-311.pyc b/callbacks/__pycache__/media.cpython-311.pyc index 20cd019..6f9f915 100644 Binary files a/callbacks/__pycache__/media.cpython-311.pyc and b/callbacks/__pycache__/media.cpython-311.pyc differ diff --git a/callbacks/__pycache__/screen.cpython-311.pyc b/callbacks/__pycache__/screen.cpython-311.pyc index be194e6..b22c93d 100644 Binary files a/callbacks/__pycache__/screen.cpython-311.pyc and b/callbacks/__pycache__/screen.cpython-311.pyc differ diff --git a/callbacks/__pycache__/v2ray.cpython-311.pyc b/callbacks/__pycache__/v2ray.cpython-311.pyc index a0ec7ac..c206cc7 100644 Binary files a/callbacks/__pycache__/v2ray.cpython-311.pyc and b/callbacks/__pycache__/v2ray.cpython-311.pyc differ diff --git a/callbacks/media.py b/callbacks/media.py index dc82a24..cf7055f 100644 --- a/callbacks/media.py +++ b/callbacks/media.py @@ -5,6 +5,7 @@ import comtypes import ctypes from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume from ctypes import cast, POINTER +from comtypes import CLSCTX_ALL from typing import Dict, Any from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager @@ -15,10 +16,25 @@ parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) if parent_dir not in sys.path: sys.path.append(parent_dir) +VOLUME = 0 +async def get_volume(args: Dict[str, Any]) -> Dict[str, Any]: + global VOLUME + + # Get default audio device + devices = AudioUtilities.GetSpeakers() + interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) + volume = cast(interface, POINTER(IAudioEndpointVolume)) + + # Get current volume level (returns a float between 0.0 and 1.0) + current_volume = volume.GetMasterVolumeLevelScalar() + + return {"value": round(current_volume*100)} + async def set_volume(args: Dict[str, Any]) -> Dict[str, Any]: + global VOLUME """ Sets the Windows system volume using the Core Audio API. @@ -36,12 +52,13 @@ async def set_volume(args: Dict[str, Any]) -> Dict[str, Any]: comtypes.CoInitialize() # Get volume level from args - level = args.get('level', 0.5) # Default to 50% if not specified + level = args.get('level', 50) # Default to 50% if not specified # Validate input level - if not isinstance(level, (int, float)) or not 0.0 <= level <= 1.0: - raise ValueError("Volume level must be a float between 0.0 and 1.0") - + if not isinstance(level, (int, float)) or not 0 <= level <= 100: + raise ValueError("Volume level must be a float between 0 and 100") + VOLUME = level + level = level/100 # Get audio endpoint interface devices = AudioUtilities.GetSpeakers() interface = devices.Activate(IAudioEndpointVolume._iid_, comtypes.CLSCTX_ALL, None) @@ -106,4 +123,16 @@ async def prev(args): # назад await current_session.try_skip_previous_async() else: - print("No media session is active.") \ No newline at end of file + print("No media session is active.") + +async def play_pause(args): + # Получаем менеджер медиасессий + sessions = await MediaManager.request_async() + current_session = sessions.get_current_session() + + if current_session: + await current_session.try_toggle_play_pause_async() + else: + print("No media session is active.") + + diff --git a/callbacks/screen.py b/callbacks/screen.py index 70cf0a8..e6559d2 100644 --- a/callbacks/screen.py +++ b/callbacks/screen.py @@ -1,5 +1,7 @@ import monitorcontrol +BRIGHTNESS = 10 + def get_monitors(): """Retrieve a list of connected monitors.""" return monitorcontrol.get_monitors() @@ -13,9 +15,16 @@ def set_brightness(monitor, brightness): print(f"Set brightness to {brightness}%") -def chenge_brightness(args): +def get_brightness(args): + global BRIGHTNESS + return {"value":BRIGHTNESS} + + +def change_brightness(args): + global BRIGHTNESS try: level = args.get('level', 10) + BRIGHTNESS = level monitors = get_monitors() if not monitors: raise OSError(f"No DDC/CI compatible monitors found.") diff --git a/callbacks/v2ray.py b/callbacks/v2ray.py index 4efee22..1ec2970 100644 --- a/callbacks/v2ray.py +++ b/callbacks/v2ray.py @@ -62,7 +62,6 @@ class V2rayAController: async with self.session.post(url, headers=headers, json=data) as response: if response.status == 200: print("Прокси успешно включен") - V2RAY_VPN_ENABLED = True await show_notification({"title":"✅ V2Ray proxy","message":"Прокси успешно включен"}) return True else: @@ -75,7 +74,7 @@ class V2rayAController: async def disable_proxy(self): """Выключение прокси""" - global V2RAY_VPN_ENABLED + if not self.token: print("Не выполнен вход. Пожалуйста, сначала выполните аутентификацию") return False @@ -88,7 +87,6 @@ class V2rayAController: async with self.session.delete(url, headers=headers, json=data) as response: if response.status == 200: print("Прокси успешно выключен") - V2RAY_VPN_ENABLED = False await show_notification({"title":"🔴 V2Ray proxy","message":"Прокси успешно выключен"}) return True else: @@ -105,6 +103,7 @@ class V2rayAController: await self.session.close() async def enable_vpn(args): + global V2RAY_VPN_ENABLED # Инициализация контроллера config = await read_config("v2ray.yaml") if config.get("username") and config.get("password"): @@ -119,9 +118,11 @@ async def enable_vpn(args): # Закрытие сессии await controller.close() + V2RAY_VPN_ENABLED = True else: raise OSError("Config unset") async def disable_vpn(args): + global V2RAY_VPN_ENABLED # Инициализация контроллера config = await read_config("v2ray.yaml") if config.get("username") and config.get("password"): @@ -136,6 +137,7 @@ async def disable_vpn(args): # Закрытие сессии await controller.close() + V2RAY_VPN_ENABLED = False else: raise OSError("Config unset") @@ -143,5 +145,5 @@ async def disable_vpn(args): async def is_vpn_enabled(args): global V2RAY_VPN_ENABLED return { - "vpn_enabled": V2RAY_VPN_ENABLED + "value": V2RAY_VPN_ENABLED } \ No newline at end of file diff --git a/config/frontend.yaml b/config/frontend.yaml index 76e27ed..95a8f3f 100644 --- a/config/frontend.yaml +++ b/config/frontend.yaml @@ -24,21 +24,37 @@ sliders: - value: 0 label: "Яркость экрана" - action: "screen.chenge_brightness" + action: "screen.change_brightness" + get_state: "screen.get_brightness" - value: 0 label: "Громкость звука" action: "media.set_volume" + get_state: "media.get_volume" mini_icon: "/icons/mini/settings.png" +- type: "multi" + label: "Медиа" + mini_icon: "/icons/sound.svg" + buttons: + - action: "media.prev" + imageUrl: "/icons/prev_arrow.svg" + action_args: {} + - action: "media.play_pause" + imageUrl: "/icons/play_pause.svg" + action_args: {} + - action: "media.next" + imageUrl: "/icons/next_arrow.svg" + action_args: {} + - type: "break" # Разделитель label: "Сайты" - type: "image" # Кнопка с картинкой - imageUrl: "/icons/telegram-logo-svgrepo-com.svg" - label: "Telegram" + imageUrl: "/icons/yandex.svg" + label: "Яндекс" action: "web.open_url" action_args: - url: "https://ya.ru " + url: "https://ya.ru" mini_icon: "/icons/mini/arrow-up-right-from-square.png" - type: "image" # Кнопка с картинкой @@ -46,12 +62,12 @@ label: "Cloud" action: "web.open_url" action_args: - url: "https://cloud.viadev.su " + url: "https://cloud.viadev.su" mini_icon: "/icons/mini/arrow-up-right-from-square.png" - type: "break" # Разделитель label: "Приложения" - + - type: "image" # Кнопка с картинкой imageUrl: "/icons/steam-svgrepo-com.svg" label: "Steam" @@ -60,6 +76,13 @@ path: "C:/Users/Slava/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Steam/Steam.lnk" mini_icon: "/icons/mini/arrow-up-right-from-square.png" +- type: "image" # Кнопка с картинкой + imageUrl: "/icons/vscode.svg" + label: "VS Code" + action: "app.run_app" + action_args: + path: "C:/Users/Slava/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Visual Studio Code/Visual Studio Code.lnk" + mini_icon: "/icons/mini/arrow-up-right-from-square.png" - type: "break" # Разделитель label: "Управление ПК" @@ -67,5 +90,8 @@ - type: "image" # Кнопка с картинкой imageUrl: "/icons/vpn.png" label: "VPN" + is_swtch: True + get_state: "v2ray.is_vpn_enabled" action: "v2ray.enable_vpn" - action_args: {} \ No newline at end of file + action_args: {} + diff --git a/static/frontend/public/icons/next_arrow.svg b/static/frontend/public/icons/next_arrow.svg new file mode 100644 index 0000000..cdb4340 --- /dev/null +++ b/static/frontend/public/icons/next_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/frontend/public/icons/play_pause.svg b/static/frontend/public/icons/play_pause.svg new file mode 100644 index 0000000..9ac1dd9 --- /dev/null +++ b/static/frontend/public/icons/play_pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/frontend/public/icons/prev_arrow.svg b/static/frontend/public/icons/prev_arrow.svg new file mode 100644 index 0000000..420e169 --- /dev/null +++ b/static/frontend/public/icons/prev_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/frontend/public/icons/sound.svg b/static/frontend/public/icons/sound.svg new file mode 100644 index 0000000..6ca099d --- /dev/null +++ b/static/frontend/public/icons/sound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/frontend/public/icons/vscode.svg b/static/frontend/public/icons/vscode.svg new file mode 100644 index 0000000..c453e63 --- /dev/null +++ b/static/frontend/public/icons/vscode.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/frontend/public/icons/yandex.svg b/static/frontend/public/icons/yandex.svg new file mode 100644 index 0000000..d2c8d2e --- /dev/null +++ b/static/frontend/public/icons/yandex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/frontend/src/Api.jsx b/static/frontend/src/Api.jsx index a83f2ff..0db4942 100644 --- a/static/frontend/src/Api.jsx +++ b/static/frontend/src/Api.jsx @@ -26,7 +26,8 @@ export const createSha256Hash = async (input) => { export const sendRequest = async (action,args) => { try { const TS = Math.round(Date.now()/1000); - const hash = await createSha256Hash(window.localStorage.auth+JSON.stringify(args)+TS); + const AUTH = window.localStorage.auth || window.auth; + const hash = await createSha256Hash(AUTH+JSON.stringify(args)+TS); const response = await axios.post("http://"+window.location.hostname+":"+import.meta.env.VITE_API_PORT+"/action/"+action, {args:args,hash:hash+"."+TS}, { timeout: 4000, headers: { diff --git a/static/frontend/src/App.css b/static/frontend/src/App.css index 938c310..e19e6a6 100644 --- a/static/frontend/src/App.css +++ b/static/frontend/src/App.css @@ -97,4 +97,19 @@ input[type="range"] { .gauge-tile-content { justify-content: flex-start; align-items: flex-start; +} +@keyframes press { + 0% { + transform: scale(1); + } + 50% { + transform: scale(0.8); + } + 100% { + transform: scale(1); + } +} + +.click-button:active { + animation: press 0.5s ease-out; } \ No newline at end of file diff --git a/static/frontend/src/App.jsx b/static/frontend/src/App.jsx index 657362b..7e49086 100644 --- a/static/frontend/src/App.jsx +++ b/static/frontend/src/App.jsx @@ -13,7 +13,7 @@ const TextTile = ({ label }) => ( const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
- {label} + {label} {label}
); @@ -62,7 +62,7 @@ const GaugeTile = ({ gauges }) => ( ); -const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => { +const SliderTile = ({ sliders, tile, updateSliderValue, label }) => { return (
{label} @@ -78,8 +78,10 @@ const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => { min="0" max="100" value={s.value} - onChange={(e) => updateSliderValue(tileId, index, e.target.value)} + onChange={(e) => updateSliderValue(tile, index, e.target.value)} className="w-full" + onMouseUp={(e) => { sendSlider(tile, index, e.target.value) }} + onTouchEnd={(e) => { sendSlider(tile, index, e.target.value) }} />
))} @@ -88,12 +90,38 @@ const SliderTile = ({ sliders, tileId, updateSliderValue, label }) => { ); }; + +const MultiButtonTile = ({ tile, onTileClick }) => { + return ( +
+ {tile.label} +
+ {tile.buttons.map((s, index) => ( + + ))} +
+
+ ); +}; + + + + const NumberTile = ({ value, label }) => (
{value} {label}
); +const sendSlider = async (tile, sliderIndex, newValue) => { + try { + await sendRequest(tile.sliders[sliderIndex].action, { level: Number(newValue) }); + } catch (e) { + console.error(e); + } +} const App = () => { @@ -109,14 +137,17 @@ const App = () => { - const updateSliderValue = (tileId, sliderIndex, newValue) => { + + + const updateSliderValue = (Tile, sliderIndex, newValue) => { setTiles(prevTiles => prevTiles.map(tile => { - if (tile.id === tileId && tile.type === "slider") { + if (tile.id === Tile.id && tile.type === "slider") { const newSliders = [...tile.sliders]; newSliders[sliderIndex] = { ...newSliders[sliderIndex], value: parseInt(newValue) + }; return { ...tile, sliders: newSliders }; } @@ -177,8 +208,37 @@ const App = () => { 'Content-Type': 'application/json' } }); + window.localStorage.auth = passw; + + for (var tile of response.data) { + if (tile.type == "slider") { + for (var slider_id in tile.sliders) { + if (tile.sliders[slider_id].get_state) { + try { + const data = await sendRequest(tile.sliders[slider_id].get_state, {}); + tile.sliders[slider_id].value = data.result.value; + } catch (e) { + console.error(e); + } + } + } + } + if (tile.is_swtch) { + try { + const data = await sendRequest(tile.get_state, {}); + tile.enabled = data.result.value; + if (tile.action.includes("enable") && tile.enabled) { + tile.enabled = true; + tile.action = tile.action.replace("enable", "disable"); + } + } catch (e) { + console.error(e); + } + } + } + setTimeout(() => { - window.localStorage.auth = passw; + setTiles(response.data); setPhase("dash"); updateSystemStats(); @@ -236,21 +296,26 @@ const App = () => { if (tile.action) { try { - setTileProps(tile.id, { loading: true }) + if (tile.id) { + 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; + if (tile.id) { + 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) + setTileProps(tile.id, tile) + } } catch (e) { - setTileProps(tile.id, { loading: false }) + if (tile.id) + setTileProps(tile.id, { loading: false }) } } } @@ -313,7 +378,7 @@ const App = () => {
{tiles.map(tile => ( -
+
{tile.type === "break" ? (
{tile.label}
) : ( @@ -337,8 +402,9 @@ const App = () => { /> )} {tile.type === "clock" && } + {tile.type === "multi" && } {tile.type === "gauge" && } - {tile.type === "slider" && } + {tile.type === "slider" && } {tile.type === "number" && } {tile.mini_icon && (
diff --git a/web_server.py b/web_server.py index d6b2d27..9cbbadf 100644 --- a/web_server.py +++ b/web_server.py @@ -15,7 +15,7 @@ from pathlib import Path from utils.get_primary_ip import get_primary_ip PASSWORD = "10010055" - +SKIP_HASH = False app = FastAPI() @@ -90,28 +90,29 @@ 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") + if(not SKIP_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") + # 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") + # 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() + # 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") + # Verify hash + if computed_hash != received_hash: + raise HTTPException(status_code=401, detail="Invalid hash") try: # Вызываем функцию из callback.py, передавая имя действия и аргументы