homepage: sectioned panels, WIP open/closed, current model facts, Tautulli card
Both pages now group their panels under section headings rendered by shared
renderSection()/renderPanel() components in app.js (SECTION_ORDER fixes each
page's order: user = AI, Web Apps, Media Library Apps; admin = Media Library
Apps, Observability, AI, Infrastructure). The WIP badge splits into WIP: OPEN
and WIP: CLOSED via the wip value ("open"/"closed"); the checker passes the
value through as wip-open/wip-closed. The frank-large card follows the
unsloth llama.cpp cutover, frank-medium follows the FP8 checkpoint, and the
/docs model guide matches. Tautulli (Plex statistics, 192.168.0.235:8181)
joins the admin page's Media Library Apps section with an open link.
This commit is contained in:
1 parent
32eafeb7c5
commit
384747a72f
9 files changed
+221
-54
No files matched your search
@@ -2,7 +2,8 @@
|
||||
# Seam: scripts/homepage-status.py <services.json> <out.json>
|
||||
# Given a service config, the checker writes a valid status.json with the
|
||||
# correct state per service: an open port is up, a closed port is down,
|
||||
# a wip service is wip (not probed). The output carries generated_at.
|
||||
# a wip service is not probed and takes the state of its wip value
|
||||
# (wip-open / wip-closed). The output carries generated_at.
|
||||
|
||||
load helpers
|
||||
|
||||
@@ -24,13 +25,14 @@ teardown() {
|
||||
rm -rf "$TMP"
|
||||
}
|
||||
|
||||
@test "checker: open port is up, closed port is down, wip is wip" {
|
||||
@test "checker: open port is up, closed port is down, wip is wip-open / wip-closed" {
|
||||
local svc="$TMP/services.json" out="$TMP/status.json"
|
||||
cat > "$svc" <<JSON
|
||||
[
|
||||
{"name":"up","category":"user","probe":"127.0.0.1:$UP_PORT","method":"tcp","wip":false},
|
||||
{"name":"down","category":"user","probe":"127.0.0.1:$DOWN_PORT","method":"tcp","wip":false},
|
||||
{"name":"wip","category":"user","probe":"127.0.0.1:1","method":"tcp","wip":true}
|
||||
{"name":"wipopen","category":"user","probe":"127.0.0.1:1","method":"tcp","wip":"open"},
|
||||
{"name":"wipclosed","category":"user","probe":"127.0.0.1:1","method":"tcp","wip":"closed"}
|
||||
]
|
||||
JSON
|
||||
python3 "$CHECKER" "$svc" "$out"
|
||||
@@ -41,7 +43,8 @@ d = json.load(open(sys.argv[1]))
|
||||
s = {x["name"]: x["state"] for x in d["services"]}
|
||||
assert s["up"] == "up", s
|
||||
assert s["down"] == "down", s
|
||||
assert s["wip"] == "wip", s
|
||||
assert s["wipopen"] == "wip-open", s
|
||||
assert s["wipclosed"] == "wip-closed", s
|
||||
assert "generated_at" in d
|
||||
PY
|
||||
}
|
||||
@@ -91,7 +94,7 @@ PY
|
||||
|
||||
@test "the checker writes status.json world-readable so Caddy can serve it" {
|
||||
local svc="$TMP/services.json" out="$TMP/status.json"
|
||||
echo '[{"name":"x","category":"user","probe":"127.0.0.1:1","method":"tcp","wip":true}]' > "$svc"
|
||||
echo '[{"name":"x","category":"user","probe":"127.0.0.1:1","method":"tcp","wip":"closed"}]' > "$svc"
|
||||
python3 "$CHECKER" "$svc" "$out"
|
||||
[ "$(stat -c %a "$out")" = "644" ]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bats
|
||||
# Seam: shared/homepage/services.json — the service config the checker reads
|
||||
# and the pages render. It must be a JSON list; every entry carries name,
|
||||
# category, and a host:port probe. category is user or admin. method, when
|
||||
# present, is tcp or http.
|
||||
# category, section, and a host:port probe. category is user or admin;
|
||||
# section must be one the page's SECTION_ORDER renders. method, when
|
||||
# present, is tcp or http. wip, when set, is "open" or "closed".
|
||||
|
||||
load helpers
|
||||
|
||||
@@ -17,16 +18,34 @@ except FileNotFoundError:
|
||||
assert isinstance(data, list), "top level must be a list"
|
||||
assert data, "the list must not be empty"
|
||||
for svc in data:
|
||||
for field in ("name", "category", "probe"):
|
||||
for field in ("name", "category", "section", "probe"):
|
||||
assert field in svc, f"{svc.get('name', '?')} is missing {field}"
|
||||
assert svc["category"] in ("user", "admin"), svc["category"]
|
||||
if "method" in svc:
|
||||
assert svc["method"] in ("tcp", "http"), svc["method"]
|
||||
wip = svc.get("wip")
|
||||
assert wip is False or wip in ("open", "closed"), f"{svc['name']} wip must be false, 'open' or 'closed'"
|
||||
host, _, port = svc["probe"].rpartition(":")
|
||||
assert host and port.isdigit(), f"bad probe {svc['probe']}"
|
||||
PY
|
||||
}
|
||||
|
||||
@test "services.json: every section is one the page renders" {
|
||||
# A service whose section is not in app.js's SECTION_ORDER for its
|
||||
# category is silently dropped from the page. Pin the contract.
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
||||
import json, sys
|
||||
data = json.load(open(sys.argv[1]))
|
||||
allowed = {
|
||||
"user": {"AI", "Web Apps", "Media Library Apps"},
|
||||
"admin": {"Media Library Apps", "Observability", "AI", "Infrastructure"},
|
||||
}
|
||||
for svc in data:
|
||||
assert svc.get("section") in allowed[svc["category"]], (
|
||||
f"{svc['name']} has unrenderable section {svc.get('section')!r} for {svc['category']}")
|
||||
PY
|
||||
}
|
||||
|
||||
@test "services.json: every user service has an Open link, status-only backends do not" {
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
||||
import json, sys
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
# statically: the pages reference the data and the theme, the page does not
|
||||
# auto-refresh (Q41), does not alter the user's cursor (Q28), offers the
|
||||
# styling toggle (Q27), and shows the last master deployment datetime (Q43).
|
||||
# Both pages render through the shared section/panel components, and WIP
|
||||
# badges distinguish open from closed work.
|
||||
|
||||
load helpers
|
||||
|
||||
@@ -24,6 +26,21 @@ setup() {
|
||||
grep -q "services.json" "$BASE/app.js"
|
||||
}
|
||||
|
||||
@test "sections and panels are shared components rendered by both pages" {
|
||||
# Both pages load the same app.js; the grouping must live in shared
|
||||
# renderSection()/renderPanel() components, not in per-page markup.
|
||||
grep -q "renderSection" "$BASE/app.js"
|
||||
grep -q "renderPanel" "$BASE/app.js"
|
||||
grep -q "SECTION_ORDER" "$BASE/app.js"
|
||||
}
|
||||
|
||||
@test "WIP badges distinguish open from closed work" {
|
||||
grep -q "WIP: OPEN" "$BASE/app.js"
|
||||
grep -q "WIP: CLOSED" "$BASE/app.js"
|
||||
grep -q "wip-open" "$BASE/style.css"
|
||||
grep -q "wip-closed" "$BASE/style.css"
|
||||
}
|
||||
|
||||
@test "the page does not auto-refresh (Q41)" {
|
||||
! grep -q "setInterval" "$BASE/app.js"
|
||||
! grep -qi "poll" "$BASE/app.js"
|
||||
|
||||
Reference in new issue
Block a user