@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/AiStudioPage-kJHSsIcj.js +774 -0
- package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
- package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
- package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
- package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
- package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
- package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
- package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
- package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
- package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
- package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
- package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
- package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
- package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
- package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
- package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
- package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
- package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
- package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
- package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
- package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
- package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
- package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
- package/dist/RunnersPage-CLCOCclc.js +467 -0
- package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CgkRlCM5.js} +19 -50
- package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
- package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
- package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
- package/dist/TemplateCard-DsC0KNH1.js +1416 -0
- package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
- package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
- package/dist/TemplatePreview-GthzCOSo.js +231 -0
- package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
- package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
- package/dist/VariablesPage-67WR_lVE.js +949 -0
- package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
- package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
- package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
- package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
- package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
- package/dist/{WorkflowShareDialog-6RRDe-_v.js → WorkflowShareDialog-B_gn968c.js} +6 -52
- package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-CA20dVIl.js} +11 -6
- package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
- package/dist/agent-form-Bfvm32j1.js +83 -0
- package/dist/{agentTools-DJ5o2ekg.js → agentTools-CkRJB7r5.js} +3 -2
- package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
- package/dist/api-BrCHB5oA.js +224 -0
- package/dist/api-C8e0iV-Q.js +55 -0
- package/dist/button-DG7y_FVJ.js +48 -0
- package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
- package/dist/client-C9XSzSXr.js +1147 -0
- package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
- package/dist/constants-B2lhWNoN.js +202 -0
- package/dist/constants-XdvUxeWK.d.ts +162 -0
- package/dist/dialog-Cjb4lB4D.js +105 -0
- package/dist/executions.d.ts +1 -1
- package/dist/executions.js +2 -1
- package/dist/feature-flags-DM5myWaD.js +296 -0
- package/dist/flags.d.ts +3 -0
- package/dist/flags.js +4 -0
- package/dist/{flow-builder-DEzT8TQu.js → flow-builder-y2Y1iG4Y.js} +70 -1731
- package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
- package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
- package/dist/history.d.ts +1 -1
- package/dist/history.js +1 -1
- package/dist/index-72XFgvbn.d.ts +132 -0
- package/dist/index-BcdtKBM-.d.ts +56 -0
- package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
- package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
- package/dist/index.d.ts +13 -9
- package/dist/index.js +50 -5300
- package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-B91kXzIy.js} +11 -8
- package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
- package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
- package/dist/nodes.d.ts +1 -1
- package/dist/nodes.js +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -1
- package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-DggPl1vY.js} +2 -2
- package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
- package/dist/run-display-BycKYSU0.js +46 -0
- package/dist/run-list-links-ByYC0OlV.js +98 -0
- package/dist/settings.d.ts +2 -1
- package/dist/settings.js +2 -1
- package/dist/shared-CMfn30xT.js +196 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -1
- package/dist/theme-provider-B3mIuz2d.js +11 -0
- package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
- package/dist/useDurableParity-CzUQMFJ3.js +415 -0
- package/dist/useEntityTags-DhnsZcTl.js +236 -0
- package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
- package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
- package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
- package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
- package/dist/utils-uaL5UVxb.js +65 -0
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +4 -2
- package/dist/workflow-host.d.ts +6 -0
- package/dist/workflow-host.js +9 -0
- package/dist/workflow-pages.d.ts +55 -0
- package/dist/workflow-pages.js +113 -0
- package/dist/workflows-list.d.ts +1 -1
- package/dist/workflows-list.js +1 -1
- package/package.json +13 -1
- package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
- package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
- package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
- package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
- package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
- 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 };
|