Add uptime monitor features: fix history bars, lifetime stats, detail modal, custom services

- Fix 24-bar history rendering to always show 24 uniform segments with gray fill for missing hours
- Add lifetime uptime % and avg latency to status API
- Add clickable detail modal with expanded stats, large history bar, and service removal
- Add monitored_services DB table with CRUD API (GET/POST/DELETE)
- Monitor reads services from DB each interval, seeds defaults on first run
- Add inline form to add custom services to track
- Extend log retention from 7 days to 90 days for lifetime stats

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shivam Patel
2026-02-09 02:05:22 -05:00
parent 032fdc1b12
commit 372ff8cd22
5 changed files with 465 additions and 56 deletions

View File

@@ -34,6 +34,26 @@ export async function GET() {
GROUP BY service_name
`);
// Get lifetime stats (all available data)
const statsLifetime = await db.all(`
SELECT service_name,
count(*) as total,
sum(case when status = 'up' then 1 else 0 end) as up_count
FROM uptime_logs
GROUP BY service_name
`);
// Get average latency over last 24h (only for 'up' checks)
const avgLatencyRows = await db.all(`
SELECT service_name,
ROUND(AVG(latency)) as avg_latency
FROM uptime_logs
WHERE timestamp > datetime('now', '-24 hours')
AND status = 'up'
AND latency > 0
GROUP BY service_name
`);
// Get hourly history for last 24h (24 buckets per service)
const history = await db.all(`
SELECT service_name,
@@ -60,11 +80,15 @@ export async function GET() {
const results = live.map(l => {
const s24 = stats24h.find(s => s.service_name === l.name);
const s7d = stats7d.find(s => s.service_name === l.name);
const sLife = statsLifetime.find(s => s.service_name === l.name);
const avgLat = avgLatencyRows.find(s => s.service_name === l.name);
return {
...l,
uptime24h: s24 ? Math.round((s24.up_count / s24.total) * 100) : 100,
uptime7d: s7d ? Math.round((s7d.up_count / s7d.total) * 100) : 100,
uptimeLifetime: sLife ? Math.round((sLife.up_count / sLife.total) * 100) : 100,
avgLatency24h: avgLat ? avgLat.avg_latency : 0,
history: historyMap[l.name] || [],
};
});