public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type.
@ 2025-11-07 14:40 Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 1/3] pve-api-types: update /cluster/resources endpoint Stefan Hanreich
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

The patch series [1] introduced a new resource type for network entities. This
patch series implements support for handling this new resource type in the
backend, as well as the frontend. The backend can now handle both formats and
maps them to the same Resource internally.

Without this patch series applied, PDM cannot handle any Proxmox VE remotes,
that have the patch series in [1] applied.

A new match category is also introduced, in order to be able to filter for
specific network types in the UI / API.

For more information about the new resource type see the respective patch
series [1].

pre-build packages available on sani: `network-resources-pdm`

[1] https://lore.proxmox.com/pve-devel/20251107143201.689035-1-s.hanreich@proxmox.com/T/

proxmox:

Stefan Hanreich (3):
  pve-api-types: update /cluster/resources endpoint
  pve-api-types: regenerate
  pve-api-types: extend ClusterResourceNetworkType

 pve-api-types/pve-api.json           | 27 +++++++++++++++-
 pve-api-types/src/generated/types.rs | 48 ++++++++++++++++++++++++++++
 pve-api-types/src/sdn.rs             | 11 ++++++-
 3 files changed, 84 insertions(+), 2 deletions(-)


proxmox-datacenter-manager:

Stefan Hanreich (3):
  resources: support new pve network resource type
  resources: api: add network type match category
  ui: use new network resource type

 cli/client/src/resources.rs                  |   9 +-
 lib/pdm-api-types/src/resource.rs            | 115 +++++++++++-----
 lib/pdm-client/src/lib.rs                    |   2 +
 server/src/api/resources.rs                  |  98 ++++++++++---
 server/src/metric_collection/top_entities.rs |   2 +-
 ui/src/dashboard/sdn_zone_panel.rs           |   7 +-
 ui/src/lib.rs                                |   6 +-
 ui/src/pve/remote.rs                         |   2 +-
 ui/src/pve/tree.rs                           |   2 +-
 ui/src/pve/utils.rs                          |  14 +-
 ui/src/renderer.rs                           |   7 +-
 ui/src/sdn/zone_tree.rs                      | 136 +++++++++++--------
 12 files changed, 274 insertions(+), 126 deletions(-)


Summary over all repositories:
  15 files changed, 358 insertions(+), 128 deletions(-)

-- 
Generated by git-murpp 0.8.0

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


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

* [pdm-devel] [PATCH proxmox 1/3] pve-api-types: update /cluster/resources endpoint
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 2/3] pve-api-types: regenerate Stefan Hanreich
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/pve-api.json | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/pve-api-types/pve-api.json b/pve-api-types/pve-api.json
index 1775fdae..833915e8 100644
--- a/pve-api-types/pve-api.json
+++ b/pve-api-types/pve-api.json
@@ -15755,6 +15755,20 @@
                                  "renderer": "bytes",
                                  "type": "integer"
                               },
+                              "network": {
+                                 "description": "The name of a Network entity (for type 'network').",
+                                 "optional": 1,
+                                 "type": "string"
+                              },
+                              "network_type": {
+                                 "description": "The type of network resource (for type 'network').",
+                                 "enum": [
+                                    "fabric",
+                                    "zone"
+                                 ],
+                                 "optional": 1,
+                                 "type": "string"
+                              },
                               "node": {
                                  "description": "The cluster node name (for types 'node', 'storage', 'qemu', and 'lxc').",
                                  "format": "pve-node",
@@ -15771,6 +15785,11 @@
                                  "optional": 1,
                                  "type": "string"
                               },
+                              "protocol": {
+                                 "description": "The protocol of a fabric (for type 'network', network_type 'fabric').",
+                                 "optional": 1,
+                                 "type": "string"
+                              },
                               "sdn": {
                                  "description": "The name of an SDN entity (for type 'sdn')",
                                  "optional": 1,
@@ -15808,7 +15827,8 @@
                                     "qemu",
                                     "lxc",
                                     "openvz",
-                                    "sdn"
+                                    "sdn",
+                                    "network"
                                  ],
                                  "type": "string"
                               },
@@ -15825,6 +15845,11 @@
                                  "minimum": 100,
                                  "optional": 1,
                                  "type": "integer"
+                              },
+                              "zone_type": {
+                                 "description": "The type of an SDN zone (for type 'sdn').",
+                                 "optional": 1,
+                                 "type": "string"
                               }
                            },
                            "type": "object"
-- 
2.47.3


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


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

* [pdm-devel] [PATCH proxmox 2/3] pve-api-types: regenerate
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 1/3] pve-api-types: update /cluster/resources endpoint Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 3/3] pve-api-types: extend ClusterResourceNetworkType Stefan Hanreich
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/src/generated/types.rs | 48 ++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/pve-api-types/src/generated/types.rs b/pve-api-types/src/generated/types.rs
index 6c42b620..ea539ccc 100644
--- a/pve-api-types/src/generated/types.rs
+++ b/pve-api-types/src/generated/types.rs
@@ -759,6 +759,14 @@ fn test_regex_compilation_4() {
             optional: true,
             type: Integer,
         },
+        network: {
+            optional: true,
+            type: String,
+        },
+        network_type: {
+            optional: true,
+            type: ClusterResourceNetworkType,
+        },
         node: {
             format: &ApiStringFormat::Pattern(&CLUSTER_RESOURCE_NODE_RE),
             optional: true,
@@ -772,6 +780,10 @@ fn test_regex_compilation_4() {
             optional: true,
             type: String,
         },
+        protocol: {
+            optional: true,
+            type: String,
+        },
         sdn: {
             optional: true,
             type: String,
@@ -806,6 +818,10 @@ fn test_regex_compilation_4() {
             optional: true,
             type: Integer,
         },
+        zone_type: {
+            optional: true,
+            type: String,
+        },
     },
 )]
 /// Object.
