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
This commit is contained in:
Shivam Patel
2026-02-09 03:28:24 -05:00
parent a0612d74b1
commit 702d61bdef
5 changed files with 287 additions and 69 deletions

View File

@@ -3,9 +3,21 @@ import { getDb } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET() {
type TimeRange = '24h' | '7d' | '30d' | '365d';
const RANGE_CONFIG: Record<TimeRange, { sqlOffset: string; strftime: string; bars: number }> = {
'24h': { sqlOffset: '-24 hours', strftime: '%Y-%m-%d %H:00', bars: 24 },
'7d': { sqlOffset: '-7 days', strftime: '%Y-%m-%d', bars: 7 },
'30d': { sqlOffset: '-30 days', strftime: '%Y-%m-%d', bars: 30 },
'365d': { sqlOffset: '-365 days', strftime: '%Y-%m', bars: 12 },
};
export async function GET(request: Request) {
try {
const db = await getDb();
const { searchParams } = new URL(request.url);
const range = (searchParams.get('range') || '24h') as TimeRange;
const config = RANGE_CONFIG[range] || RANGE_CONFIG['24h'];
// Get live status (most recent log per service)
const live = await db.all(`
@@ -54,25 +66,26 @@ export async function GET() {
GROUP BY service_name
`);
// Get hourly history for last 24h (24 buckets per service)
// Get bucketed history for the requested range
const history = await db.all(`
SELECT service_name,
strftime('%Y-%m-%d %H:00', timestamp) as hour,
strftime('${config.strftime}', timestamp) as bucket,
count(*) as total,
sum(case when status = 'up' then 1 else 0 end) as up_count
FROM uptime_logs
WHERE timestamp > datetime('now', '-24 hours')
GROUP BY service_name, hour
ORDER BY hour ASC
WHERE timestamp > datetime('now', '${config.sqlOffset}')
GROUP BY service_name, bucket
ORDER BY bucket ASC
`);
// Group history by service
const historyMap: Record<string, Array<{ hour: string; up: boolean }>> = {};
// Group history by service — return up_count and total per bucket
const historyMap: Record<string, Array<{ bucket: string; up_count: number; total: number }>> = {};
for (const row of history) {
if (!historyMap[row.service_name]) historyMap[row.service_name] = [];
historyMap[row.service_name].push({
hour: row.hour,
up: row.up_count === row.total,
bucket: row.bucket,
up_count: row.up_count,
total: row.total,
});
}