public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add
@ 2025-09-08 14:04 Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH storage 1/1] api: status: document return types Dominik Csapak
                   ` (11 more replies)
  0 siblings, 12 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

adds the pve storages to the overview tree and a new basic panel, akin
to the one we have in PVE itself.

the returns types need annotation so i did that, and sent the relevant
pve-api.json update + regenerate commits also.

pve-storage:

Dominik Csapak (1):
  api: status: document return types

 src/PVE/API2/Storage/Status.pm | 45 +++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)


proxmox-api-types:

Dominik Csapak (3):
  regenerate pve-api.json
  node: storage: add status api
  regenerate with new node storage status api

 pve-api-types/generate.pl            |   2 +
 pve-api-types/pve-api.json           |  45 ++++++-
 pve-api-types/src/generated/code.rs  |  12 +-
 pve-api-types/src/generated/types.rs | 172 +++++++++++++++++++++++++++
 4 files changed, 229 insertions(+), 2 deletions(-)


proxmox-datacenter-manager:

Dominik Csapak (7):
  pdm-api-types: add pve storage id schema
  pdm-api-types: add PVE storage data point for RRD
  server: api: add rrddata endpoint for pve storages
  server: api: pve: add nodes/storage api for status and rrddata
  pdm-client: add pve storage status/rrddata methods
  ui: pve: add storage to tree and show basic panel
  ui: enable navigation to pve storage

 lib/pdm-api-types/src/lib.rs     |   7 +
 lib/pdm-api-types/src/rrddata.rs |  15 ++
 lib/pdm-client/src/lib.rs        |  30 ++-
 server/src/api/pve/mod.rs        |   1 +
 server/src/api/pve/node.rs       |   8 +-
 server/src/api/pve/rrddata.rs    |  57 ++++-
 server/src/api/pve/storage.rs    |  46 ++++
 ui/src/lib.rs                    |   3 +
 ui/src/pve/mod.rs                |   5 +
 ui/src/pve/storage.rs            | 360 +++++++++++++++++++++++++++++++
 ui/src/pve/tree.rs               |  37 +++-
 ui/src/pve/utils.rs              |  41 +++-
 12 files changed, 603 insertions(+), 7 deletions(-)
 create mode 100644 server/src/api/pve/storage.rs
 create mode 100644 ui/src/pve/storage.rs


Summary over all repositories:
  17 files changed, 876 insertions(+), 10 deletions(-)

-- 
Generated by git-murpp 0.8.1


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH storage 1/1] api: status: document return types
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:44   ` [pdm-devel] applied: " Wolfgang Bumiller
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 1/3] regenerate pve-api.json Dominik Csapak
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

this is useful, e.g. when we want to generate bindings for this api call

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/API2/Storage/Status.pm | 45 +++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm
index ad8c753..7bde4ec 100644
--- a/src/PVE/API2/Storage/Status.pm
+++ b/src/PVE/API2/Storage/Status.pm
@@ -300,7 +300,50 @@ __PACKAGE__->register_method({
     },
     returns => {
         type => "object",
-        properties => {},
+        properties => {
+            type => {
+                description => "Storage type.",
+                type => 'string',
+            },
+            content => {
+                description => "Allowed storage content types.",
+                type => 'string',
+                format => 'pve-storage-content-list',
+            },
+            enabled => {
+                description => "Set when storage is enabled (not disabled).",
+                type => 'boolean',
+                optional => 1,
+            },
+            active => {
+                description => "Set when storage is accessible.",
+                type => 'boolean',
+                optional => 1,
+            },
+            shared => {
+                description => "Shared flag from storage configuration.",
+                type => 'boolean',
+                optional => 1,
+            },
+            total => {
+                description => "Total storage space in bytes.",
+                type => 'integer',
+                renderer => 'bytes',
+                optional => 1,
+            },
+            used => {
+                description => "Used storage space in bytes.",
+                type => 'integer',
+                renderer => 'bytes',
+                optional => 1,
+            },
+            avail => {
+                description => "Available storage space in bytes.",
+                type => 'integer',
+                renderer => 'bytes',
+                optional => 1,
+            },
+        },
     },
     code => sub {
         my ($param) = @_;
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH proxmox-api-types 1/3] regenerate pve-api.json
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH storage 1/1] api: status: document return types Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 2/3] node: storage: add status api Dominik Csapak
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

with new pve-storage status api

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 pve-api-types/pve-api.json | 45 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/pve-api-types/pve-api.json b/pve-api-types/pve-api.json
index 84c9b4f..0880b35 100644
--- a/pve-api-types/pve-api.json
+++ b/pve-api-types/pve-api.json
@@ -46287,7 +46287,50 @@
                                        "protected": 1,
                                        "proxyto": "node",
                                        "returns": {
-                                          "properties": {},
+                                          "properties": {
+                                             "active": {
+                                                "description": "Set when storage is accessible.",
+                                                "optional": 1,
+                                                "type": "boolean"
+                                             },
+                                             "avail": {
+                                                "description": "Available storage space in bytes.",
+                                                "optional": 1,
+                                                "renderer": "bytes",
+                                                "type": "integer"
+                                             },
+                                             "content": {
+                                                "description": "Allowed storage content types.",
+                                                "format": "pve-storage-content-list",
+                                                "type": "string"
+                                             },
+                                             "enabled": {
+                                                "description": "Set when storage is enabled (not disabled).",
+                                                "optional": 1,
+                                                "type": "boolean"
+                                             },
+                                             "shared": {
+                                                "description": "Shared flag from storage configuration.",
+                                                "optional": 1,
+                                                "type": "boolean"
+                                             },
+                                             "total": {
+                                                "description": "Total storage space in bytes.",
+                                                "optional": 1,
+                                                "renderer": "bytes",
+                                                "type": "integer"
+                                             },
+                                             "type": {
+                                                "description": "Storage type.",
+                                                "type": "string"
+                                             },
+                                             "used": {
+                                                "description": "Used storage space in bytes.",
+                                                "optional": 1,
+                                                "renderer": "bytes",
+                                                "type": "integer"
+                                             }
+                                          },
                                           "type": "object"
                                        }
                                     }
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH proxmox-api-types 2/3] node: storage: add status api
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH storage 1/1] api: status: document return types Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 1/3] regenerate pve-api.json Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 3/3] regenerate with new node storage " Dominik Csapak
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 pve-api-types/generate.pl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pve-api-types/generate.pl b/pve-api-types/generate.pl
index cf154c6..d30c5f5 100644
--- a/pve-api-types/generate.pl
+++ b/pve-api-types/generate.pl
@@ -266,6 +266,8 @@ Schema2Rust::derive('NetworkInterface' => 'Clone', 'PartialEq');
 api(GET => '/nodes/{node}/storage', 'list_storages', 'return-name' => 'StorageInfo');
 Schema2Rust::derive('StorageInfo' => 'Clone', 'PartialEq');
 
