@burdenoff/fluidgrids-fe-sdk 2026.1110.2 → 2026.1110.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/{ExecutionDetailPage-UXQFBLF3.js → ExecutionDetailPage-2OY2NC3Z.js} +12 -13
  2. package/dist/{ExecutionsListPage-A4GCUNFR.js → ExecutionsListPage-GTR4XPQN.js} +8 -9
  3. package/dist/{WorkflowBuilder-ZLVMV2UA.js → WorkflowBuilder-7LPCDDJJ.js} +15 -16
  4. package/dist/{WorkflowsList-XPNGNR4A.js → WorkflowsList-JJ7UOHTC.js} +6 -6
  5. package/dist/{agentTools-V26FOMTS.js → agentTools-WHQ7UDDS.js} +3 -3
  6. package/dist/{chunk-OJHBCOBA.js → chunk-5NZANNKX.js} +40 -1
  7. package/dist/{chunk-5ZAOXCIQ.js → chunk-5WM655EY.js} +151 -7
  8. package/dist/{chunk-K22NDWSD.js → chunk-6HXJEIDT.js} +2 -2
  9. package/dist/{chunk-DIIGFBYT.js → chunk-6NSPCR5C.js} +1 -1
  10. package/dist/{chunk-ZQTANHKA.js → chunk-7MHCCYDJ.js} +2 -2
  11. package/dist/{chunk-DDINUPO3.js → chunk-CXBF63PL.js} +1 -1
  12. package/dist/{chunk-3F4DIVKT.js → chunk-DCMHDOZO.js} +1 -1
  13. package/dist/chunk-DELQGUVF.js +172 -0
  14. package/dist/{chunk-XLU63IUB.js → chunk-F7C57RJE.js} +6 -6
  15. package/dist/{chunk-L5MZ74DA.js → chunk-GA3DS2ZJ.js} +1 -1
  16. package/dist/{chunk-G7BT24PD.js → chunk-KLR4BZ75.js} +2 -2
  17. package/dist/{chunk-QGRVYPQC.js → chunk-KQMZ226I.js} +2 -2
  18. package/dist/{chunk-2DBAQR6F.js → chunk-LOU6RJIB.js} +2 -2
  19. package/dist/{chunk-Z6D6YL7C.js → chunk-MQ24K2L4.js} +1 -1
  20. package/dist/{chunk-OVZA4IM7.js → chunk-OIMQHAGU.js} +3 -3
  21. package/dist/{chunk-2H4XAES7.js → chunk-POCNK7FY.js} +2 -2
  22. package/dist/{chunk-OZNFRJKC.js → chunk-PXN3S2YU.js} +5 -5
  23. package/dist/{chunk-WGUHISC2.js → chunk-QOFEQEQL.js} +174 -23
  24. package/dist/chunk-QUYK5PAZ.js +338 -0
  25. package/dist/{chunk-5V7PMLQN.js → chunk-QYHRRSEC.js} +1118 -24
  26. package/dist/{chunk-ZZBZPBO5.js → chunk-S36ZLWDC.js} +2 -2
  27. package/dist/{chunk-7UFE2CHD.js → chunk-SKMWUKT5.js} +29 -103
  28. package/dist/{chunk-CBX2CXGH.js → chunk-U5P7E3PA.js} +6 -6
  29. package/dist/{chunk-A6Q4FWB6.js → chunk-UDS2FA24.js} +1 -1
  30. package/dist/{chunk-PICOXEUO.js → chunk-UDTAUZ7B.js} +4 -4
  31. package/dist/{chunk-PI4AW4T2.js → chunk-V6KLU67V.js} +20 -19
  32. package/dist/{chunk-5TGPH3CX.js → chunk-VRPNGY6W.js} +4 -4
  33. package/dist/{chunk-D4L5BN64.js → chunk-VXVLQ3LX.js} +94 -2
  34. package/dist/{chunk-X7XVRJK2.js → chunk-XSTTM74F.js} +1 -1
  35. package/dist/{chunk-I3DBTNWQ.js → chunk-XYLWH24V.js} +5 -5
  36. package/dist/executions.js +13 -14
  37. package/dist/history.js +5 -5
  38. package/dist/index.d.ts +35 -2
  39. package/dist/index.js +45 -46
  40. package/dist/{node-detail-view-2AFXRNX6.js → node-detail-view-JJ2JRFID.js} +8 -9
  41. package/dist/nodes.js +2 -2
  42. package/dist/observability.js +5 -5
  43. package/dist/quick-config-panel-BT2RHMXF.js +411 -0
  44. package/dist/settings.js +7 -8
  45. package/dist/templates.js +15 -16
  46. package/dist/{workflow-builder-DQ2VFyg8.d.ts → workflow-builder-DgZQtyWL.d.ts} +12 -0
  47. package/dist/workflow-builder.d.ts +1 -1
  48. package/dist/workflow-builder.js +16 -17
  49. package/dist/workflows-list.js +6 -6
  50. package/package.json +1 -1
  51. package/dist/chunk-3JGAI5YC.js +0 -1437
  52. package/dist/chunk-HORSE7WX.js +0 -54
  53. package/dist/chunk-WZ3EJVS6.js +0 -93
  54. package/dist/quick-config-panel-U4AELSCT.js +0 -363
@@ -1,29 +1,452 @@
1
- import { asRecord, ConditionTreeBuilder, SwitchRulesEditor, casesFromRules, isStringOptionList, optionText, coerceNumber, paramsFromOperation, resolveLoadOptionsSpec, optionListValues, parseMappingValue, optionListText } from './chunk-PI4AW4T2.js';
1
+ import { asRecord, ConditionTreeBuilder, SwitchRulesEditor, casesFromRules, isStringOptionList, optionText, coerceNumber, nodeTitle, paramsFromOperation, resolveLoadOptionsSpec, optionListValues, parseMappingValue, optionListText } from './chunk-V6KLU67V.js';
2
2
  import { CodeEditorField, normalizeCodeLanguage } from './chunk-KZCNJAMK.js';
3
3
  import { readAssertRulesSetting, rowsFromRules, normaliseAssertRules, ruleFromRow, describeAssertRuleProblem, operatorSpec, ASSERT_OPERATORS, emptyAssertRuleRow } from './chunk-JDLQ6QIB.js';
4
- import { AgentDefinitionSelectField, ConnectionSelector } from './chunk-ZZBZPBO5.js';
5
- import { isAgentNodeType } from './chunk-WZ3EJVS6.js';
6
- import { FluidGridsContext, isParamVisible, structuredParameterType, useRunGuard, formatParameterValue, parseParameterValue, useFluidGridsAnalytics, openRunQuotaUpgradePrompt } from './chunk-DDINUPO3.js';
4
+ import { AgentDefinitionSelectField, ConnectionSelector } from './chunk-S36ZLWDC.js';
5
+ import { useResource, describeSchedule, NativeSelect, StatusPill, FieldError, isAgentNodeType, triggerSchedule, pauseSchedule, unpauseSchedule, backfillSchedule } from './chunk-VXVLQ3LX.js';
6
+ import { FluidGridsContext, isParamVisible, structuredParameterType, useRunGuard, formatParameterValue, parseParameterValue, useFluidGridsAnalytics, openRunQuotaUpgradePrompt } from './chunk-CXBF63PL.js';
7
7
  import { useBuilderReadOnly } from './chunk-SDZEAIIE.js';
8
8
  import { useFlowStore } from './chunk-K2X63CR2.js';
9
9
  import { getGlobalStoreClient } from './chunk-TYI2XOSI.js';
10
10
  import { cn } from './chunk-JL7CASN5.js';
