@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.18

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 (119) hide show
  1. package/README.md +41 -0
  2. package/dist/AiStudioPage-BHVEU4Wp.js +774 -0
  3. package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
  4. package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
  5. package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
  6. package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
  7. package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
  8. package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
  9. package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
  10. package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
  11. package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
  12. package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
  13. package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
  14. package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
  15. package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
  16. package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
  17. package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
  18. package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
  19. package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
  20. package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
  21. package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
  22. package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
  23. package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
  24. package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
  25. package/dist/RunnersPage-CLCOCclc.js +467 -0
  26. package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CJLZvv4J.js} +743 -773
  27. package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
  28. package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
  29. package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
  30. package/dist/TemplateCard-DsC0KNH1.js +1416 -0
  31. package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
  32. package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
  33. package/dist/TemplatePreview-FayF6jxh.js +231 -0
  34. package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
  35. package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
  36. package/dist/VariablesPage-67WR_lVE.js +949 -0
  37. package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
  38. package/dist/WorkflowBuilder-CPnXEL5J.js +555 -0
  39. package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-YXRysvZc.js} +19 -585
  40. package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
  41. package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
  42. package/dist/WorkflowShareDialog-mbIwRQUY.js +168 -0
  43. package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
  44. package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-DiiGyZkT.js} +12 -6
  45. package/dist/agent-form-Bfvm32j1.js +83 -0
  46. package/dist/{agentTools-DJ5o2ekg.js → agentTools-8s_25Q_F.js} +3 -2
  47. package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
  48. package/dist/api-BrCHB5oA.js +224 -0
  49. package/dist/api-C8e0iV-Q.js +55 -0
  50. package/dist/button-DG7y_FVJ.js +48 -0
  51. package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-B6pYARgU.js} +2 -2
  52. package/dist/client-C9XSzSXr.js +1147 -0
  53. package/dist/code-editor-field-9-GdVE_8.js +111 -0
  54. package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
  55. package/dist/constants-B2lhWNoN.js +202 -0
  56. package/dist/constants-XdvUxeWK.d.ts +162 -0
  57. package/dist/dialog-Cjb4lB4D.js +105 -0
  58. package/dist/executions.d.ts +1 -1
  59. package/dist/executions.js +2 -1
  60. package/dist/feature-flags-DM5myWaD.js +296 -0
  61. package/dist/flags.d.ts +3 -0
  62. package/dist/flags.js +4 -0
  63. package/dist/{flow-builder-DEzT8TQu.js → flow-builder-CZDKRc6f.js} +662 -10716
  64. package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
  65. package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
  66. package/dist/history.d.ts +1 -1
  67. package/dist/history.js +1 -1
  68. package/dist/{index-DRf25-D3.d.ts → index-6JR2vD9J.d.ts} +33 -215
  69. package/dist/index-72XFgvbn.d.ts +132 -0
  70. package/dist/index-BcdtKBM-.d.ts +56 -0
  71. package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
  72. package/dist/index.d.ts +13 -9
  73. package/dist/index.js +52 -5300
  74. package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-CV4ZRwqh.js} +13 -8
  75. package/dist/node-editor-tab-body-DU-dh35X.js +174 -0
  76. package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
  77. package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
  78. package/dist/nodes.d.ts +1 -1
  79. package/dist/nodes.js +1 -1
  80. package/dist/observability.d.ts +1 -1
  81. package/dist/observability.js +2 -1
  82. package/dist/popover-BJHvrFGR.js +472 -0
  83. package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-ws3pLvbt.js} +3 -2
  84. package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
  85. package/dist/run-display-BycKYSU0.js +46 -0
  86. package/dist/run-list-links-ByYC0OlV.js +98 -0
  87. package/dist/schedule-panel-C0mfGQSD.js +7542 -0
  88. package/dist/settings.d.ts +2 -1
  89. package/dist/settings.js +2 -1
  90. package/dist/shared-CMfn30xT.js +196 -0
  91. package/dist/sidebar-node-editor-CR1PiZO-.js +177 -0
  92. package/dist/templates.d.ts +1 -1
  93. package/dist/templates.js +2 -1
  94. package/dist/theme-provider-B3mIuz2d.js +11 -0
  95. package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-B0dam3_o.js} +3 -397
  96. package/dist/useConnections-Cs7LCWEM.js +401 -0
  97. package/dist/useDurableParity-CzUQMFJ3.js +415 -0
  98. package/dist/useEntityTags-DhnsZcTl.js +236 -0
  99. package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
  100. package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
  101. package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
  102. package/dist/{WorkflowShareDialog-6RRDe-_v.js → useWorkflowCodeExport-Bu2QYJzA.js} +7 -210
  103. package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
  104. package/dist/utils-uaL5UVxb.js +65 -0
  105. package/dist/workflow-builder.d.ts +1 -1
  106. package/dist/workflow-builder.js +4 -2
  107. package/dist/workflow-host.d.ts +6 -0
  108. package/dist/workflow-host.js +9 -0
  109. package/dist/workflow-pages.d.ts +55 -0
  110. package/dist/workflow-pages.js +113 -0
  111. package/dist/workflows-list.d.ts +1 -1
  112. package/dist/workflows-list.js +1 -1
  113. package/package.json +13 -1
  114. package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
  115. package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
  116. package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
  117. package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
  118. package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
  119. package/dist/agent-form-BvMWhQ42.js +0 -496
