fix(proxmox): remove double-unwrap of Proxmox data envelope across all modules
handle_response() in client.rs already strips the {"data":...} wrapper
before returning to callers. Every proxmox module was calling .get("data")
a second time on the already-unwrapped Value, which always returned None
and caused all API responses to silently yield empty results or errors.
vm.rs had an additional bug: list_vms used POST on cluster/resources (a
GET-only endpoint) and dropped VMs with no cpu field via filter_map ?
instead of unwrap_or(0.0). Both corrected.
Affected modules: vm, ceph, ceph_cluster, certificates, acme, firewall,
sdn, ha, apt, updates, updates_ext, tasks, migration, metrics, shell,
auth_realm, views, backup — 18 files, 19 functions.
426 Rust tests pass. clippy -D warnings clean. tsc --noEmit clean.
This commit is contained in:
parent
b091602741
commit
9687f97d7c
@ -44,7 +44,7 @@ pub async fn list_acme_accounts(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list ACME accounts: {}", e))?;
|
||||
|
||||
if let Some(accounts) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(accounts) = response.as_array() {
|
||||
let account_list: Vec<AcmeAccount> = accounts
|
||||
.iter()
|
||||
.filter_map(|account| {
|
||||
@ -94,7 +94,8 @@ pub async fn register_acme_account(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to register ACME account: {}", e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -117,8 +118,6 @@ pub async fn register_acme_account(
|
||||
status,
|
||||
created_at,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +133,7 @@ pub async fn get_acme_challenges(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get ACME challenges for {}: {}", domain, e))?;
|
||||
|
||||
if let Some(challenges) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(challenges) = response.as_array() {
|
||||
let challenge_list: Vec<AcmeChallenge> = challenges
|
||||
.iter()
|
||||
.filter_map(|challenge| {
|
||||
@ -191,7 +190,8 @@ pub async fn request_certificate(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to request ACME certificate: {}", e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -230,8 +230,6 @@ pub async fn request_certificate(
|
||||
expires_at,
|
||||
issuer,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +245,8 @@ pub async fn get_certificate_details(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get ACME certificate {}: {}", cert_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -286,8 +285,6 @@ pub async fn get_certificate_details(
|
||||
expires_at,
|
||||
issuer,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,8 +37,7 @@ pub async fn list_apt_updates(
|
||||
.map_err(|e| format!("Failed to list APT updates: {}", e))?;
|
||||
|
||||
let updates: Vec<APTUpdate> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|update| {
|
||||
@ -97,7 +96,9 @@ pub async fn list_apt_repositories(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list APT repositories: {}", e))?;
|
||||
|
||||
if let Some(repos) = response.get("data").and_then(|d| d.as_array()) {
|
||||
let repos = response
|
||||
.as_array()
|
||||
.ok_or_else(|| "Invalid response format: expected array".to_string())?;
|
||||
let repo_list: Vec<APTRepository> = repos
|
||||
.iter()
|
||||
.filter_map(|repo| {
|
||||
@ -135,9 +136,6 @@ pub async fn list_apt_repositories(
|
||||
.collect();
|
||||
|
||||
Ok(repo_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add APT repository
|
||||
|
||||
@ -62,7 +62,7 @@ pub async fn list_auth_realms(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list authentication realms: {}", e))?;
|
||||
|
||||
if let Some(realms) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(realms) = response.as_array() {
|
||||
let realm_list: Vec<AuthRealm> = realms
|
||||
.iter()
|
||||
.filter_map(|realm| {
|
||||
@ -89,7 +89,7 @@ pub async fn list_auth_realms(
|
||||
|
||||
Ok(realm_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ pub async fn list_backup_jobs(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list backup jobs: {}", e))?;
|
||||
|
||||
if let Some(jobs) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(jobs) = response.as_array() {
|
||||
let backup_jobs: Vec<BackupJob> = jobs
|
||||
.iter()
|
||||
.filter_map(|job| {
|
||||
@ -64,7 +64,7 @@ pub async fn list_backup_jobs(
|
||||
|
||||
Ok(backup_jobs)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ pub async fn list_datastores(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list datastores: {}", e))?;
|
||||
|
||||
if let Some(datastores) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(datastores) = response.as_array() {
|
||||
let datastore_list: Vec<DatastoreInfo> = datastores
|
||||
.iter()
|
||||
.filter_map(|ds| {
|
||||
@ -183,7 +183,7 @@ pub async fn list_datastores(
|
||||
|
||||
Ok(datastore_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ pub async fn get_datastore_status(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get datastore status: {}", e))?;
|
||||
|
||||
let ds = response.get("data").ok_or("Invalid response format")?;
|
||||
let ds = &response;
|
||||
|
||||
Ok(DatastoreInfo {
|
||||
datastore: datastore.to_string(),
|
||||
@ -229,10 +229,10 @@ pub async fn list_backup_snapshots(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list backup snapshots: {}", e))?;
|
||||
|
||||
if let Some(snapshots) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(snapshots) = response.as_array() {
|
||||
Ok(snapshots.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ pub async fn list_pools(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list Ceph pools: {}", e))?;
|
||||
|
||||
if let Some(pools) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(pools) = response.as_array() {
|
||||
let pool_list: Vec<CephPool> = pools
|
||||
.iter()
|
||||
.filter_map(|pool| {
|
||||
@ -87,7 +87,7 @@ pub async fn list_pools(
|
||||
|
||||
Ok(pool_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ pub async fn list_osds(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list Ceph OSDs: {}", e))?;
|
||||
|
||||
if let Some(osds) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(osds) = response.as_array() {
|
||||
let osd_list: Vec<CephOsd> = osds
|
||||
.iter()
|
||||
.filter_map(|osd| {
|
||||
@ -179,7 +179,7 @@ pub async fn list_osds(
|
||||
|
||||
Ok(osd_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,10 +241,10 @@ pub async fn list_mds(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list Ceph MDS: {}", e))?;
|
||||
|
||||
if let Some(mds) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(mds) = response.as_array() {
|
||||
Ok(mds.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,10 +287,10 @@ pub async fn list_rbd(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list RBD images in pool {}: {}", pool, e))?;
|
||||
|
||||
if let Some(images) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(images) = response.as_array() {
|
||||
Ok(images.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,7 +415,7 @@ pub async fn list_monitors(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list Ceph monitors: {}", e))?;
|
||||
|
||||
if let Some(mons) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(mons) = response.as_array() {
|
||||
let mon_list: Vec<CephMonitor> = mons
|
||||
.iter()
|
||||
.filter_map(|mon| {
|
||||
@ -439,7 +439,7 @@ pub async fn list_monitors(
|
||||
|
||||
Ok(mon_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,7 +479,7 @@ pub async fn get_ceph_health(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Ceph health: {}", e))?;
|
||||
|
||||
let health = response.get("data").ok_or("Invalid response format")?;
|
||||
let health = &response;
|
||||
|
||||
let details: Vec<String> = health
|
||||
.get("details")
|
||||
|
||||
@ -45,7 +45,7 @@ pub async fn list_ceph_clusters(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list Ceph clusters: {}", e))?;
|
||||
|
||||
if let Some(clusters) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(clusters) = response.as_array() {
|
||||
let cluster_list: Vec<CephCluster> = clusters
|
||||
.iter()
|
||||
.filter_map(|cluster| {
|
||||
@ -150,7 +150,7 @@ pub async fn list_ceph_clusters(
|
||||
|
||||
Ok(cluster_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +166,8 @@ pub async fn get_ceph_cluster_status(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Ceph cluster {} status: {}", cluster_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("cluster_id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -195,8 +196,6 @@ pub async fn get_ceph_cluster_status(
|
||||
osd_map,
|
||||
pg_map,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,8 @@ pub async fn upload_certificate(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to upload certificate: {}", e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -110,8 +111,6 @@ pub async fn upload_certificate(
|
||||
signature_algorithm,
|
||||
san,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +126,8 @@ pub async fn get_certificate(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get certificate {}: {}", cert_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -192,8 +192,6 @@ pub async fn get_certificate(
|
||||
signature_algorithm,
|
||||
san,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,7 +206,7 @@ pub async fn list_certificates(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list certificates: {}", e))?;
|
||||
|
||||
if let Some(certs) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(certs) = response.as_array() {
|
||||
let cert_list: Vec<Certificate> = certs
|
||||
.iter()
|
||||
.filter_map(|cert| {
|
||||
@ -307,7 +305,7 @@ pub async fn list_node_certificates(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list node certificates for {}: {}", node, e))?;
|
||||
|
||||
if let Some(certs) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(certs) = response.as_array() {
|
||||
let cert_list: Vec<Certificate> = certs
|
||||
.iter()
|
||||
.filter_map(|cert| {
|
||||
@ -401,7 +399,8 @@ pub async fn upload_node_certificate(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to upload node certificate for {}: {}", node, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -466,7 +465,5 @@ pub async fn upload_node_certificate(
|
||||
signature_algorithm,
|
||||
san,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ pub async fn list_firewall_rules(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list firewall rules: {}", e))?;
|
||||
|
||||
if let Some(rules) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(rules) = response.as_array() {
|
||||
let rule_list: Vec<FirewallRule> = rules
|
||||
.iter()
|
||||
.filter_map(|rule| {
|
||||
@ -68,7 +68,7 @@ pub async fn list_firewall_rules(
|
||||
|
||||
Ok(rule_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,14 +191,12 @@ pub async fn get_firewall_status(
|
||||
.map_err(|e| format!("Failed to get firewall options: {}", e))?;
|
||||
|
||||
let enabled = options_response
|
||||
.get("data")
|
||||
.and_then(|d| d.get("enabled"))
|
||||
.get("enabled")
|
||||
.and_then(|e| e.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let rules: Vec<FirewallRule> = rules_response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.as_array()
|
||||
.unwrap_or(&Vec::new())
|
||||
.iter()
|
||||
.filter_map(|rule| {
|
||||
@ -264,10 +262,10 @@ pub async fn list_firewall_zones(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list firewall zones: {}", e))?;
|
||||
|
||||
if let Some(zones) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(zones) = response.as_array() {
|
||||
Ok(zones.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ pub async fn list_ha_groups(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list HA groups: {}", e))?;
|
||||
|
||||
if let Some(groups) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(groups) = response.as_array() {
|
||||
let group_list: Vec<HaGroup> = groups
|
||||
.iter()
|
||||
.filter_map(|group| {
|
||||
@ -68,7 +68,7 @@ pub async fn list_ha_groups(
|
||||
|
||||
Ok(group_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ pub async fn list_ha_resources(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list HA resources: {}", e))?;
|
||||
|
||||
if let Some(resources) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(resources) = response.as_array() {
|
||||
let resource_list: Vec<HaResource> = resources
|
||||
.iter()
|
||||
.filter_map(|resource| {
|
||||
@ -179,7 +179,7 @@ pub async fn list_ha_resources(
|
||||
|
||||
Ok(resource_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,8 @@ pub async fn get_node_metrics(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get node metrics for {}: {}", node, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let cpu = data.get("cpu").and_then(|c| c.as_f64()).unwrap_or(0.0);
|
||||
let memory = data.get("memory").and_then(|m| m.as_f64()).unwrap_or(0.0);
|
||||
let disk = data.get("disk").and_then(|d| d.as_f64()).unwrap_or(0.0);
|
||||
@ -52,8 +53,6 @@ pub async fn get_node_metrics(
|
||||
load,
|
||||
uptime,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +67,7 @@ pub async fn list_nodes(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list nodes: {}", e))?;
|
||||
|
||||
if let Some(resources) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(resources) = response.as_array() {
|
||||
let node_list: Vec<NodeStatus> = resources
|
||||
.iter()
|
||||
.filter_map(|resource| {
|
||||
@ -107,7 +106,7 @@ pub async fn list_nodes(
|
||||
|
||||
Ok(node_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,7 +53,8 @@ pub async fn migrate_vm(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to migrate VM {}: {}", vm_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let task_id = data
|
||||
.get("taskid")
|
||||
.and_then(|t| t.as_str())
|
||||
@ -80,8 +81,6 @@ pub async fn migrate_vm(
|
||||
end_time: None,
|
||||
error: None,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +96,7 @@ pub async fn list_migration_status(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list migration tasks for node {}: {}", node, e))?;
|
||||
|
||||
if let Some(tasks) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(tasks) = response.as_array() {
|
||||
let task_list: Vec<MigrationTask> = tasks
|
||||
.iter()
|
||||
.filter_map(|task| {
|
||||
@ -145,7 +144,7 @@ pub async fn list_migration_status(
|
||||
|
||||
Ok(task_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +161,8 @@ pub async fn get_migration_task_status(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get migration task {}: {}", task_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let status = data
|
||||
.get("status")
|
||||
.and_then(|s| s.as_str())
|
||||
@ -187,8 +187,6 @@ pub async fn get_migration_task_status(
|
||||
bytes_remaining,
|
||||
downtime,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ pub async fn list_evpn_zones(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list EVPN zones: {}", e))?;
|
||||
|
||||
if let Some(zones) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(zones) = response.as_array() {
|
||||
let zone_list: Vec<EvpnZone> = zones
|
||||
.iter()
|
||||
.filter_map(|zone| {
|
||||
@ -68,7 +68,7 @@ pub async fn list_evpn_zones(
|
||||
|
||||
Ok(zone_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ pub async fn list_vnets(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list virtual networks: {}", e))?;
|
||||
|
||||
if let Some(vnets) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(vnets) = response.as_array() {
|
||||
let vnet_list: Vec<VirtualNetwork> = vnets
|
||||
.iter()
|
||||
.filter_map(|vnet| {
|
||||
@ -166,7 +166,7 @@ pub async fn list_vnets(
|
||||
|
||||
Ok(vnet_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,10 +252,10 @@ pub async fn list_dhcp_leases(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list DHCP leases for vnet {}: {}", vnet, e))?;
|
||||
|
||||
if let Some(leases) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(leases) = response.as_array() {
|
||||
Ok(leases.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Err("Invalid response format".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,8 @@ pub async fn get_shell_ticket(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get shell ticket for remote {}: {}", remote, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let ticket_value = data
|
||||
.get("ticket")
|
||||
.and_then(|t| t.as_str())
|
||||
@ -53,8 +54,6 @@ pub async fn get_shell_ticket(
|
||||
expires,
|
||||
permissions,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +68,7 @@ pub async fn validate_shell_ticket(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to validate shell ticket: {}", e))?;
|
||||
|
||||
Ok(response.get("data").is_some())
|
||||
Ok(!response.is_null())
|
||||
}
|
||||
|
||||
/// Get shell WebSocket URL
|
||||
|
||||
@ -38,7 +38,7 @@ pub async fn list_tasks(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list tasks for node {}: {}", node, e))?;
|
||||
|
||||
if let Some(tasks) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(tasks) = response.as_array() {
|
||||
let task_list: Vec<TaskInfo> = tasks
|
||||
.iter()
|
||||
.filter_map(|task| {
|
||||
@ -97,7 +97,7 @@ pub async fn list_tasks(
|
||||
|
||||
Ok(task_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +114,8 @@ pub async fn get_task_status(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get task {}: {}", task_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -169,8 +170,6 @@ pub async fn get_task_status(
|
||||
exit_status,
|
||||
description,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +205,7 @@ pub async fn get_task_log(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get task log for {}: {}", task_id, e))?;
|
||||
|
||||
if let Some(log_entries) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(log_entries) = response.as_array() {
|
||||
let log_list: Vec<TaskLogEntry> = log_entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
@ -236,7 +235,7 @@ pub async fn get_task_log(
|
||||
|
||||
Ok(log_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,7 +257,8 @@ pub async fn forward_task(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to forward task {}: {}", task_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -283,7 +283,5 @@ pub async fn forward_task(
|
||||
exit_status: None,
|
||||
description: format!("Forwarded to {}", target_node),
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,9 +34,9 @@ pub async fn check_updates(
|
||||
let checked_at = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
let updates: Vec<UpdateInfo> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.unwrap_or(&Vec::new())
|
||||
.as_array()
|
||||
.map(|arr| arr.as_slice())
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.filter_map(|update| {
|
||||
let package = update.get("package")?.as_str()?.to_string();
|
||||
@ -74,8 +74,7 @@ pub async fn list_updates(
|
||||
.map_err(|e| format!("Failed to list updates: {}", e))?;
|
||||
|
||||
let updates: Vec<UpdateInfo> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|update| {
|
||||
@ -153,11 +152,10 @@ pub async fn get_update_history(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get update history: {}", e))?;
|
||||
|
||||
if let Some(history) = response.get("data").and_then(|d| d.as_array()) {
|
||||
Ok(history.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
response
|
||||
.as_array()
|
||||
.map(|arr| arr.to_vec())
|
||||
.ok_or_else(|| "Invalid response format: expected array".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -33,8 +33,7 @@ pub async fn list_updates_all_remotes(
|
||||
.map_err(|e| format!("Failed to list updates from all remotes: {}", e))?;
|
||||
|
||||
let updates: Vec<RemoteUpdateInfo> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|update| {
|
||||
@ -122,14 +121,10 @@ pub async fn list_pve_remotes(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list PVE remotes: {}", e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
if let Some(arr) = data.as_array() {
|
||||
if let Some(arr) = response.as_array() {
|
||||
Ok(arr.to_vec())
|
||||
} else {
|
||||
Ok(vec![data.clone()])
|
||||
}
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![response])
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,8 +141,7 @@ pub async fn check_remote_updates(
|
||||
.map_err(|e| format!("Failed to check updates for remote {}: {}", remote, e))?;
|
||||
|
||||
let updates: Vec<RemoteUpdateInfo> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|update| {
|
||||
|
||||
@ -46,7 +46,7 @@ pub async fn list_views(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list dashboard views: {}", e))?;
|
||||
|
||||
if let Some(views) = response.get("data").and_then(|d| d.as_array()) {
|
||||
if let Some(views) = response.as_array() {
|
||||
let view_list: Vec<DashboardView> = views
|
||||
.iter()
|
||||
.filter_map(|view| {
|
||||
@ -143,7 +143,7 @@ pub async fn list_views(
|
||||
|
||||
Ok(view_list)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +245,8 @@ pub async fn get_view(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get dashboard view {}: {}", view_id, e))?;
|
||||
|
||||
if let Some(data) = response.get("data") {
|
||||
{
|
||||
let data = &response;
|
||||
let id = data
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
@ -342,7 +343,5 @@ pub async fn get_view(
|
||||
created_at,
|
||||
updated_at,
|
||||
})
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,27 +131,30 @@ pub async fn list_vms(
|
||||
client: &crate::proxmox::client::ProxmoxClient,
|
||||
ticket: &str,
|
||||
) -> Result<Vec<VmInfo>, String> {
|
||||
let path = "cluster/resources";
|
||||
let params = serde_json::json!({
|
||||
"type": "qemu"
|
||||
});
|
||||
|
||||
// cluster/resources is GET-only; handle_response strips the {"data":[...]} envelope.
|
||||
let response: serde_json::Value = client
|
||||
.post(path, ¶ms, Some(ticket))
|
||||
.get("cluster/resources?type=vm", Some(ticket))
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list VMs: {}", e))?;
|
||||
|
||||
// Parse the response to extract VM info
|
||||
// The API returns a list of resources in the "data" field
|
||||
if let Some(resources) = response.get("data").and_then(|d| d.as_array()) {
|
||||
let resources = response
|
||||
.as_array()
|
||||
.ok_or_else(|| "Invalid response format".to_string())?;
|
||||
|
||||
let vms: Vec<VmInfo> = resources
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
let vmid = r.get("vmid")?.as_u64()?;
|
||||
let node = r.get("node")?.as_str()?.to_string();
|
||||
let name = r.get("name")?.as_str().map(|s| s.to_string());
|
||||
let status = r.get("status")?.as_str()?.to_string();
|
||||
let cpu = r.get("cpu")?.as_f64()?;
|
||||
// Only include qemu VMs (not LXC containers which also appear in cluster/resources?type=vm)
|
||||
let resource_type = r.get("type").and_then(|t| t.as_str()).unwrap_or("");
|
||||
if resource_type != "qemu" {
|
||||
return None;
|
||||
}
|
||||
let name = r.get("name").and_then(|n| n.as_str()).map(|s| s.to_string());
|
||||
let status = r.get("status").and_then(|s| s.as_str()).unwrap_or("unknown").to_string();
|
||||
// cpu may be absent for stopped VMs
|
||||
let cpu = r.get("cpu").and_then(|c| c.as_f64()).unwrap_or(0.0);
|
||||
|
||||
Some(VmInfo {
|
||||
id: vmid as u32,
|
||||
@ -163,10 +166,7 @@ pub async fn list_vms(
|
||||
uptime: r.get("uptime").and_then(|u| u.as_u64()).unwrap_or(0),
|
||||
node,
|
||||
template: r.get("template").and_then(|t| t.as_bool()),
|
||||
agent: r
|
||||
.get("agent")
|
||||
.and_then(|a| a.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
agent: r.get("agent").and_then(|a| a.as_str()).map(|s| s.to_string()),
|
||||
mem: r.get("mem").and_then(|m| m.as_u64()),
|
||||
max_mem: r.get("maxmem").and_then(|m| m.as_u64()),
|
||||
max_disk: r.get("maxdisk").and_then(|d| d.as_u64()),
|
||||
@ -179,9 +179,6 @@ pub async fn list_vms(
|
||||
.collect();
|
||||
|
||||
Ok(vms)
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get VM details
|
||||
@ -197,8 +194,7 @@ pub async fn get_vm(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get VM {}: {}", vmid, e))?;
|
||||
|
||||
// Parse the response to extract VM info
|
||||
let vm = response.get("data").ok_or("Invalid response format")?;
|
||||
let vm = &response;
|
||||
|
||||
Ok(VmInfo {
|
||||
id: vmid,
|
||||
@ -415,11 +411,10 @@ pub async fn list_snapshots(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list snapshots for VM {}: {}", vmid, e))?;
|
||||
|
||||
if let Some(snapshots) = response.get("data").and_then(|d| d.as_array()) {
|
||||
Ok(snapshots.to_vec())
|
||||
} else {
|
||||
Err("Invalid response format: missing 'data' field".to_string())
|
||||
}
|
||||
response
|
||||
.as_array()
|
||||
.map(|arr| arr.to_vec())
|
||||
.ok_or_else(|| "Invalid response format".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user