Openstatus www.openstatus.dev
at main 275 lines 8.1 kB view raw
1"use client"; 2 3import { TableCellLink } from "@/components/data-table/table-cell-link"; 4import { Badge } from "@/components/ui/badge"; 5import { Checkbox } from "@/components/ui/checkbox"; 6import type { ColumnDef } from "@tanstack/react-table"; 7import { DataTableRowActions } from "./data-table-row-actions"; 8 9import { DataTableColumnHeader } from "@/components/ui/data-table/data-table-column-header"; 10import type { RouterOutputs } from "@openstatus/api"; 11import { formatDistanceToNow } from "date-fns"; 12import { TableCellSkeleton } from "../dable-cell-skeleton"; 13import { TableCellDate } from "../table-cell-date"; 14import { TableCellNumber } from "../table-cell-number"; 15import { TableCellUnavailable } from "../table-cell-unavailable"; 16 17type Monitor = RouterOutputs["monitor"]["list"][number] & { 18 globalMetrics?: 19 | RouterOutputs["tinybird"]["globalMetrics"]["data"][number] 20 // NOTE: after loading the data, if the monitor has no metrics, the value will be `false` 21 | false; 22}; 23 24export const columns: ColumnDef<Monitor>[] = [ 25 { 26 id: "select", 27 header: ({ table }) => ( 28 <Checkbox 29 checked={ 30 table.getIsAllPageRowsSelected() || 31 (table.getIsSomePageRowsSelected() && "indeterminate") 32 } 33 onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} 34 aria-label="Select all" 35 /> 36 ), 37 cell: ({ row }) => ( 38 <Checkbox 39 checked={row.getIsSelected()} 40 onCheckedChange={(value) => row.toggleSelected(!!value)} 41 aria-label="Select row" 42 /> 43 ), 44 enableSorting: false, 45 enableHiding: false, 46 }, 47 { 48 accessorKey: "name", 49 header: ({ column }) => ( 50 <DataTableColumnHeader column={column} title="Name" /> 51 ), 52 cell: ({ row }) => { 53 return ( 54 <TableCellLink 55 value={row.getValue("name")} 56 href={`/monitors/${row.original.id}/overview`} 57 /> 58 ); 59 }, 60 enableHiding: false, 61 meta: { 62 cellClassName: "max-w-[150px] min-w-max", 63 }, 64 }, 65 { 66 accessorKey: "url", 67 header: ({ column }) => ( 68 <DataTableColumnHeader column={column} title="Endpoint" /> 69 ), 70 cell: ({ row }) => { 71 return ( 72 <TableCellLink value={row.getValue("url")} href={row.original.url} /> 73 ); 74 }, 75 enableHiding: true, 76 meta: { 77 cellClassName: "max-w-[150px] min-w-max", 78 }, 79 }, 80 { 81 accessorKey: "jobType", 82 header: "Type", 83 enableHiding: true, 84 }, 85 { 86 id: "status", 87 accessorFn: (row) => { 88 console.log(row); 89 return row.active ? row.status : "inactive"; 90 }, 91 header: "Status", 92 cell: ({ row }) => { 93 const value = String(row.getValue("status")); 94 95 switch (value) { 96 case "active": 97 return <div className="font-mono text-success">{value}</div>; 98 case "degraded": 99 return <div className="font-mono text-warning">{value}</div>; 100 case "error": 101 return <div className="font-mono text-destructive">{value}</div>; 102 default: 103 return <div className="font-mono text-muted-foreground">{value}</div>; 104 } 105 }, 106 filterFn: (row, _, value) => { 107 if (Array.isArray(value)) { 108 if (value.includes("inactive")) { 109 return !row.original.active; 110 } 111 if (value.includes("active")) { 112 return !!row.original.active && row.original.status === "active"; 113 } 114 return value.includes(row.original.status); 115 } 116 return row.original.status === value; 117 }, 118 enableSorting: false, 119 enableHiding: false, 120 enableGlobalFilter: false, 121 }, 122 { 123 accessorKey: "active", 124 enableHiding: true, 125 enableGlobalFilter: false, 126 }, 127 { 128 accessorKey: "tags", 129 header: "Tags", 130 cell: ({ row }) => { 131 const value = row.getValue("tags"); 132 if (!Array.isArray(value)) return null; 133 if (value.length === 0) { 134 return <div className="text-muted-foreground">-</div>; 135 } 136 return ( 137 <div className="group/badges -space-x-2 flex flex-wrap"> 138 {value.map((tag) => ( 139 <Badge 140 key={tag.id} 141 variant="outline" 142 className="relative flex translate-x-0 items-center gap-1.5 rounded-full bg-background transition-transform hover:z-10 hover:translate-x-1" 143 > 144 <div 145 className="size-2.5 rounded-full" 146 style={{ backgroundColor: tag.color }} 147 /> 148 <span>{tag.name}</span> 149 </Badge> 150 ))} 151 </div> 152 ); 153 }, 154 filterFn: (row, _, value) => { 155 const tagIds = row.original.tags.map((tag) => tag.id); 156 if (Array.isArray(value)) { 157 return value.some((v) => tagIds.includes(v)); 158 } 159 return tagIds.includes(value); 160 }, 161 getUniqueValues: (row) => row.tags.map((tag) => tag.id), 162 enableSorting: false, 163 enableHiding: false, 164 enableGlobalFilter: false, 165 }, 166 { 167 id: "lastIncident", 168 header: "Last Incident", 169 accessorFn: (row) => row.incidents?.[0]?.createdAt, 170 cell: ({ row }) => { 171 const value = row.getValue("lastIncident"); 172 return <TableCellDate value={value} formatStr="LLL dd, y" />; 173 }, 174 enableHiding: false, 175 enableGlobalFilter: false, 176 }, 177 // { 178 // id: "uptime", 179 // accessorFn: (row) => `uptime-${row.id}`, 180 // header: "Last Week", 181 // cell: ({ row }) => { 182 // return ( 183 // <ChartBarUptimeLight 184 // monitorId={String(row.original.id)} 185 // type={row.original.jobType as "http" | "tcp"} 186 // /> 187 // ); 188 // }, 189 // enableHiding: false, 190 // enableGlobalFilter: false, 191 // }, 192 { 193 id: "lastTimestamp", 194 header: "Last Checked", 195 accessorFn: (row) => 196 typeof row.globalMetrics === "object" 197 ? row.globalMetrics.lastTimestamp 198 : row.globalMetrics, 199 cell: ({ row }) => { 200 const value = row.getValue("lastTimestamp"); 201 if (value === undefined) return <TableCellSkeleton className="w-full" />; 202 return ( 203 <TableCellDate 204 value={ 205 typeof value === "number" 206 ? formatDistanceToNow(new Date(value), { addSuffix: true }) 207 : value 208 } 209 /> 210 ); 211 }, 212 enableHiding: false, 213 enableGlobalFilter: false, 214 }, 215 { 216 id: "p50", 217 accessorFn: (row) => 218 typeof row.globalMetrics === "object" 219 ? row.globalMetrics.p50Latency 220 : row.globalMetrics, 221 header: ({ column }) => ( 222 <DataTableColumnHeader column={column} title="P50" /> 223 ), 224 cell: ({ row }) => { 225 const value = row.getValue("p50"); 226 if (value === undefined) return <TableCellSkeleton />; 227 if (!value) return <TableCellUnavailable />; 228 return <TableCellNumber value={value} unit="ms" />; 229 }, 230 enableHiding: false, 231 }, 232 { 233 id: "p90", 234 accessorFn: (row) => 235 typeof row.globalMetrics === "object" 236 ? row.globalMetrics.p90Latency 237 : row.globalMetrics, 238 header: ({ column }) => ( 239 <DataTableColumnHeader column={column} title="P90" /> 240 ), 241 cell: ({ row }) => { 242 const value = row.getValue("p90"); 243 if (value === undefined) return <TableCellSkeleton />; 244 if (!value) return <TableCellUnavailable />; 245 return <TableCellNumber value={value} unit="ms" />; 246 }, 247 enableHiding: false, 248 enableGlobalFilter: false, 249 }, 250 { 251 id: "p95", 252 accessorFn: (row) => 253 typeof row.globalMetrics === "object" 254 ? row.globalMetrics.p95Latency 255 : row.globalMetrics, 256 header: ({ column }) => ( 257 <DataTableColumnHeader column={column} title="P95" /> 258 ), 259 cell: ({ row }) => { 260 const value = row.getValue("p95"); 261 if (value === undefined) return <TableCellSkeleton />; 262 if (!value) return <TableCellUnavailable />; 263 return <TableCellNumber value={value} unit="ms" />; 264 }, 265 enableHiding: false, 266 enableGlobalFilter: false, 267 }, 268 { 269 id: "actions", 270 cell: ({ row }) => <DataTableRowActions row={row} />, 271 meta: { 272 cellClassName: "w-8", 273 }, 274 }, 275];