@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
package/dist/commands/menu.js
CHANGED
|
@@ -4,13 +4,14 @@ import { enterAlternateScreenBuffer, exitAlternateScreenBuffer, clearScreen, } f
|
|
|
4
4
|
import { processUtils } from "../utils/processUtils.js";
|
|
5
5
|
import { Router } from "../router/Router.js";
|
|
6
6
|
import { NavigationProvider } from "../store/navigationStore.js";
|
|
7
|
+
import { BetaFeatureProvider } from "../store/betaFeatureStore.js";
|
|
7
8
|
function AppInner() {
|
|
8
9
|
// NavigationProvider already handles initialScreen and initialParams
|
|
9
10
|
// No need for useEffect here - provider sets state on mount
|
|
10
11
|
return _jsx(Router, {});
|
|
11
12
|
}
|
|
12
13
|
function App({ initialScreen = "menu", focusDevboxId, }) {
|
|
13
|
-
return (_jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }));
|
|
14
|
+
return (_jsx(BetaFeatureProvider, { children: _jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }) }));
|
|
14
15
|
}
|
|
15
16
|
export async function runMainMenu(initialScreen = "menu", focusDevboxId) {
|
|
16
17
|
enterAlternateScreenBuffer();
|
|
@@ -1,9 +1,384 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
4
|
+
import figures from "figures";
|
|
4
5
|
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { Header } from "../../components/Header.js";
|
|
7
|
+
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
8
|
+
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
9
|
+
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
10
|
+
import { Breadcrumb } from "../../components/Breadcrumb.js";
|
|
11
|
+
import { NavigationTips } from "../../components/NavigationTips.js";
|
|
12
|
+
import { Table, createTextColumn } from "../../components/Table.js";
|
|
13
|
+
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
14
|
+
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
15
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
5
16
|
import { output, outputError } from "../../utils/output.js";
|
|
6
|
-
|
|
17
|
+
import { colors } from "../../utils/theme.js";
|
|
18
|
+
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
19
|
+
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
20
|
+
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
21
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
22
|
+
import { useNavigation } from "../../store/navigationStore.js";
|
|
23
|
+
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
24
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
25
|
+
const ListSecretsUI = ({ onBack, onExit, }) => {
|
|
26
|
+
const { exit: inkExit } = useApp();
|
|
27
|
+
const { navigate } = useNavigation();
|
|
28
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
29
|
+
const [showPopup, setShowPopup] = React.useState(false);
|
|
30
|
+
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
31
|
+
const [selectedSecret, setSelectedSecret] = React.useState(null);
|
|
32
|
+
const [executingOperation, setExecutingOperation] = React.useState(null);
|
|
33
|
+
const [operationResult, setOperationResult] = React.useState(null);
|
|
34
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
35
|
+
const [operationLoading, setOperationLoading] = React.useState(false);
|
|
36
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
37
|
+
// Search state
|
|
38
|
+
const search = useListSearch({
|
|
39
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
40
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
41
|
+
});
|
|
42
|
+
// Calculate overhead for viewport height
|
|
43
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
44
|
+
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
45
|
+
overhead,
|
|
46
|
+
minHeight: 5,
|
|
47
|
+
});
|
|
48
|
+
const PAGE_SIZE = viewportHeight;
|
|
49
|
+
// All width constants
|
|
50
|
+
const fixedWidth = 6; // border + padding
|
|
51
|
+
const idWidth = 30;
|
|
52
|
+
const timeWidth = 20;
|
|
53
|
+
// Name width uses remaining space after fixed columns
|
|
54
|
+
const baseWidth = fixedWidth + idWidth + timeWidth;
|
|
55
|
+
const remainingWidth = terminalWidth - baseWidth;
|
|
56
|
+
const nameWidth = Math.min(80, Math.max(15, remainingWidth));
|
|
57
|
+
// Fetch function for pagination hook
|
|
58
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
59
|
+
const client = getClient();
|
|
60
|
+
const pageSecrets = [];
|
|
61
|
+
// Secrets API doesn't support cursor pagination, fetch all and paginate client-side
|
|
62
|
+
const result = await client.secrets.list({ limit: 5000 });
|
|
63
|
+
// Extract data and filter by search if needed
|
|
64
|
+
if (result.secrets && Array.isArray(result.secrets)) {
|
|
65
|
+
let filtered = result.secrets;
|
|
66
|
+
// Client-side search filtering
|
|
67
|
+
if (search.submittedSearchQuery) {
|
|
68
|
+
const query = search.submittedSearchQuery.toLowerCase();
|
|
69
|
+
filtered = filtered.filter((s) => s.name?.toLowerCase().includes(query) ||
|
|
70
|
+
s.id?.toLowerCase().includes(query));
|
|
71
|
+
}
|
|
72
|
+
// Client-side pagination
|
|
73
|
+
const startIdx = params.startingAt
|
|
74
|
+
? filtered.findIndex((s) => s.id === params.startingAt) + 1
|
|
75
|
+
: 0;
|
|
76
|
+
const pageItems = filtered.slice(startIdx, startIdx + params.limit);
|
|
77
|
+
pageItems.forEach((s) => {
|
|
78
|
+
pageSecrets.push({
|
|
79
|
+
id: s.id,
|
|
80
|
+
name: s.name,
|
|
81
|
+
create_time_ms: s.create_time_ms,
|
|
82
|
+
update_time_ms: s.update_time_ms,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
items: pageSecrets,
|
|
87
|
+
hasMore: startIdx + params.limit < filtered.length,
|
|
88
|
+
totalCount: filtered.length,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
items: pageSecrets,
|
|
93
|
+
hasMore: false,
|
|
94
|
+
totalCount: 0,
|
|
95
|
+
};
|
|
96
|
+
}, [search.submittedSearchQuery]);
|
|
97
|
+
// Use the shared pagination hook
|
|
98
|
+
const { items: secrets, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
99
|
+
fetchPage,
|
|
100
|
+
pageSize: PAGE_SIZE,
|
|
101
|
+
getItemId: (secret) => secret.id,
|
|
102
|
+
pollInterval: 10000,
|
|
103
|
+
pollingEnabled: !showPopup &&
|
|
104
|
+
!executingOperation &&
|
|
105
|
+
!showDeleteConfirm &&
|
|
106
|
+
!search.searchMode,
|
|
107
|
+
deps: [PAGE_SIZE, search.submittedSearchQuery],
|
|
108
|
+
});
|
|
109
|
+
// Operations for a specific secret (shown in popup)
|
|
110
|
+
const operations = React.useMemo(() => [
|
|
111
|
+
{
|
|
112
|
+
key: "view_details",
|
|
113
|
+
label: "View Details",
|
|
114
|
+
color: colors.primary,
|
|
115
|
+
icon: figures.pointer,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: "delete",
|
|
119
|
+
label: "Delete Secret",
|
|
120
|
+
color: colors.error,
|
|
121
|
+
icon: figures.cross,
|
|
122
|
+
},
|
|
123
|
+
], []);
|
|
124
|
+
// Build columns
|
|
125
|
+
const columns = React.useMemo(() => [
|
|
126
|
+
createTextColumn("id", "ID", (secret) => secret.id, {
|
|
127
|
+
width: idWidth + 1,
|
|
128
|
+
color: colors.idColor,
|
|
129
|
+
dimColor: false,
|
|
130
|
+
bold: false,
|
|
131
|
+
}),
|
|
132
|
+
createTextColumn("name", "Name", (secret) => secret.name || "", {
|
|
133
|
+
width: nameWidth,
|
|
134
|
+
}),
|
|
135
|
+
createTextColumn("created", "Created", (secret) => secret.create_time_ms ? formatTimeAgo(secret.create_time_ms) : "-", {
|
|
136
|
+
width: timeWidth,
|
|
137
|
+
color: colors.textDim,
|
|
138
|
+
dimColor: false,
|
|
139
|
+
bold: false,
|
|
140
|
+
}),
|
|
141
|
+
], [idWidth, nameWidth, timeWidth]);
|
|
142
|
+
// Handle Ctrl+C to exit
|
|
143
|
+
useExitOnCtrlC();
|
|
144
|
+
// Ensure selected index is within bounds
|
|
145
|
+
React.useEffect(() => {
|
|
146
|
+
if (secrets.length > 0 && selectedIndex >= secrets.length) {
|
|
147
|
+
setSelectedIndex(Math.max(0, secrets.length - 1));
|
|
148
|
+
}
|
|
149
|
+
}, [secrets.length, selectedIndex]);
|
|
150
|
+
const selectedSecretItem = secrets[selectedIndex];
|
|
151
|
+
// Calculate pagination info for display
|
|
152
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
153
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
154
|
+
const endIndex = startIndex + secrets.length;
|
|
155
|
+
const executeOperation = async (secret, operationKey) => {
|
|
156
|
+
const client = getClient();
|
|
157
|
+
if (!secret)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
setOperationLoading(true);
|
|
161
|
+
switch (operationKey) {
|
|
162
|
+
case "delete":
|
|
163
|
+
await client.secrets.delete(secret.name);
|
|
164
|
+
setOperationResult(`Secret "${secret.name}" deleted successfully`);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
setOperationError(err);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
setOperationLoading(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
useInput((input, key) => {
|
|
176
|
+
// Handle search mode input
|
|
177
|
+
if (search.searchMode) {
|
|
178
|
+
if (key.escape) {
|
|
179
|
+
search.cancelSearch();
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Handle operation result display
|
|
184
|
+
if (operationResult || operationError) {
|
|
185
|
+
if (input === "q" || key.escape || key.return) {
|
|
186
|
+
const wasDelete = executingOperation === "delete";
|
|
187
|
+
const hadError = operationError !== null;
|
|
188
|
+
setOperationResult(null);
|
|
189
|
+
setOperationError(null);
|
|
190
|
+
setExecutingOperation(null);
|
|
191
|
+
setSelectedSecret(null);
|
|
192
|
+
// Refresh the list after delete to show updated data
|
|
193
|
+
if (wasDelete && !hadError) {
|
|
194
|
+
setTimeout(() => refresh(), 0);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Handle popup navigation
|
|
200
|
+
if (showPopup) {
|
|
201
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
202
|
+
setSelectedOperation(selectedOperation - 1);
|
|
203
|
+
}
|
|
204
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
205
|
+
setSelectedOperation(selectedOperation + 1);
|
|
206
|
+
}
|
|
207
|
+
else if (key.return) {
|
|
208
|
+
setShowPopup(false);
|
|
209
|
+
const operationKey = operations[selectedOperation].key;
|
|
210
|
+
if (operationKey === "view_details") {
|
|
211
|
+
navigate("secret-detail", {
|
|
212
|
+
secretId: selectedSecretItem.id,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else if (operationKey === "delete") {
|
|
216
|
+
// Show delete confirmation
|
|
217
|
+
setSelectedSecret(selectedSecretItem);
|
|
218
|
+
setShowDeleteConfirm(true);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
setSelectedSecret(selectedSecretItem);
|
|
222
|
+
setExecutingOperation(operationKey);
|
|
223
|
+
// Execute immediately with values passed directly
|
|
224
|
+
executeOperation(selectedSecretItem, operationKey);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (input === "c") {
|
|
228
|
+
// Create hotkey
|
|
229
|
+
setShowPopup(false);
|
|
230
|
+
navigate("secret-create");
|
|
231
|
+
}
|
|
232
|
+
else if (input === "v" && selectedSecretItem) {
|
|
233
|
+
// View details hotkey
|
|
234
|
+
setShowPopup(false);
|
|
235
|
+
navigate("secret-detail", {
|
|
236
|
+
secretId: selectedSecretItem.id,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else if (key.escape || input === "q") {
|
|
240
|
+
setShowPopup(false);
|
|
241
|
+
setSelectedOperation(0);
|
|
242
|
+
}
|
|
243
|
+
else if (input === "d") {
|
|
244
|
+
// Delete hotkey - show confirmation
|
|
245
|
+
setShowPopup(false);
|
|
246
|
+
setSelectedSecret(selectedSecretItem);
|
|
247
|
+
setShowDeleteConfirm(true);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const pageSecrets = secrets.length;
|
|
252
|
+
// Handle list view navigation
|
|
253
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
254
|
+
setSelectedIndex(selectedIndex - 1);
|
|
255
|
+
}
|
|
256
|
+
else if (key.downArrow && selectedIndex < pageSecrets - 1) {
|
|
257
|
+
setSelectedIndex(selectedIndex + 1);
|
|
258
|
+
}
|
|
259
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
260
|
+
!loading &&
|
|
261
|
+
!navigating &&
|
|
262
|
+
hasMore) {
|
|
263
|
+
nextPage();
|
|
264
|
+
setSelectedIndex(0);
|
|
265
|
+
}
|
|
266
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
267
|
+
!loading &&
|
|
268
|
+
!navigating &&
|
|
269
|
+
hasPrev) {
|
|
270
|
+
prevPage();
|
|
271
|
+
setSelectedIndex(0);
|
|
272
|
+
}
|
|
273
|
+
else if (key.return && selectedSecretItem) {
|
|
274
|
+
// Enter key navigates to detail view
|
|
275
|
+
navigate("secret-detail", {
|
|
276
|
+
secretId: selectedSecretItem.id,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else if (input === "a") {
|
|
280
|
+
setShowPopup(true);
|
|
281
|
+
setSelectedOperation(0);
|
|
282
|
+
}
|
|
283
|
+
else if (input === "c") {
|
|
284
|
+
// Create shortcut
|
|
285
|
+
navigate("secret-create");
|
|
286
|
+
}
|
|
287
|
+
else if (input === "/") {
|
|
288
|
+
search.enterSearchMode();
|
|
289
|
+
}
|
|
290
|
+
else if (key.escape) {
|
|
291
|
+
if (search.handleEscape()) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (onBack) {
|
|
295
|
+
onBack();
|
|
296
|
+
}
|
|
297
|
+
else if (onExit) {
|
|
298
|
+
onExit();
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
inkExit();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// Delete confirmation
|
|
306
|
+
if (showDeleteConfirm && selectedSecret) {
|
|
307
|
+
return (_jsx(ConfirmationPrompt, { title: "Delete Secret", message: `Are you sure you want to delete "${selectedSecret.name}"?`, details: "This action cannot be undone. Any devboxes using this secret will no longer have access to it.", breadcrumbItems: [
|
|
308
|
+
{ label: "Settings" },
|
|
309
|
+
{ label: "Secrets" },
|
|
310
|
+
{ label: selectedSecret.name || selectedSecret.id },
|
|
311
|
+
{ label: "Delete", active: true },
|
|
312
|
+
], onConfirm: () => {
|
|
313
|
+
setShowDeleteConfirm(false);
|
|
314
|
+
setExecutingOperation("delete");
|
|
315
|
+
executeOperation(selectedSecret, "delete");
|
|
316
|
+
}, onCancel: () => {
|
|
317
|
+
setShowDeleteConfirm(false);
|
|
318
|
+
setSelectedSecret(null);
|
|
319
|
+
} }));
|
|
320
|
+
}
|
|
321
|
+
// Operation result display
|
|
322
|
+
if (operationResult || operationError) {
|
|
323
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
324
|
+
"Operation";
|
|
325
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
326
|
+
{ label: "Settings" },
|
|
327
|
+
{ label: "Secrets" },
|
|
328
|
+
{
|
|
329
|
+
label: selectedSecret?.name || selectedSecret?.id || "Secret",
|
|
330
|
+
},
|
|
331
|
+
{ label: operationLabel, active: true },
|
|
332
|
+
] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "Continue" }] })] }));
|
|
333
|
+
}
|
|
334
|
+
// Operation loading state
|
|
335
|
+
if (operationLoading && selectedSecret) {
|
|
336
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
337
|
+
"Operation";
|
|
338
|
+
const messages = {
|
|
339
|
+
delete: "Deleting secret...",
|
|
340
|
+
};
|
|
341
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
342
|
+
{ label: "Settings" },
|
|
343
|
+
{ label: "Secrets" },
|
|
344
|
+
{ label: selectedSecret.name || selectedSecret.id },
|
|
345
|
+
{ label: operationLabel, active: true },
|
|
346
|
+
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
|
|
347
|
+
}
|
|
348
|
+
// Loading state
|
|
349
|
+
if (loading && secrets.length === 0) {
|
|
350
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(SpinnerComponent, { message: "Loading secrets..." })] }));
|
|
351
|
+
}
|
|
352
|
+
// Error state
|
|
353
|
+
if (error) {
|
|
354
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list secrets", error: error })] }));
|
|
355
|
+
}
|
|
356
|
+
// Main list view
|
|
357
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search secrets..." }), !showPopup && (_jsx(Table, { data: secrets, keyExtractor: (secret) => secret.id, selectedIndex: selectedIndex, title: `secrets[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No secrets found. Press [c] to create one."] }) })), !showPopup && (_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"] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), navigating ? (_jsxs(Text, { color: colors.warning, children: [figures.pointer, " Loading page ", currentPage + 1, "..."] })) : (_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Page ", currentPage + 1, " of ", totalPages] }))] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Showing ", startIndex + 1, "-", endIndex, " of ", totalCount] }), search.submittedSearchQuery && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.warning, children: ["Filtered: \"", search.submittedSearchQuery, "\""] })] }))] })), showPopup && selectedSecretItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedSecretItem, operations: operations.map((op) => ({
|
|
358
|
+
key: op.key,
|
|
359
|
+
label: op.label,
|
|
360
|
+
color: op.color,
|
|
361
|
+
icon: op.icon,
|
|
362
|
+
shortcut: op.key === "view_details"
|
|
363
|
+
? "v"
|
|
364
|
+
: op.key === "delete"
|
|
365
|
+
? "d"
|
|
366
|
+
: "",
|
|
367
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
368
|
+
{
|
|
369
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
370
|
+
label: "Page",
|
|
371
|
+
condition: hasMore || hasPrev,
|
|
372
|
+
},
|
|
373
|
+
{ key: "Enter", label: "Details" },
|
|
374
|
+
{ key: "c", label: "Create" },
|
|
375
|
+
{ key: "a", label: "Actions" },
|
|
376
|
+
{ key: "/", label: "Search" },
|
|
377
|
+
{ key: "Esc", label: "Back" },
|
|
378
|
+
] })] }));
|
|
379
|
+
};
|
|
380
|
+
// Export the UI component for use in the main menu
|
|
381
|
+
export { ListSecretsUI };
|
|
7
382
|
export async function listSecrets(options = {}) {
|
|
8
383
|
try {
|
|
9
384
|
const client = getClient();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BenchmarkMenu - Sub-menu for benchmark-related resources
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
7
|
+
import figures from "figures";
|
|
8
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
9
|
+
import { NavigationTips } from "./NavigationTips.js";
|
|
10
|
+
import { colors } from "../utils/theme.js";
|
|
11
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
12
|
+
const benchmarkMenuItems = [
|
|
13
|
+
{
|
|
14
|
+
key: "benchmark-runs",
|
|
15
|
+
label: "Benchmark Runs",
|
|
16
|
+
description: "View and manage benchmark executions",
|
|
17
|
+
icon: "▶",
|
|
18
|
+
color: colors.success,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: "scenario-runs",
|
|
22
|
+
label: "Scenario Runs",
|
|
23
|
+
description: "View individual scenario results",
|
|
24
|
+
icon: "◈",
|
|
25
|
+
color: colors.info,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
export const BenchmarkMenu = ({ onSelect, onBack }) => {
|
|
29
|
+
const { exit } = useApp();
|
|
30
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
31
|
+
const { stdout } = useStdout();
|
|
32
|
+
// Get terminal dimensions for responsive layout
|
|
33
|
+
const getTerminalDimensions = React.useCallback(() => {
|
|
34
|
+
return {
|
|
35
|
+
height: stdout?.rows && stdout.rows > 0 ? stdout.rows : 20,
|
|
36
|
+
width: stdout?.columns && stdout.columns > 0 ? stdout.columns : 80,
|
|
37
|
+
};
|
|
38
|
+
}, [stdout]);
|
|
39
|
+
const [terminalDimensions, setTerminalDimensions] = React.useState(getTerminalDimensions);
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
setTerminalDimensions(getTerminalDimensions());
|
|
42
|
+
if (!stdout)
|
|
43
|
+
return;
|
|
44
|
+
const handleResize = () => {
|
|
45
|
+
setTerminalDimensions(getTerminalDimensions());
|
|
46
|
+
};
|
|
47
|
+
stdout.on("resize", handleResize);
|
|
48
|
+
return () => {
|
|
49
|
+
stdout.off("resize", handleResize);
|
|
50
|
+
};
|
|
51
|
+
}, [stdout, getTerminalDimensions]);
|
|
52
|
+
const terminalWidth = terminalDimensions.width;
|
|
53
|
+
const isNarrow = terminalWidth < 70;
|
|
54
|
+
// Handle Ctrl+C to exit
|
|
55
|
+
useExitOnCtrlC();
|
|
56
|
+
useInput((input, key) => {
|
|
57
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
58
|
+
setSelectedIndex(selectedIndex - 1);
|
|
59
|
+
}
|
|
60
|
+
else if (key.downArrow && selectedIndex < benchmarkMenuItems.length - 1) {
|
|
61
|
+
setSelectedIndex(selectedIndex + 1);
|
|
62
|
+
}
|
|
63
|
+
else if (key.return) {
|
|
64
|
+
onSelect(benchmarkMenuItems[selectedIndex].key);
|
|
65
|
+
}
|
|
66
|
+
else if (key.escape) {
|
|
67
|
+
onBack();
|
|
68
|
+
}
|
|
69
|
+
else if (input === "b" || input === "1") {
|
|
70
|
+
onSelect("benchmark-runs");
|
|
71
|
+
}
|
|
72
|
+
else if (input === "s" || input === "2") {
|
|
73
|
+
onSelect("scenario-runs");
|
|
74
|
+
}
|
|
75
|
+
else if (input === "q") {
|
|
76
|
+
exit();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home" }, { label: "Benchmarks", active: true }] }), _jsxs(Box, { paddingX: 2, marginBottom: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "Benchmarks" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow ? "" : " • Performance testing and evaluation" })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: benchmarkMenuItems.map((item, index) => {
|
|
80
|
+
const isSelected = index === selectedIndex;
|
|
81
|
+
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));
|
|
82
|
+
}) }), _jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
|
|
83
|
+
{ key: "1-2", label: "Quick select" },
|
|
84
|
+
{ key: "Enter", label: "Select" },
|
|
85
|
+
{ key: "Esc", label: "Back" },
|
|
86
|
+
{ key: "q", label: "Quit" },
|
|
87
|
+
] })] }));
|
|
88
|
+
};
|