2026-02-08 02:32:45 -05:00
|
|
|
import { NextResponse } from 'next/server';
|
2026-02-08 03:03:53 -05:00
|
|
|
import { getDb } from '@/lib/db';
|
2026-02-08 02:32:45 -05:00
|
|
|
|
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
|
|
2026-02-09 03:28:24 -05:00
|
|
|
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) {
|
2026-02-08 03:03:53 -05:00
|
|
|
try {
|
|
|
|
|
const db = await getDb();
|
2026-02-09 03:28:24 -05:00
|
|
|
const { searchParams } = new URL(request.url);
|
|
|
|
|
const range = (searchParams.get('range') || '24h') as TimeRange;
|
|
|
|
|
const config = RANGE_CONFIG[range] || RANGE_CONFIG['24h'];
|
2026-02-08 03:03:53 -05:00
|
|
|
|
|
|
|
|
// Get live status (most recent log per service)
|
|
|
|
|
const live = await db.all(`
|
2026-02-09 00:50:40 -05:00
|
|
|
SELECT service_name as name, url, status, latency
|
|
|
|
|
FROM uptime_logs
|
2026-02-08 03:03:53 -05:00
|
|
|
WHERE id IN (SELECT MAX(id) FROM uptime_logs GROUP BY service_name)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Get 24h stats
|
|
|
|
|
const stats24h = await db.all(`
|
2026-02-09 00:50:40 -05:00
|
|
|
SELECT service_name,
|
|
|
|
|
count(*) as total,
|
|
|
|
|
sum(case when status = 'up' then 1 else 0 end) as up_count
|
|
|
|
|
FROM uptime_logs
|
2026-02-08 03:03:53 -05:00
|
|
|
WHERE timestamp > datetime('now', '-24 hours')
|
|
|
|
|
GROUP BY service_name
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Get 7d stats
|
|
|
|
|
const stats7d = await db.all(`
|
2026-02-09 00:50:40 -05:00
|
|
|
SELECT service_name,
|
|
|
|
|
count(*) as total,
|
|
|
|
|
sum(case when status = 'up' then 1 else 0 end) as up_count
|
|
|
|
|
FROM uptime_logs
|
2026-02-08 03:03:53 -05:00
|
|
|
WHERE timestamp > datetime('now', '-7 days')
|
|
|
|
|
GROUP BY service_name
|
|
|
|
|
`);
|
|
|
|
|
|
2026-02-09 02:05:22 -05:00
|
|
|
// Get lifetime stats (all available data)
|
|
|
|
|
const statsLifetime = await db.all(`
|
|
|
|
|
SELECT service_name,
|
|
|
|
|
count(*) as total,
|
|
|
|
|
sum(case when status = 'up' then 1 else 0 end) as up_count
|
|
|
|
|
FROM uptime_logs
|
|
|
|
|
GROUP BY service_name
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Get average latency over last 24h (only for 'up' checks)
|
|
|
|
|
const avgLatencyRows = await db.all(`
|
|
|
|
|
SELECT service_name,
|
|
|
|
|
ROUND(AVG(latency)) as avg_latency
|
|
|
|
|
FROM uptime_logs
|
|
|
|
|
WHERE timestamp > datetime('now', '-24 hours')
|
|
|
|
|
AND status = 'up'
|
|
|
|
|
AND latency > 0
|
|
|
|
|
GROUP BY service_name
|
|
|
|
|
`);
|
|
|
|
|
|
2026-02-09 03:28:24 -05:00
|
|
|
// Get bucketed history for the requested range
|
2026-02-09 00:50:40 -05:00
|
|
|
const history = await db.all(`
|
|
|
|
|
SELECT service_name,
|
2026-02-09 03:28:24 -05:00
|
|
|
strftime('${config.strftime}', timestamp) as bucket,
|
2026-02-09 00:50:40 -05:00
|
|
|
count(*) as total,
|
|
|
|
|
sum(case when status = 'up' then 1 else 0 end) as up_count
|
|
|
|
|
FROM uptime_logs
|
2026-02-09 03:28:24 -05:00
|
|
|
WHERE timestamp > datetime('now', '${config.sqlOffset}')
|
|
|
|
|
GROUP BY service_name, bucket
|
|
|
|
|
ORDER BY bucket ASC
|
2026-02-09 00:50:40 -05:00
|
|
|
`);
|
|
|
|
|
|
2026-02-09 03:28:24 -05:00
|
|
|
// Group history by service — return up_count and total per bucket
|
|
|
|
|
const historyMap: Record<string, Array<{ bucket: string; up_count: number; total: number }>> = {};
|
2026-02-09 00:50:40 -05:00
|
|
|
for (const row of history) {
|
|
|
|
|
if (!historyMap[row.service_name]) historyMap[row.service_name] = [];
|
|
|
|
|
historyMap[row.service_name].push({
|
2026-02-09 03:28:24 -05:00
|
|
|
bucket: row.bucket,
|
|
|
|
|
up_count: row.up_count,
|
|
|
|
|
total: row.total,
|
2026-02-09 00:50:40 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 03:03:53 -05:00
|
|
|
// Merge data
|
|
|
|
|
const results = live.map(l => {
|
|
|
|
|
const s24 = stats24h.find(s => s.service_name === l.name);
|
|
|
|
|
const s7d = stats7d.find(s => s.service_name === l.name);
|
2026-02-09 02:05:22 -05:00
|
|
|
const sLife = statsLifetime.find(s => s.service_name === l.name);
|
|
|
|
|
const avgLat = avgLatencyRows.find(s => s.service_name === l.name);
|
2026-02-08 03:03:53 -05:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...l,
|
|
|
|
|
uptime24h: s24 ? Math.round((s24.up_count / s24.total) * 100) : 100,
|
|
|
|
|
uptime7d: s7d ? Math.round((s7d.up_count / s7d.total) * 100) : 100,
|
2026-02-09 02:05:22 -05:00
|
|
|
uptimeLifetime: sLife ? Math.round((sLife.up_count / sLife.total) * 100) : 100,
|
|
|
|
|
avgLatency24h: avgLat ? avgLat.avg_latency : 0,
|
2026-02-09 00:50:40 -05:00
|
|
|
history: historyMap[l.name] || [],
|
2026-02-08 03:03:53 -05:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return NextResponse.json(results);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Uptime stats error:', error);
|
|
|
|
|
return NextResponse.json([]);
|
|
|
|
|
}
|
2026-02-08 02:32:45 -05:00
|
|
|
}
|