@@ -0,0 +1,2742 @@
1
+ "use client";
2
+ import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
+ import { M as DEFAULT_EXECUTIONS_FILTER, N as useExecutionsStore, m as describeOperationError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
4
+ import { c as OPERATORS_BY_TYPE, i as useExecutionStats, l as buildSearchAttributeFilter, o as useExecutionsWithStore, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, y as validateDateRange } from "./useExecutions-9aLuOt1c.js";
5
+ import { c as useDurableParityEnabled, f as usePhaseCFlags } from "./feature-flags-DM5myWaD.js";
6
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
7
+ import { n as useFluidGridsPageControls } from "./pageControlsBridge-0pX9s-3k.js";
8
+ import { t as executionsControls } from "./pageControlSpecs-DZJ9K37m.js";
9
+ import { t as TagFilterSelect } from "./TagFilterSelect-DCBOH6Ou.js";
10
+ import { a as useRunStatusCounts, c as runStatusLabels, l as RUN_STATUS_CONFIG, o as ExecutionStatusBadge } from "./useDurableParity-CzUQMFJ3.js";
11
+ import { n as formatRunVersion } from "./run-display-BycKYSU0.js";
12
+ import { in as formatElapsedMs, on as runDurationMs } from "./FluidGridsProvider-dvZSNtSW.js";
13
+ import { n as runListLinkKey, t as runListFilterFromSearch } from "./run-list-links-ByYC0OlV.js";
14
+ import { h as useWorkflowDefinitions } from "./useWorkflows-BlVVu8NM.js";
15
+ import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
16
+ import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
17
+ import { C as jsonArg, F as startBatchOperation, I as startBatchTerminate, P as startBatchExport, m as fetchBatchOperations, n as cancelBatchOperation, p as fetchBatchOperation, x as fetchSearchAttributes } from "./api-BrCHB5oA.js";
18
+ import { a as RUN_CONTROL_DIALOG_STYLE, c as parseJsonInput, i as NativeSelect, o as StatusPill, r as JsonField, t as FieldError, u as useResource } from "./shared-CMfn30xT.js";
19
+ import { xr as WorkflowBatchOperationUpdatedDocument } from "./graphql-operation-types-kX-MMjgW.js";
20
+ import { RF as WorkflowResetType, bF as WorkflowBatchOperationType } from "./graphql-types-CDKVWH2R.js";
21
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
+ import { useNavigate, useSearchParams } from "react-router";
23
+ import { Icon } from "@iconify/react";
24
+ import { cn } from "@burdenoff/fe-libs/utils";
25
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
+ import { toast } from "sonner";
27
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Label, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@burdenoff/fe-libs/ui";
28
+ import { Button as Button$1 } from "@burdenoff/fe-libs/button";
29
+
30
+ // src/modules/executions/utils/active-filters.ts
31
+ function countActiveFilters(filter, parityEnabled) {
32
+ return [
33
+ filter.status !== "all",
34
+ filter.workflowKey !== "",
35
+ filter.dateRange.preset !== "all",
36
+ filter.search !== "",
37
+ filter.triggerType !== "all",
38
+ filter.durationMinSeconds !== null,
39
+ filter.durationMaxSeconds !== null,
40
+ filter.errorType !== "all",
41
+ filter.nodeId !== "",
42
+ filter.failedNodeType !== "",
43
+ filter.tagId !== "",
44
+ parityEnabled && filter.workflowId.trim() !== ""
45
+ ].filter(Boolean).length;
46
+ }
47
+
48
+ // src/modules/executions/components/ExecutionFilters.tsx
49
+ /**
50
+ * An error type set by a link (Insights, BOFF-9974) that the dropdown offers
51
+ * no option for, so it still shows as selected.
52
+ */
53
+ function LinkedErrorTypeOption({ errorType, offered }) {
54
+ if (errorType === "all" || offered.some(({ value }) => value === errorType)) return null;
55
+ return /* @__PURE__ */ jsx("option", {
56
+ value: errorType,
57
+ children: errorType
58
+ });
59
+ }
60
+ /** The failed-node-type filter (BOFF-9974), set only by links, with its clear button. */
61
+ function FailedNodeTypeChip({ nodeType, onClear }) {
62
+ const tr = useTr();
63
+ if (!nodeType) return null;
64
+ return /* @__PURE__ */ jsxs("span", {
65
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary",
66
+ "data-testid": "runs-failed-node-type-filter",
67
+ children: [tr("fluidgrids.executions.executionFilters.failedNodeType", "Failed node: {{nodeType}}", { nodeType }), /* @__PURE__ */ jsx("button", {
68
+ type: "button",
69
+ onClick: onClear,
70
+ "aria-label": tr("fluidgrids.executions.executionFilters.clearFailedNodeType", "Remove the failed node filter"),
71
+ className: "text-text-secondary hover:text-text-primary",
72
+ children: /* @__PURE__ */ jsx(Icon, {
73
+ icon: "mdi:close",
74
+ width: 14
75
+ })
76
+ })]
77
+ });
78
+ }
79
+ function ExecutionFilters({ workflows = [], onRefresh }) {
80
+ const tr = useTr();
81
+ const statusOptions = useMemo(() => [
82
+ {
83
+ value: "all",
84
+ label: tr("fluidgrids.executions.executionFilters.statusAll", "All Statuses")
85
+ },
86
+ {
87
+ value: "RUNNING",
88
+ label: tr("fluidgrids.executions.executionFilters.statusRunning", "Running")
89
+ },
90
+ {
91
+ value: "COMPLETED",
92
+ label: tr("fluidgrids.executions.executionFilters.statusCompleted", "Completed")
93
+ },
94
+ {
95
+ value: "FAILED",
96
+ label: tr("fluidgrids.executions.executionFilters.statusFailed", "Failed")
97
+ },
98
+ {
99
+ value: "PENDING",
100
+ label: tr("fluidgrids.executions.executionFilters.statusPending", "Pending")
101
+ },
102
+ {
103
+ value: "CANCELLED",
104
+ label: tr("fluidgrids.executions.executionFilters.statusCancelled", "Cancelled")
105
+ },
106
+ {
107
+ value: "WAITING",
108
+ label: tr("fluidgrids.executions.executionFilters.statusWaiting", "Waiting")
109
+ },
110
+ {
111
+ value: "TERMINATED",
112
+ label: tr("fluidgrids.executions.executionFilters.statusTerminated", "Terminated")
113
+ }
114
+ ], [tr]);
115
+ const errorOptions = [
116
+ {
117
+ value: "TIMEOUT",
118
+ label: tr("fluidgrids.executions.executionFilters.errorTimeout", "TIMEOUT")
119
+ },
120
+ {
121
+ value: "TRANSIENT",
122
+ label: tr("fluidgrids.executions.executionFilters.errorTransient", "TRANSIENT")
123
+ },
124
+ {
125
+ value: "DEPENDENCY",
126
+ label: tr("fluidgrids.executions.executionFilters.errorDependency", "DEPENDENCY")
127
+ },
128
+ {
129
+ value: "AUTHORIZATION",
130
+ label: tr("fluidgrids.executions.executionFilters.errorAuthorization", "AUTHORIZATION")
131
+ },
132
+ {
133
+ value: "VALIDATION",
134
+ label: tr("fluidgrids.executions.executionFilters.errorValidation", "VALIDATION")
135
+ },
136
+ {
137
+ value: "PERMANENT",
138
+ label: tr("fluidgrids.executions.executionFilters.errorPermanent", "PERMANENT")
139
+ },
140
+ {
141
+ value: "UNKNOWN",
142
+ label: tr("fluidgrids.executions.executionFilters.errorUnknown", "UNKNOWN")
143
+ }
144
+ ];
145
+ const datePresets = [
146
+ {
147
+ value: "all",
148
+ label: tr("fluidgrids.executions.executionFilters.dateAllTime", "All Time")
149
+ },
150
+ {
151
+ value: "today",
152
+ label: tr("fluidgrids.executions.executionFilters.dateToday", "Today")
153
+ },
154
+ {
155
+ value: "yesterday",
156
+ label: tr("fluidgrids.executions.executionFilters.dateYesterday", "Yesterday")
157
+ },
158
+ {
159
+ value: "7days",
160
+ label: tr("fluidgrids.executions.executionFilters.dateLast7Days", "Last 7 Days")
161
+ },
162
+ {
163
+ value: "30days",
164
+ label: tr("fluidgrids.executions.executionFilters.dateLast30Days", "Last 30 Days")
165
+ },
166
+ {
167
+ value: "90days",
168
+ label: tr("fluidgrids.executions.executionFilters.dateLast90Days", "Last 90 Days")
169
+ },
170
+ {
171
+ value: "custom",
172
+ label: tr("fluidgrids.executions.executionFilters.dateCustomRange", "Custom Range")
173
+ }
174
+ ];
175
+ const { filter, setStatusFilter, setWorkflowFilter, setDateRange, setSearchQuery, setTriggerTypeFilter, setDurationFilter, setErrorTypeFilter, setNodeFilter, setFailedNodeTypeFilter, setTagFilter, resetFilters, autoRefresh, setAutoRefresh } = useExecutionsStore();
176
+ const [showCustomDate, setShowCustomDate] = useState(() => filter.dateRange.preset === "custom");
177
+ const handleDatePresetChange = useCallback((preset) => {
178
+ const now = /* @__PURE__ */ new Date();
179
+ let start = null;
180
+ let end = null;
181
+ switch (preset) {
182
+ case "today":
183
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
184
+ end = now;
185
+ break;
186
+ case "yesterday":
187
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
188
+ end = new Date(now.getFullYear(), now.getMonth(), now.getDate());
189
+ break;
190
+ case "7days":
191
+ start = /* @__PURE__ */ new Date(now.getTime() - 6048e5);
192
+ end = now;
193
+ break;
194
+ case "30days":
195
+ start = /* @__PURE__ */ new Date(now.getTime() - 2592e6);
196
+ end = now;
197
+ break;
198
+ case "90days":
199
+ start = /* @__PURE__ */ new Date(now.getTime() - 7776e6);
200
+ end = now;
201
+ break;
202
+ case "custom":
203
+ setShowCustomDate(true);
204
+ return;
205
+ default:
206
+ start = null;
207
+ end = null;
208
+ }
209
+ setShowCustomDate(false);
210
+ setDateRange({
211
+ start,
212
+ end,
213
+ preset
214
+ });
215
+ }, [setDateRange]);
216
+ const parityEnabled = useDurableParityEnabled();
217
+ const activeFiltersCount = countActiveFilters(filter, parityEnabled);
218
+ const assistantControls = useMemo(() => executionsControls({
219
+ page: tr("fluidgrids.executions.executionsList.title", "Executions"),
220
+ search: setSearchQuery,
221
+ status: {
222
+ label: tr("fluidgrids.executions.executionTable.colStatus", "Status"),
223
+ options: statusOptions,
224
+ neutral: "all",
225
+ value: filter.status,
226
+ set: setStatusFilter
227
+ },
228
+ workflow: {
229
+ label: tr("fluidgrids.executions.executionTable.colWorkflow", "Workflow"),
230
+ allLabel: tr("fluidgrids.executions.executionFilters.allWorkflows", "All Workflows"),
231
+ options: workflows.map((wf) => ({
232
+ value: wf.workflowKey,
233
+ label: wf.name
234
+ })),
235
+ value: filter.workflowKey,
236
+ set: setWorkflowFilter
237
+ }
238
+ }), [
239
+ tr,
240
+ statusOptions,
241
+ workflows,
242
+ filter.status,
243
+ filter.workflowKey,
244
+ setSearchQuery,
245
+ setStatusFilter,
246
+ setWorkflowFilter
247
+ ]);
248
+ useFluidGridsPageControls(assistantControls);
249
+ return /* @__PURE__ */ jsxs("div", {
250
+ className: "space-y-4",
251
+ children: [
252
+ /* @__PURE__ */ jsxs("div", {
253
+ className: "flex flex-col sm:flex-row gap-3",
254
+ children: [
255
+ /* @__PURE__ */ jsxs("div", {
256
+ className: "relative flex-1",
257
+ children: [
258
+ /* @__PURE__ */ jsx(Icon, {
259
+ icon: "mdi:magnify",
260
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary",
261
+ width: 18
262
+ }),
263
+ /* @__PURE__ */ jsx("input", {
264
+ type: "text",
265
+ placeholder: tr("fluidgrids.executions.executionFilters.searchPlaceholder", "Search by workflow name or run ID..."),
266
+ value: filter.search,
267
+ onChange: (e) => setSearchQuery(e.target.value),
268
+ className: "w-full pl-[3.25rem] pr-4 py-2 bg-background-secondary border border-border-secondary rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent"
269
+ }),
270
+ filter.search && /* @__PURE__ */ jsx("button", {
271
+ onClick: () => setSearchQuery(""),
272
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary",
273
+ children: /* @__PURE__ */ jsx(Icon, {
274
+ icon: "mdi:close",
275
+ width: 18
276
+ })
277
+ })
278
+ ]
279
+ }),
280
+ /* @__PURE__ */ jsx("select", {
281
+ value: filter.status,
282
+ onChange: (e) => setStatusFilter(e.target.value),
283
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
284
+ children: statusOptions.map((option) => /* @__PURE__ */ jsx("option", {
285
+ value: option.value,
286
+ className: "bg-bg-surface text-text-primary",
287
+ children: option.label
288
+ }, option.value))
289
+ }),
290
+ workflows.length > 0 && /* @__PURE__ */ jsxs("select", {
291
+ value: filter.workflowKey,
292
+ onChange: (e) => setWorkflowFilter(e.target.value),
293
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg max-w-[200px] scheme-normal dark:scheme-dark",
294
+ children: [/* @__PURE__ */ jsx("option", {
295
+ value: "",
296
+ className: "bg-bg-surface text-text-primary",
297
+ children: tr("fluidgrids.executions.executionFilters.allWorkflows", "All Workflows")
298
+ }), workflows.map((wf) => /* @__PURE__ */ jsx("option", {
299
+ value: wf.workflowKey,
300
+ className: "bg-bg-surface text-text-primary",
301
+ children: wf.name
302
+ }, wf.workflowKey))]
303
+ }),
304
+ /* @__PURE__ */ jsx(TagFilterSelect, {
305
+ value: filter.tagId,
306
+ onChange: setTagFilter,
307
+ className: "max-w-[200px]"
308
+ }),
309
+ /* @__PURE__ */ jsx("select", {
310
+ value: filter.dateRange.preset,
311
+ onChange: (e) => handleDatePresetChange(e.target.value),
312
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
313
+ children: datePresets.map((option) => /* @__PURE__ */ jsx("option", {
314
+ value: option.value,
315
+ className: "bg-bg-surface text-text-primary",
316
+ children: option.label
317
+ }, option.value))
318
+ })
319
+ ]
320
+ }),
321
+ /* @__PURE__ */ jsxs("div", {
322
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5",
323
+ children: [
324
+ /* @__PURE__ */ jsxs("select", {
325
+ "aria-label": tr("fluidgrids.executions.executionFilters.triggerType", "Trigger type"),
326
+ value: filter.triggerType,
327
+ onChange: (e) => setTriggerTypeFilter(e.target.value),
328
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
329
+ children: [
330
+ /* @__PURE__ */ jsx("option", {
331
+ value: "all",
332
+ children: tr("fluidgrids.executions.executionFilters.allTriggers", "All trigger types")
333
+ }),
334
+ /* @__PURE__ */ jsx("option", {
335
+ value: "MANUAL",
336
+ children: tr("fluidgrids.executions.executionFilters.manual", "Manual")
337
+ }),
338
+ /* @__PURE__ */ jsx("option", {
339
+ value: "WEBHOOK",
340
+ children: tr("fluidgrids.executions.executionFilters.webhook", "Webhook")
341
+ }),
342
+ /* @__PURE__ */ jsx("option", {
343
+ value: "SCHEDULE",
344
+ children: tr("fluidgrids.executions.executionFilters.schedule", "Schedule")
345
+ }),
346
+ /* @__PURE__ */ jsx("option", {
347
+ value: "EVENT",
348
+ children: tr("fluidgrids.executions.executionFilters.event", "Event")
349
+ })
350
+ ]
351
+ }),
352
+ /* @__PURE__ */ jsxs("select", {
353
+ "aria-label": tr("fluidgrids.executions.executionFilters.errorType", "Error type"),
354
+ value: filter.errorType,
355
+ onChange: (e) => setErrorTypeFilter(e.target.value),
356
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
357
+ children: [
358
+ /* @__PURE__ */ jsx("option", {
359
+ value: "all",
360
+ children: tr("fluidgrids.executions.executionFilters.allErrors", "All error types")
361
+ }),
362
+ errorOptions.map(({ value, label }) => /* @__PURE__ */ jsx("option", {
363
+ value,
364
+ children: label
365
+ }, value)),
366
+ /* @__PURE__ */ jsx(LinkedErrorTypeOption, {
367
+ errorType: filter.errorType,
368
+ offered: errorOptions
369
+ })
370
+ ]
371
+ }),
372
+ /* @__PURE__ */ jsx(FailedNodeTypeChip, {
373
+ nodeType: filter.failedNodeType,
374
+ onClear: () => setFailedNodeTypeFilter("")
375
+ }),
376
+ /* @__PURE__ */ jsx("input", {
377
+ type: "text",
378
+ value: filter.nodeId,
379
+ onChange: (e) => setNodeFilter(e.target.value),
380
+ placeholder: tr("fluidgrids.executions.executionFilters.nodePlaceholder", "Node ID"),
381
+ "aria-label": tr("fluidgrids.executions.executionFilters.nodeId", "Node ID"),
382
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
383
+ }),
384
+ /* @__PURE__ */ jsx("input", {
385
+ type: "number",
386
+ min: 0,
387
+ value: filter.durationMinSeconds ?? "",
388
+ onChange: (e) => setDurationFilter(e.target.value ? Number(e.target.value) : null, filter.durationMaxSeconds),
389
+ placeholder: tr("fluidgrids.executions.executionFilters.durationMin", "Min seconds"),
390
+ "aria-label": tr("fluidgrids.executions.executionFilters.durationMin", "Minimum duration in seconds"),
391
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
392
+ }),
393
+ /* @__PURE__ */ jsx("input", {
394
+ type: "number",
395
+ min: 0,
396
+ value: filter.durationMaxSeconds ?? "",
397
+ onChange: (e) => setDurationFilter(filter.durationMinSeconds, e.target.value ? Number(e.target.value) : null),
398
+ placeholder: tr("fluidgrids.executions.executionFilters.durationMax", "Max seconds"),
399
+ "aria-label": tr("fluidgrids.executions.executionFilters.durationMax", "Maximum duration in seconds"),
400
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
401
+ })
402
+ ]
403
+ }),
404
+ showCustomDate && /* @__PURE__ */ jsxs("div", {
405
+ className: "space-y-2",
406
+ children: [/* @__PURE__ */ jsxs("div", {
407
+ className: "flex flex-wrap gap-3 items-center p-3 bg-background-secondary rounded-lg border border-border-secondary",
408
+ children: [
409
+ /* @__PURE__ */ jsx("label", {
410
+ className: "text-sm text-text-secondary",
411
+ children: tr("fluidgrids.executions.executionFilters.fromLabel", "From:")
412
+ }),
413
+ /* @__PURE__ */ jsx("input", {
414
+ type: "datetime-local",
415
+ value: filter.dateRange.start?.toISOString().slice(0, 16) ?? "",
416
+ onChange: (e) => setDateRange({
417
+ ...filter.dateRange,
418
+ start: e.target.value ? new Date(e.target.value) : null,
419
+ preset: "custom"
420
+ }),
421
+ className: "px-3 py-1.5 bg-background-primary border border-border-secondary rounded text-text-primary text-sm"
422
+ }),
423
+ /* @__PURE__ */ jsx("label", {
424
+ className: "text-sm text-text-secondary",
425
+ children: tr("fluidgrids.executions.executionFilters.toLabel", "To:")
426
+ }),
427
+ /* @__PURE__ */ jsx("input", {
428
+ type: "datetime-local",
429
+ value: filter.dateRange.end?.toISOString().slice(0, 16) ?? "",
430
+ onChange: (e) => setDateRange({
431
+ ...filter.dateRange,
432
+ end: e.target.value ? new Date(e.target.value) : null,
433
+ preset: "custom"
434
+ }),
435
+ className: "px-3 py-1.5 bg-background-primary border border-border-secondary rounded text-text-primary text-sm"
436
+ }),
437
+ /* @__PURE__ */ jsx("button", {
438
+ onClick: () => setShowCustomDate(false),
439
+ className: "text-text-secondary hover:text-text-primary",
440
+ children: /* @__PURE__ */ jsx(Icon, {
441
+ icon: "mdi:close",
442
+ width: 18
443
+ })
444
+ })
445
+ ]
446
+ }), !validateDateRange(filter.dateRange.start, filter.dateRange.end).valid && /* @__PURE__ */ jsx("p", {
447
+ role: "alert",
448
+ className: "text-sm text-[var(--color-status-error-text)]",
449
+ children: tr("fluidgrids.executions.executionFilters.dateRangeInvalid", "Invalid date range: \"From\" must be before or equal to \"To\".")
450
+ })]
451
+ }),
452
+ /* @__PURE__ */ jsxs("div", {
453
+ className: "flex flex-wrap items-center justify-between gap-3",
454
+ children: [/* @__PURE__ */ jsxs("div", {
455
+ className: "flex items-center gap-3",
456
+ children: [/* @__PURE__ */ jsxs("label", {
457
+ className: "inline-flex items-center gap-2 cursor-pointer",
458
+ children: [/* @__PURE__ */ jsx("input", {
459
+ type: "checkbox",
460
+ checked: autoRefresh,
461
+ onChange: (e) => setAutoRefresh(e.target.checked),
462
+ className: "w-4 h-4 rounded border-border-secondary text-action-primary-bg focus:ring-action-primary-bg"
463
+ }), /* @__PURE__ */ jsx("span", {
464
+ className: "text-sm text-text-secondary",
465
+ children: tr("fluidgrids.executions.executionFilters.autoRefresh", "Auto-refresh")
466
+ })]
467
+ }), onRefresh && /* @__PURE__ */ jsxs(Button$1, {
468
+ variant: "outline",
469
+ size: "sm",
470
+ onClick: onRefresh,
471
+ className: "gap-1.5",
472
+ children: [/* @__PURE__ */ jsx(Icon, {
473
+ icon: "mdi:refresh",
474
+ width: 16
475
+ }), tr("fluidgrids.executions.executionFilters.refresh", "Refresh")]
476
+ })]
477
+ }), /* @__PURE__ */ jsxs("div", {
478
+ className: "flex items-center gap-3",
479
+ children: [activeFiltersCount > 0 && /* @__PURE__ */ jsxs("span", {
480
+ className: "text-sm text-text-secondary",
481
+ children: [
482
+ activeFiltersCount,
483
+ " filter",
484
+ activeFiltersCount > 1 ? "s" : "",
485
+ " ",
486
+ "active"
487
+ ]
488
+ }), activeFiltersCount > 0 && /* @__PURE__ */ jsxs(Button$1, {
489
+ variant: "ghost",
490
+ size: "sm",
491
+ onClick: resetFilters,
492
+ className: "gap-1.5",
493
+ children: [/* @__PURE__ */ jsx(Icon, {
494
+ icon: "mdi:filter-remove",
495
+ width: 16
496
+ }), tr("fluidgrids.executions.executionFilters.clearFilters", "Clear Filters")]
497
+ })]
498
+ })]
499
+ })
500
+ ]
501
+ });
502
+ }
503
+
504
+ // src/modules/executions/components/ExecutionStats.tsx
505
+ function StatCard({ label, value, icon, color, trend, onClick }) {
506
+ return /* @__PURE__ */ jsxs("button", {
507
+ onClick,
508
+ disabled: !onClick,
509
+ className: `
510
+ flex flex-col p-4 bg-background-secondary rounded-lg border border-border-secondary
511
+ hover:border-border-primary transition-colors
512
+ ${onClick ? "cursor-pointer" : "cursor-default"}
513
+ text-left w-full
514
+ `,
515
+ children: [
516
+ /* @__PURE__ */ jsxs("div", {
517
+ className: "flex items-center justify-between mb-2",
518
+ children: [/* @__PURE__ */ jsx(Icon, {
519
+ icon,
520
+ className: color,
521
+ width: 24
522
+ }), trend && /* @__PURE__ */ jsxs("span", {
523
+ className: `text-xs font-medium ${trend.isPositive ? "text-[var(--color-status-success-text)]" : "text-[var(--color-status-error-text)]"}`,
524
+ children: [
525
+ trend.isPositive ? "+" : "-",
526
+ Math.abs(trend.value),
527
+ "%"
528
+ ]
529
+ })]
530
+ }),
531
+ /* @__PURE__ */ jsx("div", {
532
+ className: "text-2xl font-bold text-text-primary",
533
+ children: value
534
+ }),
535
+ /* @__PURE__ */ jsx("div", {
536
+ className: "text-sm text-text-secondary mt-1",
537
+ children: label
538
+ })
539
+ ]
540
+ });
541
+ }
542
+ function ExecutionStats({ stats, loading, onFilterByStatus }) {
543
+ const tr = useTr();
544
+ if (loading) return /* @__PURE__ */ jsx("div", {
545
+ className: "grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-4",
546
+ children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-24 bg-background-secondary rounded-lg border border-border-secondary animate-pulse" }, i))
547
+ });
548
+ if (!stats) return null;
549
+ const formatDuration = (seconds) => {
550
+ if (seconds < 60) return `${Math.round(seconds)}s`;
551
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
552
+ return `${Math.round(seconds / 3600)}h`;
553
+ };
554
+ return /* @__PURE__ */ jsxs("div", {
555
+ className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4",
556
+ children: [
557
+ /* @__PURE__ */ jsx(StatCard, {
558
+ label: tr("fluidgrids.executions.executionStats.totalRuns", "Total Runs"),
559
+ value: stats.total.toLocaleString(),
560
+ icon: "mdi:play-box-multiple",
561
+ color: "text-text-primary",
562
+ onClick: () => onFilterByStatus?.("all")
563
+ }),
564
+ /* @__PURE__ */ jsx(StatCard, {
565
+ label: tr("fluidgrids.executions.executionStats.running", "Running"),
566
+ value: stats.running,
567
+ icon: "mdi:play-circle",
568
+ color: "text-[var(--color-status-info-text)]",
569
+ onClick: () => onFilterByStatus?.("RUNNING")
570
+ }),
571
+ /* @__PURE__ */ jsx(StatCard, {
572
+ label: tr("fluidgrids.executions.executionStats.completed", "Completed"),
573
+ value: stats.completed.toLocaleString(),
574
+ icon: "mdi:check-circle",
575
+ color: "text-[var(--color-status-success-text)]",
576
+ onClick: () => onFilterByStatus?.("COMPLETED")
577
+ }),
578
+ /* @__PURE__ */ jsx(StatCard, {
579
+ label: tr("fluidgrids.executions.executionStats.failed", "Failed"),
580
+ value: stats.failed,
581
+ icon: "mdi:alert-circle",
582
+ color: "text-[var(--color-status-error-text)]",
583
+ onClick: () => onFilterByStatus?.("FAILED")
584
+ }),
585
+ /* @__PURE__ */ jsx(StatCard, {
586
+ label: tr("fluidgrids.executions.executionStats.successRate", "Success Rate"),
587
+ value: `${(stats.successRate ?? 0).toFixed(1)}%`,
588
+ icon: "mdi:percent",
589
+ color: "text-accent"
590
+ }),
591
+ /* @__PURE__ */ jsx(StatCard, {
592
+ label: tr("fluidgrids.executions.executionStats.avgDuration", "Avg Duration"),
593
+ value: formatDuration(stats.avgDuration ?? 0),
594
+ icon: "mdi:timer",
595
+ color: "text-[var(--color-status-warning-text)]"
596
+ })
597
+ ]
598
+ });
599
+ }
600
+
601
+ // src/modules/executions/components/ExecutionTable.tsx
602
+ function ExecutionTable({ executions, workflowNames, loading, onRowClick, onAction }) {
603
+ const tr = useTr();
604
+ const navigate = useNavigate();
605
+ const { selection, toggleSelectRun, selectAll, expandedRows, toggleRowExpanded } = useExecutionsStore();
606
+ const allRunIds = useMemo(() => executions.map((e) => e.id), [executions]);
607
+ const formatDate = (dateStr) => {
608
+ if (!dateStr) return "-";
609
+ const date = new Date(dateStr);
610
+ const diff = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
611
+ if (diff < 6e4) return tr("fluidgrids.executions.executionTable.justNow", "Just now");
612
+ if (diff < 36e5) return `${Math.round(diff / 6e4)}m ago`;
613
+ if (diff < 864e5) return `${Math.round(diff / 36e5)}h ago`;
614
+ if (diff < 6048e5) return `${Math.round(diff / 864e5)}d ago`;
615
+ return date.toLocaleDateString("en-US", {
616
+ month: "short",
617
+ day: "numeric",
618
+ hour: "2-digit",
619
+ minute: "2-digit"
620
+ });
621
+ };
622
+ const getRowActions = (execution) => {
623
+ const actions = [];
624
+ switch (execution.status) {
625
+ case "RUNNING":
626
+ actions.push({
627
+ icon: "mdi:pause",
628
+ label: tr("fluidgrids.executions.executionTable.actionPause", "Pause"),
629
+ action: "pause"
630
+ });
631
+ actions.push({
632
+ icon: "mdi:stop",
633
+ label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
634
+ action: "cancel"
635
+ });
636
+ break;
637
+ case "WAITING":
638
+ actions.push({
639
+ icon: "mdi:play",
640
+ label: tr("fluidgrids.executions.executionTable.actionResume", "Resume"),
641
+ action: "resume"
642
+ });
643
+ actions.push({
644
+ icon: "mdi:stop",
645
+ label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
646
+ action: "cancel"
647
+ });
648
+ break;
649
+ case "FAILED":
650
+ actions.push({
651
+ icon: "mdi:refresh",
652
+ label: tr("fluidgrids.executions.executionTable.actionRetry", "Retry"),
653
+ action: "retry"
654
+ });
655
+ break;
656
+ case "PENDING": actions.push({
657
+ icon: "mdi:stop",
658
+ label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
659
+ action: "cancel"
660
+ });
661
+ }
662
+ return actions;
663
+ };
664
+ if (loading && executions.length === 0) return /* @__PURE__ */ jsx("div", {
665
+ className: "bg-background-secondary rounded-lg border border-border-secondary overflow-hidden",
666
+ children: /* @__PURE__ */ jsx("div", {
667
+ className: "overflow-x-auto",
668
+ children: /* @__PURE__ */ jsxs("table", {
669
+ className: "w-full min-w-[800px]",
670
+ children: [/* @__PURE__ */ jsx("thead", {
671
+ className: "bg-background-tertiary border-b border-border-secondary",
672
+ children: /* @__PURE__ */ jsxs("tr", { children: [
673
+ /* @__PURE__ */ jsx("th", { className: "w-10 px-4 py-3" }),
674
+ /* @__PURE__ */ jsx("th", {
675
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
676
+ children: tr("fluidgrids.executions.executionTable.colStatus", "Status")
677
+ }),
678
+ /* @__PURE__ */ jsx("th", {
679
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
680
+ children: tr("fluidgrids.executions.executionTable.colWorkflow", "Workflow")
681
+ }),
682
+ /* @__PURE__ */ jsx("th", {
683
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
684
+ children: tr("fluidgrids.executions.executionTable.colRunId", "Run ID")
685
+ }),
686
+ /* @__PURE__ */ jsx("th", {
687
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
688
+ children: tr("fluidgrids.executions.executionTable.colStarted", "Started")
689
+ }),
690
+ /* @__PURE__ */ jsx("th", {
691
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
692
+ children: tr("fluidgrids.executions.executionTable.colDuration", "Duration")
693
+ }),
694
+ /* @__PURE__ */ jsx("th", {
695
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase",
696
+ children: tr("fluidgrids.executions.executionTable.colProgress", "Progress")
697
+ }),
698
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3" })
699
+ ] })
700
+ }), /* @__PURE__ */ jsx("tbody", { children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("tr", {
701
+ className: "border-b border-border-secondary",
702
+ children: /* @__PURE__ */ jsx("td", {
703
+ colSpan: 8,
704
+ className: "px-4 py-4",
705
+ children: /* @__PURE__ */ jsx("div", { className: "h-8 bg-background-tertiary rounded animate-pulse" })
706
+ })
707
+ }, i)) })]
708
+ })
709
+ })
710
+ });
711
+ if (executions.length === 0) return /* @__PURE__ */ jsxs("div", {
712
+ className: "bg-background-secondary rounded-lg border border-border-secondary p-12 text-center",
713
+ children: [
714
+ /* @__PURE__ */ jsx(Icon, {
715
+ icon: "mdi:play-box-outline",
716
+ className: "mx-auto text-text-secondary mb-4",
717
+ width: 48
718
+ }),
719
+ /* @__PURE__ */ jsx("h3", {
720
+ className: "text-lg font-medium text-text-primary mb-2",
721
+ children: tr("fluidgrids.executions.executionTable.emptyTitle", "No executions found")
722
+ }),
723
+ /* @__PURE__ */ jsx("p", {
724
+ className: "text-text-secondary mb-4",
725
+ children: tr("fluidgrids.executions.executionTable.emptyDescription", "There are no workflow executions matching your filters.")
726
+ }),
727
+ /* @__PURE__ */ jsx(Button$1, {
728
+ variant: "outline",
729
+ onClick: () => navigate("../list"),
730
+ children: tr("fluidgrids.executions.executionTable.viewWorkflows", "View Workflows")
731
+ })
732
+ ]
733
+ });
734
+ return /* @__PURE__ */ jsxs("div", {
735
+ className: "bg-background-secondary rounded-lg border border-border-secondary overflow-hidden",
736
+ children: [/* @__PURE__ */ jsx("div", {
737
+ className: "overflow-x-auto",
738
+ children: /* @__PURE__ */ jsxs("table", {
739
+ className: "w-full min-w-[800px]",
740
+ children: [/* @__PURE__ */ jsx("thead", {
741
+ className: "bg-background-tertiary border-b border-border-secondary",
742
+ children: /* @__PURE__ */ jsxs("tr", { children: [
743
+ /* @__PURE__ */ jsx("th", {
744
+ className: "w-10 px-4 py-3",
745
+ children: /* @__PURE__ */ jsx("input", {
746
+ type: "checkbox",
747
+ checked: selection.allSelected,
748
+ onChange: () => selectAll(allRunIds),
749
+ className: "w-4 h-4 rounded border-border-secondary"
750
+ })
751
+ }),
752
+ /* @__PURE__ */ jsx("th", {
753
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
754
+ children: tr("fluidgrids.executions.executionTable.colStatus", "Status")
755
+ }),
756
+ /* @__PURE__ */ jsx("th", {
757
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
758
+ children: tr("fluidgrids.executions.executionTable.colWorkflow", "Workflow")
759
+ }),
760
+ /* @__PURE__ */ jsx("th", {
761
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
762
+ children: tr("fluidgrids.executions.executionTable.colRunId", "Run ID")
763
+ }),
764
+ /* @__PURE__ */ jsx("th", {
765
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
766
+ children: tr("fluidgrids.executions.executionTable.colStarted", "Started")
767
+ }),
768
+ /* @__PURE__ */ jsx("th", {
769
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
770
+ children: tr("fluidgrids.executions.executionTable.colDuration", "Duration")
771
+ }),
772
+ /* @__PURE__ */ jsx("th", {
773
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
774
+ children: tr("fluidgrids.executions.executionTable.colProgress", "Progress")
775
+ }),
776
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 w-32" })
777
+ ] })
778
+ }), /* @__PURE__ */ jsx("tbody", {
779
+ className: "divide-y divide-border-secondary",
780
+ children: executions.map((execution) => {
781
+ const isSelected = selection.runIds.includes(execution.id);
782
+ const isExpanded = expandedRows.has(execution.id);
783
+ const actions = getRowActions(execution);
784
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", {
785
+ className: `
786
+ hover:bg-background-tertiary/50 cursor-pointer transition-colors
787
+ ${isSelected ? "bg-action-primary-bg/10" : ""}
788
+ `,
789
+ onClick: () => onRowClick?.(execution),
790
+ children: [
791
+ /* @__PURE__ */ jsx("td", {
792
+ className: "px-4 py-3",
793
+ onClick: (e) => e.stopPropagation(),
794
+ children: /* @__PURE__ */ jsx("input", {
795
+ type: "checkbox",
796
+ checked: isSelected,
797
+ onChange: () => toggleSelectRun(execution.id),
798
+ className: "w-4 h-4 rounded border-border-secondary"
799
+ })
800
+ }),
801
+ /* @__PURE__ */ jsx("td", {
802
+ className: "px-4 py-3",
803
+ children: /* @__PURE__ */ jsx(ExecutionStatusBadge, {
804
+ status: execution.status,
805
+ size: "sm"
806
+ })
807
+ }),
808
+ /* @__PURE__ */ jsx("td", {
809
+ className: "px-4 py-3",
810
+ children: /* @__PURE__ */ jsxs("div", {
811
+ className: "flex flex-col",
812
+ children: [/* @__PURE__ */ jsx("span", {
813
+ className: "text-sm font-medium text-text-primary",
814
+ children: execution.workflow?.name || workflowNames?.[execution.workflowKey] || execution.workflowKey
815
+ }), /* @__PURE__ */ jsx("span", {
816
+ className: "text-xs text-text-secondary",
817
+ children: formatRunVersion(execution.version)
818
+ })]
819
+ })
820
+ }),
821
+ /* @__PURE__ */ jsx("td", {
822
+ className: "px-4 py-3",
823
+ children: /* @__PURE__ */ jsxs("span", {
824
+ className: "text-sm font-mono text-text-secondary",
825
+ children: [execution.id.slice(0, 8), "..."]
826
+ })
827
+ }),
828
+ /* @__PURE__ */ jsx("td", {
829
+ className: "px-4 py-3",
830
+ children: /* @__PURE__ */ jsx("span", {
831
+ className: "text-sm text-text-secondary",
832
+ children: formatDate(execution.startedAt || execution.createdAt)
833
+ })
834
+ }),
835
+ /* @__PURE__ */ jsx("td", {
836
+ className: "px-4 py-3",
837
+ children: /* @__PURE__ */ jsx("span", {
838
+ className: "text-sm text-text-secondary",
839
+ children: formatElapsedMs(runDurationMs(execution))
840
+ })
841
+ }),
842
+ /* @__PURE__ */ jsx("td", {
843
+ className: "px-4 py-3",
844
+ children: /* @__PURE__ */ jsxs("div", {
845
+ className: "flex items-center gap-2",
846
+ children: [/* @__PURE__ */ jsx("div", {
847
+ className: "flex-1 h-2 bg-background-tertiary rounded-full overflow-hidden max-w-[100px]",
848
+ children: /* @__PURE__ */ jsx("div", {
849
+ className: `h-full transition-all ${execution.status === "FAILED" ? "bg-[var(--color-status-error-bg)]" : execution.status === "COMPLETED" ? "bg-[var(--color-status-success-bg)]" : "bg-[var(--color-status-info-bg)]"}`,
850
+ style: { width: `${execution.progress}%` }
851
+ })
852
+ }), /* @__PURE__ */ jsxs("span", {
853
+ className: "text-xs text-text-secondary min-w-[45px]",
854
+ children: [
855
+ execution.completedNodes ?? 0,
856
+ "/",
857
+ execution.totalNodes ?? 0
858
+ ]
859
+ })]
860
+ })
861
+ }),
862
+ /* @__PURE__ */ jsx("td", {
863
+ className: "px-4 py-3",
864
+ onClick: (e) => e.stopPropagation(),
865
+ children: /* @__PURE__ */ jsxs("div", {
866
+ className: "flex items-center gap-1",
867
+ children: [
868
+ /* @__PURE__ */ jsx("button", {
869
+ onClick: () => toggleRowExpanded(execution.id),
870
+ className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
871
+ children: /* @__PURE__ */ jsx(Icon, {
872
+ icon: isExpanded ? "mdi:chevron-up" : "mdi:chevron-down",
873
+ width: 18
874
+ })
875
+ }),
876
+ actions.slice(0, 2).map((action) => /* @__PURE__ */ jsx("button", {
877
+ "data-agent": "confirm",
878
+ onClick: () => onAction?.(action.action, execution),
879
+ className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
880
+ title: action.label,
881
+ children: /* @__PURE__ */ jsx(Icon, {
882
+ icon: action.icon,
883
+ width: 18
884
+ })
885
+ }, action.action)),
886
+ /* @__PURE__ */ jsx("button", {
887
+ onClick: () => navigate(`../runs/${execution.id}`),
888
+ className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
889
+ title: tr("fluidgrids.executions.executionTable.viewDetails", "View Details"),
890
+ children: /* @__PURE__ */ jsx(Icon, {
891
+ icon: "mdi:arrow-right",
892
+ width: 18
893
+ })
894
+ })
895
+ ]
896
+ })
897
+ })
898
+ ]
899
+ }, execution.id), isExpanded && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
900
+ colSpan: 8,
901
+ className: "px-4 py-4 bg-background-tertiary/30",
902
+ children: /* @__PURE__ */ jsxs("div", {
903
+ className: "grid grid-cols-2 lg:grid-cols-4 gap-4",
904
+ children: [
905
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
906
+ className: "text-xs text-text-secondary mb-1",
907
+ children: tr("fluidgrids.executions.executionTable.triggerNode", "Trigger Node")
908
+ }), /* @__PURE__ */ jsx("div", {
909
+ className: "text-sm text-text-primary font-medium",
910
+ children: execution.triggerNodeId
911
+ })] }),
912
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
913
+ className: "text-xs text-text-secondary mb-1",
914
+ children: tr("fluidgrids.executions.executionTable.nodeStats", "Node Stats")
915
+ }), /* @__PURE__ */ jsxs("div", {
916
+ className: "flex gap-2 text-sm",
917
+ children: [
918
+ /* @__PURE__ */ jsxs("span", {
919
+ className: "text-[var(--color-status-success-text)]",
920
+ children: [
921
+ execution.completedNodes ?? 0,
922
+ " ",
923
+ tr("fluidgrids.executions.executionTable.completed", "completed")
924
+ ]
925
+ }),
926
+ (execution.failedNodes ?? 0) > 0 && /* @__PURE__ */ jsxs("span", {
927
+ className: "text-[var(--color-status-error-text)]",
928
+ children: [
929
+ execution.failedNodes,
930
+ " ",
931
+ tr("fluidgrids.executions.executionTable.failed", "failed")
932
+ ]
933
+ }),
934
+ (execution.skippedNodes ?? 0) > 0 && /* @__PURE__ */ jsxs("span", {
935
+ className: "text-tertiary",
936
+ children: [
937
+ execution.skippedNodes,
938
+ " ",
939
+ tr("fluidgrids.executions.executionTable.skipped", "skipped")
940
+ ]
941
+ })
942
+ ]
943
+ })] }),
944
+ execution.errorMessage && /* @__PURE__ */ jsxs("div", {
945
+ className: "col-span-2",
946
+ children: [/* @__PURE__ */ jsx("div", {
947
+ className: "text-xs text-text-secondary mb-1",
948
+ children: tr("fluidgrids.executions.executionTable.error", "Error")
949
+ }), /* @__PURE__ */ jsx("div", {
950
+ className: "text-sm text-[var(--color-status-error-text)] bg-[var(--color-status-error-bg-subtle)] p-2 rounded",
951
+ children: execution.errorMessage
952
+ })]
953
+ }),
954
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
955
+ className: "text-xs text-text-secondary mb-1",
956
+ children: tr("fluidgrids.executions.executionTable.fullRunId", "Full Run ID")
957
+ }), /* @__PURE__ */ jsx("div", {
958
+ className: "text-sm font-mono text-text-secondary break-all",
959
+ children: execution.id
960
+ })] })
961
+ ]
962
+ })
963
+ }) }, `${execution.id}-expanded`)] });
964
+ })
965
+ })]
966
+ })
967
+ }), /* @__PURE__ */ jsx("div", {
968
+ className: "sm:hidden px-4 py-2 bg-background-tertiary border-t border-border-secondary text-xs text-text-secondary text-center",
969
+ children: tr("fluidgrids.executions.executionTable.swipeHint", "Swipe to see more columns")
970
+ })]
971
+ });
972
+ }
973
+
974
+ // src/modules/executions/components/ExecutionCards.tsx
975
+ /**
976
+ * ExecutionCards — Card-grid view for workflow executions.
977
+ * Displays each execution as a card with status badge, timing info, and progress bar.
978
+ * Used when viewMode is 'cards' in ExecutionsListPage.
979
+ */
980
+ function formatRelativeTime(dateStr) {
981
+ if (!dateStr) return "--";
982
+ const date = new Date(dateStr);
983
+ const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
984
+ const diffMins = Math.floor(diffMs / 6e4);
985
+ if (diffMins < 1) return "just now";
986
+ if (diffMins < 60) return `${diffMins}m ago`;
987
+ const diffHours = Math.floor(diffMins / 60);
988
+ if (diffHours < 24) return `${diffHours}h ago`;
989
+ return `${Math.floor(diffHours / 24)}d ago`;
990
+ }
991
+ function getProgressColor(status) {
992
+ switch (status) {
993
+ case "COMPLETED": return "bg-status-success-bg";
994
+ case "FAILED":
995
+ case "TERMINATED": return "bg-status-error-bg";
996
+ case "RUNNING": return "bg-status-info-bg";
997
+ case "CANCELLED": return "bg-status-warning-bg";
998
+ default: return "bg-text-muted";
999
+ }
1000
+ }
1001
+ function ExecutionCards({ executions, workflowNames, loading, onCardClick, onAction }) {
1002
+ const tr = useTr();
1003
+ if (loading) return /* @__PURE__ */ jsx("div", {
1004
+ className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
1005
+ children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs("div", {
1006
+ className: "rounded-lg border border-border-secondary bg-background-secondary p-4 animate-pulse",
1007
+ children: [
1008
+ /* @__PURE__ */ jsxs("div", {
1009
+ className: "flex justify-between mb-3",
1010
+ children: [/* @__PURE__ */ jsx("div", { className: "h-4 bg-background-tertiary rounded w-32" }), /* @__PURE__ */ jsx("div", { className: "h-5 bg-background-tertiary rounded-full w-20" })]
1011
+ }),
1012
+ /* @__PURE__ */ jsx("div", { className: "h-3 bg-background-tertiary rounded w-48 mb-2" }),
1013
+ /* @__PURE__ */ jsx("div", { className: "h-2 bg-background-tertiary rounded w-full mb-3" }),
1014
+ /* @__PURE__ */ jsxs("div", {
1015
+ className: "flex justify-between",
1016
+ children: [/* @__PURE__ */ jsx("div", { className: "h-3 bg-background-tertiary rounded w-16" }), /* @__PURE__ */ jsx("div", { className: "h-3 bg-background-tertiary rounded w-24" })]
1017
+ })
1018
+ ]
1019
+ }, i))
1020
+ });
1021
+ if (executions.length === 0) return /* @__PURE__ */ jsxs("div", {
1022
+ className: "flex flex-col items-center justify-center py-16 text-text-secondary",
1023
+ children: [
1024
+ /* @__PURE__ */ jsx(Icon, {
1025
+ icon: "mdi:play-circle-outline",
1026
+ width: 48,
1027
+ className: "mb-3 opacity-40"
1028
+ }),
1029
+ /* @__PURE__ */ jsx("p", {
1030
+ className: "text-lg font-medium",
1031
+ children: tr("fluidgrids.executions.executionCards.emptyTitle", "No executions found")
1032
+ }),
1033
+ /* @__PURE__ */ jsx("p", {
1034
+ className: "text-sm mt-1",
1035
+ children: tr("fluidgrids.executions.executionCards.emptyDescription", "Run a workflow to see results here")
1036
+ })
1037
+ ]
1038
+ });
1039
+ return /* @__PURE__ */ jsx("div", {
1040
+ className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
1041
+ children: executions.map((execution) => {
1042
+ const config = RUN_STATUS_CONFIG[execution.status];
1043
+ const canCancel = execution.status === "RUNNING" || execution.status === "PENDING";
1044
+ const canPause = execution.status === "RUNNING";
1045
+ return /* @__PURE__ */ jsxs("div", {
1046
+ onClick: () => onCardClick(execution),
1047
+ className: `
1048
+ rounded-lg border bg-background-secondary p-4 cursor-pointer
1049
+ transition-all duration-200 hover:shadow-md hover:border-border-primary
1050
+ ${config.borderColor}
1051
+ `,
1052
+ children: [
1053
+ /* @__PURE__ */ jsxs("div", {
1054
+ className: "flex items-start justify-between mb-3",
1055
+ children: [/* @__PURE__ */ jsxs("div", {
1056
+ className: "min-w-0 flex-1 mr-2",
1057
+ children: [/* @__PURE__ */ jsx("p", {
1058
+ className: "text-sm font-semibold text-text-primary truncate",
1059
+ children: execution.workflow?.name || workflowNames?.[execution.workflowKey] || execution.workflowKey
1060
+ }), /* @__PURE__ */ jsx("p", {
1061
+ className: "text-xs text-text-tertiary mt-0.5",
1062
+ children: formatRunVersion(execution.version)
1063
+ })]
1064
+ }), /* @__PURE__ */ jsx(ExecutionStatusBadge, {
1065
+ status: execution.status,
1066
+ size: "sm"
1067
+ })]
1068
+ }),
1069
+ /* @__PURE__ */ jsxs("div", {
1070
+ className: "mb-3",
1071
+ children: [/* @__PURE__ */ jsxs("div", {
1072
+ className: "flex justify-between text-xs text-text-tertiary mb-1",
1073
+ children: [/* @__PURE__ */ jsx("span", { children: tr("fluidgrids.executions.executionCards.progressLabel", "Progress") }), /* @__PURE__ */ jsxs("span", { children: [Math.round(execution.progress), "%"] })]
1074
+ }), /* @__PURE__ */ jsx("div", {
1075
+ className: "h-1.5 bg-background-tertiary rounded-full overflow-hidden",
1076
+ children: /* @__PURE__ */ jsx("div", {
1077
+ className: `h-full rounded-full transition-all duration-500 ${getProgressColor(execution.status)}`,
1078
+ style: { width: `${Math.min(execution.progress, 100)}%` }
1079
+ })
1080
+ })]
1081
+ }),
1082
+ (execution.totalNodes ?? 0) > 0 && /* @__PURE__ */ jsxs("div", {
1083
+ className: "flex gap-3 text-xs text-text-secondary mb-3",
1084
+ children: [
1085
+ /* @__PURE__ */ jsxs("span", {
1086
+ className: "flex items-center gap-1",
1087
+ children: [/* @__PURE__ */ jsx(Icon, {
1088
+ icon: "mdi:check-circle",
1089
+ width: 12,
1090
+ className: "text-status-success-text"
1091
+ }), execution.completedNodes ?? 0]
1092
+ }),
1093
+ /* @__PURE__ */ jsxs("span", {
1094
+ className: "flex items-center gap-1",
1095
+ children: [/* @__PURE__ */ jsx(Icon, {
1096
+ icon: "mdi:alert-circle",
1097
+ width: 12,
1098
+ className: "text-status-error-text"
1099
+ }), execution.failedNodes ?? 0]
1100
+ }),
1101
+ /* @__PURE__ */ jsxs("span", {
1102
+ className: "flex items-center gap-1",
1103
+ children: [/* @__PURE__ */ jsx(Icon, {
1104
+ icon: "mdi:skip-next",
1105
+ width: 12,
1106
+ className: "text-text-muted"
1107
+ }), execution.skippedNodes ?? 0]
1108
+ }),
1109
+ /* @__PURE__ */ jsxs("span", {
1110
+ className: "flex items-center gap-1 ml-auto",
1111
+ children: [/* @__PURE__ */ jsx(Icon, {
1112
+ icon: "mdi:sigma",
1113
+ width: 12
1114
+ }), execution.totalNodes]
1115
+ })
1116
+ ]
1117
+ }),
1118
+ /* @__PURE__ */ jsxs("div", {
1119
+ className: "flex items-center justify-between text-xs text-text-tertiary pt-2 border-t border-border-secondary",
1120
+ children: [/* @__PURE__ */ jsxs("span", {
1121
+ className: "flex items-center gap-1",
1122
+ children: [/* @__PURE__ */ jsx(Icon, {
1123
+ icon: "mdi:timer-outline",
1124
+ width: 12
1125
+ }), formatElapsedMs(runDurationMs(execution), "--")]
1126
+ }), /* @__PURE__ */ jsx("span", { children: formatRelativeTime(execution.startedAt ?? execution.createdAt) })]
1127
+ }),
1128
+ (canCancel || canPause) && /* @__PURE__ */ jsxs("div", {
1129
+ className: "flex gap-1 mt-2 pt-2 border-t border-border-secondary",
1130
+ children: [canPause && /* @__PURE__ */ jsxs("button", {
1131
+ type: "button",
1132
+ onClick: (e) => {
1133
+ e.stopPropagation();
1134
+ onAction("pause", execution);
1135
+ },
1136
+ className: "flex items-center gap-1 text-xs px-2 py-1 rounded bg-background-tertiary hover:bg-status-warning-bg-subtle text-text-secondary hover:text-status-warning-text transition-colors",
1137
+ children: [
1138
+ /* @__PURE__ */ jsx(Icon, {
1139
+ icon: "mdi:pause",
1140
+ width: 12
1141
+ }),
1142
+ " ",
1143
+ tr("fluidgrids.executions.executionCards.pauseButton", "Pause")
1144
+ ]
1145
+ }), canCancel && /* @__PURE__ */ jsxs("button", {
1146
+ type: "button",
1147
+ onClick: (e) => {
1148
+ e.stopPropagation();
1149
+ onAction("cancel", execution);
1150
+ },
1151
+ className: "flex items-center gap-1 text-xs px-2 py-1 rounded bg-background-tertiary hover:bg-status-error-bg-subtle text-text-secondary hover:text-status-error-text transition-colors",
1152
+ children: [
1153
+ /* @__PURE__ */ jsx(Icon, {
1154
+ icon: "mdi:stop",
1155
+ width: 12
1156
+ }),
1157
+ " ",
1158
+ tr("fluidgrids.executions.executionCards.cancelButton", "Cancel")
1159
+ ]
1160
+ })]
1161
+ })
1162
+ ]
1163
+ }, execution.id);
1164
+ })
1165
+ });
1166
+ }
1167
+
1168
+ // src/modules/executions/components/ExecutionTimeline.tsx
1169
+ /**
1170
+ * ExecutionTimeline — Chronological timeline view for workflow executions.
1171
+ * Displays executions as a vertical timeline with status indicators and duration bars.
1172
+ * Used when viewMode is 'timeline' in ExecutionsListPage.
1173
+ */
1174
+ function formatTime(dateStr) {
1175
+ if (!dateStr) return "--";
1176
+ return new Date(dateStr).toLocaleTimeString(void 0, {
1177
+ hour: "2-digit",
1178
+ minute: "2-digit",
1179
+ second: "2-digit"
1180
+ });
1181
+ }
1182
+ function formatDate(dateStr) {
1183
+ if (!dateStr) return "";
1184
+ return new Date(dateStr).toLocaleDateString(void 0, {
1185
+ weekday: "short",
1186
+ month: "short",
1187
+ day: "numeric"
1188
+ });
1189
+ }
1190
+ function getStatusDotColor(status) {
1191
+ switch (status) {
1192
+ case "COMPLETED": return "bg-status-success-bg";
1193
+ case "FAILED":
1194
+ case "TERMINATED": return "bg-status-error-bg";
1195
+ case "RUNNING": return "bg-status-info-bg animate-pulse";
1196
+ case "CANCELLED": return "bg-status-warning-bg";
1197
+ case "WAITING": return "bg-chart-3";
1198
+ default: return "bg-text-muted";
1199
+ }
1200
+ }
1201
+ /** Group executions by date for timeline sections */
1202
+ function groupByDate(executions) {
1203
+ const groups = /* @__PURE__ */ new Map();
1204
+ for (const exec of executions) {
1205
+ const dateKey = formatDate(exec.startedAt ?? exec.createdAt);
1206
+ if (!groups.has(dateKey)) groups.set(dateKey, []);
1207
+ groups.get(dateKey).push(exec);
1208
+ }
1209
+ return groups;
1210
+ }
1211
+ function ExecutionTimeline({ executions, loading, onItemClick, onAction }) {
1212
+ const tr = useTr();
1213
+ if (loading) return /* @__PURE__ */ jsx("div", {
1214
+ className: "space-y-6 pl-8",
1215
+ children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs("div", {
1216
+ className: "flex gap-4 animate-pulse",
1217
+ children: [/* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-full bg-background-tertiary mt-1.5 shrink-0" }), /* @__PURE__ */ jsxs("div", {
1218
+ className: "flex-1",
1219
+ children: [
1220
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-background-tertiary rounded w-48 mb-2" }),
1221
+ /* @__PURE__ */ jsx("div", { className: "h-3 bg-background-tertiary rounded w-32 mb-1" }),
1222
+ /* @__PURE__ */ jsx("div", { className: "h-3 bg-background-tertiary rounded w-24" })
1223
+ ]
1224
+ })]
1225
+ }, i))
1226
+ });
1227
+ if (executions.length === 0) return /* @__PURE__ */ jsxs("div", {
1228
+ className: "flex flex-col items-center justify-center py-16 text-text-secondary",
1229
+ children: [
1230
+ /* @__PURE__ */ jsx(Icon, {
1231
+ icon: "mdi:timeline",
1232
+ width: 48,
1233
+ className: "mb-3 opacity-40"
1234
+ }),
1235
+ /* @__PURE__ */ jsx("p", {
1236
+ className: "text-lg font-medium",
1237
+ children: tr("fluidgrids.executions.executionTimeline.emptyTitle", "No executions found")
1238
+ }),
1239
+ /* @__PURE__ */ jsx("p", {
1240
+ className: "text-sm mt-1",
1241
+ children: tr("fluidgrids.executions.executionTimeline.emptyDescription", "Run a workflow to see the timeline")
1242
+ })
1243
+ ]
1244
+ });
1245
+ const grouped = groupByDate(executions);
1246
+ return /* @__PURE__ */ jsx("div", {
1247
+ className: "space-y-8",
1248
+ children: Array.from(grouped.entries()).map(([dateLabel, dateExecutions]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
1249
+ className: "flex items-center gap-2 mb-4",
1250
+ children: [
1251
+ /* @__PURE__ */ jsx(Icon, {
1252
+ icon: "mdi:calendar",
1253
+ width: 14,
1254
+ className: "text-text-tertiary"
1255
+ }),
1256
+ /* @__PURE__ */ jsx("span", {
1257
+ className: "text-xs font-semibold text-text-tertiary uppercase tracking-wider",
1258
+ children: dateLabel
1259
+ }),
1260
+ /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border-secondary" }),
1261
+ /* @__PURE__ */ jsxs("span", {
1262
+ className: "text-xs text-text-tertiary",
1263
+ children: [
1264
+ dateExecutions.length,
1265
+ " ",
1266
+ tr("fluidgrids.executions.executionTimeline.runsCountSuffix", "runs")
1267
+ ]
1268
+ })
1269
+ ]
1270
+ }), /* @__PURE__ */ jsxs("div", {
1271
+ className: "relative ml-4",
1272
+ children: [/* @__PURE__ */ jsx("div", { className: "absolute left-[5px] top-0 bottom-0 w-px bg-border-secondary" }), /* @__PURE__ */ jsx("div", {
1273
+ className: "space-y-4",
1274
+ children: dateExecutions.map((execution, idx) => {
1275
+ const isLast = idx === dateExecutions.length - 1;
1276
+ const canCancel = execution.status === "RUNNING" || execution.status === "PENDING";
1277
+ return /* @__PURE__ */ jsxs("div", {
1278
+ onClick: () => onItemClick(execution),
1279
+ className: "relative flex gap-4 cursor-pointer group",
1280
+ children: [/* @__PURE__ */ jsx("div", {
1281
+ className: "relative z-10 shrink-0 mt-1.5",
1282
+ children: /* @__PURE__ */ jsx("div", { className: `w-[11px] h-[11px] rounded-full border-2 border-background-primary ${getStatusDotColor(execution.status)}` })
1283
+ }), /* @__PURE__ */ jsxs("div", {
1284
+ className: `
1285
+ flex-1 rounded-lg border border-border-secondary bg-background-secondary p-3
1286
+ transition-all duration-200 group-hover:shadow-sm group-hover:border-border-primary
1287
+ ${isLast ? "" : "mb-0"}
1288
+ `,
1289
+ children: [/* @__PURE__ */ jsxs("div", {
1290
+ className: "flex items-start justify-between gap-2",
1291
+ children: [/* @__PURE__ */ jsxs("div", {
1292
+ className: "min-w-0 flex-1",
1293
+ children: [
1294
+ /* @__PURE__ */ jsxs("div", {
1295
+ className: "flex items-center gap-2",
1296
+ children: [/* @__PURE__ */ jsx("span", {
1297
+ className: "text-sm font-semibold text-text-primary truncate",
1298
+ children: execution.workflowKey
1299
+ }), /* @__PURE__ */ jsxs("span", {
1300
+ className: "text-xs text-text-tertiary",
1301
+ children: ["v", execution.version]
1302
+ })]
1303
+ }),
1304
+ /* @__PURE__ */ jsxs("div", {
1305
+ className: "flex items-center gap-3 mt-1 text-xs text-text-secondary",
1306
+ children: [
1307
+ /* @__PURE__ */ jsxs("span", {
1308
+ className: "flex items-center gap-1",
1309
+ children: [/* @__PURE__ */ jsx(Icon, {
1310
+ icon: "mdi:clock-outline",
1311
+ width: 12
1312
+ }), formatTime(execution.startedAt ?? execution.createdAt)]
1313
+ }),
1314
+ /* @__PURE__ */ jsxs("span", {
1315
+ className: "flex items-center gap-1",
1316
+ children: [/* @__PURE__ */ jsx(Icon, {
1317
+ icon: "mdi:timer-outline",
1318
+ width: 12
1319
+ }), formatElapsedMs(runDurationMs(execution), "--")]
1320
+ }),
1321
+ (execution.totalNodes ?? 0) > 0 && /* @__PURE__ */ jsxs("span", {
1322
+ className: "flex items-center gap-1",
1323
+ children: [
1324
+ /* @__PURE__ */ jsx(Icon, {
1325
+ icon: "mdi:puzzle-outline",
1326
+ width: 12
1327
+ }),
1328
+ execution.completedNodes ?? 0,
1329
+ "/",
1330
+ execution.totalNodes,
1331
+ " ",
1332
+ tr("fluidgrids.executions.executionTimeline.nodesCountSuffix", "nodes")
1333
+ ]
1334
+ })
1335
+ ]
1336
+ }),
1337
+ execution.errorMessage && /* @__PURE__ */ jsx("p", {
1338
+ className: "mt-1.5 text-xs text-status-error-text truncate max-w-sm",
1339
+ children: execution.errorMessage
1340
+ })
1341
+ ]
1342
+ }), /* @__PURE__ */ jsxs("div", {
1343
+ className: "flex items-center gap-2 shrink-0",
1344
+ children: [/* @__PURE__ */ jsx(ExecutionStatusBadge, {
1345
+ status: execution.status,
1346
+ size: "sm"
1347
+ }), canCancel && /* @__PURE__ */ jsx("button", {
1348
+ type: "button",
1349
+ onClick: (e) => {
1350
+ e.stopPropagation();
1351
+ onAction("cancel", execution);
1352
+ },
1353
+ className: "opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-status-error-bg-subtle text-text-tertiary hover:text-status-error-text transition-all",
1354
+ title: tr("fluidgrids.executions.executionTimeline.cancelTooltip", "Cancel execution"),
1355
+ children: /* @__PURE__ */ jsx(Icon, {
1356
+ icon: "mdi:stop",
1357
+ width: 14
1358
+ })
1359
+ })]
1360
+ })]
1361
+ }), execution.status === "RUNNING" && /* @__PURE__ */ jsx("div", {
1362
+ className: "mt-2",
1363
+ children: /* @__PURE__ */ jsx("div", {
1364
+ className: "h-1 bg-background-tertiary rounded-full overflow-hidden",
1365
+ children: /* @__PURE__ */ jsx("div", {
1366
+ className: "h-full bg-status-info-bg rounded-full transition-all duration-500 animate-pulse",
1367
+ style: { width: `${Math.min(execution.progress, 100)}%` }
1368
+ })
1369
+ })
1370
+ })]
1371
+ })]
1372
+ }, execution.id);
1373
+ })
1374
+ })]
1375
+ })] }, dateLabel))
1376
+ });
1377
+ }
1378
+
1379
+ // src/modules/executions/components/Pagination.tsx
1380
+ function Pagination({ totalItems, showPageSize = true }) {
1381
+ const tr = useTr();
1382
+ const { pagination, setPage, setPageSize } = useExecutionsStore();
1383
+ const { page, pageSize, total } = pagination;
1384
+ const totalPages = Math.ceil((totalItems ?? total) / pageSize);
1385
+ const startItem = (page - 1) * pageSize + 1;
1386
+ const endItem = Math.min(page * pageSize, totalItems ?? total);
1387
+ const pageSizeOptions = [
1388
+ 10,
1389
+ 25,
1390
+ 50,
1391
+ 100
1392
+ ];
1393
+ const goToPage = (newPage) => {
1394
+ if (newPage >= 1 && newPage <= totalPages) setPage(newPage);
1395
+ };
1396
+ const getPageNumbers = () => {
1397
+ const pages = [];
1398
+ if (totalPages <= 7) for (let i = 1; i <= totalPages; i++) pages.push(i);
1399
+ else {
1400
+ pages.push(1);
1401
+ if (page > 3) pages.push("ellipsis");
1402
+ const start = Math.max(2, page - 1);
1403
+ const end = Math.min(totalPages - 1, page + 1);
1404
+ for (let i = start; i <= end; i++) pages.push(i);
1405
+ if (page < totalPages - 2) pages.push("ellipsis");
1406
+ pages.push(totalPages);
1407
+ }
1408
+ return pages;
1409
+ };
1410
+ if (totalPages <= 1 && !showPageSize) return null;
1411
+ return /* @__PURE__ */ jsxs("div", {
1412
+ className: "flex flex-col sm:flex-row items-center justify-between gap-4 py-4",
1413
+ children: [/* @__PURE__ */ jsxs("div", {
1414
+ className: "text-sm text-text-secondary",
1415
+ children: [
1416
+ tr("fluidgrids.executions.pagination.showingLabel", "Showing"),
1417
+ " ",
1418
+ /* @__PURE__ */ jsx("span", {
1419
+ className: "font-medium text-text-primary",
1420
+ children: startItem
1421
+ }),
1422
+ " ",
1423
+ tr("fluidgrids.executions.pagination.toLabel", "to"),
1424
+ " ",
1425
+ /* @__PURE__ */ jsx("span", {
1426
+ className: "font-medium text-text-primary",
1427
+ children: endItem
1428
+ }),
1429
+ " ",
1430
+ tr("fluidgrids.executions.pagination.ofLabel", "of"),
1431
+ " ",
1432
+ /* @__PURE__ */ jsx("span", {
1433
+ className: "font-medium text-text-primary",
1434
+ children: totalItems ?? total
1435
+ }),
1436
+ " ",
1437
+ tr("fluidgrids.executions.pagination.resultsLabel", "results")
1438
+ ]
1439
+ }), /* @__PURE__ */ jsxs("div", {
1440
+ className: "flex items-center gap-4",
1441
+ children: [showPageSize && /* @__PURE__ */ jsxs("div", {
1442
+ className: "flex items-center gap-2",
1443
+ children: [/* @__PURE__ */ jsx("label", {
1444
+ className: "text-sm text-text-secondary",
1445
+ children: tr("fluidgrids.executions.pagination.pageSizeLabel", "Show")
1446
+ }), /* @__PURE__ */ jsx("select", {
1447
+ value: pageSize,
1448
+ onChange: (e) => setPageSize(Number(e.target.value)),
1449
+ className: "px-2 py-1 bg-bg-surface border border-border-default rounded text-sm text-text-primary scheme-normal dark:scheme-dark",
1450
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx("option", {
1451
+ value: size,
1452
+ className: "bg-bg-surface text-text-primary",
1453
+ children: size
1454
+ }, size))
1455
+ })]
1456
+ }), totalPages > 1 && /* @__PURE__ */ jsxs("nav", {
1457
+ className: "flex items-center gap-1",
1458
+ children: [
1459
+ /* @__PURE__ */ jsx("button", {
1460
+ onClick: () => goToPage(page - 1),
1461
+ disabled: page === 1,
1462
+ className: "p-2 rounded hover:bg-background-tertiary disabled:opacity-50 disabled:cursor-not-allowed",
1463
+ children: /* @__PURE__ */ jsx(Icon, {
1464
+ icon: "mdi:chevron-left",
1465
+ width: 20,
1466
+ className: "text-text-secondary"
1467
+ })
1468
+ }),
1469
+ /* @__PURE__ */ jsx("div", {
1470
+ className: "hidden sm:flex items-center gap-1",
1471
+ children: getPageNumbers().map((pageNum, index) => pageNum === "ellipsis" ? /* @__PURE__ */ jsx("span", {
1472
+ className: "px-2 text-text-secondary",
1473
+ children: "..."
1474
+ }, `ellipsis-${index}`) : /* @__PURE__ */ jsx("button", {
1475
+ onClick: () => goToPage(pageNum),
1476
+ className: `
1477
+ min-w-[32px] h-8 px-2 rounded text-sm font-medium
1478
+ ${pageNum === page ? "bg-action-primary-bg text-action-primary-text" : "hover:bg-background-tertiary text-text-secondary"}
1479
+ `,
1480
+ children: pageNum
1481
+ }, pageNum))
1482
+ }),
1483
+ /* @__PURE__ */ jsxs("span", {
1484
+ className: "sm:hidden px-3 text-sm text-text-secondary",
1485
+ children: [
1486
+ page,
1487
+ " / ",
1488
+ totalPages
1489
+ ]
1490
+ }),
1491
+ /* @__PURE__ */ jsx("button", {
1492
+ onClick: () => goToPage(page + 1),
1493
+ disabled: page === totalPages,
1494
+ className: "p-2 rounded hover:bg-background-tertiary disabled:opacity-50 disabled:cursor-not-allowed",
1495
+ children: /* @__PURE__ */ jsx(Icon, {
1496
+ icon: "mdi:chevron-right",
1497
+ width: 20,
1498
+ className: "text-text-secondary"
1499
+ })
1500
+ })
1501
+ ]
1502
+ })]
1503
+ })]
1504
+ });
1505
+ }
1506
+
1507
+ // src/modules/run-control/components/SearchAttributeFilterBar.tsx
1508
+ /**
1509
+ * The Runs list's search-attribute filter builder (BOFF-9906, spec code-first
1510
+ * 11 §2.8): rows of attribute, operator and value over the workspace's
1511
+ * definitions, combined with "all" or "any", plus free text over TEXT
1512
+ * attributes. *Apply* hands the filter to the list; the list sends it only
1513
+ * when no filter the service refuses alongside it is set (search, node,
1514
+ * trigger type), and says so here.
1515
+ */
1516
+ const OPERATOR_LABELS = {
1517
+ EQ: "=",
1518
+ NEQ: "≠",
1519
+ IN: "in",
1520
+ GT: ">",
1521
+ GTE: "≥",
1522
+ LT: "<",
1523
+ LTE: "≤",
1524
+ STARTS_WITH: "starts with",
1525
+ CONTAINS: "contains"
1526
+ };
1527
+ function SearchAttributeFilterBar({ applied, onApply, conflicts }) {
1528
+ const tr = useTr();
1529
+ const definitions = useResource("search-attributes", true, fetchSearchAttributes);
1530
+ const [state, setState] = useState(EMPTY_SEARCH_FILTER);
1531
+ const types = useMemo(() => new Map((definitions.data ?? []).map((definition) => [definition.name, definition.type])), [definitions.data]);
1532
+ const built = buildSearchAttributeFilter(state, types);
1533
+ const names = [...types.keys()].sort();
1534
+ const setRow = (index, patch) => setState((current) => ({
1535
+ ...current,
1536
+ rows: current.rows.map((row, i) => {
1537
+ if (i !== index) return row;
1538
+ const next = {
1539
+ ...row,
1540
+ ...patch
1541
+ };
1542
+ const type = types.get(next.name);
1543
+ if (type && !OPERATORS_BY_TYPE[type].includes(next.op)) next.op = OPERATORS_BY_TYPE[type][0];
1544
+ return next;
1545
+ })
1546
+ }));
1547
+ const addRow = () => {
1548
+ const name = names[0];
1549
+ if (!name) return;
1550
+ const type = types.get(name);
1551
+ setState((current) => ({
1552
+ ...current,
1553
+ rows: [...current.rows, {
1554
+ name,
1555
+ op: type ? OPERATORS_BY_TYPE[type][0] : "EQ",
1556
+ value: ""
1557
+ }]
1558
+ }));
1559
+ };
1560
+ return /* @__PURE__ */ jsxs("section", {
1561
+ className: "mb-4 space-y-3 rounded-lg border border-border-secondary bg-background-secondary p-3",
1562
+ "data-testid": "search-attribute-filter",
1563
+ children: [
1564
+ /* @__PURE__ */ jsxs("div", {
1565
+ className: "flex flex-wrap items-center gap-2",
1566
+ children: [
1567
+ /* @__PURE__ */ jsx(Icon, {
1568
+ icon: "mdi:tag-search-outline",
1569
+ width: 18,
1570
+ className: "text-text-secondary"
1571
+ }),
1572
+ /* @__PURE__ */ jsx("span", {
1573
+ className: "text-sm font-medium text-text-primary",
1574
+ children: tr("fluidgrids.runControl.searchFilter.title", "Search attributes")
1575
+ }),
1576
+ state.rows.length > 1 && /* @__PURE__ */ jsxs(NativeSelect, {
1577
+ value: state.combine,
1578
+ onChange: (value) => setState((current) => ({
1579
+ ...current,
1580
+ combine: value === "or" ? "or" : "and"
1581
+ })),
1582
+ ariaLabel: tr("fluidgrids.runControl.searchFilter.combine", "Match"),
1583
+ testId: "search-filter-combine",
1584
+ children: [/* @__PURE__ */ jsx("option", {
1585
+ value: "and",
1586
+ children: tr("fluidgrids.runControl.searchFilter.all", "all conditions")
1587
+ }), /* @__PURE__ */ jsx("option", {
1588
+ value: "or",
1589
+ children: tr("fluidgrids.runControl.searchFilter.any", "any condition")
1590
+ })]
1591
+ }),
1592
+ /* @__PURE__ */ jsx(Input, {
1593
+ value: state.text,
1594
+ onChange: (event) => setState((current) => ({
1595
+ ...current,
1596
+ text: event.target.value
1597
+ })),
1598
+ placeholder: tr("fluidgrids.runControl.searchFilter.text", "Words in text attributes"),
1599
+ className: "h-9 max-w-xs",
1600
+ maxLength: 256,
1601
+ "data-testid": "search-filter-text"
1602
+ }),
1603
+ /* @__PURE__ */ jsxs(Button$1, {
1604
+ size: "sm",
1605
+ variant: "outline",
1606
+ onClick: addRow,
1607
+ disabled: names.length === 0 || state.rows.length >= 20,
1608
+ "data-testid": "search-filter-add",
1609
+ children: [/* @__PURE__ */ jsx(Icon, {
1610
+ icon: "mdi:plus",
1611
+ className: "mr-1",
1612
+ width: 14
1613
+ }), tr("fluidgrids.runControl.searchFilter.add", "Condition")]
1614
+ }),
1615
+ /* @__PURE__ */ jsxs("div", {
1616
+ className: "ml-auto flex gap-2",
1617
+ children: [applied && /* @__PURE__ */ jsx(Button$1, {
1618
+ size: "sm",
1619
+ variant: "ghost",
1620
+ onClick: () => {
1621
+ setState(EMPTY_SEARCH_FILTER);
1622
+ onApply(null);
1623
+ },
1624
+ "data-testid": "search-filter-clear",
1625
+ children: tr("fluidgrids.runControl.searchFilter.clear", "Clear")
1626
+ }), /* @__PURE__ */ jsx(Button$1, {
1627
+ size: "sm",
1628
+ onClick: () => onApply(built.filter),
1629
+ disabled: built.incomplete > 0,
1630
+ "data-testid": "search-filter-apply",
1631
+ children: tr("fluidgrids.runControl.searchFilter.apply", "Apply")
1632
+ })]
1633
+ })
1634
+ ]
1635
+ }),
1636
+ definitions.data && names.length === 0 && /* @__PURE__ */ jsx("p", {
1637
+ className: "text-xs text-text-secondary",
1638
+ "data-testid": "search-filter-no-definitions",
1639
+ children: tr("fluidgrids.runControl.searchFilter.noDefinitions", "No search attributes are defined. Define them in Settings → Run control.")
1640
+ }),
1641
+ /* @__PURE__ */ jsx(FieldError, { message: definitions.error }),
1642
+ state.rows.map((row, index) => {
1643
+ const type = types.get(row.name);
1644
+ return /* @__PURE__ */ jsxs("div", {
1645
+ className: "flex flex-wrap items-center gap-2",
1646
+ "data-testid": `search-filter-row-${index}`,
1647
+ children: [
1648
+ /* @__PURE__ */ jsx("div", {
1649
+ className: "w-44",
1650
+ children: /* @__PURE__ */ jsx(NativeSelect, {
1651
+ value: row.name,
1652
+ onChange: (name) => setRow(index, { name }),
1653
+ ariaLabel: tr("fluidgrids.runControl.searchFilter.attribute", "Attribute"),
1654
+ testId: `search-filter-name-${index}`,
1655
+ children: names.map((name) => /* @__PURE__ */ jsx("option", {
1656
+ value: name,
1657
+ children: name
1658
+ }, name))
1659
+ })
1660
+ }),
1661
+ /* @__PURE__ */ jsx("div", {
1662
+ className: "w-32",
1663
+ children: /* @__PURE__ */ jsx(NativeSelect, {
1664
+ value: row.op,
1665
+ onChange: (op) => setRow(index, { op: (type ? OPERATORS_BY_TYPE[type] : []).find((item) => item === op) ?? row.op }),
1666
+ ariaLabel: tr("fluidgrids.runControl.searchFilter.operator", "Operator"),
1667
+ testId: `search-filter-op-${index}`,
1668
+ children: (type ? OPERATORS_BY_TYPE[type] : []).map((op) => /* @__PURE__ */ jsx("option", {
1669
+ value: op,
1670
+ children: OPERATOR_LABELS[op]
1671
+ }, op))
1672
+ })
1673
+ }),
1674
+ /* @__PURE__ */ jsx(Input, {
1675
+ value: row.value,
1676
+ onChange: (event) => setRow(index, { value: event.target.value }),
1677
+ placeholder: row.op === "IN" ? tr("fluidgrids.runControl.searchFilter.listValue", "a, b, c") : type ?? "",
1678
+ className: "h-9 max-w-xs",
1679
+ "aria-label": tr("fluidgrids.runControl.searchFilter.value", "Value"),
1680
+ "data-testid": `search-filter-value-${index}`
1681
+ }),
1682
+ /* @__PURE__ */ jsx(Button$1, {
1683
+ size: "sm",
1684
+ variant: "ghost",
1685
+ "aria-label": tr("fluidgrids.runControl.searchFilter.remove", "Remove condition"),
1686
+ onClick: () => setState((current) => ({
1687
+ ...current,
1688
+ rows: current.rows.filter((_, i) => i !== index)
1689
+ })),
1690
+ children: /* @__PURE__ */ jsx(Icon, {
1691
+ icon: "mdi:close",
1692
+ width: 14
1693
+ })
1694
+ })
1695
+ ]
1696
+ }, index);
1697
+ }),
1698
+ built.incomplete > 0 && /* @__PURE__ */ jsx("p", {
1699
+ className: "text-xs text-[var(--color-status-warning-text)]",
1700
+ "data-testid": "search-filter-incomplete",
1701
+ children: tr("fluidgrids.runControl.searchFilter.incomplete", "Give every condition a value of its attribute's type.")
1702
+ }),
1703
+ applied && conflicts.length > 0 && /* @__PURE__ */ jsx("p", {
1704
+ className: "text-xs text-[var(--color-status-warning-text)]",
1705
+ "data-testid": "search-filter-conflicts",
1706
+ children: tr("fluidgrids.runControl.searchFilter.conflicts", "The attribute filter is not applied while these filters are set: {{filters}}. Clear them to use it.", { filters: conflicts.join(", ") })
1707
+ })
1708
+ ]
1709
+ });
1710
+ }
1711
+
1712
+ // src/modules/run-control/utils/batch.ts
1713
+ /** The operations the batch menu offers, in menu order. */
1714
+ const BATCH_MENU_OPERATIONS = [
1715
+ "SIGNAL",
1716
+ "CANCEL",
1717
+ "TERMINATE",
1718
+ "RESET",
1719
+ "PAUSE_ACTIVITY",
1720
+ "UNPAUSE_ACTIVITY",
1721
+ "RESET_ACTIVITY",
1722
+ "EXPORT"
1723
+ ];
1724
+ /**
1725
+ * Operations on durable work only. A copy of the service's
1726
+ * `DURABLE_BATCH_TYPES` (as far as the menu offers them): the service refuses
1727
+ * them with FEATURE_DISABLED unless durable functions are on besides run
1728
+ * control.
1729
+ */
1730
+ const DURABLE_BATCH_OPERATIONS = /* @__PURE__ */ new Set([
1731
+ "RESET",
1732
+ "PAUSE_ACTIVITY",
1733
+ "UNPAUSE_ACTIVITY",
1734
+ "RESET_ACTIVITY"
1735
+ ]);
1736
+ /**
1737
+ * The operations the batch menu offers: nothing without run control; the
1738
+ * durable-only ones need run control with durable functions; TERMINATE the
1739
+ * workflow-run delete permission, EXPORT run archival, the rest execute.
1740
+ */
1741
+ function allowedBatchOperations(gates, permissions) {
1742
+ const allowed = /* @__PURE__ */ new Set();
1743
+ if (!gates.runControl) return allowed;
1744
+ if (permissions.canExecute) for (const type of BATCH_MENU_OPERATIONS) {
1745
+ if (type === "TERMINATE" || type === "EXPORT") continue;
1746
+ if (DURABLE_BATCH_OPERATIONS.has(type) && !gates.runControlDurable) continue;
1747
+ allowed.add(type);
1748
+ }
1749
+ if (permissions.canDelete) allowed.add("TERMINATE");
1750
+ if (gates.runArchival) allowed.add("EXPORT");
1751
+ return allowed;
1752
+ }
1753
+ /**
1754
+ * The mutation (and so the RBAC pair) each operation goes through: TERMINATE
1755
+ * is the workflow-run delete pair, EXPORT the read pair, the rest execute.
1756
+ */
1757
+ function batchMutationOf(type) {
1758
+ if (type === "TERMINATE") return "terminate";
1759
+ if (type === "EXPORT") return "export";
1760
+ return "operation";
1761
+ }
1762
+ /** The most run ids one job takes; beyond that, a filter selects. */
1763
+ const MAX_BATCH_RUN_IDS = 1e3;
1764
+ /**
1765
+ * The runs a job selects: the chosen ids, or (for "select all matching") the
1766
+ * list's filter without paging, which the service ignores anyway.
1767
+ */
1768
+ function batchSelection(input, selection) {
1769
+ if (selection.allMatching) {
1770
+ const filter = { ...input };
1771
+ delete filter.limit;
1772
+ delete filter.offset;
1773
+ return { filter };
1774
+ }
1775
+ return { runIds: selection.runIds.slice(0, MAX_BATCH_RUN_IDS) };
1776
+ }
1777
+ /** Whether the operation needs the activity selector. */
1778
+ function needsActivitySelector(type) {
1779
+ return type === "PAUSE_ACTIVITY" || type === "UNPAUSE_ACTIVITY" || type === "RESET_ACTIVITY";
1780
+ }
1781
+ /** Progress of a job: succeeded, failed and skipped runs out of its total. */
1782
+ function batchProgress(job) {
1783
+ const done = job.succeeded + job.failed + job.skipped;
1784
+ const total = Math.max(job.total, 0);
1785
+ const finished = job.status === "COMPLETED" || job.status === "FAILED" || job.status === "CANCELLED";
1786
+ return {
1787
+ done,
1788
+ total,
1789
+ percent: total === 0 ? finished ? 100 : 0 : Math.min(100, Math.round(done / total * 100)),
1790
+ finished
1791
+ };
1792
+ }
1793
+
1794
+ // src/modules/run-control/components/BatchOperations.tsx
1795
+ /**
1796
+ * Batch operations of the Runs list (BOFF-9906, spec code-first 11 §2.19):
1797
+ * the batch menu over the selected runs (or every run matching the list's
1798
+ * filter), the dialog that dry-runs and starts a job, and the progress drawer
1799
+ * that follows it. TERMINATE goes through startBatchTerminate (the workflow-run
1800
+ * delete pair) and EXPORT through startBatchExport (the read pair); the rest
1801
+ * through startBatchOperation.
1802
+ */
1803
+ /** The drawer re-reads a running job this often (besides the subscription). */
1804
+ const BATCH_POLL_INTERVAL_MS = 3e3;
1805
+ const OPERATION_TYPES = {
1806
+ SIGNAL: "SIGNAL",
1807
+ CANCEL: "CANCEL",
1808
+ TERMINATE: "TERMINATE",
1809
+ RESET: "RESET",
1810
+ PAUSE_ACTIVITY: "PAUSE_ACTIVITY",
1811
+ UNPAUSE_ACTIVITY: "UNPAUSE_ACTIVITY",
1812
+ RESET_ACTIVITY: "RESET_ACTIVITY",
1813
+ EXPORT: "EXPORT"
1814
+ };
1815
+ function useBatchOperationLabels() {
1816
+ const tr = useTr();
1817
+ return {
1818
+ SIGNAL: tr("fluidgrids.runControl.batch.op.signal", "Signal"),
1819
+ CANCEL: tr("fluidgrids.runControl.batch.op.cancel", "Cancel"),
1820
+ TERMINATE: tr("fluidgrids.runControl.batch.op.terminate", "Terminate"),
1821
+ RESET: tr("fluidgrids.runControl.batch.op.reset", "Reset to last clean task"),
1822
+ PAUSE_ACTIVITY: tr("fluidgrids.runControl.batch.op.pauseActivity", "Pause activities"),
1823
+ UNPAUSE_ACTIVITY: tr("fluidgrids.runControl.batch.op.unpauseActivity", "Unpause activities"),
1824
+ RESET_ACTIVITY: tr("fluidgrids.runControl.batch.op.resetActivity", "Reset activity attempts"),
1825
+ EXPORT: tr("fluidgrids.runControl.batch.op.export", "Export to archive")
1826
+ };
1827
+ }
1828
+ function BatchMenu({ allowed, onPick, onShowJobs }) {
1829
+ const tr = useTr();
1830
+ const labels = useBatchOperationLabels();
1831
+ const offered = BATCH_MENU_OPERATIONS.filter((type) => allowed.has(type));
1832
+ return /* @__PURE__ */ jsxs("div", {
1833
+ className: "flex items-center gap-2",
1834
+ children: [/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1835
+ asChild: true,
1836
+ children: /* @__PURE__ */ jsxs(Button$1, {
1837
+ variant: "outline",
1838
+ size: "sm",
1839
+ disabled: offered.length === 0,
1840
+ "data-testid": "batch-menu",
1841
+ children: [
1842
+ /* @__PURE__ */ jsx(Icon, {
1843
+ icon: "mdi:layers-triple-outline",
1844
+ className: "mr-1",
1845
+ width: 14
1846
+ }),
1847
+ tr("fluidgrids.runControl.batch.menu", "Batch"),
1848
+ /* @__PURE__ */ jsx(Icon, {
1849
+ icon: "mdi:chevron-down",
1850
+ className: "ml-1",
1851
+ width: 14
1852
+ })
1853
+ ]
1854
+ })
1855
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
1856
+ style: RUN_CONTROL_DIALOG_STYLE,
1857
+ children: offered.map((type) => /* @__PURE__ */ jsx(DropdownMenuItem, {
1858
+ onSelect: () => onPick(type),
1859
+ "data-testid": `batch-op-${type}`,
1860
+ children: labels[type]
1861
+ }, type))
1862
+ })] }), /* @__PURE__ */ jsx(Button$1, {
1863
+ variant: "ghost",
1864
+ size: "sm",
1865
+ onClick: onShowJobs,
1866
+ "data-testid": "batch-jobs-button",
1867
+ children: tr("fluidgrids.runControl.batch.jobs", "Batch jobs")
1868
+ })]
1869
+ });
1870
+ }
1871
+ async function startJob(type, selection, common, payload) {
1872
+ const mutation = batchMutationOf(type);
1873
+ if (mutation === "terminate") return startBatchTerminate({
1874
+ ...selection,
1875
+ ...common
1876
+ });
1877
+ if (mutation === "export") return startBatchExport({
1878
+ ...selection,
1879
+ ...common
1880
+ });
1881
+ return startBatchOperation({
1882
+ ...selection,
1883
+ ...common,
1884
+ type: OPERATION_TYPES[type],
1885
+ payload
1886
+ });
1887
+ }
1888
+ function BatchOperationDialog({ type, input, selection, onClose, onStarted }) {
1889
+ const tr = useTr();
1890
+ const labels = useBatchOperationLabels();
1891
+ const [reason, setReason] = useState("");
1892
+ const [rate, setRate] = useState("50");
1893
+ const [signalName, setSignalName] = useState("");
1894
+ const [signalPayload, setSignalPayload] = useState("");
1895
+ const [pauseReason, setPauseReason] = useState("");
1896
+ const [dryRun, setDryRun] = useState(null);
1897
+ const [busy, setBusy] = useState(null);
1898
+ const [error, setError] = useState(null);
1899
+ const parsedPayload = parseJsonInput(signalPayload);
1900
+ const maxOperationsPerSecond = Math.min(50, Math.max(1, Math.round(Number(rate) || 0)));
1901
+ const payload = () => {
1902
+ if (type === "SIGNAL") return {
1903
+ signalName: signalName.trim(),
1904
+ signalPayload: parsedPayload.ok ? jsonArg(parsedPayload.value) : null
1905
+ };
1906
+ if (type === "RESET") return { resetTo: { type: "LAST_TASK" } };
1907
+ if (needsActivitySelector(type)) return { selector: { all: true } };
1908
+ return null;
1909
+ };
1910
+ const valid = reason.trim().length > 0 && Number.isFinite(Number(rate)) && (type !== "SIGNAL" || signalName.trim().length > 0 && parsedPayload.ok) && (type !== "PAUSE_ACTIVITY" || pauseReason.trim().length > 0);
1911
+ const jobReason = type === "PAUSE_ACTIVITY" && pauseReason.trim() ? pauseReason.trim() : reason.trim();
1912
+ const run = async (asDryRun) => {
1913
+ if (!valid) return;
1914
+ setBusy(asDryRun ? "dry" : "start");
1915
+ setError(null);
1916
+ try {
1917
+ const chosen = batchSelection(input, selection);
1918
+ const job = await startJob(type, "filter" in chosen ? { filter: chosen.filter } : { runIds: chosen.runIds }, {
1919
+ reason: jobReason,
1920
+ maxOperationsPerSecond,
1921
+ dryRun: asDryRun
1922
+ }, payload());
1923
+ if (asDryRun) setDryRun(job);
1924
+ else onStarted(job);
1925
+ } catch (err) {
1926
+ setError(describeOperationError(err));
1927
+ } finally {
1928
+ setBusy(null);
1929
+ }
1930
+ };
1931
+ return /* @__PURE__ */ jsx(Dialog, {
1932
+ open: true,
1933
+ onOpenChange: (open) => !open && onClose(),
1934
+ children: /* @__PURE__ */ jsxs(DialogContent, {
1935
+ style: RUN_CONTROL_DIALOG_STYLE,
1936
+ "data-testid": "batch-dialog",
1937
+ children: [
1938
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: labels[type] }), /* @__PURE__ */ jsx(DialogDescription, { children: selection.allMatching ? tr("fluidgrids.runControl.batch.allMatching", "Every run that matches the list's filters.") : tr("fluidgrids.runControl.batch.selected", "{{count}} selected runs.", { count: Math.min(selection.runIds.length, 1e3) }) })] }),
1939
+ /* @__PURE__ */ jsxs("div", {
1940
+ className: "space-y-3",
1941
+ children: [
1942
+ type === "SIGNAL" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1943
+ className: "space-y-1.5",
1944
+ children: [/* @__PURE__ */ jsx(Label, {
1945
+ htmlFor: "batch-signal-name",
1946
+ children: tr("fluidgrids.runControl.batch.signalName", "Signal name")
1947
+ }), /* @__PURE__ */ jsx(Input, {
1948
+ id: "batch-signal-name",
1949
+ value: signalName,
1950
+ onChange: (event) => setSignalName(event.target.value),
1951
+ "data-testid": "batch-signal-name"
1952
+ })]
1953
+ }), /* @__PURE__ */ jsx(JsonField, {
1954
+ id: "batch-signal-payload",
1955
+ label: tr("fluidgrids.runControl.batch.signalPayload", "Payload (JSON, optional)"),
1956
+ value: signalPayload,
1957
+ onChange: setSignalPayload,
1958
+ rows: 3,
1959
+ error: parsedPayload.ok ? null : parsedPayload.error
1960
+ })] }),
1961
+ type === "PAUSE_ACTIVITY" && /* @__PURE__ */ jsxs("div", {
1962
+ className: "space-y-1.5",
1963
+ children: [/* @__PURE__ */ jsx(Label, {
1964
+ htmlFor: "batch-pause-reason",
1965
+ children: tr("fluidgrids.runControl.batch.pauseReason", "Pause reason")
1966
+ }), /* @__PURE__ */ jsx(Input, {
1967
+ id: "batch-pause-reason",
1968
+ value: pauseReason,
1969
+ onChange: (event) => setPauseReason(event.target.value),
1970
+ "data-testid": "batch-pause-reason"
1971
+ })]
1972
+ }),
1973
+ needsActivitySelector(type) && /* @__PURE__ */ jsx("p", {
1974
+ className: "text-xs text-text-secondary",
1975
+ children: tr("fluidgrids.runControl.batch.allActivities", "Applies to every open activity of each run.")
1976
+ }),
1977
+ /* @__PURE__ */ jsxs("div", {
1978
+ className: "space-y-1.5",
1979
+ children: [/* @__PURE__ */ jsx(Label, {
1980
+ htmlFor: "batch-reason",
1981
+ children: tr("fluidgrids.runControl.batch.reason", "Reason")
1982
+ }), /* @__PURE__ */ jsx(Input, {
1983
+ id: "batch-reason",
1984
+ value: reason,
1985
+ maxLength: 1e3,
1986
+ onChange: (event) => setReason(event.target.value),
1987
+ "data-testid": "batch-reason"
1988
+ })]
1989
+ }),
1990
+ /* @__PURE__ */ jsxs("div", {
1991
+ className: "space-y-1.5",
1992
+ children: [/* @__PURE__ */ jsx(Label, {
1993
+ htmlFor: "batch-rate",
1994
+ children: tr("fluidgrids.runControl.batch.rate", "Runs per second (1 to 50)")
1995
+ }), /* @__PURE__ */ jsx(Input, {
1996
+ id: "batch-rate",
1997
+ inputMode: "numeric",
1998
+ value: rate,
1999
+ onChange: (event) => setRate(event.target.value),
2000
+ "data-testid": "batch-rate"
2001
+ })]
2002
+ }),
2003
+ dryRun && /* @__PURE__ */ jsx("p", {
2004
+ className: "rounded-md bg-background-primary p-2 text-sm text-text-primary",
2005
+ "data-testid": "batch-dry-run-result",
2006
+ children: tr("fluidgrids.runControl.batch.dryRunResult", "The job would select {{count}} runs.", { count: dryRun.total })
2007
+ }),
2008
+ /* @__PURE__ */ jsx(FieldError, {
2009
+ message: error,
2010
+ testId: "batch-error"
2011
+ })
2012
+ ]
2013
+ }),
2014
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
2015
+ /* @__PURE__ */ jsx(Button$1, {
2016
+ variant: "outline",
2017
+ onClick: onClose,
2018
+ disabled: busy !== null,
2019
+ children: tr("fluidgrids.runControl.common.cancel", "Cancel")
2020
+ }),
2021
+ /* @__PURE__ */ jsx(Button$1, {
2022
+ variant: "outline",
2023
+ onClick: () => void run(true),
2024
+ disabled: !valid || busy !== null,
2025
+ "data-testid": "batch-dry-run",
2026
+ children: busy === "dry" ? tr("fluidgrids.runControl.batch.counting", "Counting…") : tr("fluidgrids.runControl.batch.dryRun", "Dry run")
2027
+ }),
2028
+ /* @__PURE__ */ jsx(Button$1, {
2029
+ onClick: () => void run(false),
2030
+ disabled: !valid || busy !== null,
2031
+ "data-testid": "batch-start",
2032
+ children: busy === "start" ? tr("fluidgrids.runControl.common.working", "Working…") : tr("fluidgrids.runControl.batch.start", "Start job")
2033
+ })
2034
+ ] })
2035
+ ]
2036
+ })
2037
+ });
2038
+ }
2039
+ function statusTone(status) {
2040
+ if (status === "COMPLETED") return "success";
2041
+ if (status === "FAILED") return "error";
2042
+ if (status === "CANCELLED") return "neutral";
2043
+ return "info";
2044
+ }
2045
+ function isBatchMenuOperation(type) {
2046
+ return BATCH_MENU_OPERATIONS.some((item) => item === type);
2047
+ }
2048
+ /** Follows one job: its subscription, plus a re-read on a timer while it runs. */
2049
+ function useBatchJob(initial) {
2050
+ const [job, setJob] = useState(initial);
2051
+ const id = job?.id ?? null;
2052
+ const finished = job ? batchProgress(job).finished : true;
2053
+ useEffect(() => {
2054
+ if (!id || finished) return;
2055
+ let cancelled = false;
2056
+ const subscription = getFluidGridsClient().subscribe({
2057
+ query: WorkflowBatchOperationUpdatedDocument,
2058
+ variables: { id }
2059
+ }).subscribe({
2060
+ next: ({ data }) => {
2061
+ if (!cancelled && data?.workflowBatchOperationUpdated) setJob(data.workflowBatchOperationUpdated);
2062
+ },
2063
+ error: () => void 0
2064
+ });
2065
+ const timer = setInterval(() => {
2066
+ fetchBatchOperation(id).then((next) => {
2067
+ if (!cancelled) setJob(next);
2068
+ }).catch(() => void 0);
2069
+ }, BATCH_POLL_INTERVAL_MS);
2070
+ return () => {
2071
+ cancelled = true;
2072
+ subscription.unsubscribe();
2073
+ clearInterval(timer);
2074
+ };
2075
+ }, [id, finished]);
2076
+ return {
2077
+ job,
2078
+ setJob
2079
+ };
2080
+ }
2081
+ function BatchProgressDrawer({ job: initial, open, onOpenChange, canCancel }) {
2082
+ const tr = useTr();
2083
+ const labels = useBatchOperationLabels();
2084
+ const { job, setJob } = useBatchJob(initial);
2085
+ const [error, setError] = useState(null);
2086
+ const jobs = useResource("batch-jobs", open && !job, () => fetchBatchOperations({ first: 20 }));
2087
+ useEffect(() => setJob(initial), [initial, setJob]);
2088
+ const cancel = async () => {
2089
+ if (!job) return;
2090
+ try {
2091
+ setJob(await cancelBatchOperation(job.id));
2092
+ } catch (err) {
2093
+ setError(describeOperationError(err));
2094
+ }
2095
+ };
2096
+ const labelOf = (type) => isBatchMenuOperation(type) ? labels[type] : type;
2097
+ return /* @__PURE__ */ jsx(Sheet, {
2098
+ open,
2099
+ onOpenChange,
2100
+ children: /* @__PURE__ */ jsxs(SheetContent, {
2101
+ style: RUN_CONTROL_DIALOG_STYLE,
2102
+ className: "w-full sm:max-w-md",
2103
+ "data-testid": "batch-drawer",
2104
+ children: [
2105
+ /* @__PURE__ */ jsxs(SheetHeader, { children: [/* @__PURE__ */ jsx(SheetTitle, { children: job ? labelOf(job.type) : tr("fluidgrids.runControl.batch.jobs", "Batch jobs") }), /* @__PURE__ */ jsx(SheetDescription, { children: job ? job.reason : tr("fluidgrids.runControl.batch.jobsDescription", "The workspace's recent batch jobs.") })] }),
2106
+ job ? /* @__PURE__ */ jsx(BatchJobProgress, {
2107
+ job,
2108
+ onCancel: canCancel ? () => void cancel() : void 0
2109
+ }) : /* @__PURE__ */ jsxs("div", {
2110
+ className: "mt-4 space-y-2",
2111
+ children: [
2112
+ /* @__PURE__ */ jsx(FieldError, { message: jobs.error }),
2113
+ (jobs.data?.items ?? []).map((item) => /* @__PURE__ */ jsxs("button", {
2114
+ type: "button",
2115
+ className: "w-full rounded-md border border-border-secondary p-2 text-left hover:bg-background-tertiary",
2116
+ onClick: () => setJob(item),
2117
+ "data-testid": `batch-job-${item.id}`,
2118
+ children: [/* @__PURE__ */ jsxs("div", {
2119
+ className: "flex items-center gap-2 text-sm",
2120
+ children: [
2121
+ /* @__PURE__ */ jsx("span", {
2122
+ className: "font-medium text-text-primary",
2123
+ children: labelOf(item.type)
2124
+ }),
2125
+ /* @__PURE__ */ jsx(StatusPill, {
2126
+ tone: statusTone(item.status),
2127
+ children: item.status
2128
+ }),
2129
+ /* @__PURE__ */ jsxs("span", {
2130
+ className: "ml-auto text-xs text-text-secondary",
2131
+ children: [batchProgress(item).percent, "%"]
2132
+ })
2133
+ ]
2134
+ }), /* @__PURE__ */ jsx("p", {
2135
+ className: "truncate text-xs text-text-secondary",
2136
+ children: item.reason
2137
+ })]
2138
+ }, item.id)),
2139
+ jobs.data && jobs.data.items.length === 0 && /* @__PURE__ */ jsx("p", {
2140
+ className: "text-sm text-text-secondary",
2141
+ children: tr("fluidgrids.runControl.batch.noJobs", "No batch jobs yet.")
2142
+ })
2143
+ ]
2144
+ }),
2145
+ /* @__PURE__ */ jsx(FieldError, { message: error })
2146
+ ]
2147
+ })
2148
+ });
2149
+ }
2150
+ function BatchJobProgress({ job, onCancel }) {
2151
+ const tr = useTr();
2152
+ const progress = batchProgress(job);
2153
+ return /* @__PURE__ */ jsxs("div", {
2154
+ className: "mt-4 space-y-3",
2155
+ "data-testid": "batch-progress",
2156
+ children: [
2157
+ /* @__PURE__ */ jsxs("div", {
2158
+ className: "flex items-center gap-2",
2159
+ children: [/* @__PURE__ */ jsx(StatusPill, {
2160
+ tone: statusTone(job.status),
2161
+ testId: "batch-progress-status",
2162
+ children: job.status
2163
+ }), /* @__PURE__ */ jsxs("span", {
2164
+ className: "text-sm text-text-secondary",
2165
+ "data-testid": "batch-progress-count",
2166
+ children: [
2167
+ progress.done,
2168
+ " / ",
2169
+ progress.total
2170
+ ]
2171
+ })]
2172
+ }),
2173
+ /* @__PURE__ */ jsx("div", {
2174
+ className: "h-2 overflow-hidden rounded-full bg-background-tertiary",
2175
+ children: /* @__PURE__ */ jsx("div", {
2176
+ className: "h-full bg-[var(--color-status-info-bg)]",
2177
+ style: { width: `${progress.percent}%` }
2178
+ })
2179
+ }),
2180
+ /* @__PURE__ */ jsxs("dl", {
2181
+ className: "grid grid-cols-3 gap-2 text-center text-xs",
2182
+ children: [
2183
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
2184
+ className: "text-text-secondary",
2185
+ children: tr("fluidgrids.runControl.batch.succeeded", "Succeeded")
2186
+ }), /* @__PURE__ */ jsx("dd", {
2187
+ className: "text-base font-semibold text-text-primary",
2188
+ children: job.succeeded
2189
+ })] }),
2190
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
2191
+ className: "text-text-secondary",
2192
+ children: tr("fluidgrids.runControl.batch.failed", "Failed")
2193
+ }), /* @__PURE__ */ jsx("dd", {
2194
+ className: "text-base font-semibold text-text-primary",
2195
+ children: job.failed
2196
+ })] }),
2197
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
2198
+ className: "text-text-secondary",
2199
+ children: tr("fluidgrids.runControl.batch.skipped", "Skipped")
2200
+ }), /* @__PURE__ */ jsx("dd", {
2201
+ className: "text-base font-semibold text-text-primary",
2202
+ children: job.skipped
2203
+ })] })
2204
+ ]
2205
+ }),
2206
+ job.failureMessage && /* @__PURE__ */ jsx(FieldError, { message: job.failureMessage }),
2207
+ job.errors.length > 0 && /* @__PURE__ */ jsx("ul", {
2208
+ className: "max-h-48 space-y-1 overflow-auto text-xs",
2209
+ "data-testid": "batch-progress-errors",
2210
+ children: job.errors.map((item) => /* @__PURE__ */ jsxs("li", { children: [
2211
+ /* @__PURE__ */ jsx("code", {
2212
+ className: "font-mono",
2213
+ children: item.runId.slice(0, 12)
2214
+ }),
2215
+ " ",
2216
+ item.message
2217
+ ] }, item.runId))
2218
+ }),
2219
+ onCancel && !progress.finished && !job.cancelRequestedAt && /* @__PURE__ */ jsx(Button$1, {
2220
+ variant: "outline",
2221
+ size: "sm",
2222
+ onClick: onCancel,
2223
+ "data-testid": "batch-cancel",
2224
+ children: tr("fluidgrids.runControl.batch.cancel", "Cancel job")
2225
+ })
2226
+ ]
2227
+ });
2228
+ }
2229
+
2230
+ // src/modules/executions/components/RunsParityBar.tsx
2231
+ /**
2232
+ * Runs list additions from durable-function parity (BOFF-9905, spec
2233
+ * code-first 11 §2.1, §2.34): a workflow-id filter and the number of runs per
2234
+ * status for the current filter (`workflowRunsCount`, the same filter and
2235
+ * visibility as the list). Rendered only while fluidgrids.durable-parity is
2236
+ * on.
2237
+ */
2238
+ /** How long typing in the workflow-id box settles before it filters. */
2239
+ const WORKFLOW_ID_FILTER_DEBOUNCE_MS = 350;
2240
+ const RUN_STATUSES = [
2241
+ "PENDING",
2242
+ "RUNNING",
2243
+ "WAITING",
2244
+ "COMPLETED",
2245
+ "FAILED",
2246
+ "CANCELLED",
2247
+ "TERMINATED"
2248
+ ];
2249
+ function isRunStatus(value) {
2250
+ return RUN_STATUSES.includes(value);
2251
+ }
2252
+ function RunsParityBar({ workflowId, onWorkflowIdChange, total, counts, countsError, selectedStatus, onSelectStatus }) {
2253
+ const tr = useTr();
2254
+ const statusLabels = runStatusLabels(tr);
2255
+ const [draft, setDraft] = useState(workflowId);
2256
+ useEffect(() => {
2257
+ setDraft(workflowId);
2258
+ }, [workflowId]);
2259
+ useEffect(() => {
2260
+ if (draft === workflowId) return;
2261
+ const timer = setTimeout(() => onWorkflowIdChange(draft), 350);
2262
+ return () => clearTimeout(timer);
2263
+ }, [
2264
+ draft,
2265
+ workflowId,
2266
+ onWorkflowIdChange
2267
+ ]);
2268
+ return /* @__PURE__ */ jsxs("div", {
2269
+ "data-testid": "runs-parity-bar",
2270
+ className: "mb-6 flex flex-col gap-3 lg:flex-row lg:items-center",
2271
+ children: [/* @__PURE__ */ jsxs("label", {
2272
+ className: "flex min-w-0 items-center gap-2 text-sm text-text-secondary lg:w-80",
2273
+ children: [
2274
+ /* @__PURE__ */ jsx(Icon, {
2275
+ icon: "mdi:key-outline",
2276
+ width: 16,
2277
+ className: "shrink-0"
2278
+ }),
2279
+ /* @__PURE__ */ jsx("span", {
2280
+ className: "sr-only",
2281
+ children: tr("fluidgrids.executions.parity.workflowIdFilter", "Workflow ID")
2282
+ }),
2283
+ /* @__PURE__ */ jsx(Input, {
2284
+ value: draft,
2285
+ maxLength: 256,
2286
+ placeholder: tr("fluidgrids.executions.parity.workflowIdPlaceholder", "Filter by workflow ID"),
2287
+ onChange: (event) => setDraft(event.target.value),
2288
+ "data-testid": "runs-workflow-id-filter"
2289
+ })
2290
+ ]
2291
+ }), /* @__PURE__ */ jsx("div", {
2292
+ className: "flex flex-wrap items-center gap-2",
2293
+ "data-testid": "runs-status-counts",
2294
+ children: countsError ? /* @__PURE__ */ jsxs("span", {
2295
+ className: "text-xs text-[var(--color-status-error-text)]",
2296
+ children: [
2297
+ tr("fluidgrids.executions.parity.countsError", "Could not count the runs:"),
2298
+ " ",
2299
+ countsError
2300
+ ]
2301
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [total !== null && /* @__PURE__ */ jsx("button", {
2302
+ type: "button",
2303
+ onClick: () => onSelectStatus("all"),
2304
+ className: cn("rounded-full border px-2.5 py-1 text-xs", selectedStatus === "all" ? "border-action-primary-bg bg-action-primary-bg text-action-primary-text" : "border-border-secondary text-text-secondary hover:text-text-primary"),
2305
+ "aria-pressed": selectedStatus === "all",
2306
+ "data-testid": "runs-count-all",
2307
+ children: tr("fluidgrids.executions.parity.countAll", "All {{count}}", { count: total })
2308
+ }), counts.map(({ status, count }) => isRunStatus(status) ? /* @__PURE__ */ jsx("button", {
2309
+ type: "button",
2310
+ onClick: () => onSelectStatus(status),
2311
+ className: cn("rounded-full border px-2.5 py-1 text-xs", selectedStatus === status ? "border-action-primary-bg bg-action-primary-bg text-action-primary-text" : "border-border-secondary text-text-secondary hover:text-text-primary"),
2312
+ "aria-pressed": selectedStatus === status,
2313
+ "data-testid": `runs-count-${status}`,
2314
+ children: tr("fluidgrids.executions.parity.countStatus", "{{status}} {{count}}", {
2315
+ status: statusLabels[status],
2316
+ count
2317
+ })
2318
+ }, status) : null)] })
2319
+ })]
2320
+ });
2321
+ }
2322
+
2323
+ // src/modules/executions/pages/ExecutionsListPage.tsx
2324
+ var ExecutionsListPage_exports = /* @__PURE__ */ __exportAll({ ExecutionsListPage: () => ExecutionsListPage });
2325
+ function ExecutionsListPage() {
2326
+ const tr = useTr();
2327
+ const navigate = useNavigate();
2328
+ const { viewMode, setViewMode, setSelectedExecution, selection, clearSelection, setStatusFilter, setWorkflowFilter, setWorkflowIdFilter, updateFilter, filter, autoRefresh, autoRefreshInterval } = useExecutionsStore();
2329
+ const [searchParams] = useSearchParams();
2330
+ const workflowKeyParam = searchParams.get("workflowKey") ?? "";
2331
+ const lastWorkflowKeyParam = useRef(null);
2332
+ const linkKey = runListLinkKey(searchParams);
2333
+ const [appliedLinkKey, setAppliedLinkKey] = useState(null);
2334
+ if (appliedLinkKey !== linkKey) {
2335
+ setAppliedLinkKey(linkKey);
2336
+ const linked = runListFilterFromSearch(searchParams);
2337
+ if (linked) updateFilter({
2338
+ ...DEFAULT_EXECUTIONS_FILTER,
2339
+ ...linked,
2340
+ workflowKey: workflowKeyParam
2341
+ });
2342
+ }
2343
+ if (lastWorkflowKeyParam.current !== workflowKeyParam) {
2344
+ lastWorkflowKeyParam.current = workflowKeyParam;
2345
+ if (useExecutionsStore.getState().filter.workflowKey !== workflowKeyParam) setWorkflowFilter(workflowKeyParam);
2346
+ }
2347
+ const parityEnabled = useDurableParityEnabled();
2348
+ const phaseC = usePhaseCFlags();
2349
+ const [searchFilter, setSearchFilter] = useState(null);
2350
+ const [allMatching, setAllMatching] = useState(false);
2351
+ const [batchType, setBatchType] = useState(null);
2352
+ const [batchDrawer, setBatchDrawer] = useState(null);
2353
+ const { allowed: canExecuteRuns } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_RUN_EXECUTE);
2354
+ const { allowed: canDeleteRuns } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_RUN_DELETE);
2355
+ const { executions, total, loading, error, refetch, input } = useExecutionsWithStore({
2356
+ durableParity: parityEnabled,
2357
+ searchAttributes: phaseC.searchAttributes ? searchFilter : null
2358
+ });
2359
+ const inputKey = JSON.stringify({
2360
+ ...input,
2361
+ limit: 0,
2362
+ offset: 0
2363
+ });
2364
+ useEffect(() => setAllMatching(false), [inputKey]);
2365
+ const batchOperations = useMemo(() => allowedBatchOperations({
2366
+ runControl: phaseC.runControl,
2367
+ runControlDurable: phaseC.runControlDurable,
2368
+ runArchival: phaseC.runArchival
2369
+ }, {
2370
+ canExecute: canExecuteRuns,
2371
+ canDelete: canDeleteRuns
2372
+ }), [
2373
+ phaseC.runControl,
2374
+ phaseC.runControlDurable,
2375
+ phaseC.runArchival,
2376
+ canExecuteRuns,
2377
+ canDeleteRuns
2378
+ ]);
2379
+ const statusCounts = useRunStatusCounts(input, parityEnabled);
2380
+ const refetchCounts = statusCounts.refetch;
2381
+ const { stats, loading: statsLoading, refetch: refetchStats } = useExecutionStats();
2382
+ const { workflows } = useWorkflowDefinitions({ limit: 100 });
2383
+ const { cancelExecution, pauseExecution, resumeExecution, retryExecution, retryExecutions } = useExecutionActions();
2384
+ useAutoRefresh(useCallback(() => {
2385
+ refetch();
2386
+ refetchStats();
2387
+ refetchCounts();
2388
+ }, [
2389
+ refetch,
2390
+ refetchStats,
2391
+ refetchCounts
2392
+ ]), autoRefresh, autoRefreshInterval);
2393
+ const executionStats = useMemo(() => {
2394
+ if (!stats) return null;
2395
+ return {
2396
+ total: stats.runsCountTotal,
2397
+ running: stats.runsRunningCount,
2398
+ completed: stats.runsCompletedCount,
2399
+ failed: stats.runsFailedCount,
2400
+ cancelled: stats.runsCancelledCount ?? 0,
2401
+ pending: stats.runsPendingCount,
2402
+ waiting: stats.runsWaitingCount ?? 0,
2403
+ successRate: stats.workflowSuccessRate,
2404
+ avgDuration: stats.avgWorkflowExecutionTimeSeconds
2405
+ };
2406
+ }, [stats]);
2407
+ const workflowOptions = useMemo(() => workflows.map((w) => ({
2408
+ workflowKey: w.workflowKey,
2409
+ name: w.name
2410
+ })), [workflows]);
2411
+ const workflowNames = useMemo(() => {
2412
+ const map = {};
2413
+ for (const w of workflows) map[w.workflowKey] = w.name;
2414
+ return map;
2415
+ }, [workflows]);
2416
+ const handleRowClick = (execution) => {
2417
+ setSelectedExecution(execution);
2418
+ navigate(`../runs/${execution.id}`);
2419
+ };
2420
+ const handleRetry = async (execution) => {
2421
+ const result = await retryExecution(execution);
2422
+ if (!result.ok) {
2423
+ toast.error(tr("fluidgrids.executions.executionsList.retryFailed", "Could not start a new run"), { description: result.message });
2424
+ return;
2425
+ }
2426
+ toast.success(tr("fluidgrids.executions.executionsList.retryStarted", "New run started"));
2427
+ navigate(`../runs/${result.run.runId}`);
2428
+ };
2429
+ const handleAction = async (action, execution) => {
2430
+ switch (action) {
2431
+ case "cancel":
2432
+ await cancelExecution(execution.id);
2433
+ break;
2434
+ case "pause":
2435
+ await pauseExecution(execution.id);
2436
+ break;
2437
+ case "resume":
2438
+ await resumeExecution(execution.id);
2439
+ break;
2440
+ case "retry":
2441
+ await handleRetry(execution);
2442
+ break;
2443
+ case "view": navigate(`../runs/${execution.id}`);
2444
+ }
2445
+ refetch();
2446
+ };
2447
+ const handleFilterByStatus = (status) => {
2448
+ setStatusFilter(status);
2449
+ };
2450
+ const handleBulkRetry = async () => {
2451
+ if (selection.runIds.length === 0) return;
2452
+ const retried = await retryExecutions(selection.runIds.slice(0, 10));
2453
+ if (retried.length === 0) {
2454
+ toast.error(tr("fluidgrids.executions.executionsList.bulkRetryFailed", "Could not retry the selected runs"));
2455
+ return;
2456
+ }
2457
+ toast.success(tr("fluidgrids.executions.executionsList.bulkRetryStarted", `${retried.length} new run${retried.length === 1 ? "" : "s"} started`));
2458
+ clearSelection();
2459
+ refetch();
2460
+ };
2461
+ const handleBulkAction = async (action) => {
2462
+ if (selection.runIds.length === 0) return;
2463
+ const promises = selection.runIds.map((id) => {
2464
+ switch (action) {
2465
+ case "cancel": return cancelExecution(id);
2466
+ case "pause": return pauseExecution(id);
2467
+ case "resume": return resumeExecution(id);
2468
+ }
2469
+ });
2470
+ const results = await Promise.allSettled(promises);
2471
+ const failures = results.filter((r) => r.status === "rejected");
2472
+ if (failures.length > 0) console.error(`Bulk action partially failed: ${failures.length}/${results.length} failed`);
2473
+ clearSelection();
2474
+ refetch();
2475
+ };
2476
+ return /* @__PURE__ */ jsxs("div", {
2477
+ className: "p-4 sm:p-6 bg-background-primary min-h-screen",
2478
+ children: [
2479
+ /* @__PURE__ */ jsxs("div", {
2480
+ className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6",
2481
+ "data-tour": "fluidgrids-runs-header",
2482
+ children: [/* @__PURE__ */ jsxs("div", { children: [
2483
+ /* @__PURE__ */ jsx("h1", {
2484
+ className: "text-2xl sm:text-3xl font-bold text-text-primary",
2485
+ children: tr("fluidgrids.executions.executionsList.title", "Executions")
2486
+ }),
2487
+ /* @__PURE__ */ jsx("p", {
2488
+ className: "text-text-secondary mt-1",
2489
+ children: tr("fluidgrids.executions.executionsList.subtitle", "Monitor and manage your workflow runs")
2490
+ }),
2491
+ /* @__PURE__ */ jsx(FluidGridsDocsLink, {
2492
+ topic: "runs",
2493
+ className: "mt-1"
2494
+ })
2495
+ ] }), /* @__PURE__ */ jsxs("div", {
2496
+ className: "flex flex-wrap items-center gap-3",
2497
+ children: [/* @__PURE__ */ jsxs("div", {
2498
+ className: "flex items-center bg-background-secondary rounded-lg border border-border-secondary p-1",
2499
+ children: [
2500
+ /* @__PURE__ */ jsx("button", {
2501
+ onClick: () => setViewMode("table"),
2502
+ className: `p-2 rounded ${viewMode === "table" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2503
+ title: tr("fluidgrids.executions.executionsList.tableViewTooltip", "Table View"),
2504
+ children: /* @__PURE__ */ jsx(Icon, {
2505
+ icon: "mdi:table",
2506
+ width: 18
2507
+ })
2508
+ }),
2509
+ /* @__PURE__ */ jsx("button", {
2510
+ onClick: () => setViewMode("cards"),
2511
+ className: `p-2 rounded ${viewMode === "cards" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2512
+ title: tr("fluidgrids.executions.executionsList.cardViewTooltip", "Card View"),
2513
+ children: /* @__PURE__ */ jsx(Icon, {
2514
+ icon: "mdi:view-grid",
2515
+ width: 18
2516
+ })
2517
+ }),
2518
+ /* @__PURE__ */ jsx("button", {
2519
+ onClick: () => setViewMode("timeline"),
2520
+ className: `p-2 rounded ${viewMode === "timeline" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2521
+ title: tr("fluidgrids.executions.executionsList.timelineViewTooltip", "Timeline View"),
2522
+ children: /* @__PURE__ */ jsx(Icon, {
2523
+ icon: "mdi:timeline",
2524
+ width: 18
2525
+ })
2526
+ })
2527
+ ]
2528
+ }), /* @__PURE__ */ jsxs(Button$1, {
2529
+ variant: "outline",
2530
+ onClick: () => navigate("../list"),
2531
+ children: [/* @__PURE__ */ jsx(Icon, {
2532
+ icon: "mdi:arrow-left",
2533
+ className: "mr-2",
2534
+ width: 16
2535
+ }), tr("fluidgrids.executions.executionsList.backToWorkflows", "Back to Workflows")]
2536
+ })]
2537
+ })]
2538
+ }),
2539
+ /* @__PURE__ */ jsx("div", {
2540
+ className: "mb-6",
2541
+ "data-tour": "fluidgrids-runs-stats",
2542
+ children: /* @__PURE__ */ jsx(ExecutionStats, {
2543
+ stats: executionStats,
2544
+ loading: statsLoading,
2545
+ onFilterByStatus: handleFilterByStatus
2546
+ })
2547
+ }),
2548
+ /* @__PURE__ */ jsx("div", {
2549
+ className: "mb-6",
2550
+ "data-tour": "fluidgrids-runs-filters",
2551
+ children: /* @__PURE__ */ jsx(ExecutionFilters, {
2552
+ workflows: workflowOptions,
2553
+ onRefresh: refetch
2554
+ })
2555
+ }),
2556
+ parityEnabled && /* @__PURE__ */ jsx(RunsParityBar, {
2557
+ workflowId: filter.workflowId,
2558
+ onWorkflowIdChange: setWorkflowIdFilter,
2559
+ total: statusCounts.total,
2560
+ counts: statusCounts.counts,
2561
+ countsError: statusCounts.error,
2562
+ selectedStatus: filter.status,
2563
+ onSelectStatus: handleFilterByStatus
2564
+ }),
2565
+ phaseC.searchAttributes && /* @__PURE__ */ jsx(SearchAttributeFilterBar, {
2566
+ applied: searchFilter,
2567
+ onApply: setSearchFilter,
2568
+ conflicts: searchFilter ? searchFilterConflicts(input) : []
2569
+ }),
2570
+ phaseC.runControl && selection.runIds.length === 0 && /* @__PURE__ */ jsx("div", {
2571
+ className: "mb-4 flex justify-end",
2572
+ children: /* @__PURE__ */ jsxs(Button$1, {
2573
+ variant: "ghost",
2574
+ size: "sm",
2575
+ onClick: () => setBatchDrawer({ job: null }),
2576
+ "data-testid": "batch-jobs-button",
2577
+ children: [/* @__PURE__ */ jsx(Icon, {
2578
+ icon: "mdi:layers-triple-outline",
2579
+ className: "mr-1",
2580
+ width: 14
2581
+ }), tr("fluidgrids.runControl.batch.jobs", "Batch jobs")]
2582
+ })
2583
+ }),
2584
+ selection.runIds.length > 0 && /* @__PURE__ */ jsxs("div", {
2585
+ className: "mb-4 p-3 bg-action-primary-bg/10 rounded-lg flex flex-wrap items-center gap-3",
2586
+ children: [
2587
+ /* @__PURE__ */ jsxs("span", {
2588
+ className: "text-sm text-text-primary",
2589
+ children: [
2590
+ selection.runIds.length,
2591
+ " ",
2592
+ selection.runIds.length > 1 ? tr("fluidgrids.executions.executionsList.selectionCountPlural", "executions selected") : tr("fluidgrids.executions.executionsList.selectionCountSingular", "execution selected")
2593
+ ]
2594
+ }),
2595
+ /* @__PURE__ */ jsxs("div", {
2596
+ className: "flex gap-2",
2597
+ children: [
2598
+ /* @__PURE__ */ jsxs(Button$1, {
2599
+ "data-agent": "confirm",
2600
+ variant: "outline",
2601
+ size: "sm",
2602
+ onClick: () => void handleBulkRetry(),
2603
+ children: [/* @__PURE__ */ jsx(Icon, {
2604
+ icon: "mdi:refresh",
2605
+ className: "mr-1",
2606
+ width: 14
2607
+ }), tr("fluidgrids.executions.executionsList.bulkRetryButton", "Retry selected")]
2608
+ }),
2609
+ /* @__PURE__ */ jsxs(Button$1, {
2610
+ "data-agent": "confirm",
2611
+ variant: "outline",
2612
+ size: "sm",
2613
+ onClick: () => handleBulkAction("cancel"),
2614
+ children: [/* @__PURE__ */ jsx(Icon, {
2615
+ icon: "mdi:stop",
2616
+ className: "mr-1",
2617
+ width: 14
2618
+ }), tr("fluidgrids.executions.executionsList.bulkCancelButton", "Cancel")]
2619
+ }),
2620
+ /* @__PURE__ */ jsxs(Button$1, {
2621
+ "data-agent": "confirm",
2622
+ variant: "outline",
2623
+ size: "sm",
2624
+ onClick: () => handleBulkAction("pause"),
2625
+ children: [/* @__PURE__ */ jsx(Icon, {
2626
+ icon: "mdi:pause",
2627
+ className: "mr-1",
2628
+ width: 14
2629
+ }), tr("fluidgrids.executions.executionsList.bulkPauseButton", "Pause")]
2630
+ }),
2631
+ /* @__PURE__ */ jsxs(Button$1, {
2632
+ "data-agent": "confirm",
2633
+ variant: "outline",
2634
+ size: "sm",
2635
+ onClick: () => handleBulkAction("resume"),
2636
+ children: [/* @__PURE__ */ jsx(Icon, {
2637
+ icon: "mdi:play",
2638
+ className: "mr-1",
2639
+ width: 14
2640
+ }), tr("fluidgrids.executions.executionsList.bulkResumeButton", "Resume")]
2641
+ })
2642
+ ]
2643
+ }),
2644
+ phaseC.runControl && /* @__PURE__ */ jsxs(Fragment, { children: [selection.allSelected && total > selection.runIds.length && /* @__PURE__ */ jsx("button", {
2645
+ type: "button",
2646
+ onClick: () => setAllMatching((current) => !current),
2647
+ className: "text-sm text-action-primary-text hover:underline",
2648
+ "data-testid": "select-all-matching",
2649
+ "aria-pressed": allMatching,
2650
+ children: allMatching ? tr("fluidgrids.runControl.batch.allMatchingSelected", "All {{count}} matching runs are selected", { count: total }) : tr("fluidgrids.runControl.batch.selectAllMatching", "Select all {{count}} matching runs", { count: total })
2651
+ }), /* @__PURE__ */ jsx(BatchMenu, {
2652
+ allowed: batchOperations,
2653
+ onPick: setBatchType,
2654
+ onShowJobs: () => setBatchDrawer({ job: null })
2655
+ })] }),
2656
+ /* @__PURE__ */ jsx("button", {
2657
+ onClick: () => {
2658
+ setAllMatching(false);
2659
+ clearSelection();
2660
+ },
2661
+ className: "ml-auto text-sm text-text-secondary hover:text-text-primary",
2662
+ children: tr("fluidgrids.executions.executionsList.clearSelection", "Clear selection")
2663
+ })
2664
+ ]
2665
+ }),
2666
+ error && /* @__PURE__ */ jsxs("div", {
2667
+ className: "mb-6 p-4 bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)] rounded-lg",
2668
+ children: [
2669
+ /* @__PURE__ */ jsxs("div", {
2670
+ className: "flex items-center gap-2 text-[var(--color-status-error-text)]",
2671
+ children: [/* @__PURE__ */ jsx(Icon, {
2672
+ icon: "mdi:alert-circle",
2673
+ width: 20
2674
+ }), /* @__PURE__ */ jsx("span", {
2675
+ className: "font-medium",
2676
+ children: tr("fluidgrids.executions.executionsList.loadErrorTitle", "Error loading executions")
2677
+ })]
2678
+ }),
2679
+ /* @__PURE__ */ jsx("p", {
2680
+ className: "mt-1 text-sm text-[var(--color-status-error-text)]",
2681
+ children: error.message
2682
+ }),
2683
+ /* @__PURE__ */ jsx(Button$1, {
2684
+ variant: "outline",
2685
+ size: "sm",
2686
+ onClick: refetch,
2687
+ className: "mt-3 border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] hover:bg-[var(--color-status-error-bg-subtle)]",
2688
+ children: tr("fluidgrids.executions.executionsList.retryButton", "Retry")
2689
+ })
2690
+ ]
2691
+ }),
2692
+ viewMode === "table" && /* @__PURE__ */ jsx(ExecutionTable, {
2693
+ workflowNames,
2694
+ executions,
2695
+ loading,
2696
+ onRowClick: handleRowClick,
2697
+ onAction: handleAction
2698
+ }),
2699
+ viewMode === "cards" && /* @__PURE__ */ jsx(ExecutionCards, {
2700
+ workflowNames,
2701
+ executions,
2702
+ loading,
2703
+ onCardClick: handleRowClick,
2704
+ onAction: handleAction
2705
+ }),
2706
+ viewMode === "timeline" && /* @__PURE__ */ jsx(ExecutionTimeline, {
2707
+ executions,
2708
+ loading,
2709
+ onItemClick: handleRowClick,
2710
+ onAction: handleAction
2711
+ }),
2712
+ /* @__PURE__ */ jsx(Pagination, { totalItems: total }),
2713
+ batchType && /* @__PURE__ */ jsx(BatchOperationDialog, {
2714
+ type: batchType,
2715
+ input,
2716
+ selection: {
2717
+ runIds: selection.runIds,
2718
+ allMatching
2719
+ },
2720
+ onClose: () => setBatchType(null),
2721
+ onStarted: (job) => {
2722
+ setBatchType(null);
2723
+ setAllMatching(false);
2724
+ clearSelection();
2725
+ setBatchDrawer({ job });
2726
+ }
2727
+ }),
2728
+ batchDrawer && /* @__PURE__ */ jsx(BatchProgressDrawer, {
2729
+ job: batchDrawer.job,
2730
+ open: true,
2731
+ onOpenChange: (open) => {
2732
+ if (open) return;
2733
+ setBatchDrawer(null);
2734
+ refetch();
2735
+ },
2736
+ canCancel: canExecuteRuns
2737
+ })
2738
+ ]
2739
+ });
2740
+ }
2741
+
2742
+ export { ExecutionsListPage_exports as n, ExecutionsListPage as t };