@@ -906,6 +922,13 @@ pub struct ClusterResource {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub netout: Option<i64>,
 
+    /// The name of a Network entity (for type 'network').
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub network: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub network_type: Option<ClusterResourceNetworkType>,
+
     /// The cluster node name (for types 'node', 'storage', 'qemu', and 'lxc').
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub node: Option<String>,
@@ -918,6 +941,10 @@ pub struct ClusterResource {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub pool: Option<String>,
 
+    /// The protocol of a fabric (for type 'network', network_type 'fabric').
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub protocol: Option<String>,
+
     /// The name of an SDN entity (for type 'sdn')
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub sdn: Option<String>,
@@ -952,6 +979,10 @@ pub struct ClusterResource {
     #[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")]
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub vmid: Option<u32>,
+
+    /// The type of an SDN zone (for type 'sdn').
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub zone_type: Option<String>,
 }
 
 #[api]
@@ -974,6 +1005,20 @@ pub enum ClusterResourceKind {
 serde_plain::derive_display_from_serialize!(ClusterResourceKind);
 serde_plain::derive_fromstr_from_deserialize!(ClusterResourceKind);
 
+#[api]
+/// The type of network resource (for type 'network').
+#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+pub enum ClusterResourceNetworkType {
+    #[serde(rename = "fabric")]
+    /// fabric.
+    Fabric,
+    #[serde(rename = "zone")]
+    /// zone.
+    Zone,
+}
+serde_plain::derive_display_from_serialize!(ClusterResourceNetworkType);
+serde_plain::derive_fromstr_from_deserialize!(ClusterResourceNetworkType);
+
 #[api]
 /// Resource type.
 #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
@@ -999,6 +1044,9 @@ pub enum ClusterResourceType {
     #[serde(rename = "sdn")]
     /// sdn.
     Sdn,
+    #[serde(rename = "network")]
+    /// network.
+    Network,
 }
 serde_plain::derive_display_from_serialize!(ClusterResourceType);
 serde_plain::derive_fromstr_from_deserialize!(ClusterResourceType);
-- 
2.47.3


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


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

* [pdm-devel] [PATCH proxmox 3/3] pve-api-types: extend ClusterResourceNetworkType
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 1/3] pve-api-types: update /cluster/resources endpoint Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 2/3] pve-api-types: regenerate Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 1/3] resources: support new pve network resource type Stefan Hanreich
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 pve-api-types/src/sdn.rs | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/pve-api-types/src/sdn.rs b/pve-api-types/src/sdn.rs
index 000da6f5..8188c35f 100644
--- a/pve-api-types/src/sdn.rs
+++ b/pve-api-types/src/sdn.rs
@@ -1,4 +1,4 @@
-use crate::{SdnController, SdnVnet, SdnZone};
+use crate::{ClusterResourceNetworkType, SdnController, SdnVnet, SdnZone};
 
 impl SdnVnet {
     /// returns the tag from the pending property if it has a value, otherwise it returns self.tag
@@ -31,3 +31,12 @@ impl SdnController {
             .or(self.asn)
     }
 }
+
+impl ClusterResourceNetworkType {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            ClusterResourceNetworkType::Fabric => "fabric",
+            ClusterResourceNetworkType::Zone => "zone",
+        }
+    }
+}
-- 
2.47.3


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


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

