From: Dominik Csapak <d.csapak@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager 1/8] lib: add new ResourceView type and move accessors there
Date: Fri, 14 Aug 2026 15:43:57 +0200 [thread overview]
Message-ID: <20260814134548.3446943-2-d.csapak@proxmox.com> (raw)
In-Reply-To: <20260814134548.3446943-1-d.csapak@proxmox.com>
This is intended as a view type for Resource and PveResource, which will
always share some enum type. This way, we can reuse all accessors
cheaply for PveResource values without cloning or consuming.
This makes it easier to share code for both of them, for instance in the
UI where we want to render properties consistently for resources.
Introduces also a AsResourceView trait to make converting more
ergonomic.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
lib/pdm-api-types/src/resource.rs | 261 +++++++++++++++++++++---------
1 file changed, 184 insertions(+), 77 deletions(-)
diff --git a/lib/pdm-api-types/src/resource.rs b/lib/pdm-api-types/src/resource.rs
index 303ac3cb..a8c4849d 100644
--- a/lib/pdm-api-types/src/resource.rs
+++ b/lib/pdm-api-types/src/resource.rs
@@ -45,102 +45,31 @@ impl Resource {
/// Returns the local ID, not a globally unique one, e.g.
/// `qemu/<vmid>`
pub fn id(&self) -> String {
- match self {
- Resource::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
- Resource::PveQemu(r) => format!("qemu/{}", r.vmid),
- Resource::PveLxc(r) => format!("lxc/{}", r.vmid),
- Resource::PveNode(r) => format!("node/{}", r.node),
- Resource::PveNetwork(r) => {
- if let PveNetworkResource::Zone(z) = r {
- if z.legacy {
- return format!("sdn/{}/{}", r.node(), r.name());
- }
- }
-
- format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
- }
- Resource::PbsNode(r) => format!("node/{}", r.name),
- Resource::PbsDatastore(r) => r.name.clone(),
- }
+ self.as_resource_view().id()
}
/// Returns the PDM global ID for the resource, e.g.
/// `remote/<remote-id>/guest/<vmid>`
pub fn global_id(&self) -> &str {
- match self {
- Resource::PveStorage(r) => r.id.as_str(),
- Resource::PveQemu(r) => r.id.as_str(),
- Resource::PveLxc(r) => r.id.as_str(),
- Resource::PveNode(r) => r.id.as_str(),
- Resource::PveNetwork(r) => r.id(),
- Resource::PbsNode(r) => r.id.as_str(),
- Resource::PbsDatastore(r) => r.id.as_str(),
- }
+ self.as_resource_view().global_id()
}
/// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
/// the hostname for nodes
pub fn name(&self) -> &str {
- match self {
- Resource::PveStorage(r) => r.storage.as_str(),
- Resource::PveQemu(r) => r.name.as_str(),
- Resource::PveLxc(r) => r.name.as_str(),
- Resource::PveNode(r) => r.node.as_str(),
- Resource::PveNetwork(r) => r.name(),
- Resource::PbsNode(r) => r.name.as_str(),
- Resource::PbsDatastore(r) => r.name.as_str(),
- }
+ self.as_resource_view().name()
}
pub fn resource_type(&self) -> ResourceType {
- match self {
- Resource::PveStorage(_) => ResourceType::PveStorage,
- Resource::PveQemu(_) => ResourceType::PveQemu,
- Resource::PveLxc(_) => ResourceType::PveLxc,
- Resource::PveNetwork(_) => ResourceType::PveNetwork,
- Resource::PveNode(_) | Resource::PbsNode(_) => ResourceType::Node,
- Resource::PbsDatastore(_) => ResourceType::PbsDatastore,
- }
+ self.as_resource_view().resource_type()
}
pub fn status(&self) -> &str {
- match self {
- Resource::PveStorage(r) => r.status.as_str(),
- Resource::PveQemu(r) => r.status.as_str(),
- Resource::PveLxc(r) => r.status.as_str(),
- Resource::PveNode(r) => r.status.as_str(),
- Resource::PveNetwork(r) => r.status(),
- Resource::PbsNode(r) => {
- if r.uptime > 0 {
- "online"
- } else {
- "offline"
- }
- }
- Resource::PbsDatastore(r) => {
- if r.maintenance.is_none() {
- "online"
- } else {
- "under-maintenance"
- }
- }
- }
+ self.as_resource_view().status()
}
pub fn properties(&self) -> String {
- let mut properties = Vec::new();
- if let Resource::PbsDatastore(r) = self {
- if let Some(backend_type) = &r.backend_type {
- properties.push(backend_type.to_string());
- }
- if r.backing_device.is_some() {
- properties.push("removable".to_string());
- }
- if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
- properties.push("high-usage".to_string());
- }
- }
- properties.join(",")
+ self.as_resource_view().properties()
}
}
@@ -229,6 +158,184 @@ pub enum PveResource {
Network(PveNetworkResource),
}
+/// A borrowed view of a resource than can come from a [Resource] or a [PveResource]
+/// This is useful for having a single implementation of field access regardless which
+/// of the types is used.
+#[derive(Clone, Debug, PartialEq)]
+pub enum ResourceView<'a> {
+ /// Storage resource in PVE.
+ PveStorage(&'a PveStorageResource),
+ /// QEMU guest resource in PVE.
+ PveQemu(&'a PveQemuResource),
+ /// LXC guest resource in PVE.
+ PveLxc(&'a PveLxcResource),
+ /// A PVE node.
+ PveNode(&'a PveNodeResource),
+ /// Network in PVE.
+ PveNetwork(&'a PveNetworkResource),
+ /// A PBS node.
+ PbsNode(&'a PbsNodeResource),
+ /// Datastore on a PBS node.
+ PbsDatastore(&'a PbsDatastoreResource),
+}
+
+impl<'a> ResourceView<'a> {
+ /// Returns the local ID, not a globally unique one, e.g.
+ /// `qemu/<vmid>`
+ pub fn id(&'a self) -> String {
+ match self {
+ ResourceView::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
+ ResourceView::PveQemu(r) => format!("qemu/{}", r.vmid),
+ ResourceView::PveLxc(r) => format!("lxc/{}", r.vmid),
+ ResourceView::PveNode(r) => format!("node/{}", r.node),
+ ResourceView::PveNetwork(r) => {
+ if let PveNetworkResource::Zone(z) = r {
+ if z.legacy {
+ return format!("sdn/{}/{}", r.node(), r.name());
+ }
+ }
+
+ format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
+ }
+ ResourceView::PbsNode(r) => format!("node/{}", r.name),
+ ResourceView::PbsDatastore(r) => r.name.clone(),
+ }
+ }
+
+ /// Returns the PDM global ID for the resource, e.g.
+ /// `remote/<remote-id>/guest/<vmid>`
+ pub fn global_id(&self) -> &'a str {
+ match self {
+ ResourceView::PveStorage(r) => r.id.as_str(),
+ ResourceView::PveQemu(r) => r.id.as_str(),
+ ResourceView::PveLxc(r) => r.id.as_str(),
+ ResourceView::PveNode(r) => r.id.as_str(),
+ ResourceView::PveNetwork(r) => r.id(),
+ ResourceView::PbsNode(r) => r.id.as_str(),
+ ResourceView::PbsDatastore(r) => r.id.as_str(),
+ }
+ }
+
+ /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
+ /// the hostname for nodes
+ pub fn name(&self) -> &'a str {
+ match self {
+ ResourceView::PveStorage(r) => r.storage.as_str(),
+ ResourceView::PveQemu(r) => r.name.as_str(),
+ ResourceView::PveLxc(r) => r.name.as_str(),
+ ResourceView::PveNode(r) => r.node.as_str(),
+ ResourceView::PveNetwork(r) => r.name(),
+ ResourceView::PbsNode(r) => r.name.as_str(),
+ ResourceView::PbsDatastore(r) => r.name.as_str(),
+ }
+ }
+
+ pub fn resource_type(&self) -> ResourceType {
+ match self {
+ ResourceView::PveStorage(_) => ResourceType::PveStorage,
+ ResourceView::PveQemu(_) => ResourceType::PveQemu,
+ ResourceView::PveLxc(_) => ResourceType::PveLxc,
+ ResourceView::PveNetwork(_) => ResourceType::PveNetwork,
+ ResourceView::PveNode(_) | ResourceView::PbsNode(_) => ResourceType::Node,
+ ResourceView::PbsDatastore(_) => ResourceType::PbsDatastore,
+ }
+ }
+
+ pub fn status(&self) -> &'a str {
+ match self {
+ ResourceView::PveStorage(r) => r.status.as_str(),
+ ResourceView::PveQemu(r) => r.status.as_str(),
+ ResourceView::PveLxc(r) => r.status.as_str(),
+ ResourceView::PveNode(r) => r.status.as_str(),
+ ResourceView::PveNetwork(r) => r.status(),
+ ResourceView::PbsNode(r) => {
+ if r.uptime > 0 {
+ "online"
+ } else {
+ "offline"
+ }
+ }
+ ResourceView::PbsDatastore(r) => {
+ if r.maintenance.is_none() {
+ "online"
+ } else {
+ "under-maintenance"
+ }
+ }
+ }
+ }
+
+ pub fn properties(&self) -> String {
+ let mut properties = Vec::new();
+ if let ResourceView::PbsDatastore(r) = self {
+ if let Some(backend_type) = &r.backend_type {
+ properties.push(backend_type.to_string());
+ }
+ if r.backing_device.is_some() {
+ properties.push("removable".to_string());
+ }
+ if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
+ properties.push("high-usage".to_string());
+ }
+ }
+ properties.join(",")
+ }
+}
+
+impl<'a> From<&'a Resource> for ResourceView<'a> {
+ fn from(value: &'a Resource) -> Self {
+ match value {
+ Resource::PveStorage(r) => ResourceView::PveStorage(r),
+ Resource::PveQemu(r) => ResourceView::PveQemu(r),
+ Resource::PveLxc(r) => ResourceView::PveLxc(r),
+ Resource::PveNode(r) => ResourceView::PveNode(r),
+ Resource::PveNetwork(r) => ResourceView::PveNetwork(r),
+ Resource::PbsNode(r) => ResourceView::PbsNode(r),
+ Resource::PbsDatastore(r) => ResourceView::PbsDatastore(r),
+ }
+ }
+}
+
+impl<'a> From<&'a PveResource> for ResourceView<'a> {
+ fn from(value: &'a PveResource) -> Self {
+ match value {
+ PveResource::Storage(r) => ResourceView::PveStorage(r),
+ PveResource::Qemu(r) => ResourceView::PveQemu(r),
+ PveResource::Lxc(r) => ResourceView::PveLxc(r),
+ PveResource::Node(r) => ResourceView::PveNode(r),
+ PveResource::Network(r) => ResourceView::PveNetwork(r),
+ }
+ }
+}
+
+pub trait AsResourceView {
+ fn as_resource_view<'a>(&'a self) -> ResourceView<'a>;
+}
+
+impl AsResourceView for Resource {
+ fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
+ ResourceView::from(self)
+ }
+}
+
+impl AsResourceView for &Resource {
+ fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
+ ResourceView::from(*self)
+ }
+}
+
+impl AsResourceView for PveResource {
+ fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
+ ResourceView::from(self)
+ }
+}
+
+impl AsResourceView for &PveResource {
+ fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
+ ResourceView::from(*self)
+ }
+}
+
#[api(
properties: {
tags: {
--
2.47.3
next prev parent reply other threads:[~2026-08-14 13:45 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-14 13:43 [PATCH datacenter-manager 0/8] refactor and partially fix #7371 Dominik Csapak
2026-08-14 13:43 ` Dominik Csapak [this message]
2026-08-14 13:43 ` [PATCH datacenter-manager 2/8] lib: api types: resource: add 'node' helper to ResourceView Dominik Csapak
2026-08-14 13:43 ` [PATCH datacenter-manager 3/8] lib: api-types: add 'vmid' getter " Dominik Csapak
2026-08-14 13:44 ` [PATCH datacenter-manager 4/8] ui: pve: factor out the pve-manager version extraction Dominik Csapak
2026-08-14 13:44 ` [PATCH datacenter-manager 5/8] ui: renderer: use ResourceView for rendering Dominik Csapak
2026-08-14 13:44 ` [PATCH datacenter-manager 6/8] ui: pve: tree: reuse `PveResource` for `PveTreeNode` Dominik Csapak
2026-08-14 13:44 ` [PATCH datacenter-manager 7/8] ui: pve: show ha maintenance mode for nodes Dominik Csapak
2026-08-14 13:44 ` [PATCH datacenter-manager 8/8] ui: pve: node selector: show maintenance badge with node name Dominik Csapak
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260814134548.3446943-2-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.