From 93be7554bb294d629219c1b58877cce53163f075 Mon Sep 17 00:00:00 2001 From: Upstream Data Date: Mon, 25 Nov 2024 16:03:45 -0700 Subject: [PATCH] Use get request for logs instead of websocket --- README.md | 1 - docs/development.md | 1 - goosebit/__init__.py | 3 +- goosebit/api/v1/devices/device/responses.py | 1 + goosebit/api/v1/devices/device/routes.py | 2 +- goosebit/device_manager.py | 40 -------------------- goosebit/realtime/__init__.py | 1 - goosebit/realtime/logs.py | 41 --------------------- goosebit/realtime/routes.py | 13 ------- goosebit/ui/bff/devices/device/__init__.py | 1 + goosebit/ui/bff/devices/device/routes.py | 16 ++++++++ goosebit/ui/bff/devices/routes.py | 2 + goosebit/ui/static/js/logs.js | 29 ++++----------- pyproject.toml | 1 - 14 files changed, 29 insertions(+), 123 deletions(-) delete mode 100644 goosebit/realtime/__init__.py delete mode 100644 goosebit/realtime/logs.py delete mode 100644 goosebit/realtime/routes.py create mode 100644 goosebit/ui/bff/devices/device/__init__.py create mode 100644 goosebit/ui/bff/devices/device/routes.py diff --git a/README.md b/README.md index 1e6e9239..445971fa 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,6 @@ The structure of gooseBit is as follows: - `nav`: Navbar handler. - `updater`: DDI API handler and device update manager. - `updates`: SWUpdate file parsing. -- `realtime`: Realtime API functionality with websockets. - `auth`: Authentication functions and permission handling. - `models`: Database models. - `db`: Database config and initialization. diff --git a/docs/development.md b/docs/development.md index e954451e..6a365365 100644 --- a/docs/development.md +++ b/docs/development.md @@ -71,7 +71,6 @@ The structure of gooseBit is as follows: - `nav`: Navbar handler. - `updater`: DDI API handler and device update manager. - `updates`: SWUpdate file parsing. -- `realtime`: Realtime API functionality with websockets. - `auth`: Authentication functions and permission handling. - `models`: Database models. - `db`: Database config and initialization. diff --git a/goosebit/__init__.py b/goosebit/__init__.py index ee64a28a..ccfeff7b 100644 --- a/goosebit/__init__.py +++ b/goosebit/__init__.py @@ -13,7 +13,7 @@ from starlette.exceptions import HTTPException as StarletteHTTPException from tortoise.exceptions import ValidationError -from goosebit import api, db, realtime, ui, updater +from goosebit import api, db, ui, updater from goosebit.api.telemetry import metrics from goosebit.auth import get_user_from_request, login_user, redirect_if_authenticated from goosebit.settings import config @@ -57,7 +57,6 @@ async def lifespan(_: FastAPI): app.include_router(updater.router) app.include_router(ui.router) app.include_router(api.router) -app.include_router(realtime.router) app.mount("/static", static, name="static") Instrumentor.instrument_app(app) diff --git a/goosebit/api/v1/devices/device/responses.py b/goosebit/api/v1/devices/device/responses.py index 1b03945f..b252936d 100644 --- a/goosebit/api/v1/devices/device/responses.py +++ b/goosebit/api/v1/devices/device/responses.py @@ -7,6 +7,7 @@ class DeviceLogResponse(BaseModel): log: str | None + progress: int | None class DeviceResponse(DeviceSchema): diff --git a/goosebit/api/v1/devices/device/routes.py b/goosebit/api/v1/devices/device/routes.py index 3ef4c33c..b0cc4c46 100644 --- a/goosebit/api/v1/devices/device/routes.py +++ b/goosebit/api/v1/devices/device/routes.py @@ -29,4 +29,4 @@ async def device_get(_: Request, device: Device = Depends(get_device)) -> Device async def device_logs(_: Request, device: Device = Depends(get_device)) -> DeviceLogResponse: if device is None: raise HTTPException(404) - return DeviceLogResponse(log=device.last_log) + return DeviceLogResponse(log=device.last_log, progress=device.progress) diff --git a/goosebit/device_manager.py b/goosebit/device_manager.py index 278af082..1ca75068 100644 --- a/goosebit/device_manager.py +++ b/goosebit/device_manager.py @@ -1,10 +1,7 @@ from __future__ import annotations -import asyncio import re -from contextlib import asynccontextmanager from enum import StrEnum -from typing import Awaitable, Callable from aiocache import caches @@ -36,7 +33,6 @@ class HandlingType(StrEnum): class DeviceManager: _hardware_default = None - _log_subscriptions: dict[Device, list[Callable]] = {} @staticmethod async def get_device(dev_id: str) -> Device: @@ -147,8 +143,6 @@ async def deployment_action_start(device: Device): device.progress = 0 await DeviceManager.save_device(device, update_fields=["last_log", "progress"]) - await DeviceManager.publish_log(device, None) - @staticmethod async def deployment_action_success(device: Device): device.progress = 100 @@ -206,39 +200,6 @@ async def get_update(device: Device) -> tuple[HandlingType, Software | None]: return handling_type, software - @staticmethod - @asynccontextmanager - async def subscribe_log(device: Device, callback: Callable[[str], Awaitable[None]]): - # do not modify, breaks when combined - subscribers = DeviceManager.get_log_subscribers(device) - subscribers.append(callback) - DeviceManager.set_log_subscribers(device, subscribers) - - if device is not None: - await callback(device.last_log) - try: - yield - except asyncio.CancelledError: - pass - finally: - # do not modify, breaks when combined - subscribers = DeviceManager.get_log_subscribers(device) - subscribers.remove(callback) - DeviceManager.set_log_subscribers(device, subscribers) - - @staticmethod - def get_log_subscribers(device: Device): - return DeviceManager._log_subscriptions.get(device, []) - - @staticmethod - def set_log_subscribers(device: Device, value: list): - DeviceManager._log_subscriptions[device] = value - - @staticmethod - async def publish_log(device: Device, log_data: str | None): - for cb in DeviceManager.get_log_subscribers(device): - await cb(log_data) - @staticmethod async def update_log(device: Device, log_data: str) -> None: if log_data is None: @@ -253,7 +214,6 @@ async def update_log(device: Device, log_data: str) -> None: device.progress = matches[-1] device.last_log += f"{log_data}\n" - await DeviceManager.publish_log(device, f"{log_data}\n") await DeviceManager.save_device(device, update_fields=["progress", "last_log"]) diff --git a/goosebit/realtime/__init__.py b/goosebit/realtime/__init__.py deleted file mode 100644 index 2c102012..00000000 --- a/goosebit/realtime/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .routes import router # noqa: F401 diff --git a/goosebit/realtime/logs.py b/goosebit/realtime/logs.py deleted file mode 100644 index 287ad900..00000000 --- a/goosebit/realtime/logs.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, Security -from fastapi.websockets import WebSocket, WebSocketDisconnect -from pydantic import BaseModel -from websockets.exceptions import ConnectionClosed - -from goosebit.auth import validate_user_permissions -from goosebit.device_manager import DeviceManager, get_device - -router = APIRouter(prefix="/logs") - - -class RealtimeLogModel(BaseModel): - log: str | None - progress: int | None - clear: bool = False - - -@router.websocket( - "/{dev_id}", - dependencies=[Security(validate_user_permissions, scopes=["device.read"])], -) -async def device_logs(websocket: WebSocket, dev_id: str): - await websocket.accept() - - device = await get_device(dev_id) - - async def callback(log_update): - data = RealtimeLogModel(log=log_update, progress=device.progress) - if log_update is None: - data.clear = True - data.log = "" - await websocket.send_json(dict(data)) - - async with DeviceManager.subscribe_log(device, callback): - try: - while True: - await websocket.receive() - except (WebSocketDisconnect, ConnectionClosed, RuntimeError): - pass diff --git a/goosebit/realtime/routes.py b/goosebit/realtime/routes.py deleted file mode 100644 index eab0a24f..00000000 --- a/goosebit/realtime/routes.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import APIRouter, Depends - -from goosebit.auth import validate_current_user - -from . import logs - -router = APIRouter( - prefix="/realtime", - dependencies=[Depends(validate_current_user)], - tags=["realtime"], -) - -router.include_router(logs.router) diff --git a/goosebit/ui/bff/devices/device/__init__.py b/goosebit/ui/bff/devices/device/__init__.py new file mode 100644 index 00000000..6096e388 --- /dev/null +++ b/goosebit/ui/bff/devices/device/__init__.py @@ -0,0 +1 @@ +from .routes import router # noqa : F401 diff --git a/goosebit/ui/bff/devices/device/routes.py b/goosebit/ui/bff/devices/device/routes.py new file mode 100644 index 00000000..54f73ce6 --- /dev/null +++ b/goosebit/ui/bff/devices/device/routes.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from fastapi import APIRouter, Security + +from goosebit.api.v1.devices.device import routes +from goosebit.auth import validate_user_permissions + +router = APIRouter(prefix="/{dev_id}") + +router.add_api_route( + "/log", + routes.device_logs, + methods=["GET"], + dependencies=[Security(validate_user_permissions, scopes=["device.read"])], + name="bff_device_logs", +) diff --git a/goosebit/ui/bff/devices/routes.py b/goosebit/ui/bff/devices/routes.py index bb8d2e94..b802487e 100644 --- a/goosebit/ui/bff/devices/routes.py +++ b/goosebit/ui/bff/devices/routes.py @@ -19,10 +19,12 @@ from goosebit.ui.bff.common.util import parse_datatables_query from ..common.responses import DTColumnDescription, DTColumns +from . import device from .requests import DevicesPatchRequest from .responses import BFFDeviceResponse router = APIRouter(prefix="/devices") +router.include_router(device.router) @router.get( diff --git a/goosebit/ui/static/js/logs.js b/goosebit/ui/static/js/logs.js index 963c7c2c..7808aa64 100644 --- a/goosebit/ui/static/js/logs.js +++ b/goosebit/ui/static/js/logs.js @@ -1,25 +1,10 @@ -document.addEventListener("DOMContentLoaded", () => { - const logs_ws = create_ws(`/realtime/logs/${device}`); +document.addEventListener("DOMContentLoaded", async () => { + const res = await get_request(`/ui/bff/devices/${device}/log`); - logs_ws.addEventListener("message", (event) => { - const res = JSON.parse(event.data); + const logElem = document.getElementById("device-log"); + logElem.textContent = res.log; - const logElem = document.getElementById("device-log"); - if (res.clear) { - logElem.textContent = ""; - } - logElem.textContent += res.log; - - const progressElem = document.getElementById("install-progress"); - progressElem.style.width = `${res.progress}%`; - progressElem.innerHTML = `${res.progress}%`; - }); + const progressElem = document.getElementById("install-progress"); + progressElem.style.width = `${res.progress}%`; + progressElem.innerHTML = `${res.progress}%`; }); - -function create_ws(s) { - const l = window.location; - const protocol = l.protocol === "https:" ? "wss://" : "ws://"; - const port = l.port !== "80" || l.port !== "443" ? l.port : ""; - const url = `${protocol}${l.hostname}:${port}${s}`; - return new WebSocket(url); -} diff --git a/pyproject.toml b/pyproject.toml index 9a570fae..2409c5f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ jinja2 = "^3.1.4" itsdangerous = "^2.2.0" tortoise-orm = "^0.22.1" aerich = "^0.7.2" -websockets = "^14.0" argon2-cffi = "^23.1.0" joserfc = "^1.0.0" semver = "^3.0.2"