* [pdm-devel] [PATCH proxmox-datacenter-manager 1/3] resources: support new pve network resource type
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
                   ` (2 preceding siblings ...)
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 3/3] pve-api-types: extend ClusterResourceNetworkType Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 2/3] resources: api: add network type match category Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type Stefan Hanreich
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

Proxmox VE now returns the new network resource type, in addition to
the sdn resource type. This patch adds support for this new format.

Since PDM makes one API call to a single remote (`/cluster/resources`)
to obtain the resources, handling of the different resource types is
already done by the Proxmox VE remote. If it is on a version that uses
the sdn resource type, then only sdn resources will get returned and
vice vers.

For that reason, the whole output can simply be deserialized and used
without any special case handling on PDM side. Both resource types get
mapped to the same PDM-internal struct. An additional legacy flag has
been added to the zone resource, in order for us to be able to
distinguish whether the resource came from an older or newer PVE
version. This is required to generate the correct URLs for the
resources in the frontend, among other things.

Distinguishing between different types of network entities, now
happens via the network_type, field of network resources. Instead of
returning a specific type like `sdn-zone`, use the same type for all
network resources and provide filtering. This will be implemented in a
successive patch.

There is no need for providing a backwards-compatible Resource struct
for deserialization purposes, since the only place where the current
sdn resources are used, use the ephemeral resource cache - which gets
deleted upon restarting the service.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 cli/client/src/resources.rs                  |   9 +-
 lib/pdm-api-types/src/resource.rs            | 115 +++++++++++++------
 lib/pdm-client/src/lib.rs                    |   2 +
 server/src/api/resources.rs                  |  87 ++++++++++----
 server/src/metric_collection/top_entities.rs |   2 +-
 5 files changed, 158 insertions(+), 57 deletions(-)

diff --git a/cli/client/src/resources.rs b/cli/client/src/resources.rs
index dbf9f26..2fc30e2 100644
--- a/cli/client/src/resources.rs
+++ b/cli/client/src/resources.rs
@@ -52,7 +52,7 @@ async fn get_resources(max_age: Option<u64>) -> Result<(), Error> {
                     Resource::PveQemu(r) => println!("{}", PrintResource(r)),
                     Resource::PveLxc(r) => println!("{}", PrintResource(r)),
                     Resource::PveNode(r) => println!("{}", PrintResource(r)),
-                    Resource::PveSdn(r) => println!("{}", PrintResource(r)),
+                    Resource::PveNetwork(r) => println!("{}", PrintResource(r)),
                     Resource::PbsNode(r) => println!("{}", PrintResource(r)),
                     Resource::PbsDatastore(r) => println!("{}", PrintResource(r)),
                 }
@@ -70,7 +70,7 @@ fn resource_order(item: &Resource) -> usize {
         Resource::PveStorage(_) => 1,
         Resource::PveLxc(_) => 2,
         Resource::PveQemu(_) => 3,
-        Resource::PveSdn(_) => 4,
+        Resource::PveNetwork(_) => 4,
 
         Resource::PbsNode(_) => 0,
         Resource::PbsDatastore(_) => 1,
@@ -150,11 +150,12 @@ impl fmt::Display for PrintResource<resource::PveNodeResource> {
     }
 }
 
-impl fmt::Display for PrintResource<resource::PveSdnResource> {
+impl fmt::Display for PrintResource<resource::PveNetworkResource> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(
             f,
-            "    sdn zone {name} ({status}) on {node}",
+            "    network {network_type} {name} ({status}) on {node}",
+            network_type = self.0.network_type(),
             name = self.0.name(),
             status = self.0.status(),
             node = self.0.node(),
diff --git a/lib/pdm-api-types/src/resource.rs b/lib/pdm-api-types/src/resource.rs
index e09678d..d89b45d 100644
--- a/lib/pdm-api-types/src/resource.rs
+++ b/lib/pdm-api-types/src/resource.rs
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
 use proxmox_schema::{api, ApiStringFormat, ApiType, EnumEntry, OneOfSchema, Schema, StringSchema};
 
 use super::remotes::{RemoteType, REMOTE_ID_SCHEMA};
+use pve_api_types::ClusterResourceNetworkType;
 
 /// High PBS datastore usage threshold
 pub const PBS_DATASTORE_HIGH_USAGE_THRESHOLD: f64 = 0.80;
@@ -25,7 +26,7 @@ pub enum Resource {
     PveQemu(PveQemuResource),
     PveLxc(PveLxcResource),
     PveNode(PveNodeResource),
-    PveSdn(PveSdnResource),
+    PveNetwork(PveNetworkResource),
     PbsNode(PbsNodeResource),
     PbsDatastore(PbsDatastoreResource),
 }
@@ -39,7 +40,15 @@ impl Resource {
             Resource::PveQemu(r) => format!("qemu/{}", r.vmid),
             Resource::PveLxc(r) => format!("lxc/{}", r.vmid),
             Resource::PveNode(r) => format!("node/{}", r.node),
-            Resource::PveSdn(PveSdnResource::Zone(r)) => format!("sdn/{}/{}", r.node, r.name),
+            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(),
         }
@@ -53,7 +62,7 @@ impl Resource {
             Resource::PveQemu(r) => r.id.as_str(),
             Resource::PveLxc(r) => r.id.as_str(),
             Resource::PveNode(r) => r.id.as_str(),
-            Resource::PveSdn(r) => r.id(),
+            Resource::PveNetwork(r) => r.id(),
             Resource::PbsNode(r) => r.id.as_str(),
             Resource::PbsDatastore(r) => r.id.as_str(),
         }
@@ -67,7 +76,7 @@ impl Resource {
             Resource::PveQemu(r) => r.name.as_str(),
             Resource::PveLxc(r) => r.name.as_str(),
             Resource::PveNode(r) => r.node.as_str(),
-            Resource::PveSdn(r) => r.name(),
+            Resource::PveNetwork(r) => r.name(),
             Resource::PbsNode(r) => r.name.as_str(),
             Resource::PbsDatastore(r) => r.name.as_str(),
         }
@@ -78,7 +87,7 @@ impl Resource {
             Resource::PveStorage(_) => ResourceType::PveStorage,
             Resource::PveQemu(_) => ResourceType::PveQemu,
             Resource::PveLxc(_) => ResourceType::PveLxc,
-            Resource::PveSdn(PveSdnResource::Zone(_)) => ResourceType::PveSdnZone,
+            Resource::PveNetwork(_) => ResourceType::PveNetwork,
             Resource::PveNode(_) | Resource::PbsNode(_) => ResourceType::Node,
             Resource::PbsDatastore(_) => ResourceType::PbsDatastore,
         }
@@ -90,7 +99,7 @@ impl Resource {
             Resource::PveQemu(r) => r.status.as_str(),
             Resource::PveLxc(r) => r.status.as_str(),
             Resource::PveNode(r) => r.status.as_str(),
-            Resource::PveSdn(r) => r.status().as_str(),
+            Resource::PveNetwork(r) => r.status(),
             Resource::PbsNode(r) => {
                 if r.uptime > 0 {
                     "online"
@@ -138,9 +147,9 @@ pub enum ResourceType {
     /// PVE LXC Resource
     #[serde(rename = "lxc")]
     PveLxc,
-    /// PVE SDN Resource
-    #[serde(rename = "sdn-zone")]
-    PveSdnZone,
+    /// PVE Network Resource
+    #[serde(rename = "network")]
+    PveNetwork,
     /// PBS Datastore Resource
     #[serde(rename = "datastore")]
     PbsDatastore,
@@ -156,7 +165,7 @@ impl ResourceType {
             ResourceType::PveStorage => "storage",
             ResourceType::PveQemu => "qemu",
             ResourceType::PveLxc => "lxc",
-            ResourceType::PveSdnZone => "sdn-zone",
+            ResourceType::PveNetwork => "network",
             ResourceType::PbsDatastore => "datastore",
             ResourceType::Node => "node",
         }
@@ -177,7 +186,7 @@ impl std::str::FromStr for ResourceType {
             "storage" => ResourceType::PveStorage,
             "qemu" => ResourceType::PveQemu,
             "lxc" => ResourceType::PveLxc,
-            "sdn-zone" => ResourceType::PveSdnZone,
+            "network" => ResourceType::PveNetwork,
             "datastore" => ResourceType::PbsDatastore,
             "node" => ResourceType::Node,
             _ => bail!("invalid resource type"),
@@ -201,7 +210,7 @@ pub enum PveResource {
     Qemu(PveQemuResource),
     Lxc(PveLxcResource),
     Node(PveNodeResource),
-    Sdn(PveSdnResource),
+    Network(PveNetworkResource),
 }
 
 #[api(
@@ -352,15 +361,48 @@ pub struct PveStorageResource {
 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
 #[serde(rename_all = "kebab-case")]
 /// SDN Zone
-pub struct SdnZoneResource {
+pub struct NetworkZoneResource {
     /// Resource ID
     pub id: String,
+    /// Cluster node name
+    pub node: String,
     /// Name of the resource
-    pub name: String,
+    pub network: String,
+    /// SDN status (available / error)
+    pub status: SdnStatus,
+    /// Zone type
+    pub zone_type: String,
+    /// legacy
+    pub legacy: bool,
+}
+
+impl NetworkZoneResource {
+    pub fn status(&self) -> SdnStatus {
+        self.status
+    }
+}
+
+#[api]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// SDN Fabric
+pub struct NetworkFabricResource {
+    /// Resource ID
+    pub id: String,
     /// Cluster node name
     pub node: String,
+    /// Name of the resource
+    pub network: String,
     /// SDN status (available / error)
     pub status: SdnStatus,
+    /// faabric protocol
+    pub protocol: String,
+}
+
+impl NetworkFabricResource {
+    pub fn status(&self) -> SdnStatus {
+        self.status
+    }
 }
 
 #[derive(Clone, Debug, Serialize, PartialEq, Copy, Default)]
@@ -407,58 +449,67 @@ impl ApiType for SdnStatus {
 }
 
 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
-#[serde(tag = "sdn_type", rename_all = "lowercase")]
+#[serde(tag = "network_type", rename_all = "lowercase")]
 /// SDN resource in PDM
-pub enum PveSdnResource {
-    Zone(SdnZoneResource),
+pub enum PveNetworkResource {
+    Fabric(NetworkFabricResource),
+    Zone(NetworkZoneResource),
 }
 
-impl ApiType for PveSdnResource {
+impl ApiType for PveNetworkResource {
     const API_SCHEMA: Schema = OneOfSchema::new(
-        "PVE SDN resource",
+        "PVE Network resource",
         &(
-            "sdn_type",
+            "network_type",
             false,
-            &StringSchema::new("PVE SDN resource type")
-                .format(&ApiStringFormat::Enum(&[EnumEntry::new(
-                    "zone",
-                    "An SDN zone.",
-                )]))
+            &StringSchema::new("PVE Network resource type")
+                .format(&ApiStringFormat::Enum(&[
+                    EnumEntry::new("zone", "An SDN zone."),
+                    EnumEntry::new("fabric", "An SDN fabric."),
+                ]))
                 .schema(),
         ),
-        &[("zone", &SdnZoneResource::API_SCHEMA)],
+        &[
+            ("fabric", &NetworkFabricResource::API_SCHEMA),
+            ("zone", &NetworkZoneResource::API_SCHEMA),
+        ],
     )
     .schema();
 }
 
-impl PveSdnResource {
+impl PveNetworkResource {
     pub fn id(&self) -> &str {
         match self {
             Self::Zone(zone) => zone.id.as_str(),
+            Self::Fabric(fabric) => fabric.id.as_str(),
         }
     }
 
     pub fn name(&self) -> &str {
         match self {
-            Self::Zone(zone) => zone.name.as_str(),
+            Self::Zone(zone) => zone.network.as_str(),
+            Self::Fabric(fabric) => fabric.network.as_str(),
         }
     }
 
     pub fn node(&self) -> &str {
         match self {
             Self::Zone(zone) => zone.node.as_str(),
+            Self::Fabric(fabric) => fabric.node.as_str(),
         }
     }
 
-    pub fn status(&self) -> SdnStatus {
+    pub fn status(&self) -> &str {
         match self {
-            Self::Zone(zone) => zone.status,
+            Self::Zone(zone) => zone.status.as_str(),
+            Self::Fabric(fabric) => fabric.status.as_str(),
         }
     }
 
-    pub fn sdn_type(&self) -> &'static str {
+    pub fn network_type(&self) -> ClusterResourceNetworkType {
         match self {
-            Self::Zone(_) => "sdn-zone",
+            Self::Zone(_) => ClusterResourceNetworkType::Zone,
+            Self::Fabric(_) => ClusterResourceNetworkType::Fabric,
         }
     }
 }
diff --git a/lib/pdm-client/src/lib.rs b/lib/pdm-client/src/lib.rs
index 0cab769..dd1eb02 100644
--- a/lib/pdm-client/src/lib.rs
+++ b/lib/pdm-client/src/lib.rs
@@ -65,6 +65,8 @@ pub mod types {
     };
     pub use pve_api_types::{ListControllersType, ListZonesType, SdnObjectState};
 
+    pub use pve_api_types::ClusterResourceNetworkType;
+
     pub use pve_api_types::StorageStatus as PveStorageStatus;
 }
 
diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
index dad3e6b..0204f95 100644
--- a/server/src/api/resources.rs
+++ b/server/src/api/resources.rs
@@ -11,9 +11,10 @@ use pbs_api_types::{
 };
 use pdm_api_types::remotes::{Remote, RemoteType};
 use pdm_api_types::resource::{
-    FailedRemote, PbsDatastoreResource, PbsNodeResource, PveLxcResource, PveNodeResource,
-    PveQemuResource, PveSdnResource, PveStorageResource, RemoteResources, Resource, ResourceType,
-    ResourcesStatus, SdnStatus, SdnZoneResource, TopEntities, PBS_DATASTORE_HIGH_USAGE_THRESHOLD,
+    FailedRemote, NetworkFabricResource, NetworkZoneResource, PbsDatastoreResource,
+    PbsNodeResource, PveLxcResource, PveNetworkResource, PveNodeResource, PveQemuResource,
+    PveStorageResource, RemoteResources, Resource, ResourceType, ResourcesStatus, SdnStatus,
+    TopEntities, PBS_DATASTORE_HIGH_USAGE_THRESHOLD,
 };
 use pdm_api_types::subscription::{
     NodeSubscriptionInfo, RemoteSubscriptionState, RemoteSubscriptions, SubscriptionLevel,
@@ -28,7 +29,7 @@ use proxmox_rrd_api_types::RrdTimeframe;
 use proxmox_schema::{api, parse_boolean};
 use proxmox_sortable_macro::sortable;
 use proxmox_subscription::SubscriptionStatus;
-use pve_api_types::{ClusterResource, ClusterResourceType};
+use pve_api_types::{ClusterResource, ClusterResourceNetworkType, ClusterResourceType};
 
 use crate::connection;
 use crate::metric_collection::top_entities;
@@ -441,18 +442,18 @@ pub async fn get_status(
                     "offline" => counts.pve_nodes.offline += 1,
                     _ => counts.pve_nodes.unknown += 1,
                 },
-                Resource::PveSdn(r) => {
-                    let PveSdnResource::Zone(_) = &r;
-
-                    match r.status() {
-                        SdnStatus::Available => {
-                            counts.sdn_zones.available += 1;
-                        }
-                        SdnStatus::Error => {
-                            counts.sdn_zones.error += 1;
-                        }
-                        SdnStatus::Unknown => {
-                            counts.sdn_zones.unknown += 1;
+                Resource::PveNetwork(r) => {
+                    if let PveNetworkResource::Zone(zone) = r {
+                        match zone.status() {
+                            SdnStatus::Available => {
+                                counts.sdn_zones.available += 1;
+                            }
+                            SdnStatus::Error => {
+                                counts.sdn_zones.error += 1;
+                            }
+                            SdnStatus::Unknown => {
+                                counts.sdn_zones.unknown += 1;
+                            }
                         }
                     }
                 }
@@ -1000,30 +1001,76 @@ pub(super) fn map_pve_storage(
     }
 }
 
-pub(super) fn map_pve_sdn(remote: &str, resource: ClusterResource) -> Option<PveSdnResource> {
+pub(super) fn map_pve_sdn(remote: &str, resource: ClusterResource) -> Option<PveNetworkResource> {
     match resource.ty {
         ClusterResourceType::Sdn => {
             let node = resource.node.unwrap_or_default();
 
-            Some(PveSdnResource::Zone(SdnZoneResource {
+            Some(PveNetworkResource::Zone(NetworkZoneResource {
                 id: format!("remote/{remote}/sdn/{}", &resource.id),
-                name: resource.sdn.unwrap_or_default(),
+                network: resource.sdn.unwrap_or_default(),
                 node,
+                // is empty in this format
+                zone_type: resource.zone_type.unwrap_or_default(),
                 status: SdnStatus::from_str(resource.status.unwrap_or_default().as_str())
                     .unwrap_or_default(),
+                legacy: true,
             }))
         }
         _ => None,
     }
 }
 
+pub(super) fn map_pve_network(
+    remote: &str,
+    resource: ClusterResource,
+) -> Option<PveNetworkResource> {
+    match resource.ty {
+        ClusterResourceType::Network => {
+            let Some(network_type) = resource.network_type else {
+                return None;
+            };
+
+            let id = format!("remote/{remote}/{}", &resource.id);
+            let node = resource.node.unwrap_or_default();
+            let network = resource.network.unwrap_or_default();
+            let status = SdnStatus::from_str(resource.status.unwrap_or_default().as_str())
+                .unwrap_or_default();
+
+            match network_type {
+                ClusterResourceNetworkType::Fabric => {
+                    Some(PveNetworkResource::Fabric(NetworkFabricResource {
+                        id,
+                        network,
+                        node,
+                        status,
+                        protocol: resource.protocol.unwrap_or_default(),
+                    }))
+                }
+                ClusterResourceNetworkType::Zone => {
+                    Some(PveNetworkResource::Zone(NetworkZoneResource {
+                        id,
+                        network,
+                        node,
+                        status,
+                        zone_type: resource.zone_type.unwrap_or_default(),
+                        legacy: false,
+                    }))
+                }
+            }
+        }
+        _ => None,
+    }
+}
+
 fn map_pve_resource(remote: &str, resource: ClusterResource) -> Option<Resource> {
     match resource.ty {
         ClusterResourceType::Node => map_pve_node(remote, resource).map(Resource::PveNode),
         ClusterResourceType::Lxc => map_pve_lxc(remote, resource).map(Resource::PveLxc),
         ClusterResourceType::Qemu => map_pve_qemu(remote, resource).map(Resource::PveQemu),
         ClusterResourceType::Storage => map_pve_storage(remote, resource).map(Resource::PveStorage),
-        ClusterResourceType::Sdn => map_pve_sdn(remote, resource).map(Resource::PveSdn),
+        ClusterResourceType::Sdn => map_pve_sdn(remote, resource).map(Resource::PveNetwork),
+        ClusterResourceType::Network => map_pve_network(remote, resource).map(Resource::PveNetwork),
         _ => None,
     }
 }
diff --git a/server/src/metric_collection/top_entities.rs b/server/src/metric_collection/top_entities.rs
index 73a3e63..5b58584 100644
--- a/server/src/metric_collection/top_entities.rs
+++ b/server/src/metric_collection/top_entities.rs
@@ -111,7 +111,7 @@ pub fn calculate_top(
                             }
                         }
                     }
-                    Resource::PveSdn(_) => {}
+                    Resource::PveNetwork(_) => {}
                     Resource::PbsDatastore(_) => {}
                 }
             }
-- 
2.47.3


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


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

* [pdm-devel] [PATCH proxmox-datacenter-manager 2/3] resources: api: add network type match category
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
                   ` (3 preceding siblings ...)
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 1/3] resources: support new pve network resource type Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type Stefan Hanreich
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

