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
a72b69ec34
commit
34ddae7eb2
@ -44,7 +44,7 @@ pub async fn list_acme_accounts(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list ACME accounts: {}", e))?;
|
.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
|
let account_list: Vec<AcmeAccount> = accounts
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|account| {
|
.filter_map(|account| {
|
||||||
@ -94,7 +94,8 @@ pub async fn register_acme_account(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to register ACME account: {}", e))?;
|
.map_err(|e| format!("Failed to register ACME account: {}", e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -117,8 +118,6 @@ pub async fn register_acme_account(
|
|||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +133,7 @@ pub async fn get_acme_challenges(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get ACME challenges for {}: {}", domain, e))?;
|
.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
|
let challenge_list: Vec<AcmeChallenge> = challenges
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|challenge| {
|
.filter_map(|challenge| {
|
||||||
@ -191,7 +190,8 @@ pub async fn request_certificate(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to request ACME certificate: {}", e))?;
|
.map_err(|e| format!("Failed to request ACME certificate: {}", e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -230,8 +230,6 @@ pub async fn request_certificate(
|
|||||||
expires_at,
|
expires_at,
|
||||||
issuer,
|
issuer,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +245,8 @@ pub async fn get_certificate_details(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get ACME certificate {}: {}", cert_id, e))?;
|
.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
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -286,8 +285,6 @@ pub async fn get_certificate_details(
|
|||||||
expires_at,
|
expires_at,
|
||||||
issuer,
|
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))?;
|
.map_err(|e| format!("Failed to list APT updates: {}", e))?;
|
||||||
|
|
||||||
let updates: Vec<APTUpdate> = response
|
let updates: Vec<APTUpdate> = response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.map(|arr| {
|
.map(|arr| {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.filter_map(|update| {
|
.filter_map(|update| {
|
||||||
@ -97,47 +96,46 @@ pub async fn list_apt_repositories(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list APT repositories: {}", e))?;
|
.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
|
||||||
let repo_list: Vec<APTRepository> = repos
|
.as_array()
|
||||||
.iter()
|
.ok_or_else(|| "Invalid response format: expected array".to_string())?;
|
||||||
.filter_map(|repo| {
|
let repo_list: Vec<APTRepository> = repos
|
||||||
let id = repo.get("id")?.as_str()?.to_string();
|
.iter()
|
||||||
let url = repo.get("url")?.as_str().unwrap_or("").to_string();
|
.filter_map(|repo| {
|
||||||
let distribution = repo
|
let id = repo.get("id")?.as_str()?.to_string();
|
||||||
.get("distribution")
|
let url = repo.get("url")?.as_str().unwrap_or("").to_string();
|
||||||
.and_then(|d| d.as_str())
|
let distribution = repo
|
||||||
.unwrap_or("")
|
.get("distribution")
|
||||||
.to_string();
|
.and_then(|d| d.as_str())
|
||||||
let component = repo
|
.unwrap_or("")
|
||||||
.get("component")
|
.to_string();
|
||||||
.and_then(|c| c.as_str())
|
let component = repo
|
||||||
.unwrap_or("")
|
.get("component")
|
||||||
.to_string();
|
.and_then(|c| c.as_str())
|
||||||
let enabled = repo
|
.unwrap_or("")
|
||||||
.get("enabled")
|
.to_string();
|
||||||
.and_then(|e| e.as_bool())
|
let enabled = repo
|
||||||
.unwrap_or(true);
|
.get("enabled")
|
||||||
let type_ = repo
|
.and_then(|e| e.as_bool())
|
||||||
.get("type")
|
.unwrap_or(true);
|
||||||
.and_then(|t| t.as_str())
|
let type_ = repo
|
||||||
.unwrap_or("deb")
|
.get("type")
|
||||||
.to_string();
|
.and_then(|t| t.as_str())
|
||||||
|
.unwrap_or("deb")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
Some(APTRepository {
|
Some(APTRepository {
|
||||||
repository_id: id,
|
repository_id: id,
|
||||||
url,
|
url,
|
||||||
distribution,
|
distribution,
|
||||||
component,
|
component,
|
||||||
enabled,
|
enabled,
|
||||||
type_,
|
type_,
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(repo_list)
|
Ok(repo_list)
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add APT repository
|
/// Add APT repository
|
||||||
|
|||||||
@ -62,7 +62,7 @@ pub async fn list_auth_realms(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list authentication realms: {}", e))?;
|
.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
|
let realm_list: Vec<AuthRealm> = realms
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|realm| {
|
.filter_map(|realm| {
|
||||||
@ -89,7 +89,7 @@ pub async fn list_auth_realms(
|
|||||||
|
|
||||||
Ok(realm_list)
|
Ok(realm_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ pub async fn list_backup_jobs(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list backup jobs: {}", e))?;
|
.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
|
let backup_jobs: Vec<BackupJob> = jobs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|job| {
|
.filter_map(|job| {
|
||||||
@ -64,7 +64,7 @@ pub async fn list_backup_jobs(
|
|||||||
|
|
||||||
Ok(backup_jobs)
|
Ok(backup_jobs)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ pub async fn list_datastores(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list datastores: {}", e))?;
|
.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
|
let datastore_list: Vec<DatastoreInfo> = datastores
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|ds| {
|
.filter_map(|ds| {
|
||||||
@ -183,7 +183,7 @@ pub async fn list_datastores(
|
|||||||
|
|
||||||
Ok(datastore_list)
|
Ok(datastore_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ pub async fn get_datastore_status(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get datastore status: {}", e))?;
|
.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 {
|
Ok(DatastoreInfo {
|
||||||
datastore: datastore.to_string(),
|
datastore: datastore.to_string(),
|
||||||
@ -229,10 +229,10 @@ pub async fn list_backup_snapshots(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list backup snapshots: {}", e))?;
|
.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())
|
Ok(snapshots.to_vec())
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ pub async fn list_pools(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list Ceph pools: {}", e))?;
|
.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
|
let pool_list: Vec<CephPool> = pools
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pool| {
|
.filter_map(|pool| {
|
||||||
@ -87,7 +87,7 @@ pub async fn list_pools(
|
|||||||
|
|
||||||
Ok(pool_list)
|
Ok(pool_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list Ceph OSDs: {}", e))?;
|
.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
|
let osd_list: Vec<CephOsd> = osds
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|osd| {
|
.filter_map(|osd| {
|
||||||
@ -179,7 +179,7 @@ pub async fn list_osds(
|
|||||||
|
|
||||||
Ok(osd_list)
|
Ok(osd_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list Ceph MDS: {}", e))?;
|
.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())
|
Ok(mds.to_vec())
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list RBD images in pool {}: {}", pool, e))?;
|
.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())
|
Ok(images.to_vec())
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list Ceph monitors: {}", e))?;
|
.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
|
let mon_list: Vec<CephMonitor> = mons
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|mon| {
|
.filter_map(|mon| {
|
||||||
@ -439,7 +439,7 @@ pub async fn list_monitors(
|
|||||||
|
|
||||||
Ok(mon_list)
|
Ok(mon_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to get Ceph health: {}", e))?;
|
.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
|
let details: Vec<String> = health
|
||||||
.get("details")
|
.get("details")
|
||||||
|
|||||||
@ -45,7 +45,7 @@ pub async fn list_ceph_clusters(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list Ceph clusters: {}", e))?;
|
.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
|
let cluster_list: Vec<CephCluster> = clusters
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|cluster| {
|
.filter_map(|cluster| {
|
||||||
@ -150,7 +150,7 @@ pub async fn list_ceph_clusters(
|
|||||||
|
|
||||||
Ok(cluster_list)
|
Ok(cluster_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to get Ceph cluster {} status: {}", cluster_id, e))?;
|
.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
|
let id = data
|
||||||
.get("cluster_id")
|
.get("cluster_id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -195,8 +196,6 @@ pub async fn get_ceph_cluster_status(
|
|||||||
osd_map,
|
osd_map,
|
||||||
pg_map,
|
pg_map,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,8 @@ pub async fn upload_certificate(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to upload certificate: {}", e))?;
|
.map_err(|e| format!("Failed to upload certificate: {}", e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -110,8 +111,6 @@ pub async fn upload_certificate(
|
|||||||
signature_algorithm,
|
signature_algorithm,
|
||||||
san,
|
san,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +126,8 @@ pub async fn get_certificate(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get certificate {}: {}", cert_id, e))?;
|
.map_err(|e| format!("Failed to get certificate {}: {}", cert_id, e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -192,8 +192,6 @@ pub async fn get_certificate(
|
|||||||
signature_algorithm,
|
signature_algorithm,
|
||||||
san,
|
san,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +206,7 @@ pub async fn list_certificates(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list certificates: {}", e))?;
|
.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
|
let cert_list: Vec<Certificate> = certs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|cert| {
|
.filter_map(|cert| {
|
||||||
@ -307,7 +305,7 @@ pub async fn list_node_certificates(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list node certificates for {}: {}", node, e))?;
|
.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
|
let cert_list: Vec<Certificate> = certs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|cert| {
|
.filter_map(|cert| {
|
||||||
@ -401,7 +399,8 @@ pub async fn upload_node_certificate(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to upload node certificate for {}: {}", node, e))?;
|
.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
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -466,7 +465,5 @@ pub async fn upload_node_certificate(
|
|||||||
signature_algorithm,
|
signature_algorithm,
|
||||||
san,
|
san,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ pub async fn list_firewall_rules(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list firewall rules: {}", e))?;
|
.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
|
let rule_list: Vec<FirewallRule> = rules
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|rule| {
|
.filter_map(|rule| {
|
||||||
@ -68,7 +68,7 @@ pub async fn list_firewall_rules(
|
|||||||
|
|
||||||
Ok(rule_list)
|
Ok(rule_list)
|
||||||
} else {
|
} 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))?;
|
.map_err(|e| format!("Failed to get firewall options: {}", e))?;
|
||||||
|
|
||||||
let enabled = options_response
|
let enabled = options_response
|
||||||
.get("data")
|
.get("enabled")
|
||||||
.and_then(|d| d.get("enabled"))
|
|
||||||
.and_then(|e| e.as_bool())
|
.and_then(|e| e.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let rules: Vec<FirewallRule> = rules_response
|
let rules: Vec<FirewallRule> = rules_response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.unwrap_or(&Vec::new())
|
.unwrap_or(&Vec::new())
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|rule| {
|
.filter_map(|rule| {
|
||||||
@ -264,10 +262,10 @@ pub async fn list_firewall_zones(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list firewall zones: {}", e))?;
|
.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())
|
Ok(zones.to_vec())
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list HA groups: {}", e))?;
|
.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
|
let group_list: Vec<HaGroup> = groups
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|group| {
|
.filter_map(|group| {
|
||||||
@ -68,7 +68,7 @@ pub async fn list_ha_groups(
|
|||||||
|
|
||||||
Ok(group_list)
|
Ok(group_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list HA resources: {}", e))?;
|
.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
|
let resource_list: Vec<HaResource> = resources
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|resource| {
|
.filter_map(|resource| {
|
||||||
@ -179,7 +179,7 @@ pub async fn list_ha_resources(
|
|||||||
|
|
||||||
Ok(resource_list)
|
Ok(resource_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to get node metrics for {}: {}", node, e))?;
|
.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 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 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);
|
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,
|
load,
|
||||||
uptime,
|
uptime,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ pub async fn list_nodes(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list nodes: {}", e))?;
|
.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
|
let node_list: Vec<NodeStatus> = resources
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|resource| {
|
.filter_map(|resource| {
|
||||||
@ -107,7 +106,7 @@ pub async fn list_nodes(
|
|||||||
|
|
||||||
Ok(node_list)
|
Ok(node_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,8 @@ pub async fn migrate_vm(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to migrate VM {}: {}", vm_id, e))?;
|
.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
|
let task_id = data
|
||||||
.get("taskid")
|
.get("taskid")
|
||||||
.and_then(|t| t.as_str())
|
.and_then(|t| t.as_str())
|
||||||
@ -80,8 +81,6 @@ pub async fn migrate_vm(
|
|||||||
end_time: None,
|
end_time: None,
|
||||||
error: None,
|
error: None,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ pub async fn list_migration_status(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list migration tasks for node {}: {}", node, e))?;
|
.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
|
let task_list: Vec<MigrationTask> = tasks
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|task| {
|
.filter_map(|task| {
|
||||||
@ -145,7 +144,7 @@ pub async fn list_migration_status(
|
|||||||
|
|
||||||
Ok(task_list)
|
Ok(task_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +161,8 @@ pub async fn get_migration_task_status(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get migration task {}: {}", task_id, e))?;
|
.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
|
let status = data
|
||||||
.get("status")
|
.get("status")
|
||||||
.and_then(|s| s.as_str())
|
.and_then(|s| s.as_str())
|
||||||
@ -187,8 +187,6 @@ pub async fn get_migration_task_status(
|
|||||||
bytes_remaining,
|
bytes_remaining,
|
||||||
downtime,
|
downtime,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ pub async fn list_evpn_zones(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list EVPN zones: {}", e))?;
|
.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
|
let zone_list: Vec<EvpnZone> = zones
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|zone| {
|
.filter_map(|zone| {
|
||||||
@ -68,7 +68,7 @@ pub async fn list_evpn_zones(
|
|||||||
|
|
||||||
Ok(zone_list)
|
Ok(zone_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list virtual networks: {}", e))?;
|
.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
|
let vnet_list: Vec<VirtualNetwork> = vnets
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|vnet| {
|
.filter_map(|vnet| {
|
||||||
@ -166,7 +166,7 @@ pub async fn list_vnets(
|
|||||||
|
|
||||||
Ok(vnet_list)
|
Ok(vnet_list)
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to list DHCP leases for vnet {}: {}", vnet, e))?;
|
.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())
|
Ok(leases.to_vec())
|
||||||
} else {
|
} 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
|
.await
|
||||||
.map_err(|e| format!("Failed to get shell ticket for remote {}: {}", remote, e))?;
|
.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
|
let ticket_value = data
|
||||||
.get("ticket")
|
.get("ticket")
|
||||||
.and_then(|t| t.as_str())
|
.and_then(|t| t.as_str())
|
||||||
@ -53,8 +54,6 @@ pub async fn get_shell_ticket(
|
|||||||
expires,
|
expires,
|
||||||
permissions,
|
permissions,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ pub async fn validate_shell_ticket(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to validate shell ticket: {}", e))?;
|
.map_err(|e| format!("Failed to validate shell ticket: {}", e))?;
|
||||||
|
|
||||||
Ok(response.get("data").is_some())
|
Ok(!response.is_null())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get shell WebSocket URL
|
/// Get shell WebSocket URL
|
||||||
|
|||||||
@ -38,7 +38,7 @@ pub async fn list_tasks(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list tasks for node {}: {}", node, e))?;
|
.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
|
let task_list: Vec<TaskInfo> = tasks
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|task| {
|
.filter_map(|task| {
|
||||||
@ -97,7 +97,7 @@ pub async fn list_tasks(
|
|||||||
|
|
||||||
Ok(task_list)
|
Ok(task_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,8 @@ pub async fn get_task_status(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get task {}: {}", task_id, e))?;
|
.map_err(|e| format!("Failed to get task {}: {}", task_id, e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -169,8 +170,6 @@ pub async fn get_task_status(
|
|||||||
exit_status,
|
exit_status,
|
||||||
description,
|
description,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +205,7 @@ pub async fn get_task_log(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get task log for {}: {}", task_id, e))?;
|
.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
|
let log_list: Vec<TaskLogEntry> = log_entries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
@ -236,7 +235,7 @@ pub async fn get_task_log(
|
|||||||
|
|
||||||
Ok(log_list)
|
Ok(log_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +257,8 @@ pub async fn forward_task(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to forward task {}: {}", task_id, e))?;
|
.map_err(|e| format!("Failed to forward task {}: {}", task_id, e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
{
|
||||||
|
let data = &response;
|
||||||
let id = data
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -283,7 +283,5 @@ pub async fn forward_task(
|
|||||||
exit_status: None,
|
exit_status: None,
|
||||||
description: format!("Forwarded to {}", target_node),
|
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 checked_at = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||||
|
|
||||||
let updates: Vec<UpdateInfo> = response
|
let updates: Vec<UpdateInfo> = response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
.map(|arr| arr.as_slice())
|
||||||
.unwrap_or(&Vec::new())
|
.unwrap_or(&[])
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|update| {
|
.filter_map(|update| {
|
||||||
let package = update.get("package")?.as_str()?.to_string();
|
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))?;
|
.map_err(|e| format!("Failed to list updates: {}", e))?;
|
||||||
|
|
||||||
let updates: Vec<UpdateInfo> = response
|
let updates: Vec<UpdateInfo> = response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.map(|arr| {
|
.map(|arr| {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.filter_map(|update| {
|
.filter_map(|update| {
|
||||||
@ -153,11 +152,10 @@ pub async fn get_update_history(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get update history: {}", e))?;
|
.map_err(|e| format!("Failed to get update history: {}", e))?;
|
||||||
|
|
||||||
if let Some(history) = response.get("data").and_then(|d| d.as_array()) {
|
response
|
||||||
Ok(history.to_vec())
|
.as_array()
|
||||||
} else {
|
.map(|arr| arr.to_vec())
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
.ok_or_else(|| "Invalid response format: expected array".to_string())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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))?;
|
.map_err(|e| format!("Failed to list updates from all remotes: {}", e))?;
|
||||||
|
|
||||||
let updates: Vec<RemoteUpdateInfo> = response
|
let updates: Vec<RemoteUpdateInfo> = response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.map(|arr| {
|
.map(|arr| {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.filter_map(|update| {
|
.filter_map(|update| {
|
||||||
@ -122,14 +121,10 @@ pub async fn list_pve_remotes(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list PVE remotes: {}", e))?;
|
.map_err(|e| format!("Failed to list PVE remotes: {}", e))?;
|
||||||
|
|
||||||
if let Some(data) = response.get("data") {
|
if let Some(arr) = response.as_array() {
|
||||||
if let Some(arr) = data.as_array() {
|
Ok(arr.to_vec())
|
||||||
Ok(arr.to_vec())
|
|
||||||
} else {
|
|
||||||
Ok(vec![data.clone()])
|
|
||||||
}
|
|
||||||
} else {
|
} 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))?;
|
.map_err(|e| format!("Failed to check updates for remote {}: {}", remote, e))?;
|
||||||
|
|
||||||
let updates: Vec<RemoteUpdateInfo> = response
|
let updates: Vec<RemoteUpdateInfo> = response
|
||||||
.get("data")
|
.as_array()
|
||||||
.and_then(|d| d.as_array())
|
|
||||||
.map(|arr| {
|
.map(|arr| {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
.filter_map(|update| {
|
.filter_map(|update| {
|
||||||
|
|||||||
@ -46,7 +46,7 @@ pub async fn list_views(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list dashboard views: {}", e))?;
|
.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
|
let view_list: Vec<DashboardView> = views
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|view| {
|
.filter_map(|view| {
|
||||||
@ -143,7 +143,7 @@ pub async fn list_views(
|
|||||||
|
|
||||||
Ok(view_list)
|
Ok(view_list)
|
||||||
} else {
|
} else {
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +245,8 @@ pub async fn get_view(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get dashboard view {}: {}", view_id, e))?;
|
.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
|
let id = data
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(|i| i.as_str())
|
.and_then(|i| i.as_str())
|
||||||
@ -342,7 +343,5 @@ pub async fn get_view(
|
|||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,57 +131,54 @@ pub async fn list_vms(
|
|||||||
client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<Vec<VmInfo>, String> {
|
) -> Result<Vec<VmInfo>, String> {
|
||||||
let path = "cluster/resources";
|
// cluster/resources is GET-only; handle_response strips the {"data":[...]} envelope.
|
||||||
let params = serde_json::json!({
|
|
||||||
"type": "qemu"
|
|
||||||
});
|
|
||||||
|
|
||||||
let response: serde_json::Value = client
|
let response: serde_json::Value = client
|
||||||
.post(path, ¶ms, Some(ticket))
|
.get("cluster/resources?type=vm", Some(ticket))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list VMs: {}", e))?;
|
.map_err(|e| format!("Failed to list VMs: {}", e))?;
|
||||||
|
|
||||||
// Parse the response to extract VM info
|
let resources = response
|
||||||
// The API returns a list of resources in the "data" field
|
.as_array()
|
||||||
if let Some(resources) = response.get("data").and_then(|d| d.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()?;
|
|
||||||
|
|
||||||
Some(VmInfo {
|
let vms: Vec<VmInfo> = resources
|
||||||
id: vmid as u32,
|
.iter()
|
||||||
name,
|
.filter_map(|r| {
|
||||||
status,
|
let vmid = r.get("vmid")?.as_u64()?;
|
||||||
cpu,
|
let node = r.get("node")?.as_str()?.to_string();
|
||||||
memory: r.get("mem").and_then(|m| m.as_u64()).unwrap_or(0),
|
// Only include qemu VMs (not LXC containers which also appear in cluster/resources?type=vm)
|
||||||
disk: r.get("disk").and_then(|d| d.as_u64()).unwrap_or(0),
|
let resource_type = r.get("type").and_then(|t| t.as_str()).unwrap_or("");
|
||||||
uptime: r.get("uptime").and_then(|u| u.as_u64()).unwrap_or(0),
|
if resource_type != "qemu" {
|
||||||
node,
|
return None;
|
||||||
template: r.get("template").and_then(|t| t.as_bool()),
|
}
|
||||||
agent: r
|
let name = r.get("name").and_then(|n| n.as_str()).map(|s| s.to_string());
|
||||||
.get("agent")
|
let status = r.get("status").and_then(|s| s.as_str()).unwrap_or("unknown").to_string();
|
||||||
.and_then(|a| a.as_str())
|
// cpu may be absent for stopped VMs
|
||||||
.map(|s| s.to_string()),
|
let cpu = r.get("cpu").and_then(|c| c.as_f64()).unwrap_or(0.0);
|
||||||
mem: r.get("mem").and_then(|m| m.as_u64()),
|
|
||||||
max_mem: r.get("maxmem").and_then(|m| m.as_u64()),
|
Some(VmInfo {
|
||||||
max_disk: r.get("maxdisk").and_then(|d| d.as_u64()),
|
id: vmid as u32,
|
||||||
netin: r.get("netin").and_then(|n| n.as_u64()),
|
name,
|
||||||
netout: r.get("netout").and_then(|n| n.as_u64()),
|
status,
|
||||||
diskread: r.get("diskread").and_then(|d| d.as_u64()),
|
cpu,
|
||||||
diskwrite: r.get("diskwrite").and_then(|d| d.as_u64()),
|
memory: r.get("mem").and_then(|m| m.as_u64()).unwrap_or(0),
|
||||||
})
|
disk: r.get("disk").and_then(|d| d.as_u64()).unwrap_or(0),
|
||||||
|
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()),
|
||||||
|
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()),
|
||||||
|
netin: r.get("netin").and_then(|n| n.as_u64()),
|
||||||
|
netout: r.get("netout").and_then(|n| n.as_u64()),
|
||||||
|
diskread: r.get("diskread").and_then(|d| d.as_u64()),
|
||||||
|
diskwrite: r.get("diskwrite").and_then(|d| d.as_u64()),
|
||||||
})
|
})
|
||||||
.collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(vms)
|
Ok(vms)
|
||||||
} else {
|
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get VM details
|
/// Get VM details
|
||||||
@ -197,8 +194,7 @@ pub async fn get_vm(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get VM {}: {}", vmid, e))?;
|
.map_err(|e| format!("Failed to get VM {}: {}", vmid, e))?;
|
||||||
|
|
||||||
// Parse the response to extract VM info
|
let vm = &response;
|
||||||
let vm = response.get("data").ok_or("Invalid response format")?;
|
|
||||||
|
|
||||||
Ok(VmInfo {
|
Ok(VmInfo {
|
||||||
id: vmid,
|
id: vmid,
|
||||||
@ -415,11 +411,10 @@ pub async fn list_snapshots(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to list snapshots for VM {}: {}", vmid, e))?;
|
.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()) {
|
response
|
||||||
Ok(snapshots.to_vec())
|
.as_array()
|
||||||
} else {
|
.map(|arr| arr.to_vec())
|
||||||
Err("Invalid response format: missing 'data' field".to_string())
|
.ok_or_else(|| "Invalid response format".to_string())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user