@burdenoff/fluidgrids-fe-sdk 2026.1027.3 → 2026.1027.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 (38) hide show
  1. package/dist/{ExecutionDetailPage-HCCSFEVV.js → ExecutionDetailPage-3CSVGEQS.js} +7 -7
  2. package/dist/{ExecutionsListPage-XGYAHD6Q.js → ExecutionsListPage-M67JKEW4.js} +7 -7
  3. package/dist/{WorkflowBuilder-KZMGKXS5.js → WorkflowBuilder-IFRXDZUI.js} +9 -9
  4. package/dist/{WorkflowsList-Z275RPOC.js → WorkflowsList-53WA4UXO.js} +7 -7
  5. package/dist/{chunk-QRJ7SYR2.js → chunk-2NUE2X74.js} +1 -1
  6. package/dist/{chunk-HUOYVYXB.js → chunk-3MLOFJW3.js} +5 -5
  7. package/dist/{chunk-KEURSYHK.js → chunk-4E66IUF6.js} +1 -1
  8. package/dist/{chunk-IAPF6NJG.js → chunk-4JPNZ3M6.js} +3 -3
  9. package/dist/{chunk-HPRB4IIQ.js → chunk-5RZAO2ZF.js} +4 -4
  10. package/dist/{chunk-AQWX7PG2.js → chunk-5TRLB2QS.js} +2 -2
  11. package/dist/{chunk-PW7JSGQ2.js → chunk-72LNGKWQ.js} +29 -5
  12. package/dist/{chunk-53HVTSVI.js → chunk-B2FNI7YZ.js} +5 -5
  13. package/dist/{chunk-ABZDOQHY.js → chunk-BAPA4FLZ.js} +1 -1
  14. package/dist/{chunk-NBSNQ6I2.js → chunk-E5OMG6SN.js} +2 -2
  15. package/dist/{chunk-2FMJYM76.js → chunk-HDQZGCUY.js} +501 -50
  16. package/dist/{chunk-LMDB57LK.js → chunk-IONVLVGN.js} +3 -3
  17. package/dist/{chunk-CSJ47PYB.js → chunk-JF3VJKHF.js} +4 -4
  18. package/dist/{chunk-3UA5H5CT.js → chunk-LON27G5P.js} +1 -1
  19. package/dist/{chunk-MRL6QLBD.js → chunk-MFCT3PVR.js} +1 -1
  20. package/dist/{chunk-GI7OMVXM.js → chunk-ROG5NACW.js} +1 -1
  21. package/dist/{chunk-WXYF5RS5.js → chunk-SCPH5T5I.js} +1 -1
  22. package/dist/{chunk-SJL25YTY.js → chunk-SPJ4WF7V.js} +1 -1
  23. package/dist/{chunk-TXHY6QE4.js → chunk-SRIK33ZQ.js} +2 -2
  24. package/dist/{chunk-UNDHEXY4.js → chunk-UA6EZLBP.js} +2 -2
  25. package/dist/{chunk-VWWKBNTP.js → chunk-V7LANF6T.js} +1 -1
  26. package/dist/executions.js +9 -9
  27. package/dist/history.js +4 -4
  28. package/dist/index.d.ts +45 -3
  29. package/dist/index.js +31 -31
  30. package/dist/nodes.js +2 -2
  31. package/dist/observability.js +4 -4
  32. package/dist/settings.js +4 -4
  33. package/dist/templates.js +8 -8
  34. package/dist/{workflow-builder-Uhk2DIB6.d.ts → workflow-builder-hl-BbuPF.d.ts} +20 -16
  35. package/dist/workflow-builder.d.ts +1 -1
  36. package/dist/workflow-builder.js +10 -10
  37. package/dist/workflows-list.js +7 -7
  38. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
- import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-ABZDOQHY.js';
1
+ import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-BAPA4FLZ.js';
2
2
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
3
- import { useFlowStore } from './chunk-WXYF5RS5.js';
4
- import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery } from './chunk-QRJ7SYR2.js';
3
+ import { useFlowStore, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, throwIfResultError } from './chunk-SCPH5T5I.js';
4
+ import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery } from './chunk-2NUE2X74.js';
5
5
  import { useTr } from './chunk-UULVIT25.js';
6
+ import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
6
7
  import { useState, useMemo, useEffect, useCallback } from 'react';
7
8
  import { useNavigate, useParams } from 'react-router';
8
9
  import { Icon } from '@iconify/react';
@@ -10,6 +11,336 @@ import { Button } from '@burdenoff/fe-libs/button';
10
11
  import { Tabs, TabsList, TabsTrigger, TabsContent, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Card, CardHeader, Label, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@burdenoff/fe-libs/ui';
11
12
  import { toast } from 'sonner';
12
13
  import { jsxs, jsx } from 'react/jsx-runtime';
14
+ import { gql } from '@apollo/client';
15
+
16
+ function SettingRow({
17
+ label,
18
+ description,
19
+ children
20
+ }) {
21
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 py-4", children: [
22
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
23
+ /* @__PURE__ */ jsx(Label, { className: "text-sm font-medium text-text-primary", children: label }),
24
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary mt-0.5", children: description })
25
+ ] }),
26
+ /* @__PURE__ */ jsx("div", { className: "shrink-0", children })
27
+ ] });
28
+ }
29
+ var FIELDS = `
30
+ saveSuccessfulRuns
31
+ saveFailedRuns
32
+ saveManualRuns
33
+ retentionDays
34
+ retentionMaxRunsPerWorkflow
35
+ customized
36
+ updatedAt
37
+ `;
38
+ var RUN_DATA_SETTINGS_QUERY = gql`
39
+ query WorkflowExecutionSettings {
40
+ workflowExecutionSettings {
41
+ ${FIELDS}
42
+ }
43
+ }
44
+ `;
45
+ var UPDATE_RUN_DATA_SETTINGS = gql`
46
+ mutation UpdateWorkflowExecutionSettings($input: UpdateWorkflowExecutionSettingsInput!) {
47
+ updateWorkflowExecutionSettings(input: $input) {
48
+ ${FIELDS}
49
+ }
50
+ }
51
+ `;
52
+ function useRunDataSettings() {
53
+ const [settings, setSettings] = useState(null);
54
+ const [loading, setLoading] = useState(true);
55
+ const [error, setError] = useState(null);
56
+ useEffect(() => {
57
+ let cancelled = false;
58
+ getFluidGridsClient().query({
59
+ query: RUN_DATA_SETTINGS_QUERY,
60
+ fetchPolicy: "network-only"
61
+ }).then((result) => {
62
+ throwIfResultError(result);
63
+ if (!cancelled)
64
+ setSettings(result.data?.workflowExecutionSettings ?? null);
65
+ }).catch((err) => {
66
+ if (!cancelled)
67
+ setError(err instanceof Error ? err : new Error(String(err)));
68
+ }).finally(() => {
69
+ if (!cancelled) setLoading(false);
70
+ });
71
+ return () => {
72
+ cancelled = true;
73
+ };
74
+ }, []);
75
+ const save = useCallback(async (changes) => {
76
+ const result = await getFluidGridsClient().mutate({ mutation: UPDATE_RUN_DATA_SETTINGS, variables: { input: changes } });
77
+ throwIfResultError(result);
78
+ const saved = result.data?.updateWorkflowExecutionSettings;
79
+ if (!saved) throw new Error("No settings returned");
80
+ setSettings(saved);
81
+ return saved;
82
+ }, []);
83
+ return { settings, loading, error, save };
84
+ }
85
+
86
+ // src/modules/settings/utils/run-data.ts
87
+ function toSavingChoice(value) {
88
+ if (value === void 0) return "default";
89
+ return value ? "save" : "skip";
90
+ }
91
+ function fromSavingChoice(choice) {
92
+ if (choice === "save") return true;
93
+ if (choice === "skip") return false;
94
+ return void 0;
95
+ }
96
+ function compactOverrides(value) {
97
+ if (!value) return void 0;
98
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0);
99
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
100
+ }
101
+ var RETENTION_DAYS_RANGE = { min: 1, max: 3650 };
102
+ var RETENTION_MAX_RUNS_RANGE = { min: 1, max: 1e6 };
103
+ function parseBoundedInt(value, range) {
104
+ if (value.trim() === "") return void 0;
105
+ const parsed = Number(value);
106
+ if (!Number.isFinite(parsed)) return void 0;
107
+ return Math.min(range.max, Math.max(range.min, Math.trunc(parsed)));
108
+ }
109
+ function RunDataTab() {
110
+ const tr = useTr();
111
+ const { settings, loading, error, save } = useRunDataSettings();
112
+ const [form, setForm] = useState(null);
113
+ const [saving, setSaving] = useState(false);
114
+ const { allowed: canEdit } = useFluidGridsPermission(
115
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_DELETE
116
+ );
117
+ const locked = loading || !form || saving || !canEdit;
118
+ useEffect(() => {
119
+ if (!settings) return;
120
+ setForm({
121
+ saveSuccessfulRuns: settings.saveSuccessfulRuns,
122
+ saveFailedRuns: settings.saveFailedRuns,
123
+ saveManualRuns: settings.saveManualRuns,
124
+ retentionDays: String(settings.retentionDays),
125
+ retentionMaxRunsPerWorkflow: String(settings.retentionMaxRunsPerWorkflow)
126
+ });
127
+ }, [settings]);
128
+ const handleSave = async () => {
129
+ if (!form) return;
130
+ const retentionDays = parseBoundedInt(
131
+ form.retentionDays,
132
+ RETENTION_DAYS_RANGE
133
+ );
134
+ const retentionMaxRunsPerWorkflow = parseBoundedInt(
135
+ form.retentionMaxRunsPerWorkflow,
136
+ RETENTION_MAX_RUNS_RANGE
137
+ );
138
+ if (retentionDays === void 0 || retentionMaxRunsPerWorkflow === void 0) {
139
+ toast.error(
140
+ tr(
141
+ "fluidgrids.settings.runData.numbersRequired",
142
+ "Enter a number of days and a number of runs."
143
+ )
144
+ );
145
+ return;
146
+ }
147
+ setSaving(true);
148
+ try {
149
+ await save({
150
+ saveSuccessfulRuns: form.saveSuccessfulRuns,
151
+ saveFailedRuns: form.saveFailedRuns,
152
+ saveManualRuns: form.saveManualRuns,
153
+ retentionDays,
154
+ retentionMaxRunsPerWorkflow
155
+ });
156
+ toast.success(
157
+ tr("fluidgrids.settings.runData.savedToast", "Run data settings saved")
158
+ );
159
+ } catch (err) {
160
+ toast.error(
161
+ tr(
162
+ "fluidgrids.settings.runData.saveFailedToast",
163
+ "Failed to save settings"
164
+ ),
165
+ { description: err instanceof Error ? err.message : void 0 }
166
+ );
167
+ } finally {
168
+ setSaving(false);
169
+ }
170
+ };
171
+ if (error) {
172
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", role: "alert", children: tr(
173
+ "fluidgrids.settings.runData.loadFailed",
174
+ "Could not load the run data settings."
175
+ ) });
176
+ }
177
+ const setFlag = (key) => (checked) => setForm((f) => f ? { ...f, [key]: checked } : f);
178
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "run-data-settings", children: [
179
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 rounded-lg bg-background-secondary border border-border-secondary mb-6", children: [
180
+ /* @__PURE__ */ jsx(
181
+ Icon,
182
+ {
183
+ icon: "mdi:database-clock-outline",
184
+ width: 18,
185
+ className: "text-text-secondary shrink-0 mt-0.5"
186
+ }
187
+ ),
188
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
189
+ "fluidgrids.settings.runData.intro",
190
+ "These apply to every workflow in this workspace unless its own Execution settings say otherwise. A run whose data is not kept still shows its status, timings and error."
191
+ ) })
192
+ ] }),
193
+ !canEdit && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary mb-4", role: "note", children: tr(
194
+ "fluidgrids.settings.runData.readOnly",
195
+ "You can see these settings but not change them: changing them needs permission to delete workflows."
196
+ ) }),
197
+ /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border-secondary", children: [
198
+ /* @__PURE__ */ jsx(
199
+ SettingRow,
200
+ {
201
+ label: tr(
202
+ "fluidgrids.settings.runData.saveSuccessfulLabel",
203
+ "Keep data of successful runs"
204
+ ),
205
+ description: tr(
206
+ "fluidgrids.settings.runData.saveSuccessfulDescription",
207
+ "Trigger data, node inputs and outputs, and logs of runs that finished successfully."
208
+ ),
209
+ children: /* @__PURE__ */ jsx(
210
+ Switch,
211
+ {
212
+ "aria-label": tr(
213
+ "fluidgrids.settings.runData.saveSuccessfulLabel",
214
+ "Keep data of successful runs"
215
+ ),
216
+ checked: form?.saveSuccessfulRuns ?? true,
217
+ onCheckedChange: setFlag("saveSuccessfulRuns"),
218
+ disabled: locked
219
+ }
220
+ )
221
+ }
222
+ ),
223
+ /* @__PURE__ */ jsx(
224
+ SettingRow,
225
+ {
226
+ label: tr(
227
+ "fluidgrids.settings.runData.saveFailedLabel",
228
+ "Keep data of failed runs"
229
+ ),
230
+ description: tr(
231
+ "fluidgrids.settings.runData.saveFailedDescription",
232
+ "Runs that failed, were cancelled or were stopped."
233
+ ),
234
+ children: /* @__PURE__ */ jsx(
235
+ Switch,
236
+ {
237
+ "aria-label": tr(
238
+ "fluidgrids.settings.runData.saveFailedLabel",
239
+ "Keep data of failed runs"
240
+ ),
241
+ checked: form?.saveFailedRuns ?? true,
242
+ onCheckedChange: setFlag("saveFailedRuns"),
243
+ disabled: locked
244
+ }
245
+ )
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx(
249
+ SettingRow,
250
+ {
251
+ label: tr(
252
+ "fluidgrids.settings.runData.saveManualLabel",
253
+ "Keep data of test runs"
254
+ ),
255
+ description: tr(
256
+ "fluidgrids.settings.runData.saveManualDescription",
257
+ "Runs started from the editor to test a workflow."
258
+ ),
259
+ children: /* @__PURE__ */ jsx(
260
+ Switch,
261
+ {
262
+ "aria-label": tr(
263
+ "fluidgrids.settings.runData.saveManualLabel",
264
+ "Keep data of test runs"
265
+ ),
266
+ checked: form?.saveManualRuns ?? true,
267
+ onCheckedChange: setFlag("saveManualRuns"),
268
+ disabled: locked
269
+ }
270
+ )
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsx(
274
+ SettingRow,
275
+ {
276
+ label: tr(
277
+ "fluidgrids.settings.runData.retentionDaysLabel",
278
+ "Delete runs after"
279
+ ),
280
+ description: tr(
281
+ "fluidgrids.settings.runData.retentionDaysDescription",
282
+ "Finished runs older than this are deleted, with their data. If your plan keeps run history for less time, the plan's limit applies."
283
+ ),
284
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
285
+ /* @__PURE__ */ jsx(
286
+ Input,
287
+ {
288
+ type: "number",
289
+ min: RETENTION_DAYS_RANGE.min,
290
+ max: RETENTION_DAYS_RANGE.max,
291
+ "aria-label": tr(
292
+ "fluidgrids.settings.runData.retentionDaysLabel",
293
+ "Delete runs after"
294
+ ),
295
+ value: form?.retentionDays ?? "",
296
+ onChange: (e) => {
297
+ const retentionDays = e.target.value;
298
+ setForm((f) => f ? { ...f, retentionDays } : f);
299
+ },
300
+ disabled: locked,
301
+ className: "w-[100px] text-right"
302
+ }
303
+ ),
304
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: tr("fluidgrids.settings.runData.daysUnit", "days") })
305
+ ] })
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx(
309
+ SettingRow,
310
+ {
311
+ label: tr(
312
+ "fluidgrids.settings.runData.maxRunsLabel",
313
+ "Runs kept per workflow"
314
+ ),
315
+ description: tr(
316
+ "fluidgrids.settings.runData.maxRunsDescription",
317
+ "Beyond this many finished runs, the oldest are deleted."
318
+ ),
319
+ children: /* @__PURE__ */ jsx(
320
+ Input,
321
+ {
322
+ type: "number",
323
+ min: RETENTION_MAX_RUNS_RANGE.min,
324
+ max: RETENTION_MAX_RUNS_RANGE.max,
325
+ "aria-label": tr(
326
+ "fluidgrids.settings.runData.maxRunsLabel",
327
+ "Runs kept per workflow"
328
+ ),
329
+ value: form?.retentionMaxRunsPerWorkflow ?? "",
330
+ onChange: (e) => {
331
+ const retentionMaxRunsPerWorkflow = e.target.value;
332
+ setForm((f) => f ? { ...f, retentionMaxRunsPerWorkflow } : f);
333
+ },
334
+ disabled: locked,
335
+ className: "w-[120px] text-right"
336
+ }
337
+ )
338
+ }
339
+ )
340
+ ] }),
341
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsx(Button, { onClick: () => void handleSave(), disabled: locked, children: saving ? tr("fluidgrids.settings.runData.saving", "Saving\u2026") : tr("fluidgrids.settings.runData.save", "Save") }) })
342
+ ] });
343
+ }
13
344
 