+api(GET => '/nodes/{node}/storage/{storage}/status', 'storage_status', 'return-name' => 'StorageStatus');
+
 # FIXME: PVE9 introduced a new non optional property, but that does not
 # exist in PVE8, so make it optional here for older PVEs to work
 Schema2Rust::generate_struct(
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH proxmox-api-types 3/3] regenerate with new node storage status api
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (2 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 2/3] node: storage: add status api Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 1/7] pdm-api-types: add pve storage id schema Dominik Csapak
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 pve-api-types/src/generated/code.rs  |  12 +-
 pve-api-types/src/generated/types.rs | 172 +++++++++++++++++++++++++++
 2 files changed, 183 insertions(+), 1 deletion(-)

diff --git a/pve-api-types/src/generated/code.rs b/pve-api-types/src/generated/code.rs
index 21daf32..6dee304 100644
--- a/pve-api-types/src/generated/code.rs
+++ b/pve-api-types/src/generated/code.rs
@@ -357,7 +357,6 @@
 /// - /nodes/{node}/storage/{storage}/prunebackups
 /// - /nodes/{node}/storage/{storage}/rrd
 /// - /nodes/{node}/storage/{storage}/rrddata
-/// - /nodes/{node}/storage/{storage}/status
 /// - /nodes/{node}/storage/{storage}/upload
 /// - /nodes/{node}/suspendall
 /// - /nodes/{node}/syslog
@@ -730,6 +729,11 @@ pub trait PveClient {
         Err(Error::Other("stop_task not implemented"))
     }
 
+    /// Read storage status.
+    async fn storage_status(&self, node: &str, storage: &str) -> Result<StorageStatus, Error> {
+        Err(Error::Other("storage_status not implemented"))
+    }
+
     /// This is used to resynchronize the package index files from their sources
     /// (apt-get update).
     async fn update_apt_database(
@@ -1226,6 +1230,12 @@ where
         self.0.delete(url).await?.nodata()
     }
 
+    /// Read storage status.
+    async fn storage_status(&self, node: &str, storage: &str) -> Result<StorageStatus, Error> {
+        let url = &format!("/api2/extjs/nodes/{node}/storage/{storage}/status");
+        Ok(self.0.get(url).await?.expect_json()?.data)
+    }
+
     /// This is used to resynchronize the package index files from their sources
     /// (apt-get update).
     async fn update_apt_database(
diff --git a/pve-api-types/src/generated/types.rs b/pve-api-types/src/generated/types.rs
index b752f29..623ea76 100644
--- a/pve-api-types/src/generated/types.rs
+++ b/pve-api-types/src/generated/types.rs
@@ -9791,6 +9791,101 @@ mod storage_info_content {
     }
 }
 
+const STORAGE_STATUS_CONTENT: Schema =
+    proxmox_schema::ArraySchema::new("list", &StorageContent::API_SCHEMA).schema();
+
+mod storage_status_content {
+    use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+    #[doc(hidden)]
+    pub trait Ser: Sized {
+        fn ser<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
+        fn de<'de, D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>;
+    }
+
+    impl<T: Serialize + for<'a> Deserialize<'a>> Ser for Vec<T> {
+        fn ser<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            super::stringlist::serialize(&self[..], serializer, &super::STORAGE_STATUS_CONTENT)
+        }
+
+        fn de<'de, D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            super::stringlist::deserialize(deserializer, &super::STORAGE_STATUS_CONTENT)
+        }
+    }
+
+    impl<T: Ser> Ser for Option<T> {
+        fn ser<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            match self {
+                None => serializer.serialize_none(),
+                Some(inner) => inner.ser(serializer),
+            }
+        }
+
+        fn de<'de, D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            use std::fmt;
+            use std::marker::PhantomData;
+
+            struct V<T: Ser>(PhantomData<T>);
+
+            impl<'de, T: Ser> serde::de::Visitor<'de> for V<T> {
+                type Value = Option<T>;
+
+                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                    f.write_str("an optional string")
+                }
+
+                fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
+                    Ok(None)
+                }
+
+                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
+                where
+                    D: Deserializer<'de>,
+                {
+                    T::de(deserializer).map(Some)
+                }
+
+                fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
+                    use serde::de::IntoDeserializer;
+                    T::de(value.into_deserializer()).map(Some)
+                }
+            }
+
+            deserializer.deserialize_option(V::<T>(PhantomData))
+        }
+    }
+
+    pub fn serialize<T, S>(this: &T, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+        T: Ser,
+    {
+        this.ser(serializer)
+    }
+
+    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+        T: Ser,
+    {
+        T::de(deserializer)
+    }
+}
+
 const_regex! {
 
 SDN_CONTROLLER_ISIS_IFACES_RE = r##"^[a-zA-Z][a-zA-Z0-9_]{1,20}([:\.]\d+)?$"##;
@@ -11157,6 +11252,83 @@ pub struct StorageInfo {
     pub used_fraction: Option<f64>,
 }
 
+#[api(
+    properties: {
+        active: {
+            default: false,
+            optional: true,
+        },
+        avail: {
+            optional: true,
+            type: Integer,
+        },
+        content: {
+            format: &ApiStringFormat::PropertyString(&STORAGE_STATUS_CONTENT),
+            type: String,
+        },
+        enabled: {
+            default: false,
+            optional: true,
+        },
+        shared: {
+            default: false,
+            optional: true,
+        },
+        total: {
+            optional: true,
+            type: Integer,
+        },
+        type: {
+            type: String,
+        },
+        used: {
+            optional: true,
+            type: Integer,
+        },
+    },
+)]
+/// Object.
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct StorageStatus {
+    /// Set when storage is accessible.
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub active: Option<bool>,
+
+    /// Available storage space in bytes.
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_i64")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub avail: Option<i64>,
+
+    /// Allowed storage content types.
+    #[serde(with = "storage_status_content")]
+    pub content: Vec<StorageContent>,
+
+    /// Set when storage is enabled (not disabled).
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub enabled: Option<bool>,
+
+    /// Shared flag from storage configuration.
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub shared: Option<bool>,
+
+    /// Total storage space in bytes.
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_i64")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub total: Option<i64>,
+
+    /// Storage type.
+    #[serde(rename = "type")]
+    pub ty: String,
+
+    /// Used storage space in bytes.
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_i64")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub used: Option<i64>,
+}
+
 #[api(
     properties: {
         n: {
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 1/7] pdm-api-types: add pve storage id schema
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (3 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 3/3] regenerate with new node storage " Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 2/7] pdm-api-types: add PVE storage data point for RRD Dominik Csapak
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

regex is copied from pve-api-types definition

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 lib/pdm-api-types/src/lib.rs | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lib/pdm-api-types/src/lib.rs b/lib/pdm-api-types/src/lib.rs
index 6cc4b6f..a7eaa0d 100644
--- a/lib/pdm-api-types/src/lib.rs
+++ b/lib/pdm-api-types/src/lib.rs
@@ -112,6 +112,9 @@ const_regex! {
     pub HOST_OPTIONAL_PORT_REGEX = concatcp!(r"^(?:", DNS_NAME_STR, "|", IPRE_BRACKET_STR, ")(?::", PORT_REGEX_STR ,")?$");
 
     pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
+
+    // FIXME: use from pve-api-types once exposed
+    pub PVE_STORAGE_ID_REGEX = r"^(?i:[a-z][a-z0-9\-_.]*[a-z0-9])$";
 }
 
 pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
@@ -164,6 +167,10 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
     .max_length(64)
     .schema();
 
+pub const PVE_STORAGE_ID_SCHEMA: Schema = StringSchema::new("Storage ID.")
+    .format(&ApiStringFormat::Pattern(&PVE_STORAGE_ID_REGEX))
+    .schema();
+
 // Complex type definitions
 
 #[api()]
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 2/7] pdm-api-types: add PVE storage data point for RRD
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (4 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 1/7] pdm-api-types: add pve storage id schema Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 3/7] server: api: add rrddata endpoint for pve storages Dominik Csapak
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 lib/pdm-api-types/src/rrddata.rs | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lib/pdm-api-types/src/rrddata.rs b/lib/pdm-api-types/src/rrddata.rs
index 47847b7..7061923 100644
--- a/lib/pdm-api-types/src/rrddata.rs
+++ b/lib/pdm-api-types/src/rrddata.rs
@@ -136,6 +136,21 @@ pub struct NodeDataPoint {
     pub uptime: Option<f64>,
 }
 
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+/// Single point in time with all known data points for a PVE storage.
+pub struct PveStorageDataPoint {
+    /// Timestamp (UNIX epoch)
+    pub time: u64,
+    /// Total disk size
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub disk_total: Option<f64>,
+    /// Disk utiliziation
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub disk_used: Option<f64>,
+}
+
 #[api]
 #[derive(Serialize, Deserialize, Default)]
 #[serde(rename_all = "kebab-case")]
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 3/7] server: api: add rrddata endpoint for pve storages
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (5 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 2/7] pdm-api-types: add PVE storage data point for RRD Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 4/7] server: api: pve: add nodes/storage api for status and rrddata Dominik Csapak
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 server/src/api/pve/rrddata.rs | 57 +++++++++++++++++++++++++++++++++--
 1 file changed, 55 insertions(+), 2 deletions(-)

