@growthub/cli 0.14.4 → 0.14.5
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/resolvers/[integrationId]/route.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +86 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +30 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +400 -188
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +24 -19
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +7 -82
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/resolver-constructor.js +217 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-registry.js +99 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/unified-resolver-registry.js +545 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +30 -2
- package/package.json +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +0 -141
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +0 -64
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +0 -376
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Check, ExternalLink, Play, Terminal, X } from "lucide-react";
|
|
4
|
-
import { getApiRegistrySandboxToolState } from "@/lib/orchestration-graph";
|
|
5
|
-
|
|
6
|
-
export function ApiRegistryActionCard({
|
|
7
|
-
registryRow,
|
|
8
|
-
workspaceConfig,
|
|
9
|
-
disabled,
|
|
10
|
-
onCreateSandboxTool,
|
|
11
|
-
onOpenSandboxTool,
|
|
12
|
-
onRunSandboxTool,
|
|
13
|
-
onTestConnection,
|
|
14
|
-
testing,
|
|
15
|
-
sandboxRunning
|
|
16
|
-
}) {
|
|
17
|
-
const state = getApiRegistrySandboxToolState(registryRow, workspaceConfig);
|
|
18
|
-
|
|
19
|
-
if (state.kind === "incomplete") {
|
|
20
|
-
const checklist = state.checklist || [];
|
|
21
|
-
return (
|
|
22
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="Complete API setup">
|
|
23
|
-
<div className="dm-api-action-card-body">
|
|
24
|
-
<p className="dm-api-action-card-eyebrow">API Registry</p>
|
|
25
|
-
<h3>Complete API setup</h3>
|
|
26
|
-
<p>
|
|
27
|
-
This API needs a registry ID, base URL, endpoint, method, and auth reference before it can become a sandbox tool.
|
|
28
|
-
</p>
|
|
29
|
-
<ul className="dm-api-action-checklist">
|
|
30
|
-
{checklist.map((item) => (
|
|
31
|
-
<li key={item.field} className={item.ok ? "is-done" : "is-pending"}>
|
|
32
|
-
{item.ok ? <Check size={14} aria-hidden="true" /> : <X size={14} aria-hidden="true" />}
|
|
33
|
-
<span>{item.field}</span>
|
|
34
|
-
</li>
|
|
35
|
-
))}
|
|
36
|
-
</ul>
|
|
37
|
-
</div>
|
|
38
|
-
</section>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (state.kind === "untested") {
|
|
43
|
-
return (
|
|
44
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="Test API first">
|
|
45
|
-
<div className="dm-api-action-card-body">
|
|
46
|
-
<p className="dm-api-action-card-eyebrow">Sandbox tool</p>
|
|
47
|
-
<h3>Test this API first</h3>
|
|
48
|
-
<p>{state.message}</p>
|
|
49
|
-
</div>
|
|
50
|
-
<button
|
|
51
|
-
type="button"
|
|
52
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
53
|
-
disabled={disabled || testing}
|
|
54
|
-
onClick={onTestConnection}
|
|
55
|
-
>
|
|
56
|
-
{testing ? "Testing…" : "Test connection"}
|
|
57
|
-
</button>
|
|
58
|
-
</section>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (state.kind === "failed") {
|
|
63
|
-
return (
|
|
64
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="API test failed">
|
|
65
|
-
<div className="dm-api-action-card-body">
|
|
66
|
-
<p className="dm-api-action-card-eyebrow">Connection</p>
|
|
67
|
-
<h3>API test failed</h3>
|
|
68
|
-
<p>{state.message}</p>
|
|
69
|
-
</div>
|
|
70
|
-
<button
|
|
71
|
-
type="button"
|
|
72
|
-
className="dm-btn-outline dm-api-action-card-cta"
|
|
73
|
-
disabled={disabled || testing}
|
|
74
|
-
onClick={onTestConnection}
|
|
75
|
-
>
|
|
76
|
-
{testing ? "Testing…" : "Retest"}
|
|
77
|
-
</button>
|
|
78
|
-
</section>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (state.kind === "existing") {
|
|
83
|
-
const toolName = String(state.row?.Name || "").trim();
|
|
84
|
-
return (
|
|
85
|
-
<section className="dm-api-action-card" aria-label="Sandbox tool ready">
|
|
86
|
-
<div className="dm-api-action-card-icon" aria-hidden="true">
|
|
87
|
-
<Terminal size={18} />
|
|
88
|
-
</div>
|
|
89
|
-
<div className="dm-api-action-card-body">
|
|
90
|
-
<p className="dm-api-action-card-eyebrow">Sandbox tool</p>
|
|
91
|
-
<h3>Sandbox tool ready</h3>
|
|
92
|
-
<p>{toolName} — governed row linked to this API Registry entry.</p>
|
|
93
|
-
</div>
|
|
94
|
-
<div className="dm-api-action-card-actions">
|
|
95
|
-
<button
|
|
96
|
-
type="button"
|
|
97
|
-
className="dm-btn-outline dm-api-action-card-cta"
|
|
98
|
-
disabled={disabled || !toolName}
|
|
99
|
-
onClick={() => onOpenSandboxTool?.({ name: toolName })}
|
|
100
|
-
>
|
|
101
|
-
<ExternalLink size={14} aria-hidden="true" />
|
|
102
|
-
Open sandbox tool
|
|
103
|
-
</button>
|
|
104
|
-
<button
|
|
105
|
-
type="button"
|
|
106
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
107
|
-
disabled={disabled || sandboxRunning || !toolName}
|
|
108
|
-
onClick={() => onRunSandboxTool?.({ name: toolName })}
|
|
109
|
-
>
|
|
110
|
-
<Play size={14} aria-hidden="true" />
|
|
111
|
-
{sandboxRunning ? "Running…" : "Run sandbox"}
|
|
112
|
-
</button>
|
|
113
|
-
</div>
|
|
114
|
-
</section>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<section className="dm-api-action-card" aria-label="Create sandbox tool">
|
|
120
|
-
<div className="dm-api-action-card-icon" aria-hidden="true">
|
|
121
|
-
<Terminal size={18} />
|
|
122
|
-
</div>
|
|
123
|
-
<div className="dm-api-action-card-body">
|
|
124
|
-
<p className="dm-api-action-card-eyebrow">API connected</p>
|
|
125
|
-
<h3>Create sandbox tool</h3>
|
|
126
|
-
<p>
|
|
127
|
-
This API is connected. Turn it into a sandbox tool that agents can run safely from this workspace.
|
|
128
|
-
</p>
|
|
129
|
-
<p className="dm-api-action-card-note">No secrets are stored. Nothing runs until you test the sandbox.</p>
|
|
130
|
-
</div>
|
|
131
|
-
<button
|
|
132
|
-
type="button"
|
|
133
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
onClick={onCreateSandboxTool}
|
|
136
|
-
>
|
|
137
|
-
Create sandbox tool
|
|
138
|
-
</button>
|
|
139
|
-
</section>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { X } from "lucide-react";
|
|
4
|
-
import { summarizeOrchestrationGraph } from "@/lib/orchestration-graph";
|
|
5
|
-
|
|
6
|
-
export function SandboxToolConfirmModal({
|
|
7
|
-
open,
|
|
8
|
-
toolName,
|
|
9
|
-
authRef,
|
|
10
|
-
orchestrationGraph,
|
|
11
|
-
onConfirm,
|
|
12
|
-
onCancel,
|
|
13
|
-
creating
|
|
14
|
-
}) {
|
|
15
|
-
if (!open) return null;
|
|
16
|
-
|
|
17
|
-
const summary = summarizeOrchestrationGraph(orchestrationGraph);
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="dm-orchestration-confirm dm-orchestration-confirm__backdrop" onClick={onCancel} role="presentation">
|
|
21
|
-
<section
|
|
22
|
-
className="dm-orchestration-confirm__dialog"
|
|
23
|
-
role="dialog"
|
|
24
|
-
aria-modal="true"
|
|
25
|
-
aria-labelledby="sandbox-tool-confirm-title"
|
|
26
|
-
onClick={(event) => event.stopPropagation()}
|
|
27
|
-
>
|
|
28
|
-
<header className="dm-orchestration-confirm__head">
|
|
29
|
-
<div>
|
|
30
|
-
<p>Confirm</p>
|
|
31
|
-
<h2 id="sandbox-tool-confirm-title">Create sandbox tool?</h2>
|
|
32
|
-
</div>
|
|
33
|
-
<button type="button" className="dm-sidebar-close" onClick={onCancel} aria-label="Close">
|
|
34
|
-
<X size={16} />
|
|
35
|
-
</button>
|
|
36
|
-
</header>
|
|
37
|
-
<div className="dm-orchestration-confirm__body">
|
|
38
|
-
<p>This creates one Sandbox Environment row from the tested API Registry record.</p>
|
|
39
|
-
<ul className="dm-orchestration-confirm__list">
|
|
40
|
-
<li>Saves orchestrationGraph on the sandbox row</li>
|
|
41
|
-
<li>Stores <code>{authRef || "authRef"}</code> only — no secrets</li>
|
|
42
|
-
<li>Does not store secrets</li>
|
|
43
|
-
<li>Does not create widgets</li>
|
|
44
|
-
<li>Does not change dashboards</li>
|
|
45
|
-
<li>Does not change canvas</li>
|
|
46
|
-
<li>Does not run until you click Run sandbox</li>
|
|
47
|
-
</ul>
|
|
48
|
-
<p className="dm-orchestration-confirm__summary">
|
|
49
|
-
<span>Run plan</span>
|
|
50
|
-
{summary}
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
<footer className="dm-orchestration-confirm__foot">
|
|
54
|
-
<button type="button" className="dm-btn-outline" disabled={creating} onClick={onCancel}>
|
|
55
|
-
Cancel
|
|
56
|
-
</button>
|
|
57
|
-
<button type="button" className="dm-btn-primary-sm" disabled={creating} onClick={onConfirm}>
|
|
58
|
-
{creating ? "Creating…" : "Create tool"}
|
|
59
|
-
</button>
|
|
60
|
-
</footer>
|
|
61
|
-
</section>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { ArrowLeft } from "lucide-react";
|
|
5
|
-
import {
|
|
6
|
-
addCanonicalNodeToGraph,
|
|
7
|
-
buildBlankOrchestrationGraphShell,
|
|
8
|
-
buildDefaultOrchestrationGraphFromRegistry,
|
|
9
|
-
getNextCanonicalNodeId,
|
|
10
|
-
isApiRegistryTestSuccessful,
|
|
11
|
-
getOrchestrationGraphUiState,
|
|
12
|
-
parseOrchestrationGraph,
|
|
13
|
-
serializeOrchestrationGraph,
|
|
14
|
-
updateGraphNode,
|
|
15
|
-
validateOrchestrationGraph
|
|
16
|
-
} from "@/lib/orchestration-graph";
|
|
17
|
-
import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
|
|
18
|
-
import { OrchestrationGraphCanvas } from "./OrchestrationGraphCanvas.jsx";
|
|
19
|
-
import { OrchestrationGraphEmptyCanvas } from "./OrchestrationGraphEmptyCanvas.jsx";
|
|
20
|
-
import { OrchestrationNodeConfigPanel } from "./OrchestrationNodeConfigPanel.jsx";
|
|
21
|
-
|
|
22
|
-
export function SandboxToolDraftPanel({
|
|
23
|
-
registryRow,
|
|
24
|
-
draftOptions,
|
|
25
|
-
onDraftChange,
|
|
26
|
-
onRequestConfirm,
|
|
27
|
-
onCancel,
|
|
28
|
-
disabled
|
|
29
|
-
}) {
|
|
30
|
-
const integrationId = String(registryRow?.integrationId || "").trim();
|
|
31
|
-
const registryName = String(registryRow?.Name || integrationId).trim();
|
|
32
|
-
const defaultName = registryRow?.Name
|
|
33
|
-
? `${String(registryRow.Name).trim()} Tool`
|
|
34
|
-
: `${integrationId} Tool`;
|
|
35
|
-
|
|
36
|
-
const [name, setName] = useState(draftOptions?.name || defaultName);
|
|
37
|
-
const [description, setDescription] = useState(draftOptions?.description || String(registryRow?.description || "").trim());
|
|
38
|
-
const [runLocality, setRunLocality] = useState(draftOptions?.runLocality || "local");
|
|
39
|
-
const [adapter, setAdapter] = useState(draftOptions?.adapter || "local-process");
|
|
40
|
-
const [authRef, setAuthRef] = useState(draftOptions?.authRef || String(registryRow?.authRef || integrationId).trim());
|
|
41
|
-
const [envRefs, setEnvRefs] = useState(draftOptions?.envRefs || "");
|
|
42
|
-
const [networkAllow, setNetworkAllow] = useState(Boolean(draftOptions?.networkAllow));
|
|
43
|
-
const [timeoutMs, setTimeoutMs] = useState(String(draftOptions?.timeoutMs || "30000"));
|
|
44
|
-
const [rootPath, setRootPath] = useState(draftOptions?.rootPath || "data");
|
|
45
|
-
const [instructions, setInstructions] = useState(draftOptions?.instructions || "");
|
|
46
|
-
const [agentHost, setAgentHost] = useState(draftOptions?.agentHost || "");
|
|
47
|
-
const [schedulerRegistryId, setSchedulerRegistryId] = useState(
|
|
48
|
-
draftOptions?.schedulerRegistryId || (draftOptions?.runLocality === "serverless" ? integrationId : "")
|
|
49
|
-
);
|
|
50
|
-
const [selectedNodeId, setSelectedNodeId] = useState("input");
|
|
51
|
-
const [configTab, setConfigTab] = useState("node");
|
|
52
|
-
const [graphError, setGraphError] = useState("");
|
|
53
|
-
const [orchestrationGraph, setOrchestrationGraph] = useState(() => {
|
|
54
|
-
if (draftOptions?.orchestrationGraph) {
|
|
55
|
-
return parseOrchestrationGraph(draftOptions.orchestrationGraph);
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const registryKey = `${integrationId}:${String(registryRow?.endpoint || "")}:${String(registryRow?.method || "")}`;
|
|
61
|
-
const graphUiState = getOrchestrationGraphUiState(orchestrationGraph);
|
|
62
|
-
const graphUnset = graphUiState === "unset";
|
|
63
|
-
const graphBlankShell = graphUiState === "blank-shell";
|
|
64
|
-
const nextNodeId = useMemo(
|
|
65
|
-
() => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
|
|
66
|
-
[orchestrationGraph]
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (graphUnset || graphBlankShell) return;
|
|
71
|
-
setOrchestrationGraph((current) => {
|
|
72
|
-
const base = buildDefaultOrchestrationGraphFromRegistry(registryRow, {
|
|
73
|
-
label: registryName,
|
|
74
|
-
authRef,
|
|
75
|
-
rootPath
|
|
76
|
-
});
|
|
77
|
-
const parsed = parseOrchestrationGraph(current) || current;
|
|
78
|
-
if (!parsed?.nodes?.length) return current;
|
|
79
|
-
return {
|
|
80
|
-
...parsed,
|
|
81
|
-
nodes: parsed.nodes.map((node) => {
|
|
82
|
-
const template = base.nodes.find((n) => n.id === node.id);
|
|
83
|
-
if (!template) return node;
|
|
84
|
-
if (node.id === "api-request") {
|
|
85
|
-
return {
|
|
86
|
-
...node,
|
|
87
|
-
subtitle: template.subtitle,
|
|
88
|
-
config: { ...template.config, ...node.config, authRef }
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
if (node.id === "transform") {
|
|
92
|
-
return { ...node, config: { ...node.config, rootPath } };
|
|
93
|
-
}
|
|
94
|
-
return node;
|
|
95
|
-
})
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
}, [registryKey, registryName, authRef, rootPath, registryRow, integrationId, graphUnset, graphBlankShell]);
|
|
99
|
-
|
|
100
|
-
const selectedNode = useMemo(() => {
|
|
101
|
-
const parsed = parseOrchestrationGraph(orchestrationGraph) || orchestrationGraph;
|
|
102
|
-
if (!selectedNodeId || !parsed?.nodes) return null;
|
|
103
|
-
return parsed.nodes.find((n) => String(n.id) === selectedNodeId) || null;
|
|
104
|
-
}, [orchestrationGraph, selectedNodeId]);
|
|
105
|
-
|
|
106
|
-
const graphSerialized = useMemo(
|
|
107
|
-
() => (graphUnset ? "" : serializeOrchestrationGraph(orchestrationGraph)),
|
|
108
|
-
[orchestrationGraph, graphUnset]
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (graphUnset || graphBlankShell) {
|
|
113
|
-
setGraphError(graphBlankShell ? "Add at least Input and API Registry nodes before creating." : "");
|
|
114
|
-
} else {
|
|
115
|
-
const validation = validateOrchestrationGraph(orchestrationGraph);
|
|
116
|
-
setGraphError(validation.ok ? "" : validation.errors[0] || "Invalid graph");
|
|
117
|
-
}
|
|
118
|
-
onDraftChange?.({
|
|
119
|
-
name,
|
|
120
|
-
description,
|
|
121
|
-
runLocality,
|
|
122
|
-
adapter,
|
|
123
|
-
authRef,
|
|
124
|
-
envRefs,
|
|
125
|
-
networkAllow,
|
|
126
|
-
timeoutMs,
|
|
127
|
-
rootPath,
|
|
128
|
-
instructions,
|
|
129
|
-
agentHost,
|
|
130
|
-
schedulerRegistryId,
|
|
131
|
-
orchestrationGraph: graphSerialized
|
|
132
|
-
});
|
|
133
|
-
}, [
|
|
134
|
-
name,
|
|
135
|
-
description,
|
|
136
|
-
runLocality,
|
|
137
|
-
adapter,
|
|
138
|
-
authRef,
|
|
139
|
-
envRefs,
|
|
140
|
-
networkAllow,
|
|
141
|
-
timeoutMs,
|
|
142
|
-
rootPath,
|
|
143
|
-
instructions,
|
|
144
|
-
agentHost,
|
|
145
|
-
schedulerRegistryId,
|
|
146
|
-
graphSerialized,
|
|
147
|
-
orchestrationGraph,
|
|
148
|
-
graphUnset,
|
|
149
|
-
graphBlankShell,
|
|
150
|
-
onDraftChange
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
function startFromRegistry() {
|
|
154
|
-
setOrchestrationGraph(buildDefaultOrchestrationGraphFromRegistry(registryRow, { authRef, rootPath }));
|
|
155
|
-
setSelectedNodeId("input");
|
|
156
|
-
setConfigTab("node");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function startBlank() {
|
|
160
|
-
setOrchestrationGraph(buildBlankOrchestrationGraphShell());
|
|
161
|
-
setSelectedNodeId("input");
|
|
162
|
-
setConfigTab("node");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function applyPastedGraph(text) {
|
|
166
|
-
const parsed = parseOrchestrationGraph(text);
|
|
167
|
-
if (parsed) setOrchestrationGraph(parsed);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function addNextNode() {
|
|
171
|
-
if (!nextNodeId) return;
|
|
172
|
-
setOrchestrationGraph((g) => addCanonicalNodeToGraph(
|
|
173
|
-
g || buildBlankOrchestrationGraphShell(),
|
|
174
|
-
nextNodeId,
|
|
175
|
-
registryRow,
|
|
176
|
-
{ authRef, rootPath }
|
|
177
|
-
));
|
|
178
|
-
setSelectedNodeId(nextNodeId);
|
|
179
|
-
setConfigTab("node");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function handleNodeConfigChange(configPatch) {
|
|
183
|
-
if (!selectedNodeId) return;
|
|
184
|
-
setOrchestrationGraph((g) => updateGraphNode(g, selectedNodeId, configPatch));
|
|
185
|
-
if (selectedNodeId === "transform" && configPatch.rootPath) {
|
|
186
|
-
setRootPath(String(configPatch.rootPath));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function handleConnectorAction(payload) {
|
|
191
|
-
const { nodeId, tab } = resolveConnectorAction(payload);
|
|
192
|
-
setSelectedNodeId(nodeId);
|
|
193
|
-
setConfigTab(tab);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const defaultInstructions = `Governed sandbox tool for ${registryName}. Calls ${String(registryRow?.method || "GET").toUpperCase()} ${registryRow?.endpoint || registryRow?.baseUrl || ""}. authRef ${authRef} only — secrets resolve server-side.`;
|
|
197
|
-
const headerBadge = isApiRegistryTestSuccessful(registryRow) ? "connected" : "draft";
|
|
198
|
-
const canCreate = graphUiState === "populated" && !graphError && isApiRegistryTestSuccessful(registryRow);
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<section className="dm-orchestration-sidecar" aria-label="Sandbox orchestration field editor">
|
|
202
|
-
<header className="dm-orchestration-header">
|
|
203
|
-
<button type="button" className="dm-orchestration-header__back" onClick={onCancel} aria-label="Back">
|
|
204
|
-
<ArrowLeft size={16} />
|
|
205
|
-
</button>
|
|
206
|
-
<div className="dm-orchestration-header__titles">
|
|
207
|
-
<h2>Sandbox tool draft</h2>
|
|
208
|
-
<p>Created from {registryName}</p>
|
|
209
|
-
</div>
|
|
210
|
-
<span className={`dm-orchestration-header__badge is-${headerBadge}`}>{headerBadge}</span>
|
|
211
|
-
<div className="dm-orchestration-header__actions">
|
|
212
|
-
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={onCancel}>
|
|
213
|
-
Cancel
|
|
214
|
-
</button>
|
|
215
|
-
<button
|
|
216
|
-
type="button"
|
|
217
|
-
className="dm-btn-primary-sm"
|
|
218
|
-
disabled={disabled || !name.trim() || !canCreate}
|
|
219
|
-
onClick={onRequestConfirm}
|
|
220
|
-
>
|
|
221
|
-
Create tool
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
</header>
|
|
225
|
-
|
|
226
|
-
<div className="dm-orchestration-sidecar__body">
|
|
227
|
-
<div className="dm-orchestration-sidecar__canvas-col">
|
|
228
|
-
{graphUnset ? (
|
|
229
|
-
<OrchestrationGraphEmptyCanvas
|
|
230
|
-
disabled={disabled}
|
|
231
|
-
onStartFromRegistry={startFromRegistry}
|
|
232
|
-
onStartBlank={startBlank}
|
|
233
|
-
onPasteGraph={applyPastedGraph}
|
|
234
|
-
/>
|
|
235
|
-
) : graphBlankShell ? (
|
|
236
|
-
<div className="dm-orchestration-canvas dm-orchestration-canvas--blank-shell">
|
|
237
|
-
<p className="dm-orchestration-canvas__blank-hint">Add first node</p>
|
|
238
|
-
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={addNextNode}>
|
|
239
|
-
+ Add Input
|
|
240
|
-
</button>
|
|
241
|
-
</div>
|
|
242
|
-
) : (
|
|
243
|
-
<>
|
|
244
|
-
<OrchestrationGraphCanvas
|
|
245
|
-
graph={orchestrationGraph}
|
|
246
|
-
selectedNodeId={selectedNodeId}
|
|
247
|
-
onSelectNode={(node) => {
|
|
248
|
-
setSelectedNodeId(String(node?.id || ""));
|
|
249
|
-
setConfigTab("node");
|
|
250
|
-
}}
|
|
251
|
-
onConnectorAction={handleConnectorAction}
|
|
252
|
-
/>
|
|
253
|
-
{nextNodeId && (
|
|
254
|
-
<button
|
|
255
|
-
type="button"
|
|
256
|
-
className="dm-btn-outline dm-orchestration-canvas__add-node"
|
|
257
|
-
disabled={disabled}
|
|
258
|
-
onClick={addNextNode}
|
|
259
|
-
>
|
|
260
|
-
+ Add {nextNodeId === "api-request" ? "API Registry" : nextNodeId === "transform" ? "Transform" : nextNodeId === "result" ? "Result" : "Input"}
|
|
261
|
-
</button>
|
|
262
|
-
)}
|
|
263
|
-
</>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
<div className="dm-orchestration-sidecar__config-col">
|
|
268
|
-
{graphUiState === "populated" && (
|
|
269
|
-
<OrchestrationNodeConfigPanel
|
|
270
|
-
node={selectedNode}
|
|
271
|
-
registryRow={registryRow}
|
|
272
|
-
disabled={disabled}
|
|
273
|
-
activeTab={configTab}
|
|
274
|
-
onTabChange={setConfigTab}
|
|
275
|
-
onConfigChange={handleNodeConfigChange}
|
|
276
|
-
/>
|
|
277
|
-
)}
|
|
278
|
-
|
|
279
|
-
<details className="dm-orchestration-runtime">
|
|
280
|
-
<summary>Runtime (sandbox row)</summary>
|
|
281
|
-
<div className="dm-orchestration-runtime__fields">
|
|
282
|
-
<label className="dm-orchestration-config__field">
|
|
283
|
-
<span>Name</span>
|
|
284
|
-
<input value={name} disabled={disabled} onChange={(e) => setName(e.target.value)} />
|
|
285
|
-
</label>
|
|
286
|
-
<label className="dm-orchestration-config__field">
|
|
287
|
-
<span>Description</span>
|
|
288
|
-
<textarea rows={2} value={description} disabled={disabled} onChange={(e) => setDescription(e.target.value)} />
|
|
289
|
-
</label>
|
|
290
|
-
<label className="dm-orchestration-config__field">
|
|
291
|
-
<span>Run locality</span>
|
|
292
|
-
<select
|
|
293
|
-
value={runLocality}
|
|
294
|
-
disabled={disabled}
|
|
295
|
-
onChange={(e) => {
|
|
296
|
-
const next = e.target.value;
|
|
297
|
-
setRunLocality(next);
|
|
298
|
-
setAdapter(next === "serverless" ? "serverless" : "local-process");
|
|
299
|
-
if (next === "serverless" && !schedulerRegistryId) {
|
|
300
|
-
setSchedulerRegistryId(integrationId);
|
|
301
|
-
}
|
|
302
|
-
}}
|
|
303
|
-
>
|
|
304
|
-
<option value="local">local</option>
|
|
305
|
-
<option value="serverless">serverless</option>
|
|
306
|
-
</select>
|
|
307
|
-
</label>
|
|
308
|
-
<label className="dm-orchestration-config__field">
|
|
309
|
-
<span>Adapter</span>
|
|
310
|
-
<select value={adapter} disabled={disabled} onChange={(e) => setAdapter(e.target.value)}>
|
|
311
|
-
<option value="local-process">local-process</option>
|
|
312
|
-
<option value="local-agent-host">local-agent-host</option>
|
|
313
|
-
<option value="serverless">serverless</option>
|
|
314
|
-
</select>
|
|
315
|
-
</label>
|
|
316
|
-
{adapter === "local-agent-host" && (
|
|
317
|
-
<label className="dm-orchestration-config__field">
|
|
318
|
-
<span>Agent host</span>
|
|
319
|
-
<input value={agentHost} disabled={disabled} onChange={(e) => setAgentHost(e.target.value)} />
|
|
320
|
-
</label>
|
|
321
|
-
)}
|
|
322
|
-
{runLocality === "serverless" && (
|
|
323
|
-
<label className="dm-orchestration-config__field">
|
|
324
|
-
<span>Scheduler registry ID</span>
|
|
325
|
-
<input
|
|
326
|
-
value={schedulerRegistryId}
|
|
327
|
-
disabled={disabled}
|
|
328
|
-
onChange={(e) => setSchedulerRegistryId(e.target.value)}
|
|
329
|
-
/>
|
|
330
|
-
</label>
|
|
331
|
-
)}
|
|
332
|
-
<label className="dm-orchestration-config__field">
|
|
333
|
-
<span>Auth reference</span>
|
|
334
|
-
<input value={authRef} disabled={disabled} onChange={(e) => setAuthRef(e.target.value)} />
|
|
335
|
-
</label>
|
|
336
|
-
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
337
|
-
<input
|
|
338
|
-
type="checkbox"
|
|
339
|
-
checked={networkAllow}
|
|
340
|
-
disabled={disabled}
|
|
341
|
-
onChange={(e) => setNetworkAllow(e.target.checked)}
|
|
342
|
-
/>
|
|
343
|
-
<span>Network allowed</span>
|
|
344
|
-
</label>
|
|
345
|
-
<label className="dm-orchestration-config__field">
|
|
346
|
-
<span>Env refs (comma-separated)</span>
|
|
347
|
-
<input value={envRefs} disabled={disabled} onChange={(e) => setEnvRefs(e.target.value)} />
|
|
348
|
-
</label>
|
|
349
|
-
<label className="dm-orchestration-config__field">
|
|
350
|
-
<span>Timeout (ms)</span>
|
|
351
|
-
<input value={timeoutMs} disabled={disabled} onChange={(e) => setTimeoutMs(e.target.value)} />
|
|
352
|
-
</label>
|
|
353
|
-
<label className="dm-orchestration-config__field">
|
|
354
|
-
<span>Instructions</span>
|
|
355
|
-
<textarea
|
|
356
|
-
rows={3}
|
|
357
|
-
value={instructions || defaultInstructions}
|
|
358
|
-
disabled={disabled}
|
|
359
|
-
onChange={(e) => setInstructions(e.target.value)}
|
|
360
|
-
/>
|
|
361
|
-
</label>
|
|
362
|
-
</div>
|
|
363
|
-
</details>
|
|
364
|
-
|
|
365
|
-
{graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
|
|
366
|
-
{graphUnset && (
|
|
367
|
-
<p className="dm-orchestration-config__hint">Start a graph before creating the sandbox tool.</p>
|
|
368
|
-
)}
|
|
369
|
-
<p className="dm-orchestration-sidecar__footnote">
|
|
370
|
-
No secrets are stored. Nothing runs until you click Run sandbox after creation.
|
|
371
|
-
</p>
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
</section>
|
|
375
|
-
);
|
|
376
|
-
}
|