"use client"; import { useQuery } from "@tanstack/react-query"; import { orgQueries } from "@app/lib/queries"; import { Tooltip, TooltipContent, TooltipTrigger } from "@app/components/ui/tooltip"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { createApiClient } from "@app/lib/api"; import { cn } from "@app/lib/cn"; import { useTranslations } from "next-intl"; function formatDuration(seconds: number): string { if (seconds === 0) return "0s"; if (seconds < 60) return `${Math.round(seconds)}s`; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.round(seconds % 60); if (h > 0) return s > 0 ? `${h}h ${m}m ${s}s` : `${h}h ${m}m`; if (m > 0 && s > 0) return `${m}m ${s}s`; return `${m}m`; } function formatDate(dateStr: string): string { return new Date(dateStr + "T00:00:00").toLocaleDateString([], { month: "short", day: "numeric", year: "numeric" }); } function formatTime(ts: number): string { return new Date(ts * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } const barColorClass: Record = { good: "bg-green-500", degraded: "bg-yellow-500", bad: "bg-red-500", no_data: "bg-neutral-200 dark:bg-neutral-700", unknown: "bg-neutral-200 dark:bg-neutral-700" }; type UptimeBarProps = { orgId?: string; siteId?: number; resourceId?: number; healthCheckId?: number; days?: number; title?: string; className?: string; }; export default function UptimeBar({ orgId, siteId, resourceId, healthCheckId, days = 90, title, className }: UptimeBarProps) { const t = useTranslations(); const api = createApiClient(useEnvContext()); const siteQuery = useQuery({ ...orgQueries.siteStatusHistory({ siteId: siteId ?? 0, days }), enabled: siteId != null, meta: { api } }); const hcQuery = useQuery({ ...orgQueries.healthCheckStatusHistory({ orgId: orgId ?? "", healthCheckId: healthCheckId ?? 0, days }), enabled: healthCheckId != null && siteId == null && resourceId == null, meta: { api } }); const resourceQuery = useQuery({ ...orgQueries.resourceStatusHistory({ resourceId, days }), enabled: resourceId != null && siteId == null && healthCheckId == null, meta: { api } }); const { data, isLoading } = siteId != null ? siteQuery : resourceId != null ? resourceQuery : hcQuery; if (isLoading) { return (
{title && ( {title} )}
{Array.from({ length: days }).map((_, i) => (
))}
{t("uptimeDaysAgo", { count: days })} {t("uptimeToday")}
); } if (!data) return null; const allNoData = data.days.every((d) => d.status === "no_data"); return (
{/* Header row */}
{title && {title}}
{!allNoData && ( <> {data.overallUptimePercent.toFixed(2)}% {" "} {t("uptimeSuffix")} {data.totalDowntimeSeconds > 0 && ( {formatDuration( data.totalDowntimeSeconds )} {" "} {t("uptimeDowntimeSuffix")} )} )} {allNoData && ( {t("uptimeNoDataAvailable")} )}
{/* Bar row */}
{data.days.map((day, i) => (
{formatDate(day.date)}
{day.status !== "no_data" && day.status !== "unknown" && (
{t("uptimeTooltipUptimeLabel")}:{" "} {day.uptimePercent.toFixed(1)}%
)} {day.totalDowntimeSeconds > 0 && (
{t("uptimeTooltipDowntimeLabel")}:{" "} {formatDuration( day.totalDowntimeSeconds )}
)} {day.downtimeWindows.length > 0 && (
{day.downtimeWindows.map((w, wi) => (
{formatTime(w.start)} {w.end ? ` – ${formatTime(w.end)}` : ` – ${t("uptimeOngoing")}`}{" "} ({w.status})
))}
)} {(day.status === "no_data" || day.status === "unknown") && (
{t("uptimeNoMonitoringData")}
)}
))}
{/* Date labels */}
{t("uptimeDaysAgo", { count: days })} {t("uptimeToday")}
); }