# Proxmox VM GUI 控制工具实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将 Python Proxmox 控制器改写为 Rust GUI 应用,使用 egui 框架,通过 Proxmox API 控制 Windows VM **Architecture:** 单窗口 GUI 应用,顶部显示状态和控制按钮,底部显示操作日志 **Tech Stack:** - GUI: egui 0.29 - HTTP: reqwest (rust-tls) - 配置: dotenv + serde --- ### Task 1: 创建 Rust 项目结构 **Files:** - Create: `Cargo.toml` - Create: `src/main.rs` - Create: `.env.example` - [ ] **Step 1: 创建 Cargo.toml** ```toml [package] name = "proxmox-vm-gui" version = "0.1.0" edition = "2021" [dependencies] egui = "0.29" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dotenv = "0.15" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } chrono = "0.4" [profile.release] opt-level = 3 lto = true codegen-units = 1 [target.x86_64-pc-windows-gnu] linker = "x86_64-w64-mingw32-gcc" ``` - [ ] **Step 2: 创建 src/main.rs 骨架** ```rust #![windows_subsystem = "windows"] fn main() { println!("Hello"); } ``` - [ ] **Step 3: 创建 .env.example** ``` PROXMOX_HOST=proxmox.example.com PROXMOX_USER=root@pam PROXMOX_TOKEN=your-api-token-here VM_ID=100 ``` - [ ] **Step 4: 验证编译** Run: `cargo build --target x86_64-pc-windows-gnu` Expected: 编译成功生成 exe --- ### Task 2: 实现 Proxmox API 客户端 **Files:** - Create: `src/api.rs` - Modify: `src/main.rs` Proxmox API 基础结构,用于所有 API 调用 - [ ] **Step 1: 创建 src/api.rs** ```rust use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; #[derive(Debug, Clone)] pub struct ProxmoxClient { client: reqwest::Client, base_url: String, token_id: String, token_secret: String, } #[derive(Debug, Serialize, Deserialize)] pub struct VmStatus { pub status: String, pub uptime: u64, } #[derive(Debug, Serialize, Deserialize)] pub struct ApiResponse { pub data: Option, } impl ProxmoxClient { pub fn new(host: &str, token_id: &str, token_secret: &str) -> Self { let base_url = format!("https://{}:8006/api2/json", host); Self { client: reqwest::Client::builder() .danger_accept_invalid_certs(true) .build() .unwrap(), base_url, token_id: token_id.to_string(), token_secret: token_secret.to_string(), } } fn auth_header(&self) -> String { format!("PVEAPIToken={}={}", self.token_id, self.token_secret) } pub async fn get_vm_status(&self, node: &str, vm_id: u32) -> Result { let url = format!("{}/nodes/{}/qemu/{}/status/current", self.base_url, node, vm_id); let resp = self.client .get(&url) .header("Authorization", self.auth_header()) .send() .await .map_err(|e| e.to_string())?; let data: ApiResponse = resp.json().await.map_err(|e| e.to_string())?; Ok(data.data.map(|d| d.status).unwrap_or_else(|| "unknown".to_string())) } pub async fn start_vm(&self, node: &str, vm_id: u32) -> Result<(), String> { let url = format!("{}/nodes/{}/qemu/{}/status/start", self.base_url, node, vm_id); self.client .post(&url) .header("Authorization", self.auth_header()) .send() .await .map_err(|e| e.to_string())?; Ok(()) } pub async fn stop_vm(&self, node: &str, vm_id: u32) -> Result<(), String> { let url = format!("{}/nodes/{}/qemu/{}/status/stop", self.base_url, node, vm_id); self.client .post(&url) .header("Authorization", self.auth_header()) .send() .await .map_err(|e| e.to_string())?; Ok(()) } pub async fn reboot_vm(&self, node: &str, vm_id: u32) -> Result<(), String> { self.stop_vm(node, vm_id).await?; tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; self.start_vm(node, vm_id).await } } ``` - [ ] **Step 2: 修改 main.rs 添加基础结构** 导入 api 模块,添加空 main 函数 ```rust mod api; use api::ProxmoxClient; fn main() { println!("Proxmox VM GUI"); } ``` - [ ] **Step 3: 测试 API 编译** Run: `cargo build --target x86_64-pc-windows-gnu` Expected: 编译成功 --- ### Task 3: 实现 egui GUI 界面 **Files:** - Create: `src/gui.rs` - Modify: `src/main.rs` 核心 GUI 界面:状态显示、控制按钮、日志区域 - [ ] **Step 1: 创建 src/gui.rs** ```rust use egui::*; use crate::api::ProxmoxClient; use std::sync::Arc; use tokio::sync::RwLock; pub struct AppState { pub client: Arc>>, pub vm_id: u32, pub node: String, pub vm_status: String, pub logs: Vec, pub is_connected: bool, } impl AppState { pub fn new() -> Self { Self { client: Arc::new(RwLock::new(None)), vm_id: 100, node: "proxmox".to_string(), vm_status: "未知".to_string(), logs: vec!["程序启动".to_string()], is_connected: false, } } pub fn add_log(&mut self, msg: &str) { let time = chrono::Local::now().format("%H:%M:%S").to_string(); self.logs.push(format!("{} {}", time, msg)); if self.logs.len() > 100 { self.logs.remove(0); } } } pub fn gui_run() { let options = NativeOptions { default_viewport: egui::ViewportBuilder::default() .with_inner_size([500.0, 400.0]) .with_min_inner_size([400.0, 300.0]) .with_title("Proxmox VM 控制器"), ..Default::default() }; run_native("Proxmox VM 控制器", options, |ctx| { set_font(ctx); App::new().clone().run(ctx, &mut AppState::new()); }).unwrap(); } fn set_font(ctx: &egui::Context) { let mut style = (*ctx.style()).clone(); style.text_styles = [ (TextStyle::Heading, FontId::new(18.0, "Microsoft YaHei")), (TextStyle::Body, FontId::new(14.0, "Microsoft YaHei")), (TextStyle::Button, FontId::new(14.0, "Microsoft YaHei")), ].into(); ctx.set_style(style); } struct App; impl App { fn new() -> Self { Self } fn run(self, ctx: &egui::Context, state: &mut AppState) { egui::CentralPanel::default().show(ctx, |ui| { self.ui_content(ui, state); }); } fn ui_content(&self, ui: &mut Ui, state: &mut AppState) { ui.heading("Proxmox VM 控制器"); ui.separator(); ui.horizontal(|ui| { ui.label("连接状态: "); ui.label(if state.is_connected { "● 已��接" } else { "○ 未连接" }); }); ui.horizontal(|ui| { ui.label("VM ID: "); ui.add(egui::DragValue::new(&mut state.vm_id).clamp_range(100..=999)); }); ui.horizontal(|ui| { ui.label("VM 状态: "); ui.label(&state.vm_status); }); ui.separator(); ui.horizontal(|ui| { if ui.button("启动").clicked() { state.add_log("点击了启动按钮"); } if ui.button("停止").clicked() { state.add_log("点击了停止按钮"); } if ui.button("刷新").clicked() { state.add_log("点击了刷新按钮"); } }); ui.separator(); ui.label("日志:"); egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { for log in &state.logs { ui.label(log); } }); } } ``` - [ ] **Step 2: 修改 main.rs** ```rust #![windows_subsystem = "windows"] mod api; mod gui; fn main() { gui::gui_run(); } ``` - [ ] **Step 3: 测试 GUI 编译** Run: `cargo build --target x86_64-pc-windows-gnu` Expected: 编译成功 --- ### Task 4: 集成配置加载 **Files:** - Modify: `src/gui.rs` - Create: `.env` 从 .env 文件加载配置 - [ ] **Step 1: 创建 .env 文件** ``` PROXMOX_HOST=proxmox.example.com PROXMOX_USER=root@pam PROXMOX_TOKEN=your-token VM_ID=100 NODE=proxmox ``` - [ ] **Step 2: 修改gui.rs 添加配置加载** 添加启动时加载配置的代码 - [ ] **Step 3: 验证配置加载** 编译测试 --- ### Task 5: 连接 API 控制逻辑 **Files:** - Modify: `src/gui.rs` 将按钮与 API 调用关联 - [ ] **Step 1: 添加异步任务处理** 在按钮点击时调用 API - [ ] **Step 2: 添加 VM 状态刷新** 刷新按钮获取最新状态 - [ ] **Step 3: 测试完整流程** 编译运行测试 --- ### Task 6: 编译发布版本 **Files:** - Modify: `Cargo.toml` 静态链接配置 - [ ] **Step 1: 更新 Cargo.toml 添加静态链接** ```toml [dependencies] rustls = "0.23" flate2 = "1.0" [target.x86_64-pc-windows-gnu] rustflags = "-C link-args=-static" ``` - [ ] **Step 2: 编译发布版本** Run: `cargo build --release --target x86_64-pc-windows-gnu` - [ ] **Step 3: 验证单个 exe** 检查是否生成单个 exe 文件 --- **Plan complete and saved to `docs/superpowers/plans/2026-04-10-proxmox-vm-gui.md`. Two execution options:** **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints **Which approach?**