//! Tauri commands for Docker management via SSH exec channels. use tauri::State; use serde::Serialize; use crate::AppState; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct DockerContainer { pub id: String, pub name: String, pub image: String, pub status: String, pub ports: String, pub created: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct DockerImage { pub id: String, pub repository: String, pub tag: String, pub size: String, pub created: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct DockerVolume { pub name: String, pub driver: String, pub mountpoint: String, } #[tauri::command] pub async fn docker_list_containers(session_id: String, all: Option, state: State<'_, AppState>) -> Result, String> { let session = state.ssh.get_session(&session_id).ok_or("Session not found")?; let flag = if all.unwrap_or(true) { "-a" } else { "" }; let output = exec(&session.handle, &format!("docker ps {} --format '{{{{.ID}}}}|{{{{.Names}}}}|{{{{.Image}}}}|{{{{.Status}}}}|{{{{.Ports}}}}|{{{{.CreatedAt}}}}' 2>&1", flag)).await?; Ok(output.lines().filter(|l| !l.is_empty() && !l.starts_with("CONTAINER")).map(|line| { let p: Vec<&str> = line.splitn(6, '|').collect(); DockerContainer { id: p.first().unwrap_or(&"").to_string(), name: p.get(1).unwrap_or(&"").to_string(), image: p.get(2).unwrap_or(&"").to_string(), status: p.get(3).unwrap_or(&"").to_string(), ports: p.get(4).unwrap_or(&"").to_string(), created: p.get(5).unwrap_or(&"").to_string(), } }).collect()) } #[tauri::command] pub async fn docker_list_images(session_id: String, state: State<'_, AppState>) -> Result, String> { let session = state.ssh.get_session(&session_id).ok_or("Session not found")?; let output = exec(&session.handle, "docker images --format '{{.ID}}|{{.Repository}}|{{.Tag}}|{{.Size}}|{{.CreatedAt}}' 2>&1").await?; Ok(output.lines().filter(|l| !l.is_empty()).map(|line| { let p: Vec<&str> = line.splitn(5, '|').collect(); DockerImage { id: p.first().unwrap_or(&"").to_string(), repository: p.get(1).unwrap_or(&"").to_string(), tag: p.get(2).unwrap_or(&"").to_string(), size: p.get(3).unwrap_or(&"").to_string(), created: p.get(4).unwrap_or(&"").to_string(), } }).collect()) } #[tauri::command] pub async fn docker_list_volumes(session_id: String, state: State<'_, AppState>) -> Result, String> { let session = state.ssh.get_session(&session_id).ok_or("Session not found")?; let output = exec(&session.handle, "docker volume ls --format '{{.Name}}|{{.Driver}}|{{.Mountpoint}}' 2>&1").await?; Ok(output.lines().filter(|l| !l.is_empty()).map(|line| { let p: Vec<&str> = line.splitn(3, '|').collect(); DockerVolume { name: p.first().unwrap_or(&"").to_string(), driver: p.get(1).unwrap_or(&"").to_string(), mountpoint: p.get(2).unwrap_or(&"").to_string(), } }).collect()) } #[tauri::command] pub async fn docker_action(session_id: String, action: String, target: String, state: State<'_, AppState>) -> Result { let session = state.ssh.get_session(&session_id).ok_or("Session not found")?; let cmd = match action.as_str() { "start" => format!("docker start {} 2>&1", target), "stop" => format!("docker stop {} 2>&1", target), "restart" => format!("docker restart {} 2>&1", target), "remove" => format!("docker rm -f {} 2>&1", target), "logs" => format!("docker logs --tail 100 {} 2>&1", target), "remove-image" => format!("docker rmi {} 2>&1", target), "remove-volume" => format!("docker volume rm {} 2>&1", target), "builder-prune" => "docker builder prune -f 2>&1".to_string(), "system-prune" => "docker system prune -f 2>&1".to_string(), "system-prune-all" => "docker system prune -a -f 2>&1".to_string(), _ => return Err(format!("Unknown docker action: {}", action)), }; exec(&session.handle, &cmd).await } async fn exec(handle: &std::sync::Arc>>, cmd: &str) -> Result { let mut channel = { let h = handle.lock().await; h.channel_open_session().await.map_err(|e| format!("Exec failed: {}", e))? }; channel.exec(true, cmd).await.map_err(|e| format!("Exec failed: {}", e))?; let mut output = String::new(); loop { match channel.wait().await { Some(russh::ChannelMsg::Data { ref data }) => { if let Ok(t) = std::str::from_utf8(data.as_ref()) { output.push_str(t); } } Some(russh::ChannelMsg::Eof) | Some(russh::ChannelMsg::Close) | None => break, _ => {} } } Ok(output) }