@burdenoff/fluidgrids-fe-sdk 2026.1111.17 → 2026.1111.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/{AiStudioPage-kJHSsIcj.js → AiStudioPage-BHVEU4Wp.js} +2 -2
  2. package/dist/{SettingsPage-CgkRlCM5.js → SettingsPage-CJLZvv4J.js} +1485 -1484
  3. package/dist/{TemplatePreview-GthzCOSo.js → TemplatePreview-FayF6jxh.js} +1 -1
  4. package/dist/{WorkflowBuilder-DEi_9YOb.js → WorkflowBuilder-CPnXEL5J.js} +1 -1
  5. package/dist/{WorkflowBuilder-iPqXgbYr.js → WorkflowBuilder-YXRysvZc.js} +2 -2
  6. package/dist/WorkflowShareDialog-mbIwRQUY.js +168 -0
  7. package/dist/{WorkflowsList-CA20dVIl.js → WorkflowsList-DiiGyZkT.js} +2 -1
  8. package/dist/{agentTools-CkRJB7r5.js → agentTools-8s_25Q_F.js} +1 -1
  9. package/dist/{canvasNoteTools-CQIc7Oxg.js → canvasNoteTools-B6pYARgU.js} +1 -1
  10. package/dist/code-editor-field-9-GdVE_8.js +111 -0
  11. package/dist/{flow-builder-y2Y1iG4Y.js → flow-builder-CZDKRc6f.js} +245 -8638
  12. package/dist/{index-W2o__Nm6.d.ts → index-6JR2vD9J.d.ts} +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +13 -11
  15. package/dist/{node-detail-view-B91kXzIy.js → node-detail-view-CV4ZRwqh.js} +4 -2
  16. package/dist/node-editor-tab-body-DU-dh35X.js +174 -0
  17. package/dist/observability.d.ts +1 -1
  18. package/dist/popover-BJHvrFGR.js +472 -0
  19. package/dist/{quick-config-panel-DggPl1vY.js → quick-config-panel-ws3pLvbt.js} +2 -1
  20. package/dist/schedule-panel-C0mfGQSD.js +7542 -0
  21. package/dist/settings.js +1 -1
  22. package/dist/sidebar-node-editor-CR1PiZO-.js +177 -0
  23. package/dist/templates.js +1 -1
  24. package/dist/{useAgentDefinitions-Dpz0HDTq.js → useAgentDefinitions-B0dam3_o.js} +1 -396
  25. package/dist/useConnections-Cs7LCWEM.js +401 -0
  26. package/dist/{WorkflowShareDialog-B_gn968c.js → useWorkflowCodeExport-Bu2QYJzA.js} +4 -161
  27. package/dist/workflow-builder.js +2 -2
  28. package/dist/workflow-pages.d.ts +1 -1
  29. package/dist/workflow-pages.js +5 -5
  30. package/dist/workflows-list.js +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1,472 @@
