Files
Admin_dash/monitor.js
Shivam Patel 702d61bdef Overhaul uptime monitor: time ranges, bar glow, tooltips, degraded state, immediate ping
- Add 24H/7D/30D/1Y time range selector with dynamic SQL bucketing
- Add bar glow (colored box-shadow) for up/degraded/down states
- Replace native title tooltips with positioned tooltips showing uptime % and check counts
- Add "degraded" (amber) state for buckets with mixed up/down checks
- Immediate HEAD ping on service add so status appears without 60s wait
- Taller bars (h-3 list, h-6 modal), rounded-sm, hover:scale-y-[1.3]
- Increase list bar opacity from 50% to 70%
- Add DB index on uptime_logs(service_name, timestamp)
- Extend data retention from 90 to 400 days for yearly view
2026-02-09 03:28:24 -05:00

120 lines
3.6 KiB
JavaScript

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();