14
345
  // src/modules/settings/utils/error-trigger.ts
15
346
  var ERROR_TRIGGER_NODE_TYPE = "internal:error-trigger";
@@ -26,24 +357,45 @@ function hasErrorTriggerNode(definitionJson) {
26
357
  // src/modules/settings/utils/optional-positive-int.ts
27
358
  function parseOptionalPositiveInt(value) {
28
359
  if (value.trim() === "") return void 0;
29
- const parsed = Number.parseInt(value, 10);
30
- return Number.isFinite(parsed) ? Math.max(1, parsed) : void 0;
360
+ const parsed = Number(value);
361
+ return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : void 0;
31
362
  }
32
363
 
33
364
  // src/modules/settings/utils/workflow-settings-persistence.ts
365
+ var DATA_SAVING_KEYS = [
366
+ "saveSuccessExecutions",
367
+ "saveFailedExecutions",
368
+ "saveManualExecutions"
369
+ ];
370
+ var RETENTION_KEYS = [
371
+ "maxAgeDays",
372
+ "maxRuns"
373
+ ];
34
374
  function mergeWorkflowExecutionSettings(existing, next) {
35
- const existingDataSaving = existing.dataSaving;
36
- const nextDataSaving = next.dataSaving;
37
- return {
38
- ...existing,
39
- ...next,
40
- ...existingDataSaving || nextDataSaving ? {
41
- dataSaving: {
42
- ...isRecord(existingDataSaving) ? existingDataSaving : {},
43
- ...nextDataSaving ?? {}
44
- }
45
- } : {}
46
- };
375
+ const merged = { ...existing, ...next };
376
+ for (const [key, editable] of [
377
+ ["dataSaving", DATA_SAVING_KEYS],
378
+ ["retention", RETENTION_KEYS]
379
+ ]) {
380
+ const nested = mergeNested(existing[key], next[key], editable);
381
+ if (nested) merged[key] = nested;
382
+ else delete merged[key];
383
+ }
384
+ return merged;
385
+ }
386
+ function mergeNested(existing, next, editable) {
387
+ const editableKeys = new Set(editable);
388
+ const kept = Object.fromEntries(
389
+ Object.entries(isRecord(existing) ? existing : {}).filter(
390
+ ([key]) => !editableKeys.has(key)
391
+ )
392
+ );
393
+ const result = Object.fromEntries(
394
+ Object.entries({ ...kept, ...next ?? {} }).filter(
395
+ ([, value]) => value !== void 0
396
+ )
397
+ );
398
+ return Object.keys(result).length > 0 ? result : void 0;
47
399
  }
48
400
  function isRecord(value) {
49
401
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -120,19 +472,6 @@ function CopyButton({ value, label }) {
120
472
  ] })
121
473
  ] }) });
122
474
  }
123
- function SettingRow({
124
- label,
125
- description,
126
- children
127
- }) {
128
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 py-4", children: [
129
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
130
- /* @__PURE__ */ jsx(Label, { className: "text-sm font-medium text-text-primary", children: label }),
131
- description && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary mt-0.5", children: description })
132
- ] }),
133
- /* @__PURE__ */ jsx("div", { className: "shrink-0", children })
134
- ] });
135
- }
136
475
  function GeneralTab() {
137
476
  const tr = useTr();
138
477
  const settings = DEFAULT_SETTINGS.general;
@@ -206,6 +545,10 @@ function ExecutionTab({ workflowKey }) {
206
545
  fetchPolicy: "cache-and-network"
207
546
  });
208
547
  const [updateWorkflowDefinition, { loading: saving }] = useUpdateWorkflowDefinitionMutation();
548
+ const { allowed: canEdit } = useFluidGridsPermission(
549
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE
550
+ );
551
+ const locked = loading || saving || !canEdit;
209
552
  const { data: otherWorkflowsData, loading: otherWorkflowsLoading } = useWorkflowDefinitionsQuery({
210
553
  variables: { input: { latestOnly: true, limit: 100 } }
211
554
  });
@@ -218,6 +561,7 @@ function ExecutionTab({ workflowKey }) {
218
561
  }, [workflow?.definitionJson]);
219
562
  const [form, setForm] = useState(persistedConfig);
220
563
  useEffect(() => setForm(persistedConfig), [persistedConfig]);
564
+ const runData = useRunDataSettings();
221
565
  const selectedErrorWorkflowKey = form.errorWorkflowKey;
222
566
  const { data: errorWorkflowData } = useWorkflowDefinitionQuery({
223
567
  variables: { workflowKey: selectedErrorWorkflowKey ?? "" },
@@ -335,7 +679,7 @@ function ExecutionTab({ workflowKey }) {
335
679
  ...f,
336
680
  timeout: parseOptionalPositiveInt(e.target.value)
337
681
  })),
338
- disabled: loading,
682
+ disabled: locked,
339
683
  className: "w-[100px] text-right"
340
684
  }
341
685
  ),
@@ -375,7 +719,7 @@ function ExecutionTab({ workflowKey }) {
375
719
  ...f,
376
720
  maxParallelism: parseOptionalPositiveInt(e.target.value)
377
721
  })),
378
- disabled: loading,
722
+ disabled: locked,
379
723
  className: "w-[100px] text-right"
380
724
  }
381
725
  )
@@ -400,7 +744,7 @@ function ExecutionTab({ workflowKey }) {
400
744
  ...f,
401
745
  onError: e.target.value
402
746
  })),
