From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type
Date: Fri, 7 Nov 2025 15:40:11 +0100 [thread overview]
Message-ID: <20251107144018.700695-7-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20251107144018.700695-1-s.hanreich@proxmox.com>
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
prev parent reply other threads:[~2025-11-07 14:40 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-07 14:40 [pdm-devel] [PATCH proxmox{, -datacenter-manager} 0/6] Add support for " 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 ` Stefan Hanreich [this message]
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=20251107144018.700695-7-s.hanreich@proxmox.com \
--to=s.hanreich@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox