import type { CellContext, Column, ColumnDef } from "@tanstack/react-table" import { Button } from "@/components/ui/button" import { cn, formatMicroseconds, hourWithSeconds } from "@/lib/utils" import { GlobeIcon, TimerIcon, WifiOffIcon, Trash2Icon, ArrowLeftRightIcon, MoreHorizontalIcon, ServerIcon, ClockIcon, NetworkIcon, RefreshCwIcon, PenBoxIcon, PauseCircleIcon, PlayCircleIcon, } from "lucide-react" import { t } from "@lingui/core/macro" import type { NetworkProbeRecord, SystemRecord } from "@/types" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Trans } from "@lingui/react/macro" import { $allSystemsById, $longestSystemName } from "@/lib/stores" import { useStore } from "@nanostores/react" import { SystemStatus } from "@/lib/enums" import { Checkbox } from "@/components/ui/checkbox" import { useMemo } from "react" const protocolColors: Record = { icmp: "bg-blue-500/15 text-blue-400", tcp: "bg-purple-500/15 text-purple-400", http: "bg-green-500/15 text-green-400", } const SYSTEM_STATUS_COLORS = { [SystemStatus.Up]: "bg-green-500", [SystemStatus.Down]: "bg-red-500", [SystemStatus.Paused]: "bg-primary/40", [SystemStatus.Pending]: "bg-yellow-500", } as const /** * A probe is considered muted if it's disabled or if its associated system is not up. */ const isMuted = (record: NetworkProbeRecord, systemRecord: SystemRecord | undefined) => !record.enabled || systemRecord?.status !== SystemStatus.Up export function getProbeColumns( longestName = "", longestTarget = "", { onEdit, onDelete, onSetEnabled, }: { onEdit?: (probe: NetworkProbeRecord) => void onDelete?: (probes: NetworkProbeRecord[]) => void | Promise onSetEnabled?: (probes: NetworkProbeRecord[], enabled: boolean) => void | Promise } = {} ): ColumnDef[] { return [ { id: "select", header: ({ table }) => ( event.stopPropagation()} onCheckedChange={(value) => table.toggleAllRowsSelected(!!value)} aria-label={t`Select all`} /> ), cell: ({ row }) => ( event.stopPropagation()} onCheckedChange={(value) => row.toggleSelected(!!value)} aria-label={t`Select row`} /> ), enableSorting: false, enableHiding: false, size: 44, }, { id: "name", sortingFn: (a, b) => (a.original.name || a.original.target).localeCompare(b.original.name || b.original.target), accessorFn: (record) => record.name || record.target, header: ({ column }) => , cell: ({ row, getValue }) => { const probe = row.original return (
{getValue() as string}
) }, }, { id: "system", accessorFn: (record) => record.system, sortingFn: (a, b) => { const allSystems = $allSystemsById.get() const systemNameA = allSystems[a.original.system]?.name ?? "" const systemNameB = allSystems[b.original.system]?.name ?? "" return systemNameA.localeCompare(systemNameB) }, header: ({ column }) => , cell: ({ getValue }) => { const system = useStore($allSystemsById)[getValue() as string] as SystemRecord | undefined const longestSystemName = useStore($longestSystemName) const name = system?.name const status = system?.status as SystemStatus // undefined val is fine but makes lsp mad return useMemo( () => (
{name}
), [status, name] ) }, }, { id: "target", sortingFn: (a, b) => a.original.target.localeCompare(b.original.target), accessorFn: (record) => record.target, header: ({ column }) => , cell: ({ getValue }) => (
{getValue() as string}
), }, { id: "protocol", accessorFn: (record) => record.protocol, header: ({ column }) => , cell: ({ getValue }) => { const protocol = getValue() as string return ( {protocol} ) }, }, { id: "interval", accessorFn: (record) => record.interval, header: ({ column }) => , cell: ({ getValue }) => {getValue() as number}s, }, { id: "res", accessorFn: (record) => record.res, invertSorting: true, header: ({ column }) => , cell: responseTimeCell, }, { id: "res1h", accessorFn: (record) => record.resAvg1h, invertSorting: true, header: ({ column }) => , cell: responseTimeCell, }, { id: "max1h", accessorFn: (record) => record.resMax1h, invertSorting: true, header: ({ column }) => , cell: responseTimeCell, }, { id: "min1h", accessorFn: (record) => record.resMin1h, invertSorting: true, header: ({ column }) => , cell: responseTimeCell, }, { id: "loss", accessorFn: (record) => record.loss1h, invertSorting: true, header: ({ column }) => , cell: ({ row }) => { const { loss1h, res, system } = row.original const systemRecord = useStore($allSystemsById)[system] if (loss1h === undefined || (!res && !loss1h)) { return - } const muted = isMuted(row.original, systemRecord) let color = "bg-green-500" if (muted) { color = "bg-muted-foreground/50" } else if (loss1h) { color = loss1h > 20 ? "bg-red-500" : "bg-yellow-500" } return ( {loss1h}% ) }, }, { id: "updated", invertSorting: true, accessorFn: (record) => record.updated, header: ({ column }) => , cell: ({ getValue }) => { const timestamp = getValue() as number if (!timestamp) { return - } return {hourWithSeconds(timestamp)} }, }, { id: "actions", enableSorting: false, enableHiding: false, header: () => null, size: 40, cell: ({ row, table }) => { const selectedRows = table.getSelectedRowModel().rows const actionRows = row.getIsSelected() && selectedRows.length > 1 ? selectedRows.map((selectedRow) => selectedRow.original) : [row.original] const isBulkAction = actionRows.length > 1 const shouldPause = actionRows.some((probe) => probe.enabled) return ( event.stopPropagation()}> {!isBulkAction && ( { event.stopPropagation() onEdit?.(row.original) }} > Edit )} { event.stopPropagation() onSetEnabled?.(actionRows, !shouldPause) }} > {shouldPause ? ( <> Pause ) : ( <> Resume )} { event.stopPropagation() onDelete?.(actionRows) }} > Delete ) }, }, ] } const responseTimeThresholds = { http: { warning: 800_000, critical: 3_000_000 }, tcp: { warning: 500_000, critical: 2_000_000 }, icmp: { warning: 100_000, critical: 500_000 }, } function responseTimeCell(cell: CellContext) { const probe = cell.row.original const systemRecord = useStore($allSystemsById)[probe.system] const responseTime = cell.getValue() as number | undefined if (!responseTime) { return - } const muted = isMuted(probe, systemRecord) let color = "bg-green-500" if (muted) { color = "bg-muted-foreground/50" } else if (responseTime > responseTimeThresholds[probe.protocol].warning) { color = "bg-yellow-500" } if (!muted && responseTime > responseTimeThresholds[probe.protocol].critical) { color = "bg-red-500" } return ( {formatMicroseconds(responseTime)} ) } function HeaderButton({ column, name, Icon, }: { column: Column name: string Icon: React.ElementType }) { const isSorted = column.getIsSorted() return ( ) }