From d90c139dcefb7eb09eccbbe8909ddc480b803310 Mon Sep 17 00:00:00 2001 From: gx Date: Thu, 21 May 2026 05:27:27 +0100 Subject: [PATCH] =?UTF-8?q?controller:=20Phase=206+=20=E2=80=94=20Web=20UI?= =?UTF-8?q?=20mini=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static HTML/JS dashboard в cuda_grid_controller/static/index.html, mounted на /ui и / FastAPI endpoints. Vanilla JS + HLS.js (CDN) для video player. Controls: Audio source — buttons из /audio/{instance} list, switch через POST Intercom — Start (music↓) / End (restore) Snapshot — opens PNG в new tab Manual overlay — form для rect/text/dim/icon types Chat — placeholder (info-toast про mosquitto_pub) State — refresh каждые 2 sec, shows layout/overlays_count + raw list Player consumes HLS на http://server:8888/live/index.m3u8 (mediamtx). TV не нужен — браузер на любом устройстве в LAN. Co-Authored-By: Claude Opus 4.7 --- .../cuda_grid_controller/dynamic_overlays.py | 5 +- controller/cuda_grid_controller/http_api.py | 13 +- .../cuda_grid_controller/static/index.html | 274 ++++++++++++++++++ 3 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 controller/cuda_grid_controller/static/index.html diff --git a/controller/cuda_grid_controller/dynamic_overlays.py b/controller/cuda_grid_controller/dynamic_overlays.py index d799b2b..dbc182a 100644 --- a/controller/cuda_grid_controller/dynamic_overlays.py +++ b/controller/cuda_grid_controller/dynamic_overlays.py @@ -45,7 +45,8 @@ class ChartCfg(BaseModel): default=None, description="MQTT topic с numeric payload. Если None — fake sine wave для demo.", ) - refresh_sec: float = Field(default=2.0, ge=0.5, le=60.0) + refresh_sec: float = Field(default=0.5, ge=0.05, le=60.0, + description="0.05 = 20 Hz max; 0.5 default = 2 Hz") max_points: int = Field(default=60, ge=10, le=600) line_color: tuple[int, int, int] = (0, 255, 128) bg_color: tuple[int, int, int, int] = (0, 0, 0, 180) @@ -248,7 +249,7 @@ class DynamicRenderer: await self._register_overlay(cfg.id, cfg.target_instance, cfg.cell, cfg.x, cfg.y, cfg.opacity, cfg.z_order) registered = True - await asyncio.sleep(0.5) # tight check для chat reactivity + await asyncio.sleep(0.1) # tight check для chat reactivity (10 Hz) except asyncio.CancelledError: raise except Exception as e: diff --git a/controller/cuda_grid_controller/http_api.py b/controller/cuda_grid_controller/http_api.py index 8bfa20f..cb59e08 100644 --- a/controller/cuda_grid_controller/http_api.py +++ b/controller/cuda_grid_controller/http_api.py @@ -3,11 +3,13 @@ from __future__ import annotations import asyncio +from pathlib import Path from typing import Any import structlog from fastapi import Body, FastAPI, HTTPException -from fastapi.responses import Response +from fastapi.responses import FileResponse, Response +from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, TypeAdapter from .config import Config @@ -36,6 +38,15 @@ def create_app( description="Control plane для vf_cuda_grid FFmpeg filter", ) + # Static UI — http://controller:8080/ui/ + static_dir = Path(__file__).parent / "static" + if static_dir.exists(): + app.mount("/ui", StaticFiles(directory=str(static_dir), html=True), name="ui") + + @app.get("/") + async def root_redirect(): + return FileResponse(static_dir / "index.html") + def _check_instance(name: str): inst = next((i for i in cfg.instances if i.name == name), None) if inst is None: diff --git a/controller/cuda_grid_controller/static/index.html b/controller/cuda_grid_controller/static/index.html new file mode 100644 index 0000000..b75db12 --- /dev/null +++ b/controller/cuda_grid_controller/static/index.html @@ -0,0 +1,274 @@ + + + + +cuda-grid control + + + + + +
+

cuda-grid control

+
connecting…
+
+ +
+
+ +
+ +
+ +
+

Audio source

+
+
+ + +
+

Intercom (ducking)

+
+ + +
+
+ + +
+

Snapshot

+
+ +
+
+ + +
+

Manual overlay

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + +
+

Chat message

+
+ + +
+
+ + +
+

State

+
+
overlays raw +
+
+
+
+ +
+ + + +