From 5ff6d80333ed74ed52e58d037fe34f02817290af Mon Sep 17 00:00:00 2001 From: m17hr1l Date: Sun, 7 Jun 2026 00:37:32 +0200 Subject: [PATCH] stage-net-c network view: transitive fetcher + admin data endpoint --- src/psyc/cockpit/federation_routes.py | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/psyc/cockpit/federation_routes.py b/src/psyc/cockpit/federation_routes.py index c1adab6..02c9a96 100644 --- a/src/psyc/cockpit/federation_routes.py +++ b/src/psyc/cockpit/federation_routes.py @@ -322,6 +322,38 @@ def register(app: FastAPI, TEMPLATES: Jinja2Templates) -> None: }, ) + # ---------- admin: federation network view ---------------------- + + @app.get("/admin/federation/network", response_class=HTMLResponse) + def admin_federation_network(request: Request) -> HTMLResponse: + """Cockpit page — force-directed federation map. Data lives at /data.""" + if not _admin_ok(request): + return RedirectResponse("/admin", status_code=303) + # Build local stats up front so the header card renders even if the + # JS data-endpoint fetch fails (defensive — never give the operator a + # blank page). + view = network_view.build_local_view() + return TEMPLATES.TemplateResponse( + request, + "admin_federation_network.html", + { + "fingerprint": federation.node_fingerprint(), + "stats": view.stats, + }, + ) + + @app.get("/admin/federation/network/data") + def admin_federation_network_data(request: Request) -> JSONResponse: + """Full admin view — includes unknown/blocked peers + transitive peers. + + Public /federation/network filters those out; this surface does not, + because it sits behind the admin gate and the operator needs to see + the real shape of the federation including the parts being ignored. + """ + if not _admin_ok(request): + raise HTTPException(status_code=403, detail="admin session required") + return JSONResponse(network_view.build_admin_view(include_transitive=True)) + # ---------- admin: quorum config + per-peer/per-hash view ------- @app.get("/admin/federation/quorum", response_class=HTMLResponse)