use eframe::{NativeOptions, run_native}; use egui::{FontDefinitions, FontFamily, FontData}; use crate::api::ProxmoxClient; use std::sync::{Arc, RwLock, Mutex}; use dotenv::dotenv; use std::env; use std::thread; pub type SharedState = Arc>; pub struct AppState { pub client: Arc>>, pub vm_id: u32, pub node: String, pub vm_status: String, pub log_buffer: Vec, pub is_connected: bool, pub host: String, pub port: u16, pub show_settings: bool, pub token_id: String, pub token_secret: String, pub token_secret_shown: bool, } impl AppState { pub fn new() -> Self { dotenv().ok(); let host = env::var("PROXMOX_HOST").unwrap_or_default(); let token = env::var("PROXMOX_TOKEN").unwrap_or_default(); let vm_id: u32 = env::var("VM_ID").unwrap_or_default().parse().unwrap_or(100); let node = env::var("NODE").unwrap_or_else(|_| "proxmox".to_string()); let token_id = env::var("PROXMOX_TOKEN_ID").unwrap_or_default(); let token_secret = env::var("PROXMOX_TOKEN_SECRET").unwrap_or_default(); let port: u16 = env::var("PROXMOX_PORT").ok().and_then(|p| p.parse().ok()).unwrap_or(8006); let (tok_id, tok_secret) = if !token_id.is_empty() && !token_secret.is_empty() { (token_id.clone(), token_secret.clone()) } else if !token.is_empty() { if let Some(pos) = token.find('=') { (token[..pos].to_string(), token[pos+1..].to_string()) } else { (token.clone(), token.clone()) } } else { (String::new(), String::new()) }; let client = if !host.is_empty() && !tok_id.is_empty() && !tok_secret.is_empty() { let c = ProxmoxClient::new(&host, &tok_id, &tok_secret); Arc::new(Mutex::new(Some(c))) } else { Arc::new(Mutex::new(None)) }; let is_connected = !host.is_empty() && !tok_id.is_empty() && !tok_secret.is_empty(); Self { client, vm_id, node, vm_status: "未知".to_string(), log_buffer: vec!["程序启动".to_string()], is_connected, host, port, show_settings: false, token_id: tok_id, token_secret: tok_secret, token_secret_shown: false, } } pub fn add_log(&mut self, msg: &str) { let time = chrono::Local::now().format("%H:%M:%S").to_string(); self.log_buffer.push(format!("{} {}", time, msg)); if self.log_buffer.len() > 100 { self.log_buffer.remove(0); } } } pub fn gui_run() { let options = NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([500.0, 450.0]) .with_min_inner_size([400.0, 300.0]) .with_title("Proxmox VM 控制器"), ..Default::default() }; run_native("Proxmox VM Controller", options, Box::new(|cc| { let mut fonts = FontDefinitions::default(); fonts.font_data.insert( "my_font".to_owned(), FontData::from_static(include_bytes!(r"C:\Windows\Fonts\msyh.ttc")), ); fonts.families.get_mut(&FontFamily::Proportional) .unwrap() .insert(0, "my_font".to_owned()); fonts.families.get_mut(&FontFamily::Monospace) .unwrap() .insert(0, "my_font".to_owned()); cc.egui_ctx.set_fonts(fonts); Ok(Box::new(App::new())) })).unwrap(); } struct App { state: SharedState, } impl App { fn new() -> Self { Self { state: Arc::new(RwLock::new(AppState::new())), } } } impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let state = self.state.clone(); let ctx_clone = ctx.clone(); egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Proxmox VM 控制器"); ui.separator(); let mut st = state.write().unwrap(); ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { if ui.button("设置").clicked() { st.show_settings = !st.show_settings; } }); ui.horizontal(|ui| { ui.label("连接状态: "); ui.label(if st.is_connected { "● 已连接" } else { "○ 未连接" }); }); ui.horizontal(|ui| { ui.label("VM ID: "); ui.add(egui::DragValue::new(&mut st.vm_id).range(100..=999)); }); ui.horizontal(|ui| { ui.label("节点: "); ui.text_edit_singleline(&mut st.node); }); ui.horizontal(|ui| { ui.label("VM 状态: "); ui.label(&st.vm_status); }); ui.separator(); let vm_id_start = st.vm_id; let node_start = st.node.clone(); let client_start = st.client.clone(); let vm_id_stop = st.vm_id; let node_stop = st.node.clone(); let client_stop = st.client.clone(); let vm_id_refresh = st.vm_id; let node_refresh = st.node.clone(); let client_refresh = st.client.clone(); ui.horizontal(|ui| { if ui.button("启动").clicked() { let client = client_start.clone(); let node = node_start.clone(); let vm_id = vm_id_start; let state = state.clone(); let ctx = ctx_clone.clone(); st.add_log("正在启动 VM..."); ctx.request_repaint(); thread::spawn(move || { let rt = tokio::runtime::Runtime::new().unwrap(); let result = rt.block_on(async { let client = client.lock().unwrap(); if let Some(c) = client.as_ref() { c.start_vm(&node, vm_id).await } else { Err("未连接".to_string()) } }); let msg = match result { Ok(_) => "启动命令已发送".to_string(), Err(e) => format!("启动失败: {}", e), }; state.write().unwrap().add_log(&msg); }); } if ui.button("停止").clicked() { let client = client_stop.clone(); let node = node_stop.clone(); let vm_id = vm_id_stop; let state = state.clone(); let ctx = ctx_clone.clone(); st.add_log("正在停止 VM..."); ctx.request_repaint(); thread::spawn(move || { let rt = tokio::runtime::Runtime::new().unwrap(); let result = rt.block_on(async { let client = client.lock().unwrap(); if let Some(c) = client.as_ref() { c.stop_vm(&node, vm_id).await } else { Err("未连接".to_string()) } }); let msg = match result { Ok(_) => "停止命令已发送".to_string(), Err(e) => format!("停止失败: {}", e), }; state.write().unwrap().add_log(&msg); }); } if ui.button("刷新").clicked() { let client = client_refresh.clone(); let node = node_refresh.clone(); let vm_id = vm_id_refresh; let state = state.clone(); let ctx = ctx_clone.clone(); st.add_log("正在获取状态..."); ctx.request_repaint(); thread::spawn(move || { let rt = tokio::runtime::Runtime::new().unwrap(); let result = rt.block_on(async { let client = client.lock().unwrap(); if let Some(c) = client.as_ref() { c.get_vm_status(&node, vm_id).await } else { Err("未连接".to_string()) } }); let msg = match result { Ok(status) => format!("VM状态: {}", status), Err(e) => format!("获取状态失败: {}", e), }; state.write().unwrap().add_log(&msg); }); } }); ui.separator(); ui.label("日志:"); egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { for log in &st.log_buffer { ui.label(log); } }); let show_settings = st.show_settings; let mut host = st.host.clone(); let mut port = st.port; let token_secret = st.token_secret.clone(); let token_secret_shown = st.token_secret_shown; let token_id = st.token_id.clone(); let state_clone = state.clone(); if show_settings { egui::Window::new("设置") .collapsible(false) .resizable(false) .show(ctx, |ui| { ui.label("Base URL"); ui.label(format!("https://{}:{}/api2/json/", host, port)); ui.horizontal(|ui| { ui.label("Host"); ui.text_edit_singleline(&mut host); }); ui.horizontal(|ui| { ui.label("端口"); ui.add(egui::DragValue::new(&mut port).range(1..=65535)); }); ui.horizontal(|ui| { ui.label("Token Secret"); if token_secret_shown { ui.label(&token_secret); } else if !token_secret.is_empty() { ui.label("已保存"); } else { ui.label("未设置"); } }); if ui.button("应用设置").clicked() { let ts = token_secret.clone(); let tid = token_id.clone(); if !host.is_empty() && !tid.is_empty() && !ts.is_empty() { let client = ProxmoxClient::new(&host, &tid, &ts); state_clone.write().unwrap().client = Arc::new(Mutex::new(Some(client))); state_clone.write().unwrap().is_connected = true; state_clone.write().unwrap().add_log("已应用设置"); } } ui.separator(); ui.label("提示: 修改后需点击应用设置以生效"); }); } }); } }