@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, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from
|
|
3
|
-
import { Box, Text, useInput, useApp, useStdout } from
|
|
4
|
-
import TextInput from
|
|
5
|
-
import figures from
|
|
6
|
-
import { getClient } from
|
|
7
|
-
import { Header } from
|
|
8
|
-
import { SpinnerComponent } from
|
|
9
|
-
import { ErrorMessage } from
|
|
10
|
-
import { SuccessMessage } from
|
|
11
|
-
import { Breadcrumb } from
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
import figures from "figures";
|
|
6
|
+
import { getClient } from "../utils/client.js";
|
|
7
|
+
import { Header } from "./Header.js";
|
|
8
|
+
import { SpinnerComponent } from "./Spinner.js";
|
|
9
|
+
import { ErrorMessage } from "./ErrorMessage.js";
|
|
10
|
+
import { SuccessMessage } from "./SuccessMessage.js";
|
|
11
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
12
|
+
import { colors } from "../utils/theme.js";
|
|
12
13
|
export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
13
|
-
{ label:
|
|
14
|
-
{ label: devbox.name || devbox.id, active: true }
|
|
14
|
+
{ label: "Devboxes" },
|
|
15
|
+
{ label: devbox.name || devbox.id, active: true },
|
|
15
16
|
], initialOperation, initialOperationIndex = 0, skipOperationsMenu = false, onSSHRequest, }) => {
|
|
16
17
|
const { exit } = useApp();
|
|
17
18
|
const { stdout } = useStdout();
|
|
18
19
|
const [loading, setLoading] = React.useState(false);
|
|
19
20
|
const [selectedOperation, setSelectedOperation] = React.useState(initialOperationIndex);
|
|
20
21
|
const [executingOperation, setExecutingOperation] = React.useState(initialOperation || null);
|
|
21
|
-
const [operationInput, setOperationInput] = React.useState(
|
|
22
|
+
const [operationInput, setOperationInput] = React.useState("");
|
|
22
23
|
const [operationResult, setOperationResult] = React.useState(null);
|
|
23
24
|
const [operationError, setOperationError] = React.useState(null);
|
|
24
25
|
const [logsWrapMode, setLogsWrapMode] = React.useState(false);
|
|
@@ -26,38 +27,99 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
26
27
|
const [execScroll, setExecScroll] = React.useState(0);
|
|
27
28
|
const [copyStatus, setCopyStatus] = React.useState(null);
|
|
28
29
|
const allOperations = [
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
30
|
+
{
|
|
31
|
+
key: "logs",
|
|
32
|
+
label: "View Logs",
|
|
33
|
+
color: colors.info,
|
|
34
|
+
icon: figures.info,
|
|
35
|
+
shortcut: "l",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "exec",
|
|
39
|
+
label: "Execute Command",
|
|
40
|
+
color: colors.success,
|
|
41
|
+
icon: figures.play,
|
|
42
|
+
shortcut: "e",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "upload",
|
|
46
|
+
label: "Upload File",
|
|
47
|
+
color: colors.success,
|
|
48
|
+
icon: figures.arrowUp,
|
|
49
|
+
shortcut: "u",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "snapshot",
|
|
53
|
+
label: "Create Snapshot",
|
|
54
|
+
color: colors.warning,
|
|
55
|
+
icon: figures.circleFilled,
|
|
56
|
+
shortcut: "n",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "ssh",
|
|
60
|
+
label: "SSH onto the box",
|
|
61
|
+
color: colors.primary,
|
|
62
|
+
icon: figures.arrowRight,
|
|
63
|
+
shortcut: "s",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "tunnel",
|
|
67
|
+
label: "Open Tunnel",
|
|
68
|
+
color: colors.secondary,
|
|
69
|
+
icon: figures.pointerSmall,
|
|
70
|
+
shortcut: "t",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: "suspend",
|
|
74
|
+
label: "Suspend Devbox",
|
|
75
|
+
color: colors.warning,
|
|
76
|
+
icon: figures.squareSmallFilled,
|
|
77
|
+
shortcut: "p",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "resume",
|
|
81
|
+
label: "Resume Devbox",
|
|
82
|
+
color: colors.success,
|
|
83
|
+
icon: figures.play,
|
|
84
|
+
shortcut: "r",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "delete",
|
|
88
|
+
label: "Shutdown Devbox",
|
|
89
|
+
color: colors.error,
|
|
90
|
+
icon: figures.cross,
|
|
91
|
+
shortcut: "d",
|
|
92
|
+
},
|
|
38
93
|
];
|
|
39
94
|
// Filter operations based on devbox status
|
|
40
|
-
const operations = devbox
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
95
|
+
const operations = devbox
|
|
96
|
+
? allOperations.filter((op) => {
|
|
97
|
+
const status = devbox.status;
|
|
98
|
+
// When suspended: logs and resume
|
|
99
|
+
if (status === "suspended") {
|
|
100
|
+
return op.key === "resume" || op.key === "logs";
|
|
101
|
+
}
|
|
102
|
+
// When not running (shutdown, failure, etc): only logs
|
|
103
|
+
if (status !== "running" &&
|
|
104
|
+
status !== "provisioning" &&
|
|
105
|
+
status !== "initializing") {
|
|
106
|
+
return op.key === "logs";
|
|
107
|
+
}
|
|
108
|
+
// When running: everything except resume
|
|
109
|
+
if (status === "running") {
|
|
110
|
+
return op.key !== "resume";
|
|
111
|
+
}
|
|
112
|
+
// Default for transitional states (provisioning, initializing)
|
|
113
|
+
return op.key === "logs" || op.key === "delete";
|
|
114
|
+
})
|
|
115
|
+
: allOperations;
|
|
57
116
|
// Auto-execute operations that don't need input
|
|
58
117
|
React.useEffect(() => {
|
|
59
|
-
const autoExecuteOps = [
|
|
60
|
-
if (executingOperation &&
|
|
118
|
+
const autoExecuteOps = ["delete", "ssh", "logs", "suspend", "resume"];
|
|
119
|
+
if (executingOperation &&
|
|
120
|
+
autoExecuteOps.includes(executingOperation) &&
|
|
121
|
+
!loading &&
|
|
122
|
+
devbox) {
|
|
61
123
|
executeOperation();
|
|
62
124
|
}
|
|
63
125
|
}, [executingOperation]);
|
|
@@ -67,16 +129,16 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
67
129
|
if (key.return && operationInput.trim()) {
|
|
68
130
|
executeOperation();
|
|
69
131
|
}
|
|
70
|
-
else if (input ===
|
|
132
|
+
else if (input === "q" || key.escape) {
|
|
71
133
|
console.clear();
|
|
72
134
|
setExecutingOperation(null);
|
|
73
|
-
setOperationInput(
|
|
135
|
+
setOperationInput("");
|
|
74
136
|
}
|
|
75
137
|
return;
|
|
76
138
|
}
|
|
77
139
|
// Handle operation result display
|
|
78
140
|
if (operationResult || operationError) {
|
|
79
|
-
if (input ===
|
|
141
|
+
if (input === "q" || key.escape || key.return) {
|
|
80
142
|
console.clear();
|
|
81
143
|
// If skipOperationsMenu is true, go back to parent instead of operations menu
|
|
82
144
|
if (skipOperationsMenu) {
|
|
@@ -86,144 +148,197 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
86
148
|
setOperationResult(null);
|
|
87
149
|
setOperationError(null);
|
|
88
150
|
setExecutingOperation(null);
|
|
89
|
-
setOperationInput(
|
|
151
|
+
setOperationInput("");
|
|
90
152
|
setLogsWrapMode(true);
|
|
91
153
|
setLogsScroll(0);
|
|
92
154
|
setExecScroll(0);
|
|
93
155
|
setCopyStatus(null);
|
|
94
156
|
}
|
|
95
157
|
}
|
|
96
|
-
else if ((key.upArrow || input ===
|
|
158
|
+
else if ((key.upArrow || input === "k") &&
|
|
159
|
+
operationResult &&
|
|
160
|
+
typeof operationResult === "object" &&
|
|
161
|
+
operationResult.__customRender === "exec") {
|
|
97
162
|
setExecScroll(Math.max(0, execScroll - 1));
|
|
98
163
|
}
|
|
99
|
-
else if ((key.downArrow || input ===
|
|
164
|
+
else if ((key.downArrow || input === "j") &&
|
|
165
|
+
operationResult &&
|
|
166
|
+
typeof operationResult === "object" &&
|
|
167
|
+
operationResult.__customRender === "exec") {
|
|
100
168
|
setExecScroll(execScroll + 1);
|
|
101
169
|
}
|
|
102
|
-
else if (key.pageUp &&
|
|
170
|
+
else if (key.pageUp &&
|
|
171
|
+
operationResult &&
|
|
172
|
+
typeof operationResult === "object" &&
|
|
173
|
+
operationResult.__customRender === "exec") {
|
|
103
174
|
setExecScroll(Math.max(0, execScroll - 10));
|
|
104
175
|
}
|
|
105
|
-
else if (key.pageDown &&
|
|
176
|
+
else if (key.pageDown &&
|
|
177
|
+
operationResult &&
|
|
178
|
+
typeof operationResult === "object" &&
|
|
179
|
+
operationResult.__customRender === "exec") {
|
|
106
180
|
setExecScroll(execScroll + 10);
|
|
107
181
|
}
|
|
108
|
-
else if (input ===
|
|
182
|
+
else if (input === "g" &&
|
|
183
|
+
operationResult &&
|
|
184
|
+
typeof operationResult === "object" &&
|
|
185
|
+
operationResult.__customRender === "exec") {
|
|
109
186
|
setExecScroll(0);
|
|
110
187
|
}
|
|
111
|
-
else if (input ===
|
|
112
|
-
|
|
188
|
+
else if (input === "G" &&
|
|
189
|
+
operationResult &&
|
|
190
|
+
typeof operationResult === "object" &&
|
|
191
|
+
operationResult.__customRender === "exec") {
|
|
192
|
+
const lines = [
|
|
193
|
+
...(operationResult.stdout || "").split("\n"),
|
|
194
|
+
...(operationResult.stderr || "").split("\n"),
|
|
195
|
+
];
|
|
113
196
|
const terminalHeight = stdout?.rows || 30;
|
|
114
197
|
const viewportHeight = Math.max(10, terminalHeight - 15);
|
|
115
198
|
const maxScroll = Math.max(0, lines.length - viewportHeight);
|
|
116
199
|
setExecScroll(maxScroll);
|
|
117
200
|
}
|
|
118
|
-
else if (input ===
|
|
201
|
+
else if (input === "c" &&
|
|
202
|
+
operationResult &&
|
|
203
|
+
typeof operationResult === "object" &&
|
|
204
|
+
operationResult.__customRender === "exec") {
|
|
119
205
|
// Copy exec output to clipboard
|
|
120
|
-
const output = (operationResult.stdout ||
|
|
206
|
+
const output = (operationResult.stdout || "") +
|
|
207
|
+
(operationResult.stderr || "");
|
|
121
208
|
const copyToClipboard = async (text) => {
|
|
122
|
-
const { spawn } = await import(
|
|
209
|
+
const { spawn } = await import("child_process");
|
|
123
210
|
const platform = process.platform;
|
|
124
211
|
let command;
|
|
125
212
|
let args;
|
|
126
|
-
if (platform ===
|
|
127
|
-
command =
|
|
213
|
+
if (platform === "darwin") {
|
|
214
|
+
command = "pbcopy";
|
|
128
215
|
args = [];
|
|
129
216
|
}
|
|
130
|
-
else if (platform ===
|
|
131
|
-
command =
|
|
217
|
+
else if (platform === "win32") {
|
|
218
|
+
command = "clip";
|
|
132
219
|
args = [];
|
|
133
220
|
}
|
|
134
221
|
else {
|
|
135
|
-
command =
|
|
136
|
-
args = [
|
|
222
|
+
command = "xclip";
|
|
223
|
+
args = ["-selection", "clipboard"];
|
|
137
224
|
}
|
|
138
225
|
const proc = spawn(command, args);
|
|
139
226
|
proc.stdin.write(text);
|
|
140
227
|
proc.stdin.end();
|
|
141
|
-
proc.on(
|
|
228
|
+
proc.on("exit", (code) => {
|
|
142
229
|
if (code === 0) {
|
|
143
|
-
setCopyStatus(
|
|
230
|
+
setCopyStatus("Copied to clipboard!");
|
|
144
231
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
145
232
|
}
|
|
146
233
|
else {
|
|
147
|
-
setCopyStatus(
|
|
234
|
+
setCopyStatus("Failed to copy");
|
|
148
235
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
149
236
|
}
|
|
150
237
|
});
|
|
151
|
-
proc.on(
|
|
152
|
-
setCopyStatus(
|
|
238
|
+
proc.on("error", () => {
|
|
239
|
+
setCopyStatus("Copy not supported");
|
|
153
240
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
154
241
|
});
|
|
155
242
|
};
|
|
156
243
|
copyToClipboard(output);
|
|
157
244
|
}
|
|
158
|
-
else if ((key.upArrow || input ===
|
|
245
|
+
else if ((key.upArrow || input === "k") &&
|
|
246
|
+
operationResult &&
|
|
247
|
+
typeof operationResult === "object" &&
|
|
248
|
+
operationResult.__customRender === "logs") {
|
|
159
249
|
setLogsScroll(Math.max(0, logsScroll - 1));
|
|
160
250
|
}
|
|
161
|
-
else if ((key.downArrow || input ===
|
|
251
|
+
else if ((key.downArrow || input === "j") &&
|
|
252
|
+
operationResult &&
|
|
253
|
+
typeof operationResult === "object" &&
|
|
254
|
+
operationResult.__customRender === "logs") {
|
|
162
255
|
setLogsScroll(logsScroll + 1);
|
|
163
256
|
}
|
|
164
|
-
else if (key.pageUp &&
|
|
257
|
+
else if (key.pageUp &&
|
|
258
|
+
operationResult &&
|
|
259
|
+
typeof operationResult === "object" &&
|
|
260
|
+
operationResult.__customRender === "logs") {
|
|
165
261
|
setLogsScroll(Math.max(0, logsScroll - 10));
|
|
166
262
|
}
|
|
167
|
-
else if (key.pageDown &&
|
|
263
|
+
else if (key.pageDown &&
|
|
264
|
+
operationResult &&
|
|
265
|
+
typeof operationResult === "object" &&
|
|
266
|
+
operationResult.__customRender === "logs") {
|
|
168
267
|
setLogsScroll(logsScroll + 10);
|
|
169
268
|
}
|
|
170
|
-
else if (input ===
|
|
269
|
+
else if (input === "g" &&
|
|
270
|
+
operationResult &&
|
|
271
|
+
typeof operationResult === "object" &&
|
|
272
|
+
operationResult.__customRender === "logs") {
|
|
171
273
|
setLogsScroll(0);
|
|
172
274
|
}
|
|
173
|
-
else if (input ===
|
|
275
|
+
else if (input === "G" &&
|
|
276
|
+
operationResult &&
|
|
277
|
+
typeof operationResult === "object" &&
|
|
278
|
+
operationResult.__customRender === "logs") {
|
|
174
279
|
const logs = operationResult.__logs || [];
|
|
175
280
|
const terminalHeight = stdout?.rows || 30;
|
|
176
281
|
const viewportHeight = Math.max(10, terminalHeight - 10);
|
|
177
282
|
const maxScroll = Math.max(0, logs.length - viewportHeight);
|
|
178
283
|
setLogsScroll(maxScroll);
|
|
179
284
|
}
|
|
180
|
-
else if (input ===
|
|
285
|
+
else if (input === "w" &&
|
|
286
|
+
operationResult &&
|
|
287
|
+
typeof operationResult === "object" &&
|
|
288
|
+
operationResult.__customRender === "logs") {
|
|
181
289
|
setLogsWrapMode(!logsWrapMode);
|
|
182
290
|
}
|
|
183
|
-
else if (input ===
|
|
291
|
+
else if (input === "c" &&
|
|
292
|
+
operationResult &&
|
|
293
|
+
typeof operationResult === "object" &&
|
|
294
|
+
operationResult.__customRender === "logs") {
|
|
184
295
|
// Copy logs to clipboard
|
|
185
296
|
const logs = operationResult.__logs || [];
|
|
186
|
-
const logsText = logs
|
|
297
|
+
const logsText = logs
|
|
298
|
+
.map((log) => {
|
|
187
299
|
const time = new Date(log.timestamp_ms).toLocaleString();
|
|
188
|
-
const level = log.level ||
|
|
189
|
-
const source = log.source ||
|
|
190
|
-
const message = log.message ||
|
|
191
|
-
const cmd = log.cmd ? `[${log.cmd}] ` :
|
|
192
|
-
const exitCode = log.exit_code !== null && log.exit_code !== undefined
|
|
300
|
+
const level = log.level || "INFO";
|
|
301
|
+
const source = log.source || "exec";
|
|
302
|
+
const message = log.message || "";
|
|
303
|
+
const cmd = log.cmd ? `[${log.cmd}] ` : "";
|
|
304
|
+
const exitCode = log.exit_code !== null && log.exit_code !== undefined
|
|
305
|
+
? `(${log.exit_code}) `
|
|
306
|
+
: "";
|
|
193
307
|
return `${time} ${level}/${source} ${exitCode}${cmd}${message}`;
|
|
194
|
-
})
|
|
308
|
+
})
|
|
309
|
+
.join("\n");
|
|
195
310
|
const copyToClipboard = async (text) => {
|
|
196
|
-
const { spawn } = await import(
|
|
311
|
+
const { spawn } = await import("child_process");
|
|
197
312
|
const platform = process.platform;
|
|
198
313
|
let command;
|
|
199
314
|
let args;
|
|
200
|
-
if (platform ===
|
|
201
|
-
command =
|
|
315
|
+
if (platform === "darwin") {
|
|
316
|
+
command = "pbcopy";
|
|
202
317
|
args = [];
|
|
203
318
|
}
|
|
204
|
-
else if (platform ===
|
|
205
|
-
command =
|
|
319
|
+
else if (platform === "win32") {
|
|
320
|
+
command = "clip";
|
|
206
321
|
args = [];
|
|
207
322
|
}
|
|
208
323
|
else {
|
|
209
|
-
command =
|
|
210
|
-
args = [
|
|
324
|
+
command = "xclip";
|
|
325
|
+
args = ["-selection", "clipboard"];
|
|
211
326
|
}
|
|
212
327
|
const proc = spawn(command, args);
|
|
213
328
|
proc.stdin.write(text);
|
|
214
329
|
proc.stdin.end();
|
|
215
|
-
proc.on(
|
|
330
|
+
proc.on("exit", (code) => {
|
|
216
331
|
if (code === 0) {
|
|
217
|
-
setCopyStatus(
|
|
332
|
+
setCopyStatus("Copied to clipboard!");
|
|
218
333
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
219
334
|
}
|
|
220
335
|
else {
|
|
221
|
-
setCopyStatus(
|
|
336
|
+
setCopyStatus("Failed to copy");
|
|
222
337
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
223
338
|
}
|
|
224
339
|
});
|
|
225
|
-
proc.on(
|
|
226
|
-
setCopyStatus(
|
|
340
|
+
proc.on("error", () => {
|
|
341
|
+
setCopyStatus("Copy not supported");
|
|
227
342
|
setTimeout(() => setCopyStatus(null), 2000);
|
|
228
343
|
});
|
|
229
344
|
};
|
|
@@ -232,7 +347,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
232
347
|
return;
|
|
233
348
|
}
|
|
234
349
|
// Operations selection mode
|
|
235
|
-
if (input ===
|
|
350
|
+
if (input === "q" || key.escape) {
|
|
236
351
|
console.clear();
|
|
237
352
|
onBack();
|
|
238
353
|
setSelectedOperation(0);
|
|
@@ -250,7 +365,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
250
365
|
}
|
|
251
366
|
else if (input) {
|
|
252
367
|
// Check if input matches any operation shortcut
|
|
253
|
-
const matchedOp = operations.find(op => op.shortcut === input);
|
|
368
|
+
const matchedOp = operations.find((op) => op.shortcut === input);
|
|
254
369
|
if (matchedOp) {
|
|
255
370
|
console.clear();
|
|
256
371
|
setExecutingOperation(matchedOp.key);
|
|
@@ -262,48 +377,50 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
262
377
|
try {
|
|
263
378
|
setLoading(true);
|
|
264
379
|
switch (executingOperation) {
|
|
265
|
-
case
|
|
380
|
+
case "exec":
|
|
266
381
|
const execResult = await client.devboxes.executeSync(devbox.id, {
|
|
267
382
|
command: operationInput,
|
|
268
383
|
});
|
|
269
384
|
// Format exec result for custom rendering
|
|
270
385
|
const formattedExecResult = {
|
|
271
|
-
__customRender:
|
|
386
|
+
__customRender: "exec",
|
|
272
387
|
command: operationInput,
|
|
273
|
-
stdout: execResult.stdout ||
|
|
274
|
-
stderr: execResult.stderr ||
|
|
388
|
+
stdout: execResult.stdout || "",
|
|
389
|
+
stderr: execResult.stderr || "",
|
|
275
390
|
exitCode: execResult.exit_code ?? 0,
|
|
276
391
|
};
|
|
277
392
|
setOperationResult(formattedExecResult);
|
|
278
393
|
break;
|
|
279
|
-
case
|
|
280
|
-
const fs = await import(
|
|
394
|
+
case "upload":
|
|
395
|
+
const fs = await import("fs");
|
|
281
396
|
const fileStream = fs.createReadStream(operationInput);
|
|
282
|
-
const filename = operationInput.split(
|
|
397
|
+
const filename = operationInput.split("/").pop() || "file";
|
|
283
398
|
await client.devboxes.uploadFile(devbox.id, {
|
|
284
399
|
path: filename,
|
|
285
400
|
file: fileStream,
|
|
286
401
|
});
|
|
287
402
|
setOperationResult(`File ${filename} uploaded successfully`);
|
|
288
403
|
break;
|
|
289
|
-
case
|
|
404
|
+
case "snapshot":
|
|
290
405
|
const snapshot = await client.devboxes.snapshotDisk(devbox.id, {
|
|
291
406
|
name: operationInput || `snapshot-${Date.now()}`,
|
|
292
407
|
});
|
|
293
408
|
setOperationResult(`Snapshot created: ${snapshot.id}`);
|
|
294
409
|
break;
|
|
295
|
-
case
|
|
410
|
+
case "ssh":
|
|
296
411
|
const sshKey = await client.devboxes.createSSHKey(devbox.id);
|
|
297
|
-
const fsModule = await import(
|
|
298
|
-
const pathModule = await import(
|
|
299
|
-
const osModule = await import(
|
|
300
|
-
const sshDir = pathModule.join(osModule.homedir(),
|
|
412
|
+
const fsModule = await import("fs");
|
|
413
|
+
const pathModule = await import("path");
|
|
414
|
+
const osModule = await import("os");
|
|
415
|
+
const sshDir = pathModule.join(osModule.homedir(), ".runloop", "ssh_keys");
|
|
301
416
|
fsModule.mkdirSync(sshDir, { recursive: true });
|
|
302
417
|
const keyPath = pathModule.join(sshDir, `${devbox.id}.pem`);
|
|
303
|
-
fsModule.writeFileSync(keyPath, sshKey.ssh_private_key, {
|
|
304
|
-
|
|
418
|
+
fsModule.writeFileSync(keyPath, sshKey.ssh_private_key, {
|
|
419
|
+
mode: 0o600,
|
|
420
|
+
});
|
|
421
|
+
const sshUser = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
305
422
|
const env = process.env.RUNLOOP_ENV?.toLowerCase();
|
|
306
|
-
const sshHost = env ===
|
|
423
|
+
const sshHost = env === "dev" ? "ssh.runloop.pro" : "ssh.runloop.ai";
|
|
307
424
|
const proxyCommand = `openssl s_client -quiet -verify_quiet -servername %h -connect ${sshHost}:443 2>/dev/null`;
|
|
308
425
|
const sshConfig = {
|
|
309
426
|
keyPath,
|
|
@@ -311,7 +428,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
311
428
|
sshUser,
|
|
312
429
|
url: sshKey.url,
|
|
313
430
|
devboxId: devbox.id,
|
|
314
|
-
devboxName: devbox.name || devbox.id
|
|
431
|
+
devboxName: devbox.name || devbox.id,
|
|
315
432
|
};
|
|
316
433
|
// Notify parent that SSH is requested
|
|
317
434
|
if (onSSHRequest) {
|
|
@@ -319,43 +436,45 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
319
436
|
exit();
|
|
320
437
|
}
|
|
321
438
|
else {
|
|
322
|
-
setOperationError(new Error(
|
|
439
|
+
setOperationError(new Error("SSH session handler not configured"));
|
|
323
440
|
}
|
|
324
441
|
break;
|
|
325
|
-
case
|
|
442
|
+
case "logs":
|
|
326
443
|
const logsResult = await client.devboxes.logs.list(devbox.id);
|
|
327
444
|
if (logsResult.logs.length === 0) {
|
|
328
|
-
setOperationResult(
|
|
445
|
+
setOperationResult("No logs available for this devbox.");
|
|
329
446
|
}
|
|
330
447
|
else {
|
|
331
|
-
logsResult.__customRender =
|
|
448
|
+
logsResult.__customRender = "logs";
|
|
332
449
|
logsResult.__logs = logsResult.logs;
|
|
333
450
|
logsResult.__totalCount = logsResult.logs.length;
|
|
334
451
|
setOperationResult(logsResult);
|
|
335
452
|
}
|
|
336
453
|
break;
|
|
337
|
-
case
|
|
454
|
+
case "tunnel":
|
|
338
455
|
const port = parseInt(operationInput);
|
|
339
456
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
340
|
-
setOperationError(new Error(
|
|
457
|
+
setOperationError(new Error("Invalid port number. Please enter a port between 1 and 65535."));
|
|
341
458
|
}
|
|
342
459
|
else {
|
|
343
|
-
const tunnel = await client.devboxes.createTunnel(devbox.id, {
|
|
460
|
+
const tunnel = await client.devboxes.createTunnel(devbox.id, {
|
|
461
|
+
port,
|
|
462
|
+
});
|
|
344
463
|
setOperationResult(`Tunnel created!\n\n` +
|
|
345
464
|
`Local Port: ${port}\n` +
|
|
346
465
|
`Public URL: ${tunnel.url}\n\n` +
|
|
347
466
|
`You can now access port ${port} on the devbox via:\n${tunnel.url}`);
|
|
348
467
|
}
|
|
349
468
|
break;
|
|
350
|
-
case
|
|
469
|
+
case "suspend":
|
|
351
470
|
await client.devboxes.suspend(devbox.id);
|
|
352
471
|
setOperationResult(`Devbox ${devbox.id} suspended successfully`);
|
|
353
472
|
break;
|
|
354
|
-
case
|
|
473
|
+
case "resume":
|
|
355
474
|
await client.devboxes.resume(devbox.id);
|
|
356
475
|
setOperationResult(`Devbox ${devbox.id} resumed successfully`);
|
|
357
476
|
break;
|
|
358
|
-
case
|
|
477
|
+
case "delete":
|
|
359
478
|
await client.devboxes.shutdown(devbox.id);
|
|
360
479
|
setOperationResult(`Devbox ${devbox.id} shut down successfully`);
|
|
361
480
|
break;
|
|
@@ -368,18 +487,20 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
368
487
|
setLoading(false);
|
|
369
488
|
}
|
|
370
489
|
};
|
|
371
|
-
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
490
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label || "Operation";
|
|
372
491
|
// Operation result display
|
|
373
492
|
if (operationResult || operationError) {
|
|
374
493
|
// Check for custom exec rendering
|
|
375
|
-
if (operationResult &&
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
494
|
+
if (operationResult &&
|
|
495
|
+
typeof operationResult === "object" &&
|
|
496
|
+
operationResult.__customRender === "exec") {
|
|
497
|
+
const command = operationResult.command || "";
|
|
498
|
+
const stdout = operationResult.stdout || "";
|
|
499
|
+
const stderr = operationResult.stderr || "";
|
|
379
500
|
const exitCode = operationResult.exitCode;
|
|
380
|
-
const stdoutLines = stdout ? stdout.split(
|
|
381
|
-
const stderrLines = stderr ? stderr.split(
|
|
382
|
-
const allLines = [...stdoutLines, ...stderrLines].filter(line => line !==
|
|
501
|
+
const stdoutLines = stdout ? stdout.split("\n") : [];
|
|
502
|
+
const stderrLines = stderr ? stderr.split("\n") : [];
|
|
503
|
+
const allLines = [...stdoutLines, ...stderrLines].filter((line) => line !== "");
|
|
383
504
|
const terminalHeight = stdout?.rows || 30;
|
|
384
505
|
const viewportHeight = Math.max(10, terminalHeight - 15);
|
|
385
506
|
const maxScroll = Math.max(0, allLines.length - viewportHeight);
|
|
@@ -387,16 +508,21 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
387
508
|
const visibleLines = allLines.slice(actualScroll, actualScroll + viewportHeight);
|
|
388
509
|
const hasMore = actualScroll + viewportHeight < allLines.length;
|
|
389
510
|
const hasLess = actualScroll > 0;
|
|
390
|
-
const exitCodeColor = exitCode === 0 ?
|
|
391
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
511
|
+
const exitCodeColor = exitCode === 0 ? colors.success : colors.error;
|
|
512
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
513
|
+
...breadcrumbItems,
|
|
514
|
+
{ label: "Execute Command", active: true },
|
|
515
|
+
] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.primary, paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Command:"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: colors.text, children: command })] }), _jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Exit Code:", " "] }), _jsx(Text, { color: exitCodeColor, bold: true, children: exitCode })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, paddingX: 1, children: [allLines.length === 0 && (_jsx(Text, { color: colors.textDim, dimColor: true, children: "No output" })), visibleLines.map((line, index) => {
|
|
392
516
|
const actualIndex = actualScroll + index;
|
|
393
517
|
const isStderr = actualIndex >= stdoutLines.length;
|
|
394
|
-
const lineColor = isStderr ?
|
|
518
|
+
const lineColor = isStderr ? colors.error : colors.text;
|
|
395
519
|
return (_jsx(Box, { children: _jsx(Text, { color: lineColor, children: line }) }, index));
|
|
396
|
-
}), hasLess && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color:
|
|
520
|
+
}), hasLess && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.primary, children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { marginTop: hasLess ? 0 : 1, children: _jsxs(Text, { color: colors.primary, children: [figures.arrowDown, " More below"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", allLines.length] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "lines"] }), allLines.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, allLines.length), " of", " ", allLines.length] })] })), stdout && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.success, dimColor: true, children: ["stdout: ", stdoutLines.length, " lines"] })] })), stderr && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.error, dimColor: true, children: ["stderr: ", stderrLines.length, " lines"] })] })), copyStatus && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: colors.success, bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
|
|
397
521
|
}
|
|
398
522
|
// Check for custom logs rendering
|
|
399
|
-
if (operationResult &&
|
|
523
|
+
if (operationResult &&
|
|
524
|
+
typeof operationResult === "object" &&
|
|
525
|
+
operationResult.__customRender === "logs") {
|
|
400
526
|
const logs = operationResult.__logs || [];
|
|
401
527
|
const totalCount = operationResult.__totalCount || 0;
|
|
402
528
|
const terminalHeight = stdout?.rows || 30;
|
|
@@ -407,74 +533,85 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
|
|
|
407
533
|
const visibleLogs = logs.slice(actualScroll, actualScroll + viewportHeight);
|
|
408
534
|
const hasMore = actualScroll + viewportHeight < logs.length;
|
|
409
535
|
const hasLess = actualScroll > 0;
|
|
410
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label:
|
|
536
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: "Logs", active: true }] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, paddingX: 1, children: [visibleLogs.map((log, index) => {
|
|
411
537
|
const time = new Date(log.timestamp_ms).toLocaleTimeString();
|
|
412
|
-
const level = log.level ? log.level[0].toUpperCase() :
|
|
413
|
-
const source = log.source ? log.source.substring(0, 8) :
|
|
414
|
-
const fullMessage = log.message ||
|
|
415
|
-
const cmd = log.cmd
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
levelColor =
|
|
538
|
+
const level = log.level ? log.level[0].toUpperCase() : "I";
|
|
539
|
+
const source = log.source ? log.source.substring(0, 8) : "exec";
|
|
540
|
+
const fullMessage = log.message || "";
|
|
541
|
+
const cmd = log.cmd
|
|
542
|
+
? `[${log.cmd.substring(0, 40)}${log.cmd.length > 40 ? "..." : ""}] `
|
|
543
|
+
: "";
|
|
544
|
+
const exitCode = log.exit_code !== null && log.exit_code !== undefined
|
|
545
|
+
? `(${log.exit_code}) `
|
|
546
|
+
: "";
|
|
547
|
+
let levelColor = colors.textDim;
|
|
548
|
+
if (level === "E")
|
|
549
|
+
levelColor = colors.error;
|
|
550
|
+
else if (level === "W")
|
|
551
|
+
levelColor = colors.warning;
|
|
552
|
+
else if (level === "I")
|
|
553
|
+
levelColor = colors.primary;
|
|
424
554
|
if (logsWrapMode) {
|
|
425
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color:
|
|
555
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: colors.warning, children: exitCode }), cmd && (_jsx(Text, { color: colors.info, dimColor: true, children: cmd })), _jsx(Text, { children: fullMessage })] }, index));
|
|
426
556
|
}
|
|
427
557
|
else {
|
|
428
558
|
const metadataWidth = 11 + 1 + 1 + 1 + 8 + 1 + exitCode.length + cmd.length + 6;
|
|
429
559
|
const availableMessageWidth = Math.max(20, terminalWidth - metadataWidth);
|
|
430
560
|
const truncatedMessage = fullMessage.length > availableMessageWidth
|
|
431
|
-
? fullMessage.substring(0, availableMessageWidth - 3) +
|
|
561
|
+
? fullMessage.substring(0, availableMessageWidth - 3) +
|
|
562
|
+
"..."
|
|
432
563
|
: fullMessage;
|
|
433
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color:
|
|
564
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: colors.warning, children: exitCode }), cmd && (_jsx(Text, { color: colors.info, dimColor: true, children: cmd })), _jsx(Text, { children: truncatedMessage })] }, index));
|
|
434
565
|
}
|
|
435
|
-
}), hasLess && (_jsx(Box, { children: _jsxs(Text, { color:
|
|
566
|
+
}), hasLess && (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: [figures.arrowDown, " More below"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total logs"] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, logs.length), " of", " ", logs.length] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: logsWrapMode ? colors.success : colors.textDim, bold: logsWrapMode, children: logsWrapMode ? "Wrap: ON" : "Wrap: OFF" }), copyStatus && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: colors.success, bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [w] Toggle Wrap \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
|
|
436
567
|
}
|
|
437
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && _jsx(ErrorMessage, { message: "Operation failed", error: operationError }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color:
|
|
568
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter], [q], or [esc] to continue" }) })] }));
|
|
438
569
|
}
|
|
439
570
|
// Operation input mode
|
|
440
571
|
if (executingOperation && devbox) {
|
|
441
|
-
const needsInput = executingOperation ===
|
|
442
|
-
executingOperation ===
|
|
443
|
-
executingOperation ===
|
|
444
|
-
executingOperation ===
|
|
572
|
+
const needsInput = executingOperation === "exec" ||
|
|
573
|
+
executingOperation === "upload" ||
|
|
574
|
+
executingOperation === "snapshot" ||
|
|
575
|
+
executingOperation === "tunnel";
|
|
445
576
|
if (loading) {
|
|
446
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
577
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
578
|
+
...breadcrumbItems,
|
|
579
|
+
{ label: operationLabel, active: true },
|
|
580
|
+
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: "Please wait..." })] }));
|
|
447
581
|
}
|
|
448
582
|
if (!needsInput) {
|
|
449
583
|
const messages = {
|
|
450
|
-
ssh:
|
|
451
|
-
logs:
|
|
452
|
-
suspend:
|
|
453
|
-
resume:
|
|
454
|
-
delete:
|
|
584
|
+
ssh: "Creating SSH key...",
|
|
585
|
+
logs: "Fetching logs...",
|
|
586
|
+
suspend: "Suspending devbox...",
|
|
587
|
+
resume: "Resuming devbox...",
|
|
588
|
+
delete: "Shutting down devbox...",
|
|
455
589
|
};
|
|
456
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
590
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
591
|
+
...breadcrumbItems,
|
|
592
|
+
{ label: operationLabel, active: true },
|
|
593
|
+
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
|
|
457
594
|
}
|
|
458
595
|
const prompts = {
|
|
459
|
-
exec:
|
|
460
|
-
upload:
|
|
461
|
-
snapshot:
|
|
462
|
-
tunnel:
|
|
596
|
+
exec: "Command to execute:",
|
|
597
|
+
upload: "File path to upload:",
|
|
598
|
+
snapshot: "Snapshot name (optional):",
|
|
599
|
+
tunnel: "Port number to expose:",
|
|
463
600
|
};
|
|
464
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: operationLabel }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color:
|
|
465
|
-
?
|
|
466
|
-
: executingOperation ===
|
|
467
|
-
?
|
|
468
|
-
: executingOperation ===
|
|
469
|
-
?
|
|
470
|
-
:
|
|
601
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: operationLabel }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.primary, bold: true, children: devbox.name || devbox.id }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.textDim, children: [prompts[executingOperation], " "] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: operationInput, onChange: setOperationInput, placeholder: executingOperation === "exec"
|
|
602
|
+
? "ls -la"
|
|
603
|
+
: executingOperation === "upload"
|
|
604
|
+
? "/path/to/file"
|
|
605
|
+
: executingOperation === "tunnel"
|
|
606
|
+
? "8080"
|
|
607
|
+
: "my-snapshot" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" }) })] })] }));
|
|
471
608
|
}
|
|
472
609
|
// Operations selection mode - only show if not skipping
|
|
473
610
|
if (!skipOperationsMenu || !executingOperation) {
|
|
474
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color:
|
|
611
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems, showVersionCheck: true }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Operations"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
|
|
475
612
|
const isSelected = index === selectedOperation;
|
|
476
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ?
|
|
477
|
-
}) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color:
|
|
613
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isSelected ? op.color : colors.textDim, bold: isSelected, children: [op.icon, " ", op.label] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", op.shortcut, "]"] })] }, op.key));
|
|
614
|
+
}) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Select \u2022 [q] Back"] }) })] }));
|
|
478
615
|
}
|
|
479
616
|
// If skipOperationsMenu is true and executingOperation is set, show loading while it executes
|
|
480
617
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SpinnerComponent, { message: "Loading..." })] }));
|