[pdm-devel] [PATCH datacenter-manager 4/5] ui: generate URLs with the option 'web-url' property

Dominik Csapak d.csapak at proxmox.com
Fri Jan 10 11:21:41 CET 2025


This reworks `get_deep_url` so that we use the additional setting for
generating the URL. It still falls back to the previous URL generation
if either the values are not set, or no valid URL can be constructed.

Introduces a helper to extract the node from a resource.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 ui/src/dashboard/top_entities.rs |  6 ++-
 ui/src/lib.rs                    | 75 ++++++++++++++++++++------------
 ui/src/pve/mod.rs                |  4 +-
 ui/src/pve/tree.rs               | 17 ++++++--
 ui/src/widget/resource_tree.rs   | 21 +++++----
 5 files changed, 78 insertions(+), 45 deletions(-)

diff --git a/ui/src/dashboard/top_entities.rs b/ui/src/dashboard/top_entities.rs
index 4bd71b9..af3f853 100644
--- a/ui/src/dashboard/top_entities.rs
+++ b/ui/src/dashboard/top_entities.rs
@@ -21,7 +21,7 @@ use pwt::{
 use pdm_client::types::{Resource, TopEntity};
 
 use crate::{
-    get_deep_url, navigate_to,
+    get_deep_url, get_resource_node, navigate_to,
     renderer::{render_resource_icon, render_resource_name},
 };
 
@@ -131,6 +131,8 @@ impl Component for TopEntitiesComp {
             let rrd = &entity.rrd_data;
             let remote = &entity.remote;
 
+            let node = get_resource_node(resource).map(|n| n.to_string());
+
             let tooltip_anchor = if let Some(info) = self.tooltip_info.as_ref() {
                 if info.id == resource.global_id() {
                     tooltip = Some(create_tooltip(remote, resource, info, &props.metrics_title));
@@ -171,7 +173,7 @@ impl Component for TopEntitiesComp {
                         let remote = remote.clone();
                         let id = resource.id();
                         move |_| {
-                            if let Some(url) = get_deep_url(&link, &remote, &id) {
+                            if let Some(url) = get_deep_url(&link, &remote, node.as_deref(), &id) {
                                 let _ = web_sys::window().unwrap().open_with_url(&url.href());
                             }
                         }
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index 5a046f6..5c6d076 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -1,4 +1,5 @@
 use pdm_api_types::resource::{PveLxcResource, PveQemuResource};
+use pdm_client::types::Resource;
 use serde::{Deserialize, Serialize};
 
 mod administration;
@@ -78,38 +79,47 @@ pub(crate) fn get_remote<C: yew::Component>(
 pub(crate) fn get_deep_url<C: yew::Component>(
     link: &yew::html::Scope<C>,
     remote: &str,
+    node: Option<&str>,
     id: &str,
 ) -> Option<web_sys::Url> {
     let remote = get_remote(link, remote)?;
-    let (default_port, hash) = match remote.ty {
-        pdm_api_types::remotes::RemoteType::Pve => (
-            "8006",
-            if id.is_empty() {
-                String::new()
-            } else {
-                format!("v1::={id}")
-            },
-        ),
-        pdm_api_types::remotes::RemoteType::Pbs => (
-            "8007",
-            if id.is_empty() {
-                String::new()
-            } else {
-                format!("DataStore-{id}")
-            },
-        ),
+    let hash = match (id, remote.ty) {
+        ("", _) => String::new(),
+        (id, pdm_api_types::remotes::RemoteType::Pve) => format!("v1::={id}"),
+        (id, pdm_api_types::remotes::RemoteType::Pbs) => format!("DataStore-{id}"),
     };
-    let node = remote.nodes.first()?;
-    let url = web_sys::Url::new(&format!("https://{}/", node.hostname));
-    if let Ok(url) = url {
-        if url.port() == "" {
-            url.set_port(default_port);
-        }
-        url.set_hash(&hash);
-        Some(url)
-    } else {
-        None
+
+    let url = match remote.web_url {
+        Some(web_url) => match (web_url.per_node_template.as_deref(), node) {
+            (Some(template), Some(node)) => {
+                web_sys::Url::new(&template.replace("{{nodename}}", node)).ok()
+            }
+            _ => web_url
+                .base_url
+                .as_ref()
+                .map(|url| web_sys::Url::new(url).ok())
+                .flatten(),
+        },
+        None => None,
     }
+    .or_else(|| {
+        let node = remote.nodes.first()?;
+        let url = web_sys::Url::new(&format!("https://{}/", node.hostname));
+        url.ok().map(|url| {
+            if url.port() == "" {
+                let default_port = match remote.ty {
+                    pdm_api_types::remotes::RemoteType::Pve => "8006",
+                    pdm_api_types::remotes::RemoteType::Pbs => "8007",
+                };
+                url.set_port(default_port);
+            }
+            url
+        })
+    });
+    url.map(|url| {
+        url.set_hash(&hash);
+        url
+    })
 }
 
 pub(crate) fn navigate_to<C: yew::Component>(
@@ -135,3 +145,14 @@ pub(crate) fn navigate_to<C: yew::Component>(
         nav.push(&yew_router::AnyRoute::new(format!("/remote-{remote}/{id}")));
     }
 }
+
+pub(crate) fn get_resource_node(resource: &Resource) -> Option<&str> {
+    match resource {
+        Resource::PveStorage(storage) => Some(&storage.node),
+        Resource::PveQemu(qemu) => Some(&qemu.node),
+        Resource::PveLxc(lxc) => Some(&lxc.node),
+        Resource::PveNode(node) => Some(&node.node),
+        Resource::PbsNode(_) => None,
+        Resource::PbsDatastore(_) => None,
+    }
+}
diff --git a/ui/src/pve/mod.rs b/ui/src/pve/mod.rs
index 7925bbf..763e91d 100644
--- a/ui/src/pve/mod.rs
+++ b/ui/src/pve/mod.rs
@@ -181,7 +181,9 @@ impl LoadableComponent for PveRemoteComp {
                                 let link = ctx.link().clone();
                                 let remote = ctx.props().remote.clone();
                                 move |_| {
-                                    if let Some(url) = get_deep_url(&link.yew_link(), &remote, "") {
+                                    if let Some(url) =
+                                        get_deep_url(&link.yew_link(), &remote, None, "")
+                                    {
                                         let _ = window().open_with_url(&url.href());
                                     }
                                 }
diff --git a/ui/src/pve/tree.rs b/ui/src/pve/tree.rs
index 8b1f5ba..50a5ae4 100644
--- a/ui/src/pve/tree.rs
+++ b/ui/src/pve/tree.rs
@@ -574,7 +574,7 @@ fn columns(
         DataTableColumn::new(tr!("Actions"))
             .width("180px")
             .render(move |entry: &PveTreeNode| {
-                let (id, local_id, guest_info) = match entry {
+                let (id, local_id, guest_info, node) = match entry {
                     PveTreeNode::Lxc(r) => {
                         let guest_info = GuestInfo::new(GuestType::Lxc, r.vmid);
                         let local_id = guest_info.local_id();
@@ -582,6 +582,7 @@ fn columns(
                             r.id.as_str(),
                             local_id,
                             Some((guest_info, r.status.as_str())),
+                            Some(r.node.clone()),
                         )
                     }
                     PveTreeNode::Qemu(r) => {
@@ -591,10 +592,16 @@ fn columns(
                             r.id.as_str(),
                             local_id,
                             Some((guest_info, r.status.as_str())),
+                            Some(r.node.clone()),
                         )
                     }
-                    PveTreeNode::Root => ("root", "root".to_string(), None),
-                    PveTreeNode::Node(r) => (r.id.as_str(), format!("node/{}", r.node), None),
+                    PveTreeNode::Root => ("root", "root".to_string(), None, None),
+                    PveTreeNode::Node(r) => (
+                        r.id.as_str(),
+                        format!("node/{}", r.node),
+                        None,
+                        Some(r.node.clone()),
+                    ),
                 };
 
                 Row::new()
@@ -642,7 +649,9 @@ fn columns(
                         let remote = remote.clone();
                         move |()| {
                             // there must be a remote with a connections config if were already here
-                            if let Some(url) = get_deep_url(link.yew_link(), &remote, &local_id) {
+                            if let Some(url) =
+                                get_deep_url(link.yew_link(), &remote, node.as_deref(), &local_id)
+                            {
                                 let _ = window().open_with_url(&url.href());
                             }
                         }
diff --git a/ui/src/widget/resource_tree.rs b/ui/src/widget/resource_tree.rs
index 6c4537d..1b764f7 100644
--- a/ui/src/widget/resource_tree.rs
+++ b/ui/src/widget/resource_tree.rs
@@ -23,7 +23,7 @@ use proxmox_yew_comp::{http_get, Status};
 use pdm_api_types::resource::{RemoteResources, Resource};
 
 use crate::{
-    get_deep_url,
+    get_deep_url, get_resource_node,
     renderer::{render_resource_name, render_status_icon},
     RemoteList,
 };
@@ -321,12 +321,9 @@ fn columns(
             .render(|item: &PdmTreeEntry| {
                 match item {
                     PdmTreeEntry::Root => "",
-                    PdmTreeEntry::Resource(_, resource) => match resource {
-                        Resource::PveStorage(r) => &r.node,
-                        Resource::PveQemu(r) => &r.node,
-                        Resource::PveLxc(r) => &r.node,
-                        _ => "",
-                    },
+                    PdmTreeEntry::Resource(_, resource) => {
+                        get_resource_node(&resource).unwrap_or("")
+                    }
                     PdmTreeEntry::Remote(_, _) => "",
                 }
                 .into()
@@ -338,13 +335,15 @@ fn columns(
             .render({
                 let link = link.clone();
                 move |item: &PdmTreeEntry| {
-                    let (remote, id) = match item {
+                    let (remote, id, node) = match item {
                         PdmTreeEntry::Root => return html! {},
-                        PdmTreeEntry::Resource(remote_id, resource) => (remote_id, resource.id()),
-                        PdmTreeEntry::Remote(remote_id, _) => (remote_id, String::new()),
+                        PdmTreeEntry::Resource(remote_id, resource) => {
+                            (remote_id, resource.id(), get_resource_node(resource))
+                        }
+                        PdmTreeEntry::Remote(remote_id, _) => (remote_id, String::new(), None),
                     };
 
-                    match get_deep_url(&link, remote, &id) {
+                    match get_deep_url(&link, remote, node, &id) {
                         Some(url) => ActionIcon::new("fa fa-external-link")
                             .on_activate(move |()| {
                                 let _ = window().unwrap().open_with_url(&url.href());
-- 
2.39.5





More information about the pdm-devel mailing list