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
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 13s
This commit is contained in:
83
admin.js
83
admin.js
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user