diff --git a/server/src/api/pve/rrddata.rs b/server/src/api/pve/rrddata.rs
index b6a0403..e08d4b4 100644
--- a/server/src/api/pve/rrddata.rs
+++ b/server/src/api/pve/rrddata.rs
@@ -8,8 +8,8 @@ use proxmox_rrd_api_types::{RrdMode, RrdTimeframe};
 use proxmox_schema::api;
 
 use pdm_api_types::remotes::REMOTE_ID_SCHEMA;
-use pdm_api_types::rrddata::{LxcDataPoint, NodeDataPoint, QemuDataPoint};
-use pdm_api_types::{NODE_SCHEMA, PRIV_RESOURCE_AUDIT, VMID_SCHEMA};
+use pdm_api_types::rrddata::{LxcDataPoint, NodeDataPoint, PveStorageDataPoint, QemuDataPoint};
+use pdm_api_types::{NODE_SCHEMA, PRIV_RESOURCE_AUDIT, PVE_STORAGE_ID_SCHEMA, VMID_SCHEMA};
 
 use crate::api::rrd_common::{self, DataPoint};
 use crate::metric_collection;
@@ -146,6 +146,27 @@ impl DataPoint for LxcDataPoint {
     }
 }
 
+impl DataPoint for PveStorageDataPoint {
+    fn new(time: u64) -> Self {
+        Self {
+            time,
+            ..Default::default()
+        }
+    }
+
+    fn fields() -> &'static [&'static str] {
+        &["disk_total", "disk_used"]
+    }
+
+    fn set_field(&mut self, name: &str, value: f64) {
+        match name {
+            "disk_total" => self.disk_total = Some(value),
+            "disk_used" => self.disk_used = Some(value),
+            _ => {}
+        }
+    }
+}
+
 #[api(
     input: {
         properties: {
@@ -233,6 +254,37 @@ async fn get_node_rrd_data(
     get_rrd_datapoints(remote, base, timeframe, cf).await
 }
 
+#[api(
+    input: {
+        properties: {
+            remote: { schema: REMOTE_ID_SCHEMA },
+            node: { schema: NODE_SCHEMA },
+            storage: { schema: PVE_STORAGE_ID_SCHEMA },
+            timeframe: {
+                type: RrdTimeframe,
+            },
+            cf: {
+                type: RrdMode,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["resource", "{remote}", "storage", "{storage}"], PRIV_RESOURCE_AUDIT, false),
+    },
+)]
+/// Read node stats
+async fn get_storage_rrd_data(
+    remote: String,
+    node: String,
+    storage: String,
+    timeframe: RrdTimeframe,
+    cf: RrdMode,
+    _param: Value,
+) -> Result<Vec<NodeDataPoint>, Error> {
+    let base = format!("pve/{remote}/storage/{node}/{storage}");
+    get_rrd_datapoints(remote, base, timeframe, cf).await
+}
+
 async fn get_rrd_datapoints<T: DataPoint + Send + 'static>(
     remote: String,
     basepath: String,
@@ -260,3 +312,4 @@ async fn get_rrd_datapoints<T: DataPoint + Send + 'static>(
 pub const QEMU_RRD_ROUTER: Router = Router::new().get(&API_METHOD_GET_QEMU_RRD_DATA);
 pub const LXC_RRD_ROUTER: Router = Router::new().get(&API_METHOD_GET_LXC_RRD_DATA);
 pub const NODE_RRD_ROUTER: Router = Router::new().get(&API_METHOD_GET_NODE_RRD_DATA);
+pub const STORAGE_RRD_ROUTER: Router = Router::new().get(&API_METHOD_GET_STORAGE_RRD_DATA);
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 4/7] server: api: pve: add nodes/storage api for status and rrddata
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (6 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 3/7] server: api: add rrddata endpoint for pve storages Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 5/7] pdm-client: add pve storage status/rrddata methods Dominik Csapak
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

just tunnels through the pve api

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 server/src/api/pve/mod.rs     |  1 +
 server/src/api/pve/node.rs    |  8 +++++-
 server/src/api/pve/storage.rs | 46 +++++++++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+), 1 deletion(-)
 create mode 100644 server/src/api/pve/storage.rs

diff --git a/server/src/api/pve/mod.rs b/server/src/api/pve/mod.rs
index 0768083..edafdde 100644
--- a/server/src/api/pve/mod.rs
+++ b/server/src/api/pve/mod.rs
@@ -36,6 +36,7 @@ mod lxc;
 mod node;
 mod qemu;
 mod rrddata;
