From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <d.csapak@proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by lists.proxmox.com (Postfix) with ESMTPS id 0C5B363F49
 for <pbs-devel@lists.proxmox.com>; Thu, 29 Oct 2020 11:51:59 +0100 (CET)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id EB5CB27A16
 for <pbs-devel@lists.proxmox.com>; Thu, 29 Oct 2020 11:51:28 +0100 (CET)
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [212.186.127.180])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS id 1549E27A0E
 for <pbs-devel@lists.proxmox.com>; Thu, 29 Oct 2020 11:51:28 +0100 (CET)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id CE8F945F82
 for <pbs-devel@lists.proxmox.com>; Thu, 29 Oct 2020 11:51:27 +0100 (CET)
From: Dominik Csapak <d.csapak@proxmox.com>
To: pbs-devel@lists.proxmox.com
Date: Thu, 29 Oct 2020 11:51:26 +0100
Message-Id: <20201029105126.8984-1-d.csapak@proxmox.com>
X-Mailer: git-send-email 2.20.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL 0.444 Adjusted score from AWL reputation of From: address
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 RCVD_IN_DNSWL_MED        -2.3 Sender listed at https://www.dnswl.org/,
 medium trust
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See
 http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more
 information. [storage.total, datastore.rs, proxmox-backup-client.rs,
 counts.host, mod.rs, result.host]
Subject: [pbs-devel] [PATCH proxmox-backup] api2/admin/datstore/status:
 improve status api call and restore compatibility
X-BeenThere: pbs-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox Backup Server development discussion
 <pbs-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pbs-devel/>
List-Post: <mailto:pbs-devel@lists.proxmox.com>
List-Help: <mailto:pbs-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Thu, 29 Oct 2020 10:51:59 -0000

by moving the properties of the storage status out again to the top
level object

also introduce proper structs for the types used, to get type-safety
and better documentation for the api calls

this changes the backup counts from an array of [groups,snapshots] to
an object/struct with { groups, snapshots } and include 'other' types
(though we do not have any at this moment)

this way it is better documented

this also adapts the ui code to cope with the api changes

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
restores compatibility with client <=0.9.1 but breaks 0.9.2
 src/api2/admin/datastore.rs      | 72 ++++++++++++++++----------------
 src/api2/types/mod.rs            | 65 ++++++++++++++++++++++++++++
 src/bin/proxmox-backup-client.rs |  2 +-
 www/DataStoreSummary.js          | 26 ++++++------
 4 files changed, 117 insertions(+), 48 deletions(-)

diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 3f3de7ba..a160c844 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -423,12 +423,18 @@ pub fn list_snapshots (
     Ok(snapshots)
 }
 
