Openstatus
www.openstatus.dev
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];