+mod storage;
 pub mod tasks;
 
 pub const ROUTER: Router = Router::new()
diff --git a/server/src/api/pve/node.rs b/server/src/api/pve/node.rs
index 99539d1..1924e25 100644
--- a/server/src/api/pve/node.rs
+++ b/server/src/api/pve/node.rs
@@ -7,6 +7,8 @@ use proxmox_sortable_macro::sortable;
 use pdm_api_types::{remotes::REMOTE_ID_SCHEMA, NODE_SCHEMA, PRIV_RESOURCE_AUDIT};
 use pve_api_types::StorageContent;
 
+use crate::api::pve::storage;
+
 pub const ROUTER: Router = Router::new()
     .get(&list_subdirs_api_method!(SUBDIRS))
     .subdirs(SUBDIRS);
@@ -16,10 +18,14 @@ const SUBDIRS: SubdirMap = &sorted!([
     ("apt", &super::apt::ROUTER),
     ("rrddata", &super::rrddata::NODE_RRD_ROUTER),
     ("network", &Router::new().get(&API_METHOD_GET_NETWORK)),
-    ("storage", &Router::new().get(&API_METHOD_GET_STORAGES)),
+    ("storage", &STORAGE_ROUTER),
     ("status", &Router::new().get(&API_METHOD_GET_STATUS)),
 ]);
 
+const STORAGE_ROUTER: Router = Router::new()
+    .get(&API_METHOD_GET_STORAGES)
+    .match_all("storage", &storage::ROUTER);
+
 #[api(
     input: {
         properties: {
diff --git a/server/src/api/pve/storage.rs b/server/src/api/pve/storage.rs
new file mode 100644
index 0000000..e337d06
--- /dev/null
+++ b/server/src/api/pve/storage.rs
@@ -0,0 +1,46 @@
+use anyhow::Error;
+
+use proxmox_router::{list_subdirs_api_method, Permission, Router, SubdirMap};
+use proxmox_schema::api;
+use proxmox_sortable_macro::sortable;
+
+use pdm_api_types::remotes::REMOTE_ID_SCHEMA;
+use pdm_api_types::{NODE_SCHEMA, PRIV_RESOURCE_AUDIT, PVE_STORAGE_ID_SCHEMA};
+
+use super::connect_to_remote;
+
+pub const ROUTER: Router = Router::new()
+    .get(&list_subdirs_api_method!(STORAGE_SUBDIR))
+    .subdirs(STORAGE_SUBDIR);
+#[sortable]
+const STORAGE_SUBDIR: SubdirMap = &sorted!([
+    ("rrddata", &super::rrddata::STORAGE_RRD_ROUTER),
+    ("status", &Router::new().get(&API_METHOD_GET_STATUS)),
+]);
+
+#[api(
+    input: {
+        properties: {
+            remote: { schema: REMOTE_ID_SCHEMA },
+            node: { schema: NODE_SCHEMA, },
+            storage: { schema: PVE_STORAGE_ID_SCHEMA, },
+        },
+    },
+    returns: { type: pve_api_types::QemuStatus },
+    access: {
+        permission: &Permission::Privilege(&["resource", "{remote}", "storage", "{storage}"], PRIV_RESOURCE_AUDIT, false),
+    },
+)]
+/// Get the status of a qemu VM from a remote. If a node is provided, the VM must be on that
+/// node, otherwise the node is determined automatically.
+pub async fn get_status(
+    remote: String,
+    node: String,
+    storage: String,
+) -> Result<pve_api_types::StorageStatus, Error> {
+    let (remotes, _) = pdm_config::remotes::config()?;
+
+    let pve = connect_to_remote(&remotes, &remote)?;
+
+    Ok(pve.storage_status(&node, &storage).await?)
+}
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 5/7] pdm-client: add pve storage status/rrddata methods
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (7 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 4/7] server: api: pve: add nodes/storage api for status and rrddata Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 6/7] ui: pve: add storage to tree and show basic panel Dominik Csapak
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 lib/pdm-client/src/lib.rs | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/lib/pdm-client/src/lib.rs b/lib/pdm-client/src/lib.rs
index da1ad43..845738f 100644
--- a/lib/pdm-client/src/lib.rs
+++ b/lib/pdm-client/src/lib.rs
@@ -6,7 +6,8 @@ use std::time::Duration;
 use pdm_api_types::remotes::TlsProbeOutcome;
 use pdm_api_types::resource::{PveResource, RemoteResources, TopEntities};
 use pdm_api_types::rrddata::{
-    LxcDataPoint, NodeDataPoint, PbsDatastoreDataPoint, PbsNodeDataPoint, QemuDataPoint,
+    LxcDataPoint, NodeDataPoint, PbsDatastoreDataPoint, PbsNodeDataPoint, PveStorageDataPoint,
+    QemuDataPoint,
 };
 use pdm_api_types::sdn::{ListVnet, ListZone};
 use pdm_api_types::BasicRealmInfo;
@@ -63,6 +64,8 @@ pub mod types {
         CreateVnetParams, CreateZoneParams, ListController, ListVnet, ListZone, SDN_ID_SCHEMA,
     };
     pub use pve_api_types::{ListControllersType, ListZonesType, SdnObjectState};
+
+    pub use pve_api_types::StorageStatus as PveStorageStatus;
 }
 
 pub struct PdmClient<T: HttpApiClient>(pub T);
@@ -903,6 +906,31 @@ impl<T: HttpApiClient> PdmClient<T> {
         Ok(self.0.get(&path).await?.expect_json()?.data)
     }
 
