Add the web interface

Plain HTML, CSS and JavaScript modules, with no build step. Hash routes
and relative URLs make it work under any path. The job list is polled:
every 2 seconds while a job runs, and less often otherwise.
This commit is contained in:
tim
2026-09-27 23:38:28 +02:00
parent 09d6b76a17
commit 2f4271314b
30 changed files with 4442 additions and 0 deletions
+35
View File
@@ -1,6 +1,9 @@
"""The web API: login modes, the CSRF header, URL_BASE and the web interface."""
import base64
import hashlib
import io
import re
import time
import pytest
@@ -177,6 +180,7 @@ def test_incomplete_login_settings_refuse_every_request(client, monkeypatch, set
):
assert (response.status_code, response.json) == (503, {"error": message})
assert client.get("/api/health").status_code == 200
assert client.get("/").status_code == 200
def test_api_key(client, cwa, monkeypatch):
@@ -309,3 +313,34 @@ def test_metadata_edit(client):
assert (response.status_code, response.json) == (400, {"error": "A story needs a title"})
assert client.put(f"/api/stories/{story_id}/metadata", json={}, headers=H).status_code == 400
assert client.put("/api/stories/999/metadata", json={"values": {}}, headers=H).status_code == 404
def test_web_interface_is_served(client):
page = client.get("/")
assert page.status_code == 200 and b'<script type="module" src="static/js/main.js">' in page.data
assert client.get("/static/js/main.js").status_code == 200
assert client.get("/static/style.css").status_code == 200
def test_the_page_allows_only_its_own_scripts(client):
page = client.get("/")
policy = page.headers["Content-Security-Policy"]
assert "default-src 'self'" in policy and "unsafe-inline" not in policy
scripts = re.findall(r"<script>(.*?)</script>", page.get_data(as_text=True), re.DOTALL)
assert scripts
for script in scripts:
digest = base64.b64encode(hashlib.sha256(script.encode()).digest()).decode()
assert f"'sha256-{digest}'" in policy
def test_url_base():
app = web.PrefixMiddleware(web.app.wsgi_app, "/bindery")
web.app.wsgi_app, original = app, web.app.wsgi_app
try:
client = web.app.test_client()
assert client.get("/bindery/api/health").status_code == 200
assert client.get("/api/health").status_code == 200
assert client.get("/bindery/static/style.css").status_code == 200
assert client.get("/other").status_code == 404
finally:
web.app.wsgi_app = original