@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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ScenarioRunListScreen - List view for scenario runs
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
7
|
+
import figures from "figures";
|
|
8
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
9
|
+
import { SpinnerComponent } from "../components/Spinner.js";
|
|
10
|
+
import { ErrorMessage } from "../components/ErrorMessage.js";
|
|
11
|
+
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
12
|
+
import { NavigationTips } from "../components/NavigationTips.js";
|
|
13
|
+
import { Table, createTextColumn, createComponentColumn, } from "../components/Table.js";
|
|
14
|
+
import { ActionsPopup } from "../components/ActionsPopup.js";
|
|
15
|
+
import { formatTimeAgo } from "../components/ResourceListView.js";
|
|
16
|
+
import { SearchBar } from "../components/SearchBar.js";
|
|
17
|
+
import { getStatusDisplay } from "../components/StatusBadge.js";
|
|
18
|
+
import { colors } from "../utils/theme.js";
|
|
19
|
+
import { useViewportHeight } from "../hooks/useViewportHeight.js";
|
|
20
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
21
|
+
import { useCursorPagination } from "../hooks/useCursorPagination.js";
|
|
22
|
+
import { useListSearch } from "../hooks/useListSearch.js";
|
|
23
|
+
import { listScenarioRuns } from "../services/benchmarkService.js";
|
|
24
|
+
export function ScenarioRunListScreen({ benchmarkRunId, }) {
|
|
25
|
+
const { exit: inkExit } = useApp();
|
|
26
|
+
const { navigate, goBack } = useNavigation();
|
|
27
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
28
|
+
const [showPopup, setShowPopup] = React.useState(false);
|
|
29
|
+
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
30
|
+
// Search state
|
|
31
|
+
const search = useListSearch({
|
|
32
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
33
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
34
|
+
});
|
|
35
|
+
// Calculate overhead for viewport height
|
|
36
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
37
|
+
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
38
|
+
overhead,
|
|
39
|
+
minHeight: 5,
|
|
40
|
+
});
|
|
41
|
+
const PAGE_SIZE = viewportHeight;
|
|
42
|
+
// Column widths
|
|
43
|
+
const fixedWidth = 6;
|
|
44
|
+
const idWidth = 25;
|
|
45
|
+
const statusWidth = 12;
|
|
46
|
+
const scoreWidth = 8;
|
|
47
|
+
const timeWidth = 18;
|
|
48
|
+
const baseWidth = fixedWidth + idWidth + statusWidth + scoreWidth + timeWidth;
|
|
49
|
+
const remainingWidth = terminalWidth - baseWidth;
|
|
50
|
+
const nameWidth = Math.min(60, Math.max(15, remainingWidth));
|
|
51
|
+
// Fetch function for pagination hook
|
|
52
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
53
|
+
const result = await listScenarioRuns({
|
|
54
|
+
limit: params.limit,
|
|
55
|
+
startingAfter: params.startingAt,
|
|
56
|
+
benchmarkRunId,
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
items: result.scenarioRuns,
|
|
60
|
+
hasMore: result.hasMore,
|
|
61
|
+
totalCount: result.totalCount,
|
|
62
|
+
};
|
|
63
|
+
}, [benchmarkRunId]);
|
|
64
|
+
// Use the shared pagination hook
|
|
65
|
+
const { items: scenarioRuns, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
66
|
+
fetchPage,
|
|
67
|
+
pageSize: PAGE_SIZE,
|
|
68
|
+
getItemId: (run) => run.id,
|
|
69
|
+
pollInterval: 5000,
|
|
70
|
+
pollingEnabled: !showPopup && !search.searchMode,
|
|
71
|
+
deps: [PAGE_SIZE, benchmarkRunId],
|
|
72
|
+
});
|
|
73
|
+
// Operations for scenario runs
|
|
74
|
+
const operations = React.useMemo(() => [
|
|
75
|
+
{
|
|
76
|
+
key: "view_details",
|
|
77
|
+
label: "View Details",
|
|
78
|
+
color: colors.primary,
|
|
79
|
+
icon: figures.pointer,
|
|
80
|
+
},
|
|
81
|
+
], []);
|
|
82
|
+
// Build columns
|
|
83
|
+
const columns = React.useMemo(() => [
|
|
84
|
+
createTextColumn("id", "ID", (run) => run.id, {
|
|
85
|
+
width: idWidth + 1,
|
|
86
|
+
color: colors.idColor,
|
|
87
|
+
dimColor: false,
|
|
88
|
+
bold: false,
|
|
89
|
+
}),
|
|
90
|
+
createTextColumn("name", "Name", (run) => run.name || "", {
|
|
91
|
+
width: nameWidth,
|
|
92
|
+
}),
|
|
93
|
+
createComponentColumn("status", "Status", (run, _index, isSelected) => {
|
|
94
|
+
const statusDisplay = getStatusDisplay(run.state);
|
|
95
|
+
const text = statusDisplay.text
|
|
96
|
+
.slice(0, statusWidth)
|
|
97
|
+
.padEnd(statusWidth, " ");
|
|
98
|
+
return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
|
|
99
|
+
}, { width: statusWidth }),
|
|
100
|
+
createTextColumn("score", "Score", (run) => {
|
|
101
|
+
const score = run.scoring_contract_result?.score;
|
|
102
|
+
return score !== undefined ? String(score) : "";
|
|
103
|
+
}, {
|
|
104
|
+
width: scoreWidth,
|
|
105
|
+
color: colors.info,
|
|
106
|
+
}),
|
|
107
|
+
createTextColumn("created", "Created", (run) => run.start_time_ms ? formatTimeAgo(run.start_time_ms) : "", {
|
|
108
|
+
width: timeWidth,
|
|
109
|
+
color: colors.textDim,
|
|
110
|
+
dimColor: false,
|
|
111
|
+
bold: false,
|
|
112
|
+
}),
|
|
113
|
+
], [idWidth, nameWidth, statusWidth, scoreWidth, timeWidth]);
|
|
114
|
+
// Handle Ctrl+C to exit
|
|
115
|
+
useExitOnCtrlC();
|
|
116
|
+
// Ensure selected index is within bounds
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
if (scenarioRuns.length > 0 && selectedIndex >= scenarioRuns.length) {
|
|
119
|
+
setSelectedIndex(Math.max(0, scenarioRuns.length - 1));
|
|
120
|
+
}
|
|
121
|
+
}, [scenarioRuns.length, selectedIndex]);
|
|
122
|
+
const selectedRun = scenarioRuns[selectedIndex];
|
|
123
|
+
// Calculate pagination info for display
|
|
124
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
125
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
126
|
+
const endIndex = startIndex + scenarioRuns.length;
|
|
127
|
+
useInput((input, key) => {
|
|
128
|
+
// Handle search mode input
|
|
129
|
+
if (search.searchMode) {
|
|
130
|
+
if (key.escape) {
|
|
131
|
+
search.cancelSearch();
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Handle popup navigation
|
|
136
|
+
if (showPopup) {
|
|
137
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
138
|
+
setSelectedOperation(selectedOperation - 1);
|
|
139
|
+
}
|
|
140
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
141
|
+
setSelectedOperation(selectedOperation + 1);
|
|
142
|
+
}
|
|
143
|
+
else if (key.return) {
|
|
144
|
+
setShowPopup(false);
|
|
145
|
+
const operationKey = operations[selectedOperation].key;
|
|
146
|
+
if (operationKey === "view_details") {
|
|
147
|
+
navigate("scenario-run-detail", {
|
|
148
|
+
scenarioRunId: selectedRun.id,
|
|
149
|
+
benchmarkRunId,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (input === "v" && selectedRun) {
|
|
154
|
+
setShowPopup(false);
|
|
155
|
+
navigate("scenario-run-detail", {
|
|
156
|
+
scenarioRunId: selectedRun.id,
|
|
157
|
+
benchmarkRunId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else if (key.escape || input === "q") {
|
|
161
|
+
setShowPopup(false);
|
|
162
|
+
setSelectedOperation(0);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const pageRuns = scenarioRuns.length;
|
|
167
|
+
// Handle list view navigation
|
|
168
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
169
|
+
setSelectedIndex(selectedIndex - 1);
|
|
170
|
+
}
|
|
171
|
+
else if (key.downArrow && selectedIndex < pageRuns - 1) {
|
|
172
|
+
setSelectedIndex(selectedIndex + 1);
|
|
173
|
+
}
|
|
174
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
175
|
+
!loading &&
|
|
176
|
+
!navigating &&
|
|
177
|
+
hasMore) {
|
|
178
|
+
nextPage();
|
|
179
|
+
setSelectedIndex(0);
|
|
180
|
+
}
|
|
181
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
182
|
+
!loading &&
|
|
183
|
+
!navigating &&
|
|
184
|
+
hasPrev) {
|
|
185
|
+
prevPage();
|
|
186
|
+
setSelectedIndex(0);
|
|
187
|
+
}
|
|
188
|
+
else if (key.return && selectedRun) {
|
|
189
|
+
navigate("scenario-run-detail", {
|
|
190
|
+
scenarioRunId: selectedRun.id,
|
|
191
|
+
benchmarkRunId,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (input === "a" && selectedRun) {
|
|
195
|
+
setShowPopup(true);
|
|
196
|
+
setSelectedOperation(0);
|
|
197
|
+
}
|
|
198
|
+
else if (input === "/") {
|
|
199
|
+
search.enterSearchMode();
|
|
200
|
+
}
|
|
201
|
+
else if (key.escape) {
|
|
202
|
+
if (search.handleEscape()) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
goBack();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
// Build breadcrumb items
|
|
209
|
+
const breadcrumbItems = [
|
|
210
|
+
{ label: "Home" },
|
|
211
|
+
{ label: "Benchmarks" },
|
|
212
|
+
];
|
|
213
|
+
if (benchmarkRunId) {
|
|
214
|
+
breadcrumbItems.push({
|
|
215
|
+
label: `Run: ${benchmarkRunId.substring(0, 8)}...`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
breadcrumbItems.push({ label: "Scenario Runs", active: true });
|
|
219
|
+
// Loading state
|
|
220
|
+
if (loading && scenarioRuns.length === 0) {
|
|
221
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SpinnerComponent, { message: "Loading scenario runs..." })] }));
|
|
222
|
+
}
|
|
223
|
+
// Error state
|
|
224
|
+
if (error) {
|
|
225
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(ErrorMessage, { message: "Failed to list scenario runs", error: error })] }));
|
|
226
|
+
}
|
|
227
|
+
// Main list view
|
|
228
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search scenario runs..." }), !showPopup && (_jsx(Table, { data: scenarioRuns, keyExtractor: (run) => run.id, selectedIndex: selectedIndex, title: `scenario_runs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No scenario runs found"] }) })), !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] }), benchmarkRunId && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.info, children: ["Run: ", benchmarkRunId.substring(0, 8), "..."] })] }))] })), showPopup && selectedRun && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedRun, operations: operations.map((op) => ({
|
|
229
|
+
key: op.key,
|
|
230
|
+
label: op.label,
|
|
231
|
+
color: op.color,
|
|
232
|
+
icon: op.icon,
|
|
233
|
+
shortcut: op.key === "view_details" ? "v" : "",
|
|
234
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
235
|
+
{
|
|
236
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
237
|
+
label: "Page",
|
|
238
|
+
condition: hasMore || hasPrev,
|
|
239
|
+
},
|
|
240
|
+
{ key: "Enter", label: "Details" },
|
|
241
|
+
{ key: "a", label: "Actions" },
|
|
242
|
+
{ key: "/", label: "Search" },
|
|
243
|
+
{ key: "Esc", label: "Back" },
|
|
244
|
+
] })] }));
|
|
245
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
3
|
+
import { SecretCreatePage } from "../components/SecretCreatePage.js";
|
|
4
|
+
export function SecretCreateScreen() {
|
|
5
|
+
const { goBack, navigate } = useNavigation();
|
|
6
|
+
return (_jsx(SecretCreatePage, { onBack: goBack, onCreate: (secret) => navigate("secret-detail", { secretId: secret.id }) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* SecretDetailScreen - Detail page for secrets
|
|
4
|
+
* Uses the generic ResourceDetailPage component
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Text } from "ink";
|
|
8
|
+
import figures from "figures";
|
|
9
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
10
|
+
import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
|
|
11
|
+
import { getClient } from "../utils/client.js";
|
|
12
|
+
import { SpinnerComponent } from "../components/Spinner.js";
|
|
13
|
+
import { ErrorMessage } from "../components/ErrorMessage.js";
|
|
14
|
+
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
15
|
+
import { ConfirmationPrompt } from "../components/ConfirmationPrompt.js";
|
|
16
|
+
import { colors } from "../utils/theme.js";
|
|
17
|
+
export function SecretDetailScreen({ secretId }) {
|
|
18
|
+
const { goBack } = useNavigation();
|
|
19
|
+
const [loading, setLoading] = React.useState(false);
|
|
20
|
+
const [error, setError] = React.useState(null);
|
|
21
|
+
const [secret, setSecret] = React.useState(null);
|
|
22
|
+
const [deleting, setDeleting] = React.useState(false);
|
|
23
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
24
|
+
// Fetch secret from API
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (secretId && !loading && !secret) {
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError(null);
|
|
29
|
+
const client = getClient();
|
|
30
|
+
// Secrets API doesn't have a direct get by ID, so we list all and find
|
|
31
|
+
client.secrets
|
|
32
|
+
.list({ limit: 5000 })
|
|
33
|
+
.then((result) => {
|
|
34
|
+
const found = result.secrets?.find((s) => s.id === secretId || s.name === secretId);
|
|
35
|
+
if (found) {
|
|
36
|
+
setSecret(found);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
setError(new Error("Secret not found"));
|
|
40
|
+
}
|
|
41
|
+
setLoading(false);
|
|
42
|
+
})
|
|
43
|
+
.catch((err) => {
|
|
44
|
+
setError(err);
|
|
45
|
+
setLoading(false);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}, [secretId, loading, secret]);
|
|
49
|
+
// Show loading state while fetching or before fetch starts
|
|
50
|
+
if (!secret && secretId && !error) {
|
|
51
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
52
|
+
{ label: "Settings" },
|
|
53
|
+
{ label: "Secrets" },
|
|
54
|
+
{ label: "Loading...", active: true },
|
|
55
|
+
] }), _jsx(SpinnerComponent, { message: "Loading secret details..." })] }));
|
|
56
|
+
}
|
|
57
|
+
// Show error state if fetch failed
|
|
58
|
+
if (error && !secret) {
|
|
59
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
60
|
+
{ label: "Settings" },
|
|
61
|
+
{ label: "Secrets" },
|
|
62
|
+
{ label: "Error", active: true },
|
|
63
|
+
] }), _jsx(ErrorMessage, { message: "Failed to load secret details", error: error })] }));
|
|
64
|
+
}
|
|
65
|
+
// Show error if no secret found
|
|
66
|
+
if (!secret) {
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
68
|
+
{ label: "Settings" },
|
|
69
|
+
{ label: "Secrets" },
|
|
70
|
+
{ label: "Not Found", active: true },
|
|
71
|
+
] }), _jsx(ErrorMessage, { message: `Secret ${secretId || "unknown"} not found`, error: new Error("Secret not found") })] }));
|
|
72
|
+
}
|
|
73
|
+
// Build detail sections
|
|
74
|
+
const detailSections = [];
|
|
75
|
+
// Basic details section
|
|
76
|
+
const basicFields = [];
|
|
77
|
+
basicFields.push({
|
|
78
|
+
label: "Name",
|
|
79
|
+
value: secret.name,
|
|
80
|
+
});
|
|
81
|
+
if (secret.create_time_ms) {
|
|
82
|
+
basicFields.push({
|
|
83
|
+
label: "Created",
|
|
84
|
+
value: formatTimestamp(secret.create_time_ms),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (secret.update_time_ms) {
|
|
88
|
+
basicFields.push({
|
|
89
|
+
label: "Last Updated",
|
|
90
|
+
value: formatTimestamp(secret.update_time_ms),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
detailSections.push({
|
|
94
|
+
title: "Details",
|
|
95
|
+
icon: figures.squareSmallFilled,
|
|
96
|
+
color: colors.warning,
|
|
97
|
+
fields: basicFields,
|
|
98
|
+
});
|
|
99
|
+
// Security notice section
|
|
100
|
+
detailSections.push({
|
|
101
|
+
title: "Security",
|
|
102
|
+
icon: figures.warning,
|
|
103
|
+
color: colors.info,
|
|
104
|
+
fields: [
|
|
105
|
+
{
|
|
106
|
+
label: "Value",
|
|
107
|
+
value: (_jsx(Text, { color: colors.textDim, dimColor: true, children: "Secret values are never displayed for security reasons" })),
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
// Operations available for secrets (only delete - secrets are immutable)
|
|
112
|
+
const operations = [
|
|
113
|
+
{
|
|
114
|
+
key: "delete",
|
|
115
|
+
label: "Delete Secret",
|
|
116
|
+
color: colors.error,
|
|
117
|
+
icon: figures.cross,
|
|
118
|
+
shortcut: "d",
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
// Handle operation selection
|
|
122
|
+
const handleOperation = async (operation, _resource) => {
|
|
123
|
+
switch (operation) {
|
|
124
|
+
case "delete":
|
|
125
|
+
// Show confirmation dialog
|
|
126
|
+
setShowDeleteConfirm(true);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
// Execute delete after confirmation
|
|
131
|
+
const executeDelete = async () => {
|
|
132
|
+
if (!secret)
|
|
133
|
+
return;
|
|
134
|
+
setShowDeleteConfirm(false);
|
|
135
|
+
setDeleting(true);
|
|
136
|
+
try {
|
|
137
|
+
const client = getClient();
|
|
138
|
+
await client.secrets.delete(secret.name);
|
|
139
|
+
goBack();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
setError(err);
|
|
143
|
+
setDeleting(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
// Build detailed info lines for full details view
|
|
147
|
+
const buildDetailLines = (s) => {
|
|
148
|
+
const lines = [];
|
|
149
|
+
// Core Information
|
|
150
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Secret Details" }, "core-title"));
|
|
151
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", s.id] }, "core-id"));
|
|
152
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", s.name] }, "core-name"));
|
|
153
|
+
if (s.create_time_ms) {
|
|
154
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Created: ", new Date(s.create_time_ms).toLocaleString()] }, "core-created"));
|
|
155
|
+
}
|
|
156
|
+
if (s.update_time_ms) {
|
|
157
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Last Updated: ", new Date(s.update_time_ms).toLocaleString()] }, "core-updated"));
|
|
158
|
+
}
|
|
159
|
+
lines.push(_jsx(Text, { children: " " }, "core-space"));
|
|
160
|
+
// Security Notice
|
|
161
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Security Notice" }, "security-title"));
|
|
162
|
+
lines.push(_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "Secret values are write-only and cannot be retrieved."] }, "security-notice"));
|
|
163
|
+
lines.push(_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "To change a secret, delete it and create a new one with the same name."] }, "security-notice2"));
|
|
164
|
+
lines.push(_jsx(Text, { children: " " }, "security-space"));
|
|
165
|
+
// Raw JSON (without value)
|
|
166
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
|
|
167
|
+
const jsonObj = {
|
|
168
|
+
id: s.id,
|
|
169
|
+
name: s.name,
|
|
170
|
+
create_time_ms: s.create_time_ms,
|
|
171
|
+
update_time_ms: s.update_time_ms,
|
|
172
|
+
};
|
|
173
|
+
const jsonLines = JSON.stringify(jsonObj, null, 2).split("\n");
|
|
174
|
+
jsonLines.forEach((line, idx) => {
|
|
175
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
|
|
176
|
+
});
|
|
177
|
+
return lines;
|
|
178
|
+
};
|
|
179
|
+
// Show delete confirmation
|
|
180
|
+
if (showDeleteConfirm && secret) {
|
|
181
|
+
return (_jsx(ConfirmationPrompt, { title: "Delete Secret", message: `Are you sure you want to delete "${secret.name}"?`, details: "This action cannot be undone. Any devboxes using this secret will no longer have access to it.", breadcrumbItems: [
|
|
182
|
+
{ label: "Settings" },
|
|
183
|
+
{ label: "Secrets" },
|
|
184
|
+
{ label: secret.name || secret.id },
|
|
185
|
+
{ label: "Delete", active: true },
|
|
186
|
+
], onConfirm: executeDelete, onCancel: () => setShowDeleteConfirm(false) }));
|
|
187
|
+
}
|
|
188
|
+
// Show deleting state
|
|
189
|
+
if (deleting) {
|
|
190
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
191
|
+
{ label: "Settings" },
|
|
192
|
+
{ label: "Secrets" },
|
|
193
|
+
{ label: secret.name || secret.id },
|
|
194
|
+
{ label: "Deleting...", active: true },
|
|
195
|
+
] }), _jsx(SpinnerComponent, { message: "Deleting secret..." })] }));
|
|
196
|
+
}
|
|
197
|
+
return (_jsx(ResourceDetailPage, { resource: secret, resourceType: "Secrets", breadcrumbPrefix: [{ label: "Settings" }], getDisplayName: (s) => s.name || s.id, getId: (s) => s.id, getStatus: () => "active", detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines }));
|
|
198
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
3
|
+
import { ListSecretsUI } from "../commands/secret/list.js";
|
|
4
|
+
export function SecretListScreen() {
|
|
5
|
+
const { goBack } = useNavigation();
|
|
6
|
+
return _jsx(ListSecretsUI, { onBack: goBack });
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
3
|
+
import { SettingsMenu } from "../components/SettingsMenu.js";
|
|
4
|
+
export function SettingsMenuScreen() {
|
|
5
|
+
const { navigate, goBack } = useNavigation();
|
|
6
|
+
const handleSelect = (key) => {
|
|
7
|
+
switch (key) {
|
|
8
|
+
case "network-policies":
|
|
9
|
+
navigate("network-policy-list");
|
|
10
|
+
break;
|
|
11
|
+
case "secrets":
|
|
12
|
+
navigate("secret-list");
|
|
13
|
+
break;
|
|
14
|
+
default:
|
|
15
|
+
// Fallback for any other screen names
|
|
16
|
+
navigate(key);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const handleBack = () => {
|
|
20
|
+
goBack();
|
|
21
|
+
};
|
|
22
|
+
return _jsx(SettingsMenu, { onSelect: handleSelect, onBack: handleBack });
|
|
23
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark Service - Handles all benchmark-related API calls
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../utils/client.js";
|
|
5
|
+
/**
|
|
6
|
+
* List benchmark runs with pagination
|
|
7
|
+
*/
|
|
8
|
+
export async function listBenchmarkRuns(options) {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const queryParams = {
|
|
11
|
+
limit: options.limit,
|
|
12
|
+
};
|
|
13
|
+
if (options.startingAfter) {
|
|
14
|
+
queryParams.starting_after = options.startingAfter;
|
|
15
|
+
}
|
|
16
|
+
const page = await client.benchmarkRuns.list(queryParams);
|
|
17
|
+
const benchmarkRuns = page.runs || [];
|
|
18
|
+
return {
|
|
19
|
+
benchmarkRuns,
|
|
20
|
+
totalCount: page.total_count || benchmarkRuns.length,
|
|
21
|
+
hasMore: page.has_more || false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get benchmark run by ID
|
|
26
|
+
*/
|
|
27
|
+
export async function getBenchmarkRun(id) {
|
|
28
|
+
const client = getClient();
|
|
29
|
+
return client.benchmarkRuns.retrieve(id);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* List scenario runs with pagination
|
|
33
|
+
*/
|
|
34
|
+
export async function listScenarioRuns(options) {
|
|
35
|
+
const client = getClient();
|
|
36
|
+
// If we have a benchmark run ID, use the dedicated endpoint
|
|
37
|
+
if (options.benchmarkRunId) {
|
|
38
|
+
const queryParams = {
|
|
39
|
+
limit: options.limit,
|
|
40
|
+
};
|
|
41
|
+
if (options.startingAfter) {
|
|
42
|
+
queryParams.starting_after = options.startingAfter;
|
|
43
|
+
}
|
|
44
|
+
const page = await client.benchmarkRuns.listScenarioRuns(options.benchmarkRunId, queryParams);
|
|
45
|
+
const scenarioRuns = page.runs || [];
|
|
46
|
+
return {
|
|
47
|
+
scenarioRuns,
|
|
48
|
+
totalCount: page.total_count || scenarioRuns.length,
|
|
49
|
+
hasMore: page.has_more || false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Otherwise, list all scenario runs
|
|
53
|
+
const queryParams = {
|
|
54
|
+
limit: options.limit,
|
|
55
|
+
};
|
|
56
|
+
if (options.startingAfter) {
|
|
57
|
+
queryParams.starting_after = options.startingAfter;
|
|
58
|
+
}
|
|
59
|
+
const page = await client.scenarios.runs.list(queryParams);
|
|
60
|
+
const scenarioRuns = page.runs || [];
|
|
61
|
+
return {
|
|
62
|
+
scenarioRuns,
|
|
63
|
+
totalCount: page.total_count || scenarioRuns.length,
|
|
64
|
+
hasMore: page.has_more || false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get scenario run by ID
|
|
69
|
+
*/
|
|
70
|
+
export async function getScenarioRun(id) {
|
|
71
|
+
const client = getClient();
|
|
72
|
+
return client.scenarios.runs.retrieve(id);
|
|
73
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark Store - Manages benchmark run and scenario run state
|
|
3
|
+
*/
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
const MAX_CACHE_SIZE = 10;
|
|
6
|
+
export const useBenchmarkStore = create((set, get) => ({
|
|
7
|
+
// Initial benchmark run state
|
|
8
|
+
benchmarkRuns: [],
|
|
9
|
+
benchmarkRunsLoading: false,
|
|
10
|
+
benchmarkRunsError: null,
|
|
11
|
+
benchmarkRunsTotalCount: 0,
|
|
12
|
+
benchmarkRunsHasMore: false,
|
|
13
|
+
benchmarkRunsCurrentPage: 0,
|
|
14
|
+
// Initial scenario run state
|
|
15
|
+
scenarioRuns: [],
|
|
16
|
+
scenarioRunsLoading: false,
|
|
17
|
+
scenarioRunsError: null,
|
|
18
|
+
scenarioRunsTotalCount: 0,
|
|
19
|
+
scenarioRunsHasMore: false,
|
|
20
|
+
scenarioRunsCurrentPage: 0,
|
|
21
|
+
// Filters
|
|
22
|
+
benchmarkRunIdFilter: undefined,
|
|
23
|
+
// Selection
|
|
24
|
+
selectedBenchmarkRunIndex: 0,
|
|
25
|
+
selectedScenarioRunIndex: 0,
|
|
26
|
+
// Caches
|
|
27
|
+
benchmarkRunPageCache: new Map(),
|
|
28
|
+
scenarioRunPageCache: new Map(),
|
|
29
|
+
// Benchmark Run Actions
|
|
30
|
+
setBenchmarkRuns: (runs) => set({ benchmarkRuns: runs }),
|
|
31
|
+
setBenchmarkRunsLoading: (loading) => set({ benchmarkRunsLoading: loading }),
|
|
32
|
+
setBenchmarkRunsError: (error) => set({ benchmarkRunsError: error }),
|
|
33
|
+
setBenchmarkRunsTotalCount: (count) => set({ benchmarkRunsTotalCount: count }),
|
|
34
|
+
setBenchmarkRunsHasMore: (hasMore) => set({ benchmarkRunsHasMore: hasMore }),
|
|
35
|
+
setBenchmarkRunsCurrentPage: (page) => set({ benchmarkRunsCurrentPage: page }),
|
|
36
|
+
setSelectedBenchmarkRunIndex: (index) => set({ selectedBenchmarkRunIndex: index }),
|
|
37
|
+
// Scenario Run Actions
|
|
38
|
+
setScenarioRuns: (runs) => set({ scenarioRuns: runs }),
|
|
39
|
+
setScenarioRunsLoading: (loading) => set({ scenarioRunsLoading: loading }),
|
|
40
|
+
setScenarioRunsError: (error) => set({ scenarioRunsError: error }),
|
|
41
|
+
setScenarioRunsTotalCount: (count) => set({ scenarioRunsTotalCount: count }),
|
|
42
|
+
setScenarioRunsHasMore: (hasMore) => set({ scenarioRunsHasMore: hasMore }),
|
|
43
|
+
setScenarioRunsCurrentPage: (page) => set({ scenarioRunsCurrentPage: page }),
|
|
44
|
+
setSelectedScenarioRunIndex: (index) => set({ selectedScenarioRunIndex: index }),
|
|
45
|
+
setBenchmarkRunIdFilter: (id) => set({ benchmarkRunIdFilter: id }),
|
|
46
|
+
// Cache management
|
|
47
|
+
cacheBenchmarkRunPage: (page, data) => {
|
|
48
|
+
const state = get();
|
|
49
|
+
const cache = state.benchmarkRunPageCache;
|
|
50
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
51
|
+
const oldestKey = cache.keys().next().value;
|
|
52
|
+
if (oldestKey !== undefined) {
|
|
53
|
+
cache.delete(oldestKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const plainData = data.map((d) => JSON.parse(JSON.stringify(d)));
|
|
57
|
+
cache.set(page, plainData);
|
|
58
|
+
set({});
|
|
59
|
+
},
|
|
60
|
+
getCachedBenchmarkRunPage: (page) => {
|
|
61
|
+
return get().benchmarkRunPageCache.get(page);
|
|
62
|
+
},
|
|
63
|
+
cacheScenarioRunPage: (page, data) => {
|
|
64
|
+
const state = get();
|
|
65
|
+
const cache = state.scenarioRunPageCache;
|
|
66
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
67
|
+
const oldestKey = cache.keys().next().value;
|
|
68
|
+
if (oldestKey !== undefined) {
|
|
69
|
+
cache.delete(oldestKey);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const plainData = data.map((d) => JSON.parse(JSON.stringify(d)));
|
|
73
|
+
cache.set(page, plainData);
|
|
74
|
+
set({});
|
|
75
|
+
},
|
|
76
|
+
getCachedScenarioRunPage: (page) => {
|
|
77
|
+
return get().scenarioRunPageCache.get(page);
|
|
78
|
+
},
|
|
79
|
+
clearCache: () => {
|
|
80
|
+
const state = get();
|
|
81
|
+
state.benchmarkRunPageCache.clear();
|
|
82
|
+
state.scenarioRunPageCache.clear();
|
|
83
|
+
set({
|
|
84
|
+
benchmarkRunPageCache: new Map(),
|
|
85
|
+
scenarioRunPageCache: new Map(),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
clearAll: () => {
|
|
89
|
+
const state = get();
|
|
90
|
+
state.benchmarkRunPageCache.clear();
|
|
91
|
+
state.scenarioRunPageCache.clear();
|
|
92
|
+
set({
|
|
93
|
+
benchmarkRuns: [],
|
|
94
|
+
benchmarkRunsLoading: false,
|
|
95
|
+
benchmarkRunsError: null,
|
|
96
|
+
benchmarkRunsTotalCount: 0,
|
|
97
|
+
benchmarkRunsHasMore: false,
|
|
98
|
+
benchmarkRunsCurrentPage: 0,
|
|
99
|
+
scenarioRuns: [],
|
|
100
|
+
scenarioRunsLoading: false,
|
|
101
|
+
scenarioRunsError: null,
|
|
102
|
+
scenarioRunsTotalCount: 0,
|
|
103
|
+
scenarioRunsHasMore: false,
|
|
104
|
+
scenarioRunsCurrentPage: 0,
|
|
105
|
+
benchmarkRunIdFilter: undefined,
|
|
106
|
+
selectedBenchmarkRunIndex: 0,
|
|
107
|
+
selectedScenarioRunIndex: 0,
|
|
108
|
+
benchmarkRunPageCache: new Map(),
|
|
109
|
+
scenarioRunPageCache: new Map(),
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
getSelectedBenchmarkRun: () => {
|
|
113
|
+
const state = get();
|
|
114
|
+
return state.benchmarkRuns[state.selectedBenchmarkRunIndex];
|
|
115
|
+
},
|
|
116
|
+
getSelectedScenarioRun: () => {
|
|
117
|
+
const state = get();
|
|
118
|
+
return state.scenarioRuns[state.selectedScenarioRunIndex];
|
|
119
|
+
},
|
|
120
|
+
}));
|