+    pub async fn pve_storage_status(
+        &self,
+        remote: &str,
+        node: &str,
+        storage: &str,
+    ) -> Result<PveStorageStatus, Error> {
+        let path =
+            format!("/api2/extjs/pve/remotes/{remote}/nodes/{node}/storage/{storage}/status");
+        Ok(self.0.get(&path).await?.expect_json()?.data)
+    }
+
+    pub async fn pve_storage_rrddata(
+        &self,
+        remote: &str,
+        node: &str,
+        storage: &str,
+        mode: RrdMode,
+        timeframe: RrdTimeframe,
+    ) -> Result<Vec<PveStorageDataPoint>, Error> {
+        let path = format!(
+            "/api2/extjs/pve/remotes/{remote}/nodes/{node}/storage/{storage}/rrddata?cf={mode}&timeframe={timeframe}"
+        );
+        Ok(self.0.get(&path).await?.expect_json()?.data)
+    }
+
     pub async fn get_top_entities(&self) -> Result<TopEntities, Error> {
         let path = "/api2/extjs/resources/top-entities";
         Ok(self.0.get(path).await?.expect_json()?.data)
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 6/7] ui: pve: add storage to tree and show basic panel
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (8 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 5/7] pdm-client: add pve storage status/rrddata methods Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 7/7] ui: enable navigation to pve storage Dominik Csapak
  2025-09-08 14:59 ` [pdm-devel] applied-series: [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Wolfgang Bumiller
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

similar to the panel we have in PVE already

storage and content type strings were copied from pve-managers Utils.js

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 ui/src/pve/mod.rs     |   5 +
 ui/src/pve/storage.rs | 360 ++++++++++++++++++++++++++++++++++++++++++
 ui/src/pve/tree.rs    |  37 ++++-
 ui/src/pve/utils.rs   |  41 ++++-
 4 files changed, 440 insertions(+), 3 deletions(-)
 create mode 100644 ui/src/pve/storage.rs

diff --git a/ui/src/pve/mod.rs b/ui/src/pve/mod.rs
index 496cbc6..7313d5e 100644
--- a/ui/src/pve/mod.rs
+++ b/ui/src/pve/mod.rs
@@ -26,6 +26,7 @@ pub mod lxc;
 pub mod node;
 pub mod qemu;
 pub mod remote;
+pub mod storage;
 pub mod utils;
 
 mod tree;
@@ -180,6 +181,10 @@ impl LoadableComponent for PveRemoteComp {
             PveTreeNode::Lxc(lxc) => {
                 lxc::LxcPanel::new(remote.clone(), lxc.node.clone(), lxc.clone()).into()
             }
+            PveTreeNode::Storage(storage) => {
+                storage::StoragePanel::new(remote.clone(), storage.node.clone(), storage.clone())
+                    .into()
+            }
         };
 
         let link = ctx.link();
diff --git a/ui/src/pve/storage.rs b/ui/src/pve/storage.rs
new file mode 100644
index 0000000..4602397
--- /dev/null
+++ b/ui/src/pve/storage.rs
@@ -0,0 +1,360 @@
+use core::f64;
+use std::rc::Rc;
+
+use gloo_timers::callback::Timeout;
+use yew::{
+    virtual_dom::{VComp, VNode},
+    Properties,
+};
+
+use proxmox_human_byte::HumanByte;
+use proxmox_yew_comp::{RRDGraph, RRDTimeframe, RRDTimeframeSelector, Series, Status};
+use pwt::{
+    css::{AlignItems, ColorScheme, FlexFit, JustifyContent},
+    prelude::*,
+    props::WidgetBuilder,
+    widget::{Column, Container, Fa, Panel, Progress, Row},
+    AsyncPool,
+};
+
+use pdm_api_types::{resource::PveStorageResource, rrddata::PveStorageDataPoint};
+use pdm_client::types::PveStorageStatus;
+
+use crate::{
+    pve::utils::{render_content_type, render_storage_type},
+    renderer::separator,
+};
+
+#[derive(Clone, Debug, Properties)]
+pub struct StoragePanel {
+    remote: String,
+    node: String,
+    info: PveStorageResource,
+
+    #[prop_or(60_000)]
+    /// The interval for refreshing the rrd data
+    pub rrd_interval: u32,
+
+    #[prop_or(10_000)]
+    /// The interval for refreshing the status data
+    pub status_interval: u32,
+}
+
+impl PartialEq for StoragePanel {
+    fn eq(&self, other: &Self) -> bool {
+        if self.remote == other.remote && self.node == other.node {
+            // only check some fields, so we don't update when e.g. only the cpu changes
+            self.info.storage == other.info.storage
+                && self.info.id == other.info.id
+                && self.info.node == other.node
+        } else {
+            false
+        }
+    }
+}
+impl Eq for StoragePanel {}
+
+impl StoragePanel {
+    pub fn new(remote: String, node: String, info: PveStorageResource) -> Self {
+        yew::props!(Self { remote, node, info })
+    }
+}
+
+impl Into<VNode> for StoragePanel {
+    fn into(self) -> VNode {
+        VComp::new::<StoragePanelComp>(Rc::new(self), None).into()
+    }
+}
+
+pub enum Msg {
+    ReloadStatus,
+    ReloadRrd,
+    StatusResult(Result<PveStorageStatus, proxmox_client::Error>),
+    RrdResult(Result<Vec<PveStorageDataPoint>, proxmox_client::Error>),
+    UpdateRrdTimeframe(RRDTimeframe),
+}
+
+pub struct StoragePanelComp {
+    status: Option<PveStorageStatus>,
+    last_status_error: Option<proxmox_client::Error>,
+    last_rrd_error: Option<proxmox_client::Error>,
+    _status_timeout: Option<Timeout>,
+    _rrd_timeout: Option<Timeout>,
+    _async_pool: AsyncPool,
+
+    rrd_time_frame: RRDTimeframe,
+
+    time: Rc<Vec<i64>>,
+    disk: Rc<Series>,
+    disk_max: Rc<Series>,
+}
+
+impl StoragePanelComp {
+    async fn reload_status(
+        remote: &str,
+        node: &str,
+        id: &str,
+    ) -> Result<PveStorageStatus, proxmox_client::Error> {
+        let status = crate::pdm_client()
+            .pve_storage_status(remote, node, id)
+            .await?;
+        Ok(status)
+    }
+
+    async fn reload_rrd(
+        remote: &str,
+        node: &str,
+        id: &str,
+        rrd_time_frame: RRDTimeframe,
+    ) -> Result<Vec<PveStorageDataPoint>, proxmox_client::Error> {
+        let rrd = crate::pdm_client()
+            .pve_storage_rrddata(
+                remote,
+                node,
+                id,
+                rrd_time_frame.mode,
+                rrd_time_frame.timeframe,
+            )
+            .await?;
+        Ok(rrd)
+    }
+}
+
+impl yew::Component for StoragePanelComp {
+    type Message = Msg;
+
+    type Properties = StoragePanel;
+
+    fn create(ctx: &yew::Context<Self>) -> Self {
+        ctx.link()
+            .send_message_batch(vec![Msg::ReloadStatus, Msg::ReloadRrd]);
+        Self {
+            status: None,
+            _status_timeout: None,
+            _rrd_timeout: None,
+            _async_pool: AsyncPool::new(),
+            last_rrd_error: None,
+            last_status_error: None,
+
+            rrd_time_frame: RRDTimeframe::load(),
+
+            time: Rc::new(Vec::new()),
+            disk: Rc::new(Series::new("", Vec::new())),
+            disk_max: Rc::new(Series::new("", Vec::new())),
+        }
+    }
+
+    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
+        let link = ctx.link().clone();
+        let props = ctx.props();
+        let remote = props.remote.clone();
+        let node = props.node.clone();
+        let id = props.info.storage.clone();
+        match msg {
+            Msg::ReloadStatus => {
+                self._async_pool.send_future(link, async move {
+                    Msg::StatusResult(Self::reload_status(&remote, &node, &id).await)
+                });
+                false
+            }
+            Msg::ReloadRrd => {
+                let timeframe = self.rrd_time_frame;
+                self._async_pool.send_future(link, async move {
+                    Msg::RrdResult(Self::reload_rrd(&remote, &node, &id, timeframe).await)
+                });
+                false
+            }
+            Msg::StatusResult(res) => {
+                match res {
+                    Ok(status) => {
+                        self.last_status_error = None;
+                        self.status = Some(status);
+                    }
+                    Err(err) => {
+                        self.last_status_error = Some(err);
+                    }
+                }
+
+                self._status_timeout = Some(Timeout::new(props.status_interval, move || {
+                    link.send_message(Msg::ReloadStatus)
+                }));
+                true
+            }
+            Msg::RrdResult(res) => {
+                match res {
+                    Ok(rrd) => {
+                        self.last_rrd_error = None;
+
+                        let mut disk = Vec::new();
+                        let mut disk_max = Vec::new();
+                        let mut time = Vec::new();
+                        for data in rrd {
+                            disk.push(data.disk_used.unwrap_or(f64::NAN));
+                            disk_max.push(data.disk_total.unwrap_or(f64::NAN));
+                            time.push(data.time as i64);
+                        }
+
+                        self.disk = Rc::new(Series::new(tr!("Usage"), disk));
+                        self.disk_max = Rc::new(Series::new(tr!("Total"), disk_max));
+                        self.time = Rc::new(time);
+                    }
+                    Err(err) => self.last_rrd_error = Some(err),
+                }
+                self._status_timeout = Some(Timeout::new(props.rrd_interval, move || {
+                    link.send_message(Msg::ReloadRrd)
+                }));
+                true
+            }
+            Msg::UpdateRrdTimeframe(rrd_time_frame) => {
+                self.rrd_time_frame = rrd_time_frame;
+                ctx.link().send_message(Msg::ReloadRrd);
+                false
+            }
+        }
+    }
+
+    fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
+        let props = ctx.props();
+
+        if props.remote != old_props.remote || props.info != old_props.info {
+            self.status = None;
+            self.last_status_error = None;
+            self.last_rrd_error = None;
+
+            self.time = Rc::new(Vec::new());
+            self.disk = Rc::new(Series::new("", Vec::new()));
+            self.disk_max = Rc::new(Series::new("", Vec::new()));
+            self._async_pool = AsyncPool::new();
+            ctx.link()
+                .send_message_batch(vec![Msg::ReloadStatus, Msg::ReloadRrd]);
+            true
+        } else {
+            false
+        }
+    }
+
+    fn view(&self, ctx: &yew::Context<Self>) -> yew::Html {
+        let props = ctx.props();
+        let title: Html = Row::new()
+            .gap(2)
+            .class(AlignItems::Baseline)
+            .with_child(Fa::new("database"))
+            .with_child(tr! {"Storage '{0}'", props.info.storage})
+            .into();
+
+        let mut status_comp = Column::new().gap(2).padding(4);
+        let status = match &self.status {
+            Some(status) => status,
+            None => &PveStorageStatus {
+                active: None,
+                avail: Some(props.info.maxdisk as i64 - props.info.disk as i64),
+                content: vec![],
+                enabled: None,
+                shared: None,
+                total: Some(props.info.maxdisk as i64),
+                ty: String::new(),
+                used: Some(props.info.disk as i64),
+            },
+        };
+
+        status_comp = status_comp
+            .with_child(make_row(
+                tr!("Enabled"),
+                Fa::new(if status.enabled.unwrap_or_default() {
+                    "toggle-on"
+                } else {
+                    "toggle-off"
+                }),
+                String::new(),
+            ))
+            .with_child(make_row(
+                tr!("Active"),
+                Fa::from(if status.active.unwrap_or_default() {
+                    Status::Success
+                } else {
+                    Status::Error
+                }),
+                String::new(),
+            ))
+            .with_child(make_row(
+                tr!("Content"),
+                Fa::new("list"),
+                status
+                    .content
+                    .iter()
+                    .map(|c| render_content_type(&c))
+                    .collect::<Vec<_>>()
+                    .join(", "),
+            ))
+            .with_child(make_row(
+                tr!("Type"),
+                Fa::new("database"),
+                render_storage_type(&status.ty),
+            ));
+
+        status_comp.add_child(Container::new().padding(1)); // spacer
+
+        let disk = status.used.unwrap_or_default();
+        let maxdisk = status.total.unwrap_or_default();
+        let disk_usage = disk as f64 / maxdisk as f64;
+        status_comp.add_child(crate::renderer::status_row(
+            tr!("Usage"),
+            Fa::new("database"),
+            tr!(
+                "{0}% ({1} of {2})",
+                format!("{:.2}", disk_usage * 100.0),
+                HumanByte::from(disk as u64),
+                HumanByte::from(maxdisk as u64),
+            ),
+            Some(disk_usage as f32),
+            false,
+        ));
+
+        let loading = self.status.is_none() && self.last_status_error.is_none();
+
+        Panel::new()
+            .class(FlexFit)
+            .title(title)
+            .class(ColorScheme::Neutral)
+            .with_child(
+                // FIXME: add some 'visible' or 'active' property to the progress
+                Progress::new()
+                    .value((!loading).then_some(0.0))
+                    .style("opacity", (!loading).then_some("0")),
+            )
+            .with_child(status_comp)
+            .with_child(separator().padding_x(4))
+            .with_child(
+                Row::new()
+                    .padding_x(4)
+                    .padding_y(1)
+                    .class(JustifyContent::FlexEnd)
+                    .with_child(
+                        RRDTimeframeSelector::new()
+                            .on_change(ctx.link().callback(Msg::UpdateRrdTimeframe)),
+                    ),
+            )
+            .with_child(
+                Container::new().class(FlexFit).with_child(
+                    Column::new().padding(4).gap(4).with_child(
+                        RRDGraph::new(self.time.clone())
+                            .title(tr!("Usage"))
+                            .render_value(|v: &f64| {
+                                if v.is_finite() {
+                                    proxmox_human_byte::HumanByte::from(*v as u64).to_string()
+                                } else {
+                                    v.to_string()
+                                }
+                            })
+                            .serie0(Some(self.disk.clone()))
+                            .serie1(Some(self.disk_max.clone())),
+                    ),
+                ),
+            )
+            .into()
+    }
+}
+
+fn make_row(title: String, icon: Fa, text: String) -> Column {
+    crate::renderer::status_row(title, icon, text, None, true)
+}
diff --git a/ui/src/pve/tree.rs b/ui/src/pve/tree.rs
index c4d1322..168e322 100644
--- a/ui/src/pve/tree.rs
+++ b/ui/src/pve/tree.rs
@@ -22,7 +22,7 @@ use pwt::widget::{
 use pwt::{prelude::*, widget::Button};
 
 use pdm_api_types::{
-    resource::{PveLxcResource, PveNodeResource, PveQemuResource, PveResource},
+    resource::{PveLxcResource, PveNodeResource, PveQemuResource, PveResource, PveStorageResource},
     RemoteUpid,
 };
 
@@ -39,6 +39,7 @@ pub enum PveTreeNode {
     Node(PveNodeResource),
     Lxc(PveLxcResource),
     Qemu(PveQemuResource),
+    Storage(PveStorageResource),
 }
 
 impl ExtractPrimaryKey for PveTreeNode {
@@ -48,6 +49,7 @@ impl ExtractPrimaryKey for PveTreeNode {
             PveTreeNode::Node(node) => node.id.as_str(),
             PveTreeNode::Lxc(lxc) => lxc.id.as_str(),
             PveTreeNode::Qemu(qemu) => qemu.id.as_str(),
+            PveTreeNode::Storage(storage) => storage.id.as_str(),
         })
     }
 }
@@ -59,6 +61,9 @@ impl PveTreeNode {
             PveTreeNode::Node(node) => format!("node+{}", node.node),
             PveTreeNode::Lxc(lxc) => format!("guest+{}", lxc.vmid),
             PveTreeNode::Qemu(qemu) => format!("guest+{}", qemu.vmid),
+            PveTreeNode::Storage(storage) => {
+                format!("storage+{}+{}", storage.node, storage.storage)
+            }
         }
     }
 }
@@ -181,7 +186,19 @@ impl PveTreeComp {
                     }
                     node.append(PveTreeNode::Lxc(lxc_info.clone()));
                 }
-                _ => {} //PveResource::Storage(pve_storage_resource) => todo!(),
+                PveResource::Storage(storage) => {
+                    let node_id = format!("remote/{}/node/{}", remote, storage.node);
+                    let key = Key::from(node_id.as_str());
+                    let mut node = match root.find_node_by_key_mut(&key) {
+                        Some(node) => node,
+                        None => root.append(create_empty_node(node_id)),
+                    };
+
+                    if !self.loaded {
+                        node.set_expanded(true);
+                    }
+                    node.append(PveTreeNode::Storage(storage.clone()));
+                }
             }
         }
         if !self.loaded {
@@ -212,6 +229,13 @@ impl PveTreeComp {
             (PveTreeNode::Qemu(a), PveTreeNode::Qemu(b)) => {
                 cmp_guests(a.template, b.template, a.vmid, b.vmid)
             }
+            (PveTreeNode::Lxc(_) | PveTreeNode::Qemu(_), PveTreeNode::Storage(_)) => {
+                std::cmp::Ordering::Less
+            }
+            (PveTreeNode::Storage(_), PveTreeNode::Lxc(_) | PveTreeNode::Qemu(_)) => {
+                std::cmp::Ordering::Greater
+            }
+            (PveTreeNode::Storage(a), PveTreeNode::Storage(b)) => a.id.cmp(&b.id),
         });
         let first_id = root
             .children()
@@ -549,6 +573,9 @@ fn columns(
                     PveTreeNode::Lxc(r) => {
                         (utils::render_lxc_status_icon(r), render_lxc_name(r, true))
                     }
+                    PveTreeNode::Storage(r) => {
+                        (utils::render_storage_status_icon(r), r.storage.clone())
+                    }
                 };
 
                 Row::new()
@@ -604,6 +631,12 @@ fn columns(
                         None,
                         Some(r.node.clone()),
                     ),
+                    PveTreeNode::Storage(r) => (
+                        r.id.as_str(),
+                        format!("storage/{}/{}", r.node, r.storage),
+                        None,
+                        Some(r.node.clone()),
+                    ),
                 };
 
                 Row::new()
diff --git a/ui/src/pve/utils.rs b/ui/src/pve/utils.rs
index d0c8ccc..7663734 100644
--- a/ui/src/pve/utils.rs
+++ b/ui/src/pve/utils.rs
@@ -4,13 +4,14 @@ use pdm_api_types::resource::{
 };
 use pdm_client::types::{
     LxcConfig, LxcConfigMp, LxcConfigRootfs, LxcConfigUnused, PveQmIde, QemuConfig, QemuConfigSata,
-    QemuConfigScsi, QemuConfigUnused, QemuConfigVirtio,
+    QemuConfigScsi, QemuConfigUnused, QemuConfigVirtio, StorageContent,
 };
 use proxmox_schema::property_string::PropertyString;
 use proxmox_yew_comp::{GuestState, NodeState, StorageState};
 use pwt::{
     css::Opacity,
     props::{ContainerBuilder, WidgetBuilder, WidgetStyleBuilder},
+    tr,
     widget::{Container, Fa, Row},
 };
 
@@ -238,3 +239,41 @@ where
         f(&key, res.map_err(Error::from));
     }
 }
+
+/// Renders the backend types of storages from PVE to a human understandable type
+pub(crate) fn render_storage_type(ty: &str) -> String {
+    if ty == "dir" {
+        return tr!("Directory");
+    }
+    String::from(match ty {
+        "lvm" => "LVM",
+        "lvmthin" => "LVM-Thin",
+        "btrfs" => "BTRFS",
+        "nfs" => "NFS",
+        "cifs" => "SMB/CIFS",
+        "iscsi" => "iSCSI",
+        "cephfs" => "CephFS",
+        "pvecephfs" => "CephFS (PVE)",
+        "rbd" => "RBD",
+        "pveceph" => "RBD (PVE)",
+        "zfs" => "ZFS over iSCSI",
+        "zfspool" => "ZFS",
+        "pbs" => "Proxmox Backup Server",
+        "esxi" => "ESXi",
+        _ => ty,
+    })
+}
+
+/// Renders the backend content type of PVE into a human understandable type
+pub(crate) fn render_content_type(ty: &StorageContent) -> String {
+    match ty {
+        StorageContent::Backup => tr!("Backup"),
+        StorageContent::Images => tr!("Disk Image"),
+        StorageContent::Import => tr!("Import"),
+        StorageContent::Iso => tr!("ISO image"),
+        StorageContent::Rootdir => tr!("Container"),
+        StorageContent::Snippets => tr!("Snippets"),
+        StorageContent::Vztmpl => tr!("Container template"),
+        StorageContent::None => tr!("None"),
+    }
+}
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 7/7] ui: enable navigation to pve storage
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (9 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 6/7] ui: pve: add storage to tree and show basic panel Dominik Csapak
@ 2025-09-08 14:04 ` Dominik Csapak
  2025-09-08 14:59 ` [pdm-devel] applied-series: [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Wolfgang Bumiller
  11 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2025-09-08 14:04 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 ui/src/lib.rs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index 02bf337..37e6458 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -148,6 +148,9 @@ pub(crate) fn navigate_to<C: yew::Component>(
                         format!("guest+{vmid}")
                     }
                     pdm_client::types::Resource::PveNode(node) => format!("node+{}", node.node),
+                    pdm_client::types::Resource::PveStorage(storage) => {
+                        format!("storage+{}+{}", storage.node, storage.storage)
+                    }
                     pdm_client::types::Resource::PbsDatastore(store) => store.name.clone(),
                     // FIXME: implement
                     _ => return None,
-- 
2.47.2



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] applied: [PATCH storage 1/1] api: status: document return types
  2025-09-08 14:04 ` [pdm-devel] [PATCH storage 1/1] api: status: document return types Dominik Csapak
