@checkstack/automation-frontend 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # @checkstack/automation-frontend
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b218e3e: Migrate every list table to the shared `DataTable`, so columns can now be
8
+ sorted by clicking their headers (name, status, severity, timestamps, counts,
9
+ ...) and tables that had no search gain a global search box. Tables render on
10
+ an opaque `bg-card` surface, fixing the previously transparent, hard-to-read
11
+ tables (e.g. Catalog Management). Existing per-page filters, bulk selection,
12
+ access gating, extension slots, provenance locks, row-click drawers, and
13
+ mobile card layouts are preserved. Incident/maintenance severity and status
14
+ sort by impact rank (most urgent first), not alphabetically. Server-paginated
15
+ tables keep server-side ordering and do not add a misleading page-local search.
16
+
17
+ Row action buttons are now standardized on the shared `RowActions`/`RowAction`
18
+ primitive, so every table's edit/delete/etc. look identical (a subtle ghost
19
+ icon button; destructive tinted red, confirmatory tinted green, never a loud
20
+ filled button). Redundant section headings that merely echoed the page title on
21
+ single-table pages (Incidents, Maintenances, SLO Objectives, Installed Plugins,
22
+ Satellite Nodes) were removed. The Infrastructure Settings tab rail gained an
23
+ accessible `Infrastructure settings` navigation label so its tab buttons stay
24
+ distinguishable from the new sortable column-header buttons in each tab's table.
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [b218e3e]
29
+ - Updated dependencies [b218e3e]
30
+ - @checkstack/auth-frontend@0.12.0
31
+ - @checkstack/gitops-frontend@0.7.0
32
+ - @checkstack/ui@1.25.0
33
+ - @checkstack/script-packages-frontend@0.4.9
34
+ - @checkstack/secrets-frontend@0.3.8
35
+
36
+ ## 0.10.3
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [c55d7c6]
41
+ - Updated dependencies [c55d7c6]
42
+ - @checkstack/ui@1.24.0
43
+ - @checkstack/common@0.21.0
44
+ - @checkstack/auth-frontend@0.11.3
45
+ - @checkstack/gitops-frontend@0.6.8
46
+ - @checkstack/script-packages-frontend@0.4.8
47
+ - @checkstack/secrets-frontend@0.3.7
48
+ - @checkstack/ai-common@0.6.5
49
+ - @checkstack/auth-common@0.12.2
50
+ - @checkstack/automation-common@0.9.2
51
+ - @checkstack/catalog-common@2.6.2
52
+ - @checkstack/frontend-api@0.13.2
53
+ - @checkstack/integration-common@0.9.7
54
+ - @checkstack/template-engine@0.4.10
55
+ - @checkstack/signal-frontend@0.3.4
56
+
3
57
  ## 0.10.2
4
58
 
5
59
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/automation-frontend",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -16,20 +16,20 @@
16
16
  "lint:code": "eslint . --max-warnings 0"
17
17
  },
18
18
  "dependencies": {
19
- "@checkstack/auth-common": "0.12.1",
20
- "@checkstack/auth-frontend": "0.11.2",
21
- "@checkstack/automation-common": "0.9.1",
22
- "@checkstack/ai-common": "0.6.4",
23
- "@checkstack/catalog-common": "2.6.1",
24
- "@checkstack/common": "0.20.0",
25
- "@checkstack/frontend-api": "0.13.1",
26
- "@checkstack/gitops-frontend": "0.6.7",
27
- "@checkstack/integration-common": "0.9.6",
28
- "@checkstack/script-packages-frontend": "0.4.7",
29
- "@checkstack/secrets-frontend": "0.3.6",
30
- "@checkstack/signal-frontend": "0.3.3",
31
- "@checkstack/template-engine": "0.4.9",
32
- "@checkstack/ui": "1.23.0",
19
+ "@checkstack/auth-common": "0.12.2",
20
+ "@checkstack/auth-frontend": "0.12.0",
21
+ "@checkstack/automation-common": "0.9.2",
22
+ "@checkstack/ai-common": "0.6.5",
23
+ "@checkstack/catalog-common": "2.6.2",
24
+ "@checkstack/common": "0.21.0",
25
+ "@checkstack/frontend-api": "0.13.2",
26
+ "@checkstack/gitops-frontend": "0.7.0",
27
+ "@checkstack/integration-common": "0.9.7",
28
+ "@checkstack/script-packages-frontend": "0.4.9",
29
+ "@checkstack/secrets-frontend": "0.3.8",
30
+ "@checkstack/signal-frontend": "0.3.4",
31
+ "@checkstack/template-engine": "0.4.10",
32
+ "@checkstack/ui": "1.25.0",
33
33
  "@dnd-kit/core": "^6.3.1",
34
34
  "@dnd-kit/sortable": "^8.0.0",
35
35
  "@dnd-kit/utilities": "^3.2.2",
@@ -43,6 +43,6 @@
43
43
  "typescript": "^5.0.0",
44
44
  "@types/react": "^19.0.0",
45
45
  "@checkstack/tsconfig": "0.0.7",
46
- "@checkstack/scripts": "0.7.1"
46
+ "@checkstack/scripts": "0.7.2"
47
47
  }
