"use client" import {useMemo} from "react" import {Card} from "@/components/ui/card" import {HealthcheckLog} from "@/db/schema/15_healthcheck-log" type HealthStatus = "healthy" | "degraded" | "down" | "unknown" interface HealthCheckData { timestamp: Date status: HealthStatus } interface Props { logs: HealthcheckLog[] } const INTERVAL_MINUTES = 10 const WINDOW_HOURS = 12 function roundDateToInterval(date: Date, intervalMinutes: number): Date { const ms = intervalMinutes * 60 * 1000 return new Date(Math.floor(date.getTime() / ms) * ms) } function buildTimeSeries(logs: HealthcheckLog[]): HealthCheckData[] { const intervalMs = INTERVAL_MINUTES * 60 * 1000 const now = new Date() const roundedNow = roundDateToInterval(now, INTERVAL_MINUTES) const buckets = (WINDOW_HOURS * 60) / INTERVAL_MINUTES const data: HealthCheckData[] = [] const oldestLog = logs.length > 0 ? getOldestLog(logs) : null for (let i = buckets - 1; i >= 0; i--) { const start = new Date(roundedNow.getTime() - i * intervalMs) const end = new Date(start.getTime() + intervalMs) const bucketLogs = logs.filter( (l) => l.date && new Date(l.date) >= start && new Date(l.date) < end ) let status: HealthStatus = "unknown" if (!oldestLog || new Date(oldestLog.date!) > start) { status = "unknown" } else if (new Date(oldestLog.date!) < start) { status = "down" } if (bucketLogs.length > 0) { const hasFailure = bucketLogs.some((l) => l.status === "failed") const hasSuccess = bucketLogs.some((l) => l.status === "success") if (hasFailure && hasSuccess) { status = "degraded" } else if (hasFailure) { status = "down" } else if (hasSuccess) { status = "healthy" } } data.push({timestamp: start, status}) } return data } function getStatusColor(status: HealthStatus): string { switch (status) { case "healthy": return "bg-emerald-500" case "degraded": return "bg-emerald-700" case "down": return "bg-red-500" case "unknown": return "bg-zinc-700" } } function getOldestLog(logs: HealthcheckLog[]): HealthcheckLog { console.log(logs) const validLogs = logs.filter(l => l.date) return validLogs.reduce((oldest, current) => new Date(current.date!) < new Date(oldest.date!) ? current : oldest ) } function formatTime(date: Date): string { return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false, }) } export const HealthCheckGraph = ({logs}: Props) => { const data = useMemo(() => { return buildTimeSeries(logs) }, [logs]) const hourLabels = useMemo(() => { if (data.length === 0) return [] const isMobile = window.innerWidth <= 768 return data .map((item, index) => ({ item, index })) .filter(({ item }) => { const hours = item.timestamp.getHours() const minutes = item.timestamp.getMinutes() if (isMobile) { return minutes === 0 && hours % 3 === 0 } else { return minutes === 0 } }) .map(({ item, index }) => ({ hour: formatTime(item.timestamp), index, })) }, [data]) const healthyCount = data.filter((d) => d.status === "healthy").length const uptimePercent = data.length > 0 ? ((healthyCount / data.length) * 100).toFixed(1) : "0.0" return (
Last 12 hours • {INTERVAL_MINUTES} minute intervals
{uptimePercent}%
Uptime