403
- disabled: loading,
747
+ disabled: locked,
404
748
  className: "w-[160px] rounded-md border border-border-secondary bg-background-secondary px-2 py-1.5 text-sm text-text-primary",
405
749
  children: [
406
750
  /* @__PURE__ */ jsx("option", { value: "stop", children: tr("fluidgrids.settings.execution.onErrorStop", "Stop") }),
@@ -431,7 +775,7 @@ function ExecutionTab({ workflowKey }) {
431
775
  ...f,
432
776
  errorWorkflowKey: val === "__none__" ? void 0 : val
433
777
  })),
434
- disabled: loading || otherWorkflowsLoading,
778
+ disabled: locked || otherWorkflowsLoading,
435
779
  children: [
436
780
  /* @__PURE__ */ jsx(SelectTrigger, { className: "w-[220px] bg-background-secondary border-border-secondary", children: /* @__PURE__ */ jsx(
437
781
  SelectValue,
@@ -466,22 +810,104 @@ function ExecutionTab({ workflowKey }) {
466
810
  ] })
467
811
  }
468
812
  ),
813
+ [
814
+ {
815
+ key: "saveSuccessExecutions",
816
+ workspaceKey: "saveSuccessfulRuns",
817
+ labelKey: "fluidgrids.settings.execution.saveSuccessLabel",
818
+ label: "Keep data of successful runs"
819
+ },
820
+ {
821
+ key: "saveFailedExecutions",
822
+ workspaceKey: "saveFailedRuns",
823
+ labelKey: "fluidgrids.settings.execution.saveFailedLabel",
824
+ label: "Keep data of failed runs"
825
+ },
826
+ {
827
+ key: "saveManualExecutions",
828
+ workspaceKey: "saveManualRuns",
829
+ labelKey: "fluidgrids.settings.execution.saveManualLabel",
830
+ label: "Keep data of test runs"
831
+ }
832
+ ].map((row) => {
833
+ const inherited = runData.settings?.[row.workspaceKey];
834
+ return /* @__PURE__ */ jsx(SettingRow, { label: tr(row.labelKey, row.label), children: /* @__PURE__ */ jsxs(
835
+ Select,
836
+ {
837
+ value: toSavingChoice(form.dataSaving?.[row.key]),
838
+ onValueChange: (choice) => setForm((f) => ({
839
+ ...f,
840
+ dataSaving: compactOverrides({
841
+ ...f.dataSaving,
842
+ [row.key]: fromSavingChoice(choice)
843
+ })
844
+ })),
845
+ disabled: locked,
846
+ children: [
847
+ /* @__PURE__ */ jsx(
848
+ SelectTrigger,
849
+ {
850
+ "aria-label": tr(row.labelKey, row.label),
851
+ className: "w-[220px] bg-background-secondary border-border-secondary",
852
+ children: /* @__PURE__ */ jsx(SelectValue, {})
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
856
+ /* @__PURE__ */ jsx(SelectItem, { value: "default", children: inherited === void 0 ? tr(
857
+ "fluidgrids.settings.execution.savingDefault",
858
+ "Workspace default"
859
+ ) : inherited ? tr(
860
+ "fluidgrids.settings.execution.savingDefaultOn",
861
+ "Workspace default (keep)"
862
+ ) : tr(
863
+ "fluidgrids.settings.execution.savingDefaultOff",
864
+ "Workspace default (don't keep)"
865
+ ) }),
866
+ /* @__PURE__ */ jsx(SelectItem, { value: "save", children: tr("fluidgrids.settings.execution.savingKeep", "Keep") }),
867
+ /* @__PURE__ */ jsx(SelectItem, { value: "skip", children: tr(
868
+ "fluidgrids.settings.execution.savingDontKeep",
869
+ "Don't keep"
870
+ ) })
871
+ ] })
872
+ ]
873
+ }
874
+ ) }, row.key);
875
+ }),
469
876
  /* @__PURE__ */ jsx(
470
877
  SettingRow,
471
878
  {
472
879
  label: tr(
473
- "fluidgrids.settings.execution.saveSuccessLabel",
474
- "Save successful execution data"
880
+ "fluidgrids.settings.execution.retentionDaysLabel",
881
+ "Delete runs after (days)"
882
+ ),
883
+ description: tr(
884
+ "fluidgrids.settings.execution.retentionDaysDescription",
885
+ "Leave empty to follow the workspace's Run data settings."
475
886
  ),
476
887
  children: /* @__PURE__ */ jsx(
477
- Switch,
888
+ Input,
478
889
  {
479
- checked: form.dataSaving?.saveSuccessExecutions ?? true,
480
- onCheckedChange: (checked) => setForm((f) => ({
890
+ type: "number",
891
+ min: RETENTION_DAYS_RANGE.min,
892
+ max: RETENTION_DAYS_RANGE.max,
893
+ "aria-label": tr(
894
+ "fluidgrids.settings.execution.retentionDaysLabel",
895
+ "Delete runs after (days)"
896
+ ),
897
+ value: form.retention?.maxAgeDays ?? "",
898
+ placeholder: runData.settings ? String(runData.settings.retentionDays) : "",
899
+ onChange: (e) => setForm((f) => ({
481
900
  ...f,
482
- dataSaving: { ...f.dataSaving, saveSuccessExecutions: checked }
901
+ retention: compactOverrides({
902
+ ...f.retention,
903
+ maxAgeDays: parseBoundedInt(
904
+ e.target.value,
905
+ RETENTION_DAYS_RANGE
906
+ )
907
+ })
483
908
  })),
484
- disabled: loading
909
+ disabled: locked,
910
+ className: "w-[100px] text-right"
485
911
  }
486
912
  )
487
913
  }
@@ -489,25 +915,45 @@ function ExecutionTab({ workflowKey }) {
489
915
  /* @__PURE__ */ jsx(
490
916
  SettingRow,
491
917
  {
492
- label: tr(
493
- "fluidgrids.settings.execution.saveFailedLabel",
494
- "Save failed execution data"
918
+ label: tr("fluidgrids.settings.execution.maxRunsLabel", "Runs kept"),
919
+ description: tr(
920
+ "fluidgrids.settings.execution.maxRunsDescription",
921
+ "Leave empty to follow the workspace's Run data settings."
495
922
  ),
496
923
  children: /* @__PURE__ */ jsx(
497
- Switch,
924
+ Input,
498
925
  {
499
- checked: form.dataSaving?.saveFailedExecutions ?? true,
500
- onCheckedChange: (checked) => setForm((f) => ({
926
+ type: "number",
927
+ min: RETENTION_MAX_RUNS_RANGE.min,
928
+ max: RETENTION_MAX_RUNS_RANGE.max,
929
+ "aria-label": tr(
930
+ "fluidgrids.settings.execution.maxRunsLabel",
931
+ "Runs kept"
932
+ ),
933
+ value: form.retention?.maxRuns ?? "",
934
+ placeholder: runData.settings ? String(runData.settings.retentionMaxRunsPerWorkflow) : "",
935
+ onChange: (e) => setForm((f) => ({
501
936
  ...f,
502
- dataSaving: { ...f.dataSaving, saveFailedExecutions: checked }
937
+ retention: compactOverrides({
938
+ ...f.retention,
939
+ maxRuns: parseBoundedInt(
940
+ e.target.value,
941
+ RETENTION_MAX_RUNS_RANGE
942
+ )
943
+ })
503
944
  })),
504
- disabled: loading
945
+ disabled: locked,
946
+ className: "w-[120px] text-right"
505
947
  }
506
948
  )
507
949
  }
508
950
  )
509
951
  ] }),
510
- /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsx(Button, { onClick: () => void handleSave(), disabled: loading || saving, children: saving ? tr("fluidgrids.settings.execution.saving", "Saving\u2026") : tr("fluidgrids.settings.execution.save", "Save") }) })
952
+ !canEdit && /* @__PURE__ */ jsx("p", { className: "mt-4 text-xs text-text-secondary", role: "note", children: tr(
953
+ "fluidgrids.settings.execution.readOnly",
954
+ "You can see these settings but not change them: changing them needs permission to edit workflows."
955
+ ) }),
956
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsx(Button, { onClick: () => void handleSave(), disabled: locked, children: saving ? tr("fluidgrids.settings.execution.saving", "Saving\u2026") : tr("fluidgrids.settings.execution.save", "Save") }) })
511
957
  ] });