All network resources are now contained inside the top-level
PveNetwork variant of the Resource enum. In order to provide filtering
based on the sub-types of the network entities, introduce a new
network-type filter to the search, which can be used for matching
specific types of network resources.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 server/src/api/resources.rs | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs
index 0204f95..8b9d3b1 100644
--- a/server/src/api/resources.rs
+++ b/server/src/api/resources.rs
@@ -54,6 +54,7 @@ const SUBDIRS: SubdirMap = &sorted!([
 
 enum MatchCategory {
     Type,
+    NetworkType,
     Name,
     Id,
     Status,
@@ -69,6 +70,7 @@ impl std::str::FromStr for MatchCategory {
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let category = match s {
             "type" => MatchCategory::Type,
+            "network-type" => MatchCategory::NetworkType,
             "name" => MatchCategory::Name,
             "id" => MatchCategory::Id,
             "status" => MatchCategory::Status,
@@ -85,7 +87,7 @@ impl std::str::FromStr for MatchCategory {
 impl MatchCategory {
     fn matches(&self, value: &str, search_term: &str) -> bool {
         match self {
-            MatchCategory::Type | MatchCategory::Status => value
+            MatchCategory::Type | MatchCategory::Status | MatchCategory::NetworkType => value
                 .to_lowercase()
                 .starts_with(&search_term.to_lowercase()),
             MatchCategory::Name | MatchCategory::Id | MatchCategory::Remote => {
@@ -132,6 +134,12 @@ fn resource_matches_search_term(
             },
             MatchCategory::Remote => category.matches(remote_name, &term.value),
             MatchCategory::RemoteType => return None,
+            MatchCategory::NetworkType => match resource {
+                Resource::PveNetwork(network_resource) => {
+                    category.matches(&network_resource.network_type().as_str(), &term.value)
+                }
+                _ => false,
+            },
         },
         Some(Err(_)) => false,
         None => {
@@ -162,6 +170,7 @@ fn remote_matches_search_term(
             MatchCategory::Property => false,
             MatchCategory::Template => false,
             MatchCategory::RemoteType => category.matches(&remote.ty.to_string(), &term.value),
+            MatchCategory::NetworkType => false,
         },
         Some(Err(_)) => false,
         None => {
-- 
2.47.3


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


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

* [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type
  2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
                   ` (4 preceding siblings ...)
  2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 2/3] resources: api: add network type match category Stefan Hanreich
@ 2025-11-07 14:40 ` Stefan Hanreich
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Hanreich @ 2025-11-07 14:40 UTC (permalink / raw)
  To: pdm-devel

Instead of an SDN resource, the backend now returns the network
resource. Fix all occurences of this enum in the UI code, so they use
the new variant.

In addition, network entities can now be filtered via the network-type
search category. Adjust the SDN zone panel from the Dashboard, so it
supports this new match category, when filtering for SDN zones.

Depending on the current version of the remote, the URL structure for
inspecting resources has changed. By utilizing the legacy parameter
returned by the backend, we can decide which link to generate in the
SDN zone tree.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 ui/src/dashboard/sdn_zone_panel.rs |   7 +-
 ui/src/lib.rs                      |   6 +-
 ui/src/pve/remote.rs               |   2 +-
 ui/src/pve/tree.rs                 |   2 +-
 ui/src/pve/utils.rs                |  14 ++-
 ui/src/renderer.rs                 |   7 +-
 ui/src/sdn/zone_tree.rs            | 136 ++++++++++++++++++-----------
 7 files changed, 106 insertions(+), 68 deletions(-)

diff --git a/ui/src/dashboard/sdn_zone_panel.rs b/ui/src/dashboard/sdn_zone_panel.rs
index 8112550..ed734f6 100644
--- a/ui/src/dashboard/sdn_zone_panel.rs
+++ b/ui/src/dashboard/sdn_zone_panel.rs
@@ -148,9 +148,12 @@ fn create_list_tile(
 }
 
 fn create_sdn_zone_search_term(status: Option<SdnStatus>) -> Search {
-    let resource_type: ResourceType = ResourceType::PveSdnZone;
+    let resource_type: ResourceType = ResourceType::PveNetwork;
 
-    let mut terms = vec![SearchTerm::new(resource_type.as_str()).category(Some("type"))];
+    let mut terms = vec![
+        SearchTerm::new(resource_type.as_str()).category(Some("type")),
+        SearchTerm::new("zone").category(Some("network-type")),
+    ];
 
     if let Some(status) = status {
         terms.push(SearchTerm::new(status.to_string()).category(Some("status")));
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index bdf890a..98761e0 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -1,6 +1,6 @@
 use js_sys::{Array, JsString, Object};
 use pdm_api_types::remotes::RemoteType;
-use pdm_api_types::resource::{PveLxcResource, PveQemuResource, PveSdnResource};
+use pdm_api_types::resource::{PveLxcResource, PveNetworkResource, PveQemuResource};
 use pdm_client::types::Resource;
 use serde::{Deserialize, Serialize};
 
@@ -182,7 +182,7 @@ pub(crate) fn navigate_to<C: yew::Component>(
                         true,
                         format!("storage+{}+{}", storage.node, storage.storage),
                     ),
-                    pdm_client::types::Resource::PveSdn(PveSdnResource::Zone(_)) => {
+                    pdm_client::types::Resource::PveNetwork(PveNetworkResource::Zone(_)) => {
                         (false, "sdn/zones".to_string())
                     }
                     pdm_client::types::Resource::PbsDatastore(store) => (true, store.name.clone()),
@@ -208,7 +208,7 @@ pub(crate) fn get_resource_node(resource: &Resource) -> Option<&str> {
         Resource::PveQemu(qemu) => Some(&qemu.node),
         Resource::PveLxc(lxc) => Some(&lxc.node),
         Resource::PveNode(node) => Some(&node.node),
-        Resource::PveSdn(sdn) => Some(sdn.node()),
+        Resource::PveNetwork(network) => Some(network.node()),
         Resource::PbsNode(_) => None,
         Resource::PbsDatastore(_) => None,
     }
diff --git a/ui/src/pve/remote.rs b/ui/src/pve/remote.rs
index 09afcfd..2c26e7e 100644
--- a/ui/src/pve/remote.rs
+++ b/ui/src/pve/remote.rs
@@ -115,7 +115,7 @@ impl RemotePanelComp {
                         _ => level = Some(""),
                     }
                 }
-                PveResource::Sdn(_) => {}
+                PveResource::Network(_) => {}
             }
         }
         // render, but this would be all better with some actual types...
diff --git a/ui/src/pve/tree.rs b/ui/src/pve/tree.rs
index 9ef3c6d..605ba04 100644
--- a/ui/src/pve/tree.rs
+++ b/ui/src/pve/tree.rs
@@ -199,7 +199,7 @@ impl PveTreeComp {
                     }
                     node.append(PveTreeNode::Storage(storage.clone()));
                 }
-                PveResource::Sdn(_) => {}
+                PveResource::Network(_) => {}
             }
         }
         if !self.loaded {
diff --git a/ui/src/pve/utils.rs b/ui/src/pve/utils.rs
index 5923855..fe412a2 100644
--- a/ui/src/pve/utils.rs
+++ b/ui/src/pve/utils.rs
@@ -1,7 +1,7 @@
 use anyhow::Error;
 use pdm_api_types::resource::{
-    PveLxcResource, PveNodeResource, PveQemuResource, PveStorageResource, SdnStatus,
-    SdnZoneResource,
+    PveLxcResource, PveNetworkResource, PveNodeResource, PveQemuResource, PveStorageResource,
+    SdnStatus,
 };
 use pdm_client::types::{
     LxcConfig, LxcConfigMp, LxcConfigRootfs, LxcConfigUnused, PveQmIde, QemuConfig, QemuConfigSata,
@@ -90,12 +90,18 @@ pub fn render_node_status_icon(node: &PveNodeResource) -> Container {
 }
 
 /// Renders the status icon for a PveSdnZone
-pub fn render_sdn_status_icon(zone: &SdnZoneResource) -> Container {
-    let extra = match zone.status {
+pub fn render_sdn_status_icon(network: &PveNetworkResource) -> Container {
+    let sdn_status = match network {
+        PveNetworkResource::Zone(zone) => zone.status(),
+        PveNetworkResource::Fabric(fabric) => fabric.status(),
+    };
+
+    let extra = match sdn_status {
         SdnStatus::Available => NodeState::Online,
         SdnStatus::Error => NodeState::Offline,
         _ => NodeState::Unknown,
     };
+
     Container::new()
         .class("pdm-type-icon")
         .with_child(Fa::new("th").fixed_width())
diff --git a/ui/src/renderer.rs b/ui/src/renderer.rs
index 5f7c658..12b3e94 100644
--- a/ui/src/renderer.rs
+++ b/ui/src/renderer.rs
@@ -1,4 +1,3 @@
-use pdm_api_types::resource::PveSdnResource;
 use proxmox_yew_comp::MeterLabel;
 use pwt::{
     css::AlignItems,
@@ -19,7 +18,7 @@ pub fn render_resource_name(resource: &Resource, vmid_first: bool) -> String {
         Resource::PveQemu(qemu) => pve::utils::render_qemu_name(qemu, vmid_first),
         Resource::PveLxc(lxc) => pve::utils::render_lxc_name(lxc, vmid_first),
         Resource::PveNode(node) => node.node.clone(),
-        Resource::PveSdn(sdn) => sdn.name().to_string(),
+        Resource::PveNetwork(network) => network.name().to_string(),
         Resource::PbsNode(node) => node.name.clone(),
         Resource::PbsDatastore(store) => store.name.clone(),
     }
@@ -31,7 +30,7 @@ pub fn render_resource_icon(resource: &Resource) -> Fa {
         Resource::PveQemu(_) => "desktop",
         Resource::PveLxc(_) => "cube",
         Resource::PveNode(_) => "building",
-        Resource::PveSdn(_) => "fa-sdn",
+        Resource::PveNetwork(_) => "fa-sdn",
         Resource::PbsNode(_) => "building-o",
         Resource::PbsDatastore(_) => "floppy-o",
     };
@@ -45,7 +44,7 @@ pub fn render_status_icon(resource: &Resource) -> Container {
         Resource::PveQemu(qemu) => pve::utils::render_qemu_status_icon(qemu),
         Resource::PveLxc(lxc) => pve::utils::render_lxc_status_icon(lxc),
         Resource::PveNode(node) => pve::utils::render_node_status_icon(node),
-        Resource::PveSdn(PveSdnResource::Zone(zone)) => pve::utils::render_sdn_status_icon(zone),
+        Resource::PveNetwork(network) => pve::utils::render_sdn_status_icon(network),
         // FIXME: implement remaining types
         _ => Container::new().with_child(render_resource_icon(resource)),
     }
diff --git a/ui/src/sdn/zone_tree.rs b/ui/src/sdn/zone_tree.rs
index 1f53438..536804c 100644
--- a/ui/src/sdn/zone_tree.rs
+++ b/ui/src/sdn/zone_tree.rs
@@ -6,10 +6,8 @@ use std::rc::Rc;
 use yew::virtual_dom::{Key, VComp, VNode};
 use yew::{html, ContextHandle, Html, Properties};
 
-use pdm_api_types::resource::{
-    PveSdnResource, RemoteResources, ResourceType, SdnStatus, SdnZoneResource,
-};
-use pdm_client::types::Resource;
+use pdm_api_types::resource::{PveNetworkResource, RemoteResources, ResourceType, SdnStatus};
+use pdm_client::types::{ClusterResourceNetworkType, Resource};
 use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
 use pwt::props::EventSubscriber;
 use pwt::widget::{ActionIcon, Button, Toolbar};
@@ -44,11 +42,13 @@ impl From<ZoneTree> for VNode {
 }
 
 #[derive(Clone, PartialEq, Debug)]
-struct ZoneData {
+struct NetworkData {
     remote: String,
     node: String,
     name: String,
+    network_type: ClusterResourceNetworkType,
     status: SdnStatus,
+    legacy: bool,
 }
 
 #[derive(Clone, PartialEq, Debug)]
@@ -56,17 +56,31 @@ enum ZoneTreeEntry {
     Root,
     Remote(String),
     Node(String, String),
-    Zone(ZoneData),
+    NetworkResource(NetworkData),
 }
 
 impl ZoneTreeEntry {
-    fn from_zone_resource(remote: String, value: SdnZoneResource) -> Self {
-        Self::Zone(ZoneData {
-            remote,
-            node: value.node.clone(),
-            name: value.name.clone(),
-            status: value.status,
-        })
+    fn from_network_resource(remote: String, value: PveNetworkResource) -> Self {
+        let network_type = value.network_type();
+
+        match value {
+            PveNetworkResource::Zone(zone) => Self::NetworkResource(NetworkData {
+                remote: remote,
+                node: zone.node,
+                name: zone.network,
+                status: zone.status,
+                network_type,
+                legacy: zone.legacy,
+            }),
+            PveNetworkResource::Fabric(fabric) => Self::NetworkResource(NetworkData {
+                remote: remote,
+                node: fabric.node,
+                name: fabric.network,
+                status: fabric.status,
+                network_type,
+                legacy: false,
+            }),
+        }
     }
 
     fn name(&self) -> &str {
@@ -74,7 +88,7 @@ impl ZoneTreeEntry {
             Self::Root => "",
             Self::Remote(name) => name,
             Self::Node(_, name) => name,
-            Self::Zone(zone) => &zone.name,
+            Self::NetworkResource(network_resource) => &network_resource.name,
         }
     }
 }
@@ -85,7 +99,9 @@ impl ExtractPrimaryKey for ZoneTreeEntry {
             ZoneTreeEntry::Root => "/".to_string(),
             ZoneTreeEntry::Remote(name) => format!("/{name}"),
             ZoneTreeEntry::Node(remote_name, name) => format!("/{remote_name}/{name}"),
-            ZoneTreeEntry::Zone(zone) => format!("/{}/{}/{}", zone.remote, zone.node, zone.name),
+            ZoneTreeEntry::NetworkResource(r) => {
+                format!("/{}/{}/{}/{}", r.remote, r.node, r.network_type, r.name)
+            }
         })
     }
 }
@@ -121,7 +137,10 @@ impl ZoneTreeComponent {
                     let icon = match entry {
                         ZoneTreeEntry::Remote(_) => Some("server"),
                         ZoneTreeEntry::Node(_, _) => Some("building"),
-                        ZoneTreeEntry::Zone(_) => Some("th"),
+                        ZoneTreeEntry::NetworkResource(r) => match r.network_type {
+                            ClusterResourceNetworkType::Fabric => Some("road"),
+                            ClusterResourceNetworkType::Zone => Some("th"),
+                        },
                         _ => None,
                     };
 
@@ -138,8 +157,8 @@ impl ZoneTreeComponent {
                 .render(|entry: &ZoneTreeEntry| {
                     let mut row = Row::new().class(css::AlignItems::Baseline).gap(2);
 
-                    if let ZoneTreeEntry::Zone(zone) = entry {
-                        row = match zone.status {
+                    if let ZoneTreeEntry::NetworkResource(r) = entry {
+                        row = match r.status {
                             SdnStatus::Available => {
                                 row.with_child(Fa::new("check").class(FontColor::Success))
                             }
@@ -149,7 +168,7 @@ impl ZoneTreeComponent {
                             _ => row,
                         };
 
-                        row = row.with_child(zone.status);
+                        row = row.with_child(r.status);
                     } else {
                         row = row.with_child("");
                     }
@@ -168,9 +187,22 @@ impl ZoneTreeComponent {
                             let hash = "#v1:0:18:4:::::::53";
                             crate::get_deep_url_low_level(link.yew_link(), remote, None, hash)
                         }
-                        ZoneTreeEntry::Zone(zone_data) => {
-                            let id = format!("sdn/{}/{}", zone_data.node, zone_data.name);
-                            get_deep_url(link.yew_link(), &zone_data.remote, None, &id)
+                        ZoneTreeEntry::NetworkResource(network_resource) => {
+                            if network_resource.legacy {
+                                let id = format!(
+                                    "sdn/{}/{}",
+                                    network_resource.node, network_resource.name
+                                );
+                                get_deep_url(link.yew_link(), &network_resource.remote, None, &id)
+                            } else {
+                                let id = format!(
+                                    "network/{}/{}/{}",
+                                    network_resource.node,
+                                    network_resource.network_type,
+                                    network_resource.name
+                                );
+                                get_deep_url(link.yew_link(), &network_resource.remote, None, &id)
+                            }
                         }
                     };
 
@@ -211,36 +243,34 @@ fn build_store_from_response(
         remote.set_expanded(true);
 
         for resource in resources.resources {
-            if let Resource::PveSdn(PveSdnResource::Zone(zone_resource)) = resource {
-                let node_entry = remote.children_mut().find(|entry| {
-                    if let ZoneTreeEntry::Node(_, name) = entry.record() {
-                        if name == &zone_resource.node {
-                            return true;
-                        }
-                    }
-
-                    false
-                });
-
-                let node_name = zone_resource.node.clone();
-
-                let entry =
-                    ZoneTreeEntry::from_zone_resource(resources.remote.clone(), zone_resource);
-
-                match node_entry {
-                    Some(mut node_entry) => {
-                        node_entry.append(entry);
-                    }
-                    None => {
-                        let mut node_entry =
-                            remote.append(ZoneTreeEntry::Node(resources.remote.clone(), node_name));
-
-                        node_entry.set_expanded(true);
-
-                        node_entry.append(entry);
-                    }
-                };
-            }
+            let Resource::PveNetwork(resource) = resource else {
+                continue;
+            };
+
+            let node_entry = remote.children_mut().find(|entry| {
+                if let ZoneTreeEntry::Node(_, name) = entry.record() {
+                    return name == resource.node();
+                }
+
+                false
+            });
+
+            let node_name = resource.node().to_string();
+            let entry = ZoneTreeEntry::from_network_resource(resources.remote.clone(), resource);
+
+            match node_entry {
+                Some(mut node_entry) => {
+                    node_entry.append(entry);
+                }
+                None => {
+                    let mut node_entry =
+                        remote.append(ZoneTreeEntry::Node(resources.remote.clone(), node_name));
+
+                    node_entry.set_expanded(true);
+
+                    node_entry.append(entry);
+                }
+            };
         }
     }
 
@@ -281,7 +311,7 @@ impl LoadableComponent for ZoneTreeComponent {
         Box::pin(async move {
             let client = pdm_client();
             let remote_resources = client
-                .resources_by_type(None, ResourceType::PveSdnZone)
+                .resources_by_type(None, ResourceType::PveNetwork)
                 .await?;
             link.send_message(Self::Message::LoadFinished(remote_resources));
 
-- 
2.47.3


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


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

end of thread, other threads:[~2025-11-07 14:40 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for network resource type Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 1/3] pve-api-types: update /cluster/resources endpoint Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 2/3] pve-api-types: regenerate Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox 3/3] pve-api-types: extend ClusterResourceNetworkType Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 1/3] resources: support new pve network resource type Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 2/3] resources: api: add network type match category Stefan Hanreich
2025-11-07 14:40 ` [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type Stefan Hanreich

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