From 57db32f1b0b4e62b75eb46dcc4a08a837341c266 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 1 Apr 2026 11:32:27 +0200 Subject: [PATCH] feat(status-page): add app release tier section to status.mana.how Parse tier data automatically from mana-apps.ts (awk, read-only volume mount) so the status page stays in sync without manual updates. Shows founder/alpha/beta/public cards with per-app development status. Tier data is also included in status.json for ManaScore consumption. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.macmini.yml | 23 +++--- docs/MAC_MINI_SERVER.md | 19 +++++ scripts/generate-status-page.sh | 142 +++++++++++++++++++++++++++++++- 3 files changed, 171 insertions(+), 13 deletions(-) diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index d23046870..a199d845d 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -172,7 +172,7 @@ services: mc mb --ignore-existing myminio/manacore-storage; mc mb --ignore-existing myminio/picture-storage; mc mb --ignore-existing myminio/chat-storage; - mc mb --ignore-existing myminio/manadeck-storage; + mc mb --ignore-existing myminio/cards-storage; mc mb --ignore-existing myminio/presi-storage; mc mb --ignore-existing myminio/calendar-storage; mc mb --ignore-existing myminio/contacts-storage; @@ -276,7 +276,7 @@ services: SMTP_PASS: ${SMTP_PASSWORD} SYNAPSE_OIDC_CLIENT_SECRET: ${SYNAPSE_OIDC_CLIENT_SECRET:-} MAX_DAILY_SIGNUPS: ${MAX_DAILY_SIGNUPS:-0} - CORS_ORIGINS: https://mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://docs.mana.how,https://element.mana.how,https://inventar.mana.how,https://link.mana.how,https://manadeck.mana.how,https://matrix.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://playground.mana.how,https://presi.mana.how,https://questions.mana.how,https://skilltree.mana.how,https://storage.mana.how,https://times.mana.how,https://todo.mana.how,https://traces.mana.how,https://zitare.mana.how + CORS_ORIGINS: https://mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://docs.mana.how,https://element.mana.how,https://inventar.mana.how,https://link.mana.how,https://cards.mana.how,https://matrix.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://playground.mana.how,https://presi.mana.how,https://questions.mana.how,https://skilltree.mana.how,https://storage.mana.how,https://times.mana.how,https://todo.mana.how,https://traces.mana.how,https://zitare.mana.how ports: - "3001:3001" healthcheck: @@ -310,7 +310,7 @@ services: STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-} STRIPE_WEBHOOK_SECRET: ${STRIPE_CREDITS_WEBHOOK_SECRET:-} BASE_URL: https://credits.mana.how - CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://picture.mana.how,https://todo.mana.how,https://zitare.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how,https://manadeck.mana.how,https://presi.mana.how,https://storage.mana.how,https://nutriphi.mana.how,https://planta.mana.how,https://mukke.mana.how,https://context.mana.how,https://photos.mana.how,https://questions.mana.how,https://calc.mana.how + CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://picture.mana.how,https://todo.mana.how,https://zitare.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how,https://cards.mana.how,https://presi.mana.how,https://storage.mana.how,https://nutriphi.mana.how,https://planta.mana.how,https://mukke.mana.how,https://context.mana.how,https://photos.mana.how,https://questions.mana.how,https://calc.mana.how ports: - "3002:3002" healthcheck: @@ -341,7 +341,7 @@ services: DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_user MANA_CORE_AUTH_URL: http://mana-auth:3001 MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY} - CORS_ORIGINS: https://mana.how,https://calc.mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://manadeck.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://presi.mana.how,https://questions.mana.how,https://storage.mana.how,https://todo.mana.how,https://zitare.mana.how + CORS_ORIGINS: https://mana.how,https://calc.mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://cards.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://presi.mana.how,https://questions.mana.how,https://storage.mana.how,https://todo.mana.how,https://zitare.mana.how ports: - "3062:3062" healthcheck: @@ -799,7 +799,7 @@ services: MATRIX_CONTACTS_BOT_TOKEN: ${MATRIX_CONTACTS_BOT_TOKEN:-} MATRIX_CONTACTS_BOT_ROOMS: ${MATRIX_CONTACTS_BOT_ROOMS:-} MATRIX_CHAT_BOT_TOKEN: ${MATRIX_CHAT_BOT_TOKEN:-} - MATRIX_MANADECK_BOT_TOKEN: ${MATRIX_MANADECK_BOT_TOKEN:-} + MATRIX_CARDS_BOT_TOKEN: ${MATRIX_CARDS_BOT_TOKEN:-} MATRIX_NUTRIPHI_BOT_TOKEN: ${MATRIX_NUTRIPHI_BOT_TOKEN} MATRIX_NUTRIPHI_BOT_ROOMS: ${MATRIX_NUTRIPHI_BOT_ROOMS:-} MATRIX_PICTURE_BOT_TOKEN: ${MATRIX_PICTURE_BOT_TOKEN:-} @@ -1082,20 +1082,20 @@ services: retries: 3 start_period: 45s - manadeck-web: + cards-web: build: context: . - dockerfile: apps/manadeck/apps/web/Dockerfile - image: manadeck-web:local - container_name: mana-app-manadeck-web + dockerfile: apps/cards/apps/web/Dockerfile + image: cards-web:local + container_name: mana-app-cards-web restart: always mem_limit: 128m environment: NODE_ENV: production PORT: 5023 - PUBLIC_BACKEND_URL: http://manadeck-backend:3036 + PUBLIC_BACKEND_URL: http://cards-backend:3036 PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001 - PUBLIC_BACKEND_URL_CLIENT: https://manadeck-api.mana.how + PUBLIC_BACKEND_URL_CLIENT: https://cards-api.mana.how PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how ports: - "5023:5023" @@ -1808,6 +1808,7 @@ services: OUTPUT_FILE: /output/index.html volumes: - ./scripts/generate-status-page.sh:/generate.sh:ro + - ./packages/shared-branding/src/mana-apps.ts:/mana-apps.ts:ro - /Volumes/ManaData/landings/status:/output command: - sh diff --git a/docs/MAC_MINI_SERVER.md b/docs/MAC_MINI_SERVER.md index ea26d837a..675e3faf0 100644 --- a/docs/MAC_MINI_SERVER.md +++ b/docs/MAC_MINI_SERVER.md @@ -163,6 +163,25 @@ docker logs -f manacore-chat-backend # Live-Logs Zeigt probe_success und probe_duration_seconds aller Dienste via Blackbox Exporter (Port 9115). Alerts: WebAppDown (2 min), APIDown (1 min), InfraToolDown (3 min), GPUServiceDown (5 min), SlowHTTPResponse (5 min > 5s). +### Public Status Page (status.mana.how) + +Statische HTML-Seite, die alle 60 Sekunden vom Container `mana-status-gen` neu generiert wird. + +| Komponente | Pfad | +|---|---| +| Generator-Script | `scripts/generate-status-page.sh` | +| Docker-Service | `status-page-gen` in `docker-compose.macmini.yml` | +| Output | `/Volumes/ManaData/landings/status/index.html` + `status.json` | +| Nginx-Config | `docker/nginx/landings.conf` → `status.mana.how` | + +**Datenquellen:** +- **Service-Uptime:** VictoriaMetrics via Blackbox Exporter (`probe_success`, `probe_duration_seconds`) +- **App Release Tiers:** Automatisch aus `packages/shared-branding/src/mana-apps.ts` geparst (per awk, read-only Volume-Mount). Zeigt welche Apps in welchem Tier (founder/alpha/beta/public) sind. + +**Automatische Aktualisierung:** Änderungen an `requiredTier` in `mana-apps.ts` werden nach dem nächsten `git pull` auf dem Server automatisch beim nächsten 60s-Refresh auf der Statusseite sichtbar — kein Container-Restart nötig, da die Datei live gemountet ist. + +**`status.json`** wird parallel generiert und enthält Service-Status + Tier-Daten als JSON (genutzt von ManaScore Live-Badges). + ### Service Management ```bash diff --git a/scripts/generate-status-page.sh b/scripts/generate-status-page.sh index 7cd1bc2de..7243c1ee8 100755 --- a/scripts/generate-status-page.sh +++ b/scripts/generate-status-page.sh @@ -4,7 +4,11 @@ # Läuft in einem Alpine-Container im Docker-Netz (http://victoriametrics:9090) # oder auf dem Host (http://localhost:9090). # -# Ausgabe: /output/index.html (gemountet als /Volumes/ManaData/landings/status/) +# Datenquellen: +# - Service-Uptime: Blackbox Exporter via VictoriaMetrics +# - App Release Tiers: Automatisch aus mana-apps.ts geparst (Volume-Mount) +# +# Ausgabe: /output/index.html + /output/status.json set -eu @@ -136,6 +140,97 @@ fi TIMESTAMP="$(date -u '+%d. %B %Y, %H:%M Uhr UTC')" +# ── App Release Tiers (automatisch aus mana-apps.ts) ──────────────────────── +# Gemountet als /mana-apps.ts (read-only) im Container. +# Format je Zeile: name|tier|status + +MANA_APPS_TS="${MANA_APPS_TS:-/mana-apps.ts}" + +if [ -f "$MANA_APPS_TS" ]; then + TIER_APPS="$(awk ' + /^export const MANA_APPS/ { inside=1; next } + /^];/ { inside=0 } + !inside { next } + /^\t\{/ { name=""; tier=""; st=""; archived=0 } + /^\t\tname:/ { gsub(/.*name:[[:space:]]*'\''/, ""); gsub(/'\''.*/, ""); name=$0 } + /^\t\trequiredTier:/ { gsub(/.*requiredTier:[[:space:]]*'\''/, ""); gsub(/'\''.*/, ""); tier=$0 } + /^\t\tstatus:/ { gsub(/.*status:[[:space:]]*'\''/, ""); gsub(/'\''.*/, ""); st=$0 } + /^\t\tarchived:[[:space:]]*true/ { archived=1 } + /^\t\},/ { if (name != "" && tier != "" && archived == 0) print name "|" tier "|" st } + ' "$MANA_APPS_TS")" +else + echo "$(date '+%H:%M:%S') WARNUNG: $MANA_APPS_TS nicht gefunden — Tier-Sektion wird übersprungen" + TIER_APPS="" +fi + +render_tier_rows() { + tier="$1" + echo "$TIER_APPS" | while IFS='|' read -r name t status; do + [ -z "$name" ] && continue + [ "$t" != "$tier" ] && continue + case "$status" in + published) status_class="published"; status_label="Published" ;; + beta) status_class="beta-status"; status_label="Beta" ;; + development) status_class="dev-status"; status_label="Development" ;; + planning) status_class="plan-status"; status_label="Planned" ;; + *) status_class="dev-status"; status_label="$status" ;; + esac + printf '%s%s\n' \ + "$name" "$status_class" "$status_label" + done +} + +count_tier() { + tier="$1" + echo "$TIER_APPS" | grep -c "|${tier}|" || echo "0" +} + +tier_founder_count="$(count_tier founder)" +tier_alpha_count="$(count_tier alpha)" +tier_beta_count="$(count_tier beta)" +tier_public_count="$(count_tier public)" + +render_tier_section() { + [ -z "$TIER_APPS" ] && return + cat << TIEREOF +
+
+

