* [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