@@ -39,9 +39,45 @@ export const DevboxDetailPage: React.FC<DevboxDetailPageProps> = ({ devbox: init
3939 const [ showDetailedInfo , setShowDetailedInfo ] = React . useState ( false ) ;
4040 const [ detailScroll , setDetailScroll ] = React . useState ( 0 ) ;
4141 const [ showActions , setShowActions ] = React . useState ( false ) ;
42+ const [ selectedOperation , setSelectedOperation ] = React . useState ( 0 ) ;
4243
4344 const selectedDevbox = initialDevbox ;
4445
46+ const allOperations = [
47+ { key : 'logs' , label : 'View Logs' , color : 'blue' , icon : figures . info , shortcut : 'l' } ,
48+ { key : 'exec' , label : 'Execute Command' , color : 'green' , icon : figures . play , shortcut : 'e' } ,
49+ { key : 'upload' , label : 'Upload File' , color : 'green' , icon : figures . arrowUp , shortcut : 'u' } ,
50+ { key : 'snapshot' , label : 'Create Snapshot' , color : 'yellow' , icon : figures . circleFilled , shortcut : 'n' } ,
51+ { key : 'ssh' , label : 'SSH onto the box' , color : 'cyan' , icon : figures . arrowRight , shortcut : 's' } ,
52+ { key : 'tunnel' , label : 'Open Tunnel' , color : 'magenta' , icon : figures . pointerSmall , shortcut : 't' } ,
53+ { key : 'suspend' , label : 'Suspend Devbox' , color : 'yellow' , icon : figures . squareSmallFilled , shortcut : 'p' } ,
54+ { key : 'resume' , label : 'Resume Devbox' , color : 'green' , icon : figures . play , shortcut : 'r' } ,
55+ { key : 'delete' , label : 'Shutdown Devbox' , color : 'red' , icon : figures . cross , shortcut : 'd' } ,
56+ ] ;
57+
58+ // Filter operations based on devbox status
59+ const operations = selectedDevbox ? allOperations . filter ( op => {
60+ const status = selectedDevbox . status ;
61+
62+ // When suspended: logs and resume
63+ if ( status === 'suspended' ) {
64+ return op . key === 'resume' || op . key === 'logs' ;
65+ }
66+
67+ // When not running (shutdown, failure, etc): only logs
68+ if ( status !== 'running' && status !== 'provisioning' && status !== 'initializing' ) {
69+ return op . key === 'logs' ;
70+ }
71+
72+ // When running: everything except resume
73+ if ( status === 'running' ) {
74+ return op . key !== 'resume' ;
75+ }
76+
77+ // Default for transitional states (provisioning, initializing)
78+ return op . key === 'logs' || op . key === 'delete' ;
79+ } ) : allOperations ;
80+
4581 // Memoize time-based values to prevent re-rendering on every tick
4682 const formattedCreateTime = React . useMemo (
4783 ( ) => selectedDevbox . create_time_ms ? new Date ( selectedDevbox . create_time_ms ) . toLocaleString ( ) : '' ,
@@ -80,17 +116,31 @@ export const DevboxDetailPage: React.FC<DevboxDetailPageProps> = ({ devbox: init
80116 return ;
81117 }
82118
83- // Operations selection mode
119+ // Main view input handling
84120 if ( input === 'q' || key . escape ) {
85121 console . clear ( ) ;
86122 onBack ( ) ;
87123 } else if ( input === 'i' ) {
88124 setShowDetailedInfo ( true ) ;
89125 setDetailScroll ( 0 ) ;
90- } else if ( input === 'a' ) {
126+ } else if ( key . upArrow && selectedOperation > 0 ) {
127+ setSelectedOperation ( selectedOperation - 1 ) ;
128+ } else if ( key . downArrow && selectedOperation < operations . length - 1 ) {
129+ setSelectedOperation ( selectedOperation + 1 ) ;
130+ } else if ( key . return || input === 'a' ) {
91131 console . clear ( ) ;
92132 setShowActions ( true ) ;
93- } else if ( input === 'o' ) {
133+ } else if ( input ) {
134+ // Check if input matches any operation shortcut
135+ const matchedOpIndex = operations . findIndex ( op => op . shortcut === input ) ;
136+ if ( matchedOpIndex !== - 1 ) {
137+ setSelectedOperation ( matchedOpIndex ) ;
138+ console . clear ( ) ;
139+ setShowActions ( true ) ;
140+ }
141+ }
142+
143+ if ( input === 'o' ) {
94144 // Open in browser
95145 const url = `https://platform.runloop.ai/devboxes/${ selectedDevbox . id } ` ;
96146 const openBrowser = async ( ) => {
@@ -256,12 +306,15 @@ export const DevboxDetailPage: React.FC<DevboxDetailPageProps> = ({ devbox: init
256306 return lines ;
257307 } ;
258308
259- // Actions view
309+ // Actions view - show the DevboxActionsMenu when an action is triggered
260310 if ( showActions ) {
261311 return (
262312 < DevboxActionsMenu
263313 devbox = { selectedDevbox }
264- onBack = { ( ) => setShowActions ( false ) }
314+ onBack = { ( ) => {
315+ setShowActions ( false ) ;
316+ setSelectedOperation ( 0 ) ;
317+ } }
265318 breadcrumbItems = { [
266319 { label : 'Devboxes' } ,
267320 { label : selectedDevbox . name || selectedDevbox . id } ,
@@ -419,9 +472,26 @@ export const DevboxDetailPage: React.FC<DevboxDetailPageProps> = ({ devbox: init
419472 </ Box >
420473 ) }
421474
475+ { /* Operations - inline display */ }
476+ < Box flexDirection = "column" marginTop = { 1 } >
477+ < Text color = "cyan" bold > { figures . play } Actions</ Text >
478+ < Box flexDirection = "column" >
479+ { operations . map ( ( op , index ) => {
480+ const isSelected = index === selectedOperation ;
481+ return (
482+ < Box key = { op . key } >
483+ < Text color = { isSelected ? 'cyan' : 'gray' } > { isSelected ? figures . pointer : ' ' } </ Text >
484+ < Text color = { isSelected ? op . color : 'gray' } bold = { isSelected } > { op . icon } { op . label } </ Text >
485+ < Text color = "gray" dimColor > [{ op . shortcut } ]</ Text >
486+ </ Box >
487+ ) ;
488+ } ) }
489+ </ Box >
490+ </ Box >
491+
422492 < Box marginTop = { 1 } >
423493 < Text color = "gray" dimColor >
424- [a] Actions • [i] Full Details • [o] Browser • [q] Back
494+ { figures . arrowUp } { figures . arrowDown } Navigate • [Enter] or [shortcut] Execute • [i] Full Details • [o] Browser • [q] Back
425495 </ Text >
426496 </ Box >
427497 </ >
0 commit comments