11
- import { useRunFromNodeEligibilityQuery, PreviewScheduleDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument } from './chunk-A6Q4FWB6.js';
11
+ import { useRunFromNodeEligibilityQuery, PreviewScheduleDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument } from './chunk-UDS2FA24.js';
12
12
  import { useTr } from './chunk-UULVIT25.js';
13
- import { getFluidGridsClientSafe, getFluidGridsClient } from './chunk-PDODDDHY.js';
13
+ import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-PDODDDHY.js';
14
14
  import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
15
15
  import { cn as cn$1 } from '@burdenoff/fe-libs/utils';
16
16
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
17
  import * as React from 'react';
18
- import { useContext, useId, useState, useMemo, useRef, useEffect, useCallback } from 'react';
19
- import * as PopoverPrimitive from '@radix-ui/react-popover';
18
+ import { useState, useContext, useId, useRef, useCallback, useEffect, useMemo } from 'react';
20
19
  import { Icon } from '@iconify/react';
20
+ import { Button } from '@burdenoff/fe-libs/button';
21
+ import { Label, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Card, Button as Button$1 } from '@burdenoff/fe-libs/ui';
22
+ import { toast } from 'sonner';
23
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
21
24
  import { gql } from '@apollo/client';
22
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Card, Button } from '@burdenoff/fe-libs/ui';
23
25
  import cronstrue from 'cronstrue';
24
26
  import { digest } from 'ohash';
25
27
  import { X, Plus } from 'lucide-react';
26
28
 
