@burdenoff/fluidgrids-fe-sdk 2026.1101.1 → 2026.1101.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.
- package/dist/{ExecutionDetailPage-4LPCNWM3.js → ExecutionDetailPage-Q77U6YJO.js} +11 -8
- package/dist/{ExecutionsListPage-HNPUKYD7.js → ExecutionsListPage-X4H4FVLN.js} +6 -5
- package/dist/{WorkflowBuilder-QFSCYCUC.js → WorkflowBuilder-RJPRUMQR.js} +11 -8
- package/dist/{WorkflowsList-RV4BRJO7.js → WorkflowsList-R2JJK7BL.js} +7 -6
- package/dist/agentTools-MISTH7JI.js +349 -0
- package/dist/{chunk-77HIU3CX.js → chunk-26HGYPBC.js} +1 -1
- package/dist/{chunk-76UUEMTN.js → chunk-37VUN74D.js} +4 -4
- package/dist/{chunk-QPZCJKII.js → chunk-3ERT6FQK.js} +3 -3
- package/dist/chunk-472HJ3S3.js +3929 -0
- package/dist/{chunk-WNZV4SLU.js → chunk-4QHCXTHW.js} +4 -4
- package/dist/chunk-AASHQI77.js +126 -0
- package/dist/chunk-CGXQVZGY.js +260 -0
- package/dist/chunk-CRDG2SQQ.js +185 -0
- package/dist/chunk-DQ2ZCXYX.js +21875 -0
- package/dist/chunk-F7DD7BOB.js +1164 -0
- package/dist/chunk-GTEBQQ7J.js +24 -0
- package/dist/{chunk-ALFMJSD6.js → chunk-H7XPXONT.js} +1 -1
- package/dist/{chunk-UO5DUKYN.js → chunk-HKKSXOFO.js} +8 -7
- package/dist/chunk-IA2ZCHNQ.js +1336 -0
- package/dist/{chunk-3M36XWAH.js → chunk-IGRIN7NL.js} +8 -7
- package/dist/chunk-JDLQ6QIB.js +283 -0
- package/dist/{chunk-AD7BICOT.js → chunk-JKBCLBHY.js} +1 -1
- package/dist/{chunk-6JX5UPDW.js → chunk-JNZCXFLD.js} +1 -1
- package/dist/{chunk-SEFOT2LD.js → chunk-LWLHPVIA.js} +1 -1
- package/dist/{chunk-JWROEOG5.js → chunk-LYTR6IQO.js} +1 -38
- package/dist/chunk-MR2B67NW.js +338 -0
- package/dist/{chunk-RIHYPPMQ.js → chunk-NLYQ3XDE.js} +1 -1
- package/dist/{chunk-6ILIBJFH.js → chunk-NZJWKVER.js} +7904 -7943
- package/dist/chunk-OCJTTHTS.js +46 -0
- package/dist/chunk-OSQ3OCR3.js +1193 -0
- package/dist/{chunk-VJL3DFJT.js → chunk-REO4S5MY.js} +2 -2
- package/dist/{chunk-XKQMCPSB.js → chunk-T2UUYYVJ.js} +4 -4
- package/dist/{chunk-BXR6BWXM.js → chunk-W7MTSATB.js} +5 -5
- package/dist/{chunk-A47PSD7A.js → chunk-WF6GMBR4.js} +7 -4
- package/dist/chunk-WYCB6OQL.js +19709 -0
- package/dist/{chunk-YOLPHVOE.js → chunk-YKFR7BZJ.js} +1 -1
- package/dist/{chunk-26NENGXA.js → chunk-YSXNJGCY.js} +4 -4
- package/dist/code-editor-field-JKCUR6BI.js +3 -0
- package/dist/executions.js +12 -9
- package/dist/history.js +4 -3
- package/dist/index.js +41 -32
- package/dist/node-detail-view-PCYFQX62.js +2034 -0
- package/dist/observability.js +5 -4
- package/dist/quick-config-panel-BRZZHOSI.js +402 -0
- package/dist/settings.js +6 -5
- package/dist/templates.js +10 -7
- package/dist/workflow-builder.js +12 -9
- package/dist/workflows-list.js +7 -6
- package/package.json +4 -1
- package/dist/chunk-NMEI7TQW.js +0 -2628
- package/dist/chunk-S7XV65DB.js +0 -50350
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { AgentsTab } from './chunk-YKFR7BZJ.js';
|
|
2
|
+
import { ActionPoliciesTab } from './chunk-JKBCLBHY.js';
|
|
3
|
+
import { LogStreamingTab } from './chunk-NLYQ3XDE.js';
|
|
4
4
|
import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
|
|
5
|
-
import { useFlowStore, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useSaveStatusStore, findSaveConflict, useDurableParityEnabled, throwIfResultError, describeOperationError, useConnections } from './chunk-
|
|
5
|
+
import { useFlowStore, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useSaveStatusStore, findSaveConflict, useDurableParityEnabled, throwIfResultError, describeOperationError, useConnections } from './chunk-NZJWKVER.js';
|
|
6
6
|
import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS } from './chunk-ZUVFD5KV.js';
|
|
7
7
|
import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
|
|
8
8
|
import { getFluidGridsConfig } from './chunk-6AMNEX2Y.js';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useBuilderReadOnly, toCodeMirrorDiagnostics } from './chunk-OCJTTHTS.js';
|
|
2
|
+
import { useMemo, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import CodeMirror, { EditorView } from '@uiw/react-codemirror';
|
|
4
|
+
import { lintGutter, setDiagnostics } from '@codemirror/lint';
|
|
5
|
+
import { cn } from '@burdenoff/fe-libs/utils';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function useLanguageExtension(language) {
|
|
9
|
+
const [extension, setExtension] = useState(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
const loaded = language === "python" ? import('@codemirror/lang-python').then((m) => m.python()) : import('@codemirror/lang-javascript').then(
|
|
13
|
+
(m) => m.javascript({ typescript: language === "typescript" })
|
|
14
|
+
);
|
|
15
|
+
void loaded.then((ext) => {
|
|
16
|
+
if (!cancelled) setExtension(ext);
|
|
17
|
+
});
|
|
18
|
+
return () => {
|
|
19
|
+
cancelled = true;
|
|
20
|
+
};
|
|
21
|
+
}, [language]);
|
|
22
|
+
return extension;
|
|
23
|
+
}
|
|
24
|
+
function normalizeCodeLanguage(value) {
|
|
25
|
+
return typeof value === "string" && value.toLowerCase().startsWith("py") ? "python" : "javascript";
|
|
26
|
+
}
|
|
27
|
+
function useIsDarkTheme() {
|
|
28
|
+
const [dark, setDark] = useState(
|
|
29
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
|
|
30
|
+
);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const observer = new MutationObserver(
|
|
34
|
+
() => setDark(root.classList.contains("dark"))
|
|
35
|
+
);
|
|
36
|
+
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, []);
|
|
39
|
+
return dark;
|
|
40
|
+
}
|
|
41
|
+
function CodeEditorField({
|
|
42
|
+
value,
|
|
43
|
+
language,
|
|
44
|
+
placeholder,
|
|
45
|
+
label,
|
|
46
|
+
onChange,
|
|
47
|
+
readOnly: readOnlyProp = false,
|
|
48
|
+
diagnostics,
|
|
49
|
+
minHeight = "180px",
|
|
50
|
+
maxHeight = "420px",
|
|
51
|
+
className,
|
|
52
|
+
"data-testid": testId
|
|
53
|
+
}) {
|
|
54
|
+
const dark = useIsDarkTheme();
|
|
55
|
+
const withLint = diagnostics !== void 0;
|
|
56
|
+
const builderReadOnly = useBuilderReadOnly();
|
|
57
|
+
const readOnly = readOnlyProp || builderReadOnly;
|
|
58
|
+
const languageExtension = useLanguageExtension(language);
|
|
59
|
+
const extensions = useMemo(
|
|
60
|
+
() => [
|
|
61
|
+
...languageExtension ? [languageExtension] : [],
|
|
62
|
+
// CodeMirror's contenteditable is the control assistive tech focuses; name it.
|
|
63
|
+
...label ? [EditorView.contentAttributes.of({ "aria-label": label })] : [],
|
|
64
|
+
...withLint ? [lintGutter()] : []
|
|
65
|
+
],
|
|
66
|
+
[languageExtension, label, withLint]
|
|
67
|
+
);
|
|
68
|
+
const viewRef = useRef(null);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const view = viewRef.current;
|
|
71
|
+
if (!view || diagnostics === void 0) return;
|
|
72
|
+
view.dispatch(
|
|
73
|
+
setDiagnostics(
|
|
74
|
+
view.state,
|
|
75
|
+
toCodeMirrorDiagnostics(view.state.doc.toString(), diagnostics)
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
}, [diagnostics]);
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
"data-testid": testId,
|
|
83
|
+
className: cn(
|
|
84
|
+
"overflow-hidden rounded-md border border-input text-sm transition-colors focus-within:border-primary",
|
|
85
|
+
className
|
|
86
|
+
),
|
|
87
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
88
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
89
|
+
children: /* @__PURE__ */ jsx(
|
|
90
|
+
CodeMirror,
|
|
91
|
+
{
|
|
92
|
+
value,
|
|
93
|
+
onChange: readOnly ? void 0 : onChange,
|
|
94
|
+
extensions,
|
|
95
|
+
theme: dark ? "dark" : "light",
|
|
96
|
+
placeholder,
|
|
97
|
+
minHeight,
|
|
98
|
+
maxHeight,
|
|
99
|
+
editable: !readOnly,
|
|
100
|
+
readOnly,
|
|
101
|
+
onCreateEditor: (view) => {
|
|
102
|
+
viewRef.current = view;
|
|
103
|
+
if (diagnostics?.length)
|
|
104
|
+
view.dispatch(
|
|
105
|
+
setDiagnostics(
|
|
106
|
+
view.state,
|
|
107
|
+
toCodeMirrorDiagnostics(view.state.doc.toString(), diagnostics)
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
basicSetup: {
|
|
112
|
+
lineNumbers: true,
|
|
113
|
+
foldGutter: true,
|
|
114
|
+
highlightActiveLine: !readOnly,
|
|
115
|
+
autocompletion: !readOnly,
|
|
116
|
+
bracketMatching: true,
|
|
117
|
+
closeBrackets: !readOnly,
|
|
118
|
+
indentOnInput: !readOnly
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { CodeEditorField, normalizeCodeLanguage };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { throwIfResultError } from './chunk-NZJWKVER.js';
|
|
2
|
+
import { useTr } from './chunk-UULVIT25.js';
|
|
3
|
+
import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
|
|
4
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
5
|
+
import { gql } from '@apollo/client';
|
|
6
|
+
import { cn } from '@burdenoff/fe-libs/utils';
|
|
7
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
|
+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@burdenoff/fe-libs/ui';
|
|
9
|
+
|
|
10
|
+
var RUNNER_FIELDS = gql`
|
|
11
|
+
fragment SandboxRunnerFields on SandboxRunner {
|
|
12
|
+
id
|
|
13
|
+
name
|
|
14
|
+
status
|
|
15
|
+
hostname
|
|
16
|
+
os
|
|
17
|
+
arch
|
|
18
|
+
driver
|
|
19
|
+
version
|
|
20
|
+
capacity
|
|
21
|
+
privateNetwork {
|
|
22
|
+
target
|
|
23
|
+
ports
|
|
24
|
+
nodes
|
|
25
|
+
}
|
|
26
|
+
tokenPrefix
|
|
27
|
+
lastSeenAt
|
|
28
|
+
connectedAt
|
|
29
|
+
createdBy
|
|
30
|
+
createdAt
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
var LIST_RUNNERS = gql`
|
|
34
|
+
${RUNNER_FIELDS}
|
|
35
|
+
query SandboxRunners {
|
|
36
|
+
sandboxRunners(context: {}) {
|
|
37
|
+
...SandboxRunnerFields
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
var CREATE_RUNNER = gql`
|
|
42
|
+
${RUNNER_FIELDS}
|
|
43
|
+
mutation CreateSandboxRunner($name: String!) {
|
|
44
|
+
createSandboxRunner(input: { name: $name, context: {} }) {
|
|
45
|
+
token
|
|
46
|
+
bridgeUrl
|
|
47
|
+
runner {
|
|
48
|
+
...SandboxRunnerFields
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
var ROTATE_RUNNER_TOKEN = gql`
|
|
54
|
+
${RUNNER_FIELDS}
|
|
55
|
+
mutation RotateSandboxRunnerToken($id: ID!) {
|
|
56
|
+
rotateSandboxRunnerToken(id: $id, context: {}) {
|
|
57
|
+
token
|
|
58
|
+
bridgeUrl
|
|
59
|
+
runner {
|
|
60
|
+
...SandboxRunnerFields
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
var DELETE_RUNNER = gql`
|
|
66
|
+
mutation DeleteSandboxRunner($id: ID!) {
|
|
67
|
+
deleteSandboxRunner(id: $id, context: {})
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
var RUNNER_INSTALL_SCRIPT_URL = "https://www.fluidgrids.ai/downloads/runner/install.sh";
|
|
71
|
+
function runnerInstallCommand(bridgeUrl) {
|
|
72
|
+
const base = `curl -fsSL ${RUNNER_INSTALL_SCRIPT_URL} | sh`;
|
|
73
|
+
return bridgeUrl && bridgeUrl !== DEFAULT_BRIDGE_URL ? `${base} -s -- --url ${bridgeUrl}` : base;
|
|
74
|
+
}
|
|
75
|
+
var DEFAULT_BRIDGE_URL = "wss://graphqlworkspaces.burdenoff.com/runner-bridge";
|
|
76
|
+
function useRunners(options) {
|
|
77
|
+
const [runners, setRunners] = useState([]);
|
|
78
|
+
const [loading, setLoading] = useState(!options?.skip);
|
|
79
|
+
const [error, setError] = useState(null);
|
|
80
|
+
const mounted = useRef(true);
|
|
81
|
+
const fetchRunners = useCallback(async () => {
|
|
82
|
+
if (options?.skip) return;
|
|
83
|
+
try {
|
|
84
|
+
const result = await getFluidGridsClient().query({ query: LIST_RUNNERS, fetchPolicy: "network-only" });
|
|
85
|
+
if (!mounted.current) return;
|
|
86
|
+
setRunners(result.data?.sandboxRunners ?? []);
|
|
87
|
+
setError(null);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (mounted.current) setError(err);
|
|
90
|
+
} finally {
|
|
91
|
+
if (mounted.current) setLoading(false);
|
|
92
|
+
}
|
|
93
|
+
}, [options?.skip]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
mounted.current = true;
|
|
96
|
+
void fetchRunners();
|
|
97
|
+
const pollMs = options?.pollMs ?? 1e4;
|
|
98
|
+
const timer = pollMs > 0 ? setInterval(fetchRunners, pollMs) : null;
|
|
99
|
+
return () => {
|
|
100
|
+
mounted.current = false;
|
|
101
|
+
if (timer) clearInterval(timer);
|
|
102
|
+
};
|
|
103
|
+
}, [fetchRunners, options?.pollMs]);
|
|
104
|
+
return { runners, loading, error, refetch: fetchRunners };
|
|
105
|
+
}
|
|
106
|
+
function useRunnerActions() {
|
|
107
|
+
const [busy, setBusy] = useState(false);
|
|
108
|
+
const run = useCallback(async (fn) => {
|
|
109
|
+
setBusy(true);
|
|
110
|
+
try {
|
|
111
|
+
return await fn();
|
|
112
|
+
} finally {
|
|
113
|
+
setBusy(false);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
const createRunner = useCallback(
|
|
117
|
+
(name) => run(async () => {
|
|
118
|
+
const result = throwIfResultError(
|
|
119
|
+
await getFluidGridsClient().mutate({ mutation: CREATE_RUNNER, variables: { name } })
|
|
120
|
+
);
|
|
121
|
+
if (!result.data?.createSandboxRunner)
|
|
122
|
+
throw new Error("No runner returned");
|
|
123
|
+
return result.data.createSandboxRunner;
|
|
124
|
+
}),
|
|
125
|
+
[run]
|
|
126
|
+
);
|
|
127
|
+
const rotateToken = useCallback(
|
|
128
|
+
(id) => run(async () => {
|
|
129
|
+
const result = throwIfResultError(
|
|
130
|
+
await getFluidGridsClient().mutate({ mutation: ROTATE_RUNNER_TOKEN, variables: { id } })
|
|
131
|
+
);
|
|
132
|
+
if (!result.data?.rotateSandboxRunnerToken)
|
|
133
|
+
throw new Error("No runner returned");
|
|
134
|
+
return result.data.rotateSandboxRunnerToken;
|
|
135
|
+
}),
|
|
136
|
+
[run]
|
|
137
|
+
);
|
|
138
|
+
const deleteRunner = useCallback(
|
|
139
|
+
(id) => run(async () => {
|
|
140
|
+
const result = throwIfResultError(
|
|
141
|
+
await getFluidGridsClient().mutate({
|
|
142
|
+
mutation: DELETE_RUNNER,
|
|
143
|
+
variables: { id }
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
return result.data?.deleteSandboxRunner === true;
|
|
147
|
+
}),
|
|
148
|
+
[run]
|
|
149
|
+
);
|
|
150
|
+
return { createRunner, rotateToken, deleteRunner, busy };
|
|
151
|
+
}
|
|
152
|
+
var STYLES = {
|
|
153
|
+
ONLINE: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
|
|
154
|
+
OFFLINE: "border-border-default bg-bg-sunken text-text-muted",
|
|
155
|
+
NEVER_CONNECTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
|
|
156
|
+
};
|
|
157
|
+
function RunnerStatusPill({
|
|
158
|
+
status,
|
|
159
|
+
className
|
|
160
|
+
}) {
|
|
161
|
+
const tr = useTr();
|
|
162
|
+
const label = status === "ONLINE" ? tr("fluidgrids.runners.online", "Online") : status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet");
|
|
163
|
+
return /* @__PURE__ */ jsx(
|
|
164
|
+
"span",
|
|
165
|
+
{
|
|
166
|
+
className: cn(
|
|
167
|
+
"inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium",
|
|
168
|
+
STYLES[status],
|
|
169
|
+
className
|
|
170
|
+
),
|
|
171
|
+
"data-testid": "runner-status",
|
|
172
|
+
"data-status": status,
|
|
173
|
+
children: label
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
var CLOUD = "__fluidgrids_cloud__";
|
|
178
|
+
function RunOnSection({
|
|
179
|
+
value,
|
|
180
|
+
onChange,
|
|
181
|
+
sectionLabelClassName
|
|
182
|
+
}) {
|
|
183
|
+
const tr = useTr();
|
|
184
|
+
const { runners, loading } = useRunners({ pollMs: 15e3 });
|
|
185
|
+
const selected = value ? runners.find((r) => r.id === value.runnerId) : void 0;
|
|
186
|
+
return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "data-testid": "ndv-settings-run-on", children: [
|
|
187
|
+
/* @__PURE__ */ jsx("div", { className: sectionLabelClassName, children: tr("fluidgrids.builder.nodeSettings.runOn", "Run on") }),
|
|
188
|
+
/* @__PURE__ */ jsxs(
|
|
189
|
+
Select,
|
|
190
|
+
{
|
|
191
|
+
value: value?.runnerId ?? CLOUD,
|
|
192
|
+
onValueChange: (v) => onChange(v === CLOUD ? null : { kind: "runner", runnerId: v }),
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsx(
|
|
195
|
+
SelectTrigger,
|
|
196
|
+
{
|
|
197
|
+
className: "w-full",
|
|
198
|
+
"data-testid": "ndv-settings-run-on-select",
|
|
199
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
/* @__PURE__ */ jsxs(
|
|
203
|
+
SelectContent,
|
|
204
|
+
{
|
|
205
|
+
style: { zIndex: 1100 },
|
|
206
|
+
"data-testid": "ndv-settings-run-on-options",
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsx(SelectItem, { value: CLOUD, "data-testid": "run-on-cloud", children: tr(
|
|
209
|
+
"fluidgrids.builder.nodeSettings.runOnCloud",
|
|
210
|
+
"FluidGrids Cloud (default)"
|
|
211
|
+
) }),
|
|
212
|
+
runners.map((runner) => /* @__PURE__ */ jsxs(
|
|
213
|
+
SelectItem,
|
|
214
|
+
{
|
|
215
|
+
value: runner.id,
|
|
216
|
+
"data-testid": "run-on-runner-option",
|
|
217
|
+
children: [
|
|
218
|
+
runner.name,
|
|
219
|
+
runner.status !== "ONLINE" ? ` (${runner.status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet")})` : ""
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
runner.id
|
|
223
|
+
)),
|
|
224
|
+
value && !loading && !selected && /* @__PURE__ */ jsx(SelectItem, { value: value.runnerId, disabled: true, children: tr(
|
|
225
|
+
"fluidgrids.builder.nodeSettings.runOnMissing",
|
|
226
|
+
"Removed runner"
|
|
227
|
+
) })
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
value ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs text-text-secondary", children: [
|
|
235
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
236
|
+
selected && /* @__PURE__ */ jsx(RunnerStatusPill, { status: selected.status }),
|
|
237
|
+
/* @__PURE__ */ jsx("span", { children: tr(
|
|
238
|
+
"fluidgrids.builder.nodeSettings.runOnRunnerHint",
|
|
239
|
+
"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."
|
|
240
|
+
) })
|
|
241
|
+
] }),
|
|
242
|
+
selected && /* @__PURE__ */ jsx("p", { "data-testid": "run-on-private-network", children: (selected.privateNetwork ?? []).length === 0 ? tr(
|
|
243
|
+
"fluidgrids.builder.nodeSettings.runOnPublicOnly",
|
|
244
|
+
"On this runner, nodes reach the public internet only. Private destinations are opened on the runner machine (fluidgrids-runner network allow)."
|
|
245
|
+
) : tr(
|
|
246
|
+
"fluidgrids.builder.nodeSettings.runOnPrivateNetwork",
|
|
247
|
+
"This runner opens {{count}} private destination(s) \u2014 see Runners for which nodes may use them.",
|
|
248
|
+
{ count: (selected.privateNetwork ?? []).length }
|
|
249
|
+
) })
|
|
250
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: runners.length === 0 && !loading ? tr(
|
|
251
|
+
"fluidgrids.builder.nodeSettings.runOnNoRunners",
|
|
252
|
+
"Runs in FluidGrids Cloud. Add a runner under Workflows \u2192 Runners to run nodes on your own machine."
|
|
253
|
+
) : tr(
|
|
254
|
+
"fluidgrids.builder.nodeSettings.runOnCloudHint",
|
|
255
|
+
"Runs in FluidGrids Cloud."
|
|
256
|
+
) })
|
|
257
|
+
] });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { RUNNER_INSTALL_SCRIPT_URL, RunOnSection, RunnerStatusPill, runnerInstallCommand, useRunnerActions, useRunners };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { STICKY_NOTE_TYPE, isNodeTrigger, isLocked, useEditLeaseStore, useSaveStatusStore, canAutosave, editStateOf, workflowFingerprint, snapshotForSave, leaveNeedsConfirmation, runPreparationOf, useFlowStore, resolveInputHandles } from './chunk-NZJWKVER.js';
|
|
2
|
+
import { getConnectedEdges, useReactFlow } from '@xyflow/react';
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
function findEdges(node, connectedEdges) {
|
|
6
|
+
const outgoingEdges = connectedEdges.filter(
|
|
7
|
+
(edge) => edge.source === node.id
|
|
8
|
+
);
|
|
9
|
+
const incomingEdges = connectedEdges.filter(
|
|
10
|
+
(edge) => edge.target === node.id
|
|
11
|
+
);
|
|
12
|
+
return { outgoingEdges, incomingEdges };
|
|
13
|
+
}
|
|
14
|
+
function checkFlowTerminalNode(outgoingEdges, incomingEdges, isTrigger, type) {
|
|
15
|
+
if (isTrigger) {
|
|
16
|
+
return outgoingEdges.length >= 1;
|
|
17
|
+
}
|
|
18
|
+
switch (type) {
|
|
19
|
+
case "end" /* END */:
|
|
20
|
+
return incomingEdges.length >= 1;
|
|
21
|
+
default:
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function checkLoneNode(outgoingEdges, incomingEdges, isTrigger) {
|
|
26
|
+
if (isTrigger) {
|
|
27
|
+
return outgoingEdges.length === 0;
|
|
28
|
+
}
|
|
29
|
+
return incomingEdges.length === 0;
|
|
30
|
+
}
|
|
31
|
+
function isFlowComplete(nodes, edges) {
|
|
32
|
+
const connectedEdges = getConnectedEdges(nodes, edges);
|
|
33
|
+
let isTriggerConnected = false;
|
|
34
|
+
let hasTriggerNode = false;
|
|
35
|
+
let hasEndNode = false;
|
|
36
|
+
let isEndConnected = false;
|
|
37
|
+
const nodesWithEmptyTarget = [];
|
|
38
|
+
for (const node of nodes) {
|
|
39
|
+
if (node.type === STICKY_NOTE_TYPE) continue;
|
|
40
|
+
const { outgoingEdges, incomingEdges } = findEdges(node, connectedEdges);
|
|
41
|
+
const isTrigger = isNodeTrigger(node);
|
|
42
|
+
if (isTrigger) {
|
|
43
|
+
hasTriggerNode = true;
|
|
44
|
+
if (checkFlowTerminalNode(
|
|
45
|
+
outgoingEdges,
|
|
46
|
+
incomingEdges,
|
|
47
|
+
true,
|
|
48
|
+
node.type
|
|
49
|
+
)) {
|
|
50
|
+
isTriggerConnected = true;
|
|
51
|
+
}
|
|
52
|
+
} else if (node.type === "end" /* END */) {
|
|
53
|
+
hasEndNode = true;
|
|
54
|
+
isEndConnected = checkFlowTerminalNode(
|
|
55
|
+
outgoingEdges,
|
|
56
|
+
incomingEdges,
|
|
57
|
+
false,
|
|
58
|
+
"end" /* END */
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (checkLoneNode(outgoingEdges, incomingEdges, isTrigger))
|
|
62
|
+
nodesWithEmptyTarget.push(node);
|
|
63
|
+
}
|
|
64
|
+
const hasAnyLoneNode = nodesWithEmptyTarget.length > 0;
|
|
65
|
+
const isEndValid = !hasEndNode || isEndConnected;
|
|
66
|
+
return hasTriggerNode && isTriggerConnected && isEndValid && !hasAnyLoneNode;
|
|
67
|
+
}
|
|
68
|
+
function useFlowValidator(onValidate) {
|
|
69
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
70
|
+
const { getNodes, getEdges } = useReactFlow();
|
|
71
|
+
const validate = useCallback(async () => {
|
|
72
|
+
setIsValidating(true);
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
74
|
+
onValidate?.(isFlowComplete(getNodes(), getEdges()));
|
|
75
|
+
setIsValidating(false);
|
|
76
|
+
}, [getNodes, getEdges, onValidate]);
|
|
77
|
+
return [isValidating, validate];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/builder/lib/save-guards.ts
|
|
81
|
+
function editingLeaseLocked() {
|
|
82
|
+
return isLocked(useEditLeaseStore.getState().phase);
|
|
83
|
+
}
|
|
84
|
+
function autosaveAllowedFor(workflow) {
|
|
85
|
+
return useSaveStatusStore.getState().autosaveEnabled && canAutosave(workflow);
|
|
86
|
+
}
|
|
87
|
+
function editStateFor(state) {
|
|
88
|
+
return editStateOf(
|
|
89
|
+
useSaveStatusStore.getState().baseline,
|
|
90
|
+
{
|
|
91
|
+
fingerprint: workflowFingerprint(state.workflow),
|
|
92
|
+
generation: state.loadGeneration,
|
|
93
|
+
editRevision: state.editRevision
|
|
94
|
+
},
|
|
95
|
+
Date.now()
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function departingSave(state) {
|
|
99
|
+
const status = useSaveStatusStore.getState();
|
|
100
|
+
if (status.autosavePaused || editingLeaseLocked()) return null;
|
|
101
|
+
if (editStateFor(state) !== "dirty") return null;
|
|
102
|
+
if (!autosaveAllowedFor(state.workflow)) return null;
|
|
103
|
+
const { nodes, edges } = state.workflow;
|
|
104
|
+
if (!isFlowComplete(nodes, edges)) return null;
|
|
105
|
+
return snapshotForSave(state.workflow, state.loadGeneration);
|
|
106
|
+
}
|
|
107
|
+
function touchesSavedFields(state, previous) {
|
|
108
|
+
return state.workflow.nodes !== previous.workflow.nodes || state.workflow.edges !== previous.workflow.edges || state.workflow.name !== previous.workflow.name || state.workflow.alias !== previous.workflow.alias || state.workflow.status !== previous.workflow.status || state.loadGeneration !== previous.loadGeneration;
|
|
109
|
+
}
|
|
110
|
+
function unsavedState() {
|
|
111
|
+
const flow = useFlowStore.getState();
|
|
112
|
+
const status = useSaveStatusStore.getState();
|
|
113
|
+
return {
|
|
114
|
+
// Checked fresh: the dirty flag trails the last edit by a moment.
|
|
115
|
+
dirty: status.dirty || editStateFor(flow) === "dirty",
|
|
116
|
+
// A paused autosave (a refused save), or a page someone else is editing
|
|
117
|
+
// from, does not save on the way out either.
|
|
118
|
+
autosaveAllowed: autosaveAllowedFor(flow.workflow) && !status.autosavePaused && !editingLeaseLocked(),
|
|
119
|
+
incomplete: status.incomplete || !isFlowComplete(flow.workflow.nodes, flow.workflow.edges),
|
|
120
|
+
failed: status.failure !== null
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function leavingLosesChanges() {
|
|
124
|
+
return leaveNeedsConfirmation(unsavedState());
|
|
125
|
+
}
|
|
126
|
+
function runPreparation() {
|
|
127
|
+
return runPreparationOf(unsavedState());
|
|
128
|
+
}
|
|
129
|
+
function savedEverythingBeforeLeaving() {
|
|
130
|
+
return editStateFor(useFlowStore.getState()) !== "dirty";
|
|
131
|
+
}
|
|
132
|
+
function watchLeavingLosesChanges(onChange, delayMs) {
|
|
133
|
+
let current;
|
|
134
|
+
let timer;
|
|
135
|
+
const report = (losesChanges) => {
|
|
136
|
+
if (losesChanges === current) return;
|
|
137
|
+
current = losesChanges;
|
|
138
|
+
onChange(losesChanges);
|
|
139
|
+
};
|
|
140
|
+
const settle = () => {
|
|
141
|
+
timer = void 0;
|
|
142
|
+
report(leavingLosesChanges());
|
|
143
|
+
};
|
|
144
|
+
const settleLater = () => {
|
|
145
|
+
if (timer) clearTimeout(timer);
|
|
146
|
+
timer = setTimeout(settle, delayMs);
|
|
147
|
+
};
|
|
148
|
+
const settleNow = () => {
|
|
149
|
+
if (timer) clearTimeout(timer);
|
|
150
|
+
settle();
|
|
151
|
+
};
|
|
152
|
+
settle();
|
|
153
|
+
const unsubscribeFlow = useFlowStore.subscribe((state, previous) => {
|
|
154
|
+
if (!touchesSavedFields(state, previous)) return;
|
|
155
|
+
if (state.loadGeneration === previous.loadGeneration) report(true);
|
|
156
|
+
settleLater();
|
|
157
|
+
});
|
|
158
|
+
const unsubscribeStatus = useSaveStatusStore.subscribe(settleLater);
|
|
159
|
+
const unsubscribeLease = useEditLeaseStore.subscribe((state, previous) => {
|
|
160
|
+
if (isLocked(state.phase) !== isLocked(previous.phase)) settleNow();
|
|
161
|
+
});
|
|
162
|
+
return () => {
|
|
163
|
+
unsubscribeFlow();
|
|
164
|
+
unsubscribeStatus();
|
|
165
|
+
unsubscribeLease();
|
|
166
|
+
if (timer) clearTimeout(timer);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/builder/hooks/attach-node-helpers.ts
|
|
171
|
+
var ATTACH_GAP_X = 120;
|
|
172
|
+
var DEFAULT_NODE_WIDTH = 288;
|
|
173
|
+
function computeAttachPosition(source) {
|
|
174
|
+
if (!source) return void 0;
|
|
175
|
+
const width = source.measured?.width ?? DEFAULT_NODE_WIDTH;
|
|
176
|
+
return {
|
|
177
|
+
x: source.position.x + width + ATTACH_GAP_X,
|
|
178
|
+
y: source.position.y
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function firstInputHandleId(data) {
|
|
182
|
+
return resolveInputHandles(data)[0]?.id ?? "target";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { autosaveAllowedFor, computeAttachPosition, departingSave, editStateFor, editingLeaseLocked, firstInputHandleId, isFlowComplete, leavingLosesChanges, runPreparation, savedEverythingBeforeLeaving, touchesSavedFields, useFlowValidator, watchLeavingLosesChanges };
|