Add a browser client (web/) alongside the desktop GUI

A new HTML/CSS/JS client, added next to client/brewpi_gui.py rather
than replacing it, speaking the exact same WebSocket pub/sub protocol
- no server-side protocol changes needed. server/brewpi.py gains a
--http-port (default 8080) stdlib http.server.ThreadingHTTPServer,
in its own daemon thread and deliberately decoupled from the existing
asyncio WebSocket server, serving web/ statically.

v1 covers the Manual tab (direct heater/stirrer/controller control)
and the new Progress tab - the two most actionable surfaces, neither
needing a charting library. web/app.js ports the relevant logic from
brewpi_gui.py directly: components/sud.py's _build_step()/
_merge_defaults() for resolving the raw schedule doc, and the
StepPlate/_update_step_plates()/update_status_step_label() logic
behind the Progress tab and status line.

Deliberately deferred rather than stubbed (see web/TODO.md for the
full parity backlog against the Qt GUI): Sud control actions (Start/
Pause/Stop/Confirm), the Automatic tab's forecast plot and the Plot
tab's live strip charts, Sud file management, and auth (matching the
WebSocket server's own existing lack of it).

No new Python dependencies - functools/threading/http.server are all
stdlib.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qvu5giu7gvRCyEWzf2Vpx
This commit is contained in:
2026-06-24 20:12:01 +02:00
co-authored by Claude Sonnet 4.6
parent 2913b41cab
commit 787f02b8dc
7 changed files with 981 additions and 7 deletions
+17
View File
@@ -1,9 +1,12 @@
#!/usr/bin/env python3
import asyncio
import functools
import json
import os
import sys
import threading
import time
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from utils import ChangedFloat
from ws.message import MessageDispatcher
from ws.server.ws_server_multi_user import WsServerMultiUser
@@ -49,6 +52,13 @@ if __name__ == '__main__':
# otherwise - e.g. a much higher --sim-warp-factor for dev/testing.
parser.add_argument("--dt", type=float, default=1.0)
parser.add_argument("--sim-warp-factor", type=float, default=1.0)
# Serves web/ (the browser client - see web/README or the project
# README's "Browser client" section) so a plain browser can reach this
# same rig remotely, no desktop GUI required. A stdlib HTTP server in
# its own thread, deliberately independent of the asyncio WebSocket
# server below (ws/server/ws_server.py) - neither can affect the
# other's behavior or availability.
parser.add_argument("--http-port", type=int, default=8080)
args = parser.parse_args()
@@ -173,4 +183,11 @@ if __name__ == '__main__':
h_dispatcher = taskmgr.start()
h_server = server.listen("0.0.0.0", 8765)
asyncio.gather(h_dispatcher, h_server)
web_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, "web")
http_handler = functools.partial(SimpleHTTPRequestHandler, directory=web_dir)
http_server = ThreadingHTTPServer(("0.0.0.0", args.http_port), http_handler)
threading.Thread(target=http_server.serve_forever, daemon=True).start()
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
server.run_forever()
+2
View File
@@ -3,3 +3,5 @@ scipy
matplotlib
websockets==10.4
dpath
# web/ (the browser client) needs none of these - it's static HTML/JS
# served straight from the stdlib's http.server, no new dependency.