@ 2025-09-08 14:44   ` Wolfgang Bumiller
  0 siblings, 0 replies; 14+ messages in thread
From: Wolfgang Bumiller @ 2025-09-08 14:44 UTC (permalink / raw)
  To: Dominik Csapak; +Cc: pdm-devel

applied, thanks

On Mon, Sep 08, 2025 at 04:04:08PM +0200, Dominik Csapak wrote:
> this is useful, e.g. when we want to generate bindings for this api call
> 
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  src/PVE/API2/Storage/Status.pm | 45 +++++++++++++++++++++++++++++++++-
>  1 file changed, 44 insertions(+), 1 deletion(-)
> 
> diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm
> index ad8c753..7bde4ec 100644
> --- a/src/PVE/API2/Storage/Status.pm
> +++ b/src/PVE/API2/Storage/Status.pm
> @@ -300,7 +300,50 @@ __PACKAGE__->register_method({
>      },
>      returns => {
>          type => "object",
> -        properties => {},
> +        properties => {
> +            type => {
> +                description => "Storage type.",
> +                type => 'string',
> +            },
> +            content => {
> +                description => "Allowed storage content types.",
> +                type => 'string',
> +                format => 'pve-storage-content-list',
> +            },
> +            enabled => {
> +                description => "Set when storage is enabled (not disabled).",
> +                type => 'boolean',
> +                optional => 1,
> +            },
> +            active => {
> +                description => "Set when storage is accessible.",
> +                type => 'boolean',
> +                optional => 1,
> +            },
> +            shared => {
> +                description => "Shared flag from storage configuration.",
> +                type => 'boolean',
> +                optional => 1,
> +            },
> +            total => {
> +                description => "Total storage space in bytes.",
> +                type => 'integer',
> +                renderer => 'bytes',
> +                optional => 1,
> +            },
> +            used => {
> +                description => "Used storage space in bytes.",
> +                type => 'integer',
> +                renderer => 'bytes',
> +                optional => 1,
> +            },
> +            avail => {
> +                description => "Available storage space in bytes.",
> +                type => 'integer',
> +                renderer => 'bytes',
> +                optional => 1,
> +            },
> +        },
>      },
>      code => sub {
>          my ($param) = @_;
> -- 
> 2.47.2


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pdm-devel] applied-series: [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add
  2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
                   ` (10 preceding siblings ...)
  2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 7/7] ui: enable navigation to pve storage Dominik Csapak
