Suspend the TUI, execute a command on a sandbox via SSH, then resume. Mirrors `handle_shell_connect` but passes the user's command to SSH instead of opening an interactive shell. The TUI is suspended while the command runs; press Ctrl-C to stop and return to the TUI.
(
app: &mut App,
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
events: &EventHandler,
sandbox_name: &str,
command: &str,
)
| 982 | /// instead of opening an interactive shell. The TUI is suspended while |
| 983 | /// the command runs; press Ctrl-C to stop and return to the TUI. |
| 984 | async fn handle_exec_command( |
| 985 | app: &mut App, |
| 986 | terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, |
| 987 | events: &EventHandler, |
| 988 | sandbox_name: &str, |
| 989 | command: &str, |
| 990 | ) { |
| 991 | // Step 1: Resolve sandbox → SSH session (same as handle_shell_connect). |
| 992 | let sandbox_id = { |
| 993 | let req = openshell_core::proto::GetSandboxRequest { |
| 994 | name: sandbox_name.to_string(), |
| 995 | }; |
| 996 | match tokio::time::timeout(Duration::from_secs(5), app.client.get_sandbox(req)).await { |
| 997 | Ok(Ok(resp)) => { |
| 998 | if let Some(s) = resp.into_inner().sandbox { |
| 999 | s.object_id().to_string() |
| 1000 | } else { |
| 1001 | app.status_text = format!("exec: sandbox {sandbox_name} not found"); |
| 1002 | return; |
| 1003 | } |
| 1004 | } |
| 1005 | Ok(Err(e)) => { |
| 1006 | app.status_text = format!("exec: failed to get sandbox: {}", e.message()); |
| 1007 | return; |
| 1008 | } |
| 1009 | Err(_) => { |
| 1010 | app.status_text = "exec: get sandbox timed out".to_string(); |
| 1011 | return; |
| 1012 | } |
| 1013 | } |
| 1014 | }; |
| 1015 | |
| 1016 | let session = { |
| 1017 | let req = openshell_core::proto::CreateSshSessionRequest { |
| 1018 | sandbox_id: sandbox_id.clone(), |
| 1019 | }; |
| 1020 | match tokio::time::timeout(Duration::from_secs(5), app.client.create_ssh_session(req)).await |
| 1021 | { |
| 1022 | Ok(Ok(resp)) => resp.into_inner(), |
| 1023 | Ok(Err(e)) => { |
| 1024 | app.status_text = format!("exec: SSH session failed: {}", e.message()); |
| 1025 | return; |
| 1026 | } |
| 1027 | Err(_) => { |
| 1028 | app.status_text = "exec: SSH session timed out".to_string(); |
| 1029 | return; |
| 1030 | } |
| 1031 | } |
| 1032 | }; |
| 1033 | if let Err(err) = validate_ssh_session_response(&session) { |
| 1034 | app.status_text = format!("exec: gateway returned invalid SSH session response: {err}"); |
| 1035 | return; |
| 1036 | } |
| 1037 | |
| 1038 | // Step 2: Resolve gateway and build ProxyCommand (same as handle_shell_connect). |
| 1039 | #[allow(clippy::cast_possible_truncation)] |
| 1040 | let gateway_port_u16 = session.gateway_port as u16; |
| 1041 | let (gateway_host, gateway_port) = |
no test coverage detected