Add NAS status proxy
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 33s

This commit is contained in:
Kroonk
2026-05-21 16:22:34 +02:00
parent f0674cda37
commit 29b3b623eb
10 changed files with 570 additions and 18 deletions

104
admin.js
View File

@@ -18,6 +18,10 @@ const checkAllButton = document.querySelector('#checkAllButton');
const saveLocalButton = document.querySelector('#saveLocalButton');
const resetLocalButton = document.querySelector('#resetLocalButton');
const downloadConfigButton = document.querySelector('#downloadConfigButton');
const refreshOpsButton = document.querySelector('#refreshOpsButton');
const diskStatus = document.querySelector('#diskStatus');
const watchtowerStatus = document.querySelector('#watchtowerStatus');
const containerStatus = document.querySelector('#containerStatus');
let config = null;
let tokenSet = null;
@@ -271,6 +275,104 @@ function downloadConfig() {
URL.revokeObjectURL(url);
}
function formatBytes(bytes) {
if (!Number.isFinite(bytes)) return '--';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let value = bytes;
let unit = 0;
while (value >= 1024 && unit < units.length - 1) {
value /= 1024;
unit += 1;
}
return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`;
}
function renderDisks(disks = []) {
diskStatus.innerHTML = disks.map((disk) => {
if (disk.error) {
return `<div class="ops-line"><strong>${disk.label}</strong><span>${disk.error}</span></div>`;
}
const level = disk.usedPercent >= 90 ? 'danger' : disk.usedPercent >= 80 ? 'warn' : 'ok';
return `
<div class="ops-line">
<strong>${disk.label}</strong>
<span>${formatBytes(disk.used)} / ${formatBytes(disk.total)} (${disk.usedPercent}%)</span>
</div>
<div class="usage-bar" data-level="${level}">
<span style="width:${Math.min(disk.usedPercent, 100)}%"></span>
</div>
`;
}).join('');
}
function renderWatchtower(watchtower) {
const session = watchtower?.lastSession;
if (!session) {
watchtowerStatus.textContent = 'Kein Watchtower-Lauf gefunden.';
return;
}
const finished = session.finishedAt ? new Date(session.finishedAt).toLocaleString('de-DE') : '--';
watchtowerStatus.innerHTML = `
<div class="ops-line"><strong>Letzter Lauf</strong><span>${finished}</span></div>
<div class="ops-line"><strong>Geprueft</strong><span>${session.scanned ?? '--'}</span></div>
<div class="ops-line"><strong>Aktualisiert</strong><span>${session.updated ?? '--'}</span></div>
<div class="ops-line"><strong>Fehler</strong><span>${session.failed ?? '--'}</span></div>
`;
}
function renderContainers(containers) {
if (!Array.isArray(containers)) {
containerStatus.textContent = containers?.error || 'Containerdaten nicht verfuegbar.';
return;
}
const important = [
'mischlabs',
'watchtower',
'gitea',
'gitea-runner',
'auth',
'nextcloud_app',
'vaultwarden',
'photography-website',
'michess',
'calibre-web',
'audiobookshelf'
];
const rows = containers
.filter((container) => important.includes(container.name))
.sort((a, b) => important.indexOf(a.name) - important.indexOf(b.name));
containerStatus.innerHTML = rows.map((container) => `
<div class="ops-line">
<strong>${container.name}</strong>
<span>${container.status}</span>
</div>
`).join('');
}
async function refreshOps() {
diskStatus.textContent = 'Lade...';
watchtowerStatus.textContent = 'Lade...';
containerStatus.textContent = 'Lade...';
try {
const response = await fetch('/api/status', { cache: 'no-store' });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const status = await response.json();
renderDisks(status.disks);
renderWatchtower(status.watchtower);
renderContainers(status.containers);
} catch (error) {
diskStatus.textContent = `Statusproxy nicht erreichbar: ${error.message}`;
watchtowerStatus.textContent = 'Nicht verfuegbar.';
containerStatus.textContent = 'Nicht verfuegbar.';
}
}
async function boot() {
await handleCallback();
tokenSet = getStoredTokens();
@@ -293,6 +395,7 @@ async function boot() {
logoutButton.hidden = false;
adminUser.textContent = `Angemeldet als ${claims.preferred_username || claims.email}`;
renderEditor();
refreshOps();
}
loginButton.addEventListener('click', login);
@@ -322,6 +425,7 @@ resetLocalButton.addEventListener('click', () => {
});
downloadConfigButton.addEventListener('click', downloadConfig);
refreshOpsButton.addEventListener('click', refreshOps);
boot().catch((error) => {
loginMessage.textContent = `Adminseite konnte nicht starten: ${error.message}`;