diff --git a/admin.html b/admin.html index bfba291..f80a817 100644 --- a/admin.html +++ b/admin.html @@ -6,7 +6,7 @@ MischLabs Admin - +
@@ -49,7 +49,7 @@
- Hinweis: Ohne Backend speichert diese Adminseite Aenderungen lokal im Browser und kann eine neue `services.json` exportieren. Fuer globale Live-Aenderungen bauen wir als naechsten Schritt ein kleines Backend oder einen Gitea-Commit-Workflow. + Hinweis: Betriebsdaten kommen aus dem MischLabs-Statusproxy. Kachel-Aenderungen werden aktuell lokal im Browser gespeichert oder als `services.json` exportiert; fuer globale Live-Aenderungen folgt spaeter ein Gitea-Commit-Workflow.
@@ -80,6 +80,6 @@ - + diff --git a/admin.js b/admin.js index 2246632..cb5e627 100644 --- a/admin.js +++ b/admin.js @@ -290,7 +290,10 @@ function formatBytes(bytes) { function renderDisks(disks = []) { diskStatus.innerHTML = disks.map((disk) => { if (disk.error) { - return `
${disk.label}${disk.error}
`; + const hint = disk.mount + ? `Mount fehlt: ${disk.mount}` + : disk.error; + return `
${disk.label}${hint}
`; } const level = disk.usedPercent >= 90 ? 'danger' : disk.usedPercent >= 80 ? 'warn' : 'ok'; @@ -309,7 +312,10 @@ function renderDisks(disks = []) { function renderWatchtower(watchtower) { const session = watchtower?.lastSession; if (!session) { - watchtowerStatus.textContent = 'Kein Watchtower-Lauf gefunden.'; + const tail = watchtower?.tail?.slice(-4)?.join(' | '); + watchtowerStatus.textContent = tail + ? `Kein Lauf geparst. Letzte Logs: ${tail}` + : 'Kein Watchtower-Lauf gefunden.'; return; } diff --git a/index.html b/index.html index 73ca94d..191dd96 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@ - +
diff --git a/offline.html b/offline.html index 7fac260..3dfd458 100644 --- a/offline.html +++ b/offline.html @@ -5,7 +5,7 @@ MischLabs - Offline - +
diff --git a/server.js b/server.js index 58f1bf5..0281f9d 100644 --- a/server.js +++ b/server.js @@ -11,8 +11,8 @@ const PUBLIC_DIR = fs.existsSync(dockerPublicDir) ? dockerPublicDir : __dirname; const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock'; const WATCHTOWER_CONTAINER = process.env.WATCHTOWER_CONTAINER || 'watchtower'; const DISK_TARGETS = [ - { id: 'volume1', label: 'Volume 1', path: process.env.STATUS_VOLUME1_PATH || '/host/volume1' }, - { id: 'volume2', label: 'Volume 2', path: process.env.STATUS_VOLUME2_PATH || '/host/volume2' }, + { id: 'volume1', label: 'Volume 1', path: process.env.STATUS_VOLUME1_PATH || '/host/volume1', mount: '/volume1:/host/volume1:ro' }, + { id: 'volume2', label: 'Volume 2', path: process.env.STATUS_VOLUME2_PATH || '/host/volume2', mount: '/volume2:/host/volume2:ro' }, { id: 'root', label: 'Root', path: '/' } ]; @@ -164,7 +164,13 @@ function parseWatchtower(lines) { const timeMatch = line.match(/time="([^"]+)"/); const time = timeMatch?.[1] || null; - if (line.includes('Running a one time update.') || line.includes('Checking all containers')) { + if ( + line.includes('Running a one time update.') + || line.includes('Checking all containers') + || line.includes('Found new') + || line.includes('Stopping /') + || line.includes('Creating /') + ) { current = current || { startedAt: time, found: [], warnings: [] }; } @@ -178,7 +184,7 @@ function parseWatchtower(lines) { current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, '')); } - const done = line.match(/Session done" Failed=(\d+) Scanned=(\d+) Updated=(\d+)/); + const done = line.match(/Session done(?:\\"|") Failed=(\d+) Scanned=(\d+) Updated=(\d+)/); if (done) { current = current || { startedAt: time, found: [], warnings: [] }; current.finishedAt = time; @@ -190,7 +196,24 @@ function parseWatchtower(lines) { } } - return sessions.at(-1) || null; + if (sessions.length) return sessions.at(-1); + + const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done')); + const done = lastDoneLine?.match(/Failed=(\d+) Scanned=(\d+) Updated=(\d+)/); + if (done) { + const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null; + return { + startedAt: null, + finishedAt: time, + failed: Number(done[1]), + scanned: Number(done[2]), + updated: Number(done[3]), + found: [], + warnings: [] + }; + } + + return null; } function probeUrl(target) { diff --git a/style.css b/style.css index ec3d5e9..155445d 100644 --- a/style.css +++ b/style.css @@ -555,6 +555,10 @@ main { text-align: right; } +.ops-line.is-warning span { + color: #fde68a; +} + .usage-bar { height: 7px; overflow: hidden; diff --git a/sw.js b/sw.js index bd97fd7..9edd277 100644 --- a/sw.js +++ b/sw.js @@ -1,9 +1,9 @@ -const CACHE_NAME = 'mischlabs-pwa-v6'; +const CACHE_NAME = 'mischlabs-pwa-v7'; const APP_SHELL = [ '/', '/index.html', '/offline.html', - '/style.css?v=12', + '/style.css?v=13', '/app.js?v=3', '/services.json?v=1', '/manifest.webmanifest?v=3',