29
+ function TabButton({
30
+ active,
31
+ onClick,
32
+ children,
33
+ testId
34
+ }) {
35
+ return /* @__PURE__ */ jsx(
36
+ "button",
37
+ {
38
+ type: "button",
39
+ onClick,
40
+ "data-testid": testId,
41
+ className: cn$1(
42
+ "-mb-px border-b-2 py-2.5 text-sm transition-colors",
43
+ active ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
44
+ ),
45
+ children
46
+ }
47
+ );
48
+ }
49
+ var SCHEDULE_NODE_TYPE = "internal:schedule";
50
+ var OVERLAP_POLICIES = [
51
+ "SKIP" /* Skip */,
52
+ "BUFFER_ONE" /* BufferOne */,
53
+ "BUFFER_ALL" /* BufferAll */,
54
+ "CANCEL_OTHER" /* CancelOther */,
55
+ "TERMINATE_OTHER" /* TerminateOther */,
56
+ "ALLOW_ALL" /* AllowAll */
57
+ ];
58
+ var MAX_BACKFILL = 1e3;
59
+ function overlapPolicyOf(value) {
60
+ return OVERLAP_POLICIES.find((policy) => policy === value) ?? null;
61
+ }
62
+ function secondsOf(value) {
63
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0)
64
+ return value;
65
+ if (typeof value !== "string") return null;
66
+ const match = /^(\d+)\s*(s|m|h|d)?$/.exec(value.trim());
67
+ if (!match) return null;
68
+ const unit = { s: 1, m: 60, h: 3600, d: 86400 }[match[2] ?? "s"] ?? 1;
69
+ return Number(match[1]) * unit;
70
+ }
71
+ function SchedulePanel({
72
+ state,
73
+ writeField,
74
+ workflowKey,
75
+ triggerNodeId,
76
+ canExecute,
77
+ canUpdate
78
+ }) {
79
+ const tr = useTr();
80
+ const policy = overlapPolicyOf(state.overlapPolicy);
81
+ const catchup = secondsOf(state.catchupWindow);
82
+ const limited = typeof state.limitedActions === "number" ? state.limitedActions : null;
83
+ const describe = useResource(
84
+ workflowKey ? `schedule:${workflowKey}:${triggerNodeId}` : null,
85
+ !!workflowKey,
86
+ () => describeSchedule(workflowKey ?? "", triggerNodeId)
87
+ );
88
+ const [note, setNote] = useState("");
89
+ const [from, setFrom] = useState("");
90
+ const [to, setTo] = useState("");
91
+ const [busy, setBusy] = useState(null);
92
+ const [error, setError] = useState(null);
93
+ const act = async (name, run) => {
94
+ setBusy(name);
95
+ setError(null);
96
+ try {
97
+ await run();
98
+ } catch (err) {
99
+ setError(describeOperationError(err));
100
+ } finally {
101
+ setBusy(null);
102
+ }
103
+ };
104
+ const live = describe.data;
105
+ const backfillFrom = from ? new Date(from) : null;
106
+ const backfillTo = to ? new Date(to) : null;
107
+ const backfillValid = !!backfillFrom && !!backfillTo && !Number.isNaN(backfillFrom.getTime()) && !Number.isNaN(backfillTo.getTime()) && backfillFrom < backfillTo && backfillTo.getTime() <= Date.now();
108
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5 p-4", "data-testid": "schedule-panel", children: [
109
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
110
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-foreground", children: tr("fluidgrids.builder.schedule.policy", "Policy") }),
111
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
112
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-overlap", children: tr(
113
+ "fluidgrids.builder.schedule.overlap",
114
+ "When the previous run is still open"
115
+ ) }),
116
+ /* @__PURE__ */ jsxs(
117
+ NativeSelect,
118
+ {
119
+ id: "schedule-overlap",
120
+ value: policy ?? "",
121
+ disabled: !canUpdate,
122
+ onChange: (value) => writeField("overlapPolicy", overlapPolicyOf(value) ?? void 0),
123
+ testId: "schedule-overlap",
124
+ children: [
125
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
126
+ "fluidgrids.builder.schedule.overlapLegacy",
127
+ "Start every run (no policy)"
128
+ ) }),
129
+ /* @__PURE__ */ jsx("option", { value: "SKIP" /* Skip */, children: tr("fluidgrids.builder.schedule.skip", "Skip the fire") }),
130
+ /* @__PURE__ */ jsx("option", { value: "BUFFER_ONE" /* BufferOne */, children: tr(
131
+ "fluidgrids.builder.schedule.bufferOne",
132
+ "Run once after it ends"
133
+ ) }),
134
+ /* @__PURE__ */ jsx("option", { value: "BUFFER_ALL" /* BufferAll */, children: tr(
135
+ "fluidgrids.builder.schedule.bufferAll",
136
+ "Run every missed fire after it ends"
137
+ ) }),
138
+ /* @__PURE__ */ jsx("option", { value: "CANCEL_OTHER" /* CancelOther */, children: tr(
139
+ "fluidgrids.builder.schedule.cancelOther",
140
+ "Cancel it and start"
141
+ ) }),
142
+ /* @__PURE__ */ jsx("option", { value: "TERMINATE_OTHER" /* TerminateOther */, children: tr(
143
+ "fluidgrids.builder.schedule.terminateOther",
144
+ "Terminate it and start"
145
+ ) }),
146
+ /* @__PURE__ */ jsx("option", { value: "ALLOW_ALL" /* AllowAll */, children: tr("fluidgrids.builder.schedule.allowAll", "Start anyway") })
147
+ ]
148
+ }
149
+ ),
150
+ !policy && canUpdate && /* @__PURE__ */ jsx(
151
+ Button,
152
+ {
153
+ size: "sm",
154
+ variant: "link",
155
+ className: "h-auto p-0 text-xs",
156
+ onClick: () => writeField("overlapPolicy", "SKIP" /* Skip */),
157
+ "data-testid": "schedule-use-policy",
158
+ children: tr(
159
+ "fluidgrids.builder.schedule.usePolicy",
160
+ "Use a policy (skip while a run is open)"
161
+ )
162
+ }
163
+ )
164
+ ] }),
165
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
166
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
167
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-catchup", children: tr(
168
+ "fluidgrids.builder.schedule.catchup",
169
+ "Catch-up window (seconds)"
170
+ ) }),
171
+ /* @__PURE__ */ jsx(
172
+ Input,
173
+ {
174
+ id: "schedule-catchup",
175
+ inputMode: "numeric",
176
+ disabled: !canUpdate,
177
+ value: catchup === null ? "" : String(catchup),
178
+ onChange: (event) => {
179
+ const value = event.target.value.trim();
180
+ writeField(
181
+ "catchupWindow",
182
+ value === "" ? void 0 : /^\d+$/.test(value) ? Math.min(Number(value), 604800) : state.catchupWindow
183
+ );
184
+ },
185
+ "data-testid": "schedule-catchup"
186
+ }
187
+ )
188
+ ] }),
189
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
190
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-limited", children: tr(
191
+ "fluidgrids.builder.schedule.limited",
192
+ "Stop after this many runs (optional)"
193
+ ) }),
194
+ /* @__PURE__ */ jsx(
195
+ Input,
196
+ {
197
+ id: "schedule-limited",
198
+ inputMode: "numeric",
199
+ disabled: !canUpdate,
200
+ value: limited === null ? "" : String(limited),
201
+ onChange: (event) => {
202
+ const value = event.target.value.trim();
203
+ writeField(
204
+ "limitedActions",
205
+ value === "" ? void 0 : /^\d+$/.test(value) ? Number(value) : state.limitedActions
206
+ );
207
+ },
208
+ "data-testid": "schedule-limited"
209
+ }
210
+ )
211
+ ] })
212
+ ] }),
213
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-foreground", children: [
214
+ tr(
215
+ "fluidgrids.builder.schedule.pauseOnFailure",
216
+ "Pause the schedule when a run it started fails"
217
+ ),
218
+ /* @__PURE__ */ jsx(
219
+ Switch,
220
+ {
221
+ checked: state.pauseOnFailure === true,
222
+ disabled: !canUpdate,
223
+ onCheckedChange: (checked) => writeField("pauseOnFailure", checked ? true : void 0),
224
+ "data-testid": "schedule-pause-on-failure"
225
+ }
226
+ )
227
+ ] }),
228
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
229
+ "fluidgrids.builder.schedule.saveHint",
230
+ "Policy changes apply when the workflow is saved and activated."
231
+ ) })
232
+ ] }),
233
+ !workflowKey ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: tr(
234
+ "fluidgrids.builder.schedule.unsaved",
235
+ "Save the workflow to see and control its schedule."
236
+ ) }) : /* @__PURE__ */ jsxs("section", { className: "space-y-3", "data-testid": "schedule-live", children: [
237
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
238
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-foreground", children: tr("fluidgrids.builder.schedule.live", "Schedule") }),
239
+ live && /* @__PURE__ */ jsx(
240
+ StatusPill,
241
+ {
242
+ tone: live.state.paused ? "warning" : "success",
243
+ testId: "schedule-state",
244
+ children: live.state.paused ? tr("fluidgrids.builder.schedule.paused", "Paused") : tr("fluidgrids.builder.schedule.active", "Active")
245
+ }
246
+ ),
247
+ /* @__PURE__ */ jsx(
248
+ Button,
249
+ {
250
+ size: "sm",
251
+ variant: "ghost",
252
+ className: "ml-auto",
253
+ onClick: () => void describe.refetch(),
254
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", width: 14 })
255
+ }
256
+ )
257
+ ] }),
258
+ /* @__PURE__ */ jsx(
259
+ FieldError,
260
+ {
261
+ message: describe.error,
262
+ testId: "schedule-describe-error"
263
+ }
264
+ ),
265
+ live?.state.paused && live.state.note && /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground", "data-testid": "schedule-note", children: live.state.note }),
266
+ live && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
267
+ /* @__PURE__ */ jsxs("div", { children: [
268
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.schedule.next", "Next fire times") }),
269
+ /* @__PURE__ */ jsxs(
270
+ "ul",
271
+ {
272
+ className: "space-y-0.5 font-mono text-xs text-foreground",
273
+ "data-testid": "schedule-next",
274
+ children: [
275
+ live.nextFireTimes.slice(0, 5).map((time) => /* @__PURE__ */ jsx("li", { children: new Date(time).toLocaleString() }, time)),
276
+ live.nextFireTimes.length === 0 && /* @__PURE__ */ jsx("li", { children: "\u2014" })
277
+ ]
278
+ }
279
+ )
280
+ ] }),
281
+ /* @__PURE__ */ jsxs("div", { children: [
282
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.schedule.recent", "Recent actions") }),
283
+ /* @__PURE__ */ jsxs(
284
+ "ul",
285
+ {
286
+ className: "space-y-0.5 text-xs text-foreground",
287
+ "data-testid": "schedule-recent",
288
+ children: [
289
+ live.recentActions.slice(0, 5).map((tick) => /* @__PURE__ */ jsxs("li", { children: [
290
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: new Date(tick.dueAt).toLocaleString() }),
291
+ " ",
292
+ "\xB7 ",
293
+ tick.kind,
294
+ " \xB7 ",
295
+ tick.outcome
296
+ ] }, tick.id)),
297
+ live.recentActions.length === 0 && /* @__PURE__ */ jsx("li", { children: "\u2014" })
298
+ ]
299
+ }
300
+ )
301
+ ] })
302
+ ] }),
303
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
304
+ canExecute && /* @__PURE__ */ jsxs(
305
+ Button,
306
+ {
307
+ size: "sm",
308
+ variant: "outline",
309
+ disabled: busy !== null,
310
+ onClick: () => void act("trigger", async () => {
311
+ const result = await triggerSchedule({
312
+ workflowKey,
313
+ triggerNodeId
314
+ });
315
+ toast.success(
316
+ result.runId ? tr(
317
+ "fluidgrids.builder.schedule.triggered",
318
+ "Run started"
319
+ ) : tr(
320
+ "fluidgrids.builder.schedule.triggeredOutcome",
321
+ "Tick recorded: {{outcome}}",
322
+ { outcome: result.tick.outcome }
323
+ )
324
+ );
325
+ await describe.refetch();
326
+ }),
327
+ "data-testid": "schedule-trigger",
328
+ children: [
329
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 14 }),
330
+ tr("fluidgrids.builder.schedule.trigger", "Trigger now")
331
+ ]
332
+ }
333
+ ),
334
+ canUpdate && live && !live.state.paused && /* @__PURE__ */ jsxs(
335
+ Button,
336
+ {
337
+ size: "sm",
338
+ variant: "outline",
339
+ disabled: busy !== null,
340
+ onClick: () => void act("pause", async () => {
341
+ describe.set(
342
+ await pauseSchedule({ workflowKey, triggerNodeId, note })
343
+ );
344
+ setNote("");
345
+ }),
346
+ "data-testid": "schedule-pause",
347
+ children: [
348
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:pause", className: "mr-1", width: 14 }),
349
+ tr("fluidgrids.builder.schedule.pause", "Pause")
350
+ ]
351
+ }
352
+ ),
353
+ canUpdate && live?.state.paused && /* @__PURE__ */ jsxs(
354
+ Button,
355
+ {
356
+ size: "sm",
357
+ variant: "outline",
358
+ disabled: busy !== null,
359
+ onClick: () => void act(
360
+ "unpause",
361
+ async () => describe.set(
362
+ await unpauseSchedule({ workflowKey, triggerNodeId })
363
+ )
364
+ ),
365
+ "data-testid": "schedule-unpause",
366
+ children: [
367
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play-pause", className: "mr-1", width: 14 }),
368
+ tr("fluidgrids.builder.schedule.unpause", "Unpause")
369
+ ]
370
+ }
371
+ )
372
+ ] }),
373
+ canUpdate && live && !live.state.paused && /* @__PURE__ */ jsx(
374
+ Input,
375
+ {
376
+ value: note,
377
+ maxLength: 1e3,
378
+ placeholder: tr(
379
+ "fluidgrids.builder.schedule.notePlaceholder",
380
+ "Note for the pause (optional)"
381
+ ),
382
+ onChange: (event) => setNote(event.target.value),
383
+ "data-testid": "schedule-pause-note"
384
+ }
385
+ ),
386
+ canExecute && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border p-3", children: [
387
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.schedule.backfill", "Backfill") }),
388
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
389
+ /* @__PURE__ */ jsx(
390
+ Input,
391
+ {
392
+ type: "datetime-local",
393
+ value: from,
394
+ onChange: (event) => setFrom(event.target.value),
395
+ "aria-label": tr("fluidgrids.builder.schedule.from", "From"),
396
+ "data-testid": "schedule-backfill-from"
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx(
400
+ Input,
401
+ {
402
+ type: "datetime-local",
403
+ value: to,
404
+ onChange: (event) => setTo(event.target.value),
405
+ "aria-label": tr("fluidgrids.builder.schedule.to", "To"),
406
+ "data-testid": "schedule-backfill-to"
407
+ }
408
+ )
409
+ ] }),
410
+ /* @__PURE__ */ jsx(
411
+ Button,
412
+ {
413
+ size: "sm",
414
+ variant: "outline",
415
+ disabled: !backfillValid || busy !== null,
416
+ onClick: () => void act("backfill", async () => {
417
+ if (!backfillFrom || !backfillTo) return;
418
+ const result = await backfillSchedule({
419
+ workflowKey,
420
+ triggerNodeId,
421
+ from: backfillFrom.toISOString(),
422
+ to: backfillTo.toISOString()
423
+ });
424
+ toast.success(
425
+ tr(
426
+ "fluidgrids.builder.schedule.backfilled",
427
+ "{{queued}} of {{fireTimes}} fire times queued",
428
+ { queued: result.queued, fireTimes: result.fireTimes }
429
+ )
430
+ );
431
+ await describe.refetch();
432
+ }),
433
+ "data-testid": "schedule-backfill",
434
+ children: tr(
435
+ "fluidgrids.builder.schedule.runBackfill",
436
+ "Run the fire times in this range"
437
+ )
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
441
+ "fluidgrids.builder.schedule.backfillHint",
442
+ "At most {{max}} past fire times; each started run counts toward the monthly runs.",
443
+ { max: MAX_BACKFILL }
444
+ ) })
445
+ ] }),
446
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "schedule-error" })
447
+ ] })
448
+ ] });
449
+ }
27
450
  function ReadOnlyFieldset({
28
451
  className,
29
452
  children
@@ -4476,7 +4899,7 @@ function ChannelAuthButton({
4476
4899
  const tr = useTr();
4477
4900
  const providerKey = PLATFORM_TO_PROVIDER_KEY[channel.platform] || channel.platform.toLowerCase();
4478
4901
  return /* @__PURE__ */ jsxs(
4479
- Button,
4902
+ Button$1,
4480
4903
  {
4481
4904
  type: "button",
4482
4905
  variant: "outline",
@@ -4562,7 +4985,7 @@ function ChannelLinkAction({
4562
4985
  const linking = state.kind === "linking";
4563
4986
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4564
4987
  /* @__PURE__ */ jsxs(
4565
- Button,
4988
+ Button$1,
4566
4989
  {
4567
4990
  type: "button",
4568
4991
  variant: "outline",
@@ -4884,7 +5307,7 @@ function NodeParamsForm({
4884
5307
  const highlight = dragField === p.name;
4885
5308
  const controlId = `${formId}-param-${p.name}`;
4886
5309
  const nativeControl = mode !== "expression" && !isCodeParam && !(structuredType && !optionList) && !p.loadOptions && !isChannelPicker && p.type !== PLATFORM_EVENT_MODULE_FIELD && p.type !== PLATFORM_EVENT_RECORD_TYPE_FIELD && p.type !== TIMECAMPUS_TIME_TRIGGER_FIELD;
4887
- const inputCx = cn$1(
5310
+ const inputCx2 = cn$1(
4888
5311
  "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
4889
5312
  highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
4890
5313
  );
@@ -4982,7 +5405,7 @@ function NodeParamsForm({
4982
5405
  schema: p.schema ?? p.type,
4983
5406
  value: fixedValue,
4984
5407
  onChange: (value) => writeFixed(p.name, value),
4985
- className: cn$1(inputCx, "font-mono"),
5408
+ className: cn$1(inputCx2, "font-mono"),
4986
5409
  testId: `${testIdPrefix}-param-${p.name}`
4987
5410
  },
4988
5411
  `${config.node?.id}:${config.currentOp?.name}:${p.name}`
@@ -5013,7 +5436,7 @@ function NodeParamsForm({
5013
5436
  connectionId: loadOptionsConnectionId,
5014
5437
  value: fixedValue,
5015
5438
  onChange: (v) => writeFixed(p.name, v),
5016
- className: inputCx,
5439
+ className: inputCx2,
5017
5440
  testId: `${testIdPrefix}-param-${p.name}`
5018
5441
  }
5019
5442
  ) : isChannelPicker ? /* @__PURE__ */ jsx(
@@ -5030,7 +5453,7 @@ function NodeParamsForm({
5030
5453
  ),
5031
5454
  nodeId: config.node?.id,
5032
5455
  label: p.label ?? p.name,
5033
- className: inputCx,
5456
+ className: inputCx2,
5034
5457
  testId: `${testIdPrefix}-param-${p.name}`
5035
5458
  }
5036
5459
  ) : p.type === "boolean" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
@@ -5089,7 +5512,7 @@ function NodeParamsForm({
5089
5512
  if (typeField && previous !== next)
5090
5513
  writeFixed(typeField.name, void 0);
5091
5514
  },
5092
- className: inputCx,
5515
+ className: inputCx2,
5093
5516
  testId: `${testIdPrefix}-param-${p.name}`
5094
5517
  }
5095
5518
  ) : p.type === PLATFORM_EVENT_RECORD_TYPE_FIELD ? /* @__PURE__ */ jsx(
@@ -5107,7 +5530,7 @@ function NodeParamsForm({
5107
5530
  required: p.required,
5108
5531
  label: p.label,
5109
5532
  onChange: (next) => writeFixed(p.name, next || void 0),
5110
- className: inputCx,
5533
+ className: inputCx2,
5111
5534
  testId: `${testIdPrefix}-param-${p.name}`
5112
5535
  }
5113
5536
  ) : p.type === TIMECAMPUS_TIME_TRIGGER_FIELD ? /* @__PURE__ */ jsx(
@@ -5116,7 +5539,7 @@ function NodeParamsForm({
5116
5539
  value: fixedValue,
5117
5540
  label: p.label,
5118
5541
  onChange: (next) => writeFixed(p.name, next || void 0),
5119
- className: inputCx,
5542
+ className: inputCx2,
5120
5543
  testId: `${testIdPrefix}-param-${p.name}`
5121
5544
  }
5122
5545
  ) : p.type === "timezone" ? /* @__PURE__ */ jsx(
@@ -5124,7 +5547,7 @@ function NodeParamsForm({
5124
5547
  {
5125
5548
  value: typeof fixedValue === "string" ? fixedValue : "",
5126
5549
  onChange: (zone) => writeFixed(p.name, zone),
5127
- className: inputCx,
5550
+ className: inputCx2,
5128
5551
  testId: `${testIdPrefix}-param-${p.name}`
5129
5552
  }
5130
5553
  ) : p.type === "multiOptions" && p.options && p.options.length > 0 ? /* @__PURE__ */ jsx(
@@ -5179,7 +5602,7 @@ function NodeParamsForm({
5179
5602
  id: controlId,
5180
5603
  "data-testid": `${testIdPrefix}-param-${p.name}`,
5181
5604
  className: cn$1(
5182
- inputCx,
5605
+ inputCx2,
5183
5606
  "scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary"
5184
5607
  ),
5185
5608
  children: [
@@ -5204,7 +5627,7 @@ function NodeParamsForm({
5204
5627
  onChange: (e) => writeFixed(p.name, coerceNumber(p.type, e.target.value)),
5205
5628
  id: controlId,
5206
5629
  "data-testid": `${testIdPrefix}-param-${p.name}`,
5207
- className: inputCx
5630
+ className: inputCx2
5208
5631
  }
5209
5632
  ) : p.type === "string" && p.isMultiline && !p.secret ? /* @__PURE__ */ jsx(
5210
5633
  "textarea",
@@ -5219,7 +5642,7 @@ function NodeParamsForm({
5219
5642
  "aria-label": p.label ?? p.name,
5220
5643
  id: controlId,
5221
5644
  "data-testid": `${testIdPrefix}-param-${p.name}`,
5222
- className: cn$1(inputCx, "min-h-24 resize-y")
5645
+ className: cn$1(inputCx2, "min-h-24 resize-y")
5223
5646
  }
5224
5647
  ) : /* @__PURE__ */ jsx(
5225
5648
  "input",
@@ -5236,7 +5659,7 @@ function NodeParamsForm({
5236
5659
  "aria-label": p.label ?? p.name,
5237
5660
  id: controlId,
5238
5661
  "data-testid": `${testIdPrefix}-param-${p.name}`,
5239
- className: inputCx
5662
+ className: inputCx2
5240
5663
  }
5241
5664
  ),
5242
5665
  p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
@@ -5247,6 +5670,677 @@ function NodeParamsForm({
5247
5670
  })
5248
5671
  ] });
5249
5672
  }
5673
+ var RUNNER_FIELDS = gql`
5674
+ fragment SandboxRunnerFields on SandboxRunner {
5675
+ id
5676
+ name
5677
+ status
5678
+ hostname
5679
+ os
5680
+ arch
5681
+ driver
5682
+ version
5683
+ capacity
5684
+ privateNetwork {
5685
+ target
5686
+ ports
5687
+ nodes
5688
+ }
5689
+ tokenPrefix
5690
+ lastSeenAt
5691
+ connectedAt
5692
+ createdBy
5693
+ createdAt
5694
+ }
5695
+ `;
5696
+ var LIST_RUNNERS = gql`
5697
+ ${RUNNER_FIELDS}
5698
+ query SandboxRunners {
5699
+ sandboxRunners(context: {}) {
5700
+ ...SandboxRunnerFields
5701
+ }
5702
+ }
5703
+ `;
5704
+ var CREATE_RUNNER = gql`
5705
+ ${RUNNER_FIELDS}
5706
+ mutation CreateSandboxRunner($name: String!) {
5707
+ createSandboxRunner(input: { name: $name, context: {} }) {
5708
+ token
5709
+ bridgeUrl
5710
+ runner {
5711
+ ...SandboxRunnerFields
5712
+ }
5713
+ }
5714
+ }
5715
+ `;
5716
+ var ROTATE_RUNNER_TOKEN = gql`
5717
+ ${RUNNER_FIELDS}
5718
+ mutation RotateSandboxRunnerToken($id: ID!) {
5719
+ rotateSandboxRunnerToken(id: $id, context: {}) {
5720
+ token
5721
+ bridgeUrl
5722
+ runner {
5723
+ ...SandboxRunnerFields
5724
+ }
5725
+ }
5726
+ }
5727
+ `;
5728
+ var DELETE_RUNNER = gql`
5729
+ mutation DeleteSandboxRunner($id: ID!) {
5730
+ deleteSandboxRunner(id: $id, context: {})
5731
+ }
5732
+ `;
5733
+ var RUNNER_INSTALL_SCRIPT_URL = "https://www.fluidgrids.ai/downloads/runner/install.sh";
5734
+ function runnerInstallCommand(bridgeUrl) {
5735
+ const base = `curl -fsSL ${RUNNER_INSTALL_SCRIPT_URL} | sh`;
5736
+ return bridgeUrl && bridgeUrl !== DEFAULT_BRIDGE_URL ? `${base} -s -- --url ${bridgeUrl}` : base;
5737
+ }
5738
+ var DEFAULT_BRIDGE_URL = "wss://graphqlworkspaces.burdenoff.com/runner-bridge";
5739
+ function useRunners(options) {
5740
+ const [runners, setRunners] = useState([]);
5741
+ const [loading, setLoading] = useState(!options?.skip);
5742
+ const [error, setError] = useState(null);
5743
+ const mounted = useRef(true);
5744
+ const fetchRunners = useCallback(async () => {
5745
+ if (options?.skip) return;
5746
+ try {
5747
+ const result = await getFluidGridsClient().query({ query: LIST_RUNNERS, fetchPolicy: "network-only" });
5748
+ if (!mounted.current) return;
5749
+ setRunners(result.data?.sandboxRunners ?? []);
5750
+ setError(null);
5751
+ } catch (err) {
5752
+ if (mounted.current) setError(err);
5753
+ } finally {
5754
+ if (mounted.current) setLoading(false);
5755
+ }
5756
+ }, [options?.skip]);
5757
+ useEffect(() => {
5758
+ mounted.current = true;
5759
+ void fetchRunners();
5760
+ const pollMs = options?.pollMs ?? 1e4;
5761
+ const timer = pollMs > 0 ? setInterval(fetchRunners, pollMs) : null;
5762
+ return () => {
5763
+ mounted.current = false;
5764
+ if (timer) clearInterval(timer);
5765
+ };
5766
+ }, [fetchRunners, options?.pollMs]);
5767
+ return { runners, loading, error, refetch: fetchRunners };
5768
+ }
5769
+ function useRunnerActions() {
5770
+ const [busy, setBusy] = useState(false);
5771
+ const run = useCallback(async (fn) => {
5772
+ setBusy(true);
5773
+ try {
5774
+ return await fn();
5775
+ } finally {
5776
+ setBusy(false);
5777
+ }
5778
+ }, []);
5779
+ const createRunner = useCallback(
5780
+ (name) => run(async () => {
5781
+ const result = throwIfResultError(
5782
+ await getFluidGridsClient().mutate({ mutation: CREATE_RUNNER, variables: { name } })
5783
+ );
5784
+ if (!result.data?.createSandboxRunner)
5785
+ throw new Error("No runner returned");
5786
+ return result.data.createSandboxRunner;
5787
+ }),
5788
+ [run]
5789
+ );
5790
+ const rotateToken = useCallback(
5791
+ (id) => run(async () => {
5792
+ const result = throwIfResultError(
5793
+ await getFluidGridsClient().mutate({ mutation: ROTATE_RUNNER_TOKEN, variables: { id } })
5794
+ );
5795
+ if (!result.data?.rotateSandboxRunnerToken)
5796
+ throw new Error("No runner returned");
5797
+ return result.data.rotateSandboxRunnerToken;
5798
+ }),
5799
+ [run]
5800
+ );
5801
+ const deleteRunner = useCallback(
5802
+ (id) => run(async () => {
5803
+ const result = throwIfResultError(
5804
+ await getFluidGridsClient().mutate({
5805
+ mutation: DELETE_RUNNER,
5806
+ variables: { id }
5807
+ })
5808
+ );
5809
+ return result.data?.deleteSandboxRunner === true;
5810
+ }),
5811
+ [run]
5812
+ );
5813
+ return { createRunner, rotateToken, deleteRunner, busy };
5814
+ }
5815
+ var STYLES = {
5816
+ ONLINE: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
5817
+ OFFLINE: "border-border-default bg-bg-sunken text-text-muted",
5818
+ NEVER_CONNECTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
5819
+ };
5820
+ function RunnerStatusPill({
5821
+ status,
5822
+ className
5823
+ }) {
5824
+ const tr = useTr();
5825
+ const label = status === "ONLINE" ? tr("fluidgrids.runners.online", "Online") : status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet");
5826
+ return /* @__PURE__ */ jsx(
5827
+ "span",
5828
+ {
5829
+ className: cn$1(
5830
+ "inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium",
5831
+ STYLES[status],
5832
+ className
5833
+ ),
5834
+ "data-testid": "runner-status",
5835
+ "data-status": status,
5836
+ children: label
5837
+ }
5838
+ );
5839
+ }
5840
+ var CLOUD = "__fluidgrids_cloud__";
5841
+ function RunOnSection({
5842
+ value,
5843
+ onChange,
5844
+ sectionLabelClassName
5845
+ }) {
5846
+ const tr = useTr();
5847
+ const { runners, loading } = useRunners({ pollMs: 15e3 });
5848
+ const selected = value ? runners.find((r) => r.id === value.runnerId) : void 0;
5849
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "data-testid": "ndv-settings-run-on", children: [
5850
+ /* @__PURE__ */ jsx("div", { className: sectionLabelClassName, children: tr("fluidgrids.builder.nodeSettings.runOn", "Run on") }),
5851
+ /* @__PURE__ */ jsxs(
5852
+ Select,
5853
+ {
5854
+ value: value?.runnerId ?? CLOUD,
5855
+ onValueChange: (v) => onChange(v === CLOUD ? null : { kind: "runner", runnerId: v }),
5856
+ children: [
5857
+ /* @__PURE__ */ jsx(
5858
+ SelectTrigger,
5859
+ {
5860
+ className: "w-full",
5861
+ "data-testid": "ndv-settings-run-on-select",
5862
+ children: /* @__PURE__ */ jsx(SelectValue, {})
5863
+ }
5864
+ ),
5865
+ /* @__PURE__ */ jsxs(
5866
+ SelectContent,
5867
+ {
5868
+ style: { zIndex: 1100 },
5869
+ "data-testid": "ndv-settings-run-on-options",
5870
+ children: [
5871
+ /* @__PURE__ */ jsx(SelectItem, { value: CLOUD, "data-testid": "run-on-cloud", children: tr(
5872
+ "fluidgrids.builder.nodeSettings.runOnCloud",
5873
+ "FluidGrids Cloud (default)"
5874
+ ) }),
5875
+ runners.map((runner) => /* @__PURE__ */ jsxs(
5876
+ SelectItem,
5877
+ {
5878
+ value: runner.id,
5879
+ "data-testid": "run-on-runner-option",
5880
+ children: [
5881
+ runner.name,
5882
+ runner.status !== "ONLINE" ? ` (${runner.status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet")})` : ""
5883
+ ]
5884
+ },
5885
+ runner.id
5886
+ )),
5887
+ value && !loading && !selected && /* @__PURE__ */ jsx(SelectItem, { value: value.runnerId, disabled: true, children: tr(
5888
+ "fluidgrids.builder.nodeSettings.runOnMissing",
5889
+ "Removed runner"
5890
+ ) })
5891
+ ]
5892
+ }
5893
+ )
5894
+ ]
5895
+ }
5896
+ ),
5897
+ value ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs text-text-secondary", children: [
5898
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
5899
+ selected && /* @__PURE__ */ jsx(RunnerStatusPill, { status: selected.status }),
5900
+ /* @__PURE__ */ jsx("span", { children: tr(
5901
+ "fluidgrids.builder.nodeSettings.runOnRunnerHint",
5902
+ "This node runs on your runner, in a local sandbox. If the runner is offline the node fails \u2014 it never falls back to FluidGrids Cloud. The node's input and the credentials it uses are sent to that machine."
5903
+ ) })
5904
+ ] }),
5905
+ selected && /* @__PURE__ */ jsx("p", { "data-testid": "run-on-private-network", children: (selected.privateNetwork ?? []).length === 0 ? tr(
5906
+ "fluidgrids.builder.nodeSettings.runOnPublicOnly",
5907
+ "On this runner, nodes reach the public internet only. Private destinations are opened on the runner machine (fluidgrids-runner network allow)."
5908
+ ) : tr(
5909
+ "fluidgrids.builder.nodeSettings.runOnPrivateNetwork",
5910
+ "This runner opens {{count}} private destination(s) \u2014 see Runners for which nodes may use them.",
5911
+ { count: (selected.privateNetwork ?? []).length }
5912
+ ) })
5913
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: runners.length === 0 && !loading ? tr(
5914
+ "fluidgrids.builder.nodeSettings.runOnNoRunners",
5915
+ "Runs in FluidGrids Cloud. Add a runner under Workflows \u2192 Runners to run nodes on your own machine."
5916
+ ) : tr(
5917
+ "fluidgrids.builder.nodeSettings.runOnCloudHint",
5918
+ "Runs in FluidGrids Cloud."
5919
+ ) })
5920
+ ] });
5921
+ }
5922
+ var SECTION_LABEL4 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
5923
+ var inputCx = "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus:border-primary disabled:opacity-50";
5924
+ function msToSeconds(ms, fallbackMs) {
5925
+ return Math.round((ms ?? fallbackMs) / 1e3 * 10) / 10;
5926
+ }
5927
+ function secondsInputToMs(raw, minMs) {
5928
+ const seconds = parseFloat(raw);
5929
+ if (Number.isNaN(seconds)) return minMs;
5930
+ return Math.max(minMs, Math.round(seconds * 1e3));
5931
+ }
5932
+ function NodeSettingsPanel({
5933
+ config
5934
+ }) {
5935
+ const tr = useTr();
5936
+ const {
5937
+ nodes,
5938
+ node,
5939
+ errorConfig,
5940
+ writeErrorConfig,
5941
+ notes,
5942
+ writeNotes,
5943
+ alwaysOutputData,
5944
+ executeOnce,
5945
+ runOn,
5946
+ writeExecutionSettings
5947
+ } = config;
5948
+ const onError = errorConfig?.onError ?? "stop";
5949
+ const retryEnabled = (errorConfig?.retryConfig?.maxRetries ?? 0) > 0;
5950
+ const timeoutEnabled = !!errorConfig?.timeout?.duration;
5951
+ const branchTargets = nodes.filter((n) => n.id !== node?.id);
5952
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": "ndv-settings-panel", children: [
5953
+ /* @__PURE__ */ jsx(
5954
+ RunOnSection,
5955
+ {
5956
+ value: runOn,
5957
+ onChange: (next) => writeExecutionSettings({ runOn: next }),
5958
+ sectionLabelClassName: SECTION_LABEL4
5959
+ }
5960
+ ),
5961
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
5962
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL4, children: tr("fluidgrids.builder.nodeSettings.retrySection", "Retry on Fail") }),
5963
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
5964
+ /* @__PURE__ */ jsx(
5965
+ "input",
5966
+ {
5967
+ type: "checkbox",
5968
+ checked: retryEnabled,
5969
+ onChange: (e) => writeErrorConfig({
5970
+ retryConfig: e.target.checked ? {
5971
+ maxRetries: errorConfig?.retryConfig?.maxRetries || 3,
5972
+ retryDelay: errorConfig?.retryConfig?.retryDelay || 1e3,
5973
+ backoffMultiplier: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
5974
+ maxDelay: errorConfig?.retryConfig?.maxDelay ?? 3e4
5975
+ } : void 0
5976
+ }),
5977
+ "data-testid": "ndv-settings-retry-enabled",
5978
+ className: "size-4"
5979
+ }
5980
+ ),
5981
+ /* @__PURE__ */ jsx("span", { children: tr(
5982
+ "fluidgrids.builder.nodeSettings.retryEnabledLabel",
5983
+ "Retry this node on failure"
5984
+ ) })
5985
+ ] }),
5986
+ retryEnabled && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pl-6", children: [
5987
+ /* @__PURE__ */ jsx(
5988
+ Field,
5989
+ {
5990
+ label: tr(
5991
+ "fluidgrids.builder.nodeSettings.maxRetries",
5992
+ "Max tries"
5993
+ ),
5994
+ children: /* @__PURE__ */ jsx(
5995
+ "input",
5996
+ {
5997
+ type: "number",
5998
+ min: 1,
5999
+ max: 20,
6000
+ value: errorConfig?.retryConfig?.maxRetries ?? 3,
6001
+ onChange: (e) => writeErrorConfig({
6002
+ retryConfig: {
6003
+ ...errorConfig?.retryConfig ?? {
6004
+ retryDelay: 1e3,
6005
+ maxRetries: 3
6006
+ },
6007
+ maxRetries: Math.max(
6008
+ 1,
6009
+ parseInt(e.target.value, 10) || 1
6010
+ )
6011
+ }
6012
+ }),
6013
+ "data-testid": "ndv-settings-max-retries",
6014
+ className: inputCx
6015
+ }
6016
+ )
6017
+ }
6018
+ ),
6019
+ /* @__PURE__ */ jsx(
6020
+ Field,
6021
+ {
6022
+ label: tr(
6023
+ "fluidgrids.builder.nodeSettings.retryDelay",
6024
+ "Wait (seconds)"
6025
+ ),
6026
+ children: /* @__PURE__ */ jsx(
6027
+ "input",
6028
+ {
6029
+ type: "number",
6030
+ min: 0,
6031
+ step: 0.1,
6032
+ value: msToSeconds(errorConfig?.retryConfig?.retryDelay, 1e3),
6033
+ onChange: (e) => writeErrorConfig({
6034
+ retryConfig: {
6035
+ ...errorConfig?.retryConfig ?? {
6036
+ maxRetries: 3,
6037
+ retryDelay: 1e3
6038
+ },
6039
+ retryDelay: secondsInputToMs(e.target.value, 0)
6040
+ }
6041
+ }),
6042
+ "data-testid": "ndv-settings-retry-delay",
6043
+ className: inputCx
6044
+ }
6045
+ )
6046
+ }
6047
+ ),
6048
+ /* @__PURE__ */ jsx(
6049
+ Field,
6050
+ {
6051
+ label: tr(
6052
+ "fluidgrids.builder.nodeSettings.backoffMultiplier",
6053
+ "Backoff \xD7"
6054
+ ),
6055
+ children: /* @__PURE__ */ jsx(
6056
+ "input",
6057
+ {
6058
+ type: "number",
6059
+ min: 1,
6060
+ step: 0.5,
6061
+ value: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
6062
+ onChange: (e) => writeErrorConfig({
6063
+ retryConfig: {
6064
+ ...errorConfig?.retryConfig ?? {
6065
+ maxRetries: 3,
6066
+ retryDelay: 1e3
6067
+ },
6068
+ backoffMultiplier: parseFloat(e.target.value) || 1
6069
+ }
6070
+ }),
6071
+ "data-testid": "ndv-settings-backoff-multiplier",
6072
+ className: inputCx
6073
+ }
6074
+ )
6075
+ }
6076
+ ),
6077
+ /* @__PURE__ */ jsx(
6078
+ Field,
6079
+ {
6080
+ label: tr(
6081
+ "fluidgrids.builder.nodeSettings.maxDelay",
6082
+ "Max wait (seconds)"
6083
+ ),
6084
+ children: /* @__PURE__ */ jsx(
6085
+ "input",
6086
+ {
6087
+ type: "number",
6088
+ min: 0,
6089
+ step: 1,
6090
+ value: msToSeconds(errorConfig?.retryConfig?.maxDelay, 3e4),
6091
+ onChange: (e) => writeErrorConfig({
6092
+ retryConfig: {
6093
+ ...errorConfig?.retryConfig ?? {
6094
+ maxRetries: 3,
6095
+ retryDelay: 1e3
6096
+ },
6097
+ maxDelay: secondsInputToMs(e.target.value, 0)
6098
+ }
6099
+ }),
6100
+ "data-testid": "ndv-settings-max-delay",
6101
+ className: inputCx
6102
+ }
6103
+ )
6104
+ }
6105
+ )
6106
+ ] })
6107
+ ] }),
6108
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
6109
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL4, children: tr("fluidgrids.builder.nodeSettings.onErrorSection", "On Error") }),
6110
+ /* @__PURE__ */ jsxs(
6111
+ "select",
6112
+ {
6113
+ value: onError,
6114
+ onChange: (e) => writeErrorConfig({
6115
+ onError: e.target.value,
6116
+ errorBranchId: e.target.value === "branch" ? errorConfig?.errorBranchId : void 0
6117
+ }),
6118
+ "data-testid": "ndv-settings-on-error",
6119
+ className: cn$1(inputCx, "max-w-xs"),
6120
+ children: [
6121
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
6122
+ "fluidgrids.builder.nodeSettings.onErrorStop",
6123
+ "Stop workflow (default)"
6124
+ ) }),
6125
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
6126
+ "fluidgrids.builder.nodeSettings.onErrorContinue",
6127
+ "Continue on fail"
6128
+ ) }),
6129
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
6130
+ "fluidgrids.builder.nodeSettings.onErrorBranch",
6131
+ "Route to error branch"
6132
+ ) })
6133
+ ]
6134
+ }
6135
+ ),
6136
+ onError === "branch" && /* @__PURE__ */ jsx("div", { className: "pl-2", children: /* @__PURE__ */ jsx(
6137
+ Field,
6138
+ {
6139
+ label: tr(
6140
+ "fluidgrids.builder.nodeSettings.errorBranchTarget",
6141
+ "Error branch target"
6142
+ ),
6143
+ children: /* @__PURE__ */ jsxs(
6144
+ "select",
6145
+ {
6146
+ value: errorConfig?.errorBranchId ?? "",
6147
+ onChange: (e) => writeErrorConfig({
6148
+ errorBranchId: e.target.value || void 0
6149
+ }),
6150
+ "data-testid": "ndv-settings-error-branch-id",
6151
+ className: cn$1(inputCx, "max-w-xs"),
6152
+ children: [
6153
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
6154
+ "fluidgrids.builder.nodeSettings.selectNode",
6155
+ "Select a node\u2026"
6156
+ ) }),
6157
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
6158
+ ]
6159
+ }
6160
+ )
6161
+ }
6162
+ ) })
6163
+ ] }),
6164
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
6165
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL4, children: tr("fluidgrids.builder.nodeSettings.timeoutSection", "Timeout") }),
6166
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
6167
+ /* @__PURE__ */ jsx(
6168
+ "input",
6169
+ {
6170
+ type: "checkbox",
6171
+ checked: timeoutEnabled,
6172
+ onChange: (e) => writeErrorConfig({
6173
+ timeout: e.target.checked ? { duration: errorConfig?.timeout?.duration || 3e4 } : void 0
6174
+ }),
6175
+ "data-testid": "ndv-settings-timeout-enabled",
6176
+ className: "size-4"
6177
+ }
6178
+ ),
6179
+ /* @__PURE__ */ jsx("span", { children: tr(
6180
+ "fluidgrids.builder.nodeSettings.timeoutEnabledLabel",
6181
+ "Abort this node if it runs too long"
6182
+ ) })
6183
+ ] }),
6184
+ timeoutEnabled && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pl-6", children: [
6185
+ /* @__PURE__ */ jsx(
6186
+ Field,
6187
+ {
6188
+ label: tr(
6189
+ "fluidgrids.builder.nodeSettings.timeoutDuration",
6190
+ "Timeout (seconds)"
6191
+ ),
6192
+ children: /* @__PURE__ */ jsx(
6193
+ "input",
6194
+ {
6195
+ type: "number",
6196
+ min: 0.1,
6197
+ step: 0.5,
6198
+ value: msToSeconds(errorConfig?.timeout?.duration, 3e4),
6199
+ onChange: (e) => writeErrorConfig({
6200
+ timeout: {
6201
+ ...errorConfig?.timeout ?? { duration: 3e4 },
6202
+ duration: secondsInputToMs(e.target.value, 100)
6203
+ }
6204
+ }),
6205
+ "data-testid": "ndv-settings-timeout-duration",
6206
+ className: cn$1(inputCx, "max-w-[10rem]")
6207
+ }
6208
+ )
6209
+ }
6210
+ ),
6211
+ /* @__PURE__ */ jsx(
6212
+ Field,
6213
+ {
6214
+ label: tr(
6215
+ "fluidgrids.builder.nodeSettings.onTimeout",
6216
+ "On timeout"
6217
+ ),
6218
+ children: /* @__PURE__ */ jsxs(
6219
+ "select",
6220
+ {
6221
+ value: errorConfig?.timeout?.onTimeout ?? "stop",
6222
+ onChange: (e) => writeErrorConfig({
6223
+ timeout: {
6224
+ ...errorConfig?.timeout ?? { duration: 3e4 },
6225
+ onTimeout: e.target.value,
6226
+ timeoutBranchId: e.target.value === "branch" ? errorConfig?.timeout?.timeoutBranchId : void 0
6227
+ }
6228
+ }),
6229
+ "data-testid": "ndv-settings-on-timeout",
6230
+ className: cn$1(inputCx, "max-w-xs"),
6231
+ children: [
6232
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
6233
+ "fluidgrids.builder.nodeSettings.onTimeoutStop",
6234
+ "Use Retry/On Error settings above"
6235
+ ) }),
6236
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
6237
+ "fluidgrids.builder.nodeSettings.onTimeoutContinue",
6238
+ "Continue on timeout"
6239
+ ) }),
6240
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
6241
+ "fluidgrids.builder.nodeSettings.onTimeoutBranch",
6242
+ "Route to a specific branch"
6243
+ ) })
6244
+ ]
6245
+ }
6246
+ )
6247
+ }
6248
+ ),
6249
+ errorConfig?.timeout?.onTimeout === "branch" && /* @__PURE__ */ jsx(
6250
+ Field,
6251
+ {
6252
+ label: tr(
6253
+ "fluidgrids.builder.nodeSettings.timeoutBranchTarget",
6254
+ "Timeout branch target"
6255
+ ),
6256
+ children: /* @__PURE__ */ jsxs(
6257
+ "select",
6258
+ {
6259
+ value: errorConfig?.timeout?.timeoutBranchId ?? "",
6260
+ onChange: (e) => writeErrorConfig({
6261
+ timeout: {
6262
+ ...errorConfig?.timeout ?? { duration: 3e4 },
6263
+ timeoutBranchId: e.target.value || void 0
6264
+ }
6265
+ }),
6266
+ "data-testid": "ndv-settings-timeout-branch-id",
6267
+ className: cn$1(inputCx, "max-w-xs"),
6268
+ children: [
6269
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
6270
+ "fluidgrids.builder.nodeSettings.selectNode",
6271
+ "Select a node\u2026"
6272
+ ) }),
6273
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
6274
+ ]
6275
+ }
6276
+ )
6277
+ }
6278
+ )
6279
+ ] })
6280
+ ] }),
6281
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
6282
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL4, children: tr("fluidgrids.builder.nodeSettings.otherSection", "Other") }),
6283
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
6284
+ /* @__PURE__ */ jsx(
6285
+ "input",
6286
+ {
6287
+ type: "checkbox",
6288
+ checked: alwaysOutputData,
6289
+ onChange: (e) => writeExecutionSettings({ alwaysOutputData: e.target.checked }),
6290
+ "data-testid": "ndv-settings-always-output-data",
6291
+ className: "size-4"
6292
+ }
6293
+ ),
6294
+ /* @__PURE__ */ jsx("span", { children: tr(
6295
+ "fluidgrids.builder.nodeSettings.alwaysOutputDataLabel",
6296
+ "Always output data"
6297
+ ) })
6298
+ ] }),
6299
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
6300
+ /* @__PURE__ */ jsx(
6301
+ "input",
6302
+ {
6303
+ type: "checkbox",
6304
+ checked: executeOnce,
6305
+ onChange: (e) => writeExecutionSettings({ executeOnce: e.target.checked }),
6306
+ "data-testid": "ndv-settings-execute-once",
6307
+ className: "size-4"
6308
+ }
6309
+ ),
6310
+ /* @__PURE__ */ jsx("span", { children: tr(
6311
+ "fluidgrids.builder.nodeSettings.executeOnceLabel",
6312
+ "Execute once"
6313
+ ) })
6314
+ ] })
6315
+ ] }),
6316
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
6317
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL4, children: tr("fluidgrids.builder.nodeSettings.notesSection", "Notes") }),
6318
+ /* @__PURE__ */ jsx(
6319
+ "textarea",
6320
+ {
6321
+ value: notes,
6322
+ onChange: (e) => writeNotes(e.target.value),
6323
+ rows: 4,
6324
+ placeholder: tr(
6325
+ "fluidgrids.builder.nodeSettings.notesPlaceholder",
6326
+ "Leave a note for teammates about what this node does\u2026"
6327
+ ),
6328
+ "data-testid": "ndv-settings-notes",
6329
+ className: cn$1(inputCx, "resize-y")
6330
+ }
6331
+ )
6332
+ ] })
6333
+ ] });
6334
+ }
6335
+ function Field({
6336
+ label,
6337
+ children
6338
+ }) {
6339
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
6340
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: label }),
6341
+ children
6342
+ ] });
6343
+ }
5250
6344
 
5251
6345
  // src/builder/lib/continuation.ts
5252
6346
  var BLOCKED = {
@@ -5591,4 +6685,4 @@ function NodeExecutionActions({
5591
6685
  );
5592
6686
  }
5593
6687
 
5594
- export { NDV_Z_INDEX, NodeExecutionActions, NodeParamsForm, Popover, PopoverContent, PopoverTrigger, ReadOnlyFieldset, useGlobalStoreClient };
6688
+ export { NDV_Z_INDEX, NodeExecutionActions, NodeParamsForm, NodeSettingsPanel, Popover, PopoverContent, PopoverTrigger, RUNNER_INSTALL_SCRIPT_URL, ReadOnlyFieldset, RunOnSection, RunnerStatusPill, SCHEDULE_NODE_TYPE, SchedulePanel, TabButton, runnerInstallCommand, useGlobalStoreClient, useRunnerActions, useRunners };