Remove SPA catch-all, add vendor route and JSON 404 handler

The SPA uses hash-based routing, so the catch-all route was dead code.
Replace with explicit routes for / and /vendor/<path>, plus a 404
handler that returns JSON for /api/ paths and empty HTML otherwise.
This commit is contained in:
2026-06-23 23:20:35 +00:00
parent e74f0a5ffb
commit feaf253403
2 changed files with 27 additions and 17 deletions
+5 -6
View File
@@ -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:
+22 -11
View File
@@ -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("/<path:path>")
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/<path:filename>")
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)