From ece4b01405b8edcf5cbe6367a2882433db1491b5 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 9 Aug 2026 04:12:34 +0200 Subject: feat(node): add config, daemon, and local web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.py: TOML + env var overrides, sane defaults. daemon.py: full startup sequence (keystore→hub→GEK→indexer→ server→UI), SIGINT/SIGTERM shutdown, calibrate-argon2 command. ui/app.py: FastAPI on localhost:18000, status+files JSON API, HTML status page (auto-refresh 10s). All bound to 127.0.0.1. Full suite: 29/29 tests. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../meshbay-node/src/meshbay_node/ui/__init__.py | 4 + packages/meshbay-node/src/meshbay_node/ui/app.py | 134 +++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 packages/meshbay-node/src/meshbay_node/ui/app.py (limited to 'packages/meshbay-node/src/meshbay_node/ui') diff --git a/packages/meshbay-node/src/meshbay_node/ui/__init__.py b/packages/meshbay-node/src/meshbay_node/ui/__init__.py index e69de29..a7ba872 100644 --- a/packages/meshbay-node/src/meshbay_node/ui/__init__.py +++ b/packages/meshbay-node/src/meshbay_node/ui/__init__.py @@ -0,0 +1,4 @@ +"""Local web UI served on localhost:18000.""" +from .app import create_ui_app + +__all__ = ["create_ui_app"] diff --git a/packages/meshbay-node/src/meshbay_node/ui/app.py b/packages/meshbay-node/src/meshbay_node/ui/app.py new file mode 100644 index 0000000..f8978d1 --- /dev/null +++ b/packages/meshbay-node/src/meshbay_node/ui/app.py @@ -0,0 +1,134 @@ +""" +MeshBay Node — local web UI (localhost:18000). + +Minimal FastAPI app providing: + GET / → status page (HTML) + GET /api/status → JSON status + GET /api/files → JSON file list from the group index + GET /api/config → JSON config summary (no secrets) + +Served only on 127.0.0.1 — not exposed to the network. +""" + +import logging +from typing import TYPE_CHECKING + +from fastapi import FastAPI +from fastapi.responses import HTMLResponse + +from meshbay_node import __version__ + +if TYPE_CHECKING: + from meshbay_node.indexer import GroupIndex + +log = logging.getLogger(__name__) + + +def create_ui_app(state: dict) -> FastAPI: + """ + Create the UI FastAPI app. + + state dict is updated by the daemon and read by UI endpoints: + state["status"] : str — "starting" | "running" | "error" + state["group_id"] : str + state["group_name"] : str + state["hub_url"] : str + state["username"] : str + state["node_port"] : int + state["index"] : GroupIndex | None + state["endpoint_hint"]: str | None + """ + app = FastAPI( + title="MeshBay Node UI", + version=__version__, + docs_url=None, # disable Swagger on local UI + redoc_url=None, + ) + + @app.get("/api/status") + async def api_status(): + index = state.get("index") + return { + "version": __version__, + "status": state.get("status", "starting"), + "hub_url": state.get("hub_url", ""), + "username": state.get("username", ""), + "group_id": state.get("group_id", ""), + "group_name": state.get("group_name", ""), + "node_port": state.get("node_port", 0), + "endpoint_hint": state.get("endpoint_hint"), + "file_count": index.count if index else 0, + "index_version": index.version if index else 0, + } + + @app.get("/api/files") + async def api_files(): + index = state.get("index") + if not index: + return {"files": []} + return { + "files": [ + { + "id": e.id[:16] + "…", + "name": e.name, + "path": e.path, + "size": e.size, + "type": e.type, + "duration": e.duration, + } + for e in index.entries + ] + } + + @app.get("/", response_class=HTMLResponse) + async def root(): + index = state.get("index") + status = state.get("status", "starting") + file_count = index.count if index else 0 + status_color = {"running": "#22c55e", "error": "#ef4444"}.get(status, "#f59e0b") + + files_html = "" + if index: + rows = "".join( + f"{e.name}{e.type}" + f"{e.size // 1024} KB{e.path or '/'}" + for e in index.entries + ) + files_html = f""" +

Files ({file_count})

+ + + {rows} +
NameTypeSizePath
""" + + return f""" + + + + MeshBay Node + + + + +

🔗 MeshBay Node {status}

+

+ Hub: {state.get("hub_url", "—")}  |  + User: {state.get("username", "—")}  |  + Group: {state.get("group_name") or state.get("group_id") or "—"}  |  + Port: {state.get("node_port", "—")} +

+

Endpoint: {state.get("endpoint_hint") or "unknown"}

+ {files_html} +
+ MeshBay Node v{__version__} — JSON status + — JSON files + +""" + + return app -- cgit v1.2.3