App Release Tiers

+ ${tier_public_count} public · ${tier_beta_count} beta · ${tier_alpha_count} alpha · ${tier_founder_count} founder +
+
+
+
+

Founder

+ ${tier_founder_count} Apps +
+ +$(render_tier_rows founder) +
+
+
+
+

Alpha

+ ${tier_alpha_count} Apps +
+ +$(render_tier_rows alpha) +
+
+
+
+

Beta

+ ${tier_beta_count} Apps +
+ +$(render_tier_rows beta) +
+
+
+
+TIEREOF +} + # ── HTML generieren ────────────────────────────────────────────────────────── cat > "${OUTPUT}.tmp" << HTMLEOF @@ -275,6 +370,34 @@ cat > "${OUTPUT}.tmp" << HTMLEOF .no-data { color: var(--muted); font-size: 13px; text-align: center; padding: 20px; } + /* ── Tier Section ── */ + .tier-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; } + .tier-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; } + .tier-card-header { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 16px; border-bottom: 1px solid var(--border); + } + .tier-card-header h3 { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; } + .tier-card-header .tier-count { font-size: 12px; color: var(--muted); } + .tier-card table { border: none; border-radius: 0; } + .tier-card tr:last-child { border-bottom: none; } + .tier-card td { padding: 8px 16px; } + .tier-card .name { font-size: 14px; } + + .tier-founder .tier-card-header { background: rgba(168,85,247,.08); } + .tier-founder .tier-card-header h3 { color: #a855f7; } + .tier-alpha .tier-card-header { background: rgba(245,158,11,.08); } + .tier-alpha .tier-card-header h3 { color: #f59e0b; } + .tier-beta .tier-card-header { background: rgba(59,130,246,.08); } + .tier-beta .tier-card-header h3 { color: #3b82f6; } + .tier-public .tier-card-header { background: rgba(34,197,94,.08); } + .tier-public .tier-card-header h3 { color: var(--green); } + + .badge.published { background: rgba(34,197,94,.12); color: var(--green); } + .badge.beta-status { background: rgba(59,130,246,.12); color: #3b82f6; } + .badge.dev-status { background: rgba(245,158,11,.12); color: #f59e0b; } + .badge.plan-status { background: rgba(102,102,102,.15); color: var(--muted); } + /* ── Footer ── */ footer { margin-top: 40px; @@ -357,6 +480,8 @@ $(render_rows blackbox-gpu) +$(render_tier_section) + @@ -374,10 +499,22 @@ echo "$(date '+%H:%M:%S') Status-Seite generiert → $OUTPUT (${total_up}/${tota JSON_OUTPUT="$(dirname "$OUTPUT")/status.json" TIMESTAMP_ISO="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" +# Tier-Daten als JSON-Array für jq +TIER_JSON="[]" +if [ -n "$TIER_APPS" ]; then + TIER_JSON="$(echo "$TIER_APPS" | awk -F'|' ' + BEGIN { printf "[" } + NR>1 { printf "," } + { printf "{\"name\":\"%s\",\"tier\":\"%s\",\"status\":\"%s\"}", $1, $2, $3 } + END { printf "]" } + ')" +fi + echo "$SUCCESS_JSON" | jq \ --arg ts "$TIMESTAMP_ISO" \ --argjson total_up "$total_up" \ --argjson total_all "$total_all" \ + --argjson tiers "$TIER_JSON" \ '{ updated: $ts, summary: { up: $total_up, total: $total_all }, @@ -394,7 +531,8 @@ echo "$SUCCESS_JSON" | jq \ ), value: (.value[1] == "1") }) | from_entries - ) + ), + tiers: ($tiers | group_by(.tier) | map({ key: .[0].tier, value: map({name, status}) }) | from_entries) }' > "${JSON_OUTPUT}.tmp" && mv "${JSON_OUTPUT}.tmp" "$JSON_OUTPUT" echo "$(date '+%H:%M:%S') status.json generiert → $JSON_OUTPUT"