diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-09 04:58:06 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-09 04:58:06 +0200 |
| commit | c9422eaf95090cde6411186c0d62b04286a8477c (patch) | |
| tree | 558378df2af6869a86215bcfe53e893b79c756bd /packages/meshbay-hub/src/meshbay_hub/api/webapp.py | |
| parent | 9b503785afe10849f40a26735aa08e0af24a6efc (diff) | |
| download | meshbay-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.py | 74 |
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> +""" |