const sqlite3 = require('sqlite3'); const { open } = require('sqlite'); // Node 18+ has global fetch built-in 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...'); const dbPath = process.env.DB_PATH || './dashboard.db'; const db = await open({ filename: dbPath, driver: sqlite3.Database }); // Ensure table exists (in case monitor runs before app) await db.exec(` CREATE TABLE IF NOT EXISTS uptime_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_name TEXT NOT NULL, url TEXT NOT NULL, status TEXT NOT NULL, latency INTEGER, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ); `); await seedDefaults(db); setInterval(async () => { console.log('Running checks...'); const now = new Date().toISOString(); // 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; try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const res = await fetch(service.url, { method: 'HEAD', signal: controller.signal }); clearTimeout(timeout); // Any HTTP response means the service is reachable (up). // Only network errors/timeouts (caught below) count as down. status = res.status < 500 ? 'up' : 'down'; const end = performance.now(); latency = Math.round(end - start); } catch (err) { status = 'down'; latency = 0; } try { await db.run( `INSERT INTO uptime_logs (service_name, url, status, latency, timestamp) VALUES (?, ?, ?, ?, ?)`, service.name, service.url, status, latency, now ); } catch (dbErr) { console.error('DB Write Error:', dbErr); } } // Prune old logs (keep 400 days for yearly view) try { await db.run(`DELETE FROM uptime_logs WHERE timestamp < datetime('now', '-400 days')`); } catch (e) { } }, 60000); // Run every minute } monitor();