import { memo, useMemo, useState } from "react" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { Trans } from "@lingui/react/macro" import { useToast } from "@/components/ui/use-toast" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { Badge } from "@/components/ui/badge" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Globe, Clock, Activity, RefreshCw, ExternalLink, Edit3, Trash2, CheckCircle2, XCircle, PauseIcon, PlayIcon, TrendingUp, TrendingDown, Plus, type LucideIcon, } from "lucide-react" import { type Heartbeat, getMonitor, getMonitorStats, getMonitorHeartbeats, manualCheck, pauseMonitor, resumeMonitor, deleteMonitor, getMonitorTypeLabel, formatUptime, formatPing, } from "@/lib/monitors" import { formatDate } from "@/lib/domains" import { addMonitorToStatusPage, createStatusPage, getStatusPageMonitors, getStatusPages, getStatusPageUrl, removeMonitorFromStatusPage, } from "@/lib/statuspages" import { Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, Cell, ComposedChart, Legend, } from "recharts" import { Link, navigate } from "@/components/router" import { AddMonitorDialog } from "@/components/monitors-table/add-monitor-dialog" import { cn } from "@/lib/utils" type HeartbeatRow = Heartbeat & { timestamp?: string } // Uptime Bar Component - Visual timeline of recent checks function UptimeBarVisualization({ heartbeats }: { heartbeats?: HeartbeatRow[] }) { const recent = useMemo(() => { if (!heartbeats?.length) return [] return heartbeats.slice(0, 30).reverse() }, [heartbeats]) if (!recent.length) { return (
{Array.from({ length: 12 }).map((_, i) => (
))}
) } return (
{recent.map((hb, i) => (
))}
{recent.length} recent checks {recent.filter(h => h.status === "up").length} up {recent.filter(h => h.status === "down").length} down
) } // Response time statistics component function ResponseTimeStats({ heartbeats }: { heartbeats?: HeartbeatRow[] }) { const stats = useMemo(() => { if (!heartbeats?.length) return null const pings = heartbeats.filter(h => h.ping && h.ping > 0).map(h => h.ping) if (!pings.length) return null const sorted = [...pings].sort((a, b) => a - b) const avg = Math.round(pings.reduce((a, b) => a + b, 0) / pings.length) const min = sorted[0] const max = sorted[sorted.length - 1] const p95 = sorted[Math.floor(sorted.length * 0.95)] const p99 = sorted[Math.floor(sorted.length * 0.99)] return { avg, min, max, p95, p99, count: pings.length } }, [heartbeats]) if (!stats) return null return (
Avg
{formatPing(stats.avg)}
Min
{formatPing(stats.min)}
Max
{formatPing(stats.max)}
P95
{formatPing(stats.p95)}
P99
{formatPing(stats.p99)}
) } // Core Web Vitals placeholder component function CoreWebVitalsCard({ url }: { url?: string }) { if (!url) return null return ( Core Web Vitals Lighthouse performance metrics (coming soon)
LCP
-
Largest Contentful Paint
FID
-
First Input Delay
CLS
-
Cumulative Layout Shift
Core Web Vitals monitoring requires additional configuration
) } // Status badge component function StatusBadge({ status }: { status: string }) { const configs = { up: { color: "bg-green-500", icon: CheckCircle2, text: "Up" }, down: { color: "bg-red-500", icon: XCircle, text: "Down" }, pending: { color: "bg-yellow-500", icon: Clock, text: "Pending" }, paused: { color: "bg-gray-500", icon: PauseIcon, text: "Paused" }, maintenance: { color: "bg-blue-500", icon: Activity, text: "Maintenance" }, } const config = configs[status as keyof typeof configs] || configs.pending const Icon = config.icon return (
{config.text}
) } // Stat card component function StatCard({ title, value, icon: Icon, subtitle, trend, className, }: { title: string value: string icon: LucideIcon subtitle?: string trend?: "up" | "down" | "neutral" className?: string }) { const TrendIcon = trend === "up" ? TrendingUp : trend === "down" ? TrendingDown : null return (

{title}

{value}

