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

@@ -2,12 +2,46 @@ const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
// Node 18+ has global fetch built-in
const SERVICES = [
const DEFAULT_SERVICES = [
{ name: 'Website', url: 'https://akkolli.net' },
{ name: 'Gitea', url: 'https://code.akkolli.net' },
{ name: 'Nextcloud', url: 'http://host.docker.internal:6060' },
];
async function getServices(db) {
try {
const rows = await db.all('SELECT name, url FROM monitored_services');
if (rows && rows.length > 0) return rows;
} catch (e) {
// Table might not exist yet
}
return DEFAULT_SERVICES;
}
async function seedDefaults(db) {
// Ensure monitored_services table exists
await db.exec(`
CREATE TABLE IF NOT EXISTS monitored_services (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
url TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
// Seed defaults if table is empty
const count = await db.get('SELECT COUNT(*) as cnt FROM monitored_services');
if (count.cnt === 0) {
for (const s of DEFAULT_SERVICES) {
await db.run(
'INSERT OR IGNORE INTO monitored_services (name, url) VALUES (?, ?)',
s.name, s.url
);
}
console.log('Seeded default services into monitored_services');
}
}
async function monitor() {
console.log('Starting monitoring loop...');
@@ -30,11 +64,16 @@ async function monitor() {
);
`);
await seedDefaults(db);
setInterval(async () => {
console.log('Running checks...');
const now = new Date().toISOString();
for (const service of SERVICES) {
// Re-read services each interval so new additions are picked up
const services = await getServices(db);
for (const service of services) {
const start = performance.now();
let status = 'down';
let latency = 0;
@@ -57,7 +96,6 @@ async function monitor() {
} catch (err) {
status = 'down';
latency = 0;
// console.error(`Failed to reach ${service.name}:`, err.message);
}
try {
@@ -70,9 +108,9 @@ async function monitor() {
}
}
// Prune old logs (keep 7 days)
// Prune old logs (keep 90 days for lifetime stats)
try {
await db.run(`DELETE FROM uptime_logs WHERE timestamp < datetime('now', '-7 days')`);
await db.run(`DELETE FROM uptime_logs WHERE timestamp < datetime('now', '-90 days')`);
} catch (e) { }
}, 60000); // Run every minute