@ 2025-09-08 14:59 ` Wolfgang Bumiller
  11 siblings, 0 replies; 14+ messages in thread
From: Wolfgang Bumiller @ 2025-09-08 14:59 UTC (permalink / raw)
  To: Dominik Csapak; +Cc: pdm-devel

applied series (except for the generated ones - redid those & added
commit hashes of non-bumped packages to the commit message)

thanks

On Mon, Sep 08, 2025 at 04:04:07PM +0200, Dominik Csapak wrote:
> adds the pve storages to the overview tree and a new basic panel, akin
> to the one we have in PVE itself.
> 
> the returns types need annotation so i did that, and sent the relevant
> pve-api.json update + regenerate commits also.
> 
> pve-storage:
> 
> Dominik Csapak (1):
>   api: status: document return types
> 
>  src/PVE/API2/Storage/Status.pm | 45 +++++++++++++++++++++++++++++++++-
>  1 file changed, 44 insertions(+), 1 deletion(-)
> 
> 
> proxmox-api-types:
> 
> Dominik Csapak (3):
>   regenerate pve-api.json
>   node: storage: add status api
>   regenerate with new node storage status api
> 
>  pve-api-types/generate.pl            |   2 +
>  pve-api-types/pve-api.json           |  45 ++++++-
>  pve-api-types/src/generated/code.rs  |  12 +-
>  pve-api-types/src/generated/types.rs | 172 +++++++++++++++++++++++++++
>  4 files changed, 229 insertions(+), 2 deletions(-)
> 
> 
> proxmox-datacenter-manager:
> 
> Dominik Csapak (7):
>   pdm-api-types: add pve storage id schema
>   pdm-api-types: add PVE storage data point for RRD
>   server: api: add rrddata endpoint for pve storages
>   server: api: pve: add nodes/storage api for status and rrddata
>   pdm-client: add pve storage status/rrddata methods
>   ui: pve: add storage to tree and show basic panel
>   ui: enable navigation to pve storage
> 
>  lib/pdm-api-types/src/lib.rs     |   7 +
>  lib/pdm-api-types/src/rrddata.rs |  15 ++
>  lib/pdm-client/src/lib.rs        |  30 ++-
>  server/src/api/pve/mod.rs        |   1 +
>  server/src/api/pve/node.rs       |   8 +-
>  server/src/api/pve/rrddata.rs    |  57 ++++-
>  server/src/api/pve/storage.rs    |  46 ++++
>  ui/src/lib.rs                    |   3 +
>  ui/src/pve/mod.rs                |   5 +
>  ui/src/pve/storage.rs            | 360 +++++++++++++++++++++++++++++++
>  ui/src/pve/tree.rs               |  37 +++-
>  ui/src/pve/utils.rs              |  41 +++-
>  12 files changed, 603 insertions(+), 7 deletions(-)
>  create mode 100644 server/src/api/pve/storage.rs
>  create mode 100644 ui/src/pve/storage.rs
> 
> 
> Summary over all repositories:
>   17 files changed, 876 insertions(+), 10 deletions(-)
> 
> -- 
> Generated by git-murpp 0.8.1


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2025-09-08 14:59 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-08 14:04 [pdm-devel] [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH storage 1/1] api: status: document return types Dominik Csapak
2025-09-08 14:44   ` [pdm-devel] applied: " Wolfgang Bumiller
2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 1/3] regenerate pve-api.json Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 2/3] node: storage: add status api Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH proxmox-api-types 3/3] regenerate with new node storage " Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 1/7] pdm-api-types: add pve storage id schema Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 2/7] pdm-api-types: add PVE storage data point for RRD Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 3/7] server: api: add rrddata endpoint for pve storages Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 4/7] server: api: pve: add nodes/storage api for status and rrddata Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 5/7] pdm-client: add pve storage status/rrddata methods Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 6/7] ui: pve: add storage to tree and show basic panel Dominik Csapak
2025-09-08 14:04 ` [pdm-devel] [PATCH datacenter-manager 7/7] ui: enable navigation to pve storage Dominik Csapak
2025-09-08 14:59 ` [pdm-devel] applied-series: [PATCH datacenter-manager/proxmox-api-types/storage 00/11] add Wolfgang Bumiller

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal