* [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.