import { Trans, useLingui } from "@lingui/react/macro" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { AlertTriangle, ArrowDownIcon, ArrowUpIcon, CheckCircleIcon, Edit3Icon, FilterIcon, GlobeIcon, LayoutGridIcon, LayoutListIcon, PauseIcon, PlayIcon, PlusIcon, RefreshCwIcon, Settings2Icon, TagIcon, Trash2Icon, XCircle, XCircleIcon, } from "lucide-react" import { memo, useMemo, useState } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { useToast } from "@/components/ui/use-toast" import { deleteMonitor, getMonitorTypeLabel, listMonitors, manualCheck, pauseMonitor, resumeMonitor, type Monitor, type MonitorStatus, type MonitorType, formatUptime, formatPing, } from "@/lib/monitors" import { cn, useBrowserStorage } from "@/lib/utils" import { AddMonitorDialog } from "./add-monitor-dialog" import { Link } from "@/components/router" import ContainersTable from "../containers-table/containers-table" // Status indicator component function StatusIndicator({ status }: { status: MonitorStatus }) { const colors = { up: "bg-green-500", down: "bg-red-500", pending: "bg-yellow-400", paused: "bg-gray-400", maintenance: "bg-blue-500", } const icons = { up: CheckCircleIcon, down: XCircleIcon, pending: RefreshCwIcon, paused: PauseIcon, maintenance: RefreshCwIcon, } const Icon = icons[status] || RefreshCwIcon return (
{status}
) } // Monitor Card component for grid view function MonitorCard({ monitor, onEdit, }: { monitor: Monitor onEdit: (m: Monitor) => void }) { const { toast } = useToast() const queryClient = useQueryClient() const checkMutation = useMutation({ mutationFn: manualCheck, onSuccess: (result) => { toast({ title: `Check complete`, description: `${monitor.name} is ${result.status} (${formatPing(result.ping)})`, }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, }) const pauseMutation = useMutation({ mutationFn: monitor.status === "paused" ? resumeMonitor : pauseMonitor, onSuccess: () => { toast({ title: monitor.status === "paused" ? "Monitor resumed" : "Monitor paused", }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, }) const deleteMutation = useMutation({ mutationFn: deleteMonitor, onSuccess: () => { toast({ title: "Monitor deleted" }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, }) return (
{monitor.name}
{monitor.url || monitor.hostname} {monitor.port ? `:${monitor.port}` : ""}
onEdit(monitor)}> Edit deleteMutation.mutate(monitor.id)} className="text-destructive" > Delete
Type
{getMonitorTypeLabel(monitor.type)}
{/* Uptime - Prominent pill display */}
{monitor.uptime_stats?.uptime_7d !== undefined && monitor.uptime_stats.uptime_7d !== monitor.uptime_stats?.uptime_24h && ( )}
Response
{monitor.last_check ? ( formatPing(monitor.uptime_stats?.last_ping || 0) ) : ( - )}
{monitor.tags && monitor.tags.length > 0 && (
{monitor.tags.map((tag) => ( {tag} ))}
)} {/* Containers Section */}

Check now

{monitor.status === "paused" ? "Resume" : "Pause"}

) } // Uptime pill badge component - big and visible function UptimePill({ uptime, label = "24h" }: { uptime: number; label?: string }) { let colorClass = "bg-green-500 text-white border-green-500" let icon = if (uptime < 99.9) { colorClass = "bg-green-500 text-white border-green-500" } if (uptime < 95) { colorClass = "bg-yellow-500 text-white border-yellow-500" icon = } if (uptime < 90) { colorClass = "bg-red-500 text-white border-red-500" icon = } return (
{icon} {formatUptime(uptime)} {label}
) } // Uptime bar component with pill style function UptimeBar({ stats }: { stats?: Record }) { const uptime24h = stats?.uptime_24h ?? 100 const uptime7d = stats?.uptime_7d ?? 100 const uptime30d = stats?.uptime_30d ?? 100 return (
{uptime7d !== 100 && uptime7d !== uptime24h && ( )} {uptime30d !== 100 && uptime30d !== uptime24h && uptime30d !== uptime7d && ( )}
) } // Mini uptime dots visualization function UptimeDots({ heartbeats }: { heartbeats?: Array<{ status: string; time: string }> }) { if (!heartbeats || heartbeats.length === 0) { return (
{Array.from({ length: 12 }).map((_, i) => (
))}
) } // Take last 12 heartbeats const recent = heartbeats.slice(-12) return (
{recent.map((hb, i) => (
))} {recent.length < 12 && Array.from({ length: 12 - recent.length }).map((_, i) => (
))}
) } // Monitor row component function MonitorRow({ monitor, onEdit, }: { monitor: Monitor onEdit: (m: Monitor) => void }) { const { toast } = useToast() const queryClient = useQueryClient() const checkMutation = useMutation({ mutationFn: manualCheck, onSuccess: (result) => { toast({ title: `Check complete`, description: `${monitor.name} is ${result.status} (${formatPing(result.ping)})`, }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, onError: () => { toast({ title: "Check failed", variant: "destructive", }) }, }) const pauseMutation = useMutation({ mutationFn: monitor.status === "paused" ? resumeMonitor : pauseMonitor, onSuccess: () => { toast({ title: monitor.status === "paused" ? "Monitor resumed" : "Monitor paused", }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, }) const deleteMutation = useMutation({ mutationFn: deleteMonitor, onSuccess: () => { toast({ title: "Monitor deleted" }) queryClient.invalidateQueries({ queryKey: ["monitors"] }) }, }) return (
{monitor.name}
{monitor.url || monitor.hostname} {monitor.port ? `:${monitor.port}` : ""}
{getMonitorTypeLabel(monitor.type)} {monitor.last_check ? (
{formatPing(monitor.uptime_stats?.last_ping || 0)}
) : ( - )}
{monitor.tags?.map((tag) => ( {tag} ))}

