@runloop/rl-cli 1.5.0 → 1.7.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/blueprint/list.js +30 -5
- package/dist/commands/devbox/create.js +18 -0
- package/dist/commands/devbox/list.js +30 -34
- package/dist/commands/devbox/tunnel.js +25 -0
- package/dist/commands/network-policy/list.js +30 -5
- package/dist/commands/object/list.js +30 -5
- package/dist/commands/snapshot/list.js +30 -5
- package/dist/components/DevboxActionsMenu.js +356 -50
- package/dist/components/ExecViewer.js +439 -0
- package/dist/components/LogsViewer.js +5 -2
- package/dist/components/ResourceDetailPage.js +2 -2
- package/dist/components/SearchBar.js +24 -0
- package/dist/components/StreamingLogsViewer.js +276 -0
- package/dist/hooks/useListSearch.js +54 -0
- package/dist/router/Router.js +3 -1
- package/dist/screens/DevboxExecScreen.js +51 -0
- package/dist/screens/SnapshotDetailScreen.js +20 -0
- package/dist/services/devboxService.js +42 -5
- package/dist/services/snapshotService.js +17 -4
- package/dist/utils/commands.js +2 -0
- package/dist/utils/output.js +8 -1
- package/package.json +3 -3
|
@@ -13,6 +13,7 @@ import { NavigationTips } from "../../components/NavigationTips.js";
|
|
|
13
13
|
import { createTextColumn, Table } from "../../components/Table.js";
|
|
14
14
|
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
15
15
|
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
16
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
16
17
|
import { output, outputError } from "../../utils/output.js";
|
|
17
18
|
import { getBlueprintUrl } from "../../utils/url.js";
|
|
18
19
|
import { colors } from "../../utils/theme.js";
|
|
@@ -21,6 +22,7 @@ import { DevboxCreatePage } from "../../components/DevboxCreatePage.js";
|
|
|
21
22
|
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
22
23
|
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
23
24
|
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
25
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
24
26
|
import { useNavigation } from "../../store/navigationStore.js";
|
|
25
27
|
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
26
28
|
const DEFAULT_PAGE_SIZE = 10;
|
|
@@ -39,8 +41,13 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
39
41
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
40
42
|
const [showPopup, setShowPopup] = React.useState(false);
|
|
41
43
|
const { navigate } = useNavigation();
|
|
44
|
+
// Search state
|
|
45
|
+
const search = useListSearch({
|
|
46
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
47
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
48
|
+
});
|
|
42
49
|
// Calculate overhead for viewport height
|
|
43
|
-
const overhead = 13;
|
|
50
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
44
51
|
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
45
52
|
overhead,
|
|
46
53
|
minHeight: 5,
|
|
@@ -70,6 +77,9 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
70
77
|
if (params.startingAt) {
|
|
71
78
|
queryParams.starting_after = params.startingAt;
|
|
72
79
|
}
|
|
80
|
+
if (search.submittedSearchQuery) {
|
|
81
|
+
queryParams.search = search.submittedSearchQuery;
|
|
82
|
+
}
|
|
73
83
|
// Fetch ONE page only
|
|
74
84
|
const page = (await client.blueprints.list(queryParams));
|
|
75
85
|
// Extract data and create defensive copies
|
|
@@ -89,7 +99,7 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
89
99
|
totalCount: page.total_count || pageBlueprints.length,
|
|
90
100
|
};
|
|
91
101
|
return result;
|
|
92
|
-
}, []);
|
|
102
|
+
}, [search.submittedSearchQuery]);
|
|
93
103
|
// Use the shared pagination hook
|
|
94
104
|
const { items: blueprints, loading, navigating, error: listError, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, } = useCursorPagination({
|
|
95
105
|
fetchPage,
|
|
@@ -99,8 +109,9 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
99
109
|
pollingEnabled: !showPopup &&
|
|
100
110
|
!showCreateDevbox &&
|
|
101
111
|
!executingOperation &&
|
|
102
|
-
!showDeleteConfirm
|
|
103
|
-
|
|
112
|
+
!showDeleteConfirm &&
|
|
113
|
+
!search.searchMode,
|
|
114
|
+
deps: [PAGE_SIZE, search.submittedSearchQuery],
|
|
104
115
|
});
|
|
105
116
|
// Memoize columns array
|
|
106
117
|
const blueprintColumns = React.useMemo(() => [
|
|
@@ -272,6 +283,13 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
272
283
|
: allOperations;
|
|
273
284
|
// Handle input for all views
|
|
274
285
|
useInput((input, key) => {
|
|
286
|
+
// Handle search mode input
|
|
287
|
+
if (search.searchMode) {
|
|
288
|
+
if (key.escape) {
|
|
289
|
+
search.cancelSearch();
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
275
293
|
// Handle operation input mode
|
|
276
294
|
if (executingOperation && !operationResult && !operationError) {
|
|
277
295
|
// Allow escape/q to cancel any operation, even during loading
|
|
@@ -431,7 +449,13 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
431
449
|
};
|
|
432
450
|
openBrowser();
|
|
433
451
|
}
|
|
452
|
+
else if (input === "/") {
|
|
453
|
+
search.enterSearchMode();
|
|
454
|
+
}
|
|
434
455
|
else if (key.escape) {
|
|
456
|
+
if (search.handleEscape()) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
435
459
|
if (onBack) {
|
|
436
460
|
onBack();
|
|
437
461
|
}
|
|
@@ -520,7 +544,7 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
520
544
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), _jsx(ErrorMessage, { message: "Failed to load blueprints", error: listError })] }));
|
|
521
545
|
}
|
|
522
546
|
// List view
|
|
523
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), !showPopup && (_jsx(Table, { data: blueprints, keyExtractor: (blueprint) => blueprint.id, selectedIndex: selectedIndex, title: `blueprints[${totalCount}]`, columns: blueprintColumns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No blueprints found. Try: rli blueprint create"] }) })), !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 && selectedBlueprintItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedBlueprintItem, operations: allOperations.map((op) => ({
|
|
547
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search blueprints..." }), !showPopup && (_jsx(Table, { data: blueprints, keyExtractor: (blueprint) => blueprint.id, selectedIndex: selectedIndex, title: `blueprints[${totalCount}]`, columns: blueprintColumns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No blueprints found. Try: rli blueprint create"] }) })), !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 && selectedBlueprintItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedBlueprintItem, operations: allOperations.map((op) => ({
|
|
524
548
|
key: op.key,
|
|
525
549
|
label: op.label,
|
|
526
550
|
color: op.color,
|
|
@@ -543,6 +567,7 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
543
567
|
{ key: "Enter", label: "Details" },
|
|
544
568
|
{ key: "a", label: "Actions" },
|
|
545
569
|
{ key: "o", label: "Browser" },
|
|
570
|
+
{ key: "/", label: "Search" },
|
|
546
571
|
{ key: "Esc", label: "Back" },
|
|
547
572
|
] })] }));
|
|
548
573
|
};
|
|
@@ -17,6 +17,20 @@ function parseEnvVars(envVars) {
|
|
|
17
17
|
}
|
|
18
18
|
return result;
|
|
19
19
|
}
|
|
20
|
+
// Parse secrets from ENV_VAR=SECRET_NAME format
|
|
21
|
+
function parseSecrets(secrets) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const secret of secrets) {
|
|
24
|
+
const eqIndex = secret.indexOf("=");
|
|
25
|
+
if (eqIndex === -1) {
|
|
26
|
+
throw new Error(`Invalid secret format: ${secret}. Expected ENV_VAR=SECRET_NAME`);
|
|
27
|
+
}
|
|
28
|
+
const envVarName = secret.substring(0, eqIndex);
|
|
29
|
+
const secretName = secret.substring(eqIndex + 1);
|
|
30
|
+
result[envVarName] = secretName;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
20
34
|
// Parse code mounts from JSON format
|
|
21
35
|
function parseCodeMounts(codeMounts) {
|
|
22
36
|
return codeMounts.map((mount) => {
|
|
@@ -110,6 +124,10 @@ export async function createDevbox(options = {}) {
|
|
|
110
124
|
if (options.codeMounts && options.codeMounts.length > 0) {
|
|
111
125
|
createRequest.code_mounts = parseCodeMounts(options.codeMounts);
|
|
112
126
|
}
|
|
127
|
+
// Handle secrets
|
|
128
|
+
if (options.secrets && options.secrets.length > 0) {
|
|
129
|
+
createRequest.secrets = parseSecrets(options.secrets);
|
|
130
|
+
}
|
|
113
131
|
if (Object.keys(launchParameters).length > 0) {
|
|
114
132
|
createRequest.launch_parameters = launchParameters;
|
|
115
133
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
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 } from "ink";
|
|
4
|
-
import TextInput from "ink-text-input";
|
|
5
4
|
import figures from "figures";
|
|
6
5
|
import { getClient } from "../../utils/client.js";
|
|
7
6
|
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
@@ -11,6 +10,7 @@ import { Breadcrumb } from "../../components/Breadcrumb.js";
|
|
|
11
10
|
import { NavigationTips } from "../../components/NavigationTips.js";
|
|
12
11
|
import { Table, createTextColumn } from "../../components/Table.js";
|
|
13
12
|
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
13
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
14
14
|
import { output, outputError } from "../../utils/output.js";
|
|
15
15
|
import { DevboxDetailPage } from "../../components/DevboxDetailPage.js";
|
|
16
16
|
import { DevboxCreatePage } from "../../components/DevboxCreatePage.js";
|
|
@@ -20,6 +20,7 @@ import { getDevboxUrl } from "../../utils/url.js";
|
|
|
20
20
|
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
21
21
|
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
22
22
|
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
23
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
23
24
|
import { colors } from "../../utils/theme.js";
|
|
24
25
|
import { useDevboxStore } from "../../store/devboxStore.js";
|
|
25
26
|
const DEFAULT_PAGE_SIZE = 10;
|
|
@@ -31,9 +32,11 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
31
32
|
const [showActions, setShowActions] = React.useState(false);
|
|
32
33
|
const [showPopup, setShowPopup] = React.useState(false);
|
|
33
34
|
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
// Search state using shared hook
|
|
36
|
+
const search = useListSearch({
|
|
37
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
38
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
39
|
+
});
|
|
37
40
|
// Get devbox store setter to sync data for detail screen
|
|
38
41
|
const setDevboxesInStore = useDevboxStore((state) => state.setDevboxes);
|
|
39
42
|
// Calculate overhead for viewport height:
|
|
@@ -44,7 +47,7 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
44
47
|
// - Help bar (marginTop + content): 2 lines
|
|
45
48
|
// - Safety buffer for edge cases: 1 line
|
|
46
49
|
// Total: 13 lines base + 2 if searching
|
|
47
|
-
const overhead = 13 + (
|
|
50
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
48
51
|
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
49
52
|
overhead,
|
|
50
53
|
minHeight: 5,
|
|
@@ -64,8 +67,8 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
64
67
|
if (status) {
|
|
65
68
|
queryParams.status = status;
|
|
66
69
|
}
|
|
67
|
-
if (submittedSearchQuery) {
|
|
68
|
-
queryParams.search = submittedSearchQuery;
|
|
70
|
+
if (search.submittedSearchQuery) {
|
|
71
|
+
queryParams.search = search.submittedSearchQuery;
|
|
69
72
|
}
|
|
70
73
|
// Fetch ONE page only
|
|
71
74
|
const page = (await client.devboxes.list(queryParams));
|
|
@@ -81,15 +84,19 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
81
84
|
totalCount: page.total_count || pageDevboxes.length,
|
|
82
85
|
};
|
|
83
86
|
return result;
|
|
84
|
-
}, [status, submittedSearchQuery]);
|
|
87
|
+
}, [status, search.submittedSearchQuery]);
|
|
85
88
|
// Use the shared pagination hook
|
|
86
89
|
const { items: devboxes, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, } = useCursorPagination({
|
|
87
90
|
fetchPage,
|
|
88
91
|
pageSize: PAGE_SIZE,
|
|
89
92
|
getItemId: (devbox) => devbox.id,
|
|
90
93
|
pollInterval: 2000,
|
|
91
|
-
pollingEnabled: !showDetails &&
|
|
92
|
-
|
|
94
|
+
pollingEnabled: !showDetails &&
|
|
95
|
+
!showCreate &&
|
|
96
|
+
!showActions &&
|
|
97
|
+
!showPopup &&
|
|
98
|
+
!search.searchMode,
|
|
99
|
+
deps: [status, search.submittedSearchQuery, PAGE_SIZE],
|
|
93
100
|
});
|
|
94
101
|
// Sync devboxes to store for detail screen
|
|
95
102
|
React.useEffect(() => {
|
|
@@ -315,10 +322,9 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
315
322
|
useInput((input, key) => {
|
|
316
323
|
const pageDevboxes = devboxes.length;
|
|
317
324
|
// Skip input handling when in search mode - let TextInput handle it
|
|
318
|
-
if (searchMode) {
|
|
325
|
+
if (search.searchMode) {
|
|
319
326
|
if (key.escape) {
|
|
320
|
-
|
|
321
|
-
setSearchQuery("");
|
|
327
|
+
search.cancelSearch();
|
|
322
328
|
}
|
|
323
329
|
return;
|
|
324
330
|
}
|
|
@@ -416,24 +422,20 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
416
422
|
openBrowser();
|
|
417
423
|
}
|
|
418
424
|
else if (input === "/") {
|
|
419
|
-
|
|
425
|
+
search.enterSearchMode();
|
|
420
426
|
}
|
|
421
427
|
else if (key.escape) {
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
428
|
+
if (search.handleEscape()) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (onBack) {
|
|
432
|
+
onBack();
|
|
433
|
+
}
|
|
434
|
+
else if (onExit) {
|
|
435
|
+
onExit();
|
|
426
436
|
}
|
|
427
437
|
else {
|
|
428
|
-
|
|
429
|
-
onBack();
|
|
430
|
-
}
|
|
431
|
-
else if (onExit) {
|
|
432
|
-
onExit();
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
inkExit();
|
|
436
|
-
}
|
|
438
|
+
inkExit();
|
|
437
439
|
}
|
|
438
440
|
}
|
|
439
441
|
});
|
|
@@ -472,13 +474,7 @@ const ListDevboxesUI = ({ status, onBack, onExit, onNavigateToDetail, }) => {
|
|
|
472
474
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list devboxes", error: error })] }));
|
|
473
475
|
}
|
|
474
476
|
// Main list view
|
|
475
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), searchMode && (_jsxs(Box, {
|
|
476
|
-
setSearchMode(false);
|
|
477
|
-
setSubmittedSearchQuery(searchQuery);
|
|
478
|
-
setSelectedIndex(0);
|
|
479
|
-
} }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to search, Esc to cancel]"] })] })), !searchMode && submittedSearchQuery && (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.primary, children: [figures.info, " Searching for: "] }), _jsx(Text, { color: colors.warning, bold: true, children: submittedSearchQuery.length > 50
|
|
480
|
-
? submittedSearchQuery.substring(0, 50) + "..."
|
|
481
|
-
: submittedSearchQuery }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "(", totalCount, " results) [/ to edit, Esc to clear]"] })] })), !showPopup && (_jsx(Table, { data: devboxes, keyExtractor: (devbox) => devbox.id, selectedIndex: selectedIndex, title: "devboxes", columns: tableColumns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No devboxes 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] }), submittedSearchQuery && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.warning, children: ["Filtered: \"", submittedSearchQuery, "\""] })] }))] })), showPopup && selectedDevbox && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedDevbox, operations: operations, selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
477
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search devboxes..." }), !showPopup && (_jsx(Table, { data: devboxes, keyExtractor: (devbox) => devbox.id, selectedIndex: selectedIndex, title: "devboxes", columns: tableColumns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No devboxes 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 && selectedDevbox && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedDevbox, operations: operations, selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
482
478
|
{
|
|
483
479
|
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
484
480
|
label: "Page",
|
|
@@ -50,11 +50,36 @@ export async function createTunnel(devboxId, options) {
|
|
|
50
50
|
`${localPort}:localhost:${remotePort}`,
|
|
51
51
|
`${user}@${sshInfo.url}`,
|
|
52
52
|
];
|
|
53
|
+
const tunnelUrl = `http://localhost:${localPort}`;
|
|
53
54
|
console.log(`Starting tunnel: local port ${localPort} -> remote port ${remotePort}`);
|
|
55
|
+
console.log(`Tunnel URL: ${tunnelUrl}`);
|
|
54
56
|
console.log("Press Ctrl+C to stop the tunnel.");
|
|
55
57
|
const tunnelProcess = spawn("/usr/bin/ssh", tunnelArgs, {
|
|
56
58
|
stdio: "inherit",
|
|
57
59
|
});
|
|
60
|
+
// Open browser if --open flag is set
|
|
61
|
+
if (options.open) {
|
|
62
|
+
// Small delay to let the tunnel establish
|
|
63
|
+
setTimeout(async () => {
|
|
64
|
+
const { exec } = await import("child_process");
|
|
65
|
+
const platform = process.platform;
|
|
66
|
+
let openCommand;
|
|
67
|
+
if (platform === "darwin") {
|
|
68
|
+
openCommand = `open "${tunnelUrl}"`;
|
|
69
|
+
}
|
|
70
|
+
else if (platform === "win32") {
|
|
71
|
+
openCommand = `start "${tunnelUrl}"`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
openCommand = `xdg-open "${tunnelUrl}"`;
|
|
75
|
+
}
|
|
76
|
+
exec(openCommand, (error) => {
|
|
77
|
+
if (error) {
|
|
78
|
+
console.log(`\nCould not open browser: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}, 1000);
|
|
82
|
+
}
|
|
58
83
|
tunnelProcess.on("close", (code) => {
|
|
59
84
|
console.log("\nTunnel closed.");
|
|
60
85
|
processUtils.exit(code || 0);
|
|
@@ -12,11 +12,13 @@ import { NavigationTips } from "../../components/NavigationTips.js";
|
|
|
12
12
|
import { Table, createTextColumn } from "../../components/Table.js";
|
|
13
13
|
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
14
14
|
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
15
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
15
16
|
import { output, outputError } from "../../utils/output.js";
|
|
16
17
|
import { colors } from "../../utils/theme.js";
|
|
17
18
|
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
18
19
|
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
19
20
|
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
21
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
20
22
|
import { useNavigation } from "../../store/navigationStore.js";
|
|
21
23
|
import { NetworkPolicyCreatePage } from "../../components/NetworkPolicyCreatePage.js";
|
|
22
24
|
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
@@ -60,8 +62,13 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
60
62
|
const [showEditPolicy, setShowEditPolicy] = React.useState(false);
|
|
61
63
|
const [editingPolicy, setEditingPolicy] = React.useState(null);
|
|
62
64
|
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
65
|
+
// Search state
|
|
66
|
+
const search = useListSearch({
|
|
67
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
68
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
69
|
+
});
|
|
63
70
|
// Calculate overhead for viewport height
|
|
64
|
-
const overhead = 13;
|
|
71
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
65
72
|
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
66
73
|
overhead,
|
|
67
74
|
minHeight: 5,
|
|
@@ -90,6 +97,9 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
90
97
|
if (params.startingAt) {
|
|
91
98
|
queryParams.starting_after = params.startingAt;
|
|
92
99
|
}
|
|
100
|
+
if (search.submittedSearchQuery) {
|
|
101
|
+
queryParams.search = search.submittedSearchQuery;
|
|
102
|
+
}
|
|
93
103
|
// Fetch ONE page only
|
|
94
104
|
const page = (await client.networkPolicies.list(queryParams));
|
|
95
105
|
// Extract data and create defensive copies
|
|
@@ -115,7 +125,7 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
115
125
|
totalCount: page.total_count || pagePolicies.length,
|
|
116
126
|
};
|
|
117
127
|
return result;
|
|
118
|
-
}, []);
|
|
128
|
+
}, [search.submittedSearchQuery]);
|
|
119
129
|
// Use the shared pagination hook
|
|
120
130
|
const { items: policies, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
121
131
|
fetchPage,
|
|
@@ -126,8 +136,9 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
126
136
|
!executingOperation &&
|
|
127
137
|
!showCreatePolicy &&
|
|
128
138
|
!showEditPolicy &&
|
|
129
|
-
!showDeleteConfirm
|
|
130
|
-
|
|
139
|
+
!showDeleteConfirm &&
|
|
140
|
+
!search.searchMode,
|
|
141
|
+
deps: [PAGE_SIZE, search.submittedSearchQuery],
|
|
131
142
|
});
|
|
132
143
|
// Operations for a specific network policy (shown in popup)
|
|
133
144
|
const operations = React.useMemo(() => [
|
|
@@ -232,6 +243,13 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
232
243
|
}
|
|
233
244
|
};
|
|
234
245
|
useInput((input, key) => {
|
|
246
|
+
// Handle search mode input
|
|
247
|
+
if (search.searchMode) {
|
|
248
|
+
if (key.escape) {
|
|
249
|
+
search.cancelSearch();
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
235
253
|
// Handle operation result display
|
|
236
254
|
if (operationResult || operationError) {
|
|
237
255
|
if (input === "q" || key.escape || key.return) {
|
|
@@ -363,7 +381,13 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
363
381
|
setEditingPolicy(selectedPolicyItem);
|
|
364
382
|
setShowEditPolicy(true);
|
|
365
383
|
}
|
|
384
|
+
else if (input === "/") {
|
|
385
|
+
search.enterSearchMode();
|
|
386
|
+
}
|
|
366
387
|
else if (key.escape) {
|
|
388
|
+
if (search.handleEscape()) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
367
391
|
if (onBack) {
|
|
368
392
|
onBack();
|
|
369
393
|
}
|
|
@@ -443,7 +467,7 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
443
467
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list network policies", error: error })] }));
|
|
444
468
|
}
|
|
445
469
|
// Main list view
|
|
446
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), !showPopup && (_jsx(Table, { data: policies, keyExtractor: (policy) => policy.id, selectedIndex: selectedIndex, title: `network_policies[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No network policies 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] })] })), showPopup && selectedPolicyItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedPolicyItem, operations: operations.map((op) => ({
|
|
470
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search network policies..." }), !showPopup && (_jsx(Table, { data: policies, keyExtractor: (policy) => policy.id, selectedIndex: selectedIndex, title: `network_policies[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No network policies 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 && selectedPolicyItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedPolicyItem, operations: operations.map((op) => ({
|
|
447
471
|
key: op.key,
|
|
448
472
|
label: op.label,
|
|
449
473
|
color: op.color,
|
|
@@ -467,6 +491,7 @@ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
|
467
491
|
{ key: "c", label: "Create" },
|
|
468
492
|
{ key: "e", label: "Edit" },
|
|
469
493
|
{ key: "a", label: "Actions" },
|
|
494
|
+
{ key: "/", label: "Search" },
|
|
470
495
|
{ key: "Esc", label: "Back" },
|
|
471
496
|
] })] }));
|
|
472
497
|
};
|
|
@@ -14,11 +14,13 @@ import { NavigationTips } from "../../components/NavigationTips.js";
|
|
|
14
14
|
import { Table, createTextColumn } from "../../components/Table.js";
|
|
15
15
|
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
16
16
|
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
17
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
17
18
|
import { output, outputError } from "../../utils/output.js";
|
|
18
19
|
import { colors } from "../../utils/theme.js";
|
|
19
20
|
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
20
21
|
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
21
22
|
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
23
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
22
24
|
import { useNavigation } from "../../store/navigationStore.js";
|
|
23
25
|
import { formatFileSize } from "../../services/objectService.js";
|
|
24
26
|
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
@@ -38,8 +40,13 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
38
40
|
const [showDownloadPrompt, setShowDownloadPrompt] = React.useState(false);
|
|
39
41
|
const [downloadPath, setDownloadPath] = React.useState("");
|
|
40
42
|
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
43
|
+
// Search state
|
|
44
|
+
const search = useListSearch({
|
|
45
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
46
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
47
|
+
});
|
|
41
48
|
// Calculate overhead for viewport height
|
|
42
|
-
const overhead = 13;
|
|
49
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
43
50
|
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
44
51
|
overhead,
|
|
45
52
|
minHeight: 5,
|
|
@@ -95,6 +102,9 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
95
102
|
if (params.startingAt) {
|
|
96
103
|
queryParams.starting_after = params.startingAt;
|
|
97
104
|
}
|
|
105
|
+
if (search.submittedSearchQuery) {
|
|
106
|
+
queryParams.search = search.submittedSearchQuery;
|
|
107
|
+
}
|
|
98
108
|
// Fetch ONE page only
|
|
99
109
|
const result = await client.objects.list(queryParams);
|
|
100
110
|
// Extract data and create defensive copies
|
|
@@ -120,7 +130,7 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
120
130
|
hasMore: pageResult.has_more || false,
|
|
121
131
|
totalCount: pageResult.total_count || pageObjects.length,
|
|
122
132
|
};
|
|
123
|
-
}, []);
|
|
133
|
+
}, [search.submittedSearchQuery]);
|
|
124
134
|
// Use the shared pagination hook
|
|
125
135
|
const { items: objects, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
126
136
|
fetchPage,
|
|
@@ -130,8 +140,9 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
130
140
|
pollingEnabled: !showPopup &&
|
|
131
141
|
!executingOperation &&
|
|
132
142
|
!showDownloadPrompt &&
|
|
133
|
-
!showDeleteConfirm
|
|
134
|
-
|
|
143
|
+
!showDeleteConfirm &&
|
|
144
|
+
!search.searchMode,
|
|
145
|
+
deps: [PAGE_SIZE, search.submittedSearchQuery],
|
|
135
146
|
});
|
|
136
147
|
// Operations for objects
|
|
137
148
|
const operations = React.useMemo(() => [
|
|
@@ -274,6 +285,13 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
274
285
|
}
|
|
275
286
|
};
|
|
276
287
|
useInput((input, key) => {
|
|
288
|
+
// Handle search mode input
|
|
289
|
+
if (search.searchMode) {
|
|
290
|
+
if (key.escape) {
|
|
291
|
+
search.cancelSearch();
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
277
295
|
// Handle operation result display
|
|
278
296
|
if (operationResult || operationError) {
|
|
279
297
|
if (input === "q" || key.escape || key.return) {
|
|
@@ -397,7 +415,13 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
397
415
|
setShowPopup(true);
|
|
398
416
|
setSelectedOperation(0);
|
|
399
417
|
}
|
|
418
|
+
else if (input === "/") {
|
|
419
|
+
search.enterSearchMode();
|
|
420
|
+
}
|
|
400
421
|
else if (key.escape) {
|
|
422
|
+
if (search.handleEscape()) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
401
425
|
if (onBack) {
|
|
402
426
|
onBack();
|
|
403
427
|
}
|
|
@@ -470,7 +494,7 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
470
494
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list storage objects", error: error })] }));
|
|
471
495
|
}
|
|
472
496
|
// Main list view
|
|
473
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), !showPopup && (_jsx(Table, { data: objects, keyExtractor: (obj) => obj.id, selectedIndex: selectedIndex, title: `storage_objects[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No storage objects found. Try: rli object upload", " ", "<file>"] }) })), !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 && selectedObjectItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedObjectItem, operations: operations.map((op) => ({
|
|
497
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search storage objects..." }), !showPopup && (_jsx(Table, { data: objects, keyExtractor: (obj) => obj.id, selectedIndex: selectedIndex, title: `storage_objects[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No storage objects found. Try: rli object upload", " ", "<file>"] }) })), !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 && selectedObjectItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedObjectItem, operations: operations.map((op) => ({
|
|
474
498
|
key: op.key,
|
|
475
499
|
label: op.label,
|
|
476
500
|
color: op.color,
|
|
@@ -490,6 +514,7 @@ const ListObjectsUI = ({ onBack, onExit, }) => {
|
|
|
490
514
|
},
|
|
491
515
|
{ key: "Enter", label: "Details" },
|
|
492
516
|
{ key: "a", label: "Actions" },
|
|
517
|
+
{ key: "/", label: "Search" },
|
|
493
518
|
{ key: "Esc", label: "Back" },
|
|
494
519
|
] })] }));
|
|
495
520
|
};
|