@burdenoff/fluidgrids-fe-sdk 2026.922.13 → 2026.923.2

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 (61) hide show
  1. package/dist/ExecutionDetailPage-BD7CS4KL.js +12 -0
  2. package/dist/{ExecutionDetailPage-OSDL7NDT.js.map → ExecutionDetailPage-BD7CS4KL.js.map} +1 -1
  3. package/dist/ExecutionsListPage-VOGSLA7T.js +11 -0
  4. package/dist/{ExecutionsListPage-AATDFFIH.js.map → ExecutionsListPage-VOGSLA7T.js.map} +1 -1
  5. package/dist/{WorkflowBuilder-D7RKU4UK.js → WorkflowBuilder-WLJBVVHS.js} +8 -8
  6. package/dist/{WorkflowBuilder-D7RKU4UK.js.map → WorkflowBuilder-WLJBVVHS.js.map} +1 -1
  7. package/dist/{WorkflowsList-JHM7Q5HE.js → WorkflowsList-QREUMT55.js} +6 -6
  8. package/dist/{WorkflowsList-JHM7Q5HE.js.map → WorkflowsList-QREUMT55.js.map} +1 -1
  9. package/dist/{chunk-5IAMVAGX.js → chunk-4C6UYNC5.js} +43 -5
  10. package/dist/chunk-4C6UYNC5.js.map +1 -0
  11. package/dist/chunk-5A7GP6O7.js +729 -0
  12. package/dist/chunk-5A7GP6O7.js.map +1 -0
  13. package/dist/{chunk-MTWEYC2W.js → chunk-5PZDLMC4.js} +4 -4
  14. package/dist/{chunk-MTWEYC2W.js.map → chunk-5PZDLMC4.js.map} +1 -1
  15. package/dist/chunk-FKOUPL6Z.js +753 -0
  16. package/dist/chunk-FKOUPL6Z.js.map +1 -0
  17. package/dist/{chunk-EUZZLC3N.js → chunk-HNVWIVJH.js} +202 -6
  18. package/dist/chunk-HNVWIVJH.js.map +1 -0
  19. package/dist/{chunk-PTGKDQ5K.js → chunk-LH2YML5F.js} +8 -2
  20. package/dist/chunk-LH2YML5F.js.map +1 -0
  21. package/dist/chunk-NK32XOQN.js +248 -0
  22. package/dist/chunk-NK32XOQN.js.map +1 -0
  23. package/dist/{chunk-CVVNQCFM.js → chunk-Q4RPP67K.js} +4 -4
  24. package/dist/{chunk-CVVNQCFM.js.map → chunk-Q4RPP67K.js.map} +1 -1
  25. package/dist/{chunk-UNW4I442.js → chunk-Q7RJYGQK.js} +3 -3
  26. package/dist/{chunk-UNW4I442.js.map → chunk-Q7RJYGQK.js.map} +1 -1
  27. package/dist/{chunk-RJQJAN6J.js → chunk-TEZV7I6A.js} +4 -4
  28. package/dist/{chunk-RJQJAN6J.js.map → chunk-TEZV7I6A.js.map} +1 -1
  29. package/dist/{chunk-XQYAM47Y.js → chunk-TOHTP3BT.js} +4 -4
  30. package/dist/{chunk-XQYAM47Y.js.map → chunk-TOHTP3BT.js.map} +1 -1
  31. package/dist/{chunk-IPVHC2DM.js → chunk-TVLE7GNH.js} +374 -7
  32. package/dist/chunk-TVLE7GNH.js.map +1 -0
  33. package/dist/{chunk-ISS73LG3.js → chunk-VPWQN7KV.js} +3 -3
  34. package/dist/{chunk-ISS73LG3.js.map → chunk-VPWQN7KV.js.map} +1 -1
  35. package/dist/{chunk-R6JFXZXN.js → chunk-VTPBYPDV.js} +47 -5
  36. package/dist/chunk-VTPBYPDV.js.map +1 -0
  37. package/dist/{chunk-GWRX2T3K.js → chunk-XF7QA3QT.js} +417 -155
  38. package/dist/chunk-XF7QA3QT.js.map +1 -0
  39. package/dist/executions.js +7 -6
  40. package/dist/history.js +4 -4
  41. package/dist/index.d.ts +71 -1
  42. package/dist/index.js +26 -23
  43. package/dist/index.js.map +1 -1
  44. package/dist/nodes.js +2 -2
  45. package/dist/observability-5ShR4Phm.d.ts +134 -0
  46. package/dist/observability.d.ts +2 -0
  47. package/dist/observability.js +8 -0
  48. package/dist/observability.js.map +1 -0
  49. package/dist/settings.js +5 -1
  50. package/dist/templates.js +6 -6
  51. package/dist/workflow-builder.js +7 -7
  52. package/dist/workflows-list.js +4 -4
  53. package/package.json +5 -1
  54. package/dist/ExecutionDetailPage-OSDL7NDT.js +0 -11
  55. package/dist/ExecutionsListPage-AATDFFIH.js +0 -10
  56. package/dist/chunk-5IAMVAGX.js.map +0 -1
  57. package/dist/chunk-EUZZLC3N.js.map +0 -1
  58. package/dist/chunk-GWRX2T3K.js.map +0 -1
  59. package/dist/chunk-IPVHC2DM.js.map +0 -1
  60. package/dist/chunk-PTGKDQ5K.js.map +0 -1
  61. package/dist/chunk-R6JFXZXN.js.map +0 -1
