@runloop/rl-cli 1.7.0 → 1.8.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/dist/commands/menu.js +2 -1
- package/dist/commands/secret/list.js +379 -4
- package/dist/components/BenchmarkMenu.js +88 -0
- package/dist/components/MainMenu.js +63 -22
- package/dist/components/SecretCreatePage.js +185 -0
- package/dist/components/SettingsMenu.js +85 -0
- package/dist/components/StatusBadge.js +73 -0
- package/dist/components/StreamingLogsViewer.js +114 -53
- package/dist/router/Router.js +21 -1
- package/dist/screens/BenchmarkMenuScreen.js +23 -0
- package/dist/screens/BenchmarkRunDetailScreen.js +189 -0
- package/dist/screens/BenchmarkRunListScreen.js +255 -0
- package/dist/screens/MenuScreen.js +5 -2
- package/dist/screens/ScenarioRunDetailScreen.js +220 -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 +23 -0
- package/dist/services/benchmarkService.js +73 -0
- package/dist/store/benchmarkStore.js +120 -0
- package/dist/store/betaFeatureStore.js +47 -0
- package/dist/store/index.js +1 -0
- package/dist/utils/config.js +8 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
4
4
|
import figures from "figures";
|
|
@@ -10,7 +10,8 @@ import { colors } from "../utils/theme.js";
|
|
|
10
10
|
import { execCommand } from "../utils/exec.js";
|
|
11
11
|
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
12
12
|
import { useUpdateCheck } from "../hooks/useUpdateCheck.js";
|
|
13
|
-
|
|
13
|
+
import { useBetaFeatures } from "../store/betaFeatureStore.js";
|
|
14
|
+
const allMenuItems = [
|
|
14
15
|
{
|
|
15
16
|
key: "devboxes",
|
|
16
17
|
label: "Devboxes",
|
|
@@ -40,10 +41,18 @@ const menuItems = [
|
|
|
40
41
|
color: colors.secondary,
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
|
-
key: "
|
|
44
|
-
label: "
|
|
45
|
-
description: "
|
|
46
|
-
icon: "
|
|
44
|
+
key: "benchmarks",
|
|
45
|
+
label: "Benchmarks",
|
|
46
|
+
description: "Performance testing and evaluation",
|
|
47
|
+
icon: "▶",
|
|
48
|
+
color: colors.success,
|
|
49
|
+
betaFeature: "benchmarks",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "settings",
|
|
53
|
+
label: "Settings",
|
|
54
|
+
description: "Network policies, secrets, and more",
|
|
55
|
+
icon: "⚙",
|
|
47
56
|
color: colors.info,
|
|
48
57
|
},
|
|
49
58
|
];
|
|
@@ -56,10 +65,22 @@ function getLayoutMode(height) {
|
|
|
56
65
|
return "compact"; // No banner + simple items + short descriptions
|
|
57
66
|
return "minimal"; // No banner + labels only
|
|
58
67
|
}
|
|
68
|
+
// Helper component for rendering beta badge
|
|
69
|
+
const BetaBadge = () => (_jsxs(Text, { color: colors.warning, bold: true, children: [" ", "[BETA]"] }));
|
|
59
70
|
export const MainMenu = ({ onSelect }) => {
|
|
60
71
|
const { exit } = useApp();
|
|
61
72
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
62
73
|
const { stdout } = useStdout();
|
|
74
|
+
const { isFeatureEnabled } = useBetaFeatures();
|
|
75
|
+
// Filter menu items based on beta feature flags
|
|
76
|
+
const menuItems = React.useMemo(() => {
|
|
77
|
+
return allMenuItems.filter((item) => {
|
|
78
|
+
if (item.betaFeature) {
|
|
79
|
+
return isFeatureEnabled(item.betaFeature);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
}, [isFeatureEnabled]);
|
|
63
84
|
// Get raw terminal dimensions, responding to resize events
|
|
64
85
|
// Default to 20 rows / 80 cols if we can't detect
|
|
65
86
|
const getTerminalDimensions = React.useCallback(() => {
|
|
@@ -89,6 +110,19 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
89
110
|
const { updateAvailable } = useUpdateCheck();
|
|
90
111
|
// Handle Ctrl+C to exit
|
|
91
112
|
useExitOnCtrlC();
|
|
113
|
+
// Helper to select menu item by key (if available in filtered list)
|
|
114
|
+
const selectByKey = React.useCallback((key) => {
|
|
115
|
+
if (menuItems.some((item) => item.key === key)) {
|
|
116
|
+
onSelect(key);
|
|
117
|
+
}
|
|
118
|
+
}, [menuItems, onSelect]);
|
|
119
|
+
// Helper to select menu item by number (1-indexed, based on filtered list)
|
|
120
|
+
const selectByNumber = React.useCallback((num) => {
|
|
121
|
+
const index = num - 1;
|
|
122
|
+
if (index >= 0 && index < menuItems.length) {
|
|
123
|
+
onSelect(menuItems[index].key);
|
|
124
|
+
}
|
|
125
|
+
}, [menuItems, onSelect]);
|
|
92
126
|
useInput((input, key) => {
|
|
93
127
|
if (key.upArrow && selectedIndex > 0) {
|
|
94
128
|
setSelectedIndex(selectedIndex - 1);
|
|
@@ -102,20 +136,26 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
102
136
|
else if (key.escape) {
|
|
103
137
|
exit();
|
|
104
138
|
}
|
|
105
|
-
else if (input === "d"
|
|
106
|
-
|
|
139
|
+
else if (input === "d") {
|
|
140
|
+
selectByKey("devboxes");
|
|
141
|
+
}
|
|
142
|
+
else if (input === "b") {
|
|
143
|
+
selectByKey("blueprints");
|
|
144
|
+
}
|
|
145
|
+
else if (input === "s") {
|
|
146
|
+
selectByKey("snapshots");
|
|
107
147
|
}
|
|
108
|
-
else if (input === "
|
|
109
|
-
|
|
148
|
+
else if (input === "o") {
|
|
149
|
+
selectByKey("objects");
|
|
110
150
|
}
|
|
111
|
-
else if (input === "
|
|
112
|
-
|
|
151
|
+
else if (input === "e") {
|
|
152
|
+
selectByKey("benchmarks");
|
|
113
153
|
}
|
|
114
|
-
else if (input === "
|
|
115
|
-
|
|
154
|
+
else if (input === "n") {
|
|
155
|
+
selectByKey("settings");
|
|
116
156
|
}
|
|
117
|
-
else if (input
|
|
118
|
-
|
|
157
|
+
else if (input >= "1" && input <= "9") {
|
|
158
|
+
selectByNumber(parseInt(input, 10));
|
|
119
159
|
}
|
|
120
160
|
else if (input === "u" && updateAvailable) {
|
|
121
161
|
// Release terminal and exec into update command (never returns)
|
|
@@ -127,8 +167,9 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
127
167
|
});
|
|
128
168
|
const layoutMode = getLayoutMode(terminalHeight);
|
|
129
169
|
// Navigation tips for all layouts
|
|
170
|
+
const quickSelectRange = `1-${menuItems.length}`;
|
|
130
171
|
const navTips = (_jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
|
|
131
|
-
{ key:
|
|
172
|
+
{ key: quickSelectRange, label: "Quick select" },
|
|
132
173
|
{ key: "Enter", label: "Select" },
|
|
133
174
|
{ key: "Esc", label: "Quit" },
|
|
134
175
|
{ key: "u", label: "Update", condition: !!updateAvailable },
|
|
@@ -137,14 +178,14 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
137
178
|
if (layoutMode === "minimal") {
|
|
138
179
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: colors.primary, bold: true, children: "RUNLOOP" }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "v", VERSION] })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
|
|
139
180
|
const isSelected = index === selectedIndex;
|
|
140
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
181
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
141
182
|
}) }), navTips] }));
|
|
142
183
|
}
|
|
143
184
|
// Compact layout - no banner, simple items with descriptions (or no descriptions if narrow)
|
|
144
185
|
if (layoutMode === "compact") {
|
|
145
186
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home", active: true }], showVersionCheck: true }), _jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: colors.primary, bold: true, children: "RUNLOOP.ai" }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 v", VERSION] })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
|
|
146
187
|
const isSelected = index === selectedIndex;
|
|
147
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow
|
|
188
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow
|
|
148
189
|
? ` [${index + 1}]`
|
|
149
190
|
: ` - ${item.description} [${index + 1}]` })] }, item.key));
|
|
150
191
|
}) }), navTips] }));
|
|
@@ -155,7 +196,7 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
155
196
|
? ` • v${VERSION}`
|
|
156
197
|
: ` • Cloud development environments • v${VERSION}` })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
|
|
157
198
|
const isSelected = index === selectedIndex;
|
|
158
|
-
return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
199
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
159
200
|
}) }), navTips] }));
|
|
160
201
|
}
|
|
161
202
|
// Full layout - big banner, bordered items (or simple items if narrow)
|
|
@@ -163,11 +204,11 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
163
204
|
// Narrow layout - no borders, compact items
|
|
164
205
|
_jsx(Box, { flexDirection: "column", marginTop: 1, children: menuItems.map((item, index) => {
|
|
165
206
|
const isSelected = index === selectedIndex;
|
|
166
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
207
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsxs(Text, { color: item.color, children: [" ", item.icon, " "] }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
167
208
|
}) })) : (
|
|
168
209
|
// Wide layout - bordered items with descriptions
|
|
169
210
|
menuItems.map((item, index) => {
|
|
170
211
|
const isSelected = index === selectedIndex;
|
|
171
|
-
return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [isSelected && (_jsxs(_Fragment, { children: [_jsx(Text, { color: item.color, bold: true, children: figures.pointer }), _jsx(Text, { children: " " })] })), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: item.description }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["[", index + 1, "]"] })] }, item.key));
|
|
212
|
+
return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [isSelected && (_jsxs(_Fragment, { children: [_jsx(Text, { color: item.color, bold: true, children: figures.pointer }), _jsx(Text, { children: " " })] })), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), item.betaFeature && _jsx(BetaBadge, {}), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: item.description }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["[", index + 1, "]"] })] }, item.key));
|
|
172
213
|
}))] }), navTips] }));
|
|
173
214
|
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* SecretCreatePage - Form for creating a new secret
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text, useInput } from "ink";
|
|
7
|
+
import figures from "figures";
|
|
8
|
+
import { getClient } from "../utils/client.js";
|
|
9
|
+
import { SpinnerComponent } from "./Spinner.js";
|
|
10
|
+
import { ErrorMessage } from "./ErrorMessage.js";
|
|
11
|
+
import { SuccessMessage } from "./SuccessMessage.js";
|
|
12
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
13
|
+
import { NavigationTips } from "./NavigationTips.js";
|
|
14
|
+
import { FormTextInput, FormActionButton } from "./form/index.js";
|
|
15
|
+
import { colors } from "../utils/theme.js";
|
|
16
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
17
|
+
export const SecretCreatePage = ({ onBack, onCreate, }) => {
|
|
18
|
+
const [currentField, setCurrentField] = React.useState("submit");
|
|
19
|
+
const [formData, setFormData] = React.useState({
|
|
20
|
+
name: "",
|
|
21
|
+
value: "",
|
|
22
|
+
});
|
|
23
|
+
const [submitting, setSubmitting] = React.useState(false);
|
|
24
|
+
const [result, setResult] = React.useState(null);
|
|
25
|
+
const [error, setError] = React.useState(null);
|
|
26
|
+
const [validationError, setValidationError] = React.useState(null);
|
|
27
|
+
const fields = [
|
|
28
|
+
{ key: "submit", label: "Create Secret", type: "action" },
|
|
29
|
+
{ key: "name", label: "Name (required)", type: "text" },
|
|
30
|
+
{ key: "value", label: "Value (required)", type: "password" },
|
|
31
|
+
];
|
|
32
|
+
const currentFieldIndex = fields.findIndex((f) => f.key === currentField);
|
|
33
|
+
// Handle Ctrl+C to exit
|
|
34
|
+
useExitOnCtrlC();
|
|
35
|
+
// Main form input handler
|
|
36
|
+
useInput((input, key) => {
|
|
37
|
+
// Handle result screen
|
|
38
|
+
if (result) {
|
|
39
|
+
if (input === "q" || key.escape || key.return) {
|
|
40
|
+
if (onCreate) {
|
|
41
|
+
onCreate(result);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
onBack();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Handle error screen
|
|
50
|
+
if (error) {
|
|
51
|
+
if (input === "r" || key.return) {
|
|
52
|
+
// Retry - clear error and return to form
|
|
53
|
+
setError(null);
|
|
54
|
+
}
|
|
55
|
+
else if (input === "q" || key.escape) {
|
|
56
|
+
// Quit - go back to list
|
|
57
|
+
onBack();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Handle submitting state
|
|
62
|
+
if (submitting) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Back to list
|
|
66
|
+
if (input === "q" || key.escape) {
|
|
67
|
+
onBack();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Submit form with Ctrl+S
|
|
71
|
+
if (input === "s" && key.ctrl) {
|
|
72
|
+
handleSubmit();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Handle Enter on any field to submit
|
|
76
|
+
if (key.return) {
|
|
77
|
+
handleSubmit();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Navigation between fields (up/down arrows and tab/shift+tab)
|
|
81
|
+
if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) {
|
|
82
|
+
setCurrentField(fields[currentFieldIndex - 1].key);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if ((key.downArrow || (key.tab && !key.shift)) &&
|
|
86
|
+
currentFieldIndex < fields.length - 1) {
|
|
87
|
+
setCurrentField(fields[currentFieldIndex + 1].key);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const handleSubmit = async () => {
|
|
92
|
+
// Validate required fields
|
|
93
|
+
if (!formData.name.trim()) {
|
|
94
|
+
setValidationError("Name is required");
|
|
95
|
+
setCurrentField("name");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!formData.value) {
|
|
99
|
+
setValidationError("Value is required");
|
|
100
|
+
setCurrentField("value");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
setSubmitting(true);
|
|
104
|
+
setError(null);
|
|
105
|
+
setValidationError(null);
|
|
106
|
+
try {
|
|
107
|
+
const client = getClient();
|
|
108
|
+
const secret = await client.secrets.create({
|
|
109
|
+
name: formData.name.trim(),
|
|
110
|
+
value: formData.value,
|
|
111
|
+
});
|
|
112
|
+
setResult(secret);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
setError(err);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
setSubmitting(false);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Result screen
|
|
122
|
+
if (result) {
|
|
123
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
124
|
+
{ label: "Settings" },
|
|
125
|
+
{ label: "Secrets" },
|
|
126
|
+
{ label: "Create", active: true },
|
|
127
|
+
] }), _jsx(SuccessMessage, { message: "Secret created successfully!" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["ID:", " "] }), _jsx(Text, { color: colors.idColor, children: result.id })] }), _jsx(Box, { children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Name: ", result.name] }) })] }), _jsx(Box, { marginTop: 1, marginLeft: 2, children: _jsxs(Text, { color: colors.info, children: [figures.info, " The secret value has been securely stored and cannot be retrieved."] }) }), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "View secret details" }] })] }));
|
|
128
|
+
}
|
|
129
|
+
// Error screen
|
|
130
|
+
if (error) {
|
|
131
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
132
|
+
{ label: "Settings" },
|
|
133
|
+
{ label: "Secrets" },
|
|
134
|
+
{ label: "Create", active: true },
|
|
135
|
+
] }), _jsx(ErrorMessage, { message: "Failed to create secret", error: error }), _jsx(NavigationTips, { tips: [
|
|
136
|
+
{ key: "Enter/r", label: "Retry" },
|
|
137
|
+
{ key: "q/esc", label: "Cancel" },
|
|
138
|
+
] })] }));
|
|
139
|
+
}
|
|
140
|
+
// Submitting screen
|
|
141
|
+
if (submitting) {
|
|
142
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
143
|
+
{ label: "Settings" },
|
|
144
|
+
{ label: "Secrets" },
|
|
145
|
+
{ label: "Create", active: true },
|
|
146
|
+
] }), _jsx(SpinnerComponent, { message: "Creating secret..." })] }));
|
|
147
|
+
}
|
|
148
|
+
// Form screen
|
|
149
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
150
|
+
{ label: "Settings" },
|
|
151
|
+
{ label: "Secrets" },
|
|
152
|
+
{ label: "Create", active: true },
|
|
153
|
+
] }), _jsx(Box, { borderStyle: "round", borderColor: colors.info, paddingX: 1, paddingY: 0, marginBottom: 1, children: _jsxs(Text, { color: colors.info, children: [figures.info, " ", _jsx(Text, { bold: true, children: "Note:" }), " Secret values are", " ", _jsx(Text, { bold: true, children: "write-only" }), ". Once created, the value cannot be retrieved or viewed. To change a secret, delete it and create a new one."] }) }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field) => {
|
|
154
|
+
const isActive = currentField === field.key;
|
|
155
|
+
if (field.type === "action") {
|
|
156
|
+
return (_jsx(FormActionButton, { label: field.label, isActive: isActive, hint: "[Enter to create]" }, field.key));
|
|
157
|
+
}
|
|
158
|
+
if (field.type === "text") {
|
|
159
|
+
const value = formData[field.key];
|
|
160
|
+
const hasError = field.key === "name" && validationError === "Name is required";
|
|
161
|
+
return (_jsx(FormTextInput, { label: field.label, value: value, onChange: (newValue) => {
|
|
162
|
+
setFormData({ ...formData, [field.key]: newValue });
|
|
163
|
+
if (validationError) {
|
|
164
|
+
setValidationError(null);
|
|
165
|
+
}
|
|
166
|
+
}, onSubmit: handleSubmit, isActive: isActive, placeholder: "my-secret-name", error: hasError ? validationError : undefined }, field.key));
|
|
167
|
+
}
|
|
168
|
+
if (field.type === "password") {
|
|
169
|
+
const value = formData[field.key];
|
|
170
|
+
const hasError = field.key === "value" && validationError === "Value is required";
|
|
171
|
+
// Display masked value when not active
|
|
172
|
+
const maskedValue = "*".repeat(value.length);
|
|
173
|
+
return (_jsx(FormTextInput, { label: field.label, value: isActive ? value : maskedValue, onChange: (newValue) => {
|
|
174
|
+
setFormData({ ...formData, [field.key]: newValue });
|
|
175
|
+
if (validationError) {
|
|
176
|
+
setValidationError(null);
|
|
177
|
+
}
|
|
178
|
+
}, onSubmit: handleSubmit, isActive: isActive, placeholder: "Enter secret value", error: hasError ? validationError : undefined }, field.key));
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}) }), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
182
|
+
{ key: "Enter", label: "Create" },
|
|
183
|
+
{ key: "q", label: "Cancel" },
|
|
184
|
+
] })] }));
|
|
185
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
4
|
+
import figures from "figures";
|
|
5
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
6
|
+
import { NavigationTips } from "./NavigationTips.js";
|
|
7
|
+
import { colors } from "../utils/theme.js";
|
|
8
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
9
|
+
const settingsMenuItems = [
|
|
10
|
+
{
|
|
11
|
+
key: "network-policies",
|
|
12
|
+
label: "Network Policies",
|
|
13
|
+
description: "Manage egress network access rules",
|
|
14
|
+
icon: "◇",
|
|
15
|
+
color: colors.info,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: "secrets",
|
|
19
|
+
label: "Secrets",
|
|
20
|
+
description: "Manage sensitive values and credentials",
|
|
21
|
+
icon: "◆",
|
|
22
|
+
color: colors.warning,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
export const SettingsMenu = ({ onSelect, onBack }) => {
|
|
26
|
+
const { exit } = useApp();
|
|
27
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
28
|
+
const { stdout } = useStdout();
|
|
29
|
+
// Get terminal dimensions for responsive layout
|
|
30
|
+
const getTerminalDimensions = React.useCallback(() => {
|
|
31
|
+
return {
|
|
32
|
+
height: stdout?.rows && stdout.rows > 0 ? stdout.rows : 20,
|
|
33
|
+
width: stdout?.columns && stdout.columns > 0 ? stdout.columns : 80,
|
|
34
|
+
};
|
|
35
|
+
}, [stdout]);
|
|
36
|
+
const [terminalDimensions, setTerminalDimensions] = React.useState(getTerminalDimensions);
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
setTerminalDimensions(getTerminalDimensions());
|
|
39
|
+
if (!stdout)
|
|
40
|
+
return;
|
|
41
|
+
const handleResize = () => {
|
|
42
|
+
setTerminalDimensions(getTerminalDimensions());
|
|
43
|
+
};
|
|
44
|
+
stdout.on("resize", handleResize);
|
|
45
|
+
return () => {
|
|
46
|
+
stdout.off("resize", handleResize);
|
|
47
|
+
};
|
|
48
|
+
}, [stdout, getTerminalDimensions]);
|
|
49
|
+
const terminalWidth = terminalDimensions.width;
|
|
50
|
+
const isNarrow = terminalWidth < 70;
|
|
51
|
+
// Handle Ctrl+C to exit
|
|
52
|
+
useExitOnCtrlC();
|
|
53
|
+
useInput((input, key) => {
|
|
54
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
55
|
+
setSelectedIndex(selectedIndex - 1);
|
|
56
|
+
}
|
|
57
|
+
else if (key.downArrow && selectedIndex < settingsMenuItems.length - 1) {
|
|
58
|
+
setSelectedIndex(selectedIndex + 1);
|
|
59
|
+
}
|
|
60
|
+
else if (key.return) {
|
|
61
|
+
onSelect(settingsMenuItems[selectedIndex].key);
|
|
62
|
+
}
|
|
63
|
+
else if (key.escape) {
|
|
64
|
+
onBack();
|
|
65
|
+
}
|
|
66
|
+
else if (input === "n" || input === "1") {
|
|
67
|
+
onSelect("network-policies");
|
|
68
|
+
}
|
|
69
|
+
else if (input === "s" || input === "2") {
|
|
70
|
+
onSelect("secrets");
|
|
71
|
+
}
|
|
72
|
+
else if (input === "q") {
|
|
73
|
+
exit();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home" }, { label: "Settings", active: true }] }), _jsxs(Box, { paddingX: 2, marginBottom: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "Settings" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow ? "" : " • Configure your environment" })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: settingsMenuItems.map((item, index) => {
|
|
77
|
+
const isSelected = index === selectedIndex;
|
|
78
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
|
|
79
|
+
}) }), _jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
|
|
80
|
+
{ key: "1-2", label: "Quick select" },
|
|
81
|
+
{ key: "Enter", label: "Select" },
|
|
82
|
+
{ key: "Esc", label: "Back" },
|
|
83
|
+
{ key: "q", label: "Quit" },
|
|
84
|
+
] })] }));
|
|
85
|
+
};
|
|
@@ -104,6 +104,79 @@ export const getStatusDisplay = (status) => {
|
|
|
104
104
|
text: "BUILDING ",
|
|
105
105
|
label: "Building: In Progress",
|
|
106
106
|
};
|
|
107
|
+
// === BENCHMARK/SCENARIO STATES ===
|
|
108
|
+
case "completed":
|
|
109
|
+
return {
|
|
110
|
+
icon: figures.tick,
|
|
111
|
+
color: colors.success,
|
|
112
|
+
text: "COMPLETED ",
|
|
113
|
+
label: "Completed",
|
|
114
|
+
};
|
|
115
|
+
case "canceled":
|
|
116
|
+
return {
|
|
117
|
+
icon: figures.cross,
|
|
118
|
+
color: colors.textDim,
|
|
119
|
+
text: "CANCELED ",
|
|
120
|
+
label: "Canceled",
|
|
121
|
+
};
|
|
122
|
+
case "scoring":
|
|
123
|
+
return {
|
|
124
|
+
icon: figures.arrowUp,
|
|
125
|
+
color: colors.warning,
|
|
126
|
+
text: "SCORING ",
|
|
127
|
+
label: "Scoring",
|
|
128
|
+
};
|
|
129
|
+
case "scored":
|
|
130
|
+
return {
|
|
131
|
+
icon: figures.tick,
|
|
132
|
+
color: colors.info,
|
|
133
|
+
text: "SCORED ",
|
|
134
|
+
label: "Scored",
|
|
135
|
+
};
|
|
136
|
+
case "timeout":
|
|
137
|
+
return {
|
|
138
|
+
icon: figures.warning,
|
|
139
|
+
color: colors.error,
|
|
140
|
+
text: "TIMEOUT ",
|
|
141
|
+
label: "Timeout",
|
|
142
|
+
};
|
|
143
|
+
// === GENERIC STATES ===
|
|
144
|
+
case "active":
|
|
145
|
+
return {
|
|
146
|
+
icon: figures.tick,
|
|
147
|
+
color: colors.success,
|
|
148
|
+
text: "ACTIVE ",
|
|
149
|
+
label: "Active",
|
|
150
|
+
};
|
|
151
|
+
// === STORAGE OBJECT STATES ===
|
|
152
|
+
case "UPLOADING":
|
|
153
|
+
return {
|
|
154
|
+
icon: figures.arrowUp,
|
|
155
|
+
color: colors.warning,
|
|
156
|
+
text: "UPLOADING ",
|
|
157
|
+
label: "Uploading",
|
|
158
|
+
};
|
|
159
|
+
case "READ_ONLY":
|
|
160
|
+
return {
|
|
161
|
+
icon: figures.tick,
|
|
162
|
+
color: colors.success,
|
|
163
|
+
text: "READ_ONLY ",
|
|
164
|
+
label: "Read Only",
|
|
165
|
+
};
|
|
166
|
+
case "DELETED":
|
|
167
|
+
return {
|
|
168
|
+
icon: figures.cross,
|
|
169
|
+
color: colors.textDim,
|
|
170
|
+
text: "DELETED ",
|
|
171
|
+
label: "Deleted",
|
|
172
|
+
};
|
|
173
|
+
case "ERROR":
|
|
174
|
+
return {
|
|
175
|
+
icon: figures.warning,
|
|
176
|
+
color: colors.error,
|
|
177
|
+
text: "ERROR ",
|
|
178
|
+
label: "Error",
|
|
179
|
+
};
|
|
107
180
|
default:
|
|
108
181
|
// Truncate and pad any unknown status to 10 chars to match column width
|
|
109
182
|
const truncated = status.toUpperCase().slice(0, 10);
|