@runloop/rl-cli 1.7.1 → 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/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,189 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BenchmarkRunDetailScreen - Detail page for benchmark runs
|
|
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 { useBenchmarkStore, } from "../store/benchmarkStore.js";
|
|
11
|
+
import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
|
|
12
|
+
import { getBenchmarkRun } from "../services/benchmarkService.js";
|
|
13
|
+
import { SpinnerComponent } from "../components/Spinner.js";
|
|
14
|
+
import { ErrorMessage } from "../components/ErrorMessage.js";
|
|
15
|
+
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
16
|
+
import { colors } from "../utils/theme.js";
|
|
17
|
+
export function BenchmarkRunDetailScreen({ benchmarkRunId, }) {
|
|
18
|
+
const { goBack, navigate } = useNavigation();
|
|
19
|
+
const benchmarkRuns = useBenchmarkStore((state) => state.benchmarkRuns);
|
|
20
|
+
const [loading, setLoading] = React.useState(false);
|
|
21
|
+
const [error, setError] = React.useState(null);
|
|
22
|
+
const [fetchedRun, setFetchedRun] = React.useState(null);
|
|
23
|
+
// Find run in store first
|
|
24
|
+
const runFromStore = benchmarkRuns.find((r) => r.id === benchmarkRunId);
|
|
25
|
+
// Polling function
|
|
26
|
+
const pollRun = React.useCallback(async () => {
|
|
27
|
+
if (!benchmarkRunId)
|
|
28
|
+
return null;
|
|
29
|
+
return getBenchmarkRun(benchmarkRunId);
|
|
30
|
+
}, [benchmarkRunId]);
|
|
31
|
+
// Fetch run from API if not in store
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (benchmarkRunId && !loading && !fetchedRun) {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setError(null);
|
|
36
|
+
getBenchmarkRun(benchmarkRunId)
|
|
37
|
+
.then((run) => {
|
|
38
|
+
setFetchedRun(run);
|
|
39
|
+
setLoading(false);
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
setError(err);
|
|
43
|
+
setLoading(false);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}, [benchmarkRunId, loading, fetchedRun]);
|
|
47
|
+
// Use fetched run for full details, fall back to store for basic display
|
|
48
|
+
const run = fetchedRun || runFromStore;
|
|
49
|
+
// Show loading state
|
|
50
|
+
if (!run && benchmarkRunId && !error) {
|
|
51
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
52
|
+
{ label: "Home" },
|
|
53
|
+
{ label: "Benchmarks" },
|
|
54
|
+
{ label: "Benchmark Runs" },
|
|
55
|
+
{ label: "Loading...", active: true },
|
|
56
|
+
] }), _jsx(SpinnerComponent, { message: "Loading benchmark run details..." })] }));
|
|
57
|
+
}
|
|
58
|
+
// Show error state
|
|
59
|
+
if (error && !run) {
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
61
|
+
{ label: "Home" },
|
|
62
|
+
{ label: "Benchmarks" },
|
|
63
|
+
{ label: "Benchmark Runs" },
|
|
64
|
+
{ label: "Error", active: true },
|
|
65
|
+
] }), _jsx(ErrorMessage, { message: "Failed to load benchmark run details", error: error })] }));
|
|
66
|
+
}
|
|
67
|
+
// Show not found error
|
|
68
|
+
if (!run) {
|
|
69
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
70
|
+
{ label: "Home" },
|
|
71
|
+
{ label: "Benchmarks" },
|
|
72
|
+
{ label: "Benchmark Runs" },
|
|
73
|
+
{ label: "Not Found", active: true },
|
|
74
|
+
] }), _jsx(ErrorMessage, { message: `Benchmark run ${benchmarkRunId || "unknown"} not found`, error: new Error("Benchmark run not found") })] }));
|
|
75
|
+
}
|
|
76
|
+
// Build detail sections
|
|
77
|
+
const detailSections = [];
|
|
78
|
+
// Basic details section
|
|
79
|
+
const basicFields = [];
|
|
80
|
+
if (run.start_time_ms) {
|
|
81
|
+
basicFields.push({
|
|
82
|
+
label: "Started",
|
|
83
|
+
value: formatTimestamp(run.start_time_ms),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const endTimeMs = run.start_time_ms && run.duration_ms
|
|
87
|
+
? run.start_time_ms + run.duration_ms
|
|
88
|
+
: undefined;
|
|
89
|
+
if (endTimeMs) {
|
|
90
|
+
basicFields.push({
|
|
91
|
+
label: "Ended",
|
|
92
|
+
value: formatTimestamp(endTimeMs),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (run.benchmark_id) {
|
|
96
|
+
basicFields.push({
|
|
97
|
+
label: "Benchmark ID",
|
|
98
|
+
value: _jsx(Text, { color: colors.idColor, children: run.benchmark_id }),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (run.score !== undefined && run.score !== null) {
|
|
102
|
+
basicFields.push({
|
|
103
|
+
label: "Score",
|
|
104
|
+
value: _jsx(Text, { color: colors.info, children: run.score }),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (basicFields.length > 0) {
|
|
108
|
+
detailSections.push({
|
|
109
|
+
title: "Details",
|
|
110
|
+
icon: figures.squareSmallFilled,
|
|
111
|
+
color: colors.warning,
|
|
112
|
+
fields: basicFields,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Metadata section
|
|
116
|
+
if (run.metadata && Object.keys(run.metadata).length > 0) {
|
|
117
|
+
const metadataFields = Object.entries(run.metadata).map(([key, value]) => ({
|
|
118
|
+
label: key,
|
|
119
|
+
value: value,
|
|
120
|
+
}));
|
|
121
|
+
detailSections.push({
|
|
122
|
+
title: "Metadata",
|
|
123
|
+
icon: figures.identical,
|
|
124
|
+
color: colors.secondary,
|
|
125
|
+
fields: metadataFields,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Operations available for benchmark runs
|
|
129
|
+
const operations = [
|
|
130
|
+
{
|
|
131
|
+
key: "view-scenarios",
|
|
132
|
+
label: "View Scenario Runs",
|
|
133
|
+
color: colors.info,
|
|
134
|
+
icon: figures.arrowRight,
|
|
135
|
+
shortcut: "s",
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
// Handle operation selection
|
|
139
|
+
const handleOperation = async (operation, resource) => {
|
|
140
|
+
switch (operation) {
|
|
141
|
+
case "view-scenarios":
|
|
142
|
+
navigate("scenario-run-list", { benchmarkRunId: resource.id });
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
// Build detailed info lines for full details view
|
|
147
|
+
const buildDetailLines = (r) => {
|
|
148
|
+
const lines = [];
|
|
149
|
+
// Core Information
|
|
150
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Benchmark Run Details" }, "core-title"));
|
|
151
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", r.id] }, "core-id"));
|
|
152
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", r.name || "(none)"] }, "core-name"));
|
|
153
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Status: ", r.state] }, "core-status"));
|
|
154
|
+
if (r.benchmark_id) {
|
|
155
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Benchmark ID: ", r.benchmark_id] }, "core-benchmark"));
|
|
156
|
+
}
|
|
157
|
+
if (r.start_time_ms) {
|
|
158
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Started: ", new Date(r.start_time_ms).toLocaleString()] }, "core-created"));
|
|
159
|
+
}
|
|
160
|
+
const detailEndTimeMs = r.start_time_ms && r.duration_ms
|
|
161
|
+
? r.start_time_ms + r.duration_ms
|
|
162
|
+
: undefined;
|
|
163
|
+
if (detailEndTimeMs) {
|
|
164
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Ended: ", new Date(detailEndTimeMs).toLocaleString()] }, "core-ended"));
|
|
165
|
+
}
|
|
166
|
+
if (r.score !== undefined && r.score !== null) {
|
|
167
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Score: ", r.score] }, "core-score"));
|
|
168
|
+
}
|
|
169
|
+
lines.push(_jsx(Text, { children: " " }, "core-space"));
|
|
170
|
+
// Metadata
|
|
171
|
+
if (r.metadata && Object.keys(r.metadata).length > 0) {
|
|
172
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Metadata" }, "meta-title"));
|
|
173
|
+
Object.entries(r.metadata).forEach(([key, value], idx) => {
|
|
174
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
|
|
175
|
+
});
|
|
176
|
+
lines.push(_jsx(Text, { children: " " }, "meta-space"));
|
|
177
|
+
}
|
|
178
|
+
// Raw JSON
|
|
179
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
|
|
180
|
+
const jsonLines = JSON.stringify(r, null, 2).split("\n");
|
|
181
|
+
jsonLines.forEach((line, idx) => {
|
|
182
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
|
|
183
|
+
});
|
|
184
|
+
return lines;
|
|
185
|
+
};
|
|
186
|
+
// Check if run is still in progress for polling
|
|
187
|
+
const isRunning = run.state === "running";
|
|
188
|
+
return (_jsx(ResourceDetailPage, { resource: run, resourceType: "Benchmark Runs", getDisplayName: (r) => r.name || r.id, getId: (r) => r.id, getStatus: (r) => r.state, detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines, pollResource: isRunning ? pollRun : undefined, breadcrumbPrefix: [{ label: "Home" }, { label: "Benchmarks" }] }));
|
|
189
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BenchmarkRunListScreen - List view for benchmark 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 { listBenchmarkRuns } from "../services/benchmarkService.js";
|
|
24
|
+
export function BenchmarkRunListScreen() {
|
|
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 timeWidth = 18;
|
|
47
|
+
const baseWidth = fixedWidth + idWidth + statusWidth + timeWidth;
|
|
48
|
+
const remainingWidth = terminalWidth - baseWidth;
|
|
49
|
+
const nameWidth = Math.min(80, Math.max(15, remainingWidth));
|
|
50
|
+
// Fetch function for pagination hook
|
|
51
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
52
|
+
const result = await listBenchmarkRuns({
|
|
53
|
+
limit: params.limit,
|
|
54
|
+
startingAfter: params.startingAt,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
items: result.benchmarkRuns,
|
|
58
|
+
hasMore: result.hasMore,
|
|
59
|
+
totalCount: result.totalCount,
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
// Use the shared pagination hook
|
|
63
|
+
const { items: benchmarkRuns, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
64
|
+
fetchPage,
|
|
65
|
+
pageSize: PAGE_SIZE,
|
|
66
|
+
getItemId: (run) => run.id,
|
|
67
|
+
pollInterval: 5000,
|
|
68
|
+
pollingEnabled: !showPopup && !search.searchMode,
|
|
69
|
+
deps: [PAGE_SIZE],
|
|
70
|
+
});
|
|
71
|
+
// Operations for benchmark runs
|
|
72
|
+
const operations = React.useMemo(() => [
|
|
73
|
+
{
|
|
74
|
+
key: "view_details",
|
|
75
|
+
label: "View Details",
|
|
76
|
+
color: colors.primary,
|
|
77
|
+
icon: figures.pointer,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "view_scenarios",
|
|
81
|
+
label: "View Scenario Runs",
|
|
82
|
+
color: colors.info,
|
|
83
|
+
icon: figures.arrowRight,
|
|
84
|
+
},
|
|
85
|
+
], []);
|
|
86
|
+
// Build columns
|
|
87
|
+
const columns = React.useMemo(() => [
|
|
88
|
+
createTextColumn("id", "ID", (run) => run.id, {
|
|
89
|
+
width: idWidth + 1,
|
|
90
|
+
color: colors.idColor,
|
|
91
|
+
dimColor: false,
|
|
92
|
+
bold: false,
|
|
93
|
+
}),
|
|
94
|
+
createTextColumn("name", "Name", (run) => run.name || "", {
|
|
95
|
+
width: nameWidth,
|
|
96
|
+
}),
|
|
97
|
+
createComponentColumn("status", "Status", (run, _index, isSelected) => {
|
|
98
|
+
const statusDisplay = getStatusDisplay(run.state);
|
|
99
|
+
const text = statusDisplay.text
|
|
100
|
+
.slice(0, statusWidth)
|
|
101
|
+
.padEnd(statusWidth, " ");
|
|
102
|
+
return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
|
|
103
|
+
}, { width: statusWidth }),
|
|
104
|
+
createTextColumn("created", "Created", (run) => run.start_time_ms ? formatTimeAgo(run.start_time_ms) : "", {
|
|
105
|
+
width: timeWidth,
|
|
106
|
+
color: colors.textDim,
|
|
107
|
+
dimColor: false,
|
|
108
|
+
bold: false,
|
|
109
|
+
}),
|
|
110
|
+
], [idWidth, nameWidth, statusWidth, timeWidth]);
|
|
111
|
+
// Handle Ctrl+C to exit
|
|
112
|
+
useExitOnCtrlC();
|
|
113
|
+
// Ensure selected index is within bounds
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (benchmarkRuns.length > 0 && selectedIndex >= benchmarkRuns.length) {
|
|
116
|
+
setSelectedIndex(Math.max(0, benchmarkRuns.length - 1));
|
|
117
|
+
}
|
|
118
|
+
}, [benchmarkRuns.length, selectedIndex]);
|
|
119
|
+
const selectedRun = benchmarkRuns[selectedIndex];
|
|
120
|
+
// Calculate pagination info for display
|
|
121
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
122
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
123
|
+
const endIndex = startIndex + benchmarkRuns.length;
|
|
124
|
+
useInput((input, key) => {
|
|
125
|
+
// Handle search mode input
|
|
126
|
+
if (search.searchMode) {
|
|
127
|
+
if (key.escape) {
|
|
128
|
+
search.cancelSearch();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Handle popup navigation
|
|
133
|
+
if (showPopup) {
|
|
134
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
135
|
+
setSelectedOperation(selectedOperation - 1);
|
|
136
|
+
}
|
|
137
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
138
|
+
setSelectedOperation(selectedOperation + 1);
|
|
139
|
+
}
|
|
140
|
+
else if (key.return) {
|
|
141
|
+
setShowPopup(false);
|
|
142
|
+
const operationKey = operations[selectedOperation].key;
|
|
143
|
+
if (operationKey === "view_details") {
|
|
144
|
+
navigate("benchmark-run-detail", {
|
|
145
|
+
benchmarkRunId: selectedRun.id,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else if (operationKey === "view_scenarios") {
|
|
149
|
+
navigate("scenario-run-list", {
|
|
150
|
+
benchmarkRunId: selectedRun.id,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (input === "v" && selectedRun) {
|
|
155
|
+
setShowPopup(false);
|
|
156
|
+
navigate("benchmark-run-detail", {
|
|
157
|
+
benchmarkRunId: selectedRun.id,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else if (input === "s" && selectedRun) {
|
|
161
|
+
setShowPopup(false);
|
|
162
|
+
navigate("scenario-run-list", {
|
|
163
|
+
benchmarkRunId: selectedRun.id,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (key.escape || input === "q") {
|
|
167
|
+
setShowPopup(false);
|
|
168
|
+
setSelectedOperation(0);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const pageRuns = benchmarkRuns.length;
|
|
173
|
+
// Handle list view navigation
|
|
174
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
175
|
+
setSelectedIndex(selectedIndex - 1);
|
|
176
|
+
}
|
|
177
|
+
else if (key.downArrow && selectedIndex < pageRuns - 1) {
|
|
178
|
+
setSelectedIndex(selectedIndex + 1);
|
|
179
|
+
}
|
|
180
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
181
|
+
!loading &&
|
|
182
|
+
!navigating &&
|
|
183
|
+
hasMore) {
|
|
184
|
+
nextPage();
|
|
185
|
+
setSelectedIndex(0);
|
|
186
|
+
}
|
|
187
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
188
|
+
!loading &&
|
|
189
|
+
!navigating &&
|
|
190
|
+
hasPrev) {
|
|
191
|
+
prevPage();
|
|
192
|
+
setSelectedIndex(0);
|
|
193
|
+
}
|
|
194
|
+
else if (key.return && selectedRun) {
|
|
195
|
+
navigate("benchmark-run-detail", {
|
|
196
|
+
benchmarkRunId: selectedRun.id,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else if (input === "a" && selectedRun) {
|
|
200
|
+
setShowPopup(true);
|
|
201
|
+
setSelectedOperation(0);
|
|
202
|
+
}
|
|
203
|
+
else if (input === "/") {
|
|
204
|
+
search.enterSearchMode();
|
|
205
|
+
}
|
|
206
|
+
else if (key.escape) {
|
|
207
|
+
if (search.handleEscape()) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
goBack();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Loading state
|
|
214
|
+
if (loading && benchmarkRuns.length === 0) {
|
|
215
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
216
|
+
{ label: "Home" },
|
|
217
|
+
{ label: "Benchmarks" },
|
|
218
|
+
{ label: "Benchmark Runs", active: true },
|
|
219
|
+
] }), _jsx(SpinnerComponent, { message: "Loading benchmark runs..." })] }));
|
|
220
|
+
}
|
|
221
|
+
// Error state
|
|
222
|
+
if (error) {
|
|
223
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
224
|
+
{ label: "Home" },
|
|
225
|
+
{ label: "Benchmarks" },
|
|
226
|
+
{ label: "Benchmark Runs", active: true },
|
|
227
|
+
] }), _jsx(ErrorMessage, { message: "Failed to list benchmark runs", error: error })] }));
|
|
228
|
+
}
|
|
229
|
+
// Main list view
|
|
230
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
231
|
+
{ label: "Home" },
|
|
232
|
+
{ label: "Benchmarks" },
|
|
233
|
+
{ label: "Benchmark Runs", active: true },
|
|
234
|
+
] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search benchmark runs..." }), !showPopup && (_jsx(Table, { data: benchmarkRuns, keyExtractor: (run) => run.id, selectedIndex: selectedIndex, title: `benchmark_runs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No benchmark 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] })] })), showPopup && selectedRun && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedRun, operations: operations.map((op) => ({
|
|
235
|
+
key: op.key,
|
|
236
|
+
label: op.label,
|
|
237
|
+
color: op.color,
|
|
238
|
+
icon: op.icon,
|
|
239
|
+
shortcut: op.key === "view_details"
|
|
240
|
+
? "v"
|
|
241
|
+
: op.key === "view_scenarios"
|
|
242
|
+
? "s"
|
|
243
|
+
: "",
|
|
244
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
245
|
+
{
|
|
246
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
247
|
+
label: "Page",
|
|
248
|
+
condition: hasMore || hasPrev,
|
|
249
|
+
},
|
|
250
|
+
{ key: "Enter", label: "Details" },
|
|
251
|
+
{ key: "a", label: "Actions" },
|
|
252
|
+
{ key: "/", label: "Search" },
|
|
253
|
+
{ key: "Esc", label: "Back" },
|
|
254
|
+
] })] }));
|
|
255
|
+
}
|
|
@@ -14,12 +14,15 @@ export function MenuScreen() {
|
|
|
14
14
|
case "snapshots":
|
|
15
15
|
navigate("snapshot-list");
|
|
16
16
|
break;
|
|
17
|
-
case "
|
|
18
|
-
navigate("
|
|
17
|
+
case "settings":
|
|
18
|
+
navigate("settings-menu");
|
|
19
19
|
break;
|
|
20
20
|
case "objects":
|
|
21
21
|
navigate("object-list");
|
|
22
22
|
break;
|
|
23
|
+
case "benchmarks":
|
|
24
|
+
navigate("benchmark-menu");
|
|
25
|
+
break;
|
|
23
26
|
default:
|
|
24
27
|
// Fallback for any other screen names
|
|
25
28
|
navigate(key);
|