-// returns a map from type to (group_count, snapshot_count)
-fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize)>, Error> {
+fn get_snapshots_count(store: &DataStore) -> Result<Counts, Error> {
     let base_path = store.base_path();
     let backup_list = BackupInfo::list_backups(&base_path)?;
     let mut groups = HashSet::new();
-    let mut result: HashMap<String, (usize, usize)> = HashMap::new();
+
+    let mut result = Counts {
+        ct: None,
+        host: None,
+        vm: None,
+        other: None,
+    };
+
     for info in backup_list {
         let group = info.backup_dir.group();
 
@@ -441,13 +447,23 @@ fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize
             new_id = true;
         }
 
-        if let Some(mut counts) = result.get_mut(backup_type) {
-            counts.1 += 1;
-            if new_id {
-                counts.0 +=1;
-            }
-        } else {
-            result.insert(backup_type.to_string(), (1, 1));
+        let mut counts = match backup_type {
+            "ct" => result.ct.take().unwrap_or(Default::default()),
+            "host" => result.host.take().unwrap_or(Default::default()),
+            "vm" => result.vm.take().unwrap_or(Default::default()),
+            _ => result.other.take().unwrap_or(Default::default()),
+        };
+
+        counts.snapshots += 1;
+        if new_id {
+            counts.groups +=1;
+        }
+
+        match backup_type {
+            "ct" => result.ct = Some(counts),
+            "host" => result.host = Some(counts),
+            "vm" => result.vm = Some(counts),
+            _ => result.other = Some(counts),
         }
     }
 
@@ -463,21 +479,7 @@ fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize
         },
     },
     returns: {
-        description: "The overall Datastore status and information.",
-        type: Object,
-        properties: {
-            storage: {
-                type: StorageStatus,
-            },
-            counts: {
-                description: "Group and Snapshot counts per Type",
-                type: Object,
-                properties: { },
-            },
-            "gc-status": {
-                type: GarbageCollectionStatus,
-            },
-        },
+        type: DataStoreStatus,
     },
     access: {
         permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
@@ -488,19 +490,19 @@ pub fn status(
     store: String,
     _info: &ApiMethod,
     _rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
+) -> Result<DataStoreStatus, Error> {
     let datastore = DataStore::lookup_datastore(&store)?;
-    let storage_status = crate::tools::disks::disk_usage(&datastore.base_path())?;
-    let counts = get_snaphots_count(&datastore)?;
+    let storage = crate::tools::disks::disk_usage(&datastore.base_path())?;
+    let counts = get_snapshots_count(&datastore)?;
     let gc_status = datastore.last_gc_status();
 
-    let res = json!({
-        "storage": storage_status,
-        "counts": counts,
-        "gc-status": gc_status,
-    });
-
-    Ok(res)
+    Ok(DataStoreStatus {
+        total: storage.total,
+        used: storage.used,
+        avail: storage.avail,
+        gc_status,
+        counts,
+    })
 }
 
 #[api(
diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs
index 1b9a305f..1f98215c 100644
--- a/src/api2/types/mod.rs
+++ b/src/api2/types/mod.rs
@@ -622,6 +622,71 @@ pub struct StorageStatus {
     pub avail: u64,
 }
 
+#[api()]
+#[derive(Serialize, Deserialize, Default)]
+/// Backup Type group/snapshot counts.
+pub struct TypeCounts {
+    /// The number of groups of the type.
+    pub groups: u64,
+    /// The number of snapshots of the type.
+    pub snapshots: u64,
+}
+
+#[api(
+    properties: {
+        ct: {
+            type: TypeCounts,
+            optional: true,
+        },
+        host: {
+            type: TypeCounts,
+            optional: true,
+        },
+        vm: {
+            type: TypeCounts,
+            optional: true,
+        },
+        other: {
+            type: TypeCounts,
+            optional: true,
+        },
+    },
+)]
+#[derive(Serialize, Deserialize)]
+/// Counts of groups/snapshots per BackupType.
+pub struct Counts {
+    /// The counts for CT backups
+    pub ct: Option<TypeCounts>,
+    /// The counts for Host backups
+    pub host: Option<TypeCounts>,
+    /// The counts for VM backups
+    pub vm: Option<TypeCounts>,
+    /// The counts for other backup types
+    pub other: Option<TypeCounts>,
+}
+
+#[api(
+    properties: {
+        "gc-status": { type: GarbageCollectionStatus, },
+        counts: { type: Counts, }
+    },
+)]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all="kebab-case")]
+/// Overall Datastore status and useful information.
+pub struct DataStoreStatus {
+    /// Total space (bytes).
+    pub total: u64,
+    /// Used space (bytes).
+    pub used: u64,
+    /// Available space (bytes).
+    pub avail: u64,
+    /// Status of last GC
+    pub gc_status: GarbageCollectionStatus,
+    /// Group/Snapshot counts
+    pub counts: Counts,
+}
+
 #[api(
     properties: {
         upid: { schema: UPID_SCHEMA },
diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs
index beaa935e..139e607b 100644
--- a/src/bin/proxmox-backup-client.rs
+++ b/src/bin/proxmox-backup-client.rs
@@ -1674,7 +1674,7 @@ async fn status(param: Value) -> Result<Value, Error> {
     let path = format!("api2/json/admin/datastore/{}/status", repo.store());
 
     let mut result = client.get(&path, None).await?;
-    let mut data = result["data"]["storage"].take();
+    let mut data = result["data"].take();
 
     record_repository(&repo);
 
diff --git a/www/DataStoreSummary.js b/www/DataStoreSummary.js
index 525486a5..93bd99a3 100644
--- a/www/DataStoreSummary.js
+++ b/www/DataStoreSummary.js
@@ -48,21 +48,23 @@ Ext.define('PBS.DataStoreInfo', {
 	    let vm = me.getViewModel();
 
 	    let counts = store.getById('counts').data.value;
-	    let storage = store.getById('storage').data.value;
+	    let total = store.getById('total').data.value;
+	    let used = store.getById('used').data.value;
 
-	    let used = Proxmox.Utils.format_size(storage.used);
-	    let total = Proxmox.Utils.format_size(storage.total);
-	    let percent = 100*storage.used/storage.total;
-	    if (storage.total === 0) {
+	    let percent = 100*used/total;
+	    if (total === 0) {
 		percent = 0;
 	    }
 	    let used_percent = `${percent.toFixed(2)}%`;
 
 	    let usage = used_percent + ' (' +
-		Ext.String.format(gettext('{0} of {1}'),
-				  used, total) + ')';
+		Ext.String.format(
+		    gettext('{0} of {1}'),
+		    Proxmox.Utils.format_size(used),
+		    Proxmox.Utils.format_size(total),
+		) + ')';
 	    vm.set('usagetext', usage);
-	    vm.set('usage', storage.used/storage.total);
+	    vm.set('usage', used/total);
 
 	    let gcstatus = store.getById('gc-status').data.value;
 
@@ -70,12 +72,12 @@ Ext.define('PBS.DataStoreInfo', {
 			(gcstatus['disk-bytes'] || Infinity);
 
 	    let countstext = function(count) {
-		return `${count[0]} ${gettext('Groups')}, ${count[1]} ${gettext('Snapshots')}`;
+		return `${count.groups || 0} ${gettext('Groups')}, ${count.snapshots || 0} ${gettext('Snapshots')}`;
 	    };
 
-	    vm.set('ctcount', countstext(counts.ct || [0, 0]));
-	    vm.set('vmcount', countstext(counts.vm || [0, 0]));
-	    vm.set('hostcount', countstext(counts.host || [0, 0]));
+	    vm.set('ctcount', countstext(counts.ct));
+	    vm.set('vmcount', countstext(counts.vm));
+	    vm.set('hostcount', countstext(counts.host));
 	    vm.set('deduplication', dedup.toFixed(2));
 	    vm.set('stillbad', gcstatus['still-bad']);
 	    vm.set('removedbytes', Proxmox.Utils.format_size(gcstatus['removed-bytes']));
-- 
2.20.1