512
958
  }
513
959
  function NotificationsTab() {
@@ -760,6 +1206,10 @@ function SettingsPage({
760
1206
  ]
761
1207
  }
762
1208
  ),
1209
+ /* @__PURE__ */ jsxs(TabsTrigger, { value: "run-data", className: "gap-1.5", children: [
1210
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:database-clock-outline", width: 16 }),
1211
+ tr("fluidgrids.settings.page.runDataTab", "Run data")
1212
+ ] }),
763
1213
  /* @__PURE__ */ jsxs(TabsTrigger, { value: "notifications", className: "gap-1.5", children: [
764
1214
  /* @__PURE__ */ jsx(Icon, { icon: "mdi:bell-outline", width: 16 }),
765
1215
  tr("fluidgrids.settings.page.notificationsTab", "Notifications")
@@ -785,6 +1235,7 @@ function SettingsPage({
785
1235
  ),
786
1236
  /* @__PURE__ */ jsx(TabsContent, { value: "general", children: /* @__PURE__ */ jsx(GeneralTab, {}) }),
787
1237
  /* @__PURE__ */ jsx(TabsContent, { value: "execution", children: /* @__PURE__ */ jsx(ExecutionTab, { workflowKey }) }),
1238
+ /* @__PURE__ */ jsx(TabsContent, { value: "run-data", children: /* @__PURE__ */ jsx(RunDataTab, {}) }),
788
1239
  /* @__PURE__ */ jsx(TabsContent, { value: "notifications", children: /* @__PURE__ */ jsx(NotificationsTab, {}) }),
789
1240
  logStreamsEnabled ? /* @__PURE__ */ jsx(TabsContent, { value: "log-streaming", children: /* @__PURE__ */ jsx(LogStreamingTab, {}) }) : null,
790
1241
  /* @__PURE__ */ jsx(TabsContent, { value: "api", children: /* @__PURE__ */ jsx(ApiTab, {}) })
@@ -1,8 +1,8 @@
1
- import { Card, FluidGridsMark } from './chunk-HUOYVYXB.js';
1
+ import { Card, FluidGridsMark } from './chunk-3MLOFJW3.js';
2
2
  import { Input } from './chunk-IEVSFCFS.js';
3
3
  import { Button } from './chunk-BQXVT32O.js';
4
- import { useWorkflowDefinition } from './chunk-AQWX7PG2.js';
5
- import { FluidGridsProvider, useFlowStore } from './chunk-WXYF5RS5.js';
4
+ import { useWorkflowDefinition } from './chunk-5TRLB2QS.js';
5
+ import { FluidGridsProvider, useFlowStore } from './chunk-SCPH5T5I.js';
6
6
  import { cn } from './chunk-JL7CASN5.js';
7
7
  import { useTr } from './chunk-UULVIT25.js';
8
8
  import { useState, useRef, useEffect, useCallback } from 'react';