From 470220b8f4c37bd88328aefc125261ae4f74786b Mon Sep 17 00:00:00 2001 From: Kroonk Date: Thu, 21 May 2026 15:11:52 +0200 Subject: [PATCH] Rework MischLabs into service dashboard --- Brain.md | 28 +- Dockerfile | 2 + app.js | 106 +++++++ index.html | 366 ++++++++++++++---------- manifest.webmanifest | 4 +- nginx.conf | 3 +- offline.html | 23 ++ style.css | 647 ++++++++++++++++++++++++------------------- sw.js | 10 +- 9 files changed, 750 insertions(+), 439 deletions(-) create mode 100644 app.js create mode 100644 offline.html diff --git a/Brain.md b/Brain.md index fc8d3ed..f301304 100644 --- a/Brain.md +++ b/Brain.md @@ -3,12 +3,15 @@ ## Projektziel Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln mit direkten Verlinkungen anzeigt. Laeuft als Docker-Container und wird ueber Git auto-deployed. +Aktueller Stand: Die Seite ist keine reine Linkliste mehr, sondern ein kompaktes Service-Dashboard mit Kategorien, Suche, Hintergrund-Statuscheck und PWA-Unterstuetzung. + ## Status - [x] Git-Repo initialisiert und Remote verbunden (https://git.mischlabs.de/MrDiderot/mischlabs.git) - [x] Dienste-Liste vom Screenshot erfasst (9 Services inkl. MiChess) - [x] Landing Page Design und Implementierung (index.html + style.css) - [x] Docker-Setup (Dockerfile, docker-compose.yml, nginx.conf) - [x] PWA-Grundlage fuer iOS/Home-Screen-Installation +- [x] Dashboard v2 mit Kategorien, Suche und Live-Statusindikatoren - [x] Push zum Remote - [x] MiChess-Kachel hinzugefuegt (Commit lokal, Push steht aus wg. Auth-Fehler) - [ ] Push des MiChess-Commits (Git-Auth-Token erneuern) @@ -32,7 +35,7 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m | 8 | tom.mischlabs.de | Persoenliche Seite | #ec4899 (Pink) | ## Technische Entscheidungen -- **Reines HTML + CSS mit minimalem PWA-JS** (kein Framework) - maximale Performance, minimales Docker-Image +- **Reines HTML + CSS mit minimalem Dashboard-/PWA-JS** (kein Framework) - maximale Performance, minimales Docker-Image - **nginx:alpine** als Base-Image - extrem leichtgewichtig (~5MB) - **Google Fonts (Inter)** fuer moderne Typografie - **Inline SVG Icons** - keine externen Dependencies @@ -40,6 +43,8 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m - **Dark Theme** mit subtilen Glow-Effekten - **Staggered Animations**: Kacheln faden nacheinander ein (9 Stueck) - **PWA**: Manifest, Apple-Touch-Icon, App-Icons und Service Worker fuer installierbare iOS/Web-App +- **Dashboard**: Kategorien `Zugang`, `Medien`, `Arbeit`, `Persoenlich`, Suche und clientseitige no-cors Statuschecks +- **Auth-Link**: Zeigt auf die Keycloak Account Console (`/realms/mischlabs/account`), nicht mehr auf die Admin-Konsole ## Docker-Setup - **Container-Name**: `mischlabs` @@ -53,6 +58,8 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m mischlabs/ index.html - Haupt-HTML mit allen 9 Service-Kacheln style.css - Komplettes Styling (Dark Theme, Grid, Animationen) + app.js - Suche, Filter, Statuschecks, Service-Worker-Registrierung + offline.html - Offline-Fallback fuer installierte PWA manifest.webmanifest - PWA-Metadaten fuer installierbare App sw.js - Schlanker Service Worker mit App-Shell-Cache icons/ - App-Icons fuer iOS/PWA @@ -98,8 +105,19 @@ mischlabs/ - `icons/icon-512.png` (512x512) - `icons/maskable-512.png` (512x512, maskable) - Service Worker: - - Cache-Name: `mischlabs-pwa-v1` - - Cached die App-Shell (`/`, `index.html`, `style.css?v=8`, Manifest und Icons) - - Navigationen fallen offline auf `index.html` zurueck + - Cache-Name: `mischlabs-pwa-v3` + - Cached die App-Shell (`/`, `index.html`, `offline.html`, `style.css?v=9`, `app.js?v=1`, Manifest und Icons) + - Navigationen fallen offline auf `offline.html` zurueck - Dockerfile kopiert Manifest, Service Worker und Icons in das Nginx-Image. -- `nginx.conf` liefert `manifest.webmanifest` mit `application/manifest+json` und `sw.js` mit `no-cache` aus. +- `nginx.conf` liefert `manifest.webmanifest` mit `application/manifest+json` und `sw.js` mit harten No-Cache-Headern aus. + +## Dashboard-v2-Stand 2026-05-21 +- Neues Logo oben nutzt `icons/app-icon-source.png`. +- Die alte oeffentliche Auth-Kachel wurde auf die Keycloak Account Console umgebogen: + `https://auth.mischlabs.de/realms/mischlabs/account` +- Kategorien: + - Zugang: SSO, Drive, Passwords + - Medien: Movies, Audiobooks, Books + - Arbeit: Git, MiChess + - Persoenlich: Tom +- `app.js` prueft Dienste im Hintergrund per `fetch(..., { mode: "no-cors" })`. Das ist bewusst nur ein Erreichbarkeitsindikator, kein vollwertiges Monitoring. diff --git a/Dockerfile b/Dockerfile index 899c79f..a7d4057 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,9 @@ FROM nginx:alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY index.html /usr/share/nginx/html/ +COPY offline.html /usr/share/nginx/html/ COPY style.css /usr/share/nginx/html/ +COPY app.js /usr/share/nginx/html/ COPY manifest.webmanifest /usr/share/nginx/html/ COPY sw.js /usr/share/nginx/html/ COPY icons/ /usr/share/nginx/html/icons/ diff --git a/app.js b/app.js new file mode 100644 index 0000000..e109a2c --- /dev/null +++ b/app.js @@ -0,0 +1,106 @@ +const cards = [...document.querySelectorAll('.service-card')]; +const sections = [...document.querySelectorAll('.section')]; +const searchInput = document.querySelector('#serviceSearch'); +const segments = [...document.querySelectorAll('.segment')]; +const emptyState = document.querySelector('#emptyState'); +const onlineCount = document.querySelector('#onlineCount'); +const lastChecked = document.querySelector('#lastChecked'); + +let activeFilter = 'all'; + +function normalize(value) { + return value.toLowerCase().trim(); +} + +function updateVisibility() { + const term = normalize(searchInput.value); + let visibleCards = 0; + + cards.forEach((card) => { + const categoryMatches = activeFilter === 'all' || card.dataset.category === activeFilter; + const searchMatches = !term || normalize(card.dataset.name).includes(term); + const isVisible = categoryMatches && searchMatches; + card.hidden = !isVisible; + if (isVisible) visibleCards += 1; + }); + + sections.forEach((section) => { + const hasVisibleCards = [...section.querySelectorAll('.service-card')].some((card) => !card.hidden); + section.hidden = !hasVisibleCards; + }); + + emptyState.hidden = visibleCards !== 0; +} + +function setActiveFilter(nextFilter) { + activeFilter = nextFilter; + segments.forEach((segment) => { + const isActive = segment.dataset.filter === nextFilter; + segment.classList.toggle('is-active', isActive); + segment.setAttribute('aria-selected', String(isActive)); + }); + updateVisibility(); +} + +async function probeService(card) { + const controller = new AbortController(); + const timeout = window.setTimeout(() => controller.abort(), 4500); + + try { + await fetch(card.dataset.url, { + method: 'GET', + mode: 'no-cors', + cache: 'no-store', + signal: controller.signal + }); + card.classList.add('is-online'); + card.classList.remove('is-offline'); + card.querySelector('.status-dot').setAttribute('aria-label', 'Online'); + return true; + } catch { + card.classList.add('is-offline'); + card.classList.remove('is-online'); + card.querySelector('.status-dot').setAttribute('aria-label', 'Nicht erreichbar'); + return false; + } finally { + window.clearTimeout(timeout); + } +} + +async function refreshStatus() { + const results = await Promise.allSettled(cards.map(probeService)); + const online = results.filter((result) => result.status === 'fulfilled' && result.value).length; + onlineCount.textContent = String(online); + lastChecked.textContent = `Status geprueft: ${new Date().toLocaleTimeString('de-DE', { + hour: '2-digit', + minute: '2-digit' + })}`; +} + +segments.forEach((segment) => { + segment.addEventListener('click', () => setActiveFilter(segment.dataset.filter)); +}); + +searchInput.addEventListener('input', updateVisibility); + +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(() => {}); + }); +} + +window.addEventListener('load', () => { + const run = () => refreshStatus().catch(() => { + onlineCount.textContent = '--'; + lastChecked.textContent = 'Status aktuell nicht verfuegbar.'; + }); + + if ('requestIdleCallback' in window) { + window.requestIdleCallback(run, { timeout: 2000 }); + } else { + window.setTimeout(run, 600); + } +}); + +updateVisibility(); + diff --git a/index.html b/index.html index 68ac57a..92e6ee6 100644 --- a/index.html +++ b/index.html @@ -3,178 +3,254 @@ - MischLabs - Services - - + MischLabs - Dashboard + + - + - + -
-
- -
-