@burdenoff/fluidgrids-fe-sdk 2026.1109.9 → 2026.1110.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{ExecutionDetailPage-2ORSV3YN.js → ExecutionDetailPage-UXQFBLF3.js} +14 -14
  2. package/dist/{ExecutionsListPage-MY45NECW.js → ExecutionsListPage-A4GCUNFR.js} +11 -11
  3. package/dist/{WorkflowBuilder-MWC2BCTQ.js → WorkflowBuilder-ZLVMV2UA.js} +21 -15
  4. package/dist/{WorkflowsList-6AG7CFZM.js → WorkflowsList-XPNGNR4A.js} +8 -8
  5. package/dist/{agentTools-5O2KPQYJ.js → agentTools-V26FOMTS.js} +5 -7
  6. package/dist/{chunk-ACME5GOU.js → chunk-2DBAQR6F.js} +2 -2
  7. package/dist/{chunk-MYE6CMGK.js → chunk-2H4XAES7.js} +3 -3
  8. package/dist/{chunk-IZSWG5G3.js → chunk-3F4DIVKT.js} +1 -1
  9. package/dist/chunk-3JGAI5YC.js +1437 -0
  10. package/dist/{chunk-MSNCU6US.js → chunk-5TGPH3CX.js} +4 -4
  11. package/dist/{chunk-U52PO4QQ.js → chunk-5V7PMLQN.js} +1320 -58
  12. package/dist/{chunk-TKSVFP36.js → chunk-5ZAOXCIQ.js} +6 -6
  13. package/dist/{chunk-POCNG3PM.js → chunk-6CPQVLHT.js} +15 -16
  14. package/dist/{chunk-2LERIIZM.js → chunk-7UFE2CHD.js} +308 -1026
  15. package/dist/{chunk-VAWYR6P4.js → chunk-A6Q4FWB6.js} +9 -1
  16. package/dist/{chunk-G4NAU6FF.js → chunk-CBX2CXGH.js} +8 -8
  17. package/dist/{chunk-5UA262ZO.js → chunk-CKF2FBUP.js} +1 -1
  18. package/dist/{chunk-5YLS3WH5.js → chunk-D4L5BN64.js} +1 -1
  19. package/dist/{chunk-VYXTP6YF.js → chunk-DDINUPO3.js} +2185 -5390
  20. package/dist/{chunk-DFFQYK2X.js → chunk-DIIGFBYT.js} +1 -1
  21. package/dist/{chunk-3T3C3475.js → chunk-G7BT24PD.js} +550 -3
  22. package/dist/chunk-HORSE7WX.js +54 -0
  23. package/dist/{chunk-7ADEPA4W.js → chunk-I3DBTNWQ.js} +6 -6
  24. package/dist/{chunk-HZ7FQCFU.js → chunk-JNSHR2J2.js} +1 -1
  25. package/dist/{chunk-ZXLXZFWO.js → chunk-K22NDWSD.js} +2 -2
  26. package/dist/{chunk-JXCJRBWD.js → chunk-K2X63CR2.js} +1 -5
  27. package/dist/{chunk-Q526TUOC.js → chunk-L5MZ74DA.js} +1 -1
  28. package/dist/{chunk-WKF72AS2.js → chunk-OVZA4IM7.js} +4 -4
  29. package/dist/{chunk-GR3QNLFS.js → chunk-OZNFRJKC.js} +5 -5
  30. package/dist/{chunk-NEB4Y7AX.js → chunk-PI4AW4T2.js} +241 -34
  31. package/dist/{chunk-TNMXKPJX.js → chunk-PICOXEUO.js} +4 -4
  32. package/dist/{chunk-QWDF72GZ.js → chunk-QGRVYPQC.js} +2 -2
  33. package/dist/{chunk-THIMC2ZD.js → chunk-WGUHISC2.js} +29 -11
  34. package/dist/{chunk-B3USL2JQ.js → chunk-X7XVRJK2.js} +2 -2
  35. package/dist/{chunk-4EZFAL24.js → chunk-XLU63IUB.js} +6 -6
  36. package/dist/{chunk-CX3LABFK.js → chunk-Z6D6YL7C.js} +1 -1
  37. package/dist/{chunk-NCXK2AO6.js → chunk-ZQTANHKA.js} +2 -2
  38. package/dist/chunk-ZZBZPBO5.js +1644 -0
  39. package/dist/executions.js +15 -15
  40. package/dist/history.js +7 -9
  41. package/dist/index.d.ts +367 -159
  42. package/dist/index.js +592 -68
  43. package/dist/{node-detail-view-ZFGS3FWU.js → node-detail-view-2AFXRNX6.js} +19 -864
  44. package/dist/nodes.js +2 -2
  45. package/dist/observability.js +7 -7
  46. package/dist/{quick-config-panel-ZDGYZR7Y.js → quick-config-panel-U4AELSCT.js} +12 -54
  47. package/dist/settings.js +9 -9
  48. package/dist/templates.js +21 -14
  49. package/dist/workflow-builder.js +22 -16
  50. package/dist/workflow-preview.js +2 -2
  51. package/dist/workflows-list.js +8 -8
  52. package/package.json +1 -1
  53. package/dist/chunk-GTEBQQ7J.js +0 -24
  54. package/dist/chunk-O2OFNGAF.js +0 -338
  55. package/dist/chunk-QTOPG7SR.js +0 -501
  56. package/dist/chunk-U7UNWR3D.js +0 -260
