From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 06E5F1FF16B for ; Fri, 7 Nov 2025 15:40:10 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7AB24150C7; Fri, 7 Nov 2025 15:40:52 +0100 (CET) From: Stefan Hanreich To: pdm-devel@lists.proxmox.com Date: Fri, 7 Nov 2025 15:40:11 +0100 Message-ID: <20251107144018.700695-7-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251107144018.700695-1-s.hanreich@proxmox.com> References: <20251107144018.700695-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.173 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pdm-devel] [PATCH proxmox-datacenter-manager 3/3] ui: use new network resource type X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "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 --- 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) -> 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( 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 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