Various changes
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use crate::error::{AppError, Result};
|
||||
use serde_json::Value;
|
||||
#[cfg(unix)]
|
||||
use libc;
|
||||
use std::process::Stdio;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
@@ -29,6 +31,13 @@ pub enum StructuredCommand {
|
||||
percent: u8,
|
||||
message: Option<String>,
|
||||
},
|
||||
/// Run an interactive program that needs the real terminal.
|
||||
/// The client suspends ratatui, inherits stdin/stdout/stderr, waits for
|
||||
/// the process to exit, then restores the TUI.
|
||||
/// The script receives the exit code as the response.
|
||||
Exec {
|
||||
shell: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -57,6 +66,7 @@ pub fn spawn(
|
||||
UnboundedReceiver<StructuredCommand>,
|
||||
tokio::sync::oneshot::Receiver<i32>,
|
||||
UnboundedSender<String>,
|
||||
tokio::sync::oneshot::Sender<()>, // kill signal
|
||||
)> {
|
||||
let mut child = Command::new("bash")
|
||||
.arg("-c")
|
||||
@@ -66,6 +76,9 @@ pub fn spawn(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
// Capture the PID before moving child into tasks (needed for kill).
|
||||
let pid = child.id();
|
||||
|
||||
let stdin = child.stdin.take().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let stderr = child.stderr.take().unwrap();
|
||||
@@ -74,6 +87,7 @@ pub fn spawn(
|
||||
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
||||
let (finished_tx, finished_rx) = tokio::sync::oneshot::channel();
|
||||
let (reply_tx, mut reply_rx) = mpsc::unbounded_channel::<String>();
|
||||
let (kill_tx, kill_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
|
||||
// Чтение stdout
|
||||
let output_tx_clone = output_tx.clone();
|
||||
@@ -120,14 +134,27 @@ pub fn spawn(
|
||||
}
|
||||
});
|
||||
|
||||
// Ожидание завершения
|
||||
// Wait for child exit normally.
|
||||
tokio::spawn(async move {
|
||||
let status = child.wait().await;
|
||||
let code = status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
|
||||
let _ = finished_tx.send(code);
|
||||
});
|
||||
|
||||
Ok((output_rx, cmd_rx, finished_rx, reply_tx))
|
||||
// Kill-watcher: when kill_tx fires, send SIGTERM to the entire process
|
||||
// group so bash AND all its children (mysqldump, php, etc.) are killed.
|
||||
tokio::spawn(async move {
|
||||
if kill_rx.await.is_ok() {
|
||||
if let Some(pid) = pid {
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
libc::kill(-(pid as libc::pid_t), libc::SIGTERM);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok((output_rx, cmd_rx, finished_rx, reply_tx, kill_tx))
|
||||
}
|
||||
|
||||
fn parse_command(json_str: &str, cmd_tx: UnboundedSender<StructuredCommand>) -> Result<()> {
|
||||
@@ -176,6 +203,10 @@ fn parse_command(json_str: &str, cmd_tx: UnboundedSender<StructuredCommand>) ->
|
||||
let message = v["message"].as_str().map(String::from);
|
||||
StructuredCommand::Progress { percent, message }
|
||||
}
|
||||
"exec" => {
|
||||
let shell = v["shell"].as_str().unwrap_or("").to_string();
|
||||
StructuredCommand::Exec { shell }
|
||||
}
|
||||
_ => return Err(AppError::Protocol(format!("Unknown command type: {}", typ))),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user