Check now

{monitor.status === "paused" ? "Resume" : "Pause"}

onEdit(monitor)}> Edit deleteMutation.mutate(monitor.id)} > Delete
) } type ViewMode = "table" | "grid" type StatusFilter = "all" | MonitorStatus type TypeFilter = "all" | MonitorType // Main component export default memo(function MonitorsTable() { const { t } = useLingui() const [filter, setFilter] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [tagFilter, setTagFilter] = useState("all") const [typeFilter, setTypeFilter] = useState("all") const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [editingMonitor, setEditingMonitor] = useState(null) const [viewMode, setViewMode] = useBrowserStorage( "monitorsViewMode", window.innerWidth < 1024 ? "grid" : "table" ) const { data: monitors = [], isLoading } = useQuery({ queryKey: ["monitors"], queryFn: listMonitors, refetchInterval: 30000, }) // Extract all unique types const allTypes = useMemo(() => { const typeSet = new Set() monitors.forEach((m) => typeSet.add(m.type)) return Array.from(typeSet).sort() }, [monitors]) // Filter by status first const statusFilteredMonitors = useMemo(() => { if (statusFilter === "all") return monitors return monitors.filter((m) => m.status === statusFilter) }, [monitors, statusFilter]) // Then filter by search text and type const filteredMonitors = useMemo(() => { let result = statusFilteredMonitors if (filter) { const f = filter.toLowerCase() result = result.filter( (m) => m.name.toLowerCase().includes(f) || (m.url || "").toLowerCase().includes(f) || (m.hostname || "").toLowerCase().includes(f) ) } if (tagFilter !== "all") { result = result.filter((m) => m.tags?.includes(tagFilter)) } if (typeFilter !== "all") { result = result.filter((m) => m.type === typeFilter) } return result }, [statusFilteredMonitors, filter, tagFilter, typeFilter]) // Extract all unique tags const allTags = useMemo(() => { const tagSet = new Set() monitors.forEach((m) => m.tags?.forEach((tag) => tagSet.add(tag))) return Array.from(tagSet).sort() }, [monitors]) const stats = useMemo(() => { const total = monitors.length const up = monitors.filter((m) => m.status === "up").length const down = monitors.filter((m) => m.status === "down").length const paused = monitors.filter((m) => m.status === "paused").length const pending = monitors.filter((m) => m.status === "pending").length const maintenance = monitors.filter((m) => m.status === "maintenance").length return { total, up, down, paused, pending, maintenance } }, [monitors]) return (
{/* Title row */}
Status Monitor websites, APIs, and services ({stats.up} {stats.down > 0 && ( <> {" "} {stats.down}{" "} )} {stats.paused > 0 && ( <> {" "} {stats.paused} )} / {stats.total})
{/* Quick status filters */}
{[ { key: "all", label: `All ${stats.total}`, color: "bg-primary" }, { key: "up", label: `Up ${stats.up}`, color: "bg-green-500" }, { key: "down", label: `Down ${stats.down}`, color: "bg-red-500" }, { key: "paused", label: `Paused ${stats.paused}`, color: "bg-gray-400" }, ].map((s) => ( ))}
{/* Filter row */}
setFilter(e.target.value)} value={filter} className="w-full" />
{allTypes.length > 0 && ( setTypeFilter(v as TypeFilter)}> All Types {allTypes.map((type) => ( {getMonitorTypeLabel(type)} ))} )} {allTags.length > 0 && ( All Tags {allTags.map((tag) => ( {tag} ))} )} {/* Layout */} Layout setViewMode(v as ViewMode)}> Table Grid {/* Status Filter */} Status setStatusFilter(v as StatusFilter)}> All ({stats.total}) Up ({stats.up}) Down ({stats.down}) Paused ({stats.paused}) {stats.pending > 0 && ( Pending ({stats.pending}) )} {stats.maintenance > 0 && ( Maintenance ({stats.maintenance}) )}
{isLoading ? (
Loading...
) : filteredMonitors.length === 0 ? (
{filter || statusFilter !== "all" || tagFilter !== "all" || typeFilter !== "all" ? ( No monitors match your filters. ) : (

No monitors configured yet.

)}
) : viewMode === "table" ? ( Name Type Status Response Uptime (24h) Tags Actions {filteredMonitors.map((monitor) => ( ))}
) : (
{filteredMonitors.map((monitor) => ( ))}
)}
{/* Add Monitor Dialog */} {/* Edit Monitor Dialog */} {editingMonitor && ( !open && setEditingMonitor(null)} monitor={editingMonitor} isEdit /> )}
) })