@burdenoff/microfe-cosmicintersection 2026.525.1 → 2026.704.1

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.
Files changed (114) hide show
  1. package/dist/cosmicintersection/CosmicIntersectionRoot.js +38 -113
  2. package/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
  3. package/dist/cosmicintersection/CosmicIntersectionRoutes.js +102 -44
  4. package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
  5. package/dist/cosmicintersection/api/cosmicApi.js +853 -0
  6. package/dist/cosmicintersection/api/cosmicApi.js.map +1 -0
  7. package/dist/cosmicintersection/components/ActionMenu.js +106 -0
  8. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -0
  9. package/dist/cosmicintersection/components/EmptyState.js +35 -0
  10. package/dist/cosmicintersection/components/EmptyState.js.map +1 -0
  11. package/dist/cosmicintersection/components/EntityFormShell.js +57 -0
  12. package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -0
  13. package/dist/cosmicintersection/components/EntityTagsField.js +77 -0
  14. package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -0
  15. package/dist/cosmicintersection/components/ErrorState.js +40 -0
  16. package/dist/cosmicintersection/components/ErrorState.js.map +1 -0
  17. package/dist/cosmicintersection/components/FileUploadField.js +146 -0
  18. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -0
  19. package/dist/cosmicintersection/components/LoadingState.js +23 -0
  20. package/dist/cosmicintersection/components/LoadingState.js.map +1 -0
  21. package/dist/cosmicintersection/components/PageLayout.js +46 -0
  22. package/dist/cosmicintersection/components/PageLayout.js.map +1 -0
  23. package/dist/cosmicintersection/components/Pagination.js +65 -0
  24. package/dist/cosmicintersection/components/Pagination.js.map +1 -0
  25. package/dist/cosmicintersection/components/SafeLink.js +35 -0
  26. package/dist/cosmicintersection/components/SafeLink.js.map +1 -0
  27. package/dist/cosmicintersection/components/StatusBadge.js +53 -0
  28. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -0
  29. package/dist/cosmicintersection/components/index.js +11 -0
  30. package/dist/cosmicintersection/context/CosmicIntersectionContext.js +46 -0
  31. package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -0
  32. package/dist/cosmicintersection/index.js +0 -2
  33. package/dist/cosmicintersection/lib/mutationResult.js +12 -0
  34. package/dist/cosmicintersection/lib/mutationResult.js.map +1 -0
  35. package/dist/cosmicintersection/lib/toast.js +36 -0
  36. package/dist/cosmicintersection/lib/toast.js.map +1 -0
  37. package/dist/cosmicintersection/pages/access/AccessPage.js +340 -0
  38. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -0
  39. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +484 -0
  40. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -0
  41. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +164 -0
  42. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -0
  43. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js +117 -0
  44. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -0
  45. package/dist/cosmicintersection/pages/collections/CollectionsPage.js +103 -0
  46. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -0
  47. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +244 -0
  48. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -0
  49. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +460 -0
  50. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -0
  51. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js +218 -0
  52. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -0
  53. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +252 -0
  54. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -0
  55. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +393 -0
  56. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -0
  57. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js +313 -0
  58. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -0
  59. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +203 -0
  60. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -0
  61. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +244 -0
  62. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -0
  63. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +210 -0
  64. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -0
  65. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +193 -0
  66. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -0
  67. package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js +184 -0
  68. package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -0
  69. package/dist/cosmicintersection/pages/releases/ReleasesPage.js +368 -0
  70. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -0
  71. package/dist/cosmicintersection/pages/runs/RunDetailPage.js +375 -0
  72. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -0
  73. package/dist/cosmicintersection/pages/runs/RunsPage.js +180 -0
  74. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -0
  75. package/dist/cosmicintersection/pages/sources/SourceFormPage.js +229 -0
  76. package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -0
  77. package/dist/cosmicintersection/pages/sources/SourcesPage.js +217 -0
  78. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -0
  79. package/dist/cosmicintersection/utils/cn.js +10 -0
  80. package/dist/cosmicintersection/utils/cn.js.map +1 -0
  81. package/dist/index.js +2 -4
  82. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  83. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  84. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  85. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  86. package/package.json +2 -1
  87. package/dist/cosmicintersection/pages/AiDiscoveryPage.js +0 -363
  88. package/dist/cosmicintersection/pages/AiDiscoveryPage.js.map +0 -1
  89. package/dist/cosmicintersection/pages/DashboardPage.js +0 -306
  90. package/dist/cosmicintersection/pages/DashboardPage.js.map +0 -1
  91. package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js +0 -287
  92. package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js.map +0 -1
  93. package/dist/cosmicintersection/pages/SpaceScienceDataPage.js +0 -385
  94. package/dist/cosmicintersection/pages/SpaceScienceDataPage.js.map +0 -1
  95. package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js +0 -420
  96. package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js.map +0 -1
  97. package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js +0 -309
  98. package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js.map +0 -1
  99. package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js +0 -361
  100. package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js.map +0 -1
  101. package/dist/cosmicintersection/pages/SpaceSciencePage.js +0 -194
  102. package/dist/cosmicintersection/pages/SpaceSciencePage.js.map +0 -1
  103. package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js +0 -290
  104. package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js.map +0 -1
  105. package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js +0 -279
  106. package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js.map +0 -1
  107. package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js +0 -27
  108. package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js.map +0 -1
  109. package/dist/cosmicintersection/store/cosmicintersectionStore.js +0 -478
  110. package/dist/cosmicintersection/store/cosmicintersectionStore.js.map +0 -1
  111. package/dist/cosmicintersection/utils/i18n.js +0 -15
  112. package/dist/cosmicintersection/utils/i18n.js.map +0 -1
  113. package/dist/cosmicintersection/utils/navigation.js +0 -8
  114. package/dist/cosmicintersection/utils/navigation.js.map +0 -1
