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:
Frank committed 2026-10-08 03:21:26 -07:00
1 parent 3c5d77d1fa
commit c1e192df00
44 files changed
+338 -102

No files matched your search

+22 -2
View File
@@ -92,6 +92,7 @@ function miCard(svc, state) {
if (m.use) parts.push('<div class="mi-use">Use: ' + esc(m.use) + '</div>');
if (m.voice) parts.push(voiceBtn(m.voice.text, m.voice.sfx));
if (m.supervision) parts.push('<div class="mi-super">' + esc(m.supervision) + '</div>');
if (m.permission) parts.push('<div class="mi-perm" title="' + esc(m.permission) + '">&#9888; ' + esc(m.permission) + '</div>');
if (m.warn) parts.push('<div class="mi-warn" title="' + esc(m.warn) + '">' +
'&#9888; ' + esc(m.warn.split(':')[0]) + ' \u2014 hover to read</div>');
const card = document.createElement('article');
@@ -125,12 +126,16 @@ function renderPanel(svc, state) {
const links = [];
if (svc.showLink && svc.url) links.push('<a class="open" href="' + esc(svc.url) + '">open</a>');
if (svc.docs) links.push('<a class="docs" href="' + esc(svc.docs) + '">docs</a>');
const art = svc.art ? '<img class="app-art" src="' + esc(svc.art) + '" alt="">' : '';
const warn = svc.warn
? '<div class="svc-warn" title="' + esc(svc.warn) + '">&#9888; privacy note \u2014 hover to read</div>'
: '';
panel.innerHTML =
'<div class="panel-head">' +
'<div class="panel-head">' + art +
' <span class="badge ' + state + '">' + BADGE_LABEL[state] + '</span>' +
' <span class="name">' + esc(svc.name) + '</span>' +
'</div>' +
'<div class="desc">' + descHTML(svc) + '</div>' +
'<div class="desc">' + descHTML(svc) + '</div>' + warn +
'<div class="links">' + links.join('') + '</div>';
return panel;
}
@@ -197,9 +202,24 @@ function fillDeployed() {
if (el && window.DEPLOYED_AT) el.textContent = window.DEPLOYED_AT;
}
// Show the admin button only to members of the authentik homepage-admins
// group (the same group the /admin* server gate enforces). The data comes
// from Caddy's /whoami, which echoes the outpost-injected identity headers.
function gateAdminLink() {
const link = document.getElementById('admin-link');
if (!link) return;
fetch('/whoami', { headers: { accept: 'application/json' } })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (me) {
if (me && /homepage-admins/i.test(me.groups || '')) link.hidden = false;
})
.catch(function () {});
}
function main() {
applyBare();
fillDeployed();
gateAdminLink();
Promise.all([load('services.json'), load('status.json')])
.then(function (pair) { renderPanels(pair[0], pair[1]); })
.catch(function (e) {