48
48
  }
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { Link, useNavigate } from "react-router-dom";
3
- import { Workflow, Plus, FlaskConical, Trash2 } from "lucide-react";
3
+ import { Workflow, Plus, FlaskConical, Trash2, History } from "lucide-react";
4
4
  import {
5
5
  usePluginClient,
6
6
  accessApiRef,
@@ -24,14 +24,10 @@ import {
24
24
  Button,
25
25
  Badge,
26
26
  Toggle,
27
- Table,
28
- TableHeader,
29
- TableRow,
30
- TableHead,
31
- TableBody,
32
- TableCell,
33
- ResponsiveTable,
34
- MobileCardList,
27
+ DataTable,
28
+ type DataTableColumn,
29
+ RowActions,
30
+ RowAction,
35
31
  LoadingSpinner,
36
32
  QueryErrorState,
37
33
  EmptyState,
@@ -186,56 +182,56 @@ const AutomationListContent: React.FC = () => {
186
182
  // All sections expanded by default so nothing is hidden on first load.
187
183
  const allGroupKeys = React.useMemo(() => groups.map((g) => g.key), [groups]);
188
184
 
189
- const renderRow = (automation: Automation) => (
190
- <TableRow
191
- key={automation.id}
192
- className="relative cursor-pointer transition-colors hover:bg-surface-inset"
193
- onClick={() =>
194
- navigate(
195
- resolveRoute(automationRoutes.routes.edit, {
196
- automationId: automation.id,
197
- }),
198
- )
199
- }
200
- >
201
- <TableCell
202
- onClick={(e) => e.stopPropagation()}
203
- className="relative pl-4"
204
- >
205
- {/* Status accent: enabled/disabled by position + hue, not toggle alone. */}
206
- <span
207
- className={cn(
208
- "absolute inset-y-0 left-0 w-1",
209
- automationAccent(automation.status),
210
- )}
211
- aria-hidden
212
- />
213
- {canAccess(automation.id) ? (
214
- <Toggle
215
- checked={automation.status === "enabled"}
216
- onCheckedChange={(enabled) =>
217
- toggleMutation.mutate({
218
- id: automation.id,
219
- enabled,
220
- })
221
- }
222
- aria-label={
223
- automation.status === "enabled"
224
- ? "Disable automation"
225
- : "Enable automation"
226
- }
185
+ const columns: DataTableColumn<Automation>[] = [
186
+ {
187
+ id: "status",
188
+ header: "",
189
+ headClassName: "w-8",
190
+ cellClassName: "relative pl-4",
191
+ cell: (automation) => (
192
+ <>
193
+ {/* Status accent: enabled/disabled by position + hue, not toggle alone. */}
194
+ <span
195
+ className={cn(
196
+ "absolute inset-y-0 left-0 w-1",
197
+ automationAccent(automation.status),
198
+ )}
199
+ aria-hidden
227
200
  />
228
- ) : (
229
- <Badge
230
- variant={
231
- automation.status === "enabled" ? "success" : "outline"
232
- }
233
- >
234
- {automation.status}
235
- </Badge>
236
- )}
237
- </TableCell>
238
- <TableCell>
201
+ <div onClick={(e) => e.stopPropagation()}>
202
+ {canAccess(automation.id) ? (
203
+ <Toggle
204
+ checked={automation.status === "enabled"}
205
+ onCheckedChange={(enabled) =>
206
+ toggleMutation.mutate({
207
+ id: automation.id,
208
+ enabled,
209
+ })
210
+ }
211
+ aria-label={
212
+ automation.status === "enabled"
213
+ ? "Disable automation"
214
+ : "Enable automation"
215
+ }
216
+ />
217
+ ) : (
218
+ <Badge
219
+ variant={
220
+ automation.status === "enabled" ? "success" : "outline"
221
+ }
222
+ >
223
+ {automation.status}
224
+ </Badge>
225
+ )}
226
+ </div>
227
+ </>
228
+ ),
229
+ },
230
+ {
231
+ id: "name",
232
+ header: "Name",
233
+ sortValue: (automation) => automation.name,
234
+ cell: (automation) => (
239
235
  <div className="flex flex-col gap-0.5">
240
236
  <span className="font-semibold text-foreground">
241
237
  {automation.name}
@@ -246,8 +242,12 @@ const AutomationListContent: React.FC = () => {
246
242
  </span>
247
243
  )}
248
244
  </div>
249
- </TableCell>
250
- <TableCell>
245
+ ),
246
+ },
247
+ {
248
+ id: "triggers",
249
+ header: "Triggers",
250
+ cell: (automation) => (
251
251
  <div className="flex flex-wrap gap-1">
252
252
  {automation.definition.triggers.slice(0, 3).map((trigger, index) => (
253
253
  <Badge
@@ -267,45 +267,62 @@ const AutomationListContent: React.FC = () => {
267
267
  </Badge>
268
268
  )}
269
269
  </div>
270
- </TableCell>
271
- <TableCell>
270
+ ),
271
+ },
272
+ {
273
+ id: "mode",
274
+ header: "Mode",
275
+ sortValue: (automation) => automation.definition.mode,
276
+ cell: (automation) => (
272
277
  <Badge variant="secondary" className="text-[10px]">
273
278
  {automation.definition.mode}
274
279
  </Badge>
275
- </TableCell>
276
- <TableCell>
280
+ ),
281
+ },
282
+ {
283
+ id: "updated",
284
+ header: "Updated",
285
+ sortValue: (automation) => new Date(automation.updatedAt).getTime(),
286
+ cell: (automation) => (
277
287
  <span className="text-xs text-muted-foreground">
278
288
  {formatDistanceToNow(new Date(automation.updatedAt), {
279
289
  addSuffix: true,
280
290
  })}
281
291
  </span>
282
- </TableCell>
283
- <TableCell onClick={(e) => e.stopPropagation()} className="text-right">
284
- <div className="flex justify-end gap-1">
285
- <Link
286
- to={resolveRoute(automationRoutes.routes.runs, {
287
- automationId: automation.id,
288
- })}
289
- >
290
- <Button variant="ghost" size="sm">
291
- Runs
292
- </Button>
293
- </Link>
294
- {canAccess(automation.id) && (
295
- <Button
296
- variant="ghost"
297
- size="icon"
298
- className="h-8 w-8 text-destructive hover:bg-destructive/10"
299
- onClick={() => setDeleteId(automation.id)}
300
- aria-label="Delete automation"
301
- >
302
- <Trash2 className="h-4 w-4" />
303
- </Button>
304
- )}
292
+ ),
293
+ },
294
+ {
295
+ id: "actions",
296
+ header: "Actions",
297
+ headClassName: "w-24 text-right",
298
+ cellClassName: "text-right",
299
+ cell: (automation) => (
300
+ <div onClick={(e) => e.stopPropagation()}>
301
+ <RowActions>
302
+ <RowAction
303
+ icon={History}
304
+ label="View runs"
305
+ onClick={() =>
306
+ navigate(
307
+ resolveRoute(automationRoutes.routes.runs, {
308
+ automationId: automation.id,
309
+ }),
310
+ )
311
+ }
312
+ />
313
+ {canAccess(automation.id) && (
314
+ <RowAction
315
+ icon={Trash2}
316
+ label="Delete automation"
317
+ tone="destructive"
318
+ onClick={() => setDeleteId(automation.id)}
319
+ />
320
+ )}
321
+ </RowActions>
305
322
  </div>
306
- </TableCell>
307
- </TableRow>
308
- );
323
+ ),
324
+ },
325
+ ];
309
326
 
310
327
  const renderMobileCard = (automation: Automation) => (
311
328
  <div
@@ -391,30 +408,28 @@ const AutomationListContent: React.FC = () => {
391
408
  addSuffix: true,
392
409
  })}
393
410
  </div>
394
- <div
395
- onClick={(e) => e.stopPropagation()}
396
- className="mt-3 flex justify-end gap-1 pl-2"
397
- >
398
- <Link
399
- to={resolveRoute(automationRoutes.routes.runs, {
400
- automationId: automation.id,
401
- })}
402
- >
403
- <Button variant="ghost" size="sm">
404
- Runs
405
- </Button>
406
- </Link>
407
- {canAccess(automation.id) && (
408
- <Button
409
- variant="ghost"
410
- size="icon"
411
- className="h-8 w-8 text-destructive hover:bg-destructive/10"
412
- onClick={() => setDeleteId(automation.id)}
413
- aria-label="Delete automation"
414
- >
415
- <Trash2 className="h-4 w-4" />
416
- </Button>
417
- )}
411
+ <div onClick={(e) => e.stopPropagation()} className="mt-3 pl-2">
412
+ <RowActions>
413
+ <RowAction
414
+ icon={History}
415
+ label="View runs"
416
+ onClick={() =>
417
+ navigate(
418
+ resolveRoute(automationRoutes.routes.runs, {
419
+ automationId: automation.id,
420
+ }),
421
+ )
422
+ }
423
+ />
424
+ {canAccess(automation.id) && (
425
+ <RowAction
426
+ icon={Trash2}
427
+ label="Delete automation"
428
+ tone="destructive"
429
+ onClick={() => setDeleteId(automation.id)}
430
+ />
431
+ )}
432
+ </RowActions>
418
433
  </div>
419
434
  </div>
420
435
  );
@@ -516,32 +531,28 @@ const AutomationListContent: React.FC = () => {
516
531
  </span>
517
532
  </AccordionTrigger>
518
533
  <AccordionContent className="px-0 pb-0">
519
- <ResponsiveTable>
520
- <Table>
521
- <TableHeader>
522
- <TableRow>
523
- <TableHead className="w-8" />
524
- <TableHead>Name</TableHead>
525
- <TableHead>Triggers</TableHead>
526
- <TableHead>Mode</TableHead>
527
- <TableHead>Updated</TableHead>
528
- <TableHead className="w-24 text-right">
529
- Actions
530
- </TableHead>
531
- </TableRow>
532
- </TableHeader>
533
- <TableBody>
534
- {group.items.map((automation) =>
535
- renderRow(automation),
536
- )}
537
- </TableBody>
538
- </Table>
539
- </ResponsiveTable>
540
- <MobileCardList className="p-2">
541
- {group.items.map((automation) =>
542
- renderMobileCard(automation),
543
- )}
544
- </MobileCardList>
534
+ <DataTable
535
+ data={group.items}
536
+ columns={columns}
537
+ getRowId={(automation) => automation.id}
538
+ searchable={false}
539
+ // Nested inside the page's opaque Card, so the default
540
+ // bg-card surface would create a panel-in-panel; the
541
+ // enclosing Card already provides the opaque background.
542
+ surface={false}
543
+ onRowClick={(automation) =>
544
+ navigate(
545
+ resolveRoute(automationRoutes.routes.edit, {
546
+ automationId: automation.id,
547
+ }),
548
+ )
549
+ }
550
+ getRowProps={() => ({
551
+ className:
552
+ "relative transition-colors hover:bg-surface-inset",
553
+ })}
554
+ renderMobileCard={renderMobileCard}
555
+ />
545
556
  </AccordionContent>
546
557
  </AccordionItem>
547
558
  ))}
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { Link, useParams } from "react-router-dom";
3
- import { History, ChevronLeft } from "lucide-react";
2
+ import { Link, useParams, useNavigate } from "react-router-dom";
3
+ import { History, ChevronLeft, ArrowUpRight } from "lucide-react";
4
4
  import {
5
5
  usePluginClient,
6
6
  accessApiRef,
@@ -12,7 +12,7 @@ import {
12
12
  automationAccess,
13
13
  automationRoutes,
14
14
  } from "@checkstack/automation-common";
15
- import type { RunStatus } from "@checkstack/automation-common";
15
+ import type { RunStatus, AutomationRun } from "@checkstack/automation-common";
16
16
  import {
17
17
  PageLayout,
18
18
  Card,
@@ -20,17 +20,13 @@ import {
20
20
  CardHeader,
21
21
  CardTitle,
22
22
  Button,
23
- Table,
24
- TableHeader,
25
- TableRow,
26
- TableHead,
27
- TableBody,
28
- TableCell,
23
+ DataTable,
24
+ type DataTableColumn,
25
+ RowActions,
26
+ RowAction,
29
27
  LoadingSpinner,
30
28
  QueryErrorState,
31
29
  EmptyState,
32
- ResponsiveTable,
33
- MobileCardList,
34
30
  } from "@checkstack/ui";
35
31
  import { resolveRoute } from "@checkstack/common";
36
32
  import { formatDistanceToNow } from "date-fns";
@@ -45,6 +41,7 @@ import { formatDuration } from "./run-duration";
45
41
  */
46
42
  const RunsPageContent: React.FC = () => {
47
43
  const { automationId } = useParams<{ automationId: string }>();
44
+ const navigate = useNavigate();
48
45
  const client = usePluginClient(AutomationApi);
49
46
  const accessApi = useApi(accessApiRef);
50
47
  const { allowed, loading: accessLoading } = accessApi.useAccess(
@@ -73,6 +70,72 @@ const RunsPageContent: React.FC = () => {
73
70
 
74
71
  const runs = runsQuery.data?.items ?? [];
75
72
 
73
+ const columns: DataTableColumn<AutomationRun>[] = [
74
+ {
75
+ id: "status",
76
+ header: "Status",
77
+ sortValue: (run) => run.status,
78
+ cell: (run) => <RunStatusPill status={run.status} />,
79
+ },
80
+ {
81
+ id: "trigger",
82
+ header: "Trigger",
83
+ sortValue: (run) => run.triggerEventId || "manual",
84
+ cell: (run) => (
85
+ <code className="font-mono text-xs">
86
+ {run.triggerEventId || "manual"}
87
+ </code>
88
+ ),
89
+ },
90
+ {
91
+ id: "started",
92
+ header: "Started",
93
+ sortValue: (run) => new Date(run.startedAt).getTime(),
94
+ cell: (run) => (
95
+ <span className="text-xs text-muted-foreground">
96
+ {formatDistanceToNow(new Date(run.startedAt), { addSuffix: true })}
97
+ </span>
98
+ ),
99
+ },
100
+ {
101
+ id: "duration",
102
+ header: "Duration",
103
+ sortValue: (run) =>
104
+ run.finishedAt
105
+ ? new Date(run.finishedAt).getTime() -
106
+ new Date(run.startedAt).getTime()
107
+ : undefined,
108
+ cell: (run) => (
109
+ <span className="text-xs text-muted-foreground">
110
+ {formatDuration(run.startedAt, run.finishedAt)}
111
+ </span>
112
+ ),
113
+ },
114
+ {
115
+ id: "actions",
116
+ header: "",
117
+ headClassName: "w-24 text-right",
118
+ cellClassName: "text-right",
119
+ cell: (run) =>
120
+ automationId ? (
121
+ <RowActions>
122
+ <RowAction
123
+ icon={ArrowUpRight}
124
+ label="Open run details"
125
+ onClick={() =>
126
+ navigate(
127
+ resolveRoute(automationRoutes.routes.runDetail, {
128
+ automationId,
129
+ runId: run.id,
130
+ }),
131
+ )
132
+ }
133
+ />
134
+ </RowActions>
135
+ ) : null,
136
+ },
137
+ ];
138
+
76
139
  return (
77
140
  <PageLayout
78
141
  title={
@@ -142,107 +205,52 @@ const RunsPageContent: React.FC = () => {
142
205
  description="Manually trigger the automation from the edit page to generate a run."
143
206
  />
144
207
  ) : (
145
- <>
146
- <ResponsiveTable>
147
- <Table>
148
- <TableHeader>
149
- <TableRow>
150
- <TableHead>Status</TableHead>
151
- <TableHead>Trigger</TableHead>
152
- <TableHead>Started</TableHead>
153
- <TableHead>Duration</TableHead>
154
- <TableHead className="w-24 text-right" />
155
- </TableRow>
156
- </TableHeader>
157
- <TableBody>
158
- {runs.map((run) => (
159
- <TableRow key={run.id}>
160
- <TableCell>
161
- <RunStatusPill status={run.status} />
162
- </TableCell>
163
- <TableCell>
164
- <code className="font-mono text-xs">
165
- {run.triggerEventId || "manual"}
166
- </code>
167
- </TableCell>
168
- <TableCell>
169
- <span className="text-xs text-muted-foreground">
170
- {formatDistanceToNow(new Date(run.startedAt), {
171
- addSuffix: true,
172
- })}
173
- </span>
174
- </TableCell>
175
- <TableCell>
176
- <span className="text-xs text-muted-foreground">
177
- {formatDuration(run.startedAt, run.finishedAt)}
178
- </span>
179
- </TableCell>
180
- <TableCell className="text-right">
181
- {automationId && (
182
- <Link
183
- to={resolveRoute(
184
- automationRoutes.routes.runDetail,
185
- {
186
- automationId,
187
- runId: run.id,
188
- },
189
- )}
190
- >
191
- <Button variant="ghost" size="sm">
192
- Open
193
- </Button>
194
- </Link>
195
- )}
196
- </TableCell>
197
- </TableRow>
198
- ))}
199
- </TableBody>
200
- </Table>
201
- </ResponsiveTable>
202
-
203
- <MobileCardList className="p-4">
204
- {runs.map((run) => (
205
- <div
206
- key={run.id}
207
- className="rounded-md border bg-surface p-4"
208
- >
209
- <div className="flex items-start justify-between gap-2">
210
- <RunStatusPill status={run.status} />
211
- {automationId && (
212
- <Link
213
- to={resolveRoute(automationRoutes.routes.runDetail, {
214
- automationId,
215
- runId: run.id,
216
- })}
217
- >
218
- <Button variant="ghost" size="sm">
219
- Open
220
- </Button>
221
- </Link>
222
- )}
208
+ <DataTable
209
+ data={runs}
210
+ columns={columns}
211
+ getRowId={(run) => run.id}
212
+ searchable={false}
213
+ renderMobileCard={(run) => (
214
+ <div className="rounded-md border bg-surface p-4">
215
+ <div className="flex items-start justify-between gap-2">
216
+ <RunStatusPill status={run.status} />
217
+ {automationId && (
218
+ <RowActions>
219
+ <RowAction
220
+ icon={ArrowUpRight}
221
+ label="Open run details"
222
+ onClick={() =>
223
+ navigate(
224
+ resolveRoute(automationRoutes.routes.runDetail, {
225
+ automationId,
226
+ runId: run.id,
227
+ }),
228
+ )
229
+ }
230
+ />
231
+ </RowActions>
232
+ )}
233
+ </div>
234
+ <div className="mt-2 flex flex-col gap-1 text-xs text-muted-foreground">
235
+ <div className="flex items-center gap-1">
236
+ <span>Trigger:</span>
237
+ <code className="font-mono">
238
+ {run.triggerEventId || "manual"}
239
+ </code>
223
240
  </div>
224
- <div className="mt-2 flex flex-col gap-1 text-xs text-muted-foreground">
225
- <div className="flex items-center gap-1">
226
- <span>Trigger:</span>
227
- <code className="font-mono">
228
- {run.triggerEventId || "manual"}
229
- </code>
230
- </div>
231
- <div>
232
- Started{" "}
233
- {formatDistanceToNow(new Date(run.startedAt), {
234
- addSuffix: true,
235
- })}
236
- </div>
237
- <div>
238
- Duration:{" "}
239
- {formatDuration(run.startedAt, run.finishedAt)}
240
- </div>
241
+ <div>
242
+ Started{" "}
243
+ {formatDistanceToNow(new Date(run.startedAt), {
244
+ addSuffix: true,
245
+ })}
246
+ </div>
247
+ <div>
248
+ Duration: {formatDuration(run.startedAt, run.finishedAt)}
241
249
  </div>
242
250
  </div>
243
- ))}
244
- </MobileCardList>
245
- </>
251
+ </div>
252
+ )}
253
+ />
246
254
  )}
247
255
  </CardContent>
248
256
  </Card>