feat: add consolidated Übersicht (Overview) dashboard tab with real-time mini-cards
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 13s

This commit is contained in:
Kroonk
2026-05-21 18:41:11 +02:00
parent 32bed2d65e
commit 6ff401ad74
3 changed files with 292 additions and 3 deletions

View File

@@ -394,6 +394,21 @@ function renderServicesStatus(services = []) {
</a>
`;
}).join('');
const miniServices = document.getElementById('mini-services-status');
if (miniServices) {
let ledHtml = itemsToRender.map(item => {
const ok = item.status.ok;
return `<span class="mini-led ${ok ? 'mini-led-ok' : 'mini-led-down'}" title="${item.name}: ${ok ? 'Online' : 'Offline'}"></span>`;
}).join('');
miniServices.innerHTML = `
<div class="mini-status-text">
${offlineCount > 0 ? `<span style="color: var(--down); font-weight: 700;">${offlineCount} offline</span>` : `<span style="color: var(--ok); font-weight: 700;">Alle online</span>`}
<span class="mini-status-total">(${totalCount} Dienste)</span>
</div>
<div class="mini-led-grid">${ledHtml}</div>
`;
}
}
function renderDisks(disks = []) {
@@ -416,6 +431,21 @@ function renderDisks(disks = []) {
</div>
`;
}).join('');
const miniDisks = document.getElementById('mini-disks-status');
if (miniDisks) {
miniDisks.innerHTML = disks.map((disk) => {
if (disk.error) return `<div class="mini-disk-row is-error"><strong>${disk.label}</strong>: Fehler</div>`;
return `
<div class="mini-disk-row">
<div class="mini-disk-meta"><strong>${disk.label}</strong><span>${disk.usedPercent}%</span></div>
<div class="usage-bar mini-bar" data-level="${disk.usedPercent >= 90 ? 'danger' : disk.usedPercent >= 80 ? 'warn' : 'ok'}">
<span style="width: ${disk.usedPercent}%"></span>
</div>
</div>
`;
}).join('');
}
}
function renderWatchtower(watchtower) {
@@ -437,6 +467,23 @@ function renderWatchtower(watchtower) {
<div class="ops-line"><strong>Status aktualisiert</strong><span>${statusGenerated}</span></div>
`;
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 = `
<div class="mini-status-text"><span style="color: #67e8f9; font-weight: 700;">${statusText}</span></div>
<div class="mini-status-sub">${subText}</div>
`;
}
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 = `
<div class="mini-status-text">
<span style="color: var(--ok); font-weight: 700;">${activeImportant} / ${totalImportant}</span>
<span class="mini-status-total">online</span>
</div>
<div class="mini-status-sub">${containers.length} Container insgesamt</div>
`;
}
}
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);
}