diff --git a/src/main.rs b/src/main.rs index 73df019..66675ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -415,7 +415,7 @@ async fn get_vm_network_info(vm_id: Option<&str>) -> Result { Ok(configs) => { configs }, - Err(e) => { + Err(_e) => { Vec::new() } }; @@ -674,38 +674,184 @@ async fn get_all() -> Result { Ok(json) } -// 从实例ID中提取UUID -fn extract_uuid_from_instance_id(instance_id: &str) -> String { - let re_str = r"\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}"; - let re = regex::Regex::new(re_str).unwrap(); - - if let Some(captures) = re.captures(instance_id) { - if let Some(m) = captures.get(0) { - return m.as_str().to_string(); - } - } - - "".to_string() -} - async fn get_snapshot(vmid: Option<&str>) -> Result { - let wmi_con = get_wmi_connection()?; - let mut snapshots = Vec::new(); - let wql = if let Some(id) = vmid { - format!("SELECT * FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType='Microsoft:Hyper-V:Snapshot:Realized' AND InstanceID LIKE '%{}%'", id) + + let powershell_cmd = if let Some(id) = vmid { + // 针对特定VM获取快照 + format!( + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; \ + $OutputEncoding = [System.Text.Encoding]::UTF8; \ + Get-VM -Id '{}' | Get-VMSnapshot | ForEach-Object {{ \ + $snap = $_; \ + [PSCustomObject]@{{ \ + Name = $snap.Name; \ + CreationTime = $snap.CreationTime.ToString('yyyy-MM-dd HH:mm:ss'); \ + }} \ + }} | ConvertTo-Json -Depth 1", + id + ) } else { - "SELECT * FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType='Microsoft:Hyper-V:Snapshot:Realized'".to_string() + // 获取所有VM的快照 + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; \ + $OutputEncoding = [System.Text.Encoding]::UTF8; \ + Get-VM | Get-VMSnapshot | ForEach-Object { \ + $snap = $_; \ + [PSCustomObject]@{ \ + VMName = $snap.VMName; \ + Name = $snap.Name; \ + CreationTime = $snap.CreationTime.ToString('yyyy-MM-dd HH:mm:ss'); \ + } \ + } | ConvertTo-Json -Depth 1".to_string() }; - let snapshot_results: Vec> = wmi_con.raw_query(&wql)?; + // 执行PowerShell命令 + let output = Command::new("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &powershell_cmd]) + .output()?; - for snapshot in snapshot_results { - // 提取快照信息 - snapshots.push(Snapshot { - name: snapshot.get("ElementName").and_then(variant_to_string).unwrap_or_default(), - creation_time: snapshot.get("CreationTime").and_then(variant_to_string).unwrap_or_default(), - }); + if output.status.success() { + // 确保使用UTF-8解码输出 + let stdout = match String::from_utf8(output.stdout.clone()) { + Ok(s) => s, + Err(_) => { + // 如果UTF-8解码失败,尝试使用系统默认编码 + String::from_utf8_lossy(&output.stdout).to_string() + } + }; + + // 检查输出是否为空 + if stdout.trim().is_empty() || stdout.trim() == "null" { + return Ok("[]".to_string()); + } + + // 尝试解析JSON输出 + if let Ok(json_value) = serde_json::from_str::(&stdout) { + // 处理单个对象或数组 + if json_value.is_array() { + // 处理数组结果 + if let Some(snapshots_array) = json_value.as_array() { + for snapshot in snapshots_array { + // 提取名称和创建时间 + let name = snapshot["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = snapshot["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } + } else if json_value.is_object() { + // 处理单个结果 + let name = json_value["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = json_value["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } else { + + // 尝试替换可能引起问题的字符并重新解析 + let cleaned_output = stdout + .replace('\u{FEFF}', "") // 删除BOM标记 + .replace('\u{FFFD}', "") // 删除UTF-8替换字符 + .trim() + .to_string(); + + if !cleaned_output.is_empty() && cleaned_output != "null" { + if let Ok(json_value) = serde_json::from_str::(&cleaned_output) { + // 处理JSON + // ... 类似上面的处理逻辑 + if json_value.is_array() { + if let Some(snapshots_array) = json_value.as_array() { + for snapshot in snapshots_array { + let name = snapshot["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = snapshot["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } + } else if json_value.is_object() { + let name = json_value["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = json_value["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } + } + } + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + + // 如果高级命令失败,尝试使用更基本的命令 + let basic_cmd = if let Some(id) = vmid { + format!( + "$OutputEncoding = [System.Text.Encoding]::UTF8; \ + Get-VM -Id '{}' | Get-VMSnapshot | Select-Object -Property Name, CreationTime | ConvertTo-Json", + id + ) + } else { + "$OutputEncoding = [System.Text.Encoding]::UTF8; \ + Get-VM | Get-VMSnapshot | Select-Object -Property VMName, Name, CreationTime | ConvertTo-Json".to_string() + }; + + let basic_output = Command::new("powershell.exe") + .args(["-Command", &basic_cmd]) + .output()?; + + if basic_output.status.success() { + let basic_stdout = String::from_utf8_lossy(&basic_output.stdout).to_string(); + + if !basic_stdout.trim().is_empty() && basic_stdout.trim() != "null" { + // 尝试解析基本命令的JSON输出 + if let Ok(json_value) = serde_json::from_str::(&basic_stdout) { + if json_value.is_array() { + if let Some(snapshots_array) = json_value.as_array() { + for snapshot in snapshots_array { + let name = snapshot["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = snapshot["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } + } else if json_value.is_object() { + let name = json_value["Name"].as_str().unwrap_or_default().to_string(); + let creation_time = json_value["CreationTime"].as_str().unwrap_or_default().to_string(); + + if !name.is_empty() { + snapshots.push(Snapshot { + name, + creation_time, + }); + } + } + } + } + } else { + let basic_stderr = String::from_utf8_lossy(&basic_output.stderr); + } } // 转换为JSON @@ -810,6 +956,51 @@ async fn get_snapshot_handler(query: web::Query Result { + // 使用PowerShell创建快照 + let output = Command::new("powershell.exe") + .args([ + "-Command", + &format!("Get-VM -Id '{}' | Checkpoint-VM -SnapshotName '{}' -ErrorAction Stop", vmid, snapshot_name) + ]) + .output()?; + + if output.status.success() { + Ok("快照创建成功".to_string()) + } else { + let error = String::from_utf8_lossy(&output.stderr); + Ok(format!("快照创建失败: {}", error)) + } +} + +async fn create_snapshot_handler(query: web::Query>, req: actix_web::HttpRequest, config: web::Data) -> impl Responder { + let key = req.headers().get("Key").and_then(|v| v.to_str().ok()); + if key != Some(&config.key) { + return HttpResponse::Unauthorized().json(serde_json::json!({ "error": "Key error" })); + } + + let vmid = query.get("VMID"); + // 使用一个固定的String变量来存储默认快照名称 + let default_snapshot_name = "Snapshot".to_string(); + let snapshot_name = query.get("Name").unwrap_or(&default_snapshot_name); + + match vmid { + Some(id) => { + if let Ok(true) = check_vmid(id).await { + match create_snapshot(id, snapshot_name).await { + Ok(result) => HttpResponse::Ok().body(result), + Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("Failed to create snapshot: {}", e) + })), + } + } else { + HttpResponse::BadRequest().json(serde_json::json!({ "error": "Invalid VMID" })) + } + }, + None => HttpResponse::BadRequest().json(serde_json::json!({ "error": "Need VMID" })), + } +} + async fn stop_vm_handler(query: web::Query>, req: actix_web::HttpRequest, config: web::Data) -> impl Responder { let key = req.headers().get("Key").and_then(|v| v.to_str().ok()); if key != Some(&config.key) { @@ -961,6 +1152,102 @@ async fn generate_vm_ticket(_vmid: &str) -> Result<(String, String)> { Ok((username, password)) } +async fn restore_snapshot(vmid: &str, snapshot_name: &str) -> Result { + // 使用PowerShell恢复快照 + let output = Command::new("powershell.exe") + .args([ + "-Command", + &format!( + "$VM = Get-VM -Id '{}'; $Snapshot = Get-VMSnapshot -VM $VM -Name '{}'; Restore-VMSnapshot -VMSnapshot $Snapshot -Confirm:$false", + vmid, + snapshot_name + ) + ]) + .output()?; + + if output.status.success() { + Ok("快照恢复成功".to_string()) + } else { + let error = String::from_utf8_lossy(&output.stderr); + Ok(format!("快照恢复失败: {}", error)) + } +} + +async fn restore_snapshot_handler(query: web::Query>, req: actix_web::HttpRequest, config: web::Data) -> impl Responder { + let key = req.headers().get("Key").and_then(|v| v.to_str().ok()); + if key != Some(&config.key) { + return HttpResponse::Unauthorized().json(serde_json::json!({ "error": "Key error" })); + } + + let vmid = query.get("VMID"); + let snapshot_name = query.get("Name"); + + match (vmid, snapshot_name) { + (Some(id), Some(name)) => { + if let Ok(true) = check_vmid(id).await { + match restore_snapshot(id, name).await { + Ok(result) => HttpResponse::Ok().body(result), + Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("Failed to restore snapshot: {}", e) + })), + } + } else { + HttpResponse::BadRequest().json(serde_json::json!({ "error": "Invalid VMID" })) + } + }, + (None, _) => HttpResponse::BadRequest().json(serde_json::json!({ "error": "Need VMID" })), + (_, None) => HttpResponse::BadRequest().json(serde_json::json!({ "error": "Need snapshot name" })), + } +} + +async fn delete_snapshot(vmid: &str, snapshot_name: &str) -> Result { + // 使用PowerShell删除快照 + let output = Command::new("powershell.exe") + .args([ + "-Command", + &format!( + "$VM = Get-VM -Id '{}'; $Snapshot = Get-VMSnapshot -VM $VM -Name '{}'; Remove-VMSnapshot -VMSnapshot $Snapshot -Confirm:$false", + vmid, + snapshot_name + ) + ]) + .output()?; + + if output.status.success() { + Ok("快照删除成功".to_string()) + } else { + let error = String::from_utf8_lossy(&output.stderr); + Ok(format!("快照删除失败: {}", error)) + } +} + +async fn delete_snapshot_handler(query: web::Query>, req: actix_web::HttpRequest, config: web::Data) -> impl Responder { + let key = req.headers().get("Key").and_then(|v| v.to_str().ok()); + if key != Some(&config.key) { + return HttpResponse::Unauthorized().json(serde_json::json!({ "error": "Key error" })); + } + + let vmid = query.get("VMID"); + let snapshot_name = query.get("Name"); + + match (vmid, snapshot_name) { + (Some(id), Some(name)) => { + if let Ok(true) = check_vmid(id).await { + match delete_snapshot(id, name).await { + Ok(result) => HttpResponse::Ok().body(result), + Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("Failed to delete snapshot: {}", e) + })), + } + } else { + HttpResponse::BadRequest().json(serde_json::json!({ "error": "Invalid VMID" })) + } + }, + (None, _) => HttpResponse::BadRequest().json(serde_json::json!({ "error": "Need VMID" })), + (_, None) => HttpResponse::BadRequest().json(serde_json::json!({ "error": "Need snapshot name" })), + } +} + #[actix_web::main] async fn main() -> std::io::Result<()> { // 检查管理员权限 @@ -1000,6 +1287,9 @@ async fn main() -> std::io::Result<()> { .route("/api2/GetALL", web::get().to(get_all_handler)) .route("/api2/GetNetWork", web::get().to(get_network_handler)) .route("/api2/GetSnapShot", web::get().to(get_snapshot_handler)) + .route("/api2/CreateSnapShot", web::post().to(create_snapshot_handler)) + .route("/api2/RestoreSnapShot", web::post().to(restore_snapshot_handler)) + .route("/api2/DeleteSnapShot", web::post().to(delete_snapshot_handler)) .route("/api2/StopVM", web::post().to(stop_vm_handler)) .route("/api2/StartVM", web::post().to(start_vm_handler)) .route("/api2/ListVM", web::get().to(list_vm_handler))