diff --git a/tests/test_server.py b/tests/test_server.py index 9388ad3..0c1154e 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -20,18 +20,17 @@ class TestSPARoutes: assert resp.status_code == 200 assert b'id="app"' in resp.data - def test_spa_catch_all_serves_index(self, client): + def test_spa_unknown_path_404(self, client): resp = client.get("/dashboard") - assert resp.status_code == 200 - assert b"index.html" in resp.data or b'id="app"' in resp.data + assert resp.status_code == 404 - def test_spa_catch_all_other_page(self, client): + def test_spa_unknown_path_404_other(self, client): resp = client.get("/zones") - assert resp.status_code == 200 + assert resp.status_code == 404 def test_api_routes_still_work(self, client): resp = client.get("/api/firewall/zones") - assert resp.status_code in (200, 502, 503) + assert resp.status_code in (200, 500) class TestWsUrlGeneration: diff --git a/webui/server.py b/webui/server.py index 617aefd..85409b8 100644 --- a/webui/server.py +++ b/webui/server.py @@ -177,29 +177,40 @@ def api_status_all(): # --------------------------------------------------------------------------- -# SPA catch-all +# SPA entry point — serve index.html for /, 404 for everything else # --------------------------------------------------------------------------- SPA_DIR = STATIC_DIR +VENDOR_DIR = PROJECT_DIR / "vendor" @app.route("/") -@app.route("/") -def spa_page(path=""): - """Single-page application catch-all. - - Serves ``index.html`` (rendered as a Jinja2 template) for all non-API, - non-static paths. The client-side router handles navigation and defaults - to ``#dashboard``. - """ - if path.startswith("api/") or path.startswith("static/"): - abort(404) +def spa_root(): + """Serve the SPA entry point. No catch-all — client handles routing.""" scheme = "wss" if request.is_secure else "ws" ws_url = f"{scheme}://{request.host}/ws" html = (SPA_DIR / "index.html").read_text() return html.replace("__WS_URL_PLACEHOLDER__", ws_url) +@app.route("/vendor/") +def vendor_files(filename): + """Serve vendored JS libraries (htm.js, etc.).""" + from flask import send_file + target = (VENDOR_DIR / filename).resolve() + if not target.is_relative_to(VENDOR_DIR): + abort(404) + return send_file(target) + + +@app.errorhandler(404) +def not_found(e): + """Return 404 JSON for API clients, 404 HTML for everything else.""" + if request.path.startswith("/api/"): + return {"ok": False, "error": "Not found"}, 404 + return "", 404 + + if __name__ == "__main__": logger.info("Starting Flask on 127.0.0.1:9090") app.run(host="127.0.0.1", port=9090)