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:
@@ -30,9 +30,35 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
await db.run('INSERT INTO monitored_services (name, url) VALUES (?, ?)', name.trim(), url.trim());
|
||||
const trimmedName = name.trim();
|
||||
const trimmedUrl = url.trim();
|
||||
await db.run('INSERT INTO monitored_services (name, url) VALUES (?, ?)', trimmedName, trimmedUrl);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
// Immediate ping so the service has data right away
|
||||
let initialStatus = 'down';
|
||||
let initialLatency = 0;
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
const start = performance.now();
|
||||
const res = await fetch(trimmedUrl, {
|
||||
method: 'HEAD',
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
initialLatency = Math.round(performance.now() - start);
|
||||
initialStatus = res.status < 500 ? 'up' : 'down';
|
||||
} catch {
|
||||
initialStatus = 'down';
|
||||
initialLatency = 0;
|
||||
}
|
||||
|
||||
await db.run(
|
||||
'INSERT INTO uptime_logs (service_name, url, status, latency, timestamp) VALUES (?, ?, ?, ?, ?)',
|
||||
trimmedName, trimmedUrl, initialStatus, initialLatency, new Date().toISOString()
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true, initialStatus, initialLatency });
|
||||
} catch (error: unknown) {
|
||||
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||
if (msg.includes('UNIQUE constraint')) {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user