homepage: app-card art, zealot-first order, common uncensored warning, admin identity gate
- Every app card carries hex-clipped art: 19 amber-tinted brand marks from the Simple Icons CDN plus 19 improvised monogram SVGs, wired via a per-service "art" field; homepage_config.bats pins art presence for all 32 apps. - Model cards render zealot -> dragoon -> archon (registry order pinned in the seam). - Dragoon and zealot share one common uncensored legal warning (adult content, harmful/hateful/illegal descriptions, sensitive offensive cybersecurity topics, severe legal risk, "don't ruin this server for everybody"), hover label on card and docs. - Archon gets a bordered red permission callout: ask Frank before use. - SearXNG obfuscation-not-anonymity note moved to a card-level hover warning (.svc-warn) and a docs hover note. - Gitea: self-service signup flow; no need to ask Frank. - Footer docs/admin/account-settings become glowing bordered buttons; the admin button is shown only to homepage-admins members via a new Caddy /whoami endpoint echoing the outpost-injected X-Authentik-* headers (spoof-proof; the /admin* 403 stays the real gate). e2e pins both sides. - Docs naming swept: Protoss names primary, bracketed api ids, explicit note that a future switchover renames the ids themselves.
This commit is contained in:
1 parent
3c5d77d1fa
commit
c1e192df00
44 files changed
+338
-102
No files matched your search
@@ -47,8 +47,11 @@ test("standard user is denied the admin panel (RBAC)", async ({ page }) => {
|
||||
expect(resp.status()).toBe(403);
|
||||
expect(await resp.text()).toContain("Forbidden: homepage admins only");
|
||||
|
||||
// The footer link a standard user can click also dead-ends at the gate.
|
||||
// The admin button is identity-gated: /whoami reports the authentik
|
||||
// groups, and the page hides the button from non-admins (the server-side
|
||||
// 403 above stays the real gate). Direct navigation dead-ends all the same.
|
||||
await page.goto("/");
|
||||
await page.locator('footer a[href="/admin/"]').click();
|
||||
await expect(page.locator("#admin-link")).toBeHidden();
|
||||
await page.goto("/admin/");
|
||||
await expect(page.locator("body")).toContainText("Forbidden: homepage admins only");
|
||||
});
|
||||
@@ -4,6 +4,10 @@ import { expect, test } from "@playwright/test";
|
||||
// allow-leg: the same path returns 403 for the standard user (10-homepage).
|
||||
|
||||
test("admin panel renders admin-category services", async ({ page }) => {
|
||||
// Identity gate (admin side): a homepage-admins member sees the admin
|
||||
// button on the user homepage (the standard user does not: 10-homepage).
|
||||
await page.goto("/");
|
||||
await expect(page.locator("#admin-link")).toBeVisible({ timeout: 60_000 });
|
||||
await page.goto("/admin/");
|
||||
await expect(page).toHaveTitle(/frank — admin/, { timeout: 60_000 });
|
||||
|
||||
|
||||
@@ -101,7 +101,35 @@ for s in fleet:
|
||||
uncensored = "ncensored" in m["spec"] or "bliterated" in m["spec"]
|
||||
if uncensored:
|
||||
assert "legal" in m.get("warn", "").lower(), f"{s['name']} is uncensored but has no legal warning"
|
||||
assert [s["name"] for s in fleet] == ["frank-zealot", "frank-dragoon", "frank-archon"], \
|
||||
"card render order must be zealot, dragoon, archon: " + str([s["name"] for s in fleet])
|
||||
w = {s["name"]: s["model"].get("warn", "") for s in fleet}
|
||||
assert w["frank-zealot"] == w["frank-dragoon"] != "", "the legal warnings must be the common uncensored text"
|
||||
assert "legal risk" in w["frank-zealot"].lower() and "everybody" in w["frank-zealot"].lower()
|
||||
archon = next(s for s in fleet if s["name"] == "frank-archon")
|
||||
assert "permission" in archon["model"], "archon card needs the ask-Frank permission callout"
|
||||
PY
|
||||
grep -q "renderModelInfo" "$REPO_ROOT/shared/homepage/app.js"
|
||||
grep -q ".mi-card" "$REPO_ROOT/shared/homepage/style.css"
|
||||
}
|
||||
|
||||
@test "every app card carries icon art and SearXNG carries the privacy note" {
|
||||
# The family sees unit-portrait art on every card, not just the models:
|
||||
# brand marks where Simple Icons has them, improvised monograms otherwise.
|
||||
# SearXNG's card must warn (hover) that obfuscation is not anonymity.
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" "$REPO_ROOT/shared/homepage" <<'PY'
|
||||
import json, os, sys
|
||||
services = json.load(open(sys.argv[1]))
|
||||
root = sys.argv[2]
|
||||
apps = [s for s in services if "model" not in s]
|
||||
for s in apps:
|
||||
art = s.get("art", "")
|
||||
assert art.startswith("/img/apps/"), f"{s['name']} has no app art"
|
||||
assert os.path.isfile(os.path.join(root, art.lstrip("/"))), f"{s['name']} art file missing: {art}"
|
||||
sx = next(s for s in services if s["name"] == "SearXNG")
|
||||
assert "anonymous" in sx.get("warn", "").lower(), "SearXNG must warn that searches are obfuscated, not anonymous"
|
||||
PY
|
||||
grep -q "app-art" "$REPO_ROOT/shared/homepage/app.js"
|
||||
grep -q ".app-art" "$REPO_ROOT/shared/homepage/style.css"
|
||||
grep -q "/whoami" "$REPO_ROOT/dankclaw/Caddyfile"
|
||||
}
|
||||
@@ -79,7 +79,7 @@ setup() {
|
||||
|
||||
@test "the user docs page exists and is reachable from the service pages" {
|
||||
[ -f "$BASE/docs.html" ]
|
||||
grep -q '<a href="/docs">docs</a>' "$BASE/index.html"
|
||||
grep -q 'class="foot-btn" href="/docs"' "$BASE/index.html"
|
||||
grep -q 'id="models"' "$BASE/docs.html"
|
||||
grep -q '<base href="/">' "$BASE/docs.html"
|
||||
grep -q 'style.css' "$BASE/docs.html"
|
||||
|
||||
Reference in new issue
Block a user