all lists on 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal