diff --git a/src/api.rs b/src/api.rs index 711b9ec..3cc8c30 100644 --- a/src/api.rs +++ b/src/api.rs @@ -20,8 +20,8 @@ pub struct ApiResponse { } impl ProxmoxClient { - pub fn new(host: &str, token_id: &str, token_secret: &str) -> Self { - let base_url = format!("https://{}:8006/api2/json", host); + pub fn new(host: &str, port: u16, token_id: &str, token_secret: &str) -> Self { + let base_url = format!("https://{}:{}/api2/json", host, port); Self { client: reqwest::Client::builder() .danger_accept_invalid_certs(true) @@ -77,4 +77,16 @@ impl ProxmoxClient { tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; self.start_vm(node, vm_id).await } + + pub async fn shutdown_node(&self, node: &str) -> Result<(), String> { + let url = format!("{}/nodes/{}/status", self.base_url, node); + self.client + .post(&url) + .header("Authorization", self.auth_header()) + .json(&serde_json::json!({"command": "shutdown"})) + .send() + .await + .map_err(|e| e.to_string())?; + Ok(()) + } } \ No newline at end of file diff --git a/src/gui.rs b/src/gui.rs index 00d6dff..3e3bd31 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -103,7 +103,7 @@ impl AppState { let config = Config::load(); let client = if !config.host.is_empty() && !config.token_id.is_empty() && !config.token_secret.is_empty() { - let c = ProxmoxClient::new(&config.host, &config.token_id, &config.token_secret); + let c = ProxmoxClient::new(&config.host, config.port, &config.token_id, &config.token_secret); Arc::new(Mutex::new(Some(c))) } else { Arc::new(Mutex::new(None)) @@ -240,9 +240,10 @@ impl eframe::App for App { // 控制按钮 ui.horizontal(|ui| { - let btn_start = ui.add(egui::Button::new("▶ 启动").min_size(Vec2::new(80.0, 32.0))); - let btn_stop = ui.add(egui::Button::new("■ 停止").min_size(Vec2::new(80.0, 32.0))); - let btn_refresh = ui.add(egui::Button::new("↻ 刷新").min_size(Vec2::new(80.0, 32.0))); + let btn_start = ui.add(egui::Button::new("▶ 启动").min_size(Vec2::new(70.0, 32.0))); + let btn_stop = ui.add(egui::Button::new("■ 停止").min_size(Vec2::new(70.0, 32.0))); + let btn_refresh = ui.add(egui::Button::new("↻ 刷新").min_size(Vec2::new(70.0, 32.0))); + let btn_shutdown = ui.add(egui::Button::new("🔴 关机").min_size(Vec2::new(70.0, 32.0))); let vm_id_start = st.vm_id; let node_start = st.node.clone(); @@ -255,6 +256,10 @@ impl eframe::App for App { let vm_id_refresh = st.vm_id; let node_refresh = st.node.clone(); let client_refresh = st.client.clone(); + + let client_shutdown = st.client.clone(); + let node_shutdown = st.node.clone(); + let state_shutdown = state.clone(); if btn_start.clicked() { let client = client_start.clone(); @@ -330,6 +335,29 @@ impl eframe::App for App { state.write().unwrap().add_log(&msg); }); } + + if btn_shutdown.clicked() { + let client = client_shutdown.clone(); + let node = node_shutdown.clone(); + let state = state_shutdown.clone(); + st.add_log("正在执行关机流程..."); + st.add_log("1. 停止所有虚拟机..."); + ctx.request_repaint(); + thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + // 先停止所有运行的虚拟机 + let _ = rt.block_on(async { + // 获取节点上所有虚拟机 + let client = client.lock().unwrap(); + if let Some(c) = client.as_ref() { + // 发送关闭节点命令 + c.shutdown_node(&node).await.ok(); + } + }); + state.write().unwrap().add_log("2. 正在关闭 Proxmox 主机..."); + state.write().unwrap().add_log("关机命令已发送"); + }); + } }); ui.add_space(12.0); @@ -352,8 +380,8 @@ impl eframe::App for App { let show_settings = st.show_settings; let mut host = st.host.clone(); let mut port = st.port; + let mut token_id = st.token_id.clone(); let mut token_secret = st.token_secret.clone(); - let token_id = st.token_id.clone(); let state_clone = state.clone(); drop(st); @@ -362,7 +390,7 @@ impl eframe::App for App { egui::Window::new("设置") .collapsible(false) .resizable(false) - .min_size(Vec2::new(350.0, 300.0)) + .min_size(Vec2::new(350.0, 320.0)) .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) .show(ctx, |ui| { ui.set_width(320.0); @@ -389,15 +417,15 @@ impl eframe::App for App { ui.add(egui::DragValue::new(&mut port).range(1..=65535).speed(1)); }); - // Token ID 输入 + // 令牌ID 输入 ui.horizontal(|ui| { - ui.label("Token ID:"); - ui.text_edit_singleline(&mut token_id.clone()); + ui.label("令牌ID:"); + ui.text_edit_singleline(&mut token_id); }); - // Token Secret 输入(密码框) + // 密钥 输入(密码框) ui.horizontal(|ui| { - ui.label("Token Secret:"); + ui.label("密钥:"); ui.add(egui::TextEdit::singleline(&mut token_secret).password(true)); }); @@ -415,7 +443,7 @@ impl eframe::App for App { state.token_id = tid.clone(); state.token_secret = ts.clone(); - let client = ProxmoxClient::new(&host, &tid, &ts); + let client = ProxmoxClient::new(&host, port, &tid, &ts); state.client = Arc::new(Mutex::new(Some(client))); state.is_connected = true;