diff --git a/server.js b/server.js index 33bd47a..05c60c4 100644 --- a/server.js +++ b/server.js @@ -501,36 +501,102 @@ function parseWatchtower(lines) { let current = null; for (const line of lines) { - const timeMatch = line.match(/time="([^"]+)"/); - const time = timeMatch?.[1] || null; + let time = null; + let isStart = false; + let isFound = false; + let isWarning = false; + let isDone = false; + let msg = ''; + + let failed = 0; + let scanned = 0; + let updated = 0; - if ( - line.includes('Running a one time update.') - || line.includes('Checking all containers') - || line.includes('Found new') - || line.includes('Stopping /') - || line.includes('Creating /') - ) { + // Try parsing as JSON first + try { + const parsed = JSON.parse(line); + time = parsed.time || null; + msg = parsed.msg || parsed.message || ''; + + if ( + msg.includes('Running a one time update.') + || msg.includes('Checking all containers') + || msg.includes('Found new') + || msg.includes('Stopping /') + || msg.includes('Creating /') + ) { + isStart = true; + } + + if (msg.includes('Found new')) { + isFound = true; + } + + if (parsed.level === 'warning' || parsed.level === 'warn') { + isWarning = true; + } + + if (msg.includes('Session done')) { + isDone = true; + failed = Number(parsed.Failed ?? parsed.failed ?? 0); + scanned = Number(parsed.Scanned ?? parsed.scanned ?? 0); + updated = Number(parsed.Updated ?? parsed.updated ?? 0); + } + } catch { + // Fallback: Parse as standard text logfmt + const timeMatch = line.match(/time="([^"]+)"/); + time = timeMatch?.[1] || null; + + if ( + line.includes('Running a one time update.') + || line.includes('Checking all containers') + || line.includes('Found new') + || line.includes('Stopping /') + || line.includes('Creating /') + ) { + isStart = true; + } + + if (line.includes('Found new')) { + isFound = true; + const msgMatch = line.match(/msg="([^"]+)"/); + msg = msgMatch?.[1] || line; + } + + if (line.includes('level=warning') || line.includes('level=warn')) { + isWarning = true; + const msgMatch = line.match(/msg="([^"]+)"/); + msg = msgMatch?.[1] || line; + } + + const doneMatch = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); + if (doneMatch) { + isDone = true; + failed = Number(doneMatch[1]); + scanned = Number(doneMatch[2]); + updated = Number(doneMatch[3]); + } + } + + // Process parsed information + if (isStart) { current = current || { startedAt: time, found: [], warnings: [] }; } - if (line.includes('Found new')) { - current = current || { startedAt: time, found: [], warnings: [] }; - current.found.push(line.replace(/^.*msg="/, '').replace(/"$/, '')); + if (isFound && current) { + current.found.push(msg || line); } - if (line.includes('level=warning')) { - current = current || { startedAt: time, found: [], warnings: [] }; - current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, '')); + if (isWarning && current) { + current.warnings.push(msg || line); } - const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); - if (done) { + if (isDone) { current = current || { startedAt: time, found: [], warnings: [] }; current.finishedAt = time; - current.failed = Number(done[1]); - current.scanned = Number(done[2]); - current.updated = Number(done[3]); + current.failed = failed; + current.scanned = scanned; + current.updated = updated; sessions.push(current); current = null; } @@ -538,19 +604,37 @@ function parseWatchtower(lines) { if (sessions.length) return sessions.at(-1); - const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done')); - const done = lastDoneLine?.match(/Failed=(\d+)\s+Scanned=(\d+)\s+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: [] - }; + // Ultimate fallback if no full session was structured sequentially but a "Session done" exists + for (const line of [...lines].reverse()) { + try { + const parsed = JSON.parse(line); + const msg = parsed.msg || parsed.message || ''; + if (msg.includes('Session done')) { + return { + startedAt: null, + finishedAt: parsed.time || null, + failed: Number(parsed.Failed ?? parsed.failed ?? 0), + scanned: Number(parsed.Scanned ?? parsed.scanned ?? 0), + updated: Number(parsed.Updated ?? parsed.updated ?? 0), + found: [], + warnings: [] + }; + } + } catch { + const doneMatch = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/); + if (doneMatch) { + const timeMatch = line.match(/time="([^"]+)"/); + return { + startedAt: null, + finishedAt: timeMatch?.[1] || null, + failed: Number(doneMatch[1]), + scanned: Number(doneMatch[2]), + updated: Number(doneMatch[3]), + found: [], + warnings: [] + }; + } + } } return null;