@runloop/rl-cli 0.0.3 → 0.1.1
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 +64 -29
- package/dist/cli.js +401 -92
- package/dist/commands/auth.js +12 -11
- package/dist/commands/blueprint/create.js +108 -0
- package/dist/commands/blueprint/get.js +37 -0
- package/dist/commands/blueprint/list.js +293 -225
- package/dist/commands/blueprint/logs.js +40 -0
- package/dist/commands/blueprint/preview.js +45 -0
- package/dist/commands/devbox/create.js +10 -9
- package/dist/commands/devbox/delete.js +8 -8
- package/dist/commands/devbox/download.js +49 -0
- package/dist/commands/devbox/exec.js +23 -13
- package/dist/commands/devbox/execAsync.js +43 -0
- package/dist/commands/devbox/get.js +37 -0
- package/dist/commands/devbox/getAsync.js +37 -0
- package/dist/commands/devbox/list.js +328 -190
- package/dist/commands/devbox/logs.js +40 -0
- package/dist/commands/devbox/read.js +49 -0
- package/dist/commands/devbox/resume.js +37 -0
- package/dist/commands/devbox/rsync.js +118 -0
- package/dist/commands/devbox/scp.js +122 -0
- package/dist/commands/devbox/shutdown.js +37 -0
- package/dist/commands/devbox/ssh.js +104 -0
- package/dist/commands/devbox/suspend.js +37 -0
- package/dist/commands/devbox/tunnel.js +120 -0
- package/dist/commands/devbox/upload.js +10 -10
- package/dist/commands/devbox/write.js +51 -0
- package/dist/commands/mcp-http.js +37 -0
- package/dist/commands/mcp-install.js +120 -0
- package/dist/commands/mcp.js +30 -0
- package/dist/commands/menu.js +20 -20
- package/dist/commands/object/delete.js +37 -0
- package/dist/commands/object/download.js +88 -0
- package/dist/commands/object/get.js +37 -0
- package/dist/commands/object/list.js +112 -0
- package/dist/commands/object/upload.js +130 -0
- package/dist/commands/snapshot/create.js +12 -11
- package/dist/commands/snapshot/delete.js +8 -8
- package/dist/commands/snapshot/list.js +56 -97
- package/dist/commands/snapshot/status.js +37 -0
- package/dist/components/ActionsPopup.js +16 -13
- package/dist/components/Banner.js +4 -4
- package/dist/components/Breadcrumb.js +55 -5
- package/dist/components/DetailView.js +7 -4
- package/dist/components/DevboxActionsMenu.js +315 -178
- package/dist/components/DevboxCard.js +15 -14
- package/dist/components/DevboxCreatePage.js +147 -113
- package/dist/components/DevboxDetailPage.js +180 -102
- package/dist/components/ErrorMessage.js +5 -4
- package/dist/components/Header.js +4 -3
- package/dist/components/MainMenu.js +34 -33
- package/dist/components/MetadataDisplay.js +17 -9
- package/dist/components/OperationsMenu.js +6 -5
- package/dist/components/ResourceActionsMenu.js +117 -0
- package/dist/components/ResourceListView.js +213 -0
- package/dist/components/Spinner.js +5 -4
- package/dist/components/StatusBadge.js +81 -31
- package/dist/components/SuccessMessage.js +4 -3
- package/dist/components/Table.example.js +53 -23
- package/dist/components/Table.js +19 -11
- package/dist/hooks/useCursorPagination.js +125 -0
- package/dist/mcp/server-http.js +416 -0
- package/dist/mcp/server.js +397 -0
- package/dist/utils/CommandExecutor.js +16 -12
- package/dist/utils/client.js +7 -7
- package/dist/utils/config.js +130 -4
- package/dist/utils/interactiveCommand.js +2 -2
- package/dist/utils/output.js +17 -17
- package/dist/utils/ssh.js +160 -0
- package/dist/utils/sshSession.js +16 -12
- package/dist/utils/theme.js +22 -0
- package/dist/utils/url.js +4 -4
- package/package.json +29 -4
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import figures from
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
4
|
+
import { colors } from "../utils/theme.js";
|
|
4
5
|
export const DevboxCard = ({ id, name, status, createdAt, }) => {
|
|
5
6
|
const getStatusDisplay = (status) => {
|
|
6
7
|
switch (status) {
|
|
7
|
-
case
|
|
8
|
-
return { icon: figures.tick, color:
|
|
9
|
-
case
|
|
10
|
-
case
|
|
11
|
-
return { icon: figures.ellipsis, color:
|
|
12
|
-
case
|
|
13
|
-
case
|
|
14
|
-
return { icon: figures.circle, color:
|
|
15
|
-
case
|
|
16
|
-
return { icon: figures.cross, color:
|
|
8
|
+
case "running":
|
|
9
|
+
return { icon: figures.tick, color: colors.success };
|
|
10
|
+
case "provisioning":
|
|
11
|
+
case "initializing":
|
|
12
|
+
return { icon: figures.ellipsis, color: colors.warning };
|
|
13
|
+
case "stopped":
|
|
14
|
+
case "suspended":
|
|
15
|
+
return { icon: figures.circle, color: colors.textDim };
|
|
16
|
+
case "failed":
|
|
17
|
+
return { icon: figures.cross, color: colors.error };
|
|
17
18
|
default:
|
|
18
|
-
return { icon: figures.circle, color:
|
|
19
|
+
return { icon: figures.circle, color: colors.textDim };
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
const statusDisplay = getStatusDisplay(status);
|
|
22
23
|
const displayName = name || id;
|
|
23
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: statusDisplay.color, children: statusDisplay.icon }), _jsx(Text, { children: " " }), _jsx(Box, { width: 20, children: _jsx(Text, { color:
|
|
24
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: statusDisplay.color, children: statusDisplay.icon }), _jsx(Text, { children: " " }), _jsx(Box, { width: 20, children: _jsx(Text, { color: colors.primary, bold: true, children: displayName.slice(0, 18) }) }), _jsx(Text, { color: colors.textDim, dimColor: true, children: id }), createdAt && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: new Date(createdAt).toLocaleDateString() })] }))] }));
|
|
24
25
|
};
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from
|
|
3
|
-
import { Box, Text, useInput } from
|
|
4
|
-
import TextInput from
|
|
5
|
-
import figures from
|
|
6
|
-
import { getClient } from
|
|
7
|
-
import { SpinnerComponent } from
|
|
8
|
-
import { ErrorMessage } from
|
|
9
|
-
import { SuccessMessage } from
|
|
10
|
-
import { Breadcrumb } from
|
|
11
|
-
import { MetadataDisplay } from
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
import figures from "figures";
|
|
6
|
+
import { getClient } from "../utils/client.js";
|
|
7
|
+
import { SpinnerComponent } from "./Spinner.js";
|
|
8
|
+
import { ErrorMessage } from "./ErrorMessage.js";
|
|
9
|
+
import { SuccessMessage } from "./SuccessMessage.js";
|
|
10
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
11
|
+
import { MetadataDisplay } from "./MetadataDisplay.js";
|
|
12
|
+
import { colors } from "../utils/theme.js";
|
|
13
|
+
export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, }) => {
|
|
14
|
+
const [currentField, setCurrentField] = React.useState("create");
|
|
14
15
|
const [formData, setFormData] = React.useState({
|
|
15
|
-
name:
|
|
16
|
-
architecture:
|
|
17
|
-
resource_size:
|
|
18
|
-
custom_cpu:
|
|
19
|
-
custom_memory:
|
|
20
|
-
custom_disk:
|
|
21
|
-
keep_alive:
|
|
16
|
+
name: "",
|
|
17
|
+
architecture: "arm64",
|
|
18
|
+
resource_size: "SMALL",
|
|
19
|
+
custom_cpu: "",
|
|
20
|
+
custom_memory: "",
|
|
21
|
+
custom_disk: "",
|
|
22
|
+
keep_alive: "3600",
|
|
22
23
|
metadata: {},
|
|
23
|
-
blueprint_id:
|
|
24
|
-
snapshot_id:
|
|
24
|
+
blueprint_id: initialBlueprintId || "",
|
|
25
|
+
snapshot_id: "",
|
|
25
26
|
});
|
|
26
|
-
const [metadataKey, setMetadataKey] = React.useState(
|
|
27
|
-
const [metadataValue, setMetadataValue] = React.useState(
|
|
27
|
+
const [metadataKey, setMetadataKey] = React.useState("");
|
|
28
|
+
const [metadataValue, setMetadataValue] = React.useState("");
|
|
28
29
|
const [inMetadataSection, setInMetadataSection] = React.useState(false);
|
|
29
30
|
const [metadataInputMode, setMetadataInputMode] = React.useState(null);
|
|
30
31
|
const [selectedMetadataIndex, setSelectedMetadataIndex] = React.useState(-1); // -1 means "add new" row
|
|
@@ -32,33 +33,45 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
32
33
|
const [result, setResult] = React.useState(null);
|
|
33
34
|
const [error, setError] = React.useState(null);
|
|
34
35
|
const baseFields = [
|
|
35
|
-
{ key:
|
|
36
|
-
{ key:
|
|
37
|
-
{ key:
|
|
38
|
-
{ key:
|
|
36
|
+
{ key: "create", label: "Devbox Create", type: "action" },
|
|
37
|
+
{ key: "name", label: "Name", type: "text" },
|
|
38
|
+
{ key: "architecture", label: "Architecture", type: "select" },
|
|
39
|
+
{ key: "resource_size", label: "Resource Size", type: "select" },
|
|
39
40
|
];
|
|
40
41
|
// Add custom resource fields if CUSTOM_SIZE is selected
|
|
41
|
-
const customFields = formData.resource_size ===
|
|
42
|
+
const customFields = formData.resource_size === "CUSTOM_SIZE"
|
|
42
43
|
? [
|
|
43
|
-
{ key:
|
|
44
|
-
{
|
|
45
|
-
|
|
44
|
+
{ key: "custom_cpu", label: "CPU Cores (2-16, even)", type: "text" },
|
|
45
|
+
{
|
|
46
|
+
key: "custom_memory",
|
|
47
|
+
label: "Memory GB (2-64, even)",
|
|
48
|
+
type: "text",
|
|
49
|
+
},
|
|
50
|
+
{ key: "custom_disk", label: "Disk GB (2-64, even)", type: "text" },
|
|
46
51
|
]
|
|
47
52
|
: [];
|
|
48
53
|
const remainingFields = [
|
|
49
|
-
{ key:
|
|
50
|
-
{ key:
|
|
51
|
-
{ key:
|
|
52
|
-
{ key:
|
|
54
|
+
{ key: "keep_alive", label: "Keep Alive (seconds)", type: "text" },
|
|
55
|
+
{ key: "blueprint_id", label: "Blueprint ID (optional)", type: "text" },
|
|
56
|
+
{ key: "snapshot_id", label: "Snapshot ID (optional)", type: "text" },
|
|
57
|
+
{ key: "metadata", label: "Metadata (optional)", type: "metadata" },
|
|
53
58
|
];
|
|
54
59
|
const fields = [...baseFields, ...customFields, ...remainingFields];
|
|
55
|
-
const architectures = [
|
|
56
|
-
const resourceSizes = [
|
|
60
|
+
const architectures = ["arm64", "x86_64"];
|
|
61
|
+
const resourceSizes = [
|
|
62
|
+
"X_SMALL",
|
|
63
|
+
"SMALL",
|
|
64
|
+
"MEDIUM",
|
|
65
|
+
"LARGE",
|
|
66
|
+
"X_LARGE",
|
|
67
|
+
"XX_LARGE",
|
|
68
|
+
"CUSTOM_SIZE",
|
|
69
|
+
];
|
|
57
70
|
const currentFieldIndex = fields.findIndex((f) => f.key === currentField);
|
|
58
71
|
useInput((input, key) => {
|
|
59
72
|
// Handle result screen
|
|
60
73
|
if (result) {
|
|
61
|
-
if (input ===
|
|
74
|
+
if (input === "q" || key.escape || key.return) {
|
|
62
75
|
if (onCreate) {
|
|
63
76
|
onCreate(result);
|
|
64
77
|
}
|
|
@@ -68,11 +81,11 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
68
81
|
}
|
|
69
82
|
// Handle error screen
|
|
70
83
|
if (error) {
|
|
71
|
-
if (input ===
|
|
84
|
+
if (input === "r" || key.return) {
|
|
72
85
|
// Retry - clear error and return to form
|
|
73
86
|
setError(null);
|
|
74
87
|
}
|
|
75
|
-
else if (input ===
|
|
88
|
+
else if (input === "q" || key.escape) {
|
|
76
89
|
// Quit - go back to list
|
|
77
90
|
onBack();
|
|
78
91
|
}
|
|
@@ -83,18 +96,18 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
83
96
|
return;
|
|
84
97
|
}
|
|
85
98
|
// Back to list
|
|
86
|
-
if (input ===
|
|
99
|
+
if (input === "q" || key.escape) {
|
|
87
100
|
console.clear();
|
|
88
101
|
onBack();
|
|
89
102
|
return;
|
|
90
103
|
}
|
|
91
104
|
// Submit form
|
|
92
|
-
if (input ===
|
|
105
|
+
if (input === "s" && key.ctrl) {
|
|
93
106
|
handleCreate();
|
|
94
107
|
return;
|
|
95
108
|
}
|
|
96
109
|
// Handle Enter on create field
|
|
97
|
-
if (currentField ===
|
|
110
|
+
if (currentField === "create" && key.return) {
|
|
98
111
|
handleCreate();
|
|
99
112
|
return;
|
|
100
113
|
}
|
|
@@ -105,11 +118,11 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
105
118
|
const maxIndex = metadataKeys.length + 1;
|
|
106
119
|
// Handle input mode (typing key or value)
|
|
107
120
|
if (metadataInputMode) {
|
|
108
|
-
if (metadataInputMode ===
|
|
109
|
-
setMetadataInputMode(
|
|
121
|
+
if (metadataInputMode === "key" && key.return && metadataKey.trim()) {
|
|
122
|
+
setMetadataInputMode("value");
|
|
110
123
|
return;
|
|
111
124
|
}
|
|
112
|
-
else if (metadataInputMode ===
|
|
125
|
+
else if (metadataInputMode === "value" && key.return) {
|
|
113
126
|
if (metadataKey.trim() && metadataValue.trim()) {
|
|
114
127
|
setFormData({
|
|
115
128
|
...formData,
|
|
@@ -119,22 +132,22 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
119
132
|
},
|
|
120
133
|
});
|
|
121
134
|
}
|
|
122
|
-
setMetadataKey(
|
|
123
|
-
setMetadataValue(
|
|
135
|
+
setMetadataKey("");
|
|
136
|
+
setMetadataValue("");
|
|
124
137
|
setMetadataInputMode(null);
|
|
125
138
|
setSelectedMetadataIndex(0); // Back to "add new" row
|
|
126
139
|
return;
|
|
127
140
|
}
|
|
128
141
|
else if (key.escape) {
|
|
129
142
|
// Cancel input
|
|
130
|
-
setMetadataKey(
|
|
131
|
-
setMetadataValue(
|
|
143
|
+
setMetadataKey("");
|
|
144
|
+
setMetadataValue("");
|
|
132
145
|
setMetadataInputMode(null);
|
|
133
146
|
return;
|
|
134
147
|
}
|
|
135
148
|
else if (key.tab) {
|
|
136
149
|
// Tab between key and value
|
|
137
|
-
setMetadataInputMode(metadataInputMode ===
|
|
150
|
+
setMetadataInputMode(metadataInputMode === "key" ? "value" : "key");
|
|
138
151
|
return;
|
|
139
152
|
}
|
|
140
153
|
return; // Don't process other keys while in input mode
|
|
@@ -149,31 +162,34 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
149
162
|
else if (key.return) {
|
|
150
163
|
if (selectedMetadataIndex === 0) {
|
|
151
164
|
// Add new
|
|
152
|
-
setMetadataKey(
|
|
153
|
-
setMetadataValue(
|
|
154
|
-
setMetadataInputMode(
|
|
165
|
+
setMetadataKey("");
|
|
166
|
+
setMetadataValue("");
|
|
167
|
+
setMetadataInputMode("key");
|
|
155
168
|
}
|
|
156
169
|
else if (selectedMetadataIndex === maxIndex) {
|
|
157
170
|
// Done - exit metadata section
|
|
158
171
|
setInMetadataSection(false);
|
|
159
172
|
setSelectedMetadataIndex(0);
|
|
160
|
-
setMetadataKey(
|
|
161
|
-
setMetadataValue(
|
|
173
|
+
setMetadataKey("");
|
|
174
|
+
setMetadataValue("");
|
|
162
175
|
setMetadataInputMode(null);
|
|
163
176
|
}
|
|
164
|
-
else if (selectedMetadataIndex >= 1 &&
|
|
177
|
+
else if (selectedMetadataIndex >= 1 &&
|
|
178
|
+
selectedMetadataIndex <= metadataKeys.length) {
|
|
165
179
|
// Edit existing (selectedMetadataIndex - 1 gives array index)
|
|
166
180
|
const keyToEdit = metadataKeys[selectedMetadataIndex - 1];
|
|
167
|
-
setMetadataKey(keyToEdit ||
|
|
168
|
-
setMetadataValue(formData.metadata[keyToEdit] ||
|
|
181
|
+
setMetadataKey(keyToEdit || "");
|
|
182
|
+
setMetadataValue(formData.metadata[keyToEdit] || "");
|
|
169
183
|
// Remove old entry
|
|
170
184
|
const newMetadata = { ...formData.metadata };
|
|
171
185
|
delete newMetadata[keyToEdit];
|
|
172
186
|
setFormData({ ...formData, metadata: newMetadata });
|
|
173
|
-
setMetadataInputMode(
|
|
187
|
+
setMetadataInputMode("key");
|
|
174
188
|
}
|
|
175
189
|
}
|
|
176
|
-
else if ((input ===
|
|
190
|
+
else if ((input === "d" || key.delete) &&
|
|
191
|
+
selectedMetadataIndex >= 1 &&
|
|
192
|
+
selectedMetadataIndex <= metadataKeys.length) {
|
|
177
193
|
// Delete selected item (selectedMetadataIndex - 1 gives array index)
|
|
178
194
|
const keyToDelete = metadataKeys[selectedMetadataIndex - 1];
|
|
179
195
|
const newMetadata = { ...formData.metadata };
|
|
@@ -185,12 +201,12 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
185
201
|
setSelectedMetadataIndex(Math.max(0, newLength));
|
|
186
202
|
}
|
|
187
203
|
}
|
|
188
|
-
else if (key.escape || input ===
|
|
204
|
+
else if (key.escape || input === "q") {
|
|
189
205
|
// Exit metadata section
|
|
190
206
|
setInMetadataSection(false);
|
|
191
207
|
setSelectedMetadataIndex(0);
|
|
192
|
-
setMetadataKey(
|
|
193
|
-
setMetadataValue(
|
|
208
|
+
setMetadataKey("");
|
|
209
|
+
setMetadataValue("");
|
|
194
210
|
setMetadataInputMode(null);
|
|
195
211
|
}
|
|
196
212
|
return;
|
|
@@ -207,46 +223,55 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
207
223
|
return;
|
|
208
224
|
}
|
|
209
225
|
// Enter key on metadata field to enter metadata section
|
|
210
|
-
if (currentField ===
|
|
226
|
+
if (currentField === "metadata" && key.return) {
|
|
211
227
|
setInMetadataSection(true);
|
|
212
228
|
setSelectedMetadataIndex(0); // Start at "add new" row
|
|
213
229
|
return;
|
|
214
230
|
}
|
|
215
231
|
// Handle select fields
|
|
216
|
-
if (field && field.type ===
|
|
217
|
-
if (currentField ===
|
|
232
|
+
if (field && field.type === "select" && (key.leftArrow || key.rightArrow)) {
|
|
233
|
+
if (currentField === "architecture") {
|
|
218
234
|
const currentIndex = architectures.indexOf(formData.architecture);
|
|
219
235
|
const newIndex = key.leftArrow
|
|
220
236
|
? Math.max(0, currentIndex - 1)
|
|
221
237
|
: Math.min(architectures.length - 1, currentIndex + 1);
|
|
222
|
-
setFormData({
|
|
238
|
+
setFormData({
|
|
239
|
+
...formData,
|
|
240
|
+
architecture: architectures[newIndex],
|
|
241
|
+
});
|
|
223
242
|
}
|
|
224
|
-
else if (currentField ===
|
|
243
|
+
else if (currentField === "resource_size") {
|
|
225
244
|
const currentIndex = resourceSizes.indexOf(formData.resource_size);
|
|
226
245
|
const newIndex = key.leftArrow
|
|
227
246
|
? Math.max(0, currentIndex - 1)
|
|
228
247
|
: Math.min(resourceSizes.length - 1, currentIndex + 1);
|
|
229
|
-
setFormData({
|
|
248
|
+
setFormData({
|
|
249
|
+
...formData,
|
|
250
|
+
resource_size: resourceSizes[newIndex],
|
|
251
|
+
});
|
|
230
252
|
}
|
|
231
253
|
return;
|
|
232
254
|
}
|
|
233
255
|
});
|
|
234
256
|
// Validate custom resource configuration
|
|
235
257
|
const validateCustomResources = () => {
|
|
236
|
-
if (formData.resource_size !==
|
|
258
|
+
if (formData.resource_size !== "CUSTOM_SIZE") {
|
|
237
259
|
return null;
|
|
238
260
|
}
|
|
239
261
|
const cpu = parseInt(formData.custom_cpu);
|
|
240
262
|
const memory = parseInt(formData.custom_memory);
|
|
241
263
|
const disk = parseInt(formData.custom_disk);
|
|
242
|
-
if (formData.custom_cpu &&
|
|
243
|
-
|
|
264
|
+
if (formData.custom_cpu &&
|
|
265
|
+
(isNaN(cpu) || cpu < 2 || cpu > 16 || cpu % 2 !== 0)) {
|
|
266
|
+
return "CPU cores must be an even number between 2 and 16";
|
|
244
267
|
}
|
|
245
|
-
if (formData.custom_memory &&
|
|
246
|
-
|
|
268
|
+
if (formData.custom_memory &&
|
|
269
|
+
(isNaN(memory) || memory < 2 || memory > 64 || memory % 2 !== 0)) {
|
|
270
|
+
return "Memory must be an even number between 2 and 64 GB";
|
|
247
271
|
}
|
|
248
|
-
if (formData.custom_disk &&
|
|
249
|
-
|
|
272
|
+
if (formData.custom_disk &&
|
|
273
|
+
(isNaN(disk) || disk < 2 || disk > 64 || disk % 2 !== 0)) {
|
|
274
|
+
return "Disk must be an even number between 2 and 64 GB";
|
|
250
275
|
}
|
|
251
276
|
// Validate CPU to memory ratio (1:2 to 1:8)
|
|
252
277
|
if (formData.custom_cpu && formData.custom_memory) {
|
|
@@ -275,7 +300,7 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
275
300
|
if (formData.resource_size) {
|
|
276
301
|
launchParameters.resource_size_request = formData.resource_size;
|
|
277
302
|
}
|
|
278
|
-
if (formData.resource_size ===
|
|
303
|
+
if (formData.resource_size === "CUSTOM_SIZE") {
|
|
279
304
|
if (formData.custom_cpu)
|
|
280
305
|
launchParameters.custom_cpu_cores = parseInt(formData.custom_cpu);
|
|
281
306
|
if (formData.custom_memory)
|
|
@@ -314,65 +339,74 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
|
|
|
314
339
|
};
|
|
315
340
|
// Result screen
|
|
316
341
|
if (result) {
|
|
317
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
318
|
-
{ label: 'Devboxes' },
|
|
319
|
-
{ label: 'Create', active: true }
|
|
320
|
-
] }), _jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || '(none)'}\nStatus: ${result.status}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter], [q], or [esc] to return to list" }) })] }));
|
|
342
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || "(none)"}\nStatus: ${result.status}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter], [q], or [esc] to return to list" }) })] }));
|
|
321
343
|
}
|
|
322
344
|
// Error screen
|
|
323
345
|
if (error) {
|
|
324
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
325
|
-
{ label: 'Devboxes' },
|
|
326
|
-
{ label: 'Create', active: true }
|
|
327
|
-
] }), _jsx(ErrorMessage, { message: "Failed to create devbox", error: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter] or [r] to retry \u2022 [q] or [esc] to cancel" }) })] }));
|
|
346
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(ErrorMessage, { message: "Failed to create devbox", error: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] or [r] to retry \u2022 [q] or [esc] to cancel" }) })] }));
|
|
328
347
|
}
|
|
329
348
|
// Creating screen
|
|
330
349
|
if (creating) {
|
|
331
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
332
|
-
{ label: 'Devboxes' },
|
|
333
|
-
{ label: 'Create', active: true }
|
|
334
|
-
] }), _jsx(SpinnerComponent, { message: "Creating devbox..." })] }));
|
|
350
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(SpinnerComponent, { message: "Creating devbox..." })] }));
|
|
335
351
|
}
|
|
336
352
|
// Form screen
|
|
337
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
338
|
-
{ label: 'Devboxes' },
|
|
339
|
-
{ label: 'Create', active: true }
|
|
340
|
-
] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field, index) => {
|
|
353
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field, index) => {
|
|
341
354
|
const isActive = currentField === field.key;
|
|
342
355
|
const fieldData = formData[field.key];
|
|
343
|
-
if (field.type ===
|
|
344
|
-
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ?
|
|
356
|
+
if (field.type === "action") {
|
|
357
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.success : colors.textDim, bold: isActive, children: [isActive ? figures.pointer : " ", " ", field.label] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to create]"] }))] }, field.key));
|
|
345
358
|
}
|
|
346
|
-
if (field.type ===
|
|
347
|
-
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ?
|
|
359
|
+
if (field.type === "text") {
|
|
360
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), isActive ? (_jsx(TextInput, { value: String(fieldData || ""), onChange: (value) => {
|
|
348
361
|
setFormData({ ...formData, [field.key]: value });
|
|
349
|
-
}, placeholder: field.key ===
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
362
|
+
}, placeholder: field.key === "name"
|
|
363
|
+
? "my-devbox"
|
|
364
|
+
: field.key === "keep_alive"
|
|
365
|
+
? "3600"
|
|
366
|
+
: field.key === "blueprint_id"
|
|
367
|
+
? "bp_xxx"
|
|
368
|
+
: field.key === "snapshot_id"
|
|
369
|
+
? "snap_xxx"
|
|
370
|
+
: "" })) : (_jsx(Text, { color: colors.text, children: String(fieldData || "(empty)") }))] }, field.key));
|
|
354
371
|
}
|
|
355
|
-
if (field.type ===
|
|
372
|
+
if (field.type === "select") {
|
|
356
373
|
const value = fieldData;
|
|
357
|
-
return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ?
|
|
374
|
+
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.primary : colors.text, bold: isActive, children: [" ", value || "(none)"] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", figures.arrowLeft, figures.arrowRight, " to change]"] }))] }, field.key));
|
|
358
375
|
}
|
|
359
|
-
if (field.type ===
|
|
376
|
+
if (field.type === "metadata") {
|
|
360
377
|
if (!inMetadataSection) {
|
|
361
378
|
// Collapsed view
|
|
362
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ?
|
|
379
|
+
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: [Object.keys(formData.metadata).length, " item(s)"] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to manage]"] }))] }), Object.keys(formData.metadata).length > 0 && (_jsx(Box, { marginLeft: 2, children: _jsx(MetadataDisplay, { metadata: formData.metadata, showBorder: false }) }))] }, field.key));
|
|
363
380
|
}
|
|
364
381
|
// Expanded metadata section view
|
|
365
382
|
const metadataKeys = Object.keys(formData.metadata);
|
|
366
383
|
// Selection model: 0 = "Add new", 1..n = Existing items, n+1 = "Done"
|
|
367
384
|
const maxIndex = metadataKeys.length + 1;
|
|
368
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
385
|
+
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 Metadata"] }), metadataInputMode && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: selectedMetadataIndex === 0
|
|
386
|
+
? colors.success
|
|
387
|
+
: colors.warning, paddingX: 1, children: [_jsx(Text, { color: selectedMetadataIndex === 0
|
|
388
|
+
? colors.success
|
|
389
|
+
: colors.warning, bold: true, children: selectedMetadataIndex === 0 ? "Adding New" : "Editing" }), _jsx(Box, { children: metadataInputMode === "key" ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.primary, children: "Key: " }), _jsx(TextInput, { value: metadataKey || "", onChange: setMetadataKey, placeholder: "env" })] })) : (_jsxs(Text, { dimColor: true, children: ["Key: ", metadataKey || ""] })) }), _jsx(Box, { children: metadataInputMode === "value" ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.primary, children: "Value: " }), _jsx(TextInput, { value: metadataValue || "", onChange: setMetadataValue, placeholder: "production" })] })) : (_jsxs(Text, { dimColor: true, children: ["Value: ", metadataValue || ""] })) })] })), !metadataInputMode && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === 0
|
|
390
|
+
? colors.primary
|
|
391
|
+
: colors.textDim, children: [selectedMetadataIndex === 0
|
|
392
|
+
? figures.pointer
|
|
393
|
+
: " ", " "] }), _jsx(Text, { color: selectedMetadataIndex === 0
|
|
394
|
+
? colors.success
|
|
395
|
+
: colors.textDim, bold: selectedMetadataIndex === 0, children: "+ Add new metadata" })] }), metadataKeys.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: metadataKeys.map((key, index) => {
|
|
369
396
|
const itemIndex = index + 1; // Items are at indices 1..n
|
|
370
397
|
const isSelected = selectedMetadataIndex === itemIndex;
|
|
371
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ?
|
|
372
|
-
}) })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === maxIndex
|
|
373
|
-
|
|
374
|
-
|
|
398
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, bold: isSelected, children: [key, ": ", formData.metadata[key]] })] }, key));
|
|
399
|
+
}) })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === maxIndex
|
|
400
|
+
? colors.primary
|
|
401
|
+
: colors.textDim, children: [selectedMetadataIndex === maxIndex
|
|
402
|
+
? figures.pointer
|
|
403
|
+
: " ", " "] }), _jsxs(Text, { color: selectedMetadataIndex === maxIndex
|
|
404
|
+
? colors.success
|
|
405
|
+
: colors.textDim, bold: selectedMetadataIndex === maxIndex, children: [figures.tick, " Done"] })] })] })), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: colors.border, paddingX: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: metadataInputMode
|
|
406
|
+
? `[Tab] Switch field • [Enter] ${metadataInputMode === "key" ? "Next" : "Save"} • [esc] Cancel`
|
|
407
|
+
: `${figures.arrowUp}${figures.arrowDown} Navigate • [Enter] ${selectedMetadataIndex === 0 ? "Add" : selectedMetadataIndex === maxIndex ? "Done" : "Edit"} • [d] Delete • [esc] Back` }) })] }, field.key));
|
|
375
408
|
}
|
|
376
409
|
return null;
|
|
377
|
-
}) }), formData.resource_size ===
|
|
410
|
+
}) }), formData.resource_size === "CUSTOM_SIZE" &&
|
|
411
|
+
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(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Create \u2022 [q] Cancel"] }) }))] }));
|
|
378
412
|
};
|