@runloop/rl-cli 1.7.1 → 1.9.0
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/README.md +19 -5
- package/dist/cli.js +0 -0
- package/dist/commands/blueprint/delete.js +21 -0
- package/dist/commands/blueprint/list.js +226 -174
- package/dist/commands/blueprint/prune.js +13 -28
- package/dist/commands/devbox/create.js +41 -0
- package/dist/commands/devbox/list.js +125 -109
- package/dist/commands/devbox/tunnel.js +4 -19
- package/dist/commands/gateway-config/create.js +44 -0
- package/dist/commands/gateway-config/delete.js +21 -0
- package/dist/commands/gateway-config/get.js +15 -0
- package/dist/commands/gateway-config/list.js +493 -0
- package/dist/commands/gateway-config/update.js +60 -0
- package/dist/commands/menu.js +2 -1
- package/dist/commands/secret/list.js +379 -4
- package/dist/commands/snapshot/list.js +11 -2
- package/dist/commands/snapshot/prune.js +265 -0
- package/dist/components/BenchmarkMenu.js +108 -0
- package/dist/components/DetailedInfoView.js +20 -0
- package/dist/components/DevboxActionsMenu.js +9 -61
- package/dist/components/DevboxCreatePage.js +531 -14
- package/dist/components/DevboxDetailPage.js +27 -22
- package/dist/components/GatewayConfigCreatePage.js +265 -0
- package/dist/components/LogsViewer.js +6 -40
- package/dist/components/MainMenu.js +63 -22
- package/dist/components/ResourceDetailPage.js +143 -160
- package/dist/components/ResourceListView.js +3 -33
- package/dist/components/ResourcePicker.js +220 -0
- package/dist/components/SecretCreatePage.js +183 -0
- package/dist/components/SettingsMenu.js +95 -0
- package/dist/components/StateHistory.js +1 -20
- package/dist/components/StatusBadge.js +80 -0
- package/dist/components/StreamingLogsViewer.js +8 -42
- package/dist/components/form/FormTextInput.js +4 -2
- package/dist/components/resourceDetailTypes.js +18 -0
- package/dist/hooks/useInputHandler.js +103 -0
- package/dist/router/Router.js +99 -2
- package/dist/screens/BenchmarkDetailScreen.js +163 -0
- package/dist/screens/BenchmarkJobCreateScreen.js +524 -0
- package/dist/screens/BenchmarkJobDetailScreen.js +614 -0
- package/dist/screens/BenchmarkJobListScreen.js +479 -0
- package/dist/screens/BenchmarkListScreen.js +266 -0
- package/dist/screens/BenchmarkMenuScreen.js +29 -0
- package/dist/screens/BenchmarkRunDetailScreen.js +425 -0
- package/dist/screens/BenchmarkRunListScreen.js +275 -0
- package/dist/screens/BlueprintDetailScreen.js +5 -1
- package/dist/screens/DevboxCreateScreen.js +2 -2
- package/dist/screens/GatewayConfigDetailScreen.js +236 -0
- package/dist/screens/GatewayConfigListScreen.js +7 -0
- package/dist/screens/MenuScreen.js +5 -2
- package/dist/screens/ScenarioRunDetailScreen.js +226 -0
- package/dist/screens/ScenarioRunListScreen.js +245 -0
- package/dist/screens/SecretCreateScreen.js +7 -0
- package/dist/screens/SecretDetailScreen.js +198 -0
- package/dist/screens/SecretListScreen.js +7 -0
- package/dist/screens/SettingsMenuScreen.js +26 -0
- package/dist/screens/SnapshotDetailScreen.js +6 -0
- package/dist/services/agentService.js +42 -0
- package/dist/services/benchmarkJobService.js +122 -0
- package/dist/services/benchmarkService.js +120 -0
- package/dist/services/gatewayConfigService.js +114 -0
- package/dist/services/scenarioService.js +34 -0
- package/dist/store/benchmarkJobStore.js +66 -0
- package/dist/store/benchmarkStore.js +183 -0
- package/dist/store/betaFeatureStore.js +47 -0
- package/dist/store/gatewayConfigStore.js +83 -0
- package/dist/store/index.js +1 -0
- package/dist/utils/browser.js +22 -0
- package/dist/utils/clipboard.js +41 -0
- package/dist/utils/commands.js +80 -0
- package/dist/utils/config.js +8 -0
- package/dist/utils/time.js +121 -0
- package/package.json +42 -43
|
@@ -10,9 +10,17 @@ import { SuccessMessage } from "./SuccessMessage.js";
|
|
|
10
10
|
import { Breadcrumb } from "./Breadcrumb.js";
|
|
11
11
|
import { NavigationTips } from "./NavigationTips.js";
|
|
12
12
|
import { MetadataDisplay } from "./MetadataDisplay.js";
|
|
13
|
+
import { ResourcePicker, createTextColumn } from "./ResourcePicker.js";
|
|
14
|
+
import { formatTimeAgo } from "./ResourceListView.js";
|
|
15
|
+
import { getStatusDisplay } from "./StatusBadge.js";
|
|
13
16
|
import { FormTextInput, FormSelect, FormActionButton, useFormSelectNavigation, } from "./form/index.js";
|
|
14
17
|
import { colors } from "../utils/theme.js";
|
|
15
18
|
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
19
|
+
import { listBlueprints } from "../services/blueprintService.js";
|
|
20
|
+
import { listSnapshots } from "../services/snapshotService.js";
|
|
21
|
+
import { listNetworkPolicies } from "../services/networkPolicyService.js";
|
|
22
|
+
import { listGatewayConfigs } from "../services/gatewayConfigService.js";
|
|
23
|
+
const sourceTypes = ["blueprint", "snapshot"];
|
|
16
24
|
const architectures = ["arm64", "x86_64"];
|
|
17
25
|
const resourceSizes = [
|
|
18
26
|
"X_SMALL",
|
|
@@ -37,6 +45,7 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
37
45
|
blueprint_id: initialBlueprintId || "",
|
|
38
46
|
snapshot_id: initialSnapshotId || "",
|
|
39
47
|
network_policy_id: "",
|
|
48
|
+
gateways: [],
|
|
40
49
|
});
|
|
41
50
|
const [metadataKey, setMetadataKey] = React.useState("");
|
|
42
51
|
const [metadataValue, setMetadataValue] = React.useState("");
|
|
@@ -46,6 +55,22 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
46
55
|
const [creating, setCreating] = React.useState(false);
|
|
47
56
|
const [result, setResult] = React.useState(null);
|
|
48
57
|
const [error, setError] = React.useState(null);
|
|
58
|
+
// Source picker states (toggle between blueprint/snapshot)
|
|
59
|
+
const [sourceTypeToggle, setSourceTypeToggle] = React.useState(initialSnapshotId ? "snapshot" : "blueprint");
|
|
60
|
+
const [showBlueprintPicker, setShowBlueprintPicker] = React.useState(false);
|
|
61
|
+
const [showSnapshotPicker, setShowSnapshotPicker] = React.useState(false);
|
|
62
|
+
const [showNetworkPolicyPicker, setShowNetworkPolicyPicker] = React.useState(false);
|
|
63
|
+
const [selectedBlueprintName, setSelectedBlueprintName] = React.useState("");
|
|
64
|
+
const [selectedSnapshotName, setSelectedSnapshotName] = React.useState("");
|
|
65
|
+
const [selectedNetworkPolicyName, setSelectedNetworkPolicyName] = React.useState("");
|
|
66
|
+
// Gateway picker states
|
|
67
|
+
const [showGatewayPicker, setShowGatewayPicker] = React.useState(false);
|
|
68
|
+
const [showSecretPicker, setShowSecretPicker] = React.useState(false);
|
|
69
|
+
const [inGatewaySection, setInGatewaySection] = React.useState(false);
|
|
70
|
+
const [gatewayEnvPrefix, setGatewayEnvPrefix] = React.useState("");
|
|
71
|
+
const [gatewayInputMode, setGatewayInputMode] = React.useState(null);
|
|
72
|
+
const [selectedGatewayIndex, setSelectedGatewayIndex] = React.useState(0);
|
|
73
|
+
const [pendingGateway, setPendingGateway] = React.useState(null);
|
|
49
74
|
const baseFields = [
|
|
50
75
|
{ key: "create", label: "Devbox Create", type: "action" },
|
|
51
76
|
{ key: "name", label: "Name", type: "text", placeholder: "my-devbox" },
|
|
@@ -83,22 +108,22 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
83
108
|
placeholder: "3600",
|
|
84
109
|
},
|
|
85
110
|
{
|
|
86
|
-
key: "
|
|
87
|
-
label: "
|
|
88
|
-
type: "
|
|
89
|
-
placeholder: "
|
|
111
|
+
key: "source",
|
|
112
|
+
label: "Source (optional)",
|
|
113
|
+
type: "source",
|
|
114
|
+
placeholder: "Select Blueprint or Snapshot...",
|
|
90
115
|
},
|
|
91
116
|
{
|
|
92
|
-
key: "
|
|
93
|
-
label: "
|
|
94
|
-
type: "
|
|
95
|
-
placeholder: "
|
|
117
|
+
key: "network_policy_id",
|
|
118
|
+
label: "Network Policy (optional)",
|
|
119
|
+
type: "picker",
|
|
120
|
+
placeholder: "Select a network policy...",
|
|
96
121
|
},
|
|
97
122
|
{
|
|
98
|
-
key: "
|
|
99
|
-
label: "
|
|
100
|
-
type: "
|
|
101
|
-
placeholder: "
|
|
123
|
+
key: "gateways",
|
|
124
|
+
label: "Gateways (optional)",
|
|
125
|
+
type: "gateways",
|
|
126
|
+
placeholder: "Configure API credential proxying...",
|
|
102
127
|
},
|
|
103
128
|
{ key: "metadata", label: "Metadata (optional)", type: "metadata" },
|
|
104
129
|
];
|
|
@@ -109,6 +134,7 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
109
134
|
// Select navigation handlers using shared hook
|
|
110
135
|
const handleArchitectureNav = useFormSelectNavigation(formData.architecture, architectures, (value) => setFormData({ ...formData, architecture: value }), currentField === "architecture");
|
|
111
136
|
const handleResourceSizeNav = useFormSelectNavigation(formData.resource_size || "SMALL", resourceSizes, (value) => setFormData({ ...formData, resource_size: value }), currentField === "resource_size");
|
|
137
|
+
const handleSourceTypeNav = useFormSelectNavigation(sourceTypeToggle, sourceTypes, (value) => setSourceTypeToggle(value), currentField === "source");
|
|
112
138
|
// Main form input handler - active when not in metadata section
|
|
113
139
|
useInput((input, key) => {
|
|
114
140
|
// Handle result screen
|
|
@@ -155,6 +181,43 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
155
181
|
setSelectedMetadataIndex(0);
|
|
156
182
|
return;
|
|
157
183
|
}
|
|
184
|
+
// Enter key on gateways field to enter gateway section
|
|
185
|
+
if (currentField === "gateways" && key.return) {
|
|
186
|
+
setInGatewaySection(true);
|
|
187
|
+
setSelectedGatewayIndex(0);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Enter key on source field to open the appropriate picker
|
|
191
|
+
if (currentField === "source" && key.return) {
|
|
192
|
+
// If something is already selected, open that type's picker to change it
|
|
193
|
+
const hasBlueprint = !!(selectedBlueprintName || formData.blueprint_id);
|
|
194
|
+
const hasSnapshot = !!(selectedSnapshotName || formData.snapshot_id);
|
|
195
|
+
if (hasBlueprint) {
|
|
196
|
+
setShowBlueprintPicker(true);
|
|
197
|
+
}
|
|
198
|
+
else if (hasSnapshot) {
|
|
199
|
+
setShowSnapshotPicker(true);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Nothing selected, use the toggle value
|
|
203
|
+
if (sourceTypeToggle === "blueprint") {
|
|
204
|
+
setShowBlueprintPicker(true);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
setShowSnapshotPicker(true);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Delete key on source field to clear selection
|
|
213
|
+
if (currentField === "source" && (input === "d" || key.delete)) {
|
|
214
|
+
handleClearSource();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (currentField === "network_policy_id" && key.return) {
|
|
218
|
+
setShowNetworkPolicyPicker(true);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
158
221
|
// Handle Enter on any field to submit
|
|
159
222
|
if (key.return) {
|
|
160
223
|
handleCreate();
|
|
@@ -165,6 +228,8 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
165
228
|
return;
|
|
166
229
|
if (handleResourceSizeNav(input, key))
|
|
167
230
|
return;
|
|
231
|
+
if (handleSourceTypeNav(input, key))
|
|
232
|
+
return;
|
|
168
233
|
// Navigation (up/down arrows and tab/shift+tab)
|
|
169
234
|
if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) {
|
|
170
235
|
setCurrentField(fields[currentFieldIndex - 1].key);
|
|
@@ -175,7 +240,93 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
175
240
|
setCurrentField(fields[currentFieldIndex + 1].key);
|
|
176
241
|
return;
|
|
177
242
|
}
|
|
178
|
-
}, {
|
|
243
|
+
}, {
|
|
244
|
+
isActive: !inMetadataSection &&
|
|
245
|
+
!inGatewaySection &&
|
|
246
|
+
!showBlueprintPicker &&
|
|
247
|
+
!showSnapshotPicker &&
|
|
248
|
+
!showNetworkPolicyPicker &&
|
|
249
|
+
!showGatewayPicker &&
|
|
250
|
+
!showSecretPicker,
|
|
251
|
+
});
|
|
252
|
+
// Handle blueprint selection
|
|
253
|
+
const handleBlueprintSelect = React.useCallback((blueprints) => {
|
|
254
|
+
if (blueprints.length > 0) {
|
|
255
|
+
const blueprint = blueprints[0];
|
|
256
|
+
setFormData((prev) => ({
|
|
257
|
+
...prev,
|
|
258
|
+
blueprint_id: blueprint.id,
|
|
259
|
+
snapshot_id: "",
|
|
260
|
+
}));
|
|
261
|
+
setSelectedBlueprintName(blueprint.name || blueprint.id);
|
|
262
|
+
setSelectedSnapshotName("");
|
|
263
|
+
}
|
|
264
|
+
setShowBlueprintPicker(false);
|
|
265
|
+
}, []);
|
|
266
|
+
// Handle snapshot selection
|
|
267
|
+
const handleSnapshotSelect = React.useCallback((snapshots) => {
|
|
268
|
+
if (snapshots.length > 0) {
|
|
269
|
+
const snapshot = snapshots[0];
|
|
270
|
+
setFormData((prev) => ({
|
|
271
|
+
...prev,
|
|
272
|
+
snapshot_id: snapshot.id,
|
|
273
|
+
blueprint_id: "",
|
|
274
|
+
}));
|
|
275
|
+
setSelectedSnapshotName(snapshot.name || snapshot.id);
|
|
276
|
+
setSelectedBlueprintName("");
|
|
277
|
+
}
|
|
278
|
+
setShowSnapshotPicker(false);
|
|
279
|
+
}, []);
|
|
280
|
+
// Handle network policy selection
|
|
281
|
+
const handleNetworkPolicySelect = React.useCallback((policies) => {
|
|
282
|
+
if (policies.length > 0) {
|
|
283
|
+
const policy = policies[0];
|
|
284
|
+
setFormData((prev) => ({ ...prev, network_policy_id: policy.id }));
|
|
285
|
+
setSelectedNetworkPolicyName(policy.name || policy.id);
|
|
286
|
+
}
|
|
287
|
+
setShowNetworkPolicyPicker(false);
|
|
288
|
+
}, []);
|
|
289
|
+
// Handle gateway config selection
|
|
290
|
+
const handleGatewaySelect = React.useCallback((configs) => {
|
|
291
|
+
if (configs.length > 0) {
|
|
292
|
+
const config = configs[0];
|
|
293
|
+
setPendingGateway({ id: config.id, name: config.name || config.id });
|
|
294
|
+
setShowGatewayPicker(false);
|
|
295
|
+
// Now show secret picker
|
|
296
|
+
setShowSecretPicker(true);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
setShowGatewayPicker(false);
|
|
300
|
+
}
|
|
301
|
+
}, []);
|
|
302
|
+
// Handle secret selection for gateway
|
|
303
|
+
const handleSecretSelect = React.useCallback((secrets) => {
|
|
304
|
+
if (secrets.length > 0 && pendingGateway && gatewayEnvPrefix) {
|
|
305
|
+
const secret = secrets[0];
|
|
306
|
+
const newGateway = {
|
|
307
|
+
envPrefix: gatewayEnvPrefix,
|
|
308
|
+
gateway: pendingGateway.id,
|
|
309
|
+
gatewayName: pendingGateway.name,
|
|
310
|
+
secret: secret.id,
|
|
311
|
+
secretName: secret.name || secret.id,
|
|
312
|
+
};
|
|
313
|
+
setFormData((prev) => ({
|
|
314
|
+
...prev,
|
|
315
|
+
gateways: [...prev.gateways, newGateway],
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
setShowSecretPicker(false);
|
|
319
|
+
setPendingGateway(null);
|
|
320
|
+
setGatewayEnvPrefix("");
|
|
321
|
+
setGatewayInputMode(null);
|
|
322
|
+
setSelectedGatewayIndex(0);
|
|
323
|
+
}, [pendingGateway, gatewayEnvPrefix]);
|
|
324
|
+
// Handle clearing source
|
|
325
|
+
const handleClearSource = React.useCallback(() => {
|
|
326
|
+
setFormData((prev) => ({ ...prev, blueprint_id: "", snapshot_id: "" }));
|
|
327
|
+
setSelectedBlueprintName("");
|
|
328
|
+
setSelectedSnapshotName("");
|
|
329
|
+
}, []);
|
|
179
330
|
// Metadata section input handler - active when in metadata section
|
|
180
331
|
useInput((input, key) => {
|
|
181
332
|
const metadataKeys = Object.keys(formData.metadata);
|
|
@@ -265,6 +416,66 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
265
416
|
setMetadataInputMode(null);
|
|
266
417
|
}
|
|
267
418
|
}, { isActive: inMetadataSection });
|
|
419
|
+
// Gateway section input handler - active when in gateway section
|
|
420
|
+
useInput((input, key) => {
|
|
421
|
+
const gatewayCount = formData.gateways.length;
|
|
422
|
+
const maxIndex = gatewayCount + 1; // Add new + existing items + Done
|
|
423
|
+
// Handle input mode (typing env prefix)
|
|
424
|
+
if (gatewayInputMode === "envPrefix") {
|
|
425
|
+
if (key.return && gatewayEnvPrefix.trim()) {
|
|
426
|
+
// Open gateway picker
|
|
427
|
+
setGatewayInputMode(null);
|
|
428
|
+
setShowGatewayPicker(true);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
else if (key.escape) {
|
|
432
|
+
setGatewayEnvPrefix("");
|
|
433
|
+
setGatewayInputMode(null);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// Navigation mode in gateway section
|
|
439
|
+
if (key.upArrow && selectedGatewayIndex > 0) {
|
|
440
|
+
setSelectedGatewayIndex(selectedGatewayIndex - 1);
|
|
441
|
+
}
|
|
442
|
+
else if (key.downArrow && selectedGatewayIndex < maxIndex) {
|
|
443
|
+
setSelectedGatewayIndex(selectedGatewayIndex + 1);
|
|
444
|
+
}
|
|
445
|
+
else if (key.return) {
|
|
446
|
+
if (selectedGatewayIndex === 0) {
|
|
447
|
+
// Add new gateway - start with env prefix input
|
|
448
|
+
setGatewayEnvPrefix("");
|
|
449
|
+
setGatewayInputMode("envPrefix");
|
|
450
|
+
}
|
|
451
|
+
else if (selectedGatewayIndex === maxIndex) {
|
|
452
|
+
// Done
|
|
453
|
+
setInGatewaySection(false);
|
|
454
|
+
setSelectedGatewayIndex(0);
|
|
455
|
+
setGatewayEnvPrefix("");
|
|
456
|
+
setGatewayInputMode(null);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else if ((input === "d" || key.delete) &&
|
|
460
|
+
selectedGatewayIndex >= 1 &&
|
|
461
|
+
selectedGatewayIndex <= gatewayCount) {
|
|
462
|
+
// Delete gateway at index
|
|
463
|
+
const indexToDelete = selectedGatewayIndex - 1;
|
|
464
|
+
const newGateways = [...formData.gateways];
|
|
465
|
+
newGateways.splice(indexToDelete, 1);
|
|
466
|
+
setFormData({ ...formData, gateways: newGateways });
|
|
467
|
+
const newLength = newGateways.length;
|
|
468
|
+
if (selectedGatewayIndex > newLength) {
|
|
469
|
+
setSelectedGatewayIndex(Math.max(0, newLength));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (key.escape || input === "q") {
|
|
473
|
+
setInGatewaySection(false);
|
|
474
|
+
setSelectedGatewayIndex(0);
|
|
475
|
+
setGatewayEnvPrefix("");
|
|
476
|
+
setGatewayInputMode(null);
|
|
477
|
+
}
|
|
478
|
+
}, { isActive: inGatewaySection && !showGatewayPicker && !showSecretPicker });
|
|
268
479
|
// Validate custom resource configuration
|
|
269
480
|
const validateCustomResources = () => {
|
|
270
481
|
if (formData.resource_size !== "CUSTOM_SIZE") {
|
|
@@ -342,6 +553,17 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
342
553
|
if (Object.keys(launchParameters).length > 0) {
|
|
343
554
|
createParams.launch_parameters = launchParameters;
|
|
344
555
|
}
|
|
556
|
+
// Add gateway specifications
|
|
557
|
+
if (formData.gateways.length > 0) {
|
|
558
|
+
const gateways = {};
|
|
559
|
+
for (const gw of formData.gateways) {
|
|
560
|
+
gateways[gw.envPrefix] = {
|
|
561
|
+
gateway: gw.gateway,
|
|
562
|
+
secret: gw.secret,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
createParams.gateways = gateways;
|
|
566
|
+
}
|
|
345
567
|
const devbox = await client.devboxes.create(createParams);
|
|
346
568
|
setResult(devbox);
|
|
347
569
|
}
|
|
@@ -367,6 +589,240 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
367
589
|
if (creating) {
|
|
368
590
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(SpinnerComponent, { message: "Creating devbox..." })] }));
|
|
369
591
|
}
|
|
592
|
+
// Blueprint picker screen
|
|
593
|
+
if (showBlueprintPicker) {
|
|
594
|
+
const blueprintColumns = [
|
|
595
|
+
{
|
|
596
|
+
key: "statusIcon",
|
|
597
|
+
label: "",
|
|
598
|
+
width: 2,
|
|
599
|
+
render: (blueprint, _index, isSelected) => {
|
|
600
|
+
const statusDisplay = getStatusDisplay(blueprint.status || "");
|
|
601
|
+
return (_jsxs(Text, { color: isSelected ? "white" : statusDisplay.color, bold: true, inverse: isSelected, wrap: "truncate", children: [statusDisplay.icon, " "] }));
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
createTextColumn("id", "ID", (blueprint) => blueprint.id, {
|
|
605
|
+
width: 25,
|
|
606
|
+
color: colors.idColor,
|
|
607
|
+
}),
|
|
608
|
+
createTextColumn("name", "Name", (blueprint) => blueprint.name || "", { width: 30 }),
|
|
609
|
+
{
|
|
610
|
+
key: "status",
|
|
611
|
+
label: "Status",
|
|
612
|
+
width: 12,
|
|
613
|
+
render: (blueprint, _index, isSelected) => {
|
|
614
|
+
const statusDisplay = getStatusDisplay(blueprint.status || "");
|
|
615
|
+
const padded = statusDisplay.text.slice(0, 12).padEnd(12, " ");
|
|
616
|
+
return (_jsx(Text, { color: isSelected ? "white" : statusDisplay.color, bold: true, inverse: isSelected, wrap: "truncate", children: padded }));
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
createTextColumn("created", "Created", (blueprint) => blueprint.create_time_ms
|
|
620
|
+
? formatTimeAgo(blueprint.create_time_ms)
|
|
621
|
+
: "", { width: 18, color: colors.textDim }),
|
|
622
|
+
];
|
|
623
|
+
// Filter out failed blueprints
|
|
624
|
+
const failedStatuses = ["failure", "build_failed", "failed"];
|
|
625
|
+
return (_jsx(ResourcePicker, { config: {
|
|
626
|
+
title: "Select Blueprint",
|
|
627
|
+
fetchPage: async (params) => {
|
|
628
|
+
const result = await listBlueprints({
|
|
629
|
+
limit: params.limit,
|
|
630
|
+
startingAfter: params.startingAt,
|
|
631
|
+
search: params.search,
|
|
632
|
+
});
|
|
633
|
+
// Filter out failed blueprints
|
|
634
|
+
const validBlueprints = result.blueprints.filter((bp) => !failedStatuses.includes(bp.status || ""));
|
|
635
|
+
return {
|
|
636
|
+
items: validBlueprints,
|
|
637
|
+
hasMore: result.hasMore,
|
|
638
|
+
totalCount: validBlueprints.length,
|
|
639
|
+
};
|
|
640
|
+
},
|
|
641
|
+
getItemId: (blueprint) => blueprint.id,
|
|
642
|
+
getItemLabel: (blueprint) => blueprint.name || blueprint.id,
|
|
643
|
+
columns: blueprintColumns,
|
|
644
|
+
mode: "single",
|
|
645
|
+
emptyMessage: "No blueprints found (failed blueprints are hidden)",
|
|
646
|
+
searchPlaceholder: "Search blueprints...",
|
|
647
|
+
breadcrumbItems: [
|
|
648
|
+
{ label: "Devboxes" },
|
|
649
|
+
{ label: "Create" },
|
|
650
|
+
{ label: "Select Blueprint", active: true },
|
|
651
|
+
],
|
|
652
|
+
}, onSelect: handleBlueprintSelect, onCancel: () => setShowBlueprintPicker(false), initialSelected: formData.blueprint_id ? [formData.blueprint_id] : [] }));
|
|
653
|
+
}
|
|
654
|
+
// Snapshot picker screen
|
|
655
|
+
if (showSnapshotPicker) {
|
|
656
|
+
const snapshotColumns = [
|
|
657
|
+
createTextColumn("id", "ID", (snapshot) => snapshot.id, {
|
|
658
|
+
width: 25,
|
|
659
|
+
color: colors.idColor,
|
|
660
|
+
}),
|
|
661
|
+
createTextColumn("name", "Name", (snapshot) => snapshot.name || "", { width: 30 }),
|
|
662
|
+
createTextColumn("status", "Status", (snapshot) => snapshot.status || "", { width: 12 }),
|
|
663
|
+
createTextColumn("created", "Created", (snapshot) => snapshot.create_time_ms ? formatTimeAgo(snapshot.create_time_ms) : "", { width: 18, color: colors.textDim }),
|
|
664
|
+
];
|
|
665
|
+
return (_jsx(ResourcePicker, { config: {
|
|
666
|
+
title: "Select Snapshot",
|
|
667
|
+
fetchPage: async (params) => {
|
|
668
|
+
const result = await listSnapshots({
|
|
669
|
+
limit: params.limit,
|
|
670
|
+
startingAfter: params.startingAt,
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
items: result.snapshots,
|
|
674
|
+
hasMore: result.hasMore,
|
|
675
|
+
totalCount: result.totalCount,
|
|
676
|
+
};
|
|
677
|
+
},
|
|
678
|
+
getItemId: (snapshot) => snapshot.id,
|
|
679
|
+
getItemLabel: (snapshot) => snapshot.name || snapshot.id,
|
|
680
|
+
columns: snapshotColumns,
|
|
681
|
+
mode: "single",
|
|
682
|
+
emptyMessage: "No snapshots found",
|
|
683
|
+
searchPlaceholder: "Search snapshots...",
|
|
684
|
+
breadcrumbItems: [
|
|
685
|
+
{ label: "Devboxes" },
|
|
686
|
+
{ label: "Create" },
|
|
687
|
+
{ label: "Select Snapshot", active: true },
|
|
688
|
+
],
|
|
689
|
+
}, onSelect: handleSnapshotSelect, onCancel: () => setShowSnapshotPicker(false), initialSelected: formData.snapshot_id ? [formData.snapshot_id] : [] }));
|
|
690
|
+
}
|
|
691
|
+
// Network policy picker screen
|
|
692
|
+
if (showNetworkPolicyPicker) {
|
|
693
|
+
// Helper to get egress type label
|
|
694
|
+
const getEgressLabel = (egress) => {
|
|
695
|
+
if (egress.allow_all)
|
|
696
|
+
return "Allow All";
|
|
697
|
+
if (egress.allowed_hostnames?.length === 0)
|
|
698
|
+
return "Deny All";
|
|
699
|
+
return `Custom (${egress.allowed_hostnames?.length || 0})`;
|
|
700
|
+
};
|
|
701
|
+
const networkPolicyColumns = [
|
|
702
|
+
createTextColumn("id", "ID", (policy) => policy.id, {
|
|
703
|
+
width: 25,
|
|
704
|
+
color: colors.idColor,
|
|
705
|
+
}),
|
|
706
|
+
createTextColumn("name", "Name", (policy) => policy.name || "", { width: 25 }),
|
|
707
|
+
createTextColumn("egress", "Egress", (policy) => getEgressLabel(policy.egress), { width: 15 }),
|
|
708
|
+
createTextColumn("created", "Created", (policy) => policy.create_time_ms ? formatTimeAgo(policy.create_time_ms) : "", { width: 18, color: colors.textDim }),
|
|
709
|
+
];
|
|
710
|
+
return (_jsx(ResourcePicker, { config: {
|
|
711
|
+
title: "Select Network Policy",
|
|
712
|
+
fetchPage: async (params) => {
|
|
713
|
+
const result = await listNetworkPolicies({
|
|
714
|
+
limit: params.limit,
|
|
715
|
+
startingAfter: params.startingAt,
|
|
716
|
+
search: params.search,
|
|
717
|
+
});
|
|
718
|
+
return {
|
|
719
|
+
items: result.networkPolicies,
|
|
720
|
+
hasMore: result.hasMore,
|
|
721
|
+
totalCount: result.totalCount,
|
|
722
|
+
};
|
|
723
|
+
},
|
|
724
|
+
getItemId: (policy) => policy.id,
|
|
725
|
+
getItemLabel: (policy) => policy.name || policy.id,
|
|
726
|
+
columns: networkPolicyColumns,
|
|
727
|
+
mode: "single",
|
|
728
|
+
emptyMessage: "No network policies found",
|
|
729
|
+
searchPlaceholder: "Search network policies...",
|
|
730
|
+
breadcrumbItems: [
|
|
731
|
+
{ label: "Devboxes" },
|
|
732
|
+
{ label: "Create" },
|
|
733
|
+
{ label: "Select Network Policy", active: true },
|
|
734
|
+
],
|
|
735
|
+
}, onSelect: handleNetworkPolicySelect, onCancel: () => setShowNetworkPolicyPicker(false), initialSelected: formData.network_policy_id ? [formData.network_policy_id] : [] }));
|
|
736
|
+
}
|
|
737
|
+
// Gateway config picker screen
|
|
738
|
+
if (showGatewayPicker) {
|
|
739
|
+
const gatewayColumns = [
|
|
740
|
+
createTextColumn("id", "ID", (config) => config.id, {
|
|
741
|
+
width: 25,
|
|
742
|
+
color: colors.idColor,
|
|
743
|
+
}),
|
|
744
|
+
createTextColumn("name", "Name", (config) => config.name || "", { width: 25 }),
|
|
745
|
+
createTextColumn("endpoint", "Endpoint", (config) => config.endpoint || "", { width: 30, color: colors.textDim }),
|
|
746
|
+
createTextColumn("created", "Created", (config) => config.create_time_ms ? formatTimeAgo(config.create_time_ms) : "", { width: 18, color: colors.textDim }),
|
|
747
|
+
];
|
|
748
|
+
return (_jsx(ResourcePicker, { config: {
|
|
749
|
+
title: "Select Gateway Config",
|
|
750
|
+
fetchPage: async (params) => {
|
|
751
|
+
const result = await listGatewayConfigs({
|
|
752
|
+
limit: params.limit,
|
|
753
|
+
startingAfter: params.startingAt,
|
|
754
|
+
search: params.search,
|
|
755
|
+
});
|
|
756
|
+
return {
|
|
757
|
+
items: result.gatewayConfigs,
|
|
758
|
+
hasMore: result.hasMore,
|
|
759
|
+
totalCount: result.totalCount,
|
|
760
|
+
};
|
|
761
|
+
},
|
|
762
|
+
getItemId: (config) => config.id,
|
|
763
|
+
getItemLabel: (config) => config.name || config.id,
|
|
764
|
+
columns: gatewayColumns,
|
|
765
|
+
mode: "single",
|
|
766
|
+
emptyMessage: "No gateway configs found",
|
|
767
|
+
searchPlaceholder: "Search gateway configs...",
|
|
768
|
+
breadcrumbItems: [
|
|
769
|
+
{ label: "Devboxes" },
|
|
770
|
+
{ label: "Create" },
|
|
771
|
+
{ label: `Gateway: ${gatewayEnvPrefix}`, active: true },
|
|
772
|
+
],
|
|
773
|
+
}, onSelect: handleGatewaySelect, onCancel: () => {
|
|
774
|
+
setShowGatewayPicker(false);
|
|
775
|
+
setGatewayEnvPrefix("");
|
|
776
|
+
setGatewayInputMode(null);
|
|
777
|
+
}, initialSelected: [] }, "gateway-config-picker"));
|
|
778
|
+
}
|
|
779
|
+
// Secret picker screen (for gateway)
|
|
780
|
+
if (showSecretPicker) {
|
|
781
|
+
const secretColumns = [
|
|
782
|
+
createTextColumn("id", "ID", (secret) => secret.id, {
|
|
783
|
+
width: 25,
|
|
784
|
+
color: colors.idColor,
|
|
785
|
+
}),
|
|
786
|
+
createTextColumn("name", "Name", (secret) => secret.name || "", { width: 30 }),
|
|
787
|
+
createTextColumn("created", "Created", (secret) => secret.create_time_ms ? formatTimeAgo(secret.create_time_ms) : "", { width: 18, color: colors.textDim }),
|
|
788
|
+
];
|
|
789
|
+
return (_jsx(ResourcePicker, { config: {
|
|
790
|
+
title: "Select Secret for Gateway",
|
|
791
|
+
fetchPage: async (params) => {
|
|
792
|
+
const client = getClient();
|
|
793
|
+
// Secrets API doesn't support cursor pagination, just limit
|
|
794
|
+
const page = await client.secrets.list({
|
|
795
|
+
limit: params.limit,
|
|
796
|
+
});
|
|
797
|
+
return {
|
|
798
|
+
items: (page.secrets || []).map((s) => ({
|
|
799
|
+
id: s.id,
|
|
800
|
+
name: s.name,
|
|
801
|
+
create_time_ms: s.create_time_ms,
|
|
802
|
+
})),
|
|
803
|
+
hasMore: false, // Secrets API doesn't support pagination
|
|
804
|
+
totalCount: page.total_count || 0,
|
|
805
|
+
};
|
|
806
|
+
},
|
|
807
|
+
getItemId: (secret) => secret.id,
|
|
808
|
+
getItemLabel: (secret) => secret.name || secret.id,
|
|
809
|
+
columns: secretColumns,
|
|
810
|
+
mode: "single",
|
|
811
|
+
emptyMessage: "No secrets found",
|
|
812
|
+
searchPlaceholder: "Search secrets...",
|
|
813
|
+
breadcrumbItems: [
|
|
814
|
+
{ label: "Devboxes" },
|
|
815
|
+
{ label: "Create" },
|
|
816
|
+
{ label: `Gateway: ${gatewayEnvPrefix}` },
|
|
817
|
+
{ label: "Select Secret", active: true },
|
|
818
|
+
],
|
|
819
|
+
}, onSelect: handleSecretSelect, onCancel: () => {
|
|
820
|
+
setShowSecretPicker(false);
|
|
821
|
+
setPendingGateway(null);
|
|
822
|
+
setGatewayEnvPrefix("");
|
|
823
|
+
setGatewayInputMode(null);
|
|
824
|
+
}, initialSelected: [] }, "secret-picker"));
|
|
825
|
+
}
|
|
370
826
|
// Form screen
|
|
371
827
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field) => {
|
|
372
828
|
const isActive = currentField === field.key;
|
|
@@ -381,6 +837,35 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
381
837
|
const value = fieldData;
|
|
382
838
|
return (_jsx(FormSelect, { label: field.label, value: value || "", options: field.key === "architecture" ? architectures : resourceSizes, onChange: (newValue) => setFormData({ ...formData, [field.key]: newValue }), isActive: isActive }, field.key));
|
|
383
839
|
}
|
|
840
|
+
if (field.type === "source") {
|
|
841
|
+
// Check if either blueprint or snapshot is selected
|
|
842
|
+
const selectedBlueprintValue = selectedBlueprintName || formData.blueprint_id;
|
|
843
|
+
const selectedSnapshotValue = selectedSnapshotName || formData.snapshot_id;
|
|
844
|
+
const hasBlueprint = !!selectedBlueprintValue;
|
|
845
|
+
const hasSnapshot = !!selectedSnapshotValue;
|
|
846
|
+
const hasSelection = hasBlueprint || hasSnapshot;
|
|
847
|
+
// If something is selected, show it clearly with its type
|
|
848
|
+
if (hasSelection) {
|
|
849
|
+
const selectedType = hasBlueprint ? "Blueprint" : "Snapshot";
|
|
850
|
+
const selectedValue = hasBlueprint
|
|
851
|
+
? selectedBlueprintValue
|
|
852
|
+
: selectedSnapshotValue;
|
|
853
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), _jsxs(Text, { color: colors.success, children: [selectedType, ": "] }), _jsx(Text, { color: colors.idColor, children: selectedValue }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to change, d to clear]"] }))] }, field.key));
|
|
854
|
+
}
|
|
855
|
+
// Nothing selected - show toggle to choose type
|
|
856
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), _jsxs(Text, { color: isActive ? colors.text : colors.textDim, children: [isActive ? figures.arrowLeft : "", " "] }), _jsx(Text, { color: sourceTypeToggle === "blueprint"
|
|
857
|
+
? colors.primary
|
|
858
|
+
: colors.textDim, bold: sourceTypeToggle === "blueprint", children: "Blueprint" }), _jsx(Text, { color: colors.textDim, children: " / " }), _jsx(Text, { color: sourceTypeToggle === "snapshot"
|
|
859
|
+
? colors.primary
|
|
860
|
+
: colors.textDim, bold: sourceTypeToggle === "snapshot", children: "Snapshot" }), _jsxs(Text, { color: isActive ? colors.text : colors.textDim, children: [" ", isActive ? figures.arrowRight : ""] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to select]"] }))] }, field.key));
|
|
861
|
+
}
|
|
862
|
+
if (field.type === "picker") {
|
|
863
|
+
const value = fieldData;
|
|
864
|
+
const displayName = field.key === "network_policy_id"
|
|
865
|
+
? selectedNetworkPolicyName || value
|
|
866
|
+
: value;
|
|
867
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), displayName ? (_jsx(Text, { color: colors.idColor, children: displayName })) : (_jsx(Text, { color: colors.textDim, dimColor: true, children: field.placeholder || "(none)" })), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to ", displayName ? "change" : "select", "]"] }))] }, field.key));
|
|
868
|
+
}
|
|
384
869
|
if (field.type === "metadata") {
|
|
385
870
|
if (!inMetadataSection) {
|
|
386
871
|
// Collapsed view
|
|
@@ -414,9 +899,41 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initial
|
|
|
414
899
|
? `[Tab] Switch field • [Enter] ${metadataInputMode === "key" ? "Next" : "Save"} • [esc] Cancel`
|
|
415
900
|
: `${figures.arrowUp}${figures.arrowDown} Navigate • [Enter] ${selectedMetadataIndex === 0 ? "Add" : selectedMetadataIndex === maxIndex ? "Done" : "Edit"} • [d] Delete • [esc] Back` }) })] }, field.key));
|
|
416
901
|
}
|
|
902
|
+
if (field.type === "gateways") {
|
|
903
|
+
if (!inGatewaySection) {
|
|
904
|
+
// Collapsed view
|
|
905
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), _jsxs(Text, { color: colors.text, children: [formData.gateways.length, " gateway(s)"] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to manage]"] }))] }), formData.gateways.length > 0 && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: formData.gateways.map((gw, idx) => (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.pointer, " ", gw.envPrefix, ": ", gw.gatewayName, " \u2192", " ", gw.secretName] }, idx))) }))] }, field.key));
|
|
906
|
+
}
|
|
907
|
+
// Expanded gateway section view
|
|
908
|
+
const gatewayCount = formData.gateways.length;
|
|
909
|
+
const maxGatewayIndex = gatewayCount + 1;
|
|
910
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.primary, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " Manage Gateway Configurations"] }), gatewayInputMode === "envPrefix" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: colors.success, paddingX: 1, children: [_jsx(Text, { color: colors.success, bold: true, children: "Adding New Gateway" }), _jsxs(Box, { children: [_jsxs(Text, { color: colors.primary, children: ["Env Prefix (e.g., GWS_ANTHROPIC):", " "] }), _jsx(TextInput, { value: gatewayEnvPrefix || "", onChange: setGatewayEnvPrefix, placeholder: "GWS_ANTHROPIC" })] }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press Enter to select gateway config" })] })), !gatewayInputMode && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedGatewayIndex === 0
|
|
911
|
+
? colors.primary
|
|
912
|
+
: colors.textDim, children: [selectedGatewayIndex === 0
|
|
913
|
+
? figures.pointer
|
|
914
|
+
: " ", " "] }), _jsx(Text, { color: selectedGatewayIndex === 0
|
|
915
|
+
? colors.success
|
|
916
|
+
: colors.textDim, bold: selectedGatewayIndex === 0, children: "+ Add new gateway" })] }), gatewayCount > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: formData.gateways.map((gw, index) => {
|
|
917
|
+
const itemIndex = index + 1;
|
|
918
|
+
const isGatewaySelected = selectedGatewayIndex === itemIndex;
|
|
919
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isGatewaySelected
|
|
920
|
+
? colors.primary
|
|
921
|
+
: colors.textDim, children: [isGatewaySelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isGatewaySelected
|
|
922
|
+
? colors.primary
|
|
923
|
+
: colors.textDim, bold: isGatewaySelected, children: [gw.envPrefix, ": ", gw.gatewayName, " \u2192", " ", gw.secretName] })] }, gw.envPrefix));
|
|
924
|
+
}) })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedGatewayIndex === maxGatewayIndex
|
|
925
|
+
? colors.primary
|
|
926
|
+
: colors.textDim, children: [selectedGatewayIndex === maxGatewayIndex
|
|
927
|
+
? figures.pointer
|
|
928
|
+
: " ", " "] }), _jsxs(Text, { color: selectedGatewayIndex === maxGatewayIndex
|
|
929
|
+
? colors.success
|
|
930
|
+
: colors.textDim, bold: selectedGatewayIndex === maxGatewayIndex, children: [figures.tick, " Done"] })] })] })), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: colors.border, paddingX: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: gatewayInputMode
|
|
931
|
+
? `[Enter] Select gateway • [esc] Cancel`
|
|
932
|
+
: `${figures.arrowUp}${figures.arrowDown} Navigate • [Enter] ${selectedGatewayIndex === 0 ? "Add" : selectedGatewayIndex === maxGatewayIndex ? "Done" : "Select"} • [d] Delete • [esc] Back` }) })] }, field.key));
|
|
933
|
+
}
|
|
417
934
|
return null;
|
|
418
935
|
}) }), formData.resource_size === "CUSTOM_SIZE" &&
|
|
419
|
-
validateCustomResources() && (_jsxs(Box, { borderStyle: "round", borderColor: colors.error, paddingX: 1, paddingY: 0, marginTop: 1, children: [_jsxs(Text, { color: colors.error, bold: true, children: [figures.cross, " Validation Error"] }), _jsx(Text, { color: colors.error, dimColor: true, children: validateCustomResources() })] })), !inMetadataSection && (_jsx(NavigationTips, { showArrows: true, tips: [
|
|
936
|
+
validateCustomResources() && (_jsxs(Box, { borderStyle: "round", borderColor: colors.error, paddingX: 1, paddingY: 0, marginTop: 1, children: [_jsxs(Text, { color: colors.error, bold: true, children: [figures.cross, " Validation Error"] }), _jsx(Text, { color: colors.error, dimColor: true, children: validateCustomResources() })] })), !inMetadataSection && !inGatewaySection && (_jsx(NavigationTips, { showArrows: true, tips: [
|
|
420
937
|
{ key: "Enter", label: "Create" },
|
|
421
938
|
{ key: "q", label: "Cancel" },
|
|
422
939
|
] }))] }));
|