@@ -0,0 +1,753 @@
1
+ import { useDataSinks, useDataSinkData, useInsightsData, useRunReplayActions } from './chunk-5A7GP6O7.js';
2
+ import { useTr } from './chunk-7YWBH2A5.js';
3
+ import { useState, useMemo, useEffect } from 'react';
4
+ import { Icon } from '@iconify/react';
5
+ import { Button } from '@burdenoff/fe-libs/button';
6
+ import { Input } from '@burdenoff/fe-libs/ui';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
+ import { useNavigate } from 'react-router';
9
+ import { toast } from 'sonner';
10
+
11
+ // src/modules/observability/utils.ts
12
+ function percentage(value, total) {
13
+ if (total <= 0) return 0;
14
+ return Math.round(value / total * 1e3) / 10;
15
+ }
16
+ function getFailureBreakdown(runs) {
17
+ const counts = /* @__PURE__ */ new Map();
18
+ for (const run of runs) {
19
+ const key = run.errorType || "UNKNOWN";
20
+ counts.set(key, (counts.get(key) ?? 0) + 1);
21
+ }
22
+ return [...counts.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
23
+ }
24
+ function getTopFailingWorkflows(runs) {
25
+ const counts = /* @__PURE__ */ new Map();
26
+ for (const run of runs) {
27
+ const current = counts.get(run.workflowKey);
28
+ if (current) {
29
+ current.count += 1;
30
+ } else {
31
+ counts.set(run.workflowKey, {
32
+ workflowKey: run.workflowKey,
33
+ label: run.workflow?.name || run.workflowKey,
34
+ count: 1
35
+ });
36
+ }
37
+ }
38
+ return [...counts.values()].sort(
39
+ (a, b) => b.count - a.count || a.label.localeCompare(b.label)
40
+ );
41
+ }
42
+ function getTopFailingNodes(nodeUsageStats) {
43
+ return [...nodeUsageStats].filter((stat) => stat.failureCount > 0).sort(
44
+ (a, b) => b.failureCount - a.failureCount || a.nodeType.localeCompare(b.nodeType)
45
+ );
46
+ }
47
+ function formatDuration(seconds) {
48
+ if (seconds === null || seconds === void 0 || !Number.isFinite(seconds)) {
49
+ return "\u2014";
50
+ }
51
+ if (seconds < 1) return `${Math.round(seconds * 1e3)}ms`;
52
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
53
+ const minutes = Math.floor(seconds / 60);
54
+ const remainingSeconds = Math.round(seconds % 60);
55
+ return `${minutes}m ${remainingSeconds}s`;
56
+ }
57
+ function formatBytes(bytes) {
58
+ if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
59
+ if (bytes < 1024) return `${bytes} B`;
60
+ const units = ["KB", "MB", "GB", "TB"];
61
+ let value = bytes / 1024;
62
+ let unit = units[0];
63
+ for (let i = 1; i < units.length && value >= 1024; i += 1) {
64
+ value /= 1024;
65
+ unit = units[i];
66
+ }
67
+ return `${value.toFixed(value >= 10 ? 0 : 1)} ${unit}`;
68
+ }
69
+ function prettyJson(value) {
70
+ try {
71
+ return JSON.stringify(value, null, 2) ?? "null";
72
+ } catch {
73
+ return String(value);
74
+ }
75
+ }
76
+ function valueLabel(value) {
77
+ if (value === null || value === void 0) return "\u2014";
78
+ if (typeof value === "object") return JSON.stringify(value);
79
+ return String(value);
80
+ }
81
+ function DataPreview({ value }) {
82
+ const tr = useTr();
83
+ if (Array.isArray(value)) {
84
+ const rows = value.filter(
85
+ (item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)
86
+ );
87
+ if (rows.length > 0) {
88
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].slice(
89
+ 0,
90
+ 12
91
+ );
92
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-x-auto rounded-lg border border-border-secondary", children: [
93
+ /* @__PURE__ */ jsxs("table", { className: "min-w-full text-left text-sm", children: [
94
+ /* @__PURE__ */ jsx("thead", { className: "bg-background-tertiary text-xs uppercase text-text-secondary", children: /* @__PURE__ */ jsx("tr", { children: keys.map((key) => /* @__PURE__ */ jsx(
95
+ "th",
96
+ {
97
+ className: "whitespace-nowrap px-3 py-2 font-medium",
98
+ children: key
99
+ },
100
+ key
101
+ )) }) }),
102
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border-secondary", children: rows.slice(0, 50).map((row, index) => /* @__PURE__ */ jsx("tr", { className: "text-text-primary", children: keys.map((key) => /* @__PURE__ */ jsx("td", { className: "max-w-xs truncate px-3 py-2", children: valueLabel(row[key]) }, key)) }, index)) })
103
+ ] }),
104
+ rows.length > 50 && /* @__PURE__ */ jsx("p", { className: "border-t border-border-secondary px-3 py-2 text-xs text-text-secondary", children: tr(
105
+ "fluidgrids.dataSinks.previewLimit",
106
+ "Showing the first 50 rows"
107
+ ) })
108
+ ] });
109
+ }
110
+ }
111
+ return /* @__PURE__ */ jsx("pre", { className: "max-h-[28rem] overflow-auto rounded-lg border border-border-secondary bg-background-primary p-4 text-xs text-text-primary", children: prettyJson(value) });
112
+ }
113
+ function SinkCard({
114
+ sink,
115
+ selected,
116
+ onSelect
117
+ }) {
118
+ const tr = useTr();
119
+ return /* @__PURE__ */ jsxs(
120
+ "button",
121
+ {
122
+ type: "button",
123
+ onClick: onSelect,
124
+ className: `w-full rounded-lg border p-4 text-left transition-colors ${selected ? "border-action-primary-bg bg-action-primary-bg/10" : "border-border-secondary bg-background-secondary hover:border-action-primary-bg/60"}`,
125
+ "aria-pressed": selected,
126
+ children: [
127
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
128
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
129
+ /* @__PURE__ */ jsx("p", { className: "truncate font-medium text-text-primary", children: sink.name || sink.key }),
130
+ /* @__PURE__ */ jsx("p", { className: "mt-1 truncate font-mono text-xs text-text-secondary", children: sink.key })
131
+ ] }),
132
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-background-tertiary px-2 py-0.5 text-xs text-text-secondary", children: sink.status })
133
+ ] }),
134
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between text-xs text-text-secondary", children: [
135
+ /* @__PURE__ */ jsx("span", { children: formatBytes(sink.sizeBytes) }),
136
+ /* @__PURE__ */ jsxs("span", { children: [
137
+ sink.linkedWorkflowCount,
138
+ " ",
139
+ tr("fluidgrids.dataSinks.workflows", "workflows")
140
+ ] })
141
+ ] })
142
+ ]
143
+ }
144
+ );
145
+ }
146
+ function DataSinkDashboardPage() {
147
+ const tr = useTr();
148
+ const [search, setSearch] = useState("");
149
+ const [selectedId, setSelectedId] = useState(null);
150
+ const { sinks, totalCount, loading, error, refetch } = useDataSinks(search);
151
+ const selectedSink = useMemo(
152
+ () => sinks.find((sink) => sink.id === selectedId) ?? sinks[0] ?? null,
153
+ [selectedId, sinks]
154
+ );
155
+ const {
156
+ sinkData,
157
+ loading: dataLoading,
158
+ error: dataError,
159
+ refetch: refetchData
160
+ } = useDataSinkData(selectedSink);
161
+ useEffect(() => {
162
+ if (selectedSink && selectedSink.id !== selectedId)
163
+ setSelectedId(selectedSink.id);
164
+ }, [selectedId, selectedSink]);
165
+ const payload = sinkData?.extracted ?? sinkData?.raw;
166
+ const totalBytes = sinks.reduce((total, sink) => total + sink.sizeBytes, 0);
167
+ return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-background-primary p-4 sm:p-6", children: [
168
+ /* @__PURE__ */ jsxs("header", { className: "mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between", children: [
169
+ /* @__PURE__ */ jsxs("div", { children: [
170
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-text-secondary", children: tr("fluidgrids.dataSinks.kicker", "BigConsole data") }),
171
+ /* @__PURE__ */ jsx("h1", { className: "mt-1 text-2xl font-semibold text-text-primary sm:text-3xl", children: tr("fluidgrids.dataSinks.title", "DataSink dashboard") }),
172
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-text-secondary", children: tr(
173
+ "fluidgrids.dataSinks.subtitle",
174
+ "Inspect workflow-produced data and its freshness without leaving FluidGrids."
175
+ ) })
176
+ ] }),
177
+ /* @__PURE__ */ jsxs(
178
+ Button,
179
+ {
180
+ variant: "outline",
181
+ onClick: () => void refetch(),
182
+ disabled: loading,
183
+ children: [
184
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", className: "mr-1", width: 16 }),
185
+ tr("fluidgrids.dataSinks.refresh", "Refresh")
186
+ ]
187
+ }
188
+ )
189
+ ] }),
190
+ /* @__PURE__ */ jsxs("section", { className: "mb-6 grid gap-4 sm:grid-cols-3", children: [
191
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-4", children: [
192
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.dataSinks.totalSinks", "DataSinks") }),
193
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-2xl font-semibold text-text-primary", children: totalCount })
194
+ ] }),
195
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-4", children: [
196
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.dataSinks.activeSinks", "Active sinks") }),
197
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-2xl font-semibold text-status-success-text", children: sinks.filter((sink) => sink.isActive).length })
198
+ ] }),
199
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-4", children: [
200
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.dataSinks.totalStorage", "Visible storage") }),
201
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-2xl font-semibold text-text-primary", children: formatBytes(totalBytes) })
202
+ ] })
203
+ ] }),
204
+ error && /* @__PURE__ */ jsx(
205
+ "div",
206
+ {
207
+ role: "alert",
208
+ className: "mb-6 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text",
209
+ children: error.message
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-[22rem_minmax(0,1fr)]", children: [
213
+ /* @__PURE__ */ jsxs("aside", { className: "space-y-3", children: [
214
+ /* @__PURE__ */ jsx(
215
+ Input,
216
+ {
217
+ value: search,
218
+ onChange: (event) => setSearch(event.target.value),
219
+ placeholder: tr(
220
+ "fluidgrids.dataSinks.search",
221
+ "Search by name or key"
222
+ ),
223
+ "aria-label": tr(
224
+ "fluidgrids.dataSinks.search",
225
+ "Search by name or key"
226
+ )
227
+ }
228
+ ),
229
+ loading && sinks.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-text-secondary", children: tr("fluidgrids.dataSinks.loading", "Loading DataSinks\u2026") }),
230
+ !loading && sinks.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-border-secondary p-6 text-center text-sm text-text-secondary", children: tr("fluidgrids.dataSinks.empty", "No active DataSinks found.") }),
231
+ sinks.map((sink) => /* @__PURE__ */ jsx(
232
+ SinkCard,
233
+ {
234
+ sink,
235
+ selected: sink.id === selectedSink?.id,
236
+ onSelect: () => setSelectedId(sink.id)
237
+ },
238
+ sink.id
239
+ ))
240
+ ] }),
241
+ /* @__PURE__ */ jsxs("main", { className: "min-w-0 rounded-xl border border-border-secondary bg-background-secondary p-5", children: [
242
+ !selectedSink && /* @__PURE__ */ jsxs("div", { className: "flex min-h-64 flex-col items-center justify-center text-center", children: [
243
+ /* @__PURE__ */ jsx(
244
+ Icon,
245
+ {
246
+ icon: "mdi:database-outline",
247
+ className: "mb-3 text-text-secondary",
248
+ width: 40
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-text-primary", children: tr("fluidgrids.dataSinks.selectTitle", "Select a DataSink") }),
252
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-text-secondary", children: tr(
253
+ "fluidgrids.dataSinks.selectDescription",
254
+ "Choose a sink to inspect its latest payload."
255
+ ) })
256
+ ] }),
257
+ selectedSink && /* @__PURE__ */ jsxs(Fragment, { children: [
258
+ /* @__PURE__ */ jsxs("div", { className: "mb-5 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
259
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
260
+ /* @__PURE__ */ jsx("h2", { className: "truncate text-lg font-semibold text-text-primary", children: selectedSink.name || selectedSink.key }),
261
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 font-mono text-xs text-text-secondary", children: [
262
+ selectedSink.key,
263
+ " \xB7",
264
+ " ",
265
+ tr("fluidgrids.dataSinks.updated", "Updated"),
266
+ " ",
267
+ new Date(selectedSink.lastRefreshedAt).toLocaleString()
268
+ ] })
269
+ ] }),
270
+ /* @__PURE__ */ jsxs(
271
+ Button,
272
+ {
273
+ variant: "outline",
274
+ size: "sm",
275
+ onClick: () => void refetchData(),
276
+ disabled: dataLoading,
277
+ children: [
278
+ /* @__PURE__ */ jsx(
279
+ Icon,
280
+ {
281
+ icon: "mdi:database-refresh-outline",
282
+ className: "mr-1",
283
+ width: 16
284
+ }
285
+ ),
286
+ tr("fluidgrids.dataSinks.refreshPayload", "Refresh payload")
287
+ ]
288
+ }
289
+ )
290
+ ] }),
291
+ dataError && /* @__PURE__ */ jsx(
292
+ "div",
293
+ {
294
+ role: "alert",
295
+ className: "mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
296
+ children: dataError.message
297
+ }
298
+ ),
299
+ dataLoading && !sinkData && /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-lg bg-background-primary" }),
300
+ !dataLoading && sinkData && /* @__PURE__ */ jsxs(Fragment, { children: [
301
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap gap-3 text-xs text-text-secondary", children: [
302
+ /* @__PURE__ */ jsx("span", { children: sinkData.cached ? tr("fluidgrids.dataSinks.cached", "Cached") : tr("fluidgrids.dataSinks.fresh", "Fresh") }),
303
+ /* @__PURE__ */ jsxs("span", { children: [
304
+ "v",
305
+ sinkData.version
306
+ ] }),
307
+ sinkData.totalCount !== null && sinkData.totalCount !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
308
+ sinkData.totalCount,
309
+ " rows"
310
+ ] })
311
+ ] }),
312
+ /* @__PURE__ */ jsx(DataPreview, { value: payload })
313
+ ] })
314
+ ] })
315
+ ] })
316
+ ] })
317
+ ] });
318
+ }
319
+ function normalizedRate(summary) {
320
+ const supplied = summary.workflowSuccessRate;
321
+ if (supplied !== null && supplied !== void 0) {
322
+ return supplied <= 1 ? supplied * 100 : supplied;
323
+ }
324
+ const finished = summary.runsCompletedCount + summary.runsFailedCount;
325
+ return percentage(summary.runsCompletedCount, finished);
326
+ }
327
+ function MetricCard({
328
+ icon,
329
+ label,
330
+ value,
331
+ detail,
332
+ tone = "text-text-primary"
333
+ }) {
334
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-4", children: [
335
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-text-secondary", children: [
336
+ /* @__PURE__ */ jsx(Icon, { icon, width: 18 }),
337
+ /* @__PURE__ */ jsx("span", { children: label })
338
+ ] }),
339
+ /* @__PURE__ */ jsx("p", { className: `mt-3 text-2xl font-semibold ${tone}`, children: value }),
340
+ detail && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-text-secondary", children: detail })
341
+ ] });
342
+ }
343
+ function LoadingState() {
344
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", "aria-busy": "true", children: [1, 2, 3, 4].map((item) => /* @__PURE__ */ jsx(
345
+ "div",
346
+ {
347
+ className: "h-28 animate-pulse rounded-xl border border-border-secondary bg-background-secondary"
348
+ },
349
+ item
350
+ )) });
351
+ }
352
+ function InsightsPage({ showErrors = false }) {
353
+ const tr = useTr();
354
+ const navigate = useNavigate();
355
+ const { summary, failedRuns, loading, error, refetch } = useInsightsData();
356
+ const { retryMany, loading: retryLoading } = useRunReplayActions();
357
+ const [activeTab, setActiveTab] = useState(
358
+ showErrors ? "errors" : "overview"
359
+ );
360
+ const failureBreakdown = useMemo(
361
+ () => getFailureBreakdown(failedRuns),
362
+ [failedRuns]
363
+ );
364
+ const topWorkflows = useMemo(
365
+ () => getTopFailingWorkflows(failedRuns).slice(0, 8),
366
+ [failedRuns]
367
+ );
368
+ const topNodes = useMemo(
369
+ () => getTopFailingNodes(summary?.nodeUsageStats ?? []).slice(0, 8),
370
+ [summary?.nodeUsageStats]
371
+ );
372
+ const retry = async (run) => {
373
+ const retried = await retryMany([run.id]);
374
+ if (retried.length === 0) {
375
+ toast.error(
376
+ tr("fluidgrids.insights.retryFailed", "Could not retry the failed run")
377
+ );
378
+ return;
379
+ }
380
+ toast.success(
381
+ tr("fluidgrids.insights.retryStarted", "A new run was started")
382
+ );
383
+ navigate(`../runs/${retried[0].runId}`);
384
+ };
385
+ if (error) {
386
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-background-primary p-4 sm:p-6", children: /* @__PURE__ */ jsxs(
387
+ "div",
388
+ {
389
+ className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle p-5 text-status-error-text",
390
+ role: "alert",
391
+ children: [
392
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: tr("fluidgrids.insights.loadError", "Could not load insights") }),
393
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: error.message }),
394
+ /* @__PURE__ */ jsx(
395
+ Button,
396
+ {
397
+ variant: "outline",
398
+ size: "sm",
399
+ className: "mt-3",
400
+ onClick: () => void refetch(),
401
+ children: tr("fluidgrids.insights.retryLoad", "Try again")
402
+ }
403
+ )
404
+ ]
405
+ }
406
+ ) });
407
+ }
408
+ const successRate = summary ? normalizedRate(summary) : 0;
409
+ const failureRate = summary ? percentage(summary.runsFailedCount, summary.runsCountTotal) : 0;
410
+ const commonError = failureBreakdown[0];
411
+ return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-background-primary p-4 sm:p-6", children: [
412
+ /* @__PURE__ */ jsxs("header", { className: "mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between", children: [
413
+ /* @__PURE__ */ jsxs("div", { children: [
414
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-text-secondary", children: tr("fluidgrids.insights.period", "Last 30 days") }),
415
+ /* @__PURE__ */ jsx("h1", { className: "mt-1 text-2xl font-semibold text-text-primary sm:text-3xl", children: tr("fluidgrids.insights.title", "Workflow insights") }),
416
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-text-secondary", children: tr(
417
+ "fluidgrids.insights.subtitle",
418
+ "Understand workflow health, failures, and the nodes that need attention."
419
+ ) })
420
+ ] }),
421
+ /* @__PURE__ */ jsxs(
422
+ Button,
423
+ {
424
+ variant: "outline",
425
+ onClick: () => void refetch(),
426
+ disabled: loading,
427
+ children: [
428
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", className: "mr-1", width: 16 }),
429
+ tr("fluidgrids.insights.refresh", "Refresh")
430
+ ]
431
+ }
432
+ )
433
+ ] }),
434
+ /* @__PURE__ */ jsxs(
435
+ "div",
436
+ {
437
+ className: "mb-6 flex gap-1 border-b border-border-secondary",
438
+ role: "tablist",
439
+ children: [
440
+ /* @__PURE__ */ jsx(
441
+ "button",
442
+ {
443
+ role: "tab",
444
+ "aria-selected": activeTab === "overview",
445
+ onClick: () => {
446
+ setActiveTab("overview");
447
+ if (showErrors) navigate("../insights");
448
+ },
449
+ className: `border-b-2 px-3 py-2 text-sm ${activeTab === "overview" ? "border-action-primary-bg text-text-primary" : "border-transparent text-text-secondary"}`,
450
+ children: tr("fluidgrids.insights.overviewTab", "Overview")
451
+ }
452
+ ),
453
+ /* @__PURE__ */ jsx(
454
+ "button",
455
+ {
456
+ role: "tab",
457
+ "aria-selected": activeTab === "errors",
458
+ onClick: () => {
459
+ setActiveTab("errors");
460
+ if (!showErrors) navigate("../insights/errors");
461
+ },
462
+ className: `border-b-2 px-3 py-2 text-sm ${activeTab === "errors" ? "border-action-primary-bg text-text-primary" : "border-transparent text-text-secondary"}`,
463
+ children: tr("fluidgrids.insights.errorsTab", "Error analytics")
464
+ }
465
+ )
466
+ ]
467
+ }
468
+ ),
469
+ loading && !summary ? /* @__PURE__ */ jsx(LoadingState, {}) : activeTab === "overview" && summary ? /* @__PURE__ */ jsx(
470
+ OverviewContent,
471
+ {
472
+ summary,
473
+ successRate,
474
+ failureRate,
475
+ topWorkflows,
476
+ topNodes,
477
+ tr
478
+ }
479
+ ) : /* @__PURE__ */ jsx(
480
+ ErrorContent,
481
+ {
482
+ summary,
483
+ failedRuns,
484
+ failureBreakdown,
485
+ commonError,
486
+ retry,
487
+ retryLoading,
488
+ tr
489
+ }
490
+ )
491
+ ] });
492
+ }
493
+ function OverviewContent({
494
+ summary,
495
+ successRate,
496
+ failureRate,
497
+ topWorkflows,
498
+ topNodes,
499
+ tr
500
+ }) {
501
+ const totalDuration = summary.runsCompletedCount * (summary.avgWorkflowExecutionTimeSeconds ?? 0);
502
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
503
+ /* @__PURE__ */ jsxs(
504
+ "section",
505
+ {
506
+ className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
507
+ "aria-label": tr("fluidgrids.insights.healthLabel", "Workflow health"),
508
+ children: [
509
+ /* @__PURE__ */ jsx(
510
+ MetricCard,
511
+ {
512
+ icon: "mdi:chart-line",
513
+ label: tr("fluidgrids.insights.successRate", "Success rate"),
514
+ value: `${successRate.toFixed(1)}%`,
515
+ detail: `${summary.runsCompletedCount.toLocaleString()} completed runs`,
516
+ tone: "text-status-success-text"
517
+ }
518
+ ),
519
+ /* @__PURE__ */ jsx(
520
+ MetricCard,
521
+ {
522
+ icon: "mdi:clock-outline",
523
+ label: tr("fluidgrids.insights.avgDuration", "Average duration"),
524
+ value: formatDuration(summary.avgWorkflowExecutionTimeSeconds),
525
+ detail: `${summary.runsCountTotal.toLocaleString()} total runs`
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsx(
529
+ MetricCard,
530
+ {
531
+ icon: "mdi:alert-circle-outline",
532
+ label: tr("fluidgrids.insights.failedRuns", "Failed runs"),
533
+ value: summary.runsFailedCount.toLocaleString(),
534
+ detail: `${failureRate.toFixed(1)}% of all runs`,
535
+ tone: "text-status-error-text"
536
+ }
537
+ ),
538
+ /* @__PURE__ */ jsx(
539
+ MetricCard,
540
+ {
541
+ icon: "mdi:timer-play-outline",
542
+ label: tr(
543
+ "fluidgrids.insights.timeProcessed",
544
+ "Execution time processed"
545
+ ),
546
+ value: formatDuration(totalDuration),
547
+ detail: tr(
548
+ "fluidgrids.insights.timeProcessedDetail",
549
+ "Based on completed runs; time-saved baselines are not configured."
550
+ )
551
+ }
552
+ )
553
+ ]
554
+ }
555
+ ),
556
+ /* @__PURE__ */ jsxs("section", { className: "grid gap-6 lg:grid-cols-2", children: [
557
+ /* @__PURE__ */ jsx(
558
+ InsightList,
559
+ {
560
+ title: tr(
561
+ "fluidgrids.insights.topFailingWorkflows",
562
+ "Top failing workflows"
563
+ ),
564
+ empty: tr(
565
+ "fluidgrids.insights.noFailedWorkflows",
566
+ "No failed workflows in this period."
567
+ ),
568
+ children: topWorkflows.map((workflow) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
569
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-text-primary", children: workflow.label }),
570
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-28 overflow-hidden rounded-full bg-background-tertiary", children: /* @__PURE__ */ jsx(
571
+ "div",
572
+ {
573
+ className: "h-full rounded-full bg-status-error-bg",
574
+ style: {
575
+ width: `${Math.max(8, percentage(workflow.count, topWorkflows[0]?.count ?? 1))}%`
576
+ }
577
+ }
578
+ ) }),
579
+ /* @__PURE__ */ jsx("span", { className: "w-8 text-right text-sm font-medium text-text-secondary", children: workflow.count })
580
+ ] }, workflow.workflowKey))
581
+ }
582
+ ),
583
+ /* @__PURE__ */ jsx(
584
+ InsightList,
585
+ {
586
+ title: tr("fluidgrids.insights.topFailingNodes", "Top failing nodes"),
587
+ empty: tr(
588
+ "fluidgrids.insights.noFailedNodes",
589
+ "No node failures in this period."
590
+ ),
591
+ children: topNodes.map((node) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
592
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-text-primary", children: node.nodeType }),
593
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-28 overflow-hidden rounded-full bg-background-tertiary", children: /* @__PURE__ */ jsx(
594
+ "div",
595
+ {
596
+ className: "h-full rounded-full bg-status-warning-bg",
597
+ style: {
598
+ width: `${Math.max(8, percentage(node.failureCount, topNodes[0]?.failureCount ?? 1))}%`
599
+ }
600
+ }
601
+ ) }),
602
+ /* @__PURE__ */ jsx("span", { className: "w-8 text-right text-sm font-medium text-text-secondary", children: node.failureCount })
603
+ ] }, node.nodeType))
604
+ }
605
+ )
606
+ ] })
607
+ ] });
608
+ }
609
+ function InsightList({
610
+ title,
611
+ empty,
612
+ children
613
+ }) {
614
+ const hasChildren = Array.isArray(children) ? children.length > 0 : Boolean(children);
615
+ return /* @__PURE__ */ jsxs("section", { className: "rounded-xl border border-border-secondary bg-background-secondary p-5", children: [
616
+ /* @__PURE__ */ jsx("h2", { className: "mb-4 text-base font-semibold text-text-primary", children: title }),
617
+ hasChildren ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: empty })
618
+ ] });
619
+ }
620
+ function ErrorContent({
621
+ summary,
622
+ failedRuns,
623
+ failureBreakdown,
624
+ commonError,
625
+ retry,
626
+ retryLoading,
627
+ tr
628
+ }) {
629
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
630
+ /* @__PURE__ */ jsxs("section", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [
631
+ /* @__PURE__ */ jsx(
632
+ MetricCard,
633
+ {
634
+ icon: "mdi:alert-outline",
635
+ label: tr("fluidgrids.insights.failureRate", "Failure rate"),
636
+ value: `${summary ? percentage(summary.runsFailedCount, summary.runsCountTotal).toFixed(1) : "0.0"}%`,
637
+ tone: "text-status-error-text"
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx(
641
+ MetricCard,
642
+ {
643
+ icon: "mdi:counter",
644
+ label: tr("fluidgrids.insights.failureCount", "Failures captured"),
645
+ value: failedRuns.length.toLocaleString(),
646
+ detail: summary ? `${summary.runsFailedCount.toLocaleString()} total recorded` : void 0
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx(
650
+ MetricCard,
651
+ {
652
+ icon: "mdi:alert-decagram-outline",
653
+ label: tr("fluidgrids.insights.commonError", "Most common error"),
654
+ value: commonError?.key ?? "\u2014",
655
+ detail: commonError ? `${commonError.count} occurrences` : void 0
656
+ }
657
+ ),
658
+ /* @__PURE__ */ jsx(
659
+ MetricCard,
660
+ {
661
+ icon: "mdi:repeat",
662
+ label: tr(
663
+ "fluidgrids.insights.recoveredByRetry",
664
+ "Recovered by retry"
665
+ ),
666
+ value: "\u2014",
667
+ detail: tr(
668
+ "fluidgrids.insights.retryBaselineMissing",
669
+ "Retry recovery baseline is not exposed by the service yet."
670
+ ),
671
+ tone: "text-text-secondary"
672
+ }
673
+ )
674
+ ] }),
675
+ /* @__PURE__ */ jsxs("section", { className: "grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]", children: [
676
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-5", children: [
677
+ /* @__PURE__ */ jsx("h2", { className: "mb-4 text-base font-semibold text-text-primary", children: tr("fluidgrids.insights.errorsByType", "Errors by type") }),
678
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
679
+ failureBreakdown.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
680
+ "fluidgrids.insights.noErrors",
681
+ "No errors in this period."
682
+ ) }),
683
+ failureBreakdown.map((entry) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
684
+ /* @__PURE__ */ jsx("span", { className: "w-28 truncate text-sm text-text-secondary", children: entry.key }),
685
+ /* @__PURE__ */ jsx("div", { className: "h-2 flex-1 overflow-hidden rounded-full bg-background-tertiary", children: /* @__PURE__ */ jsx(
686
+ "div",
687
+ {
688
+ className: "h-full rounded-full bg-status-error-bg",
689
+ style: {
690
+ width: `${Math.max(8, percentage(entry.count, failureBreakdown[0]?.count ?? 1))}%`
691
+ }
692
+ }
693
+ ) }),
694
+ /* @__PURE__ */ jsx("span", { className: "w-8 text-right text-sm font-medium text-text-primary", children: entry.count })
695
+ ] }, entry.key))
696
+ ] })
697
+ ] }),
698
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border-secondary bg-background-secondary p-5", children: [
699
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between gap-3", children: [
700
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.insights.recentFailures", "Recent failures") }),
701
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: tr(
702
+ "fluidgrids.insights.sampleNote",
703
+ "Showing the latest 100 failures"
704
+ ) })
705
+ ] }),
706
+ /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border-secondary", children: [
707
+ failedRuns.length === 0 && /* @__PURE__ */ jsx("p", { className: "py-3 text-sm text-text-secondary", children: tr(
708
+ "fluidgrids.insights.noRecentFailures",
709
+ "No recent failures."
710
+ ) }),
711
+ failedRuns.slice(0, 12).map((run) => /* @__PURE__ */ jsxs(
712
+ "div",
713
+ {
714
+ className: "flex flex-col gap-2 py-3 sm:flex-row sm:items-center sm:gap-4",
715
+ children: [
716
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
717
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-text-primary", children: run.errorMessage || run.errorType || "Workflow failed" }),
718
+ /* @__PURE__ */ jsxs("p", { className: "mt-0.5 truncate text-xs text-text-secondary", children: [
719
+ run.workflow?.name || run.workflowKey,
720
+ " \xB7",
721
+ " ",
722
+ run.id.slice(0, 10),
723
+ " \xB7 ",
724
+ formatDuration(run.duration)
725
+ ] })
726
+ ] }),
727
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-status-error-bg-subtle px-2 py-1 text-xs text-status-error-text", children: run.errorType || "UNKNOWN" }),
728
+ /* @__PURE__ */ jsx(
729
+ Button,
730
+ {
731
+ variant: "outline",
732
+ size: "sm",
733
+ onClick: () => void retry(run),
734
+ disabled: retryLoading,
735
+ children: tr("fluidgrids.insights.retry", "Retry")
736
+ }
737
+ )
738
+ ]
739
+ },
740
+ run.id
741
+ ))
742
+ ] })
743
+ ] })
744
+ ] })
745
+ ] });
746
+ }
747
+ function ErrorAnalyticsPage() {
748
+ return /* @__PURE__ */ jsx(InsightsPage, { showErrors: true });
749
+ }
750
+
751
+ export { DataSinkDashboardPage, ErrorAnalyticsPage, InsightsPage };
752
+ //# sourceMappingURL=chunk-FKOUPL6Z.js.map
753
+ //# sourceMappingURL=chunk-FKOUPL6Z.js.map