Show the API reference with Redoc

Replace the hand-built reference page with Redoc rendering the generated
OpenAPI document. The bundle is fetched by package.nix (pinned hash) and
served from the UI's own address; only that page gets a style nonce and
worker-src blob: in its CSP.

The spec now explains authentication, saved-not-applied, ETags (If-Match,
409) and errors, documents the ETag header, gives each operation a curl
sample, and no longer names MCP tools. Area GETs no longer emit
"parameters": null, which Redoc could not render.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-11-01 22:01:30 +01:00
co-authored by Claude Sonnet 5.5
parent 64e08fb485
commit 876080d3f5
14 changed files with 245 additions and 201 deletions
+23 -8
View File
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
@@ -264,14 +265,28 @@ func TestLiveSettingsValidate(t *testing.T) {
}
}
func TestAreasOnReferencePage(t *testing.T) {
a, h, _ := areaApp(t)
cookie := login(t, h)
_ = a
body := do(h, "GET", "/apireference", nil, cookie).Body.String()
for _, want := range []string{"/api/v1/settings/router/lans", "/api/v1/settings/router/firewall", "&lt;name&gt;.vlan", "null or integer between 1 and 4094", "Settings areas"} {
if !strings.Contains(body, want) {
t.Errorf("%q is missing from the reference page", want)
// Viewers (Redoc) fail on a null where the OpenAPI document needs a list.
func TestSpecHasNoNulls(t *testing.T) {
a, _, _ := areaApp(t)
spec := openAPISpec("1.2.3", a.settingsSchema(), "router.example")
var walk func(path string, v any)
walk = func(path string, v any) {
switch x := v.(type) {
case nil:
t.Errorf("null at %s", path)
case map[string]any:
for k, c := range x {
walk(path+"/"+k, c)
}
case []any:
for i, c := range x {
walk(fmt.Sprintf("%s/%d", path, i), c)
}
}
}
// Round trip, as the served document is.
data, _ := json.Marshal(spec)
var doc any
_ = json.Unmarshal(data, &doc)
walk("", doc)
}