From 026c255a45453d14eab09f10e8b327cc5b1a666d Mon Sep 17 00:00:00 2001 From: Thore Cimbal Date: Thu, 13 Aug 2026 12:00:00 +0000 Subject: [PATCH] feat(wiki): bake custom navigation + HTML rendering + UX CSS into the config job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the wiki UX reproducible: set_navigation writes the custom static sidebar (Home + Anwender + Betrieb, betrieb items visible only to admin groups 1/3); ensure_html_rendering turns on markdownCore.allowHTML (v-encoded, other renderer configs preserved) so the HTML card landing renders — note it needs a Wiki.js restart to take effect on a fresh instance. The injectCSS is rebuilt: content links get an underline + accent (the old broad primary--text coloring made non-links look like links) plus the landing card/button styles. Page content lives in git-storage. --- apps/production/wikijs-config.py | 114 ++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/apps/production/wikijs-config.py b/apps/production/wikijs-config.py index f29c629..bc5a210 100644 --- a/apps/production/wikijs-config.py +++ b/apps/production/wikijs-config.py @@ -7,9 +7,11 @@ stdlib. Alle Verträge wurden am 2026-08-12 live gegen die laufende Instanz gepr Ablauf: warten -> (falls Setup) /finalize mit SOPS-Admin -> warten auf Normalmodus - -> login -> Gruppen + Seitenregeln (Abschottung) -> OIDC-Strategy setzen - -> local auf der Login-Seite ausblenden (hideLocal). -Idempotent: jeder Schritt prüft erst den Ist-Zustand. + -> login -> Gruppen + Seitenregeln (Abschottung) -> OIDC-Strategy -> hideLocal + -> Theming/Branding -> Navigation (Custom-Sidebar) -> HTML-Rendering (allowHTML) + -> Git-Storage. +Idempotent: jeder Schritt prüft erst den Ist-Zustand. Seiten-Inhalt (Startseite, +Anwender-/Betriebsseiten) liegt in git-storage, nicht in diesem Job. Variante B ("kein manuelles Login"): local wird NICHT deaktiviert, sondern nur per Wiki.js-eigenem hideLocal aus der Login-Seite ausgeblendet. Grund: (a) local ist @@ -47,15 +49,27 @@ ADMIN_PERMS = [ ] READER_PERMS = ["read:pages", "read:assets", "read:comments"] -# Akzentfarbe fürs App-/Inhalts-UI (#0050): blau wie das alte Docusaurus. -# Gilt NICHT für die Login-Seite — die rendert Wiki.js ohne Custom-CSS. +# App-/Inhalts-UI-CSS (#0050 + UX-Fixes). Gilt NICHT für die Login-Seite (die rendert +# Wiki.js ohne Custom-CSS). +# - Content-Links: Akzent + Unterstreichung, damit klar als Link erkennbar. Die frühere +# breite .primary--text-Einfärbung ließ Nicht-Links wie Links aussehen -> raus. +# - Startseiten-Kacheln + Schnellzugriff-Buttons (die Startseite nutzt HTML, s. allowHTML). ACCENT_CSS = ( - ".v-application a{color:#2b6cb0}" - ".v-application .primary--text{color:#2b6cb0!important;caret-color:#2b6cb0!important}" - ".v-application .primary{background-color:#2b6cb0!important;border-color:#2b6cb0!important}" - ".theme--dark.v-application a{color:#63b3ed}" - ".theme--dark.v-application .primary--text{color:#63b3ed!important;caret-color:#63b3ed!important}" - ".theme--dark.v-application .primary{background-color:#63b3ed!important;border-color:#63b3ed!important}" + ".v-application .contents a{color:#2b6cb0;text-decoration:underline}" + ".theme--dark.v-application .contents a{color:#63b3ed}" + ".v-application .contents a.toc-anchor{color:inherit!important;text-decoration:none!important}" + ".wiki-hero{background:rgba(255,255,255,.05);border-radius:10px;padding:26px 24px;margin:8px 0 20px}" + ".wiki-hero h1{border:none!important;margin:0 0 6px}.wiki-hero p{margin:0;color:#9ca3af}" + ".wiki-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin:10px 0 26px}" + ".wiki-card{display:flex!important;flex-direction:column;gap:6px;padding:20px;border-radius:10px;" + "background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.10);text-decoration:none!important}" + ".wiki-card:hover{background:rgba(99,179,237,.12);border-color:#63b3ed}" + ".wiki-card .wc-t{font-size:1.15rem;font-weight:600;color:#e5e7eb}" + ".wiki-card .wc-d{color:#9ca3af;font-size:.9rem}" + ".wiki-btns{display:flex;flex-wrap:wrap;gap:10px;margin:6px 0 22px}" + ".wiki-btn{display:inline-block;padding:9px 16px;border-radius:6px;background:#2b6cb0;color:#fff!important;" + "text-decoration:none!important;font-size:.92rem;font-weight:500}" + ".wiki-btn:hover{background:#63b3ed}" ) @@ -248,6 +262,82 @@ def ensure_git_storage(jwt: str): log(f"Git-Storage aktiviert (Gitea, sync): {repo}") +def set_navigation(jwt: str): + # Eigenes Sidebar-Menü (STATIC) statt des Browse-Baums: Home + Anwender + Betrieb. + # Betrieb-Einträge nur für Admin-Gruppen sichtbar (1 Administrators, 3 authentik + # Admins); die Seiten sind ohnehin per checkAccess für Anwender gesperrt. + admin = [1, 3] + def lnk(i, label, icon, path, g=None): + return {"id": i, "kind": "link", "label": label, "icon": icon, + "targetType": "page", "target": path, + "visibilityMode": "restricted" if g else "all", "visibilityGroups": g or []} + def hdr(i, label, g=None): + return {"id": i, "kind": "header", "label": label, "icon": "", + "targetType": "external", "target": "", + "visibilityMode": "restricted" if g else "all", "visibilityGroups": g or []} + items = [ + {"id": "home", "kind": "link", "label": "Startseite", "icon": "mdi-home", + "targetType": "home", "target": "", "visibilityMode": "all", "visibilityGroups": []}, + hdr("hanw", "Anwenderdokumentation"), + lnk("a1", "Registrierung", "mdi-account-plus", "anwender/registrierung"), + lnk("a2", "Anmelden & Konto", "mdi-login", "anwender/anmelden-und-konto"), + lnk("a3", "Erste Schritte", "mdi-rocket-launch-outline", "anwender/erste-schritte"), + lnk("a4", "Nachrichten", "mdi-message-text", "anwender/nachrichten"), + lnk("a5", "Anrufe & Screensharing", "mdi-phone", "anwender/anrufe"), + lnk("a6", "Verschlüsselung", "mdi-lock", "anwender/verschluesselung"), + lnk("a7", "Benachrichtigungen", "mdi-bell", "anwender/benachrichtigungen"), + lnk("a8", "ThreadNet Desktop", "mdi-desktop-classic", "anwender/threadnet-desktop-setup"), + hdr("hbet", "Betriebsdokumentation", admin), + lnk("b0", "Einführung & Architektur", "mdi-sitemap", "betrieb/einfuehrung", admin), + lnk("b1", "Installation", "mdi-download", "betrieb/installation", admin), + lnk("b2", "ConfigMap-Sync", "mdi-sync", "betrieb/configmap-sync", admin), + lnk("b3", "Authentik OIDC", "mdi-shield-account", "betrieb/authentik-oidc", admin), + lnk("b4", "Element / ThreadNet", "mdi-palette", "betrieb/element-customization", admin), + lnk("b5", "Element Call & MatrixRTC", "mdi-video", "betrieb/element-call", admin), + lnk("b6", "TURN Server", "mdi-server-network", "betrieb/turn-server", admin), + lnk("b7", "Moderation & Scanning", "mdi-shield-check", "betrieb/moderation-content-scanning", admin), + lnk("b8", "Monitoring", "mdi-chart-line", "betrieb/monitoring", admin), + lnk("b9", "Backups", "mdi-backup-restore", "betrieb/backups", admin), + lnk("b10", "Host-Wartung", "mdi-wrench", "betrieb/host-maintenance-notifications", admin), + lnk("b11", "Room Policies", "mdi-gavel", "betrieb/room-policies", admin), + lnk("b12", "Upgrades", "mdi-update", "betrieb/upgrades", admin), + ] + gql('mutation($m:NavigationMode!){navigation{updateConfig(mode:$m){responseResult{succeeded}}}}', + jwt, {"m": "STATIC"}) + gql('mutation($t:[NavigationTreeInput]!){navigation{updateTree(tree:$t)' + '{responseResult{succeeded message}}}}', jwt, {"t": [{"locale": "en", "items": items}]}) + log("Navigation gesetzt (Custom-Sidebar; Betrieb nur für Admins)") + + +def ensure_html_rendering(jwt: str): + # Die Startseite nutzt HTML-Kacheln -> markdownCore.allowHTML muss an sein. Wiki.js' + # updateRenderers-Resolver liest jeden Wert via _.get(JSON.parse(value),'v',null), + # also {"v":…}-kodiert; die übrigen Renderer-Werte müssen mitgesendet werden, sonst + # werden sie genullt. + # ⚠️ Wiki.js lädt die Renderer-Config beim START. Auf einer FRISCHEN Instanz greift + # allowHTML erst nach einem Wiki.js-Neustart; die Startseite rendert dann beim + # nächsten git-Sync korrekt (sonst erscheint sie kurz als HTML-Text). Idempotent. + rs = gql('{rendering{renderers{isEnabled key config{key value}}}}', jwt)["rendering"]["renderers"] + def actual(v): + try: + p = json.loads(v) + return p["value"] if isinstance(p, dict) and "value" in p else p + except Exception: + return v + out = [] + for r in rs: + cfg = [] + for c in (r["config"] or []): + a = actual(c["value"]) + if r["key"] == "markdownCore" and c["key"] == "allowHTML": + a = True + cfg.append({"key": c["key"], "value": json.dumps({"v": a})}) + out.append({"isEnabled": r["isEnabled"], "key": r["key"], "config": cfg}) + gql('mutation($r:[RendererInput]){rendering{updateRenderers(renderers:$r)' + '{responseResult{succeeded message}}}}', jwt, {"r": out}) + log("HTML-Rendering aktiviert (markdownCore.allowHTML) — Neustart nötig auf frischer Instanz") + + def group_id(jwt: str, name: str): for g in gql("{groups{list{id name}}}", jwt)["groups"]["list"]: if g["name"] == name: @@ -299,6 +389,8 @@ def main(): ensure_oidc(jwt) set_hide_local(jwt) ensure_theming(jwt) + set_navigation(jwt) + ensure_html_rendering(jwt) ensure_git_storage(jwt) log("fertig — Wiki.js konfiguriert")