{TrendIcon && }
{subtitle &&

{subtitle}

}
) } export default memo(function MonitorDetail({ id }: { id: string }) { const { toast } = useToast() const queryClient = useQueryClient() const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [timeRange, setTimeRange] = useState<"24h" | "7d" | "30d">("24h") const { data: monitor, isLoading: isMonitorLoading } = useQuery({ queryKey: ["monitor", id], queryFn: () => getMonitor(id), staleTime: Infinity, refetchInterval: 30000, }) const { data: stats } = useQuery({ queryKey: ["monitor-stats", id], queryFn: () => getMonitorStats(id), refetchInterval: 30000, }) const { data: heartbeatsData } = useQuery({ queryKey: ["monitor-heartbeats", id], queryFn: () => getMonitorHeartbeats(id), refetchInterval: 30000, }) const heartbeats = heartbeatsData?.heartbeats const checkMutation = useMutation({ mutationFn: () => manualCheck(id), onSuccess: (result) => { toast({ title: `Check complete`, description: `${monitor?.name} is ${result.status}`, }) queryClient.invalidateQueries({ queryKey: ["monitor", id] }) queryClient.invalidateQueries({ queryKey: ["monitor-heartbeats", id] }) queryClient.invalidateQueries({ queryKey: ["monitor-stats", id] }) }, }) const pauseMutation = useMutation({ mutationFn: () => (monitor?.status === "paused" ? resumeMonitor(id) : pauseMonitor(id)), onSuccess: () => { toast({ title: monitor?.status === "paused" ? "Monitor resumed" : "Monitor paused", }) queryClient.invalidateQueries({ queryKey: ["monitor", id] }) }, }) const deleteMutation = useMutation({ mutationFn: () => deleteMonitor(id), onSuccess: () => { toast({ title: "Monitor deleted" }) navigate("/") }, }) const [isCreateStatusPageOpen, setIsCreateStatusPageOpen] = useState(false) const [statusPageName, setStatusPageName] = useState("") const [statusPageSlug, setStatusPageSlug] = useState("") const { data: statusPages } = useQuery({ queryKey: ["status-pages"], queryFn: () => getStatusPages(), }) const { data: linkedStatusPageMonitors } = useQuery({ queryKey: ["monitor-status-page-links", id, statusPages?.map((page) => page.id).join(",")], queryFn: async () => { if (!statusPages?.length) return [] const links = await Promise.all( statusPages.map(async (page) => (await getStatusPageMonitors(page.id)).map((link) => ({ ...link, status_page_id: page.id })) ) ) return links.flat().filter((link) => link.monitor_id === id) }, enabled: Boolean(statusPages?.length), }) const updateStatusPagesMutation = useMutation({ mutationFn: async ({ pageId, linked }: { pageId: string; linked: boolean }) => { if (linked) { await removeMonitorFromStatusPage(pageId, id) } else { await addMonitorToStatusPage(pageId, { monitor: id }) } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["monitor-status-page-links", id] }) queryClient.invalidateQueries({ queryKey: ["status-pages"] }) toast({ title: "Status pages updated" }) }, }) const createStatusPageMutation = useMutation({ mutationFn: () => createStatusPage({ name: statusPageName || `${monitor?.name} Status`, slug: statusPageSlug || monitor?.name?.toLowerCase().replace(/\s+/g, "-") || "status", title: statusPageName || `${monitor?.name} Status Page`, public: true, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["status-pages"] }) toast({ title: "Status page created" }) setIsCreateStatusPageOpen(false) setStatusPageName("") setStatusPageSlug("") }, }) const handleDelete = () => { setIsDeleteDialogOpen(true) } // Filter heartbeats by time range const filteredHeartbeats = useMemo(() => { if (!heartbeats) return [] const now = Date.now() const ranges: Record = { "24h": 24 * 60 * 60 * 1000, "7d": 7 * 24 * 60 * 60 * 1000, "30d": 30 * 24 * 60 * 60 * 1000, } const cutoff = now - (ranges[timeRange] || ranges["24h"]) return heartbeats.filter((h: HeartbeatRow) => { const t = new Date(h.time || h.timestamp || "").getTime() return t >= cutoff }) }, [heartbeats, timeRange]) // Prepare chart data from heartbeats const chartData = useMemo(() => { if (!filteredHeartbeats.length) return [] return filteredHeartbeats .slice() .reverse() .map((h: HeartbeatRow) => ({ time: new Date(h.time || h.timestamp || "").toLocaleTimeString(), responseTime: h.ping || 0, status: h.status === "up" ? 1 : 0, })) }, [filteredHeartbeats]) // Calculate stats const uptimeStats = useMemo(() => { if (!heartbeats || !Array.isArray(heartbeats) || heartbeats.length === 0) return null const total = heartbeats.length const up = heartbeats.filter((h: HeartbeatRow) => h.status === "up").length const avgResponse = heartbeats.reduce((sum: number, h: HeartbeatRow) => sum + (h.ping || 0), 0) / total return { uptime: ((up / total) * 100).toFixed(2), avgResponse: avgResponse.toFixed(0), totalChecks: total, } }, [heartbeats]) if (isMonitorLoading) { return (
) } if (!monitor) { return (

Monitor not found

The monitor you are looking for does not exist.

) } const isUp = monitor.status === "up" const isPaused = monitor.status === "paused" const isPending = monitor.status === "pending" const headerIconColor = isUp ? "text-green-500" : isPaused ? "text-gray-500" : isPending ? "text-yellow-500" : "text-red-500" const headerBgColor = isUp ? "bg-green-500/10" : isPaused ? "bg-gray-500/10" : isPending ? "bg-yellow-500/10" : "bg-red-500/10" return (
{/* Header */}

{monitor.name}

{getMonitorTypeLabel(monitor.type)} {monitor.interval && {monitor.interval}s interval} {isPending && ( Waiting for first check )}
{monitor.url &&

{monitor.url}

}
{monitor.url && ( )}
{/* Summary Bar */}
{/* Uptime Bar & Response Stats */}
Recent Uptime Visual timeline of the last 30 checks Response Time Statistics Distribution of response times
{/* Core Web Vitals */} {/* Combined Uptime & Response Chart */}
Uptime & Response Time Status and response time over the selected period
{(["24h", "7d", "30d"] as const).map((range) => ( ))}
{chartData.length > 0 ? ( (v === 1 ? "Up" : "Down")} /> {chartData.map((entry, index) => ( ))} ) : (

{isPending ? "No check data yet. Run a check to see the chart." : "No data available for selected time range"}

{isPending && ( )}
)}
Monitor Details
Type {getMonitorTypeLabel(monitor.type)}
Interval {monitor.interval}s
Retries {monitor.retries}
Created {formatDate(monitor.created)}
{monitor.last_check && (
Last Check {formatDate(monitor.last_check)}
)}
Status Page Link this monitor to public status pages {statusPages && statusPages.length > 0 ? (
{statusPages.map((page) => { const isLinked = linkedStatusPageMonitors?.some((link) => link.status_page_id === page.id) || false const linkInfo = linkedStatusPageMonitors?.find((link) => link.status_page_id === page.id) return (
{page.name} {page.public && ( )}
{isLinked && linkInfo && (

Display: {linkInfo.display_name || monitor?.name} {linkInfo.group && ` • Group: ${linkInfo.group}`}

)} {!isLinked && page.public && (

{page.monitor_count} monitor{page.monitor_count !== 1 ? 's' : ''} linked

)}
{isLinked && page.public && ( )}
) })}
) : (

No status pages yet.

Create one to share your service status publicly.

)}
Recent Checks Last 50 monitor checks Time Status Response Time Message {heartbeats?.slice(0, 50).map((hb: HeartbeatRow) => ( {formatDate(hb.time || hb.timestamp)} {hb.status} {formatPing(hb.ping)} {hb.msg || "-"} ))} {!heartbeats?.length && (

{isPending ? "No checks have been run yet." : "No check history available for the selected period."}

{isPending && ( )}
)}
{/* Create Status Page Dialog */} {isCreateStatusPageOpen && ( Create Status Page Create a public status page for this monitor.
setStatusPageName(e.target.value)} placeholder={`${monitor.name} Status`} />
setStatusPageSlug(e.target.value)} placeholder={monitor.name?.toLowerCase().replace(/\s+/g, "-")} />
setIsCreateStatusPageOpen(false)}>Cancel createStatusPageMutation.mutate()} disabled={createStatusPageMutation.isPending} > Create
)} Delete Monitor Are you sure you want to delete this monitor? This action cannot be undone. Cancel { deleteMutation.mutate() setIsDeleteDialogOpen(false) }} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > Delete
) })