1
+ "use client";
2
+ import { An as newConnectionProviderKey, Dn as connectionsForAdapter, On as isOptionalAdapter, jn as replaceAdapterConnections, kn as matchesAdapterProvider } from "./FluidGridsProvider-dvZSNtSW.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
+ import { r as useAgentsUsingConnection } from "./useAgentDefinitions-B0dam3_o.js";
5
+ import { t as cn } from "./cn-QqidFIgr.js";
6
+ import { o as ConnectionFormDialog } from "./flow-builder-CZDKRc6f.js";
7
+ import { a as SdkPermissionGate, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
8
+ import { n as useConnections } from "./useConnections-Cs7LCWEM.js";
9
+ import { a as useRunners, t as RunnerStatusPill } from "./RunnerStatusPill-Ctg4TJcP.js";
10
+ import * as React$1 from "react";
11
+ import { useCallback, useMemo, useState } from "react";
12
+ import { Icon } from "@iconify/react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@burdenoff/fe-libs/ui";
15
+ import { ProviderIcon } from "@burdenoff/fe-libs/connections";
16
+ import { formatDistanceToNow } from "date-fns";
17
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
18
+
19
+ // src/modules/agent-definitions/components/ConnectionAgentUsage.tsx
20
+ /**
21
+ * A connection's "used by agents" list (BOFF-9962).
22
+ *
23
+ * Shows which agents may use a connection, so whoever edits or reuses it knows an agent can act
24
+ * with it. The list comes from `agentDefinitionsUsingConnection`; it renders nothing when no agent
25
+ * uses the connection or the viewer cannot read agent definitions.
26
+ */
27
+ function ConnectionAgentUsage({ connectionId, connectionName }) {
28
+ const tr = useTr();
29
+ const agents = useAgentsUsingConnection(connectionId);
30
+ if (agents.length === 0) return null;
31
+ return /* @__PURE__ */ jsxs("div", {
32
+ className: "flex items-start gap-1.5 text-xs text-muted-foreground",
33
+ "data-testid": `connection-agent-usage-${connectionId}`,
34
+ children: [/* @__PURE__ */ jsx(Icon, {
35
+ icon: "mdi:robot-outline",
36
+ className: "w-3.5 h-3.5 mt-0.5 shrink-0"
37
+ }), /* @__PURE__ */ jsxs("span", { children: [
38
+ connectionName ? tr("fluidgrids.agents.usedBy.namedLabel", "{{connection}} is used by agents:", { connection: connectionName }) : tr("fluidgrids.agents.usedBy.label", "Used by agents:"),
39
+ " ",
40
+ agents.map((agent) => agent.status === "ACTIVE" ? agent.name : tr("fluidgrids.agents.usedBy.disabled", "{{name}} (disabled)", { name: agent.name })).join(", ")
41
+ ] })]
42
+ });
43
+ }
44
+
45
+ // src/modules/connections/components/ConnectionSelector.tsx
46
+ /**
47
+ * ConnectionSelector Component
48
+ *
49
+ * A dropdown selector for choosing integration connections in workflow nodes.
50
+ * Replaces the legacy CredentialSelector and queries the integrations service.
51
+ *
52
+ * This component:
53
+ * - Fetches connections from wspace-integrations-svc via the federated gateway
54
+ * - Filters connections by provider adapters defined in node manifests
55
+ * - Supports single and multiple selection modes
56
+ * - Opens an inline ConnectionFormDialog popup for creating new connections (n8n-style)
57
+ */
58
+ function formatAge(dateString) {
59
+ try {
60
+ return formatDistanceToNow(new Date(dateString), { addSuffix: true });
61
+ } catch {
62
+ return "";
63
+ }
64
+ }
65
+ function getStatusColor(status) {
66
+ switch (status) {
67
+ case "ACTIVE": return "text-[var(--color-status-success-text)]";
68
+ case "INACTIVE": return "text-text-secondary";
69
+ case "ERROR":
70
+ case "REVOKED": return "text-[var(--color-status-error-text)]";
71
+ case "EXPIRED":
72
+ case "PENDING_AUTH": return "text-[var(--color-status-warning-text)]";
73
+ default: return "text-text-secondary";
74
+ }
75
+ }
76
+ function getStatusIcon(status) {
77
+ switch (status) {
78
+ case "ACTIVE": return "mdi:check-circle";
79
+ case "INACTIVE": return "mdi:pause-circle";
80
+ case "ERROR":
81
+ case "REVOKED": return "mdi:alert-circle";
82
+ case "EXPIRED": return "mdi:clock-alert";
83
+ case "PENDING_AUTH": return "mdi:account-clock";
84
+ default: return "mdi:help-circle";
85
+ }
86
+ }
87
+ function ConnectionChip({ connection, connectionData, onRemove }) {
88
+ const tr = useTr();
89
+ const name = connectionData?.name || connection.connectionId;
90
+ const status = connectionData?.status;
91
+ return /* @__PURE__ */ jsxs("div", {
92
+ className: "flex items-center gap-1.5 px-2 py-1 bg-accent/30 rounded-md text-sm group",
93
+ children: [
94
+ /* @__PURE__ */ jsx(ProviderIcon, {
95
+ variant: "bare",
96
+ provider: connectionData?.provider ?? {
97
+ key: connection.adapter,
98
+ icon: void 0,
99
+ category: "CUSTOM"
100
+ },
101
+ size: 16
102
+ }),
103
+ /* @__PURE__ */ jsx("span", {
104
+ className: "truncate max-w-32",
105
+ children: name
106
+ }),
107
+ status && /* @__PURE__ */ jsx(Icon, {
108
+ icon: getStatusIcon(status),
109
+ className: `w-3.5 h-3.5 ${getStatusColor(status)}`
110
+ }),
111
+ /* @__PURE__ */ jsx("button", {
112
+ type: "button",
113
+ "aria-label": tr("fluidgrids.connections.connectionSelector.removeConnection", "Remove connection"),
114
+ onClick: (e) => {
115
+ e.stopPropagation();
116
+ onRemove();
117
+ },
118
+ className: "text-text-secondary hover:text-status-error-text opacity-0 group-hover:opacity-100 transition-opacity",
119
+ children: /* @__PURE__ */ jsx(Icon, {
120
+ icon: "mdi:close",
121
+ className: "w-3.5 h-3.5"
122
+ })
123
+ })
124
+ ]
125
+ });
126
+ }
127
+ function ConnectionSelector(props) {
128
+ const connectionList = useConnections();
129
+ if (props.adapters.length <= 1) return /* @__PURE__ */ jsx(AdapterConnectionSelector, {
130
+ ...props,
131
+ connectionList
132
+ });
133
+ return /* @__PURE__ */ jsx("div", {
134
+ className: props.compact ? "space-y-2" : "space-y-3",
135
+ children: props.adapters.map((adapter) => /* @__PURE__ */ jsxs("fieldset", {
136
+ className: "min-w-0 space-y-1.5",
137
+ children: [/* @__PURE__ */ jsx("legend", {
138
+ className: "text-xs font-medium text-text-secondary",
139
+ children: adapter.name
140
+ }), /* @__PURE__ */ jsx(AdapterConnectionSelector, {
141
+ ...props,
142
+ adapters: [adapter],
143
+ selectedConnections: connectionsForAdapter(props.selectedConnections, adapter.name),
144
+ onChange: (connections) => props.onChange(replaceAdapterConnections(props.selectedConnections, adapter.name, connections.map((connection) => connection.connectionId))),
145
+ showRequired: false,
146
+ connectionList
147
+ })]
148
+ }, adapter.name))
149
+ });
150
+ }
151
+ function AdapterConnectionSelector({ adapters, selectedConnections, onChange, multiple = false, showStatus = true, compact = false, showRequired = true, connectionList }) {
152
+ const tr = useTr();
153
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
154
+ const [dialogOpen, setDialogOpen] = useState(false);
155
+ const [dialogProviderKey, setDialogProviderKey] = useState();
156
+ const { connections, loading: connectionsLoading, refetch } = connectionList;
157
+ const availableConnections = useMemo(() => {
158
+ if (!adapters || adapters.length === 0) return [];
159
+ return connections.filter((connection) => connection.canUse !== false && adapters.some((adapter) => matchesAdapterProvider(adapter, connection.provider?.key ?? "")));
160
+ }, [connections, adapters]);
161
+ const mergedConnections = useMemo(() => {
162
+ const allConnections = [...availableConnections];
163
+ selectedConnections.forEach((selected) => {
164
+ if (!availableConnections.some((c) => c.id === selected.connectionId)) {
165
+ const fullConn = connections.find((c) => c.id === selected.connectionId);
166
+ if (fullConn && fullConn.canUse !== false) allConnections.push(fullConn);
167
+ }
168
+ });
169
+ return allConnections;
170
+ }, [
171
+ availableConnections,
172
+ selectedConnections,
173
+ connections
174
+ ]);
175
+ const groupedConnections = useMemo(() => {
176
+ const groups = {};
177
+ mergedConnections.forEach((conn) => {
178
+ const providerName = conn.provider?.name || "Other";
179
+ if (!groups[providerName]) groups[providerName] = [];
180
+ groups[providerName].push(conn);
181
+ });
182
+ return groups;
183
+ }, [mergedConnections]);
184
+ const handleSelect = useCallback((connection) => {
185
+ const providerKey = connection.provider?.key || "";
186
+ const adapter = adapters.find((candidate) => matchesAdapterProvider(candidate, providerKey));
187
+ if (!adapter) return;
188
+ const newSelection = {
189
+ connectionId: connection.id,
190
+ adapter: adapter.name
191
+ };
192
+ if (multiple) {
193
+ if (selectedConnections.some((c) => c.connectionId === connection.id)) onChange(selectedConnections.filter((c) => c.connectionId !== connection.id));
194
+ else onChange([...selectedConnections, newSelection]);
195
+ } else {
196
+ onChange([newSelection]);
197
+ setIsDropdownOpen(false);
198
+ }
199
+ }, [
200
+ adapters,
201
+ selectedConnections,
202
+ onChange,
203
+ multiple
204
+ ]);
205
+ const handleRemove = useCallback((connectionId) => {
206
+ onChange(selectedConnections.filter((c) => c.connectionId !== connectionId));
207
+ }, [selectedConnections, onChange]);
208
+ const handleNewConnection = useCallback((providerKey) => {
209
+ setDialogProviderKey(providerKey);
210
+ setDialogOpen(true);
211
+ setIsDropdownOpen(false);
212
+ }, []);
213
+ const handleConnectionCreated = useCallback((conn) => {
214
+ const providerKey = conn.provider?.key || "";
215
+ const adapter = adapters.find((candidate) => matchesAdapterProvider(candidate, providerKey));
216
+ if (adapter) {
217
+ const newSelection = {
218
+ connectionId: conn.id,
219
+ adapter: adapter.name
220
+ };
221
+ if (multiple) onChange([...selectedConnections, newSelection]);
222
+ else onChange([newSelection]);
223
+ }
224
+ setDialogOpen(false);
225
+ refetch();
226
+ }, [
227
+ adapters,
228
+ selectedConnections,
229
+ onChange,
230
+ multiple,
231
+ refetch
232
+ ]);
233
+ const getConnectionData = useCallback((connectionId) => connections.find((c) => c.id === connectionId), [connections]);
234
+ if (!adapters || adapters.length === 0) return null;
235
+ return /* @__PURE__ */ jsxs("div", {
236
+ className: compact ? "space-y-2" : "space-y-3",
237
+ children: [
238
+ selectedConnections.length > 0 && /* @__PURE__ */ jsx("div", {
239
+ className: "flex flex-wrap gap-1.5",
240
+ children: selectedConnections.map((conn) => {
241
+ const connData = getConnectionData(conn.connectionId);
242
+ return /* @__PURE__ */ jsx(ConnectionChip, {
243
+ connection: conn,
244
+ connectionData: connData,
245
+ onRemove: () => handleRemove(conn.connectionId)
246
+ }, conn.connectionId);
247
+ })
248
+ }),
249
+ selectedConnections.map((conn) => /* @__PURE__ */ jsx(ConnectionAgentUsage, {
250
+ connectionId: conn.connectionId,
251
+ connectionName: selectedConnections.length > 1 ? getConnectionData(conn.connectionId)?.name : null
252
+ }, `agents-${conn.connectionId}`)),
253
+ /* @__PURE__ */ jsxs("div", {
254
+ className: "flex items-center gap-2",
255
+ children: [/* @__PURE__ */ jsxs(DropdownMenu, {
256
+ open: isDropdownOpen,
257
+ onOpenChange: setIsDropdownOpen,
258
+ children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
259
+ asChild: true,
260
+ children: /* @__PURE__ */ jsxs(Button, {
261
+ variant: "outline",
262
+ size: compact ? "sm" : "default",
263
+ className: "justify-between min-w-[200px]",
264
+ children: [/* @__PURE__ */ jsxs("span", {
265
+ className: "flex items-center gap-2",
266
+ children: [/* @__PURE__ */ jsx(Icon, {
267
+ icon: "mdi:link-variant",
268
+ className: "w-4 h-4 text-text-secondary"
269
+ }), selectedConnections.length === 0 ? tr("fluidgrids.connections.connectionSelector.selectConnection", "Select connection") : multiple ? `${selectedConnections.length} selected` : tr("fluidgrids.connections.connectionSelector.changeConnection", "Change connection")]
270
+ }), /* @__PURE__ */ jsx(Icon, {
271
+ icon: "mdi:chevron-down",
272
+ className: "w-4 h-4 text-text-secondary"
273
+ })]
274
+ })
275
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
276
+ align: "start",
277
+ style: { zIndex: 1100 },
278
+ className: "w-[280px] max-h-[400px] overflow-y-auto",
279
+ children: [connectionsLoading ? /* @__PURE__ */ jsxs("div", {
280
+ className: "p-4 text-center text-sm text-text-secondary",
281
+ children: [/* @__PURE__ */ jsx(Icon, {
282
+ icon: "mdi:loading",
283
+ className: "w-6 h-6 mx-auto mb-2 animate-spin"
284
+ }), /* @__PURE__ */ jsx("p", { children: tr("fluidgrids.connections.connectionSelector.loading", "Loading connections...") })]
285
+ }) : Object.keys(groupedConnections).length === 0 ? /* @__PURE__ */ jsxs("div", {
286
+ className: "p-4 text-center text-sm text-text-secondary",
287
+ children: [
288
+ /* @__PURE__ */ jsx(Icon, {
289
+ icon: "mdi:link-off",
290
+ className: "w-8 h-8 mx-auto mb-2 opacity-50"
291
+ }),
292
+ /* @__PURE__ */ jsx("p", { children: tr("fluidgrids.connections.connectionSelector.emptyTitle", "No matching connections") }),
293
+ /* @__PURE__ */ jsx("p", {
294
+ className: "text-xs mt-1",
295
+ children: tr("fluidgrids.connections.connectionSelector.emptyHint", "Create one in Integrations")
296
+ })
297
+ ]
298
+ }) : Object.entries(groupedConnections).map(([providerName, conns]) => /* @__PURE__ */ jsxs("div", { children: [
299
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, {
300
+ className: "flex items-center gap-2 text-xs font-semibold text-text-secondary uppercase tracking-wide",
301
+ children: [/* @__PURE__ */ jsx(ProviderIcon, {
302
+ variant: "bare",
303
+ provider: conns[0]?.provider ?? {
304
+ key: "generic",
305
+ icon: void 0,
306
+ category: "CUSTOM"
307
+ },
308
+ size: 16
309
+ }), providerName]
310
+ }),
311
+ conns.map((conn) => {
312
+ const isSelected = selectedConnections.some((c) => c.connectionId === conn.id);
313
+ const isActive = conn.status === "ACTIVE";
314
+ return /* @__PURE__ */ jsxs(DropdownMenuItem, {
315
+ onSelect: (event) => {
316
+ if (multiple) event.preventDefault();
317
+ handleSelect(conn);
318
+ },
319
+ className: `flex items-center justify-between cursor-pointer ${isSelected ? "bg-action-primary-bg/10" : ""} ${!isActive ? "opacity-60" : ""}`,
320
+ children: [/* @__PURE__ */ jsxs("div", {
321
+ className: "flex flex-col min-w-0",
322
+ children: [/* @__PURE__ */ jsxs("span", {
323
+ className: "font-medium truncate flex items-center gap-1.5",
324
+ children: [conn.name, showStatus && /* @__PURE__ */ jsx("span", {
325
+ title: conn.status,
326
+ children: /* @__PURE__ */ jsx(Icon, {
327
+ icon: getStatusIcon(conn.status),
328
+ className: `w-3.5 h-3.5 ${getStatusColor(conn.status)}`
329
+ })
330
+ })]
331
+ }), /* @__PURE__ */ jsx("span", {
332
+ className: "text-xs text-text-secondary",
333
+ children: conn.externalAccountEmail || conn.updatedAt && `Updated ${formatAge(conn.updatedAt)}`
334
+ })]
335
+ }), isSelected && /* @__PURE__ */ jsx(Icon, {
336
+ icon: "mdi:check",
337
+ className: "w-4 h-4 text-primary flex-shrink-0"
338
+ })]
339
+ }, conn.id);
340
+ }),
341
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
342
+ ] }, providerName)), /* @__PURE__ */ jsx(SdkPermissionGate, {
343
+ permission: FLUIDGRIDS_PERMISSIONS.INTEGRATION_CONNECTION_CREATE,
344
+ children: /* @__PURE__ */ jsxs(DropdownMenuItem, {
345
+ onClick: () => {
346
+ handleNewConnection(newConnectionProviderKey(adapters));
347
+ },
348
+ className: "text-primary cursor-pointer",
349
+ children: [/* @__PURE__ */ jsx(Icon, {
350
+ icon: "mdi:plus",
351
+ className: "w-4 h-4 mr-2"
352
+ }), tr("fluidgrids.connections.connectionSelector.newConnection", "New connection")]
353
+ })
354
+ })]
355
+ })]
356
+ }), selectedConnections.length === 0 && !compact && /* @__PURE__ */ jsx(SdkPermissionGate, {
357
+ permission: FLUIDGRIDS_PERMISSIONS.INTEGRATION_CONNECTION_CREATE,
358
+ children: /* @__PURE__ */ jsxs(Button, {
359
+ variant: "ghost",
360
+ size: "sm",
361
+ onClick: () => handleNewConnection(newConnectionProviderKey(adapters)),
362
+ className: "text-primary hover:text-primary/80",
363
+ children: [/* @__PURE__ */ jsx(Icon, {
364
+ icon: "mdi:plus",
365
+ className: "w-4 h-4 mr-1"
366
+ }), tr("fluidgrids.connections.connectionSelector.new", "New")]
367
+ })
368
+ })]
369
+ }),
370
+ showStatus && showRequired && selectedConnections.length === 0 && adapters.some((adapter) => !isOptionalAdapter(adapter)) && /* @__PURE__ */ jsxs("div", {
371
+ className: "flex items-center gap-1.5 text-xs text-[var(--color-status-warning-text)]",
372
+ children: [/* @__PURE__ */ jsx(Icon, {
373
+ icon: "mdi:alert-circle",
374
+ className: "w-3.5 h-3.5"
375
+ }), /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.connections.connectionSelector.connectionRequired", "Connection required for this node") })]
376
+ }),
377
+ /* @__PURE__ */ jsx(ConnectionFormDialog, {
378
+ open: dialogOpen,
379
+ onClose: () => setDialogOpen(false),
380
+ providerKey: dialogProviderKey,
381
+ onConnectionCreated: handleConnectionCreated
382
+ })
383
+ ]
384
+ });
385
+ }
386
+
387
+ // src/modules/runners/components/RunOnSection.tsx
388
+ /**
389
+ * NDV Settings → "Run on": FluidGrids Cloud (default) or one of the
390
+ * workspace's self-hosted runners.
391
+ *
392
+ * Persisted at `node.data.executionSettings.runOn = { kind: "runner", runnerId }`
393
+ * (absent = FluidGrids Cloud). fluidgrids-workers reads exactly that and the
394
+ * node then runs on the runner — or fails if it is offline; it never falls
395
+ * back to the cloud.
396
+ */
397
+ const CLOUD = "__fluidgrids_cloud__";
398
+ function RunOnSection({ value, onChange, sectionLabelClassName }) {
399
+ const tr = useTr();
400
+ const { runners, loading } = useRunners({ pollMs: 15e3 });
401
+ const selected = value ? runners.find((r) => r.id === value.runnerId) : void 0;
402
+ return /* @__PURE__ */ jsxs("section", {
403
+ className: "space-y-2",
404
+ "data-testid": "ndv-settings-run-on",
405
+ children: [
406
+ /* @__PURE__ */ jsx("div", {
407
+ className: sectionLabelClassName,
408
+ children: tr("fluidgrids.builder.nodeSettings.runOn", "Run on")
409
+ }),
410
+ /* @__PURE__ */ jsxs(Select, {
411
+ value: value?.runnerId ?? CLOUD,
412
+ onValueChange: (v) => onChange(v === CLOUD ? null : {
413
+ kind: "runner",
414
+ runnerId: v
415
+ }),
416
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
417
+ className: "w-full",
418
+ "data-testid": "ndv-settings-run-on-select",
419
+ children: /* @__PURE__ */ jsx(SelectValue, {})
420
+ }), /* @__PURE__ */ jsxs(SelectContent, {
421
+ style: { zIndex: 1100 },
422
+ "data-testid": "ndv-settings-run-on-options",
423
+ children: [
424
+ /* @__PURE__ */ jsx(SelectItem, {
425
+ value: CLOUD,
426
+ "data-testid": "run-on-cloud",
427
+ children: tr("fluidgrids.builder.nodeSettings.runOnCloud", "FluidGrids Cloud (default)")
428
+ }),
429
+ runners.map((runner) => /* @__PURE__ */ jsxs(SelectItem, {
430
+ value: runner.id,
431
+ "data-testid": "run-on-runner-option",
432
+ children: [runner.name, runner.status !== "ONLINE" ? ` (${runner.status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet")})` : ""]
433
+ }, runner.id)),
434
+ value && !loading && !selected && /* @__PURE__ */ jsx(SelectItem, {
435
+ value: value.runnerId,
436
+ disabled: true,
437
+ children: tr("fluidgrids.builder.nodeSettings.runOnMissing", "Removed runner")
438
+ })
439
+ ]
440
+ })]
441
+ }),
442
+ value ? /* @__PURE__ */ jsxs("div", {
443
+ className: "space-y-1 text-xs text-text-secondary",
444
+ children: [/* @__PURE__ */ jsxs("div", {
445
+ className: "flex items-start gap-2",
446
+ children: [selected && /* @__PURE__ */ jsx(RunnerStatusPill, { status: selected.status }), /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.builder.nodeSettings.runOnRunnerHint", "This node runs on your runner, in a local sandbox. If the runner is offline the node fails — it never falls back to FluidGrids Cloud. The node's input and the credentials it uses are sent to that machine.") })]
447
+ }), selected && /* @__PURE__ */ jsx("p", {
448
+ "data-testid": "run-on-private-network",
449
+ children: (selected.privateNetwork ?? []).length === 0 ? tr("fluidgrids.builder.nodeSettings.runOnPublicOnly", "On this runner, nodes reach the public internet only. Private destinations are opened on the runner machine (fluidgrids-runner network allow).") : tr("fluidgrids.builder.nodeSettings.runOnPrivateNetwork", "This runner opens {{count}} private destination(s) — see Runners for which nodes may use them.", { count: (selected.privateNetwork ?? []).length })
450
+ })]
451
+ }) : /* @__PURE__ */ jsx("p", {
452
+ className: "text-xs text-text-secondary",
453
+ children: runners.length === 0 && !loading ? tr("fluidgrids.builder.nodeSettings.runOnNoRunners", "Runs in FluidGrids Cloud. Add a runner under Workflows → Runners to run nodes on your own machine.") : tr("fluidgrids.builder.nodeSettings.runOnCloudHint", "Runs in FluidGrids Cloud.")
454
+ })
455
+ ]
456
+ });
457
+ }
458
+
459
+ // src/ui/popover.tsx
460
+ const Popover$1 = PopoverPrimitive.Root;
461
+ const PopoverTrigger$1 = PopoverPrimitive.Trigger;
462
+ const PopoverContent$1 = React$1.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(PopoverPrimitive.Content, {
463
+ ref,
464
+ "data-slot": "popover-content",
465
+ align,
466
+ sideOffset,
467
+ className: cn("z-50 w-72 rounded-card border border-border-subtle bg-bg-elevated p-4 text-text-primary shadow-elevation-3 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className),
468
+ ...props
469
+ }) }));
470
+ PopoverContent$1.displayName = PopoverPrimitive.Content.displayName;
471
+
472
+ export { ConnectionSelector as a, RunOnSection as i, PopoverContent$1 as n, PopoverTrigger$1 as r, Popover$1 as t };
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { A as nodeAccent, H as useFlowStore, j as NodeTypeIcon } from "./node-operation-selection--oy-yEq_.js";
3
3
  import { G as nodeSubtitle, K as nodeTitle, Mn as getNodeDetail, X as useNodeConfig, rn as useNodeExecution } from "./FluidGridsProvider-dvZSNtSW.js";
4
- import { I as NodeEditorTabs, N as useNodeEditorTab, R as NodeInputFields, b as NodeExecutionActions, x as NodeEditorTabBody } from "./flow-builder-y2Y1iG4Y.js";
4
+ import { i as NodeInputFields, n as useNodeEditorTab, r as NodeEditorTabs, t as NodeEditorTabBody } from "./node-editor-tab-body-DU-dh35X.js";
5
5
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
6
+ import { r as NodeExecutionActions } from "./schedule-panel-C0mfGQSD.js";
6
7
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
8
  import { useReactFlow, useStore } from "@xyflow/react";
8
9
  import { Icon } from "@iconify/react";