public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [PATCH datacenter-manager] ui: adapt to latest LoadableComponent changes
@ 2025-12-10 10:56 Dietmar Maurer
  2025-12-10 11:29 ` [pdm-devel] applied: " Thomas Lamprecht
  0 siblings, 1 reply; 2+ messages in thread
From: Dietmar Maurer @ 2025-12-10 10:56 UTC (permalink / raw)
  To: pdm-devel

Update all LoadableComponent implementations to match the recent
trait signature changes in proxmox-yew-comp.

Details:
- LoadableComponentContext is now a normal yew Scope, so .yew_link() is
  no longer needed.
- We need to call clone manually on ctx.link() now.
- set_task_base_url is now called inside create/changed.
- Avoid sending useless SelectionChanged/Redraw messages.
---
 ui/src/configuration/subscription_panel.rs | 17 +++++++--
 ui/src/configuration/views.rs              | 25 +++++++++-----
 ui/src/pbs/mod.rs                          | 11 ++++--
 ui/src/pve/mod.rs                          | 17 +++++----
 ui/src/pve/tree.rs                         | 40 +++++++++++++---------
 ui/src/remotes/config.rs                   | 26 +++++++++-----
 ui/src/remotes/firewall/tree.rs            | 22 ++++++++----
 ui/src/remotes/updates.rs                  | 21 ++++++++----
 ui/src/sdn/evpn/evpn_panel.rs              | 15 ++++++--
 ui/src/sdn/evpn/vnet_status.rs             | 11 ++++--
 ui/src/sdn/evpn/zone_status.rs             | 13 +++++--
 ui/src/sdn/zone_tree.rs                    | 18 ++++++----
 12 files changed, 164 insertions(+), 72 deletions(-)

diff --git a/ui/src/configuration/subscription_panel.rs b/ui/src/configuration/subscription_panel.rs
index b2d5095..9338d34 100644
--- a/ui/src/configuration/subscription_panel.rs
+++ b/ui/src/configuration/subscription_panel.rs
@@ -12,7 +12,10 @@ use pwt::prelude::*;
 use pwt::widget::{error_message, Button, Column, Container, Progress, Row, Toolbar};
 
 use proxmox_yew_comp::{http_get, http_post, KVGrid, KVGridRow};
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 
 const SUBSCRIPTION_URL: &str = "/nodes/localhost/subscription";
 
@@ -31,12 +34,19 @@ pub enum Msg {
 }
 
 pub struct ProxmoxSubscriptionPanel {
+    state: LoadableComponentState<()>,
     rows: Rc<Vec<KVGridRow>>,
     data: Option<Rc<Value>>,
     loaded: bool,
     checking: bool,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(
+    ProxmoxSubscriptionPanel,
+    state,
+    LoadableComponentState<()>
+);
+
 impl LoadableComponent for ProxmoxSubscriptionPanel {
     type Message = Msg;
     type Properties = SubscriptionPanel;
@@ -44,6 +54,7 @@ impl LoadableComponent for ProxmoxSubscriptionPanel {
 
     fn create(_ctx: &LoadableComponentContext<Self>) -> Self {
         Self {
+            state: LoadableComponentState::new(),
             rows: Rc::new(rows()),
             data: None,
             loaded: false,
@@ -88,7 +99,7 @@ impl LoadableComponent for ProxmoxSubscriptionPanel {
     }
 
     fn toolbar(&self, ctx: &LoadableComponentContext<Self>) -> Option<Html> {
-        let link = ctx.link();
+        let link = ctx.link().clone();
         let toolbar = Toolbar::new()
             .class("pwt-overflow-hidden")
             .border_bottom(true)
@@ -101,7 +112,7 @@ impl LoadableComponent for ProxmoxSubscriptionPanel {
             .with_spacer()
             .with_flex_spacer()
             .with_child({
-                let loading = ctx.loading();
+                let loading = self.loading();
                 Button::refresh(loading || self.checking).on_activate(move |_| link.send_reload())
             });
 
diff --git a/ui/src/configuration/views.rs b/ui/src/configuration/views.rs
index ef14715..810bda3 100644
--- a/ui/src/configuration/views.rs
+++ b/ui/src/configuration/views.rs
@@ -3,14 +3,17 @@ use std::pin::Pin;
 use std::rc::Rc;
 
 use anyhow::{bail, Error};
-use proxmox_yew_comp::form::delete_empty_values;
-use proxmox_yew_comp::percent_encoding::percent_encode_component;
+
 use yew::virtual_dom::{Key, VComp, VNode};
 
+use proxmox_yew_comp::form::delete_empty_values;
+use proxmox_yew_comp::percent_encoding::percent_encode_component;
+use proxmox_yew_comp::{http_delete, http_get, http_post, http_put, EditWindow};
 use proxmox_yew_comp::{
-    http_delete, http_get, http_post, http_put, EditWindow, LoadableComponent,
-    LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
 };
+
 use pwt::prelude::*;
 use pwt::state::{Selection, Store};
 use pwt::widget::data_table::{DataTable, DataTableColumn, DataTableHeader};
@@ -88,7 +91,6 @@ impl From<ViewGrid> for VNode {
 }
 
 pub enum Msg {
-    SelectionChanged,
     LoadFinished(Vec<ViewConfig>),
     Remove(Key),
     Reload,
@@ -103,11 +105,14 @@ pub enum ViewState {
 
 #[doc(hidden)]
 pub struct ViewGridComp {
+    state: LoadableComponentState<ViewState>,
     store: Store<ViewConfig>,
     columns: Rc<Vec<DataTableHeader<ViewConfig>>>,
     selection: Selection,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(ViewGridComp, state, LoadableComponentState<ViewState>);
+
 impl ViewGridComp {
     fn columns() -> Rc<Vec<DataTableHeader<ViewConfig>>> {
         let columns = vec![
@@ -197,8 +202,12 @@ impl LoadableComponent for ViewGridComp {
     type ViewState = ViewState;
 
     fn create(ctx: &proxmox_yew_comp::LoadableComponentContext<Self>) -> Self {
-        let selection = Selection::new().on_select(ctx.link().callback(|_| Msg::SelectionChanged));
+        let selection = Selection::new().on_select({
+            let link = ctx.link().clone();
+            move |_| link.send_redraw()
+        });
         Self {
+            state: LoadableComponentState::new(),
             store: Store::with_extract_key(|config: &ViewConfig| config.id.as_str().into()),
             columns: Self::columns(),
             selection,
@@ -232,13 +241,11 @@ impl LoadableComponent for ViewGridComp {
                     });
                 }
             }
-            Msg::SelectionChanged => {}
             Msg::Reload => {
                 ctx.link().change_view(None);
                 ctx.link().send_reload();
                 if let Some((context, _)) = ctx
                     .link()
-                    .yew_link()
                     .context::<ViewListContext>(Callback::from(|_| {}))
                 {
                     context.update_views();
@@ -286,7 +293,7 @@ impl LoadableComponent for ViewGridComp {
     }
 
     fn main_view(&self, ctx: &proxmox_yew_comp::LoadableComponentContext<Self>) -> Html {
-        let link = ctx.link();
+        let link = ctx.link().clone();
         DataTable::new(self.columns.clone(), self.store.clone())
             .on_row_dblclick(move |_: &mut _| link.change_view(Some(ViewState::Edit)))
             .selection(self.selection.clone())
diff --git a/ui/src/pbs/mod.rs b/ui/src/pbs/mod.rs
index d14809c..6d8004b 100644
--- a/ui/src/pbs/mod.rs
+++ b/ui/src/pbs/mod.rs
@@ -8,7 +8,8 @@ use yew::virtual_dom::{VComp, VNode};
 use yew::{Html, Properties};
 
 use proxmox_yew_comp::{
-    ConsoleType, LoadableComponent, LoadableComponentContext, LoadableComponentMaster, XTermJs,
+    ConsoleType, LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState, XTermJs,
 };
 use pwt::css::{AlignItems, FlexFit};
 use pwt::prelude::*;
@@ -57,10 +58,13 @@ pub enum Msg {
 
 #[doc(hidden)]
 pub struct PbsRemoteComp {
+    state: LoadableComponentState<()>,
     datastores: Rc<Vec<DataStoreConfig>>,
     view: tree::PbsTreeNode,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(PbsRemoteComp, state, LoadableComponentState<()>);
+
 impl PbsRemoteComp {
     async fn load_datastores(remote: &str) -> Result<Vec<DataStoreConfig>, Error> {
         let datastores = pdm_client().pbs_list_datastores(remote).await?;
@@ -75,6 +79,7 @@ impl LoadableComponent for PbsRemoteComp {
 
     fn create(_ctx: &LoadableComponentContext<Self>) -> Self {
         Self {
+            state: LoadableComponentState::new(),
             datastores: Rc::new(Vec::new()),
             view: tree::PbsTreeNode::Root,
         }
@@ -144,7 +149,7 @@ impl LoadableComponent for PbsRemoteComp {
                                         let remote = ctx.props().remote.clone();
                                         move |_| {
                                             if let Some(url) =
-                                                get_deep_url(link.yew_link(), &remote, None, "")
+                                                get_deep_url(&link, &remote, None, "")
                                             {
                                                 let _ = window().open_with_url(&url.href());
                                             }
@@ -169,7 +174,7 @@ impl LoadableComponent for PbsRemoteComp {
                                 PbsTree::new(
                                     props.remote.clone(),
                                     self.datastores.clone(),
-                                    ctx.loading(),
+                                    self.loading(),
                                     ctx.link().callback(Msg::SelectedView),
                                     {
                                         let link = ctx.link().clone();
diff --git a/ui/src/pve/mod.rs b/ui/src/pve/mod.rs
index 712191a..bfa92a3 100644
--- a/ui/src/pve/mod.rs
+++ b/ui/src/pve/mod.rs
@@ -14,7 +14,10 @@ use pwt::props::{ContainerBuilder, WidgetBuilder};
 use pwt::state::NavigationContainer;
 use pwt::widget::{Button, Column, Container, Fa, Panel, Row};
 
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 
 use proxmox_client::Error;
 
@@ -141,12 +144,15 @@ pub enum Msg {
 }
 
 pub struct PveRemoteComp {
+    state: LoadableComponentState<()>,
     view: tree::PveTreeNode,
     resources: Rc<Vec<PveResource>>,
     last_error: Option<String>,
     updates: LoadResult<RemoteUpdateSummary, Error>,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(PveRemoteComp, state, LoadableComponentState<()>);
+
 impl LoadableComponent for PveRemoteComp {
     type Message = Msg;
     type Properties = PveRemote;
@@ -155,6 +161,7 @@ impl LoadableComponent for PveRemoteComp {
     fn create(ctx: &LoadableComponentContext<PveRemoteComp>) -> Self {
         ctx.link().repeated_load(5000);
         Self {
+            state: LoadableComponentState::new(),
             view: PveTreeNode::Root,
             resources: Rc::new(Vec::new()),
             last_error: None,
@@ -253,9 +260,7 @@ 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, None, "")
-                                    {
+                                    if let Some(url) = get_deep_url(&link, &remote, None, "") {
                                         let _ = window().open_with_url(&url.href());
                                     }
                                 }
@@ -275,7 +280,7 @@ impl LoadableComponent for PveRemoteComp {
                             .with_child(tree::PveTree::new(
                                 remote.to_string(),
                                 self.resources.clone(),
-                                ctx.loading(),
+                                self.loading(),
                                 link.callback(Msg::SelectedView),
                                 {
                                     let link = link.clone();
@@ -301,7 +306,7 @@ impl LoadableComponent for PveRemoteComp {
         &self,
         ctx: &LoadableComponentContext<Self>,
     ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), anyhow::Error>>>> {
-        let link = ctx.link();
+        let link = ctx.link().clone();
         let remote = ctx.props().remote.clone();
         Box::pin(async move {
             let client = crate::pdm_client();
diff --git a/ui/src/pve/tree.rs b/ui/src/pve/tree.rs
index 605ba04..7a55dd1 100644
--- a/ui/src/pve/tree.rs
+++ b/ui/src/pve/tree.rs
@@ -8,7 +8,8 @@ use yew::{
 };
 
 use proxmox_yew_comp::{
-    LoadableComponent, LoadableComponentContext, LoadableComponentLink, LoadableComponentMaster,
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster, LoadableComponentScope,
+    LoadableComponentScopeExt, LoadableComponentState,
 };
 use pwt::css::{AlignItems, ColorScheme, FlexFit, FontStyle, JustifyContent};
 use pwt::props::{ContainerBuilder, CssBorderBuilder, ExtractPrimaryKey, WidgetBuilder};
@@ -135,6 +136,7 @@ pub enum Msg {
 }
 
 pub struct PveTreeComp {
+    state: LoadableComponentState<ViewState>,
     columns: Rc<Vec<DataTableHeader<PveTreeNode>>>,
     store: TreeStore<PveTreeNode>,
     loaded: bool,
@@ -143,8 +145,10 @@ pub struct PveTreeComp {
     view_selection: Selection,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(PveTreeComp, state, LoadableComponentState<ViewState>);
+
 impl PveTreeComp {
-    fn load_tree(&mut self, ctx: &LoadableComponentContext<'_, PveTreeComp>) {
+    fn load_tree(&mut self, ctx: &LoadableComponentContext<PveTreeComp>) {
         let remote = ctx.props().remote.clone();
         let resources = ctx.props().resources.as_ref();
         let mut tree = KeyedSlabTree::new();
@@ -261,6 +265,10 @@ impl PveTreeComp {
     }
 }
 
+fn get_base_url(remote: &str) -> AttrValue {
+    format!("/pve/remotes/{remote}/tasks").into()
+}
+
 impl LoadableComponent for PveTreeComp {
     type Message = Msg;
     type Properties = PveTree;
@@ -278,12 +286,10 @@ impl LoadableComponent for PveTreeComp {
             link.callback(|selection: Selection| Msg::KeySelected(selection.selected_key())),
         );
 
-        link.task_base_url(format!("/pve/remotes/{}/tasks", ctx.props().remote));
         link.repeated_load(3000);
 
         let (_nav_ctx, _nav_handle) = ctx
             .link()
-            .yew_link()
             .context::<NavigationContext>(Callback::from({
                 let link = ctx.link().clone();
                 move |nav_ctx: NavigationContext| {
@@ -296,9 +302,13 @@ impl LoadableComponent for PveTreeComp {
         let path = _nav_ctx.path();
         ctx.link().send_message(Msg::RouteChanged(path));
 
+        let mut state = LoadableComponentState::new();
+        state.set_task_base_url(get_base_url(&ctx.props().remote));
+
         Self {
+            state,
             columns: columns(
-                link,
+                link.clone(),
                 store.clone(),
                 ctx.props().remote.clone(),
                 ctx.props().loading,
@@ -379,9 +389,7 @@ impl LoadableComponent for PveTreeComp {
 
                 if let Some(node) = root.find_node_by_key(&key) {
                     let record = node.record().clone();
-                    ctx.link()
-                        .yew_link()
-                        .push_relative_route(&record.get_path());
+                    ctx.link().push_relative_route(&record.get_path());
                     ctx.props().on_select.emit(record);
                 }
             }
@@ -437,12 +445,15 @@ impl LoadableComponent for PveTreeComp {
         _old_props: &Self::Properties,
     ) -> bool {
         let props = ctx.props();
+
+        self.state.set_task_base_url(get_base_url(&props.remote));
+
         if props.resources != _old_props.resources {
             self.load_tree(ctx);
         }
 
         self.columns = columns(
-            ctx.link(),
+            ctx.link().clone(),
             self.store.clone(),
             props.remote.clone(),
             props.loading,
@@ -567,7 +578,7 @@ fn create_empty_node(node_id: String) -> PveTreeNode {
 }
 
 fn columns(
-    link: LoadableComponentLink<PveTreeComp>,
+    link: LoadableComponentScope<PveTreeComp>,
     store: TreeStore<PveTreeNode>,
     remote: String,
     loading: bool,
@@ -713,12 +724,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,
-                                    node.as_deref(),
-                                    &local_id,
-                                ) {
+                                if let Some(url) =
+                                    get_deep_url(&link, &remote, node.as_deref(), &local_id)
+                                {
                                     let _ = window().open_with_url(&url.href());
                                 }
                             }
diff --git a/ui/src/remotes/config.rs b/ui/src/remotes/config.rs
index ac3c0f1..d241159 100644
--- a/ui/src/remotes/config.rs
+++ b/ui/src/remotes/config.rs
@@ -33,6 +33,7 @@ use pwt::widget::{
 //use proxmox_yew_comp::EditWindow;
 use proxmox_yew_comp::{
     ConfirmButton, LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
 };
 
 use pdm_api_types::remotes::{NodeUrl, RemoteType};
@@ -102,16 +103,22 @@ pub enum ViewState {
 }
 
 pub enum Msg {
-    SelectionChange,
     RemoveItem,
 }
 
 pub struct PbsRemoteConfigPanel {
+    state: LoadableComponentState<ViewState>,
     store: Store<Remote>,
     selection: Selection,
     remote_list_columns: Rc<Vec<DataTableHeader<Remote>>>,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(
+    PbsRemoteConfigPanel,
+    state,
+    LoadableComponentState<ViewState>
+);
+
 impl LoadableComponent for PbsRemoteConfigPanel {
     type Message = Msg;
     type Properties = RemoteConfigPanel;
@@ -132,11 +139,15 @@ impl LoadableComponent for PbsRemoteConfigPanel {
     fn create(ctx: &LoadableComponentContext<Self>) -> Self {
         let store = Store::with_extract_key(|record: &Remote| Key::from(record.id.clone()));
 
-        let selection = Selection::new().on_select(ctx.link().callback(|_| Msg::SelectionChange));
+        let selection = Selection::new().on_select({
+            let link = ctx.link().clone();
+            move |_| link.send_redraw()
+        });
 
         let remote_list_columns = remote_list_columns();
 
         Self {
+            state: LoadableComponentState::new(),
             store,
             selection,
             remote_list_columns,
@@ -145,11 +156,10 @@ impl LoadableComponent for PbsRemoteConfigPanel {
 
     fn update(&mut self, ctx: &LoadableComponentContext<Self>, msg: Self::Message) -> bool {
         match msg {
-            Msg::SelectionChange => true,
             Msg::RemoveItem => {
                 if let Some(key) = self.selection.selected_key() {
-                    let link = ctx.link();
-                    link.clone().spawn(async move {
+                    let link = ctx.link().clone();
+                    self.spawn(async move {
                         if let Err(err) = delete_item(key).await {
                             link.show_error(tr!("Unable to delete item"), err, true);
                         }
@@ -202,8 +212,8 @@ impl LoadableComponent for PbsRemoteConfigPanel {
             )
             .with_flex_spacer()
             .with_child({
-                let loading = ctx.loading();
-                let link = ctx.link();
+                let loading = self.loading();
+                let link = ctx.link().clone();
                 Button::refresh(loading).onclick(move |_| link.send_reload())
             });
 
@@ -212,7 +222,7 @@ impl LoadableComponent for PbsRemoteConfigPanel {
 
     fn main_view(&self, ctx: &LoadableComponentContext<Self>) -> Html {
         let columns = Rc::clone(&self.remote_list_columns);
-        let link = ctx.link();
+        let link = ctx.link().clone();
         DataTable::new(columns, self.store.clone())
             .class(pwt::css::FlexFit)
             .selection(self.selection.clone())
diff --git a/ui/src/remotes/firewall/tree.rs b/ui/src/remotes/firewall/tree.rs
index 8b1c883..bdfb4a5 100644
--- a/ui/src/remotes/firewall/tree.rs
+++ b/ui/src/remotes/firewall/tree.rs
@@ -1,10 +1,11 @@
 use futures::Future;
 use gloo_utils::window;
+use proxmox_yew_comp::LoadableComponentState;
 use std::pin::Pin;
 use std::rc::Rc;
 use yew::{ContextHandle, Html};
 
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext};
+use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentScopeExt};
 use pwt::css;
 use pwt::prelude::*;
 use pwt::props::{FieldBuilder, WidgetBuilder};
@@ -147,6 +148,7 @@ pub enum Msg {
 }
 
 pub struct FirewallTreeComponent {
+    state: LoadableComponentState<()>,
     store: TreeStore<TreeEntry>,
     selection: Selection,
     tab_selection: Selection,
@@ -161,6 +163,12 @@ pub struct FirewallTreeComponent {
     tree_collapsed: bool,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(
+    FirewallTreeComponent,
+    state,
+    LoadableComponentState<()>
+);
+
 impl FirewallTreeComponent {
     fn reset_tree_for_loading(&mut self) {
         let tree = create_loading_tree();
@@ -192,12 +200,12 @@ impl FirewallTreeComponent {
         match (node, vmid, kind) {
             (None, None, _) => {
                 let id = format!("v1:0:18:4:::::::{index}");
-                let url = crate::get_deep_url_low_level(ctx.link().yew_link(), remote, None, &id)?;
+                let url = crate::get_deep_url_low_level(ctx.link(), remote, None, &id)?;
                 Some(url.href())
             }
             (Some(node), None, _) => {
                 let id = format!("node/{node}:4:{index}");
-                let url = crate::get_deep_url(ctx.link().yew_link(), remote, Some(node), &id)?;
+                let url = crate::get_deep_url(ctx.link(), remote, Some(node), &id)?;
                 Some(url.href())
             }
             (Some(node), Some(vmid), Some(kind)) => {
@@ -205,7 +213,7 @@ impl FirewallTreeComponent {
                     GuestKind::Lxc => format!("lxc/{vmid}:4::::::{index}"),
                     GuestKind::Qemu => format!("qemu/{vmid}:4:::::{index}"),
                 };
-                let url = crate::get_deep_url(ctx.link().yew_link(), remote, Some(node), &id)?;
+                let url = crate::get_deep_url(ctx.link(), remote, Some(node), &id)?;
                 Some(url.href())
             }
             _ => None,
@@ -254,7 +262,7 @@ impl FirewallTreeComponent {
     }
 
     fn render_tree_panel(&self, ctx: &LoadableComponentContext<Self>) -> Panel {
-        let columns = create_columns(self.store.clone(), ctx.loading(), &self.scope);
+        let columns = create_columns(self.store.clone(), self.loading(), &self.scope);
         let table = DataTable::new(columns, self.store.clone())
             .selection(self.selection.clone())
             .striped(false)
@@ -309,7 +317,7 @@ impl FirewallTreeComponent {
             ))
             .with_flex_spacer()
             .with_child(
-                Button::refresh(ctx.loading()).onclick(ctx.link().callback(|_| Msg::Reload)),
+                Button::refresh(self.loading()).onclick(ctx.link().callback(|_| Msg::Reload)),
             );
 
         let column = pwt::widget::Column::new()
@@ -469,12 +477,12 @@ impl LoadableComponent for FirewallTreeComponent {
 
         let (_, context_listener) = ctx
             .link()
-            .yew_link()
             .context(ctx.link().callback(|_: RemoteList| Msg::RemoteListChanged))
             .expect("No Remote list context provided");
 
         store.set_sorter(sort_entries);
         Self {
+            state: LoadableComponentState::new(),
             store,
             selection,
             tab_selection,
diff --git a/ui/src/remotes/updates.rs b/ui/src/remotes/updates.rs
index 418aad9..d9df74a 100644
--- a/ui/src/remotes/updates.rs
+++ b/ui/src/remotes/updates.rs
@@ -19,7 +19,8 @@ use proxmox_deb_version;
 
 use proxmox_yew_comp::{
     AptPackageManager, AptRepositories, ExistingProduct, LoadableComponent,
-    LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentContext, LoadableComponentMaster, LoadableComponentScopeExt,
+    LoadableComponentState,
 };
 use pwt::props::{CssBorderBuilder, CssPaddingBuilder, WidgetStyleBuilder};
 use pwt::widget::{Button, Container, Panel, Progress, Tooltip};
@@ -128,12 +129,19 @@ enum RemoteUpdateTreeMsg {
 }
 
 struct UpdateTreeComponent {
+    state: LoadableComponentState<ViewState>,
     store: TreeStore<UpdateTreeEntry>,
     selection: Selection,
     selected_entry: Option<UpdateTreeEntry>,
     refreshing: bool,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(
+    UpdateTreeComponent,
+    state,
+    LoadableComponentState<ViewState>
+);
+
 fn default_sorter(a: &UpdateTreeEntry, b: &UpdateTreeEntry) -> Ordering {
     a.name().cmp(b.name())
 }
@@ -332,6 +340,7 @@ impl LoadableComponent for UpdateTreeComponent {
         }));
 
         Self {
+            state: LoadableComponentState::new(),
             store: store.clone(),
             selection,
             selected_entry: None,
@@ -397,7 +406,7 @@ impl LoadableComponent for UpdateTreeComponent {
                 }
             }
             Self::Message::CheckSubscription => {
-                let link = ctx.link();
+                let link = ctx.link().clone();
 
                 self.refreshing = true;
                 link.clone().spawn(async move {
@@ -412,7 +421,7 @@ impl LoadableComponent for UpdateTreeComponent {
                 });
             }
             Self::Message::RefreshAll => {
-                let link = ctx.link();
+                let link = ctx.link().clone();
 
                 link.clone().spawn(async move {
                     let client = pdm_client();
@@ -535,16 +544,14 @@ impl UpdateTreeComponent {
                             move |_| match ty {
                                 RemoteType::Pve => {
                                     let id = format!("node/{node}::apt");
-                                    if let Some(url) =
-                                        get_deep_url(link.yew_link(), &remote, None, &id)
-                                    {
+                                    if let Some(url) = get_deep_url(&link, &remote, None, &id) {
                                         let _ = gloo_utils::window().open_with_url(&url.href());
                                     }
                                 }
                                 RemoteType::Pbs => {
                                     let hash = "#pbsServerAdministration:updates";
                                     if let Some(url) =
-                                        get_deep_url_low_level(link.yew_link(), &remote, None, hash)
+                                        get_deep_url_low_level(&link, &remote, None, hash)
                                     {
                                         let _ = gloo_utils::window().open_with_url(&url.href());
                                     }
diff --git a/ui/src/sdn/evpn/evpn_panel.rs b/ui/src/sdn/evpn/evpn_panel.rs
index 90e038e..244fe2e 100644
--- a/ui/src/sdn/evpn/evpn_panel.rs
+++ b/ui/src/sdn/evpn/evpn_panel.rs
@@ -7,7 +7,10 @@ use yew::virtual_dom::{VComp, VNode};
 use yew::{html, Callback, Html, Properties};
 
 use pdm_client::types::{ListController, ListControllersType, ListVnet, ListZone, ListZonesType};
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 
 use pwt::css::{AlignItems, FlexFit, JustifyContent};
 use pwt::props::{
@@ -91,6 +94,7 @@ async fn load_vnets() -> Result<Vec<ListVnet>, Error> {
 }
 
 pub struct EvpnPanelComponent {
+    state: LoadableComponentState<EvpnPanelViewState>,
     controllers: Rc<Vec<ListController>>,
     zones: Rc<Vec<ListZone>>,
     vnets: Rc<Vec<ListVnet>>,
@@ -99,6 +103,12 @@ pub struct EvpnPanelComponent {
     selected_tab: Selection,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(
+    EvpnPanelComponent,
+    state,
+    LoadableComponentState<EvpnPanelViewState>
+);
+
 impl EvpnPanelComponent {
     fn create_toolbar(&self, ctx: &LoadableComponentContext<Self>) -> Toolbar {
         let on_add_zone = ctx
@@ -129,7 +139,7 @@ impl EvpnPanelComponent {
             .class("pwt-border-bottom")
             .with_child(MenuButton::new(tr!("Add")).show_arrow(true).menu(add_menu))
             .with_flex_spacer()
-            .with_child(Button::refresh(ctx.loading()).onclick(on_refresh))
+            .with_child(Button::refresh(self.loading()).onclick(on_refresh))
     }
 }
 
@@ -145,6 +155,7 @@ impl LoadableComponent for EvpnPanelComponent {
             .on_select(move |_| link.send_message(Self::Message::DetailSelection(None)));
 
         Self {
+            state: LoadableComponentState::new(),
             initial_load: true,
             controllers: Default::default(),
             zones: Default::default(),
diff --git a/ui/src/sdn/evpn/vnet_status.rs b/ui/src/sdn/evpn/vnet_status.rs
index 4607d44..a15a7a2 100644
--- a/ui/src/sdn/evpn/vnet_status.rs
+++ b/ui/src/sdn/evpn/vnet_status.rs
@@ -5,7 +5,10 @@ use std::rc::Rc;
 
 use anyhow::{Context, Error};
 
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 use pwt::props::ExtractPrimaryKey;
 use yew::virtual_dom::{Key, VComp, VNode};
 use yew::{AttrValue, Properties};
@@ -68,6 +71,7 @@ fn default_sorter(a: &MacVrfEntry, b: &MacVrfEntry) -> Ordering {
 }
 
 struct VnetStatusComponent {
+    state: LoadableComponentState<()>,
     store: Store<MacVrfEntry>,
     columns: Rc<Vec<DataTableHeader<MacVrfEntry>>>,
     nodes: Option<Rc<Vec<AttrValue>>>,
@@ -76,6 +80,8 @@ struct VnetStatusComponent {
     vrf_loading: bool,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(VnetStatusComponent, state, LoadableComponentState<()>);
+
 impl VnetStatusComponent {
     fn columns() -> Rc<Vec<DataTableHeader<MacVrfEntry>>> {
         Rc::new(vec![
@@ -106,6 +112,7 @@ impl LoadableComponent for VnetStatusComponent {
 
     fn create(_ctx: &LoadableComponentContext<Self>) -> Self {
         Self {
+            state: LoadableComponentState::new(),
             store: Store::new(),
             columns: Self::columns(),
             selected_node: None,
@@ -218,7 +225,7 @@ impl LoadableComponent for VnetStatusComponent {
                 ),
             )
             .with_flex_spacer()
-            .with_child(Button::refresh(ctx.loading() || self.vrf_loading).onclick(
+            .with_child(Button::refresh(self.loading() || self.vrf_loading).onclick(
                 ctx.link().callback(move |_| {
                     Self::Message::NodeSelected(selected_node.as_ref().map(ToString::to_string))
                 }),
diff --git a/ui/src/sdn/evpn/zone_status.rs b/ui/src/sdn/evpn/zone_status.rs
index 7f966d2..e426bcb 100644
--- a/ui/src/sdn/evpn/zone_status.rs
+++ b/ui/src/sdn/evpn/zone_status.rs
@@ -8,7 +8,10 @@ use yew::virtual_dom::{Key, VComp, VNode};
 use yew::{html, AttrValue, Properties};
 
 use pdm_client::types::SdnZoneIpVrf;
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 use pwt::props::{ContainerBuilder, FieldBuilder, WidgetBuilder, WidgetStyleBuilder};
 use pwt::props::{EventSubscriber, ExtractPrimaryKey};
 use pwt::state::Store;
@@ -65,6 +68,7 @@ fn default_sorter(a: &IpVrfEntry, b: &IpVrfEntry) -> Ordering {
 }
 
 struct ZoneStatusComponent {
+    state: LoadableComponentState<()>,
     store: Store<IpVrfEntry>,
     columns: Rc<Vec<DataTableHeader<IpVrfEntry>>>,
     nodes: Option<Rc<Vec<AttrValue>>>,
@@ -73,6 +77,8 @@ struct ZoneStatusComponent {
     vrf_loading: bool,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(ZoneStatusComponent, state, LoadableComponentState<()>);
+
 impl ZoneStatusComponent {
     fn columns() -> Rc<Vec<DataTableHeader<IpVrfEntry>>> {
         Rc::new(vec![
@@ -114,6 +120,7 @@ impl LoadableComponent for ZoneStatusComponent {
 
     fn create(_ctx: &LoadableComponentContext<Self>) -> Self {
         Self {
+            state: LoadableComponentState::new(),
             store: Store::new(),
             columns: Self::columns(),
             selected_node: None,
@@ -168,7 +175,7 @@ impl LoadableComponent for ZoneStatusComponent {
                     let link = ctx.link().clone();
                     let props = ctx.props().clone();
 
-                    ctx.link().spawn(async move {
+                    self.spawn(async move {
                         let status_result = pdm_client()
                             .pve_sdn_zone_get_ip_vrf(&props.remote, &node_name, &props.zone)
                             .await;
@@ -226,7 +233,7 @@ impl LoadableComponent for ZoneStatusComponent {
                 ),
             )
             .with_flex_spacer()
-            .with_child(Button::refresh(ctx.loading() || self.vrf_loading).onclick(
+            .with_child(Button::refresh(self.loading() || self.vrf_loading).onclick(
                 ctx.link().callback(move |_| {
                     Self::Message::NodeSelected(selected_node.as_ref().map(ToString::to_string))
                 }),
diff --git a/ui/src/sdn/zone_tree.rs b/ui/src/sdn/zone_tree.rs
index 7652c5b..84e22f1 100644
--- a/ui/src/sdn/zone_tree.rs
+++ b/ui/src/sdn/zone_tree.rs
@@ -9,7 +9,10 @@ use yew::{html, ContextHandle, Html, Properties};
 use pdm_api_types::resource::{PveNetworkResource, RemoteResources, ResourceType, SdnStatus};
 use pdm_client::types::{ClusterResourceNetworkType, Resource};
 use pdm_search::{Search, SearchTerm};
-use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+use proxmox_yew_comp::{
+    LoadableComponent, LoadableComponentContext, LoadableComponentMaster,
+    LoadableComponentScopeExt, LoadableComponentState,
+};
 use pwt::props::EventSubscriber;
 use pwt::widget::{ActionIcon, Button, Toolbar};
 use pwt::{
@@ -114,12 +117,15 @@ pub enum ZoneTreeMsg {
 }
 
 pub struct ZoneTreeComponent {
+    state: LoadableComponentState<()>,
     store: TreeStore<ZoneTreeEntry>,
     selection: Selection,
     remote_errors: Vec<String>,
     _context_listener: ContextHandle<RemoteList>,
 }
 
+proxmox_yew_comp::impl_deref_mut_property!(ZoneTreeComponent, state, LoadableComponentState<()>);
+
 fn default_sorter(a: &ZoneTreeEntry, b: &ZoneTreeEntry) -> Ordering {
     a.name().cmp(b.name())
 }
@@ -193,7 +199,7 @@ impl ZoneTreeComponent {
                         ZoneTreeEntry::Remote(remote) => {
                             // TODO: do not hardcode this here.
                             let hash = "#v1:0:18:4:::::::53";
-                            crate::get_deep_url_low_level(link.yew_link(), remote, None, hash)
+                            crate::get_deep_url_low_level(&link, remote, None, hash)
                         }
                         ZoneTreeEntry::NetworkResource(network_resource) => {
                             if network_resource.legacy {
@@ -201,7 +207,7 @@ impl ZoneTreeComponent {
                                     "sdn/{}/{}",
                                     network_resource.node, network_resource.name
                                 );
-                                get_deep_url(link.yew_link(), &network_resource.remote, None, &id)
+                                get_deep_url(&link, &network_resource.remote, None, &id)
                             } else {
                                 let id = format!(
                                     "network/{}/{}/{}",
@@ -209,7 +215,7 @@ impl ZoneTreeComponent {
                                     network_resource.network_type,
                                     network_resource.name
                                 );
-                                get_deep_url(link.yew_link(), &network_resource.remote, None, &id)
+                                get_deep_url(&link, &network_resource.remote, None, &id)
                             }
                         }
                     };
@@ -298,11 +304,11 @@ impl LoadableComponent for ZoneTreeComponent {
 
         let (_, _context_listener) = ctx
             .link()
-            .yew_link()
             .context(ctx.link().callback(Self::Message::RemoteListChanged))
             .expect("No Remote list context provided");
 
         Self {
+            state: LoadableComponentState::new(),
             store: store.clone(),
             selection,
             remote_errors: Vec::new(),
@@ -368,7 +374,7 @@ impl LoadableComponent for ZoneTreeComponent {
                 .class("pwt-overflow-hidden")
                 .class("pwt-border-bottom")
                 .with_flex_spacer()
-                .with_child(Button::refresh(ctx.loading()).onclick(on_refresh))
+                .with_child(Button::refresh(self.loading()).onclick(on_refresh))
                 .into(),
         )
     }
-- 
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] 2+ messages in thread

* [pdm-devel] applied: [PATCH datacenter-manager] ui: adapt to latest LoadableComponent changes
  2025-12-10 10:56 [pdm-devel] [PATCH datacenter-manager] ui: adapt to latest LoadableComponent changes Dietmar Maurer
@ 2025-12-10 11:29 ` Thomas Lamprecht
  0 siblings, 0 replies; 2+ messages in thread
From: Thomas Lamprecht @ 2025-12-10 11:29 UTC (permalink / raw)
  To: pdm-devel, Dietmar Maurer

On Wed, 10 Dec 2025 11:56:12 +0100, Dietmar Maurer wrote:
> Update all LoadableComponent implementations to match the recent
> trait signature changes in proxmox-yew-comp.
> 
> Details:
> - LoadableComponentContext is now a normal yew Scope, so .yew_link() is
>   no longer needed.
> - We need to call clone manually on ctx.link() now.
> - set_task_base_url is now called inside create/changed.
> - Avoid sending useless SelectionChanged/Redraw messages.
> 
> [...]

Applied, thanks!

[1/1] ui: adapt to latest LoadableComponent changes
      commit: f642cadb4d113d32e7c1c89b047803fde5a14ff5


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


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

end of thread, other threads:[~2025-12-10 11:29 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-10 10:56 [pdm-devel] [PATCH datacenter-manager] ui: adapt to latest LoadableComponent changes Dietmar Maurer
2025-12-10 11:29 ` [pdm-devel] applied: " Thomas Lamprecht

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