feat: 添加节点列表获取和选择功能

This commit is contained in:
xiaji
2026-04-13 23:35:29 +08:00
parent 49dffc796c
commit 0e08df76a4
2 changed files with 128 additions and 2 deletions

View File

@@ -19,6 +19,19 @@ pub struct ApiResponse<T> {
pub data: Option<T>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeInfo {
pub node: String,
pub status: String,
pub type_info: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterResources {
pub node: Option<String>,
pub type_info: Option<String>,
}
impl ProxmoxClient {
pub fn new(host: &str, port: u16, token_id: &str, token_secret: &str) -> Self {
let base_url = format!("https://{}:{}/api2/json", host, port);
@@ -37,6 +50,74 @@ impl ProxmoxClient {
format!("PVEAPIToken={}={}", self.token_id, self.token_secret)
}
pub async fn get_nodes(&self) -> Result<Vec<String>, String> {
let url = format!("{}/nodes", self.base_url);
println!("[API] 获取节点列表: GET {}", url);
let resp = self.client
.get(&url)
.header("Authorization", self.auth_header())
.send()
.await
.map_err(|e| {
let err = format!("网络错误: {}", e);
println!("[API] {}", err);
err
})?;
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
println!("[API] HTTP错误 {}: {}", status, body);
return Err(format!("HTTP {}: {}", status, body));
}
#[derive(Deserialize)]
struct NodeResponse {
data: Option<Vec<NodeInfo>>,
}
let response: NodeResponse = resp.json().await.map_err(|e| e.to_string())?;
let nodes = response.data
.unwrap_or_default()
.into_iter()
.map(|n| n.node)
.collect();
println!("[API] 节点列表: {:?}", nodes);
Ok(nodes)
}
pub async fn get_cluster_resources(&self) -> Result<Vec<ClusterResources>, String> {
let url = format!("{}/cluster/resources", self.base_url);
println!("[API] 获取集群资源: GET {}", url);
let resp = self.client
.get(&url)
.header("Authorization", self.auth_header())
.send()
.await
.map_err(|e| {
let err = format!("网络错误: {}", e);
println!("[API] {}", err);
err
})?;
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
println!("[API] HTTP错误 {}: {}", status, body);
return Err(format!("HTTP {}: {}", status, body));
}
#[derive(Deserialize)]
struct ResourceResponse {
data: Option<Vec<ClusterResources>>,
}
let response: ResourceResponse = resp.json().await.map_err(|e| e.to_string())?;
Ok(response.data.unwrap_or_default())
}
pub async fn get_vm_status(&self, node: &str, vm_id: u32) -> Result<String, String> {
let url = format!("{}/nodes/{}/qemu/{}/status/current", self.base_url, node, vm_id);
println!("[API] 获取状态: GET {}", url);

View File

@@ -129,6 +129,12 @@ pub struct SettingsData {
pub token_secret: String,
}
#[derive(Default)]
pub struct NodesData {
pub nodes: Vec<String>,
pub selected_index: usize,
}
pub fn gui_run() {
let options = NativeOptions {
viewport: egui::ViewportBuilder::default()
@@ -162,6 +168,7 @@ struct App {
state: SharedState,
show_settings: bool,
settings: SettingsData,
nodes: NodesData,
}
impl App {
@@ -178,6 +185,7 @@ impl App {
state: Arc::new(RwLock::new(AppState::new(&config))),
show_settings: false,
settings,
nodes: NodesData::default(),
}
}
}
@@ -219,8 +227,45 @@ impl eframe::App for App {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("节点:");
ui.text_edit_singleline(&mut st.node);
ui.label("← 请输入正确的节点名称");
if self.nodes.nodes.is_empty() {
ui.label("无节点");
} else {
egui::ComboBox::from_id_salt("node_combo")
.selected_text(&self.nodes.nodes[self.nodes.selected_index.min(self.nodes.nodes.len().saturating_sub(1))])
.show_ui(ui, |ui| {
for (i, node) in self.nodes.nodes.iter().enumerate() {
ui.selectable_value(&mut self.nodes.selected_index, i, node);
}
});
}
if ui.button("🔄").clicked() {
let client = st.client.clone();
let state_clone = state.clone();
st.add_log("正在获取节点列表...");
ctx.request_repaint();
thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let client = client.lock().unwrap();
if let Some(c) = client.as_ref() {
match c.get_nodes().await {
Ok(nodes) => {
if !nodes.is_empty() {
state_clone.write().unwrap().add_log(&format!("找到节点: {:?}", nodes));
} else {
state_clone.write().unwrap().add_log("未找到节点");
}
}
Err(e) => {
state_clone.write().unwrap().add_log(&format!("获取节点失败: {}", e));
}
}
} else {
state_clone.write().unwrap().add_log("未连接到服务器");
}
});
});
}
});
ui.add_space(4.0);
ui.horizontal(|ui| {