From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 36BE61FF179 for ; Wed, 10 Dec 2025 11:55:39 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id F298218723; Wed, 10 Dec 2025 11:56:17 +0100 (CET) From: Dietmar Maurer To: pdm-devel@lists.proxmox.com Date: Wed, 10 Dec 2025 11:56:12 +0100 Message-ID: <20251210105612.445627-1-dietmar@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.576 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mod.rs] Subject: [pdm-devel] [PATCH datacenter-manager] ui: adapt to latest LoadableComponent changes 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" 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>, data: Option>, 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 { + state: LoadableComponentState::new(), rows: Rc::new(rows()), data: None, loaded: false, @@ -88,7 +99,7 @@ impl LoadableComponent for ProxmoxSubscriptionPanel { } fn toolbar(&self, ctx: &LoadableComponentContext) -> Option { - 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 for VNode { } pub enum Msg { - SelectionChanged, LoadFinished(Vec), Remove(Key), Reload, @@ -103,11 +105,14 @@ pub enum ViewState { #[doc(hidden)] pub struct ViewGridComp { + state: LoadableComponentState, store: Store, columns: Rc>>, selection: Selection, } +proxmox_yew_comp::impl_deref_mut_property!(ViewGridComp, state, LoadableComponentState); + impl ViewGridComp { fn columns() -> Rc>> { let columns = vec![ @@ -197,8 +202,12 @@ impl LoadableComponent for ViewGridComp { type ViewState = ViewState; fn create(ctx: &proxmox_yew_comp::LoadableComponentContext) -> 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::(Callback::from(|_| {})) { context.update_views(); @@ -286,7 +293,7 @@ impl LoadableComponent for ViewGridComp { } fn main_view(&self, ctx: &proxmox_yew_comp::LoadableComponentContext) -> 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>, view: tree::PbsTreeNode, } +proxmox_yew_comp::impl_deref_mut_property!(PbsRemoteComp, state, LoadableComponentState<()>); + impl PbsRemoteComp { async fn load_datastores(remote: &str) -> Result, Error> { let datastores = pdm_client().pbs_list_datastores(remote).await?; @@ -75,6 +79,7 @@ impl LoadableComponent for PbsRemoteComp { fn create(_ctx: &LoadableComponentContext) -> 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>, last_error: Option, updates: LoadResult, } +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) -> 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, ) -> std::pin::Pin>>> { - 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, columns: Rc>>, store: TreeStore, loaded: bool, @@ -143,8 +145,10 @@ pub struct PveTreeComp { view_selection: Selection, } +proxmox_yew_comp::impl_deref_mut_property!(PveTreeComp, state, LoadableComponentState); + impl PveTreeComp { - fn load_tree(&mut self, ctx: &LoadableComponentContext<'_, PveTreeComp>) { + fn load_tree(&mut self, ctx: &LoadableComponentContext) { 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::(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, + link: LoadableComponentScope, store: TreeStore, 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, store: Store, selection: Selection, remote_list_columns: Rc>>, } +proxmox_yew_comp::impl_deref_mut_property!( + PbsRemoteConfigPanel, + state, + LoadableComponentState +); + impl LoadableComponent for PbsRemoteConfigPanel { type Message = Msg; type Properties = RemoteConfigPanel; @@ -132,11 +139,15 @@ impl LoadableComponent for PbsRemoteConfigPanel { fn create(ctx: &LoadableComponentContext) -> 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, 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) -> 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, 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) -> 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, store: TreeStore, selection: Selection, selected_entry: Option, refreshing: bool, } +proxmox_yew_comp::impl_deref_mut_property!( + UpdateTreeComponent, + state, + LoadableComponentState +); + 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, Error> { } pub struct EvpnPanelComponent { + state: LoadableComponentState, controllers: Rc>, zones: Rc>, vnets: Rc>, @@ -99,6 +103,12 @@ pub struct EvpnPanelComponent { selected_tab: Selection, } +proxmox_yew_comp::impl_deref_mut_property!( + EvpnPanelComponent, + state, + LoadableComponentState +); + impl EvpnPanelComponent { fn create_toolbar(&self, ctx: &LoadableComponentContext) -> 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, columns: Rc>>, nodes: Option>>, @@ -76,6 +80,8 @@ struct VnetStatusComponent { vrf_loading: bool, } +proxmox_yew_comp::impl_deref_mut_property!(VnetStatusComponent, state, LoadableComponentState<()>); + impl VnetStatusComponent { fn columns() -> Rc>> { Rc::new(vec![ @@ -106,6 +112,7 @@ impl LoadableComponent for VnetStatusComponent { fn create(_ctx: &LoadableComponentContext) -> 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, columns: Rc>>, nodes: Option>>, @@ -73,6 +77,8 @@ struct ZoneStatusComponent { vrf_loading: bool, } +proxmox_yew_comp::impl_deref_mut_property!(ZoneStatusComponent, state, LoadableComponentState<()>); + impl ZoneStatusComponent { fn columns() -> Rc>> { Rc::new(vec![ @@ -114,6 +120,7 @@ impl LoadableComponent for ZoneStatusComponent { fn create(_ctx: &LoadableComponentContext) -> 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, selection: Selection, remote_errors: Vec, _context_listener: ContextHandle, } +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