From 6ff401ad74efac69bb559fb8447e9cc1d694f18a Mon Sep 17 00:00:00 2001 From: Kroonk Date: Thu, 21 May 2026 18:41:11 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20add=20consolidated=20=C3=9Cbersicht=20(?= =?UTF-8?q?Overview)=20dashboard=20tab=20with=20real-time=20mini-cards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.html | 43 +++++++++++++- admin.js | 83 +++++++++++++++++++++++++- style.css | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 3 deletions(-) diff --git a/admin.html b/admin.html index e4fb1cc..c4ed138 100644 --- a/admin.html +++ b/admin.html @@ -6,7 +6,7 @@ MischLabs Admin - +
@@ -48,6 +48,41 @@ + + +
+
+
+
+ 🌐 +

Dienste-Status

+
+
Lade...
+
+
+
+ 💾 +

NAS Speicher

+
+
Lade...
+
+
+
+ 🐳 +

Watchtower

+
+
Lade...
+
+
+
+ 📦 +

Container

+
+
Lade...
+
+
+
+
@@ -109,6 +144,10 @@
- + diff --git a/admin.js b/admin.js index 2bd20f2..3ada7e5 100644 --- a/admin.js +++ b/admin.js @@ -394,6 +394,21 @@ function renderServicesStatus(services = []) { `; }).join(''); + + const miniServices = document.getElementById('mini-services-status'); + if (miniServices) { + let ledHtml = itemsToRender.map(item => { + const ok = item.status.ok; + return ``; + }).join(''); + miniServices.innerHTML = ` +
+ ${offlineCount > 0 ? `${offlineCount} offline` : `Alle online`} + (${totalCount} Dienste) +
+
${ledHtml}
+ `; + } } function renderDisks(disks = []) { @@ -416,6 +431,21 @@ function renderDisks(disks = []) {
`; }).join(''); + + const miniDisks = document.getElementById('mini-disks-status'); + if (miniDisks) { + miniDisks.innerHTML = disks.map((disk) => { + if (disk.error) return `
${disk.label}: Fehler
`; + return ` +
+
${disk.label}${disk.usedPercent}%
+
+ +
+
+ `; + }).join(''); + } } function renderWatchtower(watchtower) { @@ -437,6 +467,23 @@ function renderWatchtower(watchtower) {
Status aktualisiert${statusGenerated}
`; + const miniWatchtower = document.getElementById('mini-watchtower-status'); + if (miniWatchtower) { + let statusText = 'Keine Daten'; + let subText = ''; + if (lastManualWatchtowerRun) { + statusText = lastManualWatchtowerRun.running ? 'Läuft manuell...' : 'Manuell beendet'; + subText = lastManualWatchtowerRun.startedAt ? new Date(lastManualWatchtowerRun.startedAt).toLocaleTimeString('de-DE') : ''; + } else if (officialSession) { + statusText = 'Up-to-date'; + subText = `Lauf: ${officialSession.finishedAt ? new Date(officialSession.finishedAt).toLocaleDateString('de-DE') : '--'}`; + } + miniWatchtower.innerHTML = ` +
${statusText}
+
${subText}
+ `; + } + if (lastManualWatchtowerRun) { const run = lastManualWatchtowerRun; const session = run.session; @@ -498,6 +545,32 @@ function renderContainers(containers) { containerStatus.querySelectorAll('.restart-container').forEach((button) => { button.addEventListener('click', () => restartContainer(button.closest('.container-line'))); }); + + const miniContainers = document.getElementById('mini-containers-status'); + if (miniContainers && Array.isArray(containers)) { + const importantList = [ + 'mischlabs', + 'watchtower', + 'gitea', + 'gitea-runner', + 'auth', + 'nextcloud_app', + 'vaultwarden', + 'photography-website', + 'michess', + 'calibre-web', + 'audiobookshelf' + ]; + const activeImportant = containers.filter(c => importantList.includes(c.name) && c.state === 'running').length; + const totalImportant = containers.filter(c => importantList.includes(c.name)).length; + miniContainers.innerHTML = ` +
+ ${activeImportant} / ${totalImportant} + online +
+
${containers.length} Container insgesamt
+ `; + } } async function restartContainer(row) { @@ -746,13 +819,14 @@ function initCollapsibleCards() { }); } -let activeTab = localStorage.getItem('active_admin_tab') || 'services'; +let activeTab = localStorage.getItem('active_admin_tab') || 'overview'; function switchTab(tabId) { activeTab = tabId; localStorage.setItem('active_admin_tab', tabId); const tabCards = { + overview: document.getElementById('card-overview'), services: document.getElementById('card-services'), disks: document.getElementById('card-disks'), watchtower: document.getElementById('card-watchtower'), @@ -807,6 +881,13 @@ function initTabNavigation() { }); }); + // Setup click handlers for mini cards inside the Overview tab + document.querySelectorAll('.overview-mini-card[data-target-tab]').forEach((card) => { + card.addEventListener('click', () => { + switchTab(card.dataset.targetTab); + }); + }); + switchTab(activeTab); } diff --git a/style.css b/style.css index 06fd30e..1ef4ef7 100644 --- a/style.css +++ b/style.css @@ -1268,3 +1268,172 @@ main { transform: translateY(0); } } + +/* ========================================================================== + Dashboard Overview Cards & Grid + ========================================================================== */ +.overview-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + margin-bottom: 24px; +} + +@media (max-width: 620px) { + .overview-grid { + grid-template-columns: 1fr; + } +} + +.overview-mini-card { + background: rgba(17, 22, 36, 0.45); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 20px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); + display: flex; + flex-direction: column; + gap: 14px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); +} + +.overview-mini-card:hover { + background: rgba(23, 29, 45, 0.65); + border-color: rgba(103, 232, 249, 0.3); + transform: translateY(-3px); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.35), 0 0 15px rgba(103, 232, 249, 0.08); +} + +.mini-card-head { + display: flex; + align-items: center; + gap: 10px; +} + +.mini-card-icon { + font-size: 1.3rem; +} + +.mini-card-head h4 { + margin: 0; + font-size: 1.05rem; + font-weight: 700; + color: #fff; + letter-spacing: -0.01em; +} + +.mini-card-body { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 0.92rem; + color: var(--text); +} + +.mini-status-text { + display: flex; + align-items: baseline; + gap: 6px; +} + +.mini-status-total { + color: var(--dim); + font-size: 0.8rem; +} + +.mini-status-sub { + font-size: 0.8rem; + color: var(--dim); + margin-top: 2px; +} + +/* LEDs for Services */ +.mini-led-grid { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 6px; +} + +.mini-led { + width: 9px; + height: 9px; + border-radius: 50%; + display: inline-block; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.mini-led:hover { + transform: scale(1.35); +} + +.mini-led-ok { + background-color: var(--ok); + box-shadow: 0 0 6px rgba(52, 211, 153, 0.4); +} + +.mini-led-down { + background-color: var(--down); + box-shadow: 0 0 6px rgba(251, 113, 133, 0.4); +} + +/* Disks Mini Rows */ +.mini-disk-row { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 2px; +} + +.mini-disk-row:last-child { + margin-bottom: 0; +} + +.mini-disk-row.is-error { + color: var(--down); + font-weight: 500; +} + +.mini-disk-meta { + display: flex; + justify-content: space-between; + font-size: 0.82rem; +} + +.mini-disk-meta strong { + color: #fff; + font-weight: 600; +} + +/* Slim progress bars in Mini overview */ +.usage-bar.mini-bar { + height: 5px; + background: rgba(255, 255, 255, 0.05); + border-radius: 99px; + overflow: hidden; + position: relative; + width: 100%; +} + +.usage-bar.mini-bar span { + display: block; + height: 100%; + border-radius: 99px; + transition: width 0.4s ease; +} + +.usage-bar.mini-bar[data-level="ok"] span { + background-color: var(--ok); + box-shadow: 0 0 4px rgba(52, 211, 153, 0.3); +} + +.usage-bar.mini-bar[data-level="warn"] span { + background-color: var(--warn); + box-shadow: 0 0 4px rgba(251, 191, 36, 0.3); +} + +.usage-bar.mini-bar[data-level="danger"] span { + background-color: var(--down); + box-shadow: 0 0 4px rgba(251, 113, 133, 0.3); +}