@growthub/cli 0.13.2 → 0.13.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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +48 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +136 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +713 -92
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +514 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +72 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +766 -138
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +15 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +384 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +323 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +32 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +629 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +542 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +9 -0
- package/package.json +1 -1
|
@@ -1,22 +1,339 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { ArrowLeft, GitBranch } from "lucide-react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { ArrowLeft, Download, GitBranch, Play, RefreshCw, Search, Square } from "lucide-react";
|
|
5
5
|
import { parseSandboxRunTrace, normalizeRunRecord } from "@/lib/orchestration-run-trace";
|
|
6
|
+
import {
|
|
7
|
+
buildRunTimeline,
|
|
8
|
+
downloadRunBundle,
|
|
9
|
+
filterRunLogTree,
|
|
10
|
+
formatRunDuration,
|
|
11
|
+
normalizeRunConsoleRecord
|
|
12
|
+
} from "@/lib/orchestration-run-console";
|
|
6
13
|
import { redactSecretsFromText } from "@/lib/orchestration-graph";
|
|
7
14
|
|
|
15
|
+
const ACTIVE_STATUSES = new Set(["executing", "queued", "testing", "running"]);
|
|
16
|
+
const LIVE_POLL_MS = 1500;
|
|
17
|
+
|
|
18
|
+
function formatTimestamp(iso) {
|
|
19
|
+
const text = String(iso || "").trim();
|
|
20
|
+
if (!text) return "—";
|
|
21
|
+
const ms = Date.parse(text);
|
|
22
|
+
if (!Number.isFinite(ms)) return text;
|
|
23
|
+
try {
|
|
24
|
+
return new Date(ms).toLocaleString();
|
|
25
|
+
} catch {
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function statusToVariant(status) {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case "completed":
|
|
33
|
+
return "ok";
|
|
34
|
+
case "failed":
|
|
35
|
+
return "fail";
|
|
36
|
+
case "executing":
|
|
37
|
+
case "queued":
|
|
38
|
+
case "testing":
|
|
39
|
+
case "running":
|
|
40
|
+
return "active";
|
|
41
|
+
case "canceled":
|
|
42
|
+
return "canceled";
|
|
43
|
+
default:
|
|
44
|
+
return "neutral";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function describeStatus(status) {
|
|
49
|
+
switch (status) {
|
|
50
|
+
case "completed":
|
|
51
|
+
return "Completed";
|
|
52
|
+
case "failed":
|
|
53
|
+
return "Failed";
|
|
54
|
+
case "executing":
|
|
55
|
+
return "Executing";
|
|
56
|
+
case "queued":
|
|
57
|
+
return "Queued";
|
|
58
|
+
case "testing":
|
|
59
|
+
return "Testing";
|
|
60
|
+
case "running":
|
|
61
|
+
return "Running";
|
|
62
|
+
case "canceled":
|
|
63
|
+
return "Canceled";
|
|
64
|
+
default:
|
|
65
|
+
return "Unknown";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function previewRecordFromRow(row, rowTrace) {
|
|
70
|
+
const ranAt = rowTrace.ranAt || row?.lastTested || "";
|
|
71
|
+
const previewSource = {
|
|
72
|
+
runId: rowTrace.runId || row?.lastRunId || "",
|
|
73
|
+
ranAt,
|
|
74
|
+
exitCode: rowTrace.exitCode ?? null,
|
|
75
|
+
durationMs: rowTrace.durationMs ?? null,
|
|
76
|
+
error: rowTrace.error || "",
|
|
77
|
+
stdout: rowTrace.stdout || "",
|
|
78
|
+
stderr: rowTrace.stderr || "",
|
|
79
|
+
output: rowTrace.output || "",
|
|
80
|
+
runtime: rowTrace.runtime || row?.runtime || "",
|
|
81
|
+
adapter: rowTrace.adapter || row?.adapter || "",
|
|
82
|
+
runLocality: rowTrace.runLocality || row?.runLocality || "",
|
|
83
|
+
envRefsResolved: rowTrace.envRefsResolved || [],
|
|
84
|
+
envRefsMissing: rowTrace.envRefsMissing || [],
|
|
85
|
+
sourceId: row?.lastSourceId || "",
|
|
86
|
+
version: row?.version || "",
|
|
87
|
+
lifecycleStatus: row?.lifecycleStatus || ""
|
|
88
|
+
};
|
|
89
|
+
return normalizeRunConsoleRecord(previewSource);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bundleToBlobUrl(bundle) {
|
|
93
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
94
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
95
|
+
return URL.createObjectURL(blob);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function downloadFile(filename, bundle) {
|
|
99
|
+
if (typeof window === "undefined") return;
|
|
100
|
+
const url = bundleToBlobUrl(bundle);
|
|
101
|
+
const link = document.createElement("a");
|
|
102
|
+
link.href = url;
|
|
103
|
+
link.download = filename;
|
|
104
|
+
document.body.appendChild(link);
|
|
105
|
+
link.click();
|
|
106
|
+
link.remove();
|
|
107
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function LogTreeNode({ node, depth, selectedId, onSelect, timelineMax }) {
|
|
111
|
+
const isSelected = String(selectedId) === String(node.id);
|
|
112
|
+
const ratio = timelineMax > 0 && node.durationMs > 0
|
|
113
|
+
? Math.min(1, node.durationMs / timelineMax)
|
|
114
|
+
: 0;
|
|
115
|
+
const variant = statusToVariant(node.status);
|
|
116
|
+
return (
|
|
117
|
+
<div className="dm-run-console__tree-block" data-depth={depth}>
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
className={`dm-run-console__tree-row${isSelected ? " is-active" : ""}`}
|
|
121
|
+
data-variant={variant}
|
|
122
|
+
onClick={() => onSelect(node)}
|
|
123
|
+
title={node.label}
|
|
124
|
+
>
|
|
125
|
+
<span className="dm-run-console__tree-indent" aria-hidden="true" style={{ width: `${depth * 12}px` }} />
|
|
126
|
+
<span className="dm-run-console__tree-dot" data-variant={variant} aria-hidden="true" />
|
|
127
|
+
<span className="dm-run-console__tree-label">{node.label}</span>
|
|
128
|
+
<span className="dm-run-console__tree-meta">{formatRunDuration(node.durationMs)}</span>
|
|
129
|
+
<span className="dm-run-console__tree-bar" aria-hidden="true">
|
|
130
|
+
<span style={{ width: `${Math.round(ratio * 100)}%` }} data-variant={variant} />
|
|
131
|
+
</span>
|
|
132
|
+
</button>
|
|
133
|
+
{Array.isArray(node.children) && node.children.length > 0 ? (
|
|
134
|
+
<div className="dm-run-console__tree-children">
|
|
135
|
+
{node.children.map((child) => (
|
|
136
|
+
<LogTreeNode
|
|
137
|
+
key={child.id}
|
|
138
|
+
node={child}
|
|
139
|
+
depth={depth + 1}
|
|
140
|
+
selectedId={selectedId}
|
|
141
|
+
onSelect={onSelect}
|
|
142
|
+
timelineMax={timelineMax}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
) : null}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function LifecycleBlock({ lifecycle }) {
|
|
152
|
+
if (!Array.isArray(lifecycle) || lifecycle.length === 0) {
|
|
153
|
+
return <p className="dm-run-console__hint">Lifecycle timestamps not available.</p>;
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
<ol className="dm-run-console__lifecycle">
|
|
157
|
+
{lifecycle.map((step) => (
|
|
158
|
+
<li key={step.label}>
|
|
159
|
+
<span className="dm-run-console__lifecycle-label">{step.label}</span>
|
|
160
|
+
<span className="dm-run-console__lifecycle-at">{formatTimestamp(step.at)}</span>
|
|
161
|
+
{step.durationMs > 0 ? (
|
|
162
|
+
<span className="dm-run-console__lifecycle-dur">{formatRunDuration(step.durationMs)}</span>
|
|
163
|
+
) : null}
|
|
164
|
+
</li>
|
|
165
|
+
))}
|
|
166
|
+
</ol>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function KeyValueBlock({ entries }) {
|
|
171
|
+
const list = entries.filter(([, value]) => value !== "" && value != null);
|
|
172
|
+
if (!list.length) return null;
|
|
173
|
+
return (
|
|
174
|
+
<dl className="dm-run-console__kv">
|
|
175
|
+
{list.map(([label, value]) => (
|
|
176
|
+
<div key={label}>
|
|
177
|
+
<dt>{label}</dt>
|
|
178
|
+
<dd>{value}</dd>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</dl>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function CodeBlock({ label, body }) {
|
|
186
|
+
if (!body) return null;
|
|
187
|
+
return (
|
|
188
|
+
<div className="dm-run-console__code">
|
|
189
|
+
<span>{label}</span>
|
|
190
|
+
<pre>{body}</pre>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function downloadText(filename, text, mime = "text/plain") {
|
|
196
|
+
if (typeof window === "undefined") return;
|
|
197
|
+
const blob = new Blob([text || ""], { type: mime });
|
|
198
|
+
const url = URL.createObjectURL(blob);
|
|
199
|
+
const link = document.createElement("a");
|
|
200
|
+
link.href = url;
|
|
201
|
+
link.download = filename;
|
|
202
|
+
document.body.appendChild(link);
|
|
203
|
+
link.click();
|
|
204
|
+
link.remove();
|
|
205
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function copyToClipboard(text) {
|
|
209
|
+
if (typeof navigator === "undefined" || !navigator?.clipboard?.writeText) return false;
|
|
210
|
+
try {
|
|
211
|
+
await navigator.clipboard.writeText(text || "");
|
|
212
|
+
return true;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function InputsSection({ payload }) {
|
|
219
|
+
const runInputs = payload?.runInputs;
|
|
220
|
+
const summary = payload?.inputSummary;
|
|
221
|
+
if (!runInputs && (!summary || summary.fieldCount === 0)) {
|
|
222
|
+
return (
|
|
223
|
+
<section className="dm-run-console__section">
|
|
224
|
+
<h3>Inputs</h3>
|
|
225
|
+
<p className="dm-run-console__hint">No manual inputs were submitted for this run.</p>
|
|
226
|
+
</section>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const valueEntries = runInputs && runInputs.values && typeof runInputs.values === "object"
|
|
230
|
+
? Object.entries(runInputs.values)
|
|
231
|
+
: [];
|
|
232
|
+
return (
|
|
233
|
+
<section className="dm-run-console__section">
|
|
234
|
+
<h3>Inputs</h3>
|
|
235
|
+
<KeyValueBlock
|
|
236
|
+
entries={[
|
|
237
|
+
["Source", summary?.source || runInputs?.source || "manual"],
|
|
238
|
+
["Fields", String(summary?.fieldCount ?? valueEntries.length)],
|
|
239
|
+
["Files", String(summary?.fileCount ?? (Array.isArray(runInputs?.files) ? runInputs.files.length : 0))]
|
|
240
|
+
]}
|
|
241
|
+
/>
|
|
242
|
+
{valueEntries.length > 0 ? (
|
|
243
|
+
<dl className="dm-run-console__kv">
|
|
244
|
+
{valueEntries.map(([key, value]) => {
|
|
245
|
+
const isSecretRef = value && typeof value === "object" && "secretRef" in value;
|
|
246
|
+
const display = isSecretRef
|
|
247
|
+
? `secretRef: ${String(value.secretRef || "[redacted]")}`
|
|
248
|
+
: typeof value === "string"
|
|
249
|
+
? value
|
|
250
|
+
: JSON.stringify(value);
|
|
251
|
+
return (
|
|
252
|
+
<div key={key}>
|
|
253
|
+
<dt>{key}</dt>
|
|
254
|
+
<dd>{display}</dd>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</dl>
|
|
259
|
+
) : null}
|
|
260
|
+
</section>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function ExportActions({ exports, output, record, selectedLogNode }) {
|
|
265
|
+
const available = Array.isArray(exports?.available) ? exports.available : [];
|
|
266
|
+
if (available.length === 0) {
|
|
267
|
+
return <p className="dm-run-console__hint">No exports available for this run.</p>;
|
|
268
|
+
}
|
|
269
|
+
const runLabel = String(record?.runId || "run").replace(/[^a-zA-Z0-9_-]+/g, "-") || "run";
|
|
270
|
+
const [copied, setCopied] = useState("");
|
|
271
|
+
|
|
272
|
+
async function handleCopy() {
|
|
273
|
+
const ok = await copyToClipboard(output?.normalizedOutput || output?.stdout || "");
|
|
274
|
+
setCopied(ok ? "Output copied" : "Clipboard unavailable");
|
|
275
|
+
setTimeout(() => setCopied(""), 1600);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div className="dm-run-console__exports">
|
|
280
|
+
{available.includes("copy-output") && (
|
|
281
|
+
<button type="button" className="dm-btn-outline" onClick={handleCopy}>Copy output</button>
|
|
282
|
+
)}
|
|
283
|
+
{available.includes("download-stdout") && output?.stdout && (
|
|
284
|
+
<button
|
|
285
|
+
type="button"
|
|
286
|
+
className="dm-btn-outline"
|
|
287
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-stdout.txt`, output.stdout)}
|
|
288
|
+
>Download stdout</button>
|
|
289
|
+
)}
|
|
290
|
+
{available.includes("download-stderr") && output?.stderr && (
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
className="dm-btn-outline"
|
|
294
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-stderr.txt`, output.stderr)}
|
|
295
|
+
>Download stderr</button>
|
|
296
|
+
)}
|
|
297
|
+
{available.includes("download-normalized-output") && output?.normalizedOutput && output.normalizedOutput !== output.stdout && (
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
className="dm-btn-outline"
|
|
301
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-output.json`, output.normalizedOutput, "application/json")}
|
|
302
|
+
>Download output JSON</button>
|
|
303
|
+
)}
|
|
304
|
+
{available.includes("download-log-node") && selectedLogNode?.text && (
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
className="dm-btn-outline"
|
|
308
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-${selectedLogNode.id || "log"}.txt`, selectedLogNode.text)}
|
|
309
|
+
>Download selected log node</button>
|
|
310
|
+
)}
|
|
311
|
+
{copied && <span className="dm-run-console__export-toast">{copied}</span>}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
8
316
|
export function OrchestrationRunTracePanel({
|
|
9
317
|
row,
|
|
10
318
|
objectId,
|
|
11
319
|
fieldName,
|
|
12
320
|
selectedRunId,
|
|
13
321
|
onBack,
|
|
14
|
-
onOpenGraph
|
|
322
|
+
onOpenGraph,
|
|
323
|
+
onReplay,
|
|
324
|
+
running
|
|
15
325
|
}) {
|
|
16
326
|
const [history, setHistory] = useState([]);
|
|
17
327
|
const [historyMessage, setHistoryMessage] = useState("");
|
|
18
328
|
const [loading, setLoading] = useState(false);
|
|
19
329
|
const [activeRunId, setActiveRunId] = useState(String(selectedRunId || row?.lastRunId || "").trim());
|
|
330
|
+
const [query, setQuery] = useState("");
|
|
331
|
+
const [errorsOnly, setErrorsOnly] = useState(false);
|
|
332
|
+
const [showQueueTime, setShowQueueTime] = useState(false);
|
|
333
|
+
const [activeDetailTab, setActiveDetailTab] = useState("overview");
|
|
334
|
+
const [selectedLogId, setSelectedLogId] = useState("root");
|
|
335
|
+
const [replayPending, setReplayPending] = useState(false);
|
|
336
|
+
const abortRef = useRef(null);
|
|
20
337
|
|
|
21
338
|
const rowTrace = useMemo(() => parseSandboxRunTrace(row?.lastResponse), [row?.lastResponse]);
|
|
22
339
|
|
|
@@ -24,136 +341,440 @@ export function OrchestrationRunTracePanel({
|
|
|
24
341
|
setActiveRunId(String(selectedRunId || row?.lastRunId || "").trim());
|
|
25
342
|
}, [selectedRunId, row?.lastRunId, row?.lastResponse]);
|
|
26
343
|
|
|
27
|
-
|
|
344
|
+
const loadHistory = useCallback(async (signal) => {
|
|
28
345
|
const objectIdValue = String(objectId || "").trim();
|
|
29
346
|
const name = String(row?.Name || "").trim();
|
|
30
347
|
if (!objectIdValue || !name) return;
|
|
31
348
|
setLoading(true);
|
|
32
349
|
setHistoryMessage("");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
350
|
+
try {
|
|
351
|
+
const res = await fetch(
|
|
352
|
+
`/api/workspace/sandbox-run?objectId=${encodeURIComponent(objectIdValue)}&name=${encodeURIComponent(name)}`,
|
|
353
|
+
{ cache: "no-store", signal }
|
|
354
|
+
);
|
|
355
|
+
const payload = await res.json();
|
|
356
|
+
if (!payload.ok) throw new Error(payload.error || "Could not load run history");
|
|
357
|
+
const normalized = Array.isArray(payload.records)
|
|
358
|
+
? payload.records.map(normalizeRunRecord).filter(Boolean)
|
|
359
|
+
: [];
|
|
360
|
+
setHistory(normalized);
|
|
361
|
+
setHistoryMessage(`${payload.recordCount || normalized.length} saved run${(payload.recordCount || normalized.length) === 1 ? "" : "s"}`);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
if (err?.name === "AbortError") return;
|
|
364
|
+
setHistory([]);
|
|
365
|
+
setHistoryMessage(err.message || "Could not load run history");
|
|
366
|
+
} finally {
|
|
367
|
+
setLoading(false);
|
|
368
|
+
}
|
|
47
369
|
}, [objectId, row?.Name]);
|
|
48
370
|
|
|
49
|
-
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
const controller = new AbortController();
|
|
373
|
+
loadHistory(controller.signal);
|
|
374
|
+
return () => controller.abort();
|
|
375
|
+
}, [loadHistory]);
|
|
376
|
+
|
|
377
|
+
const isClientRunning = Boolean(running) || replayPending;
|
|
378
|
+
const liveReloading = isClientRunning;
|
|
379
|
+
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
if (!liveReloading) return undefined;
|
|
382
|
+
const id = setInterval(() => {
|
|
383
|
+
loadHistory();
|
|
384
|
+
}, LIVE_POLL_MS);
|
|
385
|
+
return () => clearInterval(id);
|
|
386
|
+
}, [liveReloading, loadHistory]);
|
|
387
|
+
|
|
388
|
+
const activeRawRecord = useMemo(() => {
|
|
50
389
|
if (activeRunId && history.length) {
|
|
51
390
|
const match = history.find((r) => r.runId === activeRunId);
|
|
52
391
|
if (match) return match;
|
|
53
392
|
}
|
|
54
393
|
if (fieldName === "lastResponse" || !activeRunId) {
|
|
55
|
-
return
|
|
56
|
-
runId: rowTrace.runId || row?.lastRunId || "",
|
|
57
|
-
ranAt: rowTrace.ranAt || row?.lastTested || "",
|
|
58
|
-
exitCode: rowTrace.exitCode,
|
|
59
|
-
durationMs: rowTrace.durationMs,
|
|
60
|
-
error: rowTrace.error,
|
|
61
|
-
stdout: rowTrace.stdout,
|
|
62
|
-
stderr: rowTrace.stderr,
|
|
63
|
-
output: rowTrace.output
|
|
64
|
-
};
|
|
394
|
+
return null;
|
|
65
395
|
}
|
|
66
396
|
return history[0] || null;
|
|
67
|
-
}, [activeRunId, history,
|
|
397
|
+
}, [activeRunId, history, fieldName]);
|
|
398
|
+
|
|
399
|
+
const activeConsoleRecord = useMemo(() => {
|
|
400
|
+
if (activeRawRecord) return normalizeRunConsoleRecord(activeRawRecord);
|
|
401
|
+
return previewRecordFromRow(row, rowTrace);
|
|
402
|
+
}, [activeRawRecord, row, rowTrace]);
|
|
68
403
|
|
|
69
|
-
const
|
|
404
|
+
const timeline = useMemo(() => buildRunTimeline(history), [history]);
|
|
405
|
+
const timelineMax = timeline.reduce((m, item) => Math.max(m, item.durationMs || 0), 0);
|
|
406
|
+
|
|
407
|
+
const filteredTree = useMemo(() => (
|
|
408
|
+
filterRunLogTree(activeConsoleRecord?.logTree || [], { query, errorsOnly })
|
|
409
|
+
), [activeConsoleRecord, query, errorsOnly]);
|
|
410
|
+
|
|
411
|
+
const selectedLogNode = useMemo(() => {
|
|
412
|
+
const tree = activeConsoleRecord?.logTree || [];
|
|
413
|
+
const stack = [...tree];
|
|
414
|
+
while (stack.length) {
|
|
415
|
+
const node = stack.pop();
|
|
416
|
+
if (!node) continue;
|
|
417
|
+
if (String(node.id) === String(selectedLogId)) return node;
|
|
418
|
+
if (Array.isArray(node.children)) stack.push(...node.children);
|
|
419
|
+
}
|
|
420
|
+
return tree[0] || null;
|
|
421
|
+
}, [activeConsoleRecord, selectedLogId]);
|
|
422
|
+
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
setSelectedLogId("root");
|
|
425
|
+
}, [activeRunId]);
|
|
426
|
+
|
|
427
|
+
const summaryText = fieldName === "lastSourceId"
|
|
70
428
|
? `Source ${String(row?.lastSourceId || "").trim()}`
|
|
71
429
|
: fieldName === "lastRunId"
|
|
72
430
|
? `Run ${String(row?.lastRunId || activeRunId || "").trim()}`
|
|
73
431
|
: "Latest sandbox run";
|
|
74
432
|
|
|
433
|
+
const statusVariant = statusToVariant(activeConsoleRecord?.status);
|
|
434
|
+
const statusLabel = describeStatus(activeConsoleRecord?.status);
|
|
435
|
+
|
|
436
|
+
const canReplay = typeof onReplay === "function";
|
|
437
|
+
|
|
438
|
+
function handleReplay() {
|
|
439
|
+
if (!canReplay) return;
|
|
440
|
+
setReplayPending(true);
|
|
441
|
+
const controller = new AbortController();
|
|
442
|
+
abortRef.current = controller;
|
|
443
|
+
try {
|
|
444
|
+
const result = onReplay({ signal: controller.signal });
|
|
445
|
+
Promise.resolve(result)
|
|
446
|
+
.catch(() => {})
|
|
447
|
+
.finally(() => {
|
|
448
|
+
setReplayPending(false);
|
|
449
|
+
abortRef.current = null;
|
|
450
|
+
loadHistory();
|
|
451
|
+
});
|
|
452
|
+
} catch {
|
|
453
|
+
setReplayPending(false);
|
|
454
|
+
abortRef.current = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function handleCancel() {
|
|
459
|
+
if (abortRef.current) {
|
|
460
|
+
try { abortRef.current.abort(); } catch { /* noop */ }
|
|
461
|
+
}
|
|
462
|
+
setReplayPending(false);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function handleDownload() {
|
|
466
|
+
if (!activeConsoleRecord) return;
|
|
467
|
+
const bundle = downloadRunBundle({
|
|
468
|
+
record: activeRawRecord || activeConsoleRecord,
|
|
469
|
+
runId: activeConsoleRecord.runId,
|
|
470
|
+
sourceId: activeConsoleRecord.sourceId || row?.lastSourceId || ""
|
|
471
|
+
});
|
|
472
|
+
const runLabel = String(bundle.runId || "run").replace(/[^a-zA-Z0-9_-]+/g, "-") || "run";
|
|
473
|
+
downloadFile(`growthub-run-${runLabel}.json`, bundle);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const lifecycleEntries = activeConsoleRecord?.lifecycle || [];
|
|
477
|
+
const payload = activeConsoleRecord?.payload || {};
|
|
478
|
+
const output = activeConsoleRecord?.output || {};
|
|
479
|
+
const context = activeConsoleRecord?.context || {};
|
|
480
|
+
|
|
75
481
|
return (
|
|
76
|
-
<section className="dm-
|
|
77
|
-
<header className="dm-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
<div>
|
|
82
|
-
<h2>Run trace</h2>
|
|
83
|
-
<p>{summary} · {row?.Name || "Sandbox tool"}</p>
|
|
84
|
-
</div>
|
|
85
|
-
{onOpenGraph && (
|
|
86
|
-
<button type="button" className="dm-btn-outline" onClick={onOpenGraph}>
|
|
87
|
-
<GitBranch size={14} aria-hidden="true" />
|
|
88
|
-
Edit graph
|
|
482
|
+
<section className="dm-run-console" aria-label="Live runs console">
|
|
483
|
+
<header className="dm-run-console__head">
|
|
484
|
+
{onBack && (
|
|
485
|
+
<button type="button" className="dm-orchestration-header__back" onClick={onBack} aria-label="Back to record">
|
|
486
|
+
<ArrowLeft size={16} />
|
|
89
487
|
</button>
|
|
90
488
|
)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
489
|
+
<div className="dm-run-console__head-titles">
|
|
490
|
+
<span className="dm-run-console__crumbs">
|
|
491
|
+
Runs <span aria-hidden="true">/</span> <code>{activeConsoleRecord?.runId || "preview"}</code>
|
|
492
|
+
</span>
|
|
493
|
+
<h2>Run console</h2>
|
|
494
|
+
<p>{summaryText} · {row?.Name || "Sandbox tool"}</p>
|
|
495
|
+
</div>
|
|
496
|
+
<div className="dm-run-console__head-actions">
|
|
497
|
+
{canReplay && (
|
|
498
|
+
<button
|
|
499
|
+
type="button"
|
|
500
|
+
className="dm-btn-outline"
|
|
501
|
+
onClick={handleReplay}
|
|
502
|
+
disabled={isClientRunning}
|
|
503
|
+
title="Replay current saved config"
|
|
504
|
+
>
|
|
505
|
+
<Play size={13} aria-hidden="true" />
|
|
506
|
+
{replayPending ? "Replaying" : "Replay current config"}
|
|
507
|
+
</button>
|
|
508
|
+
)}
|
|
509
|
+
{isClientRunning && (
|
|
510
|
+
<button
|
|
511
|
+
type="button"
|
|
512
|
+
className="dm-btn-outline dm-run-console__cancel"
|
|
513
|
+
onClick={handleCancel}
|
|
514
|
+
title="Cancel the in-flight client request"
|
|
515
|
+
>
|
|
516
|
+
<Square size={13} aria-hidden="true" />
|
|
517
|
+
Cancel request
|
|
518
|
+
</button>
|
|
519
|
+
)}
|
|
98
520
|
<button
|
|
99
521
|
type="button"
|
|
100
|
-
className=
|
|
101
|
-
onClick={
|
|
522
|
+
className="dm-btn-outline"
|
|
523
|
+
onClick={handleDownload}
|
|
524
|
+
disabled={!activeConsoleRecord}
|
|
525
|
+
title="Download redacted JSON log for this run"
|
|
102
526
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
527
|
+
<Download size={13} aria-hidden="true" />
|
|
528
|
+
Download logs
|
|
105
529
|
</button>
|
|
106
|
-
{
|
|
530
|
+
{onOpenGraph && (
|
|
531
|
+
<button type="button" className="dm-btn-outline" onClick={onOpenGraph}>
|
|
532
|
+
<GitBranch size={13} aria-hidden="true" />
|
|
533
|
+
Edit graph
|
|
534
|
+
</button>
|
|
535
|
+
)}
|
|
536
|
+
</div>
|
|
537
|
+
</header>
|
|
538
|
+
|
|
539
|
+
<div className="dm-run-console__split">
|
|
540
|
+
<aside className="dm-run-console__left">
|
|
541
|
+
<div className="dm-run-console__toolbar">
|
|
542
|
+
<label className="dm-run-console__search">
|
|
543
|
+
<Search size={12} aria-hidden="true" />
|
|
544
|
+
<input
|
|
545
|
+
type="search"
|
|
546
|
+
placeholder="Search log"
|
|
547
|
+
value={query}
|
|
548
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
549
|
+
aria-label="Search log"
|
|
550
|
+
/>
|
|
551
|
+
</label>
|
|
552
|
+
<label className="dm-run-console__toggle">
|
|
553
|
+
<input
|
|
554
|
+
type="checkbox"
|
|
555
|
+
checked={showQueueTime}
|
|
556
|
+
onChange={(e) => setShowQueueTime(e.target.checked)}
|
|
557
|
+
/>
|
|
558
|
+
<span>Queue time</span>
|
|
559
|
+
</label>
|
|
560
|
+
<label className="dm-run-console__toggle">
|
|
561
|
+
<input
|
|
562
|
+
type="checkbox"
|
|
563
|
+
checked={errorsOnly}
|
|
564
|
+
onChange={(e) => setErrorsOnly(e.target.checked)}
|
|
565
|
+
/>
|
|
566
|
+
<span>Errors only</span>
|
|
567
|
+
</label>
|
|
568
|
+
<span
|
|
569
|
+
className={`dm-run-console__live${liveReloading ? " is-on" : ""}`}
|
|
570
|
+
aria-live="polite"
|
|
571
|
+
>
|
|
572
|
+
<span className="dm-run-console__live-dot" aria-hidden="true" />
|
|
573
|
+
{liveReloading ? "Live reloading" : "Idle"}
|
|
574
|
+
</span>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<div className="dm-run-console__history">
|
|
578
|
+
<p className="dm-run-console__history-title">Run history</p>
|
|
579
|
+
{loading && <p className="dm-run-console__hint">Loading…</p>}
|
|
580
|
+
{!loading && historyMessage && <p className="dm-run-console__hint">{historyMessage}</p>}
|
|
107
581
|
<button
|
|
108
|
-
key={record.runId || record.ranAt}
|
|
109
582
|
type="button"
|
|
110
|
-
className={`dm-
|
|
111
|
-
onClick={() => setActiveRunId(
|
|
583
|
+
className={`dm-run-console__history-row${!activeRunId ? " is-active" : ""}`}
|
|
584
|
+
onClick={() => setActiveRunId("")}
|
|
112
585
|
>
|
|
113
|
-
<span
|
|
114
|
-
<span>
|
|
115
|
-
{
|
|
116
|
-
{record.ranAt ? ` · ${record.ranAt}` : ""}
|
|
586
|
+
<span className="dm-run-console__history-label">Row preview</span>
|
|
587
|
+
<span className="dm-run-console__history-meta">
|
|
588
|
+
{row?.status || rowTrace.status || "—"} · {formatTimestamp(row?.lastTested || rowTrace.ranAt)}
|
|
117
589
|
</span>
|
|
118
590
|
</button>
|
|
119
|
-
|
|
120
|
-
|
|
591
|
+
{history.map((record) => {
|
|
592
|
+
const isActive = activeRunId === record.runId;
|
|
593
|
+
const variant = statusToVariant(normalizeRunConsoleRecord(record)?.status);
|
|
594
|
+
return (
|
|
595
|
+
<button
|
|
596
|
+
key={record.runId || record.ranAt}
|
|
597
|
+
type="button"
|
|
598
|
+
className={`dm-run-console__history-row${isActive ? " is-active" : ""}`}
|
|
599
|
+
data-variant={variant}
|
|
600
|
+
onClick={() => setActiveRunId(record.runId)}
|
|
601
|
+
>
|
|
602
|
+
<span className="dm-run-console__history-label">
|
|
603
|
+
<span className="dm-run-console__tree-dot" data-variant={variant} aria-hidden="true" />
|
|
604
|
+
{record.runId || "run"}
|
|
605
|
+
</span>
|
|
606
|
+
<span className="dm-run-console__history-meta">
|
|
607
|
+
{record.exitCode === 0 && !record.error ? "completed" : "failed"} · {formatTimestamp(record.ranAt)}
|
|
608
|
+
</span>
|
|
609
|
+
</button>
|
|
610
|
+
);
|
|
611
|
+
})}
|
|
612
|
+
</div>
|
|
613
|
+
|
|
614
|
+
<div className="dm-run-console__tree">
|
|
615
|
+
<p className="dm-run-console__history-title">Log tree</p>
|
|
616
|
+
{filteredTree.length === 0 && (
|
|
617
|
+
<p className="dm-run-console__hint">
|
|
618
|
+
{errorsOnly ? "No error entries in this run." : "No log entries yet."}
|
|
619
|
+
</p>
|
|
620
|
+
)}
|
|
621
|
+
{filteredTree.map((node) => (
|
|
622
|
+
<LogTreeNode
|
|
623
|
+
key={node.id}
|
|
624
|
+
node={node}
|
|
625
|
+
depth={0}
|
|
626
|
+
selectedId={selectedLogNode?.id}
|
|
627
|
+
onSelect={(n) => setSelectedLogId(n.id)}
|
|
628
|
+
timelineMax={timelineMax}
|
|
629
|
+
/>
|
|
630
|
+
))}
|
|
631
|
+
</div>
|
|
121
632
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<pre>{redactSecretsFromText(activeRecord?.error || rowTrace.error)}</pre>
|
|
633
|
+
{showQueueTime && timeline.length > 0 && (
|
|
634
|
+
<div className="dm-run-console__timeline">
|
|
635
|
+
<p className="dm-run-console__history-title">Timeline</p>
|
|
636
|
+
{timeline.map((item) => {
|
|
637
|
+
const variant = statusToVariant(item.status);
|
|
638
|
+
return (
|
|
639
|
+
<div key={item.runId || item.ranAt} className="dm-run-console__timeline-row" data-variant={variant}>
|
|
640
|
+
<span className="dm-run-console__timeline-label" title={item.runId}>{item.runId || "run"}</span>
|
|
641
|
+
<span className="dm-run-console__timeline-bar" aria-hidden="true">
|
|
642
|
+
<span style={{ width: `${Math.round((item.barRatio || 0) * 100)}%` }} data-variant={variant} />
|
|
643
|
+
</span>
|
|
644
|
+
<span className="dm-run-console__timeline-dur">{formatRunDuration(item.durationMs)}</span>
|
|
645
|
+
</div>
|
|
646
|
+
);
|
|
647
|
+
})}
|
|
138
648
|
</div>
|
|
139
649
|
)}
|
|
650
|
+
</aside>
|
|
140
651
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
652
|
+
<div className="dm-run-console__right">
|
|
653
|
+
<div className="dm-run-console__detail-head">
|
|
654
|
+
<div className="dm-run-console__detail-title">
|
|
655
|
+
<span className={`dm-run-console__status-pill is-${statusVariant}`}>{statusLabel}</span>
|
|
656
|
+
<strong>{selectedLogNode?.label || "agent-run"}</strong>
|
|
657
|
+
<small>{activeConsoleRecord?.runId || "—"}</small>
|
|
658
|
+
</div>
|
|
659
|
+
<div className="dm-run-console__detail-tabs" role="tablist">
|
|
660
|
+
{["overview", "detail", "context"].map((tab) => (
|
|
661
|
+
<button
|
|
662
|
+
key={tab}
|
|
663
|
+
type="button"
|
|
664
|
+
role="tab"
|
|
665
|
+
className={`dm-run-console__detail-tab${activeDetailTab === tab ? " is-active" : ""}`}
|
|
666
|
+
onClick={() => setActiveDetailTab(tab)}
|
|
667
|
+
>
|
|
668
|
+
{tab === "overview" ? "Overview" : tab === "detail" ? "Detail" : "Context"}
|
|
669
|
+
</button>
|
|
670
|
+
))}
|
|
671
|
+
</div>
|
|
144
672
|
</div>
|
|
145
673
|
|
|
146
|
-
{
|
|
147
|
-
<div className="dm-
|
|
148
|
-
<
|
|
149
|
-
|
|
674
|
+
{activeDetailTab === "overview" && (
|
|
675
|
+
<div className="dm-run-console__detail-body">
|
|
676
|
+
<section className="dm-run-console__section">
|
|
677
|
+
<h3>Lifecycle</h3>
|
|
678
|
+
<LifecycleBlock lifecycle={lifecycleEntries} />
|
|
679
|
+
</section>
|
|
680
|
+
<section className="dm-run-console__section">
|
|
681
|
+
<h3>Summary</h3>
|
|
682
|
+
<KeyValueBlock
|
|
683
|
+
entries={[
|
|
684
|
+
["Status", statusLabel],
|
|
685
|
+
["Run ID", activeConsoleRecord?.runId || "—"],
|
|
686
|
+
["Exit code", activeConsoleRecord?.exitCode == null ? "—" : String(activeConsoleRecord.exitCode)],
|
|
687
|
+
["Duration", formatRunDuration(activeConsoleRecord?.durationMs)],
|
|
688
|
+
["Runtime", activeConsoleRecord?.runtime || "—"],
|
|
689
|
+
["Adapter", activeConsoleRecord?.adapter || "—"],
|
|
690
|
+
["Run locality", activeConsoleRecord?.runLocality || "—"],
|
|
691
|
+
["Tested", formatTimestamp(activeConsoleRecord?.ranAt)],
|
|
692
|
+
["Finished", formatTimestamp(activeConsoleRecord?.finishedAt)]
|
|
693
|
+
]}
|
|
694
|
+
/>
|
|
695
|
+
</section>
|
|
696
|
+
<section className="dm-run-console__section">
|
|
697
|
+
<h3>Payload</h3>
|
|
698
|
+
<KeyValueBlock
|
|
699
|
+
entries={[
|
|
700
|
+
["Object", payload.objectId || "—"],
|
|
701
|
+
["Name", payload.name || row?.Name || "—"],
|
|
702
|
+
["Runtime", payload.runtime || "—"],
|
|
703
|
+
["Adapter", payload.adapter || "—"],
|
|
704
|
+
["Version", payload.version || "—"],
|
|
705
|
+
["Agent host", payload.agentHost || "—"],
|
|
706
|
+
["Scheduler", payload.schedulerRegistryId || "—"],
|
|
707
|
+
["Timeout", payload.timeoutMs ? `${payload.timeoutMs} ms` : "—"]
|
|
708
|
+
]}
|
|
709
|
+
/>
|
|
710
|
+
<CodeBlock label="Command" body={payload.command} />
|
|
711
|
+
<CodeBlock label="Instructions" body={payload.instructions} />
|
|
712
|
+
</section>
|
|
713
|
+
<InputsSection payload={payload} />
|
|
714
|
+
</div>
|
|
715
|
+
)}
|
|
716
|
+
|
|
717
|
+
{activeDetailTab === "detail" && (
|
|
718
|
+
<div className="dm-run-console__detail-body">
|
|
719
|
+
<section className="dm-run-console__section">
|
|
720
|
+
<h3>Export</h3>
|
|
721
|
+
<ExportActions
|
|
722
|
+
exports={activeConsoleRecord?.exports}
|
|
723
|
+
output={output}
|
|
724
|
+
record={activeConsoleRecord}
|
|
725
|
+
selectedLogNode={selectedLogNode}
|
|
726
|
+
/>
|
|
727
|
+
</section>
|
|
728
|
+
<section className="dm-run-console__section">
|
|
729
|
+
<h3>Output</h3>
|
|
730
|
+
<CodeBlock label="Error" body={output.error} />
|
|
731
|
+
<CodeBlock label="Stdout" body={output.stdout || "—"} />
|
|
732
|
+
{output.normalizedOutput && output.normalizedOutput !== output.stdout && (
|
|
733
|
+
<CodeBlock label="Normalized output" body={output.normalizedOutput} />
|
|
734
|
+
)}
|
|
735
|
+
<CodeBlock label="Stderr" body={output.stderr} />
|
|
736
|
+
</section>
|
|
737
|
+
{selectedLogNode?.text ? (
|
|
738
|
+
<section className="dm-run-console__section">
|
|
739
|
+
<h3>{selectedLogNode.label}</h3>
|
|
740
|
+
<CodeBlock label={selectedLogNode.type || "log"} body={selectedLogNode.text} />
|
|
741
|
+
</section>
|
|
742
|
+
) : null}
|
|
150
743
|
</div>
|
|
151
744
|
)}
|
|
152
745
|
|
|
153
|
-
{
|
|
154
|
-
<div className="dm-
|
|
155
|
-
<
|
|
156
|
-
|
|
746
|
+
{activeDetailTab === "context" && (
|
|
747
|
+
<div className="dm-run-console__detail-body">
|
|
748
|
+
<section className="dm-run-console__section">
|
|
749
|
+
<h3>Environment</h3>
|
|
750
|
+
<KeyValueBlock
|
|
751
|
+
entries={[
|
|
752
|
+
["Network allow", context.networkAllow ? "true" : "false"],
|
|
753
|
+
["Allow list", context.allowList?.join(", ") || "—"],
|
|
754
|
+
["Env refs resolved", context.envRefsResolved?.join(", ") || "—"],
|
|
755
|
+
["Env refs missing", context.envRefsMissing?.join(", ") || "—"],
|
|
756
|
+
["Source ID", activeConsoleRecord?.sourceId || row?.lastSourceId || "—"]
|
|
757
|
+
]}
|
|
758
|
+
/>
|
|
759
|
+
</section>
|
|
760
|
+
{context.adapterMeta && (
|
|
761
|
+
<section className="dm-run-console__section">
|
|
762
|
+
<h3>Adapter metadata</h3>
|
|
763
|
+
<CodeBlock
|
|
764
|
+
label="adapterMeta"
|
|
765
|
+
body={redactSecretsFromText(JSON.stringify(context.adapterMeta, null, 2))}
|
|
766
|
+
/>
|
|
767
|
+
</section>
|
|
768
|
+
)}
|
|
769
|
+
{context.templateTrace && (
|
|
770
|
+
<section className="dm-run-console__section">
|
|
771
|
+
<h3>Template trace</h3>
|
|
772
|
+
<CodeBlock
|
|
773
|
+
label="templateTrace"
|
|
774
|
+
body={redactSecretsFromText(JSON.stringify(context.templateTrace, null, 2))}
|
|
775
|
+
/>
|
|
776
|
+
</section>
|
|
777
|
+
)}
|
|
157
778
|
</div>
|
|
158
779
|
)}
|
|
159
780
|
</div>
|