@@ -0,0 +1,1437 @@
1
+ import { nodeTitle } from './chunk-PI4AW4T2.js';
2
+ import { useResource, describeSchedule, NativeSelect, StatusPill, FieldError, triggerSchedule, pauseSchedule, unpauseSchedule, backfillSchedule } from './chunk-D4L5BN64.js';
3
+ import { useFlowStore } from './chunk-K2X63CR2.js';
4
+ import { useTr } from './chunk-UULVIT25.js';
5
+ import { getFluidGridsClient } from './chunk-PDODDDHY.js';
6
+ import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
7
+ import { useState, useRef, useCallback, useEffect } from 'react';
8
+ import { gql } from '@apollo/client';
9
+ import { cn } from '@burdenoff/fe-libs/utils';
10
+ import { jsx, jsxs } from 'react/jsx-runtime';
11
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Label, Input, Switch, Button as Button$1 } from '@burdenoff/fe-libs/ui';
12
+ import { Icon } from '@iconify/react';
13
+ import { Button } from '@burdenoff/fe-libs/button';
14
+ import { toast } from 'sonner';
15
+ import { useShallow } from 'zustand/shallow';
16
+
17
+ var RUNNER_FIELDS = gql`
18
+ fragment SandboxRunnerFields on SandboxRunner {
19
+ id
20
+ name
21
+ status
22
+ hostname
23
+ os
24
+ arch
25
+ driver
26
+ version
27
+ capacity
28
+ privateNetwork {
29
+ target
30
+ ports
31
+ nodes
32
+ }
33
+ tokenPrefix
34
+ lastSeenAt
35
+ connectedAt
36
+ createdBy
37
+ createdAt
38
+ }
39
+ `;
40
+ var LIST_RUNNERS = gql`
41
+ ${RUNNER_FIELDS}
42
+ query SandboxRunners {
43
+ sandboxRunners(context: {}) {
44
+ ...SandboxRunnerFields
45
+ }
46
+ }
47
+ `;
48
+ var CREATE_RUNNER = gql`
49
+ ${RUNNER_FIELDS}
50
+ mutation CreateSandboxRunner($name: String!) {
51
+ createSandboxRunner(input: { name: $name, context: {} }) {
52
+ token
53
+ bridgeUrl
54
+ runner {
55
+ ...SandboxRunnerFields
56
+ }
57
+ }
58
+ }
59
+ `;
60
+ var ROTATE_RUNNER_TOKEN = gql`
61
+ ${RUNNER_FIELDS}
62
+ mutation RotateSandboxRunnerToken($id: ID!) {
63
+ rotateSandboxRunnerToken(id: $id, context: {}) {
64
+ token
65
+ bridgeUrl
66
+ runner {
67
+ ...SandboxRunnerFields
68
+ }
69
+ }
70
+ }
71
+ `;
72
+ var DELETE_RUNNER = gql`
73
+ mutation DeleteSandboxRunner($id: ID!) {
74
+ deleteSandboxRunner(id: $id, context: {})
75
+ }
76
+ `;
77
+ var RUNNER_INSTALL_SCRIPT_URL = "https://www.fluidgrids.ai/downloads/runner/install.sh";
78
+ function runnerInstallCommand(bridgeUrl) {
79
+ const base = `curl -fsSL ${RUNNER_INSTALL_SCRIPT_URL} | sh`;
80
+ return bridgeUrl && bridgeUrl !== DEFAULT_BRIDGE_URL ? `${base} -s -- --url ${bridgeUrl}` : base;
81
+ }
82
+ var DEFAULT_BRIDGE_URL = "wss://graphqlworkspaces.burdenoff.com/runner-bridge";
83
+ function useRunners(options) {
84
+ const [runners, setRunners] = useState([]);
85
+ const [loading, setLoading] = useState(!options?.skip);
86
+ const [error, setError] = useState(null);
87
+ const mounted = useRef(true);
88
+ const fetchRunners = useCallback(async () => {
89
+ if (options?.skip) return;
90
+ try {
91
+ const result = await getFluidGridsClient().query({ query: LIST_RUNNERS, fetchPolicy: "network-only" });
92
+ if (!mounted.current) return;
93
+ setRunners(result.data?.sandboxRunners ?? []);
94
+ setError(null);
95
+ } catch (err) {
96
+ if (mounted.current) setError(err);
97
+ } finally {
98
+ if (mounted.current) setLoading(false);
99
+ }
100
+ }, [options?.skip]);
101
+ useEffect(() => {
102
+ mounted.current = true;
103
+ void fetchRunners();
104
+ const pollMs = options?.pollMs ?? 1e4;
105
+ const timer = pollMs > 0 ? setInterval(fetchRunners, pollMs) : null;
106
+ return () => {
107
+ mounted.current = false;
108
+ if (timer) clearInterval(timer);
109
+ };
110
+ }, [fetchRunners, options?.pollMs]);
111
+ return { runners, loading, error, refetch: fetchRunners };
112
+ }
113
+ function useRunnerActions() {
114
+ const [busy, setBusy] = useState(false);
115
+ const run = useCallback(async (fn) => {
116
+ setBusy(true);
117
+ try {
118
+ return await fn();
119
+ } finally {
120
+ setBusy(false);
121
+ }
122
+ }, []);
123
+ const createRunner = useCallback(
124
+ (name) => run(async () => {
125
+ const result = throwIfResultError(
126
+ await getFluidGridsClient().mutate({ mutation: CREATE_RUNNER, variables: { name } })
127
+ );
128
+ if (!result.data?.createSandboxRunner)
129
+ throw new Error("No runner returned");
130
+ return result.data.createSandboxRunner;
131
+ }),
132
+ [run]
133
+ );
134
+ const rotateToken = useCallback(
135
+ (id) => run(async () => {
136
+ const result = throwIfResultError(
137
+ await getFluidGridsClient().mutate({ mutation: ROTATE_RUNNER_TOKEN, variables: { id } })
138
+ );
139
+ if (!result.data?.rotateSandboxRunnerToken)
140
+ throw new Error("No runner returned");
141
+ return result.data.rotateSandboxRunnerToken;
142
+ }),
143
+ [run]
144
+ );
145
+ const deleteRunner = useCallback(
146
+ (id) => run(async () => {
147
+ const result = throwIfResultError(
148
+ await getFluidGridsClient().mutate({
149
+ mutation: DELETE_RUNNER,
150
+ variables: { id }
151
+ })
152
+ );
153
+ return result.data?.deleteSandboxRunner === true;
154
+ }),
155
+ [run]
156
+ );
157
+ return { createRunner, rotateToken, deleteRunner, busy };
158
+ }
159
+ var STYLES = {
160
+ ONLINE: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
161
+ OFFLINE: "border-border-default bg-bg-sunken text-text-muted",
162
+ NEVER_CONNECTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
163
+ };
164
+ function RunnerStatusPill({
165
+ status,
166
+ className
167
+ }) {
168
+ const tr = useTr();
169
+ const label = status === "ONLINE" ? tr("fluidgrids.runners.online", "Online") : status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet");
170
+ return /* @__PURE__ */ jsx(
171
+ "span",
172
+ {
173
+ className: cn(
174
+ "inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium",
175
+ STYLES[status],
176
+ className
177
+ ),
178
+ "data-testid": "runner-status",
179
+ "data-status": status,
180
+ children: label
181
+ }
182
+ );
183
+ }
184
+ var CLOUD = "__fluidgrids_cloud__";
185
+ function RunOnSection({
186
+ value,
187
+ onChange,
188
+ sectionLabelClassName
189
+ }) {
190
+ const tr = useTr();
191
+ const { runners, loading } = useRunners({ pollMs: 15e3 });
192
+ const selected = value ? runners.find((r) => r.id === value.runnerId) : void 0;
193
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "data-testid": "ndv-settings-run-on", children: [
194
+ /* @__PURE__ */ jsx("div", { className: sectionLabelClassName, children: tr("fluidgrids.builder.nodeSettings.runOn", "Run on") }),
195
+ /* @__PURE__ */ jsxs(
196
+ Select,
197
+ {
198
+ value: value?.runnerId ?? CLOUD,
199
+ onValueChange: (v) => onChange(v === CLOUD ? null : { kind: "runner", runnerId: v }),
200
+ children: [
201
+ /* @__PURE__ */ jsx(
202
+ SelectTrigger,
203
+ {
204
+ className: "w-full",
205
+ "data-testid": "ndv-settings-run-on-select",
206
+ children: /* @__PURE__ */ jsx(SelectValue, {})
207
+ }
208
+ ),
209
+ /* @__PURE__ */ jsxs(
210
+ SelectContent,
211
+ {
212
+ style: { zIndex: 1100 },
213
+ "data-testid": "ndv-settings-run-on-options",
214
+ children: [
215
+ /* @__PURE__ */ jsx(SelectItem, { value: CLOUD, "data-testid": "run-on-cloud", children: tr(
216
+ "fluidgrids.builder.nodeSettings.runOnCloud",
217
+ "FluidGrids Cloud (default)"
218
+ ) }),
219
+ runners.map((runner) => /* @__PURE__ */ jsxs(
220
+ SelectItem,
221
+ {
222
+ value: runner.id,
223
+ "data-testid": "run-on-runner-option",
224
+ children: [
225
+ runner.name,
226
+ runner.status !== "ONLINE" ? ` (${runner.status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet")})` : ""
227
+ ]
228
+ },
229
+ runner.id
230
+ )),
231
+ value && !loading && !selected && /* @__PURE__ */ jsx(SelectItem, { value: value.runnerId, disabled: true, children: tr(
232
+ "fluidgrids.builder.nodeSettings.runOnMissing",
233
+ "Removed runner"
234
+ ) })
235
+ ]
236
+ }
237
+ )
238
+ ]
239
+ }
240
+ ),
241
+ value ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs text-text-secondary", children: [
242
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
243
+ selected && /* @__PURE__ */ jsx(RunnerStatusPill, { status: selected.status }),
244
+ /* @__PURE__ */ jsx("span", { children: tr(
245
+ "fluidgrids.builder.nodeSettings.runOnRunnerHint",
246
+ "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."
247
+ ) })
248
+ ] }),
249
+ selected && /* @__PURE__ */ jsx("p", { "data-testid": "run-on-private-network", children: (selected.privateNetwork ?? []).length === 0 ? tr(
250
+ "fluidgrids.builder.nodeSettings.runOnPublicOnly",
251
+ "On this runner, nodes reach the public internet only. Private destinations are opened on the runner machine (fluidgrids-runner network allow)."
252
+ ) : tr(
253
+ "fluidgrids.builder.nodeSettings.runOnPrivateNetwork",
254
+ "This runner opens {{count}} private destination(s) \u2014 see Runners for which nodes may use them.",
255
+ { count: (selected.privateNetwork ?? []).length }
256
+ ) })
257
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: runners.length === 0 && !loading ? tr(
258
+ "fluidgrids.builder.nodeSettings.runOnNoRunners",
259
+ "Runs in FluidGrids Cloud. Add a runner under Workflows \u2192 Runners to run nodes on your own machine."
260
+ ) : tr(
261
+ "fluidgrids.builder.nodeSettings.runOnCloudHint",
262
+ "Runs in FluidGrids Cloud."
263
+ ) })
264
+ ] });
265
+ }
266
+ var SECTION_LABEL = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
267
+ 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";
268
+ function msToSeconds(ms, fallbackMs) {
269
+ return Math.round((ms ?? fallbackMs) / 1e3 * 10) / 10;
270
+ }
271
+ function secondsInputToMs(raw, minMs) {
272
+ const seconds = parseFloat(raw);
273
+ if (Number.isNaN(seconds)) return minMs;
274
+ return Math.max(minMs, Math.round(seconds * 1e3));
275
+ }
276
+ function NodeSettingsPanel({
277
+ config
278
+ }) {
279
+ const tr = useTr();
280
+ const {
281
+ nodes,
282
+ node,
283
+ errorConfig,
284
+ writeErrorConfig,
285
+ notes,
286
+ writeNotes,
287
+ alwaysOutputData,
288
+ executeOnce,
289
+ runOn,
290
+ writeExecutionSettings
291
+ } = config;
292
+ const onError = errorConfig?.onError ?? "stop";
293
+ const retryEnabled = (errorConfig?.retryConfig?.maxRetries ?? 0) > 0;
294
+ const timeoutEnabled = !!errorConfig?.timeout?.duration;
295
+ const branchTargets = nodes.filter((n) => n.id !== node?.id);
296
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": "ndv-settings-panel", children: [
297
+ /* @__PURE__ */ jsx(
298
+ RunOnSection,
299
+ {
300
+ value: runOn,
301
+ onChange: (next) => writeExecutionSettings({ runOn: next }),
302
+ sectionLabelClassName: SECTION_LABEL
303
+ }
304
+ ),
305
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
306
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.retrySection", "Retry on Fail") }),
307
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
308
+ /* @__PURE__ */ jsx(
309
+ "input",
310
+ {
311
+ type: "checkbox",
312
+ checked: retryEnabled,
313
+ onChange: (e) => writeErrorConfig({
314
+ retryConfig: e.target.checked ? {
315
+ maxRetries: errorConfig?.retryConfig?.maxRetries || 3,
316
+ retryDelay: errorConfig?.retryConfig?.retryDelay || 1e3,
317
+ backoffMultiplier: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
318
+ maxDelay: errorConfig?.retryConfig?.maxDelay ?? 3e4
319
+ } : void 0
320
+ }),
321
+ "data-testid": "ndv-settings-retry-enabled",
322
+ className: "size-4"
323
+ }
324
+ ),
325
+ /* @__PURE__ */ jsx("span", { children: tr(
326
+ "fluidgrids.builder.nodeSettings.retryEnabledLabel",
327
+ "Retry this node on failure"
328
+ ) })
329
+ ] }),
330
+ retryEnabled && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pl-6", children: [
331
+ /* @__PURE__ */ jsx(
332
+ Field,
333
+ {
334
+ label: tr(
335
+ "fluidgrids.builder.nodeSettings.maxRetries",
336
+ "Max tries"
337
+ ),
338
+ children: /* @__PURE__ */ jsx(
339
+ "input",
340
+ {
341
+ type: "number",
342
+ min: 1,
343
+ max: 20,
344
+ value: errorConfig?.retryConfig?.maxRetries ?? 3,
345
+ onChange: (e) => writeErrorConfig({
346
+ retryConfig: {
347
+ ...errorConfig?.retryConfig ?? {
348
+ retryDelay: 1e3,
349
+ maxRetries: 3
350
+ },
351
+ maxRetries: Math.max(
352
+ 1,
353
+ parseInt(e.target.value, 10) || 1
354
+ )
355
+ }
356
+ }),
357
+ "data-testid": "ndv-settings-max-retries",
358
+ className: inputCx
359
+ }
360
+ )
361
+ }
362
+ ),
363
+ /* @__PURE__ */ jsx(
364
+ Field,
365
+ {
366
+ label: tr(
367
+ "fluidgrids.builder.nodeSettings.retryDelay",
368
+ "Wait (seconds)"
369
+ ),
370
+ children: /* @__PURE__ */ jsx(
371
+ "input",
372
+ {
373
+ type: "number",
374
+ min: 0,
375
+ step: 0.1,
376
+ value: msToSeconds(errorConfig?.retryConfig?.retryDelay, 1e3),
377
+ onChange: (e) => writeErrorConfig({
378
+ retryConfig: {
379
+ ...errorConfig?.retryConfig ?? {
380
+ maxRetries: 3,
381
+ retryDelay: 1e3
382
+ },
383
+ retryDelay: secondsInputToMs(e.target.value, 0)
384
+ }
385
+ }),
386
+ "data-testid": "ndv-settings-retry-delay",
387
+ className: inputCx
388
+ }
389
+ )
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsx(
393
+ Field,
394
+ {
395
+ label: tr(
396
+ "fluidgrids.builder.nodeSettings.backoffMultiplier",
397
+ "Backoff \xD7"
398
+ ),
399
+ children: /* @__PURE__ */ jsx(
400
+ "input",
401
+ {
402
+ type: "number",
403
+ min: 1,
404
+ step: 0.5,
405
+ value: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
406
+ onChange: (e) => writeErrorConfig({
407
+ retryConfig: {
408
+ ...errorConfig?.retryConfig ?? {
409
+ maxRetries: 3,
410
+ retryDelay: 1e3
411
+ },
412
+ backoffMultiplier: parseFloat(e.target.value) || 1
413
+ }
414
+ }),
415
+ "data-testid": "ndv-settings-backoff-multiplier",
416
+ className: inputCx
417
+ }
418
+ )
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsx(
422
+ Field,
423
+ {
424
+ label: tr(
425
+ "fluidgrids.builder.nodeSettings.maxDelay",
426
+ "Max wait (seconds)"
427
+ ),
428
+ children: /* @__PURE__ */ jsx(
429
+ "input",
430
+ {
431
+ type: "number",
432
+ min: 0,
433
+ step: 1,
434
+ value: msToSeconds(errorConfig?.retryConfig?.maxDelay, 3e4),
435
+ onChange: (e) => writeErrorConfig({
436
+ retryConfig: {
437
+ ...errorConfig?.retryConfig ?? {
438
+ maxRetries: 3,
439
+ retryDelay: 1e3
440
+ },
441
+ maxDelay: secondsInputToMs(e.target.value, 0)
442
+ }
443
+ }),
444
+ "data-testid": "ndv-settings-max-delay",
445
+ className: inputCx
446
+ }
447
+ )
448
+ }
449
+ )
450
+ ] })
451
+ ] }),
452
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
453
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.onErrorSection", "On Error") }),
454
+ /* @__PURE__ */ jsxs(
455
+ "select",
456
+ {
457
+ value: onError,
458
+ onChange: (e) => writeErrorConfig({
459
+ onError: e.target.value,
460
+ errorBranchId: e.target.value === "branch" ? errorConfig?.errorBranchId : void 0
461
+ }),
462
+ "data-testid": "ndv-settings-on-error",
463
+ className: cn(inputCx, "max-w-xs"),
464
+ children: [
465
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
466
+ "fluidgrids.builder.nodeSettings.onErrorStop",
467
+ "Stop workflow (default)"
468
+ ) }),
469
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
470
+ "fluidgrids.builder.nodeSettings.onErrorContinue",
471
+ "Continue on fail"
472
+ ) }),
473
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
474
+ "fluidgrids.builder.nodeSettings.onErrorBranch",
475
+ "Route to error branch"
476
+ ) })
477
+ ]
478
+ }
479
+ ),
480
+ onError === "branch" && /* @__PURE__ */ jsx("div", { className: "pl-2", children: /* @__PURE__ */ jsx(
481
+ Field,
482
+ {
483
+ label: tr(
484
+ "fluidgrids.builder.nodeSettings.errorBranchTarget",
485
+ "Error branch target"
486
+ ),
487
+ children: /* @__PURE__ */ jsxs(
488
+ "select",
489
+ {
490
+ value: errorConfig?.errorBranchId ?? "",
491
+ onChange: (e) => writeErrorConfig({
492
+ errorBranchId: e.target.value || void 0
493
+ }),
494
+ "data-testid": "ndv-settings-error-branch-id",
495
+ className: cn(inputCx, "max-w-xs"),
496
+ children: [
497
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
498
+ "fluidgrids.builder.nodeSettings.selectNode",
499
+ "Select a node\u2026"
500
+ ) }),
501
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
502
+ ]
503
+ }
504
+ )
505
+ }
506
+ ) })
507
+ ] }),
508
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
509
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.timeoutSection", "Timeout") }),
510
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
511
+ /* @__PURE__ */ jsx(
512
+ "input",
513
+ {
514
+ type: "checkbox",
515
+ checked: timeoutEnabled,
516
+ onChange: (e) => writeErrorConfig({
517
+ timeout: e.target.checked ? { duration: errorConfig?.timeout?.duration || 3e4 } : void 0
518
+ }),
519
+ "data-testid": "ndv-settings-timeout-enabled",
520
+ className: "size-4"
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsx("span", { children: tr(
524
+ "fluidgrids.builder.nodeSettings.timeoutEnabledLabel",
525
+ "Abort this node if it runs too long"
526
+ ) })
527
+ ] }),
528
+ timeoutEnabled && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pl-6", children: [
529
+ /* @__PURE__ */ jsx(
530
+ Field,
531
+ {
532
+ label: tr(
533
+ "fluidgrids.builder.nodeSettings.timeoutDuration",
534
+ "Timeout (seconds)"
535
+ ),
536
+ children: /* @__PURE__ */ jsx(
537
+ "input",
538
+ {
539
+ type: "number",
540
+ min: 0.1,
541
+ step: 0.5,
542
+ value: msToSeconds(errorConfig?.timeout?.duration, 3e4),
543
+ onChange: (e) => writeErrorConfig({
544
+ timeout: {
545
+ ...errorConfig?.timeout ?? { duration: 3e4 },
546
+ duration: secondsInputToMs(e.target.value, 100)
547
+ }
548
+ }),
549
+ "data-testid": "ndv-settings-timeout-duration",
550
+ className: cn(inputCx, "max-w-[10rem]")
551
+ }
552
+ )
553
+ }
554
+ ),
555
+ /* @__PURE__ */ jsx(
556
+ Field,
557
+ {
558
+ label: tr(
559
+ "fluidgrids.builder.nodeSettings.onTimeout",
560
+ "On timeout"
561
+ ),
562
+ children: /* @__PURE__ */ jsxs(
563
+ "select",
564
+ {
565
+ value: errorConfig?.timeout?.onTimeout ?? "stop",
566
+ onChange: (e) => writeErrorConfig({
567
+ timeout: {
568
+ ...errorConfig?.timeout ?? { duration: 3e4 },
569
+ onTimeout: e.target.value,
570
+ timeoutBranchId: e.target.value === "branch" ? errorConfig?.timeout?.timeoutBranchId : void 0
571
+ }
572
+ }),
573
+ "data-testid": "ndv-settings-on-timeout",
574
+ className: cn(inputCx, "max-w-xs"),
575
+ children: [
576
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
577
+ "fluidgrids.builder.nodeSettings.onTimeoutStop",
578
+ "Use Retry/On Error settings above"
579
+ ) }),
580
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
581
+ "fluidgrids.builder.nodeSettings.onTimeoutContinue",
582
+ "Continue on timeout"
583
+ ) }),
584
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
585
+ "fluidgrids.builder.nodeSettings.onTimeoutBranch",
586
+ "Route to a specific branch"
587
+ ) })
588
+ ]
589
+ }
590
+ )
591
+ }
592
+ ),
593
+ errorConfig?.timeout?.onTimeout === "branch" && /* @__PURE__ */ jsx(
594
+ Field,
595
+ {
596
+ label: tr(
597
+ "fluidgrids.builder.nodeSettings.timeoutBranchTarget",
598
+ "Timeout branch target"
599
+ ),
600
+ children: /* @__PURE__ */ jsxs(
601
+ "select",
602
+ {
603
+ value: errorConfig?.timeout?.timeoutBranchId ?? "",
604
+ onChange: (e) => writeErrorConfig({
605
+ timeout: {
606
+ ...errorConfig?.timeout ?? { duration: 3e4 },
607
+ timeoutBranchId: e.target.value || void 0
608
+ }
609
+ }),
610
+ "data-testid": "ndv-settings-timeout-branch-id",
611
+ className: cn(inputCx, "max-w-xs"),
612
+ children: [
613
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
614
+ "fluidgrids.builder.nodeSettings.selectNode",
615
+ "Select a node\u2026"
616
+ ) }),
617
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
618
+ ]
619
+ }
620
+ )
621
+ }
622
+ )
623
+ ] })
624
+ ] }),
625
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
626
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.otherSection", "Other") }),
627
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
628
+ /* @__PURE__ */ jsx(
629
+ "input",
630
+ {
631
+ type: "checkbox",
632
+ checked: alwaysOutputData,
633
+ onChange: (e) => writeExecutionSettings({ alwaysOutputData: e.target.checked }),
634
+ "data-testid": "ndv-settings-always-output-data",
635
+ className: "size-4"
636
+ }
637
+ ),
638
+ /* @__PURE__ */ jsx("span", { children: tr(
639
+ "fluidgrids.builder.nodeSettings.alwaysOutputDataLabel",
640
+ "Always output data"
641
+ ) })
642
+ ] }),
643
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
644
+ /* @__PURE__ */ jsx(
645
+ "input",
646
+ {
647
+ type: "checkbox",
648
+ checked: executeOnce,
649
+ onChange: (e) => writeExecutionSettings({ executeOnce: e.target.checked }),
650
+ "data-testid": "ndv-settings-execute-once",
651
+ className: "size-4"
652
+ }
653
+ ),
654
+ /* @__PURE__ */ jsx("span", { children: tr(
655
+ "fluidgrids.builder.nodeSettings.executeOnceLabel",
656
+ "Execute once"
657
+ ) })
658
+ ] })
659
+ ] }),
660
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
661
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.notesSection", "Notes") }),
662
+ /* @__PURE__ */ jsx(
663
+ "textarea",
664
+ {
665
+ value: notes,
666
+ onChange: (e) => writeNotes(e.target.value),
667
+ rows: 4,
668
+ placeholder: tr(
669
+ "fluidgrids.builder.nodeSettings.notesPlaceholder",
670
+ "Leave a note for teammates about what this node does\u2026"
671
+ ),
672
+ "data-testid": "ndv-settings-notes",
673
+ className: cn(inputCx, "resize-y")
674
+ }
675
+ )
676
+ ] })
677
+ ] });
678
+ }
679
+ function Field({
680
+ label,
681
+ children
682
+ }) {
683
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
684
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: label }),
685
+ children
686
+ ] });
687
+ }
688
+ function TabButton({
689
+ active,
690
+ onClick,
691
+ children,
692
+ testId
693
+ }) {
694
+ return /* @__PURE__ */ jsx(
695
+ "button",
696
+ {
697
+ type: "button",
698
+ onClick,
699
+ "data-testid": testId,
700
+ className: cn(
701
+ "-mb-px border-b-2 py-2.5 text-sm transition-colors",
702
+ active ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
703
+ ),
704
+ children
705
+ }
706
+ );
707
+ }
708
+ var SCHEDULE_NODE_TYPE = "internal:schedule";
709
+ var OVERLAP_POLICIES = [
710
+ "SKIP" /* Skip */,
711
+ "BUFFER_ONE" /* BufferOne */,
712
+ "BUFFER_ALL" /* BufferAll */,
713
+ "CANCEL_OTHER" /* CancelOther */,
714
+ "TERMINATE_OTHER" /* TerminateOther */,
715
+ "ALLOW_ALL" /* AllowAll */
716
+ ];
717
+ var MAX_BACKFILL = 1e3;
718
+ function overlapPolicyOf(value) {
719
+ return OVERLAP_POLICIES.find((policy) => policy === value) ?? null;
720
+ }
721
+ function secondsOf(value) {
722
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0)
723
+ return value;
724
+ if (typeof value !== "string") return null;
725
+ const match = /^(\d+)\s*(s|m|h|d)?$/.exec(value.trim());
726
+ if (!match) return null;
727
+ const unit = { s: 1, m: 60, h: 3600, d: 86400 }[match[2] ?? "s"] ?? 1;
728
+ return Number(match[1]) * unit;
729
+ }
730
+ function SchedulePanel({
731
+ state,
732
+ writeField,
733
+ workflowKey,
734
+ triggerNodeId,
735
+ canExecute,
736
+ canUpdate
737
+ }) {
738
+ const tr = useTr();
739
+ const policy = overlapPolicyOf(state.overlapPolicy);
740
+ const catchup = secondsOf(state.catchupWindow);
741
+ const limited = typeof state.limitedActions === "number" ? state.limitedActions : null;
742
+ const describe = useResource(
743
+ workflowKey ? `schedule:${workflowKey}:${triggerNodeId}` : null,
744
+ !!workflowKey,
745
+ () => describeSchedule(workflowKey ?? "", triggerNodeId)
746
+ );
747
+ const [note, setNote] = useState("");
748
+ const [from, setFrom] = useState("");
749
+ const [to, setTo] = useState("");
750
+ const [busy, setBusy] = useState(null);
751
+ const [error, setError] = useState(null);
752
+ const act = async (name, run) => {
753
+ setBusy(name);
754
+ setError(null);
755
+ try {
756
+ await run();
757
+ } catch (err) {
758
+ setError(describeOperationError(err));
759
+ } finally {
760
+ setBusy(null);
761
+ }
762
+ };
763
+ const live = describe.data;
764
+ const backfillFrom = from ? new Date(from) : null;
765
+ const backfillTo = to ? new Date(to) : null;
766
+ const backfillValid = !!backfillFrom && !!backfillTo && !Number.isNaN(backfillFrom.getTime()) && !Number.isNaN(backfillTo.getTime()) && backfillFrom < backfillTo && backfillTo.getTime() <= Date.now();
767
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5 p-4", "data-testid": "schedule-panel", children: [
768
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
769
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-foreground", children: tr("fluidgrids.builder.schedule.policy", "Policy") }),
770
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
771
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-overlap", children: tr(
772
+ "fluidgrids.builder.schedule.overlap",
773
+ "When the previous run is still open"
774
+ ) }),
775
+ /* @__PURE__ */ jsxs(
776
+ NativeSelect,
777
+ {
778
+ id: "schedule-overlap",
779
+ value: policy ?? "",
780
+ disabled: !canUpdate,
781
+ onChange: (value) => writeField("overlapPolicy", overlapPolicyOf(value) ?? void 0),
782
+ testId: "schedule-overlap",
783
+ children: [
784
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
785
+ "fluidgrids.builder.schedule.overlapLegacy",
786
+ "Start every run (no policy)"
787
+ ) }),
788
+ /* @__PURE__ */ jsx("option", { value: "SKIP" /* Skip */, children: tr("fluidgrids.builder.schedule.skip", "Skip the fire") }),
789
+ /* @__PURE__ */ jsx("option", { value: "BUFFER_ONE" /* BufferOne */, children: tr(
790
+ "fluidgrids.builder.schedule.bufferOne",
791
+ "Run once after it ends"
792
+ ) }),
793
+ /* @__PURE__ */ jsx("option", { value: "BUFFER_ALL" /* BufferAll */, children: tr(
794
+ "fluidgrids.builder.schedule.bufferAll",
795
+ "Run every missed fire after it ends"
796
+ ) }),
797
+ /* @__PURE__ */ jsx("option", { value: "CANCEL_OTHER" /* CancelOther */, children: tr(
798
+ "fluidgrids.builder.schedule.cancelOther",
799
+ "Cancel it and start"
800
+ ) }),
801
+ /* @__PURE__ */ jsx("option", { value: "TERMINATE_OTHER" /* TerminateOther */, children: tr(
802
+ "fluidgrids.builder.schedule.terminateOther",
803
+ "Terminate it and start"
804
+ ) }),
805
+ /* @__PURE__ */ jsx("option", { value: "ALLOW_ALL" /* AllowAll */, children: tr("fluidgrids.builder.schedule.allowAll", "Start anyway") })
806
+ ]
807
+ }
808
+ ),
809
+ !policy && canUpdate && /* @__PURE__ */ jsx(
810
+ Button,
811
+ {
812
+ size: "sm",
813
+ variant: "link",
814
+ className: "h-auto p-0 text-xs",
815
+ onClick: () => writeField("overlapPolicy", "SKIP" /* Skip */),
816
+ "data-testid": "schedule-use-policy",
817
+ children: tr(
818
+ "fluidgrids.builder.schedule.usePolicy",
819
+ "Use a policy (skip while a run is open)"
820
+ )
821
+ }
822
+ )
823
+ ] }),
824
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
825
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
826
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-catchup", children: tr(
827
+ "fluidgrids.builder.schedule.catchup",
828
+ "Catch-up window (seconds)"
829
+ ) }),
830
+ /* @__PURE__ */ jsx(
831
+ Input,
832
+ {
833
+ id: "schedule-catchup",
834
+ inputMode: "numeric",
835
+ disabled: !canUpdate,
836
+ value: catchup === null ? "" : String(catchup),
837
+ onChange: (event) => {
838
+ const value = event.target.value.trim();
839
+ writeField(
840
+ "catchupWindow",
841
+ value === "" ? void 0 : /^\d+$/.test(value) ? Math.min(Number(value), 604800) : state.catchupWindow
842
+ );
843
+ },
844
+ "data-testid": "schedule-catchup"
845
+ }
846
+ )
847
+ ] }),
848
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
849
+ /* @__PURE__ */ jsx(Label, { htmlFor: "schedule-limited", children: tr(
850
+ "fluidgrids.builder.schedule.limited",
851
+ "Stop after this many runs (optional)"
852
+ ) }),
853
+ /* @__PURE__ */ jsx(
854
+ Input,
855
+ {
856
+ id: "schedule-limited",
857
+ inputMode: "numeric",
858
+ disabled: !canUpdate,
859
+ value: limited === null ? "" : String(limited),
860
+ onChange: (event) => {
861
+ const value = event.target.value.trim();
862
+ writeField(
863
+ "limitedActions",
864
+ value === "" ? void 0 : /^\d+$/.test(value) ? Number(value) : state.limitedActions
865
+ );
866
+ },
867
+ "data-testid": "schedule-limited"
868
+ }
869
+ )
870
+ ] })
871
+ ] }),
872
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-foreground", children: [
873
+ tr(
874
+ "fluidgrids.builder.schedule.pauseOnFailure",
875
+ "Pause the schedule when a run it started fails"
876
+ ),
877
+ /* @__PURE__ */ jsx(
878
+ Switch,
879
+ {
880
+ checked: state.pauseOnFailure === true,
881
+ disabled: !canUpdate,
882
+ onCheckedChange: (checked) => writeField("pauseOnFailure", checked ? true : void 0),
883
+ "data-testid": "schedule-pause-on-failure"
884
+ }
885
+ )
886
+ ] }),
887
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
888
+ "fluidgrids.builder.schedule.saveHint",
889
+ "Policy changes apply when the workflow is saved and activated."
890
+ ) })
891
+ ] }),
892
+ !workflowKey ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: tr(
893
+ "fluidgrids.builder.schedule.unsaved",
894
+ "Save the workflow to see and control its schedule."
895
+ ) }) : /* @__PURE__ */ jsxs("section", { className: "space-y-3", "data-testid": "schedule-live", children: [
896
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
897
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-foreground", children: tr("fluidgrids.builder.schedule.live", "Schedule") }),
898
+ live && /* @__PURE__ */ jsx(
899
+ StatusPill,
900
+ {
901
+ tone: live.state.paused ? "warning" : "success",
902
+ testId: "schedule-state",
903
+ children: live.state.paused ? tr("fluidgrids.builder.schedule.paused", "Paused") : tr("fluidgrids.builder.schedule.active", "Active")
904
+ }
905
+ ),
906
+ /* @__PURE__ */ jsx(
907
+ Button,
908
+ {
909
+ size: "sm",
910
+ variant: "ghost",
911
+ className: "ml-auto",
912
+ onClick: () => void describe.refetch(),
913
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", width: 14 })
914
+ }
915
+ )
916
+ ] }),
917
+ /* @__PURE__ */ jsx(
918
+ FieldError,
919
+ {
920
+ message: describe.error,
921
+ testId: "schedule-describe-error"
922
+ }
923
+ ),
924
+ live?.state.paused && live.state.note && /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground", "data-testid": "schedule-note", children: live.state.note }),
925
+ live && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
926
+ /* @__PURE__ */ jsxs("div", { children: [
927
+ /* @__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") }),
928
+ /* @__PURE__ */ jsxs(
929
+ "ul",
930
+ {
931
+ className: "space-y-0.5 font-mono text-xs text-foreground",
932
+ "data-testid": "schedule-next",
933
+ children: [
934
+ live.nextFireTimes.slice(0, 5).map((time) => /* @__PURE__ */ jsx("li", { children: new Date(time).toLocaleString() }, time)),
935
+ live.nextFireTimes.length === 0 && /* @__PURE__ */ jsx("li", { children: "\u2014" })
936
+ ]
937
+ }
938
+ )
939
+ ] }),
940
+ /* @__PURE__ */ jsxs("div", { children: [
941
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.schedule.recent", "Recent actions") }),
942
+ /* @__PURE__ */ jsxs(
943
+ "ul",
944
+ {
945
+ className: "space-y-0.5 text-xs text-foreground",
946
+ "data-testid": "schedule-recent",
947
+ children: [
948
+ live.recentActions.slice(0, 5).map((tick) => /* @__PURE__ */ jsxs("li", { children: [
949
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: new Date(tick.dueAt).toLocaleString() }),
950
+ " ",
951
+ "\xB7 ",
952
+ tick.kind,
953
+ " \xB7 ",
954
+ tick.outcome
955
+ ] }, tick.id)),
956
+ live.recentActions.length === 0 && /* @__PURE__ */ jsx("li", { children: "\u2014" })
957
+ ]
958
+ }
959
+ )
960
+ ] })
961
+ ] }),
962
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
963
+ canExecute && /* @__PURE__ */ jsxs(
964
+ Button,
965
+ {
966
+ size: "sm",
967
+ variant: "outline",
968
+ disabled: busy !== null,
969
+ onClick: () => void act("trigger", async () => {
970
+ const result = await triggerSchedule({
971
+ workflowKey,
972
+ triggerNodeId
973
+ });
974
+ toast.success(
975
+ result.runId ? tr(
976
+ "fluidgrids.builder.schedule.triggered",
977
+ "Run started"
978
+ ) : tr(
979
+ "fluidgrids.builder.schedule.triggeredOutcome",
980
+ "Tick recorded: {{outcome}}",
981
+ { outcome: result.tick.outcome }
982
+ )
983
+ );
984
+ await describe.refetch();
985
+ }),
986
+ "data-testid": "schedule-trigger",
987
+ children: [
988
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 14 }),
989
+ tr("fluidgrids.builder.schedule.trigger", "Trigger now")
990
+ ]
991
+ }
992
+ ),
993
+ canUpdate && live && !live.state.paused && /* @__PURE__ */ jsxs(
994
+ Button,
995
+ {
996
+ size: "sm",
997
+ variant: "outline",
998
+ disabled: busy !== null,
999
+ onClick: () => void act("pause", async () => {
1000
+ describe.set(
1001
+ await pauseSchedule({ workflowKey, triggerNodeId, note })
1002
+ );
1003
+ setNote("");
1004
+ }),
1005
+ "data-testid": "schedule-pause",
1006
+ children: [
1007
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:pause", className: "mr-1", width: 14 }),
1008
+ tr("fluidgrids.builder.schedule.pause", "Pause")
1009
+ ]
1010
+ }
1011
+ ),
1012
+ canUpdate && live?.state.paused && /* @__PURE__ */ jsxs(
1013
+ Button,
1014
+ {
1015
+ size: "sm",
1016
+ variant: "outline",
1017
+ disabled: busy !== null,
1018
+ onClick: () => void act(
1019
+ "unpause",
1020
+ async () => describe.set(
1021
+ await unpauseSchedule({ workflowKey, triggerNodeId })
1022
+ )
1023
+ ),
1024
+ "data-testid": "schedule-unpause",
1025
+ children: [
1026
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:play-pause", className: "mr-1", width: 14 }),
1027
+ tr("fluidgrids.builder.schedule.unpause", "Unpause")
1028
+ ]
1029
+ }
1030
+ )
1031
+ ] }),
1032
+ canUpdate && live && !live.state.paused && /* @__PURE__ */ jsx(
1033
+ Input,
1034
+ {
1035
+ value: note,
1036
+ maxLength: 1e3,
1037
+ placeholder: tr(
1038
+ "fluidgrids.builder.schedule.notePlaceholder",
1039
+ "Note for the pause (optional)"
1040
+ ),
1041
+ onChange: (event) => setNote(event.target.value),
1042
+ "data-testid": "schedule-pause-note"
1043
+ }
1044
+ ),
1045
+ canExecute && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border p-3", children: [
1046
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.schedule.backfill", "Backfill") }),
1047
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
1048
+ /* @__PURE__ */ jsx(
1049
+ Input,
1050
+ {
1051
+ type: "datetime-local",
1052
+ value: from,
1053
+ onChange: (event) => setFrom(event.target.value),
1054
+ "aria-label": tr("fluidgrids.builder.schedule.from", "From"),
1055
+ "data-testid": "schedule-backfill-from"
1056
+ }
1057
+ ),
1058
+ /* @__PURE__ */ jsx(
1059
+ Input,
1060
+ {
1061
+ type: "datetime-local",
1062
+ value: to,
1063
+ onChange: (event) => setTo(event.target.value),
1064
+ "aria-label": tr("fluidgrids.builder.schedule.to", "To"),
1065
+ "data-testid": "schedule-backfill-to"
1066
+ }
1067
+ )
1068
+ ] }),
1069
+ /* @__PURE__ */ jsx(
1070
+ Button,
1071
+ {
1072
+ size: "sm",
1073
+ variant: "outline",
1074
+ disabled: !backfillValid || busy !== null,
1075
+ onClick: () => void act("backfill", async () => {
1076
+ if (!backfillFrom || !backfillTo) return;
1077
+ const result = await backfillSchedule({
1078
+ workflowKey,
1079
+ triggerNodeId,
1080
+ from: backfillFrom.toISOString(),
1081
+ to: backfillTo.toISOString()
1082
+ });
1083
+ toast.success(
1084
+ tr(
1085
+ "fluidgrids.builder.schedule.backfilled",
1086
+ "{{queued}} of {{fireTimes}} fire times queued",
1087
+ { queued: result.queued, fireTimes: result.fireTimes }
1088
+ )
1089
+ );
1090
+ await describe.refetch();
1091
+ }),
1092
+ "data-testid": "schedule-backfill",
1093
+ children: tr(
1094
+ "fluidgrids.builder.schedule.runBackfill",
1095
+ "Run the fire times in this range"
1096
+ )
1097
+ }
1098
+ ),
1099
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
1100
+ "fluidgrids.builder.schedule.backfillHint",
1101
+ "At most {{max}} past fire times; each started run counts toward the monthly runs.",
1102
+ { max: MAX_BACKFILL }
1103
+ ) })
1104
+ ] }),
1105
+ /* @__PURE__ */ jsx(FieldError, { message: error, testId: "schedule-error" })
1106
+ ] })
1107
+ ] });
1108
+ }
1109
+ var JsonView = ({
1110
+ data
1111
+ }) => {
1112
+ const tr = useTr();
1113
+ if (!data)
1114
+ return /* @__PURE__ */ jsx("div", { className: "text-tertiary p-4", children: tr("fluidgrids.builder.logsPanel.jsonViewNoData", "No data") });
1115
+ return /* @__PURE__ */ jsx("pre", { className: "text-xs overflow-auto p-4 bg-card/50 rounded h-full", children: JSON.stringify(data, null, 2) });
1116
+ };
1117
+ var TableView = ({
1118
+ data
1119
+ }) => {
1120
+ const tr = useTr();
1121
+ if (!data)
1122
+ return /* @__PURE__ */ jsx("div", { className: "text-tertiary p-4", children: tr("fluidgrids.builder.logsPanel.tableViewNoData", "No data") });
1123
+ const entries = Object.entries(data);
1124
+ if (entries.length === 0)
1125
+ return /* @__PURE__ */ jsx("div", { className: "text-tertiary p-4", children: tr(
1126
+ "fluidgrids.builder.logsPanel.tableViewEmptyObject",
1127
+ "Empty object"
1128
+ ) });
1129
+ return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
1130
+ /* @__PURE__ */ jsx("thead", { className: "bg-card-foreground/10 sticky top-0", children: /* @__PURE__ */ jsxs("tr", { children: [
1131
+ /* @__PURE__ */ jsx("th", { className: "text-left p-2", children: tr("fluidgrids.builder.logsPanel.tableColumnField", "Field") }),
1132
+ /* @__PURE__ */ jsx("th", { className: "text-left p-2", children: tr("fluidgrids.builder.logsPanel.tableColumnType", "Type") }),
1133
+ /* @__PURE__ */ jsx("th", { className: "text-left p-2", children: tr("fluidgrids.builder.logsPanel.tableColumnValue", "Value") })
1134
+ ] }) }),
1135
+ /* @__PURE__ */ jsx("tbody", { children: entries.map(([key, value]) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-card-foreground/10", children: [
1136
+ /* @__PURE__ */ jsx("td", { className: "p-2 font-medium text-accent", children: key }),
1137
+ /* @__PURE__ */ jsx("td", { className: "p-2 text-secondary", children: typeof value }),
1138
+ /* @__PURE__ */ jsx("td", { className: "p-2 truncate max-w-[200px]", children: typeof value === "object" ? JSON.stringify(value) : String(value) })
1139
+ ] }, key)) })
1140
+ ] }) });
1141
+ };
1142
+ var LogListItem = ({
1143
+ log,
1144
+ isSelected,
1145
+ onClick
1146
+ }) => {
1147
+ const statusIcon = {
1148
+ pending: "mdi:clock-outline",
1149
+ running: "svg-spinners:ring-resize",
1150
+ completed: "mdi:check-circle",
1151
+ failed: "mdi:close-circle"
1152
+ }[log.status];
1153
+ const statusColor = {
1154
+ pending: "text-tertiary",
1155
+ running: "text-accent",
1156
+ completed: "text-[var(--color-status-success-text)]",
1157
+ failed: "text-[var(--color-status-error-text)]"
1158
+ }[log.status];
1159
+ return /* @__PURE__ */ jsxs(
1160
+ "button",
1161
+ {
1162
+ onClick,
1163
+ className: cn(
1164
+ "w-full text-left p-2 flex items-center gap-2 text-xs transition-colors",
1165
+ isSelected ? "bg-primary/20 border-l-2 border-primary" : "hover:bg-card-foreground/10"
1166
+ ),
1167
+ children: [
1168
+ /* @__PURE__ */ jsx(Icon, { icon: statusIcon, className: cn("size-4", statusColor) }),
1169
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: log.nodeName }),
1170
+ log.duration && /* @__PURE__ */ jsxs("span", { className: "text-tertiary text-[10px]", children: [
1171
+ log.duration.toFixed(2),
1172
+ "s"
1173
+ ] })
1174
+ ]
1175
+ }
1176
+ );
1177
+ };
1178
+ var LogDetailView = ({ log }) => {
1179
+ const tr = useTr();
1180
+ const [activeTab, setActiveTab] = useState("output");
1181
+ const [viewMode, setViewMode] = useState("json");
1182
+ if (!log) {
1183
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-tertiary text-sm", children: tr(
1184
+ "fluidgrids.builder.logsPanel.detailEmptyState",
1185
+ "Select a step to view details"
1186
+ ) });
1187
+ }
1188
+ const statusColor = {
1189
+ pending: "text-tertiary",
1190
+ running: "text-accent",
1191
+ completed: "text-[var(--color-status-success-text)]",
1192
+ failed: "text-[var(--color-status-error-text)]"
1193
+ }[log.status];
1194
+ const data = activeTab === "input" ? log.inputArgs : log.output;
1195
+ const itemCount = data ? Object.keys(data).length : 0;
1196
+ const resumeUrl = typeof log.output?._resumeUrl === "string" ? log.output._resumeUrl : null;
1197
+ return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col", children: [
1198
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-2 border-b border-card-foreground/10", children: [
1199
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1200
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: log.nodeName }),
1201
+ /* @__PURE__ */ jsx("span", { className: cn("text-xs capitalize", statusColor), children: log.status === "completed" ? tr("fluidgrids.builder.logsPanel.statusSuccess", "Success") : log.status }),
1202
+ log.duration && /* @__PURE__ */ jsxs("span", { className: "text-xs text-text-muted", children: [
1203
+ "in ",
1204
+ (log.duration * 1e3).toFixed(0),
1205
+ "ms"
1206
+ ] })
1207
+ ] }),
1208
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1209
+ /* @__PURE__ */ jsx(
1210
+ Button$1,
1211
+ {
1212
+ variant: viewMode === "json" ? "secondary" : "ghost",
1213
+ size: "icon",
1214
+ className: "size-6",
1215
+ onClick: () => setViewMode("json"),
1216
+ title: tr(
1217
+ "fluidgrids.builder.logsPanel.jsonViewTooltip",
1218
+ "JSON View"
1219
+ ),
1220
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:code-json", className: "size-3" })
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsx(
1224
+ Button$1,
1225
+ {
1226
+ variant: viewMode === "table" ? "secondary" : "ghost",
1227
+ size: "icon",
1228
+ className: "size-6",
1229
+ onClick: () => setViewMode("table"),
1230
+ title: tr(
1231
+ "fluidgrids.builder.logsPanel.tableViewTooltip",
1232
+ "Table View"
1233
+ ),
1234
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:table", className: "size-3" })
1235
+ }
1236
+ )
1237
+ ] })
1238
+ ] }),
1239
+ resumeUrl && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-card-foreground/10 px-2 py-2", children: [
1240
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: tr("fluidgrids.builder.logsPanel.resumeUrl", "Resume URL") }),
1241
+ /* @__PURE__ */ jsx(
1242
+ "input",
1243
+ {
1244
+ readOnly: true,
1245
+ value: resumeUrl,
1246
+ onFocus: (e) => e.currentTarget.select(),
1247
+ className: "min-w-0 flex-1 rounded-md border border-border bg-muted/40 px-2 py-1 font-mono text-[11px] outline-none focus:border-primary"
1248
+ }
1249
+ ),
1250
+ /* @__PURE__ */ jsx(
1251
+ "button",
1252
+ {
1253
+ type: "button",
1254
+ onClick: () => void navigator.clipboard?.writeText(resumeUrl),
1255
+ className: "shrink-0 rounded-md border border-border px-2 py-1 text-[11px] font-medium hover:bg-muted",
1256
+ children: tr("fluidgrids.builder.logsPanel.copy", "Copy")
1257
+ }
1258
+ )
1259
+ ] }),
1260
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-card-foreground/10", children: [
1261
+ /* @__PURE__ */ jsx(
1262
+ "button",
1263
+ {
1264
+ className: cn(
1265
+ "px-4 py-2 text-xs font-medium transition-colors duration-200",
1266
+ activeTab === "input" ? "border-b-2 border-primary text-primary" : "text-secondary hover:text-primary"
1267
+ ),
1268
+ onClick: () => setActiveTab("input"),
1269
+ children: tr("fluidgrids.builder.logsPanel.tabInput", "Input")
1270
+ }
1271
+ ),
1272
+ /* @__PURE__ */ jsx(
1273
+ "button",
1274
+ {
1275
+ className: cn(
1276
+ "px-4 py-2 text-xs font-medium transition-colors duration-200",
1277
+ activeTab === "output" ? "border-b-2 border-primary text-primary" : "text-secondary hover:text-primary"
1278
+ ),
1279
+ onClick: () => setActiveTab("output"),
1280
+ children: tr("fluidgrids.builder.logsPanel.tabOutput", "Output")
1281
+ }
1282
+ ),
1283
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1284
+ /* @__PURE__ */ jsxs("span", { className: "px-4 py-2 text-xs text-tertiary", children: [
1285
+ itemCount,
1286
+ " ",
1287
+ itemCount !== 1 ? tr("fluidgrids.builder.logsPanel.itemCountOther", "items") : tr("fluidgrids.builder.logsPanel.itemCountOne", "item")
1288
+ ] })
1289
+ ] }),
1290
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: log.error && activeTab === "output" ? /* @__PURE__ */ jsxs("div", { className: "p-4 bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)] text-xs", children: [
1291
+ /* @__PURE__ */ jsx("div", { className: "font-medium mb-1", children: tr("fluidgrids.builder.logsPanel.errorHeading", "Error:") }),
1292
+ log.error
1293
+ ] }) : viewMode === "json" ? /* @__PURE__ */ jsx(JsonView, { data }) : /* @__PURE__ */ jsx(TableView, { data }) })
1294
+ ] });
1295
+ };
1296
+ var LogsPanel = () => {
1297
+ const tr = useTr();
1298
+ const [
1299
+ logs,
1300
+ selectedLogId,
1301
+ panelExpanded,
1302
+ selectLog,
1303
+ togglePanel,
1304
+ clearLogs
1305
+ ] = useFlowStore(
1306
+ useShallow((s) => [
1307
+ s.execution.logs,
1308
+ s.execution.selectedLogId,
1309
+ s.execution.panelExpanded,
1310
+ s.actions.execution.selectLog,
1311
+ s.actions.execution.togglePanel,
1312
+ s.actions.execution.clearLogs
1313
+ ])
1314
+ );
1315
+ const selectedLog = logs.find((l) => l.id === selectedLogId) || null;
1316
+ const [panelHeight, setPanelHeight] = useState(350);
1317
+ const isResizing = useRef(false);
1318
+ const startY = useRef(0);
1319
+ const startHeight = useRef(0);
1320
+ const handleMouseDown = useCallback(
1321
+ (e) => {
1322
+ e.preventDefault();
1323
+ isResizing.current = true;
1324
+ startY.current = e.clientY;
1325
+ startHeight.current = panelHeight;
1326
+ document.body.style.cursor = "ns-resize";
1327
+ document.body.style.userSelect = "none";
1328
+ },
1329
+ [panelHeight]
1330
+ );
1331
+ useEffect(() => {
1332
+ const handleMouseMove = (e) => {
1333
+ if (!isResizing.current) return;
1334
+ const deltaY = startY.current - e.clientY;
1335
+ const newHeight = Math.max(
1336
+ 150,
1337
+ Math.min(600, startHeight.current + deltaY)
1338
+ );
1339
+ setPanelHeight(newHeight);
1340
+ };
1341
+ const handleMouseUp = () => {
1342
+ isResizing.current = false;
1343
+ document.body.style.cursor = "";
1344
+ document.body.style.userSelect = "";
1345
+ };
1346
+ document.addEventListener("mousemove", handleMouseMove);
1347
+ document.addEventListener("mouseup", handleMouseUp);
1348
+ return () => {
1349
+ document.removeEventListener("mousemove", handleMouseMove);
1350
+ document.removeEventListener("mouseup", handleMouseUp);
1351
+ };
1352
+ }, []);
1353
+ if (!panelExpanded && logs.length === 0) {
1354
+ return null;
1355
+ }
1356
+ return /* @__PURE__ */ jsxs(
1357
+ "div",
1358
+ {
1359
+ className: cn(
1360
+ "relative border-t border-card-foreground/10 bg-card/95 backdrop-blur-sm transition-[height] duration-150",
1361
+ !panelExpanded && "h-10"
1362
+ ),
1363
+ style: { height: panelExpanded ? panelHeight : 40 },
1364
+ children: [
1365
+ panelExpanded && /* @__PURE__ */ jsx(
1366
+ "div",
1367
+ {
1368
+ className: "absolute top-0 left-0 right-0 h-1.5 cursor-ns-resize bg-transparent hover:bg-primary/30 transition-colors z-10 group",
1369
+ onMouseDown: handleMouseDown,
1370
+ children: /* @__PURE__ */ jsx("div", { className: "absolute top-0.5 left-1/2 -translate-x-1/2 w-8 h-0.5 rounded-full bg-card-foreground/20 group-hover:bg-primary/50 transition-colors" })
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ jsxs("div", { className: "h-10 flex items-center justify-between px-3 border-b border-card-foreground/10", children: [
1374
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1375
+ /* @__PURE__ */ jsx(
1376
+ Button$1,
1377
+ {
1378
+ variant: "ghost",
1379
+ size: "icon",
1380
+ className: "size-6",
1381
+ onClick: togglePanel,
1382
+ children: /* @__PURE__ */ jsx(
1383
+ Icon,
1384
+ {
1385
+ icon: panelExpanded ? "mdi:chevron-down" : "mdi:chevron-up",
1386
+ className: "size-4"
1387
+ }
1388
+ )
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: tr("fluidgrids.builder.logsPanel.title", "Logs") }),
1392
+ logs.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-tertiary", children: [
1393
+ "(",
1394
+ logs.length,
1395
+ " ",
1396
+ tr("fluidgrids.builder.logsPanel.stepsSuffix", "steps"),
1397
+ ")"
1398
+ ] })
1399
+ ] }),
1400
+ logs.length > 0 && /* @__PURE__ */ jsxs(
1401
+ Button$1,
1402
+ {
1403
+ variant: "ghost",
1404
+ size: "sm",
1405
+ className: "h-6 text-xs",
1406
+ onClick: clearLogs,
1407
+ children: [
1408
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:trash-can-outline", className: "size-3 mr-1" }),
1409
+ tr(
1410
+ "fluidgrids.builder.logsPanel.clearExecution",
1411
+ "Clear execution"
1412
+ )
1413
+ ]
1414
+ }
1415
+ )
1416
+ ] }),
1417
+ panelExpanded && /* @__PURE__ */ jsxs("div", { className: "flex h-[calc(100%-40px)]", children: [
1418
+ /* @__PURE__ */ jsx("div", { className: "w-[200px] border-r border-card-foreground/10 overflow-y-auto", children: logs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 text-xs text-tertiary text-center", children: tr(
1419
+ "fluidgrids.builder.logsPanel.emptyState",
1420
+ "No execution logs yet"
1421
+ ) }) : logs.map((log) => /* @__PURE__ */ jsx(
1422
+ LogListItem,
1423
+ {
1424
+ log,
1425
+ isSelected: log.id === selectedLogId,
1426
+ onClick: () => selectLog(log.id)
1427
+ },
1428
+ log.id
1429
+ )) }),
1430
+ /* @__PURE__ */ jsx(LogDetailView, { log: selectedLog })
1431
+ ] })
1432
+ ]
1433
+ }
1434
+ );
1435
+ };
1436
+
1437
+ export { JsonView, LogsPanel, NodeSettingsPanel, RUNNER_INSTALL_SCRIPT_URL, RunOnSection, RunnerStatusPill, SCHEDULE_NODE_TYPE, SchedulePanel, TabButton, TableView, runnerInstallCommand, useRunnerActions, useRunners };