aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-09 04:58:06 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-09 04:58:06 +0200
commitc9422eaf95090cde6411186c0d62b04286a8477c (patch)
tree558378df2af6869a86215bcfe53e893b79c756bd /packages/meshbay-hub/src/meshbay_hub/api/webapp.py
parent9b503785afe10849f40a26735aa08e0af24a6efc (diff)
downloadmeshbay-c9422eaf95090cde6411186c0d62b04286a8477c.tar.gz
feat(hub): add web app + public group listing endpoint
webapp.py: serves / (HTML SPA) and /app.js (JS client). app.js: login, hub API calls, node HTTP API integration, file browser, HLS video streaming via <video> tag. groups.py: GET /v1/groups — public group listing (no auth). Deployed on https://meshbay.org — 200 OK. 47/47 tests. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/webapp.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/webapp.py74
1 files changed, 74 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
new file mode 100644
index 0000000..3d5e78b
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
@@ -0,0 +1,74 @@
+"""
+Hub web application — serves the MeshBay web client at /.
+
+The web client (HTML/JS) is a single-page app that:
+ - Logs in via the hub API
+ - Discovers public groups
+ - Connects to a node URL entered by the user
+ - Browses the node's file index
+ - Downloads or streams files via the node HTTP API
+
+Static files are served from meshbay_hub/static/.
+API routes remain at /v1/*.
+"""
+
+from pathlib import Path
+
+from fastapi import APIRouter
+from fastapi.responses import FileResponse, HTMLResponse
+
+STATIC_DIR = Path(__file__).parent.parent / "static"
+
+router = APIRouter(tags=["webapp"])
+
+
+@router.get("/app.js")
+async def app_js():
+ return FileResponse(STATIC_DIR / "app.js", media_type="application/javascript")
+
+
+@router.get("/", response_class=HTMLResponse)
+async def index():
+ return HTMLResponse(_HTML)
+
+
+_HTML = """\
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>MeshBay</title>
+ <style>
+ *, *::before, *::after { box-sizing: border-box; }
+ body { font-family: system-ui, sans-serif; margin: 0; background: #f8fafc; color: #1e293b; }
+ #nav { background: #0f172a; color: #e2e8f0; padding: 12px 24px; }
+ #nav b { color: #38bdf8; font-size: 1.2em; }
+ #main { max-width: 960px; margin: 32px auto; padding: 0 16px; }
+ h2 { color: #0f172a; margin-top: 1.5em; }
+ input { padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 6px;
+ font-size: 1em; margin: 4px; }
+ button { padding: 8px 16px; background: #0ea5e9; color: #fff; border: none;
+ border-radius: 6px; cursor: pointer; font-size: 0.9em; margin: 4px; }
+ button:hover { background: #0284c7; }
+ .card { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px;
+ padding: 16px; margin: 8px 0; cursor: pointer; }
+ .card:hover { border-color: #0ea5e9; }
+ .badge { background: #e0f2fe; color: #0284c7; padding: 2px 8px;
+ border-radius: 12px; font-size: 0.8em; margin-left: 8px; }
+ table { width: 100%; border-collapse: collapse; background: #fff;
+ border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }
+ th { background: #f1f5f9; padding: 10px; text-align: left; }
+ td { padding: 10px; border-top: 1px solid #f1f5f9; }
+ video { border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,.15); }
+ a { color: #0ea5e9; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ </style>
+</head>
+<body>
+ <div id="nav"></div>
+ <div id="main"><p>Loading…</p></div>
+ <script src="/app.js"></script>
+</body>
+</html>
+"""