This commit is contained in:
jiangcuo 2025-04-16 02:39:17 +08:00
parent 7d1310a002
commit 4717a41c33

View File

@ -415,7 +415,7 @@ async fn get_vm_network_info(vm_id: Option<&str>) -> Result<String> {
Ok(configs) => {
configs
},
Err(e) => {
Err(_e) => {
Vec::new()
}
};
@ -674,38 +674,184 @@ async fn get_all() -> Result<String> {
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<String> {
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<HashMap<String, Variant>> = 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::<serde_json::Value>(&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::<serde_json::Value>(&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::<serde_json::Value>(&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<std::collections::HashMap<String
}
}
async fn create_snapshot(vmid: &str, snapshot_name: &str) -> Result<String> {
// 使用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<std::collections::HashMap<String, String>>, req: actix_web::HttpRequest, config: web::Data<Config>) -> 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<std::collections::HashMap<String, String>>, req: actix_web::HttpRequest, config: web::Data<Config>) -> 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<String> {
// 使用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<std::collections::HashMap<String, String>>, req: actix_web::HttpRequest, config: web::Data<Config>) -> 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<String> {
// 使用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<std::collections::HashMap<String, String>>, req: actix_web::HttpRequest, config: web::Data<Config>) -> 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))