aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/ui/app.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-09 04:12:34 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-09 04:12:34 +0200
commitece4b01405b8edcf5cbe6367a2882433db1491b5 (patch)
tree339844449dc68a9675da47195665ad7d3d6fced7 /packages/meshbay-node/src/meshbay_node/ui/app.py
parent6abb68ae95f6c4da4a66453398006183a73db9d9 (diff)
downloadmeshbay-ece4b01405b8edcf5cbe6367a2882433db1491b5.tar.gz
feat(node): add config, daemon, and local web UI
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) <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/ui/app.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/ui/app.py134
1 files changed, 134 insertions, 0 deletions
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"<tr><td>{e.name}</td><td>{e.type}</td>"
+ f"<td>{e.size // 1024} KB</td><td>{e.path or '/'}</td></tr>"
+ for e in index.entries
+ )
+ files_html = f"""
+ <h2>Files ({file_count})</h2>
+ <table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
+ <thead><tr><th>Name</th><th>Type</th><th>Size</th><th>Path</th></tr></thead>
+ <tbody>{rows}</tbody>
+ </table>"""
+
+ return f"""<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>MeshBay Node</title>
+ <style>
+ body {{ font-family: monospace; max-width: 900px; margin: 40px auto; padding: 0 20px; }}
+ .badge {{ display:inline-block; padding:3px 10px; border-radius:4px;
+ color:#fff; background:{status_color}; font-weight:bold; }}
+ table {{ font-size: 0.9em; }}
+ th {{ background: #f3f4f6; }}
+ </style>
+ <meta http-equiv="refresh" content="10">
+</head>
+<body>
+ <h1>🔗 MeshBay Node <span class="badge">{status}</span></h1>
+ <p>
+ <b>Hub:</b> {state.get("hub_url", "—")} &nbsp;|&nbsp;
+ <b>User:</b> {state.get("username", "—")} &nbsp;|&nbsp;
+ <b>Group:</b> {state.get("group_name") or state.get("group_id") or "—"} &nbsp;|&nbsp;
+ <b>Port:</b> {state.get("node_port", "—")}
+ </p>
+ <p><b>Endpoint:</b> {state.get("endpoint_hint") or "unknown"}</p>
+ {files_html}
+ <hr>
+ <small>MeshBay Node v{__version__} — <a href="/api/status">JSON status</a>
+ — <a href="/api/files">JSON files</a></small>
+</body>
+</html>"""
+
+ return app