@@ -0,0 +1,375 @@
1
+ import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
2
+ import { notify as t } from "../../lib/toast.js";
3
+ import { useCosAddRunStepMutation as n, useCosRunQuery as r, useCosRunStepsQuery as ee, useCosTransitionRunMutation as te } from "../../api/cosmicApi.js";
4
+ import i from "../../components/EmptyState.js";
5
+ import a from "../../components/ErrorState.js";
6
+ import o from "../../components/LoadingState.js";
7
+ import s from "../../components/PageLayout.js";
8
+ import ne from "../../components/SafeLink.js";
9
+ import c, { formatEnumLabel as l } from "../../components/StatusBadge.js";
10
+ import "../../components/index.js";
11
+ import { describeError as re, mutationSucceeded as u } from "../../lib/mutationResult.js";
12
+ import { useMemo as ie, useState as d } from "react";
13
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
14
+ import { useParams as m } from "react-router-dom";
15
+ import { ListTree as h, Plus as g } from "lucide-react";
16
+ import { Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, Dialog as S, DialogContent as ae, DialogDescription as oe, DialogFooter as C, DialogHeader as se, DialogTitle as ce, Input as w, Label as T, Select as E, SelectContent as D, SelectItem as le, SelectTrigger as ue, SelectValue as de, Textarea as fe } from "@burdenoff/fe-libs/ui";
17
+ //#region src/cosmicintersection/pages/runs/RunDetailPage.tsx
18
+ var pe = [
19
+ "INGEST",
20
+ "CALIBRATE",
21
+ "TRANSFORM",
22
+ "ANALYZE",
23
+ "EXPORT",
24
+ "NOTIFY"
25
+ ], me = {
26
+ QUEUED: ["RUNNING", "CANCELLED"],
27
+ RUNNING: [
28
+ "SUCCEEDED",
29
+ "FAILED",
30
+ "CANCELLED"
31
+ ],
32
+ SUCCEEDED: [],
33
+ FAILED: [],
34
+ CANCELLED: []
35
+ };
36
+ function O(e) {
37
+ if (!e) return "—";
38
+ let t = new Date(e);
39
+ return Number.isNaN(t.getTime()) ? "—" : t.toLocaleString();
40
+ }
41
+ function k(e) {
42
+ if (e == null) return "—";
43
+ if (e < 1e3) return `${e} ms`;
44
+ let t = e / 1e3;
45
+ return t < 60 ? `${t.toFixed(1)} s` : `${Math.floor(t / 60)}m ${Math.round(t % 60)}s`;
46
+ }
47
+ function A({ label: e, children: t }) {
48
+ return /* @__PURE__ */ p("div", {
49
+ className: "flex flex-col gap-1 border-b border-border-seam py-3 last:border-0 sm:flex-row sm:items-start sm:gap-4",
50
+ children: [/* @__PURE__ */ f("span", {
51
+ className: "w-40 shrink-0 text-sm font-medium text-text-secondary",
52
+ children: e
53
+ }), /* @__PURE__ */ f("span", {
54
+ className: "min-w-0 text-sm text-text-primary",
55
+ children: t
56
+ })]
57
+ });
58
+ }
59
+ function j({ value: e }) {
60
+ return !e || Object.keys(e).length === 0 ? /* @__PURE__ */ f("span", {
61
+ className: "text-sm text-text-secondary",
62
+ children: "—"
63
+ }) : /* @__PURE__ */ f("pre", {
64
+ className: "overflow-x-auto rounded-card border border-border-default bg-bg-sunken p-3 font-mono text-xs text-text-primary",
65
+ children: JSON.stringify(e, null, 2)
66
+ });
67
+ }
68
+ function M() {
69
+ let { id: M = "" } = m(), { navigate: N } = e(), { data: P, loading: F, error: I, refetch: L } = r(M), { data: R, loading: he, error: ge, refetch: z } = ee(M), [_e, { loading: ve }] = te(), [ye, { loading: B }] = n(), [be, V] = d(!1), [H, U] = d(""), [W, G] = d("INGEST"), [K, q] = d(""), [J, Y] = d(""), X = P?.cosRun, Z = ie(() => [...R?.cosListRunSteps ?? []].sort((e, t) => e.position - t.position), [R]), xe = async (e) => {
70
+ if (!X) return;
71
+ let n = await _e({ variables: {
72
+ id: X.id,
73
+ status: e
74
+ } });
75
+ u(n, n.data?.cosTransitionRun?.id, "Could not update the run") && (t.success("Run updated", `Run #${X.runNumber} moved to ${l(e)}.`), await Promise.all([L(), z()]));
76
+ }, Q = () => {
77
+ U(""), G("INGEST"), q(""), Y("");
78
+ }, Se = async () => {
79
+ if (!X) return;
80
+ if (!H.trim()) {
81
+ t.error("Step name is required", "Give the step a name before adding it.");
82
+ return;
83
+ }
84
+ let e;
85
+ if (J.trim()) try {
86
+ let t = JSON.parse(J.trim());
87
+ if (typeof t != "object" || !t || Array.isArray(t)) throw Error("Inputs must be a JSON object.");
88
+ e = t;
89
+ } catch (e) {
90
+ t.error("Invalid inputs JSON", re(e) ?? "Please provide valid JSON.");
91
+ return;
92
+ }
93
+ let n = K.trim() ? Number(K.trim()) : void 0;
94
+ if (n !== void 0 && (Number.isNaN(n) || n < 0)) {
95
+ t.error("Invalid position", "Position must be a non-negative number.");
96
+ return;
97
+ }
98
+ let r = await ye({ variables: { input: {
99
+ runId: X.id,
100
+ name: H.trim(),
101
+ kind: W,
102
+ inputs: e,
103
+ position: n
104
+ } } });
105
+ u(r, r.data?.cosAddRunStep?.id, "Could not add the step") && (t.success("Step added", `“${H.trim()}” was added to the run.`), V(!1), Q(), await z());
106
+ };
107
+ if (F) return /* @__PURE__ */ f(s, {
108
+ title: "Run",
109
+ backPath: "runs",
110
+ children: /* @__PURE__ */ f(o, { label: "Loading run…" })
111
+ });
112
+ if (I) return /* @__PURE__ */ f(s, {
113
+ title: "Run",
114
+ backPath: "runs",
115
+ children: /* @__PURE__ */ f(a, { onRetry: () => void L() })
116
+ });
117
+ if (!X) return /* @__PURE__ */ f(s, {
118
+ title: "Run",
119
+ backPath: "runs",
120
+ children: /* @__PURE__ */ f(i, {
121
+ title: "Run not found",
122
+ description: "This run may have been removed or you may not have access to it.",
123
+ action: /* @__PURE__ */ f(_, {
124
+ type: "button",
125
+ variant: "outline",
126
+ onClick: () => N("runs"),
127
+ children: "Back to runs"
128
+ })
129
+ })
130
+ });
131
+ let $ = me[X.status] ?? [];
132
+ return /* @__PURE__ */ p(s, {
133
+ title: `Run #${X.runNumber}`,
134
+ description: X.pipeline ? `Pipeline: ${X.pipeline.name}` : void 0,
135
+ backPath: "runs",
136
+ actions: /* @__PURE__ */ p("div", {
137
+ className: "flex flex-wrap items-center gap-2",
138
+ children: [$.map((e) => /* @__PURE__ */ f(_, {
139
+ type: "button",
140
+ variant: e === "CANCELLED" || e === "FAILED" ? "outline" : "default",
141
+ onClick: () => void xe(e),
142
+ disabled: ve,
143
+ children: `Mark ${l(e)}`
144
+ }, e)), /* @__PURE__ */ p(_, {
145
+ type: "button",
146
+ variant: "outline",
147
+ onClick: () => V(!0),
148
+ children: [/* @__PURE__ */ f(g, { className: "size-4" }), "Add step"]
149
+ })]
150
+ }),
151
+ children: [/* @__PURE__ */ p("div", {
152
+ className: "space-y-6",
153
+ children: [
154
+ /* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(b, { children: /* @__PURE__ */ f(x, { children: "Overview" }) }), /* @__PURE__ */ p(y, { children: [
155
+ /* @__PURE__ */ f(A, {
156
+ label: "Status",
157
+ children: /* @__PURE__ */ f(c, { status: X.status })
158
+ }),
159
+ /* @__PURE__ */ f(A, {
160
+ label: "Pipeline",
161
+ children: X.pipeline ? /* @__PURE__ */ f("button", {
162
+ type: "button",
163
+ className: "text-text-primary underline-offset-2 hover:underline",
164
+ onClick: () => N(`pipelines/${X.pipelineId}`),
165
+ children: X.pipeline.name
166
+ }) : /* @__PURE__ */ f("span", {
167
+ className: "font-mono text-xs",
168
+ children: X.pipelineId
169
+ })
170
+ }),
171
+ /* @__PURE__ */ f(A, {
172
+ label: "Dataset",
173
+ children: X.datasetId ? /* @__PURE__ */ f("button", {
174
+ type: "button",
175
+ className: "text-text-primary underline-offset-2 hover:underline",
176
+ onClick: () => N(`datasets/${X.datasetId}`),
177
+ children: /* @__PURE__ */ f("span", {
178
+ className: "font-mono text-xs",
179
+ children: X.datasetId
180
+ })
181
+ }) : "—"
182
+ }),
183
+ /* @__PURE__ */ f(A, {
184
+ label: "Triggered by",
185
+ children: X.triggeredBy || "—"
186
+ }),
187
+ /* @__PURE__ */ f(A, {
188
+ label: "Started",
189
+ children: O(X.startedAt)
190
+ }),
191
+ /* @__PURE__ */ f(A, {
192
+ label: "Finished",
193
+ children: O(X.finishedAt)
194
+ }),
195
+ /* @__PURE__ */ f(A, {
196
+ label: "Duration",
197
+ children: k(X.durationMs)
198
+ }),
199
+ X.errorMessage ? /* @__PURE__ */ f(A, {
200
+ label: "Error",
201
+ children: /* @__PURE__ */ f("span", {
202
+ className: "text-status-error-text",
203
+ children: X.errorMessage
204
+ })
205
+ }) : null,
206
+ $.length === 0 ? /* @__PURE__ */ f(A, {
207
+ label: "Discuss",
208
+ children: /* @__PURE__ */ f(ne, {
209
+ href: "/conversations",
210
+ className: "text-text-primary underline-offset-2 hover:underline",
211
+ children: "Open discussions"
212
+ })
213
+ }) : null
214
+ ] })] }),
215
+ /* @__PURE__ */ p("div", {
216
+ className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
217
+ children: [/* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(b, { children: /* @__PURE__ */ f(x, { children: "Parameters" }) }), /* @__PURE__ */ f(y, { children: /* @__PURE__ */ f(j, { value: X.params }) })] }), /* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(b, { children: /* @__PURE__ */ f(x, { children: "Metrics" }) }), /* @__PURE__ */ f(y, { children: /* @__PURE__ */ f(j, { value: X.metrics }) })] })]
218
+ }),
219
+ /* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(b, { children: /* @__PURE__ */ f(x, { children: "Steps" }) }), /* @__PURE__ */ f(y, { children: he ? /* @__PURE__ */ f(o, {
220
+ variant: "rows",
221
+ rows: 4,
222
+ label: "Loading steps…"
223
+ }) : ge ? /* @__PURE__ */ f(a, {
224
+ message: "Could not load steps.",
225
+ onRetry: () => void z()
226
+ }) : Z.length === 0 ? /* @__PURE__ */ f(i, {
227
+ icon: h,
228
+ title: "No steps yet",
229
+ description: "Add steps to record the ingest, calibration, transform, analyze or export stages of this run.",
230
+ action: /* @__PURE__ */ p(_, {
231
+ type: "button",
232
+ onClick: () => V(!0),
233
+ children: [/* @__PURE__ */ f(g, { className: "size-4" }), "Add step"]
234
+ })
235
+ }) : /* @__PURE__ */ f("ol", {
236
+ className: "space-y-3",
237
+ children: Z.map((e) => /* @__PURE__ */ p("li", {
238
+ className: "flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-start sm:justify-between",
239
+ children: [/* @__PURE__ */ p("div", {
240
+ className: "flex min-w-0 items-start gap-3",
241
+ children: [/* @__PURE__ */ f("span", {
242
+ className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold tabular-nums text-text-secondary",
243
+ children: e.position
244
+ }), /* @__PURE__ */ p("div", {
245
+ className: "min-w-0",
246
+ children: [
247
+ /* @__PURE__ */ p("div", {
248
+ className: "flex flex-wrap items-center gap-2",
249
+ children: [/* @__PURE__ */ f("span", {
250
+ className: "font-medium text-text-primary",
251
+ children: e.name
252
+ }), /* @__PURE__ */ f(c, {
253
+ status: e.kind,
254
+ tone: "neutral"
255
+ })]
256
+ }),
257
+ e.errorMessage ? /* @__PURE__ */ f("p", {
258
+ className: "mt-1 text-xs text-status-error-text",
259
+ children: e.errorMessage
260
+ }) : null,
261
+ /* @__PURE__ */ p("p", {
262
+ className: "mt-1 text-xs text-text-secondary",
263
+ children: ["Duration: ", k(e.durationMs)]
264
+ })
265
+ ]
266
+ })]
267
+ }), /* @__PURE__ */ f("div", {
268
+ className: "shrink-0",
269
+ children: /* @__PURE__ */ f(c, { status: e.status })
270
+ })]
271
+ }, e.id))
272
+ }) })] })
273
+ ]
274
+ }), /* @__PURE__ */ f(S, {
275
+ open: be,
276
+ onOpenChange: (e) => {
277
+ B || (V(e), e || Q());
278
+ },
279
+ children: /* @__PURE__ */ p(ae, { children: [
280
+ /* @__PURE__ */ p(se, { children: [/* @__PURE__ */ f(ce, { children: "Add step" }), /* @__PURE__ */ f(oe, { children: "Record a stage of this run — its kind, order and optional inputs." })] }),
281
+ /* @__PURE__ */ p("div", {
282
+ className: "space-y-4",
283
+ children: [
284
+ /* @__PURE__ */ p("div", {
285
+ className: "space-y-1",
286
+ children: [/* @__PURE__ */ f(T, {
287
+ htmlFor: "step-name",
288
+ children: "Name"
289
+ }), /* @__PURE__ */ f(w, {
290
+ id: "step-name",
291
+ value: H,
292
+ onChange: (e) => U(e.target.value),
293
+ placeholder: "e.g. Calibrate flat fields"
294
+ })]
295
+ }),
296
+ /* @__PURE__ */ p("div", {
297
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
298
+ children: [/* @__PURE__ */ p("div", {
299
+ className: "space-y-1",
300
+ children: [/* @__PURE__ */ f(T, {
301
+ htmlFor: "step-kind",
302
+ children: "Kind"
303
+ }), /* @__PURE__ */ p(E, {
304
+ value: W,
305
+ onValueChange: (e) => G(e),
306
+ children: [/* @__PURE__ */ f(ue, {
307
+ id: "step-kind",
308
+ "aria-label": "Step kind",
309
+ children: /* @__PURE__ */ f(de, {})
310
+ }), /* @__PURE__ */ f(D, { children: pe.map((e) => /* @__PURE__ */ f(le, {
311
+ value: e,
312
+ children: l(e)
313
+ }, e)) })]
314
+ })]
315
+ }), /* @__PURE__ */ p("div", {
316
+ className: "space-y-1",
317
+ children: [/* @__PURE__ */ f(T, {
318
+ htmlFor: "step-position",
319
+ children: "Position"
320
+ }), /* @__PURE__ */ f(w, {
321
+ id: "step-position",
322
+ type: "number",
323
+ min: "0",
324
+ value: K,
325
+ onChange: (e) => q(e.target.value),
326
+ placeholder: "Auto"
327
+ })]
328
+ })]
329
+ }),
330
+ /* @__PURE__ */ p("div", {
331
+ className: "space-y-1",
332
+ children: [
333
+ /* @__PURE__ */ f(T, {
334
+ htmlFor: "step-inputs",
335
+ children: "Inputs (JSON)"
336
+ }),
337
+ /* @__PURE__ */ f(fe, {
338
+ id: "step-inputs",
339
+ value: J,
340
+ onChange: (e) => Y(e.target.value),
341
+ rows: 4,
342
+ spellCheck: !1,
343
+ className: "font-mono text-xs",
344
+ placeholder: "{\n \"source\": \"raw-frames\"\n}"
345
+ }),
346
+ /* @__PURE__ */ f("p", {
347
+ className: "text-xs text-text-secondary",
348
+ children: "Optional. A JSON object of step inputs."
349
+ })
350
+ ]
351
+ })
352
+ ]
353
+ }),
354
+ /* @__PURE__ */ p(C, { children: [/* @__PURE__ */ f(_, {
355
+ type: "button",
356
+ variant: "outline",
357
+ onClick: () => {
358
+ V(!1), Q();
359
+ },
360
+ disabled: B,
361
+ children: "Cancel"
362
+ }), /* @__PURE__ */ p(_, {
363
+ type: "button",
364
+ onClick: () => void Se(),
365
+ disabled: B,
366
+ children: [/* @__PURE__ */ f(g, { className: "size-4" }), B ? "Adding…" : "Add step"]
367
+ })] })
368
+ ] })
369
+ })]
370
+ });
371
+ }
372
+ //#endregion
373
+ export { M as default };
374
+
375
+ //# sourceMappingURL=RunDetailPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RunDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ListTree, Plus } from 'lucide-react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAddRunStepMutation,\n useCosRunQuery,\n useCosRunStepsQuery,\n useCosTransitionRunMutation,\n type CosRunStatus,\n type CosStepKind,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst STEP_KIND_OPTIONS: CosStepKind[] = [\n 'INGEST',\n 'CALIBRATE',\n 'TRANSFORM',\n 'ANALYZE',\n 'EXPORT',\n 'NOTIFY',\n];\n\n// Valid forward transitions for the run state machine. Terminal states have none.\nconst NEXT_STATUSES: Record<CosRunStatus, CosRunStatus[]> = {\n QUEUED: ['RUNNING', 'CANCELLED'],\n RUNNING: ['SUCCEEDED', 'FAILED', 'CANCELLED'],\n SUCCEEDED: [],\n FAILED: [],\n CANCELLED: [],\n};\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nfunction DetailRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1 border-b border-border-seam py-3 last:border-0 sm:flex-row sm:items-start sm:gap-4\">\n <span className=\"w-40 shrink-0 text-sm font-medium text-text-secondary\">{label}</span>\n <span className=\"min-w-0 text-sm text-text-primary\">{children}</span>\n </div>\n );\n}\n\nfunction JsonBlock({ value }: { value?: Record<string, unknown> | null }) {\n if (!value || Object.keys(value).length === 0) {\n return <span className=\"text-sm text-text-secondary\">—</span>;\n }\n return (\n <pre className=\"overflow-x-auto rounded-card border border-border-default bg-bg-sunken p-3 font-mono text-xs text-text-primary\">\n {JSON.stringify(value, null, 2)}\n </pre>\n );\n}\n\nexport default function RunDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosRunQuery(id);\n const {\n data: stepsData,\n loading: stepsLoading,\n error: stepsError,\n refetch: refetchSteps,\n } = useCosRunStepsQuery(id);\n\n const [transitionRun, { loading: transitioning }] = useCosTransitionRunMutation();\n const [addStep, { loading: addingStep }] = useCosAddRunStepMutation();\n\n const [stepDialogOpen, setStepDialogOpen] = useState(false);\n const [stepName, setStepName] = useState('');\n const [stepKind, setStepKind] = useState<CosStepKind>('INGEST');\n const [stepPosition, setStepPosition] = useState('');\n const [stepInputs, setStepInputs] = useState('');\n\n const run = data?.cosRun;\n const steps = useMemo(\n () => [...(stepsData?.cosListRunSteps ?? [])].sort((a, b) => a.position - b.position),\n [stepsData]\n );\n\n const handleTransition = async (nextStatus: CosRunStatus) => {\n if (!run) {\n return;\n }\n const response = await transitionRun({ variables: { id: run.id, status: nextStatus } });\n if (\n !mutationSucceeded(response, response.data?.cosTransitionRun?.id, 'Could not update the run')\n ) {\n return;\n }\n notify.success('Run updated', `Run #${run.runNumber} moved to ${formatEnumLabel(nextStatus)}.`);\n await Promise.all([refetch(), refetchSteps()]);\n };\n\n const resetStepForm = () => {\n setStepName('');\n setStepKind('INGEST');\n setStepPosition('');\n setStepInputs('');\n };\n\n const handleAddStep = async () => {\n if (!run) {\n return;\n }\n if (!stepName.trim()) {\n notify.error('Step name is required', 'Give the step a name before adding it.');\n return;\n }\n\n let inputs: Record<string, unknown> | undefined;\n if (stepInputs.trim()) {\n try {\n const parsed = JSON.parse(stepInputs.trim()) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Inputs must be a JSON object.');\n }\n inputs = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid inputs JSON',\n describeError(parseError) ?? 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const positionValue = stepPosition.trim() ? Number(stepPosition.trim()) : undefined;\n if (positionValue !== undefined && (Number.isNaN(positionValue) || positionValue < 0)) {\n notify.error('Invalid position', 'Position must be a non-negative number.');\n return;\n }\n\n const response = await addStep({\n variables: {\n input: {\n runId: run.id,\n name: stepName.trim(),\n kind: stepKind,\n inputs,\n position: positionValue,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.cosAddRunStep?.id, 'Could not add the step')) {\n return;\n }\n notify.success('Step added', `“${stepName.trim()}” was added to the run.`);\n setStepDialogOpen(false);\n resetStepForm();\n await refetchSteps();\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <LoadingState label=\"Loading run…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <ErrorState onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!run) {\n return (\n <PageLayout title=\"Run\" backPath=\"runs\">\n <EmptyState\n title=\"Run not found\"\n description=\"This run may have been removed or you may not have access to it.\"\n action={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('runs')}>\n Back to runs\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const validTransitions = NEXT_STATUSES[run.status] ?? [];\n\n return (\n <PageLayout\n title={`Run #${run.runNumber}`}\n description={run.pipeline ? `Pipeline: ${run.pipeline.name}` : undefined}\n backPath=\"runs\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n {validTransitions.map((next) => (\n <Button\n key={next}\n type=\"button\"\n variant={next === 'CANCELLED' || next === 'FAILED' ? 'outline' : 'default'}\n onClick={() => void handleTransition(next)}\n disabled={transitioning}\n >\n {`Mark ${formatEnumLabel(next)}`}\n </Button>\n ))}\n <Button type=\"button\" variant=\"outline\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n </div>\n }\n >\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent>\n <DetailRow label=\"Status\">\n <StatusBadge status={run.status} />\n </DetailRow>\n <DetailRow label=\"Pipeline\">\n {run.pipeline ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`pipelines/${run.pipelineId}`)}\n >\n {run.pipeline.name}\n </button>\n ) : (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </DetailRow>\n <DetailRow label=\"Dataset\">\n {run.datasetId ? (\n <button\n type=\"button\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n onClick={() => navigate(`datasets/${run.datasetId}`)}\n >\n <span className=\"font-mono text-xs\">{run.datasetId}</span>\n </button>\n ) : (\n '—'\n )}\n </DetailRow>\n <DetailRow label=\"Triggered by\">{run.triggeredBy || '—'}</DetailRow>\n <DetailRow label=\"Started\">{formatDate(run.startedAt)}</DetailRow>\n <DetailRow label=\"Finished\">{formatDate(run.finishedAt)}</DetailRow>\n <DetailRow label=\"Duration\">{formatDuration(run.durationMs)}</DetailRow>\n {run.errorMessage ? (\n <DetailRow label=\"Error\">\n <span className=\"text-status-error-text\">{run.errorMessage}</span>\n </DetailRow>\n ) : null}\n {validTransitions.length === 0 ? (\n <DetailRow label=\"Discuss\">\n <SafeLink\n href=\"/conversations\"\n className=\"text-text-primary underline-offset-2 hover:underline\"\n >\n Open discussions\n </SafeLink>\n </DetailRow>\n ) : null}\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Parameters</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.params} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Metrics</CardTitle>\n </CardHeader>\n <CardContent>\n <JsonBlock value={run.metrics} />\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Steps</CardTitle>\n </CardHeader>\n <CardContent>\n {stepsLoading ? (\n <LoadingState variant=\"rows\" rows={4} label=\"Loading steps…\" />\n ) : stepsError ? (\n <ErrorState message=\"Could not load steps.\" onRetry={() => void refetchSteps()} />\n ) : steps.length === 0 ? (\n <EmptyState\n icon={ListTree}\n title=\"No steps yet\"\n description=\"Add steps to record the ingest, calibration, transform, analyze or export stages of this run.\"\n action={\n <Button type=\"button\" onClick={() => setStepDialogOpen(true)}>\n <Plus className=\"size-4\" />\n Add step\n </Button>\n }\n />\n ) : (\n <ol className=\"space-y-3\">\n {steps.map((step) => (\n <li\n key={step.id}\n className=\"flex flex-col gap-2 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-start gap-3\">\n <span className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold tabular-nums text-text-secondary\">\n {step.position}\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-text-primary\">{step.name}</span>\n <StatusBadge status={step.kind} tone=\"neutral\" />\n </div>\n {step.errorMessage ? (\n <p className=\"mt-1 text-xs text-status-error-text\">{step.errorMessage}</p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n Duration: {formatDuration(step.durationMs)}\n </p>\n </div>\n </div>\n <div className=\"shrink-0\">\n <StatusBadge status={step.status} />\n </div>\n </li>\n ))}\n </ol>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Dialog\n open={stepDialogOpen}\n onOpenChange={(next) => {\n if (addingStep) {\n return;\n }\n setStepDialogOpen(next);\n if (!next) {\n resetStepForm();\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Add step</DialogTitle>\n <DialogDescription>\n Record a stage of this run — its kind, order and optional inputs.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-name\">Name</Label>\n <Input\n id=\"step-name\"\n value={stepName}\n onChange={(event) => setStepName(event.target.value)}\n placeholder=\"e.g. Calibrate flat fields\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-kind\">Kind</Label>\n <Select\n value={stepKind}\n onValueChange={(value) => setStepKind(value as CosStepKind)}\n >\n <SelectTrigger id=\"step-kind\" aria-label=\"Step kind\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STEP_KIND_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-position\">Position</Label>\n <Input\n id=\"step-position\"\n type=\"number\"\n min=\"0\"\n value={stepPosition}\n onChange={(event) => setStepPosition(event.target.value)}\n placeholder=\"Auto\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <Label htmlFor=\"step-inputs\">Inputs (JSON)</Label>\n <Textarea\n id=\"step-inputs\"\n value={stepInputs}\n onChange={(event) => setStepInputs(event.target.value)}\n rows={4}\n spellCheck={false}\n className=\"font-mono text-xs\"\n placeholder={'{\\n \"source\": \"raw-frames\"\\n}'}\n />\n <p className=\"text-xs text-text-secondary\">Optional. A JSON object of step inputs.</p>\n </div>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setStepDialogOpen(false);\n resetStepForm();\n }}\n disabled={addingStep}\n >\n Cancel\n </Button>\n <Button type=\"button\" onClick={() => void handleAddStep()} disabled={addingStep}>\n <Plus className=\"size-4\" />\n {addingStep ? 'Adding…' : 'Add step'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
2
+ import { useCosPipelinesQuery as t, useCosRunsQuery as n } from "../../api/cosmicApi.js";
3
+ import r from "../../components/ActionMenu.js";
4
+ import i from "../../components/EmptyState.js";
5
+ import a from "../../components/ErrorState.js";
6
+ import o from "../../components/LoadingState.js";
7
+ import s from "../../components/PageLayout.js";
8
+ import c, { usePagination as l } from "../../components/Pagination.js";
9
+ import u, { formatEnumLabel as d } from "../../components/StatusBadge.js";
10
+ import "../../components/index.js";
11
+ import { useMemo as f, useState as p } from "react";
12
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
13
+ import { Activity as _, Eye as v } from "lucide-react";
14
+ import { Button as y, Select as b, SelectContent as x, SelectItem as S, SelectTrigger as C, SelectValue as w, Table as T, TableBody as E, TableCell as D, TableHead as O, TableHeader as k, TableRow as A } from "@burdenoff/fe-libs/ui";
15
+ //#region src/cosmicintersection/pages/runs/RunsPage.tsx
16
+ var j = [
17
+ "QUEUED",
18
+ "RUNNING",
19
+ "SUCCEEDED",
20
+ "FAILED",
21
+ "CANCELLED"
22
+ ], M = "ALL";
23
+ function N(e) {
24
+ if (!e) return "—";
25
+ let t = new Date(e);
26
+ return Number.isNaN(t.getTime()) ? "—" : t.toLocaleString();
27
+ }
28
+ function P(e) {
29
+ if (e == null) return "—";
30
+ if (e < 1e3) return `${e} ms`;
31
+ let t = e / 1e3;
32
+ return t < 60 ? `${t.toFixed(1)} s` : `${Math.floor(t / 60)}m ${Math.round(t % 60)}s`;
33
+ }
34
+ function F() {
35
+ let { navigate: F } = e(), [I, L] = p(M), [R, z] = p(M), B = l({ resetKey: `${I}:${R}` }), { data: V } = t({ pagination: {
36
+ limit: 100,
37
+ offset: 0
38
+ } }), H = f(() => V?.cosListPipelines.items ?? [], [V]), U = f(() => {
39
+ let e = /* @__PURE__ */ new Map();
40
+ return H.forEach((t) => e.set(t.id, t.name)), e;
41
+ }, [H]), { data: W, loading: G, error: K, refetch: q } = n({
42
+ status: I === M ? void 0 : I,
43
+ pipelineId: R === M ? void 0 : R,
44
+ pagination: {
45
+ limit: B.limit,
46
+ offset: B.offset
47
+ }
48
+ }), J = f(() => W?.cosListRuns.items ?? [], [W]), Y = W?.cosListRuns.pageInfo;
49
+ return /* @__PURE__ */ h(s, {
50
+ title: "Runs",
51
+ description: "Every pipeline execution, with status, timings and outcomes.",
52
+ actions: /* @__PURE__ */ g(y, {
53
+ type: "button",
54
+ variant: "outline",
55
+ onClick: () => F("pipelines"),
56
+ children: [/* @__PURE__ */ h(v, { className: "size-4" }), "View pipelines"]
57
+ }),
58
+ children: /* @__PURE__ */ g("div", {
59
+ className: "space-y-4",
60
+ children: [/* @__PURE__ */ g("div", {
61
+ className: "flex flex-wrap items-center gap-3",
62
+ children: [/* @__PURE__ */ g("label", {
63
+ className: "flex items-center gap-2 text-sm text-text-secondary",
64
+ children: [/* @__PURE__ */ h("span", { children: "Status" }), /* @__PURE__ */ g(b, {
65
+ value: I,
66
+ onValueChange: (e) => L(e),
67
+ children: [/* @__PURE__ */ h(C, {
68
+ "aria-label": "Filter by status",
69
+ className: "w-40",
70
+ children: /* @__PURE__ */ h(w, {})
71
+ }), /* @__PURE__ */ g(x, { children: [/* @__PURE__ */ h(S, {
72
+ value: M,
73
+ children: "All statuses"
74
+ }), j.map((e) => /* @__PURE__ */ h(S, {
75
+ value: e,
76
+ children: d(e)
77
+ }, e))] })]
78
+ })]
79
+ }), /* @__PURE__ */ g("label", {
80
+ className: "flex items-center gap-2 text-sm text-text-secondary",
81
+ children: [/* @__PURE__ */ h("span", { children: "Pipeline" }), /* @__PURE__ */ g(b, {
82
+ value: R,
83
+ onValueChange: (e) => z(e),
84
+ children: [/* @__PURE__ */ h(C, {
85
+ "aria-label": "Filter by pipeline",
86
+ className: "w-56",
87
+ children: /* @__PURE__ */ h(w, {})
88
+ }), /* @__PURE__ */ g(x, { children: [/* @__PURE__ */ h(S, {
89
+ value: M,
90
+ children: "All pipelines"
91
+ }), H.map((e) => /* @__PURE__ */ h(S, {
92
+ value: e.id,
93
+ children: e.name
94
+ }, e.id))] })]
95
+ })]
96
+ })]
97
+ }), G ? /* @__PURE__ */ h(o, {
98
+ variant: "rows",
99
+ rows: 6,
100
+ label: "Loading runs…"
101
+ }) : K ? /* @__PURE__ */ h(a, {
102
+ message: "We could not load pipeline runs. Please try again.",
103
+ onRetry: () => void q()
104
+ }) : J.length === 0 ? /* @__PURE__ */ h(i, {
105
+ icon: _,
106
+ title: I === M && R === M ? "No runs yet" : "No runs match these filters",
107
+ description: I === M && R === M ? "Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics." : "Try a different status or pipeline filter."
108
+ }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
109
+ className: "overflow-x-auto rounded-card border border-border-default bg-bg-elevated",
110
+ children: /* @__PURE__ */ g(T, { children: [/* @__PURE__ */ h(k, { children: /* @__PURE__ */ g(A, { children: [
111
+ /* @__PURE__ */ h(O, { children: "Run" }),
112
+ /* @__PURE__ */ h(O, { children: "Pipeline" }),
113
+ /* @__PURE__ */ h(O, { children: "Status" }),
114
+ /* @__PURE__ */ h(O, { children: "Started" }),
115
+ /* @__PURE__ */ h(O, { children: "Finished" }),
116
+ /* @__PURE__ */ h(O, { children: "Duration" }),
117
+ /* @__PURE__ */ h(O, {
118
+ className: "w-12 text-right",
119
+ children: /* @__PURE__ */ h("span", {
120
+ className: "sr-only",
121
+ children: "Actions"
122
+ })
123
+ })
124
+ ] }) }), /* @__PURE__ */ h(E, { children: J.map((e) => /* @__PURE__ */ g(A, {
125
+ className: "cursor-pointer",
126
+ onClick: () => F(`runs/${e.id}`),
127
+ children: [
128
+ /* @__PURE__ */ g(D, {
129
+ className: "font-medium text-text-primary",
130
+ children: ["#", e.runNumber]
131
+ }),
132
+ /* @__PURE__ */ h(D, {
133
+ className: "text-text-secondary",
134
+ children: /* @__PURE__ */ h("span", {
135
+ className: "truncate",
136
+ children: U.get(e.pipelineId) ?? /* @__PURE__ */ h("span", {
137
+ className: "font-mono text-xs",
138
+ children: e.pipelineId
139
+ })
140
+ })
141
+ }),
142
+ /* @__PURE__ */ h(D, { children: /* @__PURE__ */ h(u, { status: e.status }) }),
143
+ /* @__PURE__ */ h(D, {
144
+ className: "whitespace-nowrap text-text-secondary",
145
+ children: N(e.startedAt)
146
+ }),
147
+ /* @__PURE__ */ h(D, {
148
+ className: "whitespace-nowrap text-text-secondary",
149
+ children: N(e.finishedAt)
150
+ }),
151
+ /* @__PURE__ */ h(D, {
152
+ className: "tabular-nums text-text-secondary",
153
+ children: P(e.durationMs)
154
+ }),
155
+ /* @__PURE__ */ h(D, {
156
+ className: "text-right",
157
+ children: /* @__PURE__ */ h(r, {
158
+ ariaLabel: `Actions for run #${e.runNumber}`,
159
+ onView: () => F(`runs/${e.id}`)
160
+ })
161
+ })
162
+ ]
163
+ }, e.id)) })] })
164
+ }), /* @__PURE__ */ h(c, {
165
+ offset: B.offset,
166
+ limit: B.limit,
167
+ loadedCount: J.length,
168
+ hasMore: Y?.hasMore ?? !1,
169
+ onPrev: B.prevPage,
170
+ onNext: B.nextPage,
171
+ itemLabel: "runs",
172
+ loading: G
173
+ })] })]
174
+ })
175
+ });
176
+ }
177
+ //#endregion
178
+ export { F as default };
179
+
180
+ //# sourceMappingURL=RunsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RunsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/runs/RunsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, Eye } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useCosPipelinesQuery, useCosRunsQuery, type CosRunStatus } from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\nconst STATUS_OPTIONS: CosRunStatus[] = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'];\nconst ALL = 'ALL';\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDuration(ms?: number | null): string {\n if (ms === null || ms === undefined) {\n return '—';\n }\n if (ms < 1000) {\n return `${ms} ms`;\n }\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)} s`;\n }\n const minutes = Math.floor(seconds / 60);\n const rest = Math.round(seconds % 60);\n return `${minutes}m ${rest}s`;\n}\n\nexport default function RunsPage() {\n const { navigate } = useCosmicIntersection();\n const [status, setStatus] = useState<CosRunStatus | typeof ALL>(ALL);\n const [pipelineId, setPipelineId] = useState<string | typeof ALL>(ALL);\n const pagination = usePagination({ resetKey: `${status}:${pipelineId}` });\n\n // Load pipelines to populate the filter and to resolve run -> pipeline names.\n const { data: pipelinesData } = useCosPipelinesQuery({ pagination: { limit: 100, offset: 0 } });\n const pipelines = useMemo(() => pipelinesData?.cosListPipelines.items ?? [], [pipelinesData]);\n const pipelineNameById = useMemo(() => {\n const map = new Map<string, string>();\n pipelines.forEach((pipeline) => map.set(pipeline.id, pipeline.name));\n return map;\n }, [pipelines]);\n\n const { data, loading, error, refetch } = useCosRunsQuery({\n status: status === ALL ? undefined : status,\n pipelineId: pipelineId === ALL ? undefined : pipelineId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const runs = useMemo(() => data?.cosListRuns.items ?? [], [data]);\n const pageInfo = data?.cosListRuns.pageInfo;\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading runs…\" />;\n }\n if (error) {\n return (\n <ErrorState\n message=\"We could not load pipeline runs. Please try again.\"\n onRetry={() => void refetch()}\n />\n );\n }\n if (runs.length === 0) {\n return (\n <EmptyState\n icon={Activity}\n title={\n status === ALL && pipelineId === ALL ? 'No runs yet' : 'No runs match these filters'\n }\n description={\n status === ALL && pipelineId === ALL\n ? 'Runs appear here once you start a pipeline. Each run tracks its steps, timings and metrics.'\n : 'Try a different status or pipeline filter.'\n }\n />\n );\n }\n\n return (\n <>\n <div className=\"overflow-x-auto rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Run</TableHead>\n <TableHead>Pipeline</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Finished</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead className=\"w-12 text-right\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {runs.map((run) => (\n <TableRow\n key={run.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`runs/${run.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">#{run.runNumber}</TableCell>\n <TableCell className=\"text-text-secondary\">\n <span className=\"truncate\">\n {pipelineNameById.get(run.pipelineId) ?? (\n <span className=\"font-mono text-xs\">{run.pipelineId}</span>\n )}\n </span>\n </TableCell>\n <TableCell>\n <StatusBadge status={run.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.startedAt)}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDate(run.finishedAt)}\n </TableCell>\n <TableCell className=\"tabular-nums text-text-secondary\">\n {formatDuration(run.durationMs)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for run #${run.runNumber}`}\n onView={() => navigate(`runs/${run.id}`)}\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={runs.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"runs\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Runs\"\n description=\"Every pipeline execution, with status, timings and outcomes.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('pipelines')}>\n <Eye className=\"size-4\" />\n View pipelines\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Status</span>\n <Select\n value={status}\n onValueChange={(value) => setStatus(value as CosRunStatus | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option} value={option}>\n {formatEnumLabel(option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Pipeline</span>\n <Select\n value={pipelineId}\n onValueChange={(value) => setPipelineId(value as string | typeof ALL)}\n >\n <SelectTrigger aria-label=\"Filter by pipeline\" className=\"w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All pipelines</SelectItem>\n {pipelines.map((pipeline) => (\n <SelectItem key={pipeline.id} value={pipeline.id}>\n {pipeline.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}