all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH datacenter-manager v3 20/21] ui: dashboard: use SharedState for create_*_panel
Date: Fri, 31 Oct 2025 13:44:03 +0100	[thread overview]
Message-ID: <20251031124822.2739685-21-d.csapak@proxmox.com> (raw)
In-Reply-To: <20251031124822.2739685-1-d.csapak@proxmox.com>

this avoids some unnecessary clones of the data.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 ui/src/dashboard/guest_panel.rs          | 29 ++++++++++++-----
 ui/src/dashboard/node_status_panel.rs    | 17 ++++++++--
 ui/src/dashboard/pbs_datastores_panel.rs | 13 ++++++--
 ui/src/dashboard/remote_panel.rs         | 18 ++++++++---
 ui/src/dashboard/sdn_zone_panel.rs       | 14 ++++++---
 ui/src/dashboard/tasks.rs                | 13 +++++---
 ui/src/dashboard/top_entities.rs         | 24 +++++++++-----
 ui/src/dashboard/view.rs                 | 40 +++++-------------------
 8 files changed, 102 insertions(+), 66 deletions(-)

diff --git a/ui/src/dashboard/guest_panel.rs b/ui/src/dashboard/guest_panel.rs
index be7a7a2e..03b86837 100644
--- a/ui/src/dashboard/guest_panel.rs
+++ b/ui/src/dashboard/guest_panel.rs
@@ -1,20 +1,24 @@
 use std::rc::Rc;
 
+use anyhow::Error;
+use yew::{
+    virtual_dom::{VComp, VNode},
+    Properties,
+};
+
 use pdm_api_types::resource::{GuestStatusCount, ResourceType, ResourcesStatus};
 use pdm_search::{Search, SearchTerm};
 use proxmox_yew_comp::GuestState;
 use pwt::{
     css::{self, TextAlign},
     prelude::*,
-    widget::{Container, Fa, List, ListTile, Panel},
-};
-use yew::{
-    virtual_dom::{VComp, VNode},
-    Properties,
+    state::SharedState,
+    widget::{error_message, Container, Fa, List, ListTile, Panel},
 };
 
 use crate::{
     dashboard::create_title_with_icon, pve::GuestType, search_provider::get_search_provider,
+    LoadResult,
 };
 
 use super::loading_column;
@@ -211,14 +215,23 @@ fn create_guest_search_term(
 
 /// Creates a new guest panel. Setting `guest_type` to `None` means we
 /// create one for all guests, regardless of type.
-pub fn create_guest_panel(guest_type: Option<GuestType>, status: Option<ResourcesStatus>) -> Panel {
+pub fn create_guest_panel(
+    guest_type: Option<GuestType>,
+    status: SharedState<LoadResult<ResourcesStatus, Error>>,
+) -> Panel {
     let (icon, title) = match guest_type {
         Some(GuestType::Qemu) => ("desktop", tr!("Virtual Machines")),
         Some(GuestType::Lxc) => ("cubes", tr!("Linux Container")),
         None => ("desktop", tr!("Guests")),
     };
+    let status = status.read();
     Panel::new()
         .title(create_title_with_icon(icon, title))
-        .border(true)
-        .with_child(GuestPanel::new(guest_type, status))
+        .with_child(GuestPanel::new(guest_type, status.data.clone()))
+        .with_optional_child(
+            status
+                .error
+                .as_ref()
+                .map(|err| error_message(&err.to_string())),
+        )
 }
diff --git a/ui/src/dashboard/node_status_panel.rs b/ui/src/dashboard/node_status_panel.rs
index 0873635d..60648ef0 100644
--- a/ui/src/dashboard/node_status_panel.rs
+++ b/ui/src/dashboard/node_status_panel.rs
@@ -1,18 +1,21 @@
 use std::rc::Rc;
 
+use anyhow::Error;
 use yew::virtual_dom::{VComp, VNode};
 
 use pdm_search::{Search, SearchTerm};
 use proxmox_yew_comp::Status;
 use pwt::css::{AlignItems, FlexFit, JustifyContent};
 use pwt::prelude::*;
-use pwt::widget::{Column, Fa, Panel};
+use pwt::state::SharedState;
+use pwt::widget::{error_message, Column, Fa, Panel};
 
 use pdm_api_types::resource::NodeStatusCount;
 use pdm_api_types::{remotes::RemoteType, resource::ResourcesStatus};
 
 use crate::dashboard::create_title_with_icon;
 use crate::search_provider::get_search_provider;
+use crate::LoadResult;
 
 use super::loading_column;
 
@@ -150,7 +153,7 @@ fn map_status(
 /// Passing `None` to `remote_type` means creating a panel for all nodes, regardless of remote type.
 pub fn create_node_panel(
     remote_type: Option<RemoteType>,
-    status: Option<ResourcesStatus>,
+    status: SharedState<LoadResult<ResourcesStatus, Error>>,
 ) -> Panel {
     let (icon, title) = match remote_type {
         Some(RemoteType::Pve) => ("building", tr!("Virtual Environment Nodes")),
@@ -158,7 +161,9 @@ pub fn create_node_panel(
         None => ("building", tr!("Nodes")),
     };
 
-    let (nodes_status, failed_remotes) = match status {
+    let status = status.read();
+
+    let (nodes_status, failed_remotes) = match &status.data {
         Some(status) => {
             let nodes_status = match remote_type {
                 Some(RemoteType::Pve) => Some(status.pve_nodes.clone()),
@@ -190,4 +195,10 @@ pub fn create_node_panel(
             nodes_status,
             failed_remotes,
         ))
+        .with_optional_child(
+            status
+                .error
+                .as_ref()
+                .map(|err| error_message(&err.to_string())),
+        )
 }
diff --git a/ui/src/dashboard/pbs_datastores_panel.rs b/ui/src/dashboard/pbs_datastores_panel.rs
index 6542ac00..b28647fa 100644
--- a/ui/src/dashboard/pbs_datastores_panel.rs
+++ b/ui/src/dashboard/pbs_datastores_panel.rs
@@ -1,5 +1,6 @@
 use std::rc::Rc;
 
+use anyhow::Error;
 use yew::virtual_dom::{VComp, VNode};
 
 use pdm_api_types::resource::{PbsDatastoreStatusCount, ResourceType, ResourcesStatus};
@@ -7,10 +8,12 @@ use pdm_search::{Search, SearchTerm};
 use proxmox_yew_comp::Status;
 use pwt::css::{self, TextAlign};
 use pwt::prelude::*;
+use pwt::state::SharedState;
 use pwt::widget::{Container, Fa, List, ListTile, Panel};
 
 use crate::dashboard::create_title_with_icon;
 use crate::search_provider::get_search_provider;
+use crate::LoadResult;
 
 use super::loading_column;
 
@@ -155,8 +158,14 @@ fn create_pbs_datastores_status_search_term(search_term: Option<(&str, &str)>) -
     Search::with_terms(terms)
 }
 
-pub fn create_pbs_datastores_panel(status: Option<ResourcesStatus>) -> Panel {
-    let pbs_datastores = status.map(|status| status.pbs_datastores.clone());
+pub fn create_pbs_datastores_panel(
+    status: SharedState<LoadResult<ResourcesStatus, Error>>,
+) -> Panel {
+    let pbs_datastores = status
+        .read()
+        .data
+        .as_ref()
+        .map(|status| status.pbs_datastores.clone());
 
     Panel::new()
         .title(create_title_with_icon(
diff --git a/ui/src/dashboard/remote_panel.rs b/ui/src/dashboard/remote_panel.rs
index 747f9b8d..a5682d7a 100644
--- a/ui/src/dashboard/remote_panel.rs
+++ b/ui/src/dashboard/remote_panel.rs
@@ -1,5 +1,6 @@
 use std::rc::Rc;
 
+use anyhow::Error;
 use yew::html::IntoEventCallback;
 use yew::virtual_dom::{VComp, VNode};
 
@@ -8,11 +9,13 @@ use proxmox_yew_comp::Status;
 use pwt::css;
 use pwt::prelude::*;
 use pwt::props::{ContainerBuilder, WidgetBuilder};
+use pwt::state::SharedState;
 use pwt::widget::menu::{Menu, MenuButton, MenuEvent, MenuItem};
-use pwt::widget::{Column, Container, Fa, Panel};
+use pwt::widget::{error_message, Column, Container, Fa, Panel};
 
 use pdm_api_types::resource::ResourcesStatus;
 
+use crate::LoadResult;
 use crate::{dashboard::create_title_with_icon, search_provider::get_search_provider};
 
 #[derive(Properties, PartialEq)]
@@ -116,14 +119,14 @@ fn create_search_term(failure: bool) -> Search {
 }
 
 pub fn create_remote_panel(
-    status: Option<ResourcesStatus>,
+    status: SharedState<LoadResult<ResourcesStatus, Error>>,
     on_pve_wizard: Option<impl IntoEventCallback<MenuEvent>>,
     on_pbs_wizard: Option<impl IntoEventCallback<MenuEvent>>,
 ) -> Panel {
+    let status = status.read();
     let mut panel = Panel::new()
         .title(create_title_with_icon("server", tr!("Remotes")))
-        .border(true)
-        .with_child(RemotePanel::new(status));
+        .with_child(RemotePanel::new(status.data.clone()));
 
     if on_pve_wizard.is_some() || on_pbs_wizard.is_some() {
         let mut menu = Menu::new();
@@ -143,5 +146,10 @@ pub fn create_remote_panel(
         }
         panel.add_tool(MenuButton::new(tr!("Add")).show_arrow(true).menu(menu));
     }
-    panel
+    panel.with_optional_child(
+        status
+            .error
+            .as_ref()
+            .map(|err| error_message(&err.to_string())),
+    )
 }
diff --git a/ui/src/dashboard/sdn_zone_panel.rs b/ui/src/dashboard/sdn_zone_panel.rs
index 611aadd1..8112550c 100644
--- a/ui/src/dashboard/sdn_zone_panel.rs
+++ b/ui/src/dashboard/sdn_zone_panel.rs
@@ -1,10 +1,13 @@
 use std::rc::Rc;
 
+use anyhow::Error;
+
 use pdm_api_types::resource::{ResourceType, ResourcesStatus, SdnStatus, SdnZoneCount};
 use pdm_search::{Search, SearchTerm};
 use pwt::{
     css::{self, FontColor, TextAlign},
     prelude::*,
+    state::SharedState,
     widget::{Container, Fa, List, ListTile, Panel},
 };
 use yew::{
@@ -12,7 +15,7 @@ use yew::{
     Properties,
 };
 
-use crate::{dashboard::create_title_with_icon, search_provider::get_search_provider};
+use crate::{dashboard::create_title_with_icon, search_provider::get_search_provider, LoadResult};
 
 use super::loading_column;
 
@@ -156,11 +159,14 @@ fn create_sdn_zone_search_term(status: Option<SdnStatus>) -> Search {
     Search::with_terms(terms)
 }
 
-pub fn create_sdn_panel(status: Option<ResourcesStatus>) -> Panel {
-    let sdn_zones_status = status.map(|status| status.sdn_zones);
+pub fn create_sdn_panel(status: SharedState<LoadResult<ResourcesStatus, Error>>) -> Panel {
+    let sdn_zones_status = status
+        .read()
+        .data
+        .as_ref()
+        .map(|status| status.sdn_zones.clone());
 
     Panel::new()
         .title(create_title_with_icon("sdn", tr!("SDN Zones")))
-        .border(true)
         .with_child(SdnZonePanel::new(sdn_zones_status))
 }
diff --git a/ui/src/dashboard/tasks.rs b/ui/src/dashboard/tasks.rs
index 9989d4a9..3903865c 100644
--- a/ui/src/dashboard/tasks.rs
+++ b/ui/src/dashboard/tasks.rs
@@ -4,6 +4,7 @@ use std::rc::Rc;
 
 use anyhow::Error;
 use js_sys::Date;
+use pwt::state::SharedState;
 use yew::html::Scope;
 use yew::virtual_dom::Key;
 
@@ -24,6 +25,7 @@ use crate::dashboard::create_title_with_icon;
 use crate::dashboard::loading_column;
 use crate::dashboard::refresh_config_edit::DEFAULT_TASK_SUMMARY_HOURS;
 use crate::tasks::TaskWorkerType;
+use crate::LoadResult;
 
 use super::filtered_tasks::FilteredTasks;
 
@@ -307,8 +309,7 @@ impl Component for ProxmoxTaskSummary {
 }
 
 pub fn create_task_summary_panel(
-    statistics: Option<TaskStatistics>,
-    error: Option<&Error>,
+    statistics: SharedState<LoadResult<TaskStatistics, Error>>,
     remotes: Option<u32>,
     hours: u32,
     since: i64,
@@ -317,15 +318,17 @@ pub fn create_task_summary_panel(
         Some(_count) => tr!("Task Summary Sorted by Failed Tasks (Last {0}h)", hours),
         None => tr!("Task Summary by Category (Last {0}h)", hours),
     };
-    let loading = error.is_none() && statistics.is_none();
+    let loading = !statistics.read().has_data();
+    let guard = statistics.read();
+    let data = guard.data.clone();
+    let error = guard.error.as_ref();
     Panel::new()
-        .border(true)
         .title(create_title_with_icon("list", title))
         .with_child(
             Container::new()
                 .class(css::FlexFit)
                 .padding(2)
-                .with_optional_child(statistics.map(|data| TaskSummary::new(data, since, remotes)))
+                .with_optional_child(data.map(|data| TaskSummary::new(data, since, remotes)))
                 .with_optional_child((loading).then_some(loading_column()))
                 .with_optional_child(error.map(|err| error_message(&err.to_string()))),
         )
diff --git a/ui/src/dashboard/top_entities.rs b/ui/src/dashboard/top_entities.rs
index dfe38692..e94c1b8c 100644
--- a/ui/src/dashboard/top_entities.rs
+++ b/ui/src/dashboard/top_entities.rs
@@ -1,5 +1,6 @@
 use std::rc::Rc;
 
+use pwt::state::SharedState;
 use web_sys::HtmlElement;
 use yew::virtual_dom::{VComp, VNode};
 
@@ -18,6 +19,7 @@ use pwt::{
 
 use pdm_client::types::{Resource, TopEntity};
 
+use crate::LoadResult;
 use crate::{
     dashboard::{create_title_with_icon, loading_column, types::LeaderboardType},
     get_deep_url, get_resource_node, navigate_to,
@@ -327,37 +329,45 @@ fn graph_from_data(data: &Vec<Option<f64>>, threshold: f64) -> Container {
 }
 
 pub fn create_top_entities_panel(
-    entities: Option<Vec<TopEntity>>,
-    error: Option<&proxmox_client::Error>,
+    top_entities: SharedState<
+        LoadResult<pdm_api_types::resource::TopEntities, proxmox_client::Error>,
+    >,
     leaderboard_type: LeaderboardType,
 ) -> Panel {
-    let (icon, title, metrics_title, threshold) = match leaderboard_type {
+    let top_entities = top_entities.read();
+    let (entities, icon, title, metrics_title, threshold) = match leaderboard_type {
         LeaderboardType::GuestCpu => (
+            top_entities.data.as_ref().map(|e| e.guest_cpu.clone()),
             "desktop",
             tr!("Guests With the Highest CPU Usage"),
             tr!("CPU usage"),
             0.85,
         ),
         LeaderboardType::NodeCpu => (
+            top_entities.data.as_ref().map(|e| e.node_cpu.clone()),
             "building",
             tr!("Nodes With the Highest CPU Usage"),
             tr!("CPU usage"),
             0.85,
         ),
         LeaderboardType::NodeMemory => (
+            top_entities.data.as_ref().map(|e| e.node_memory.clone()),
             "building",
             tr!("Nodes With the Highest Memory Usage"),
             tr!("Memory usage"),
             0.95,
         ),
     };
-    let loading = entities.is_none() && error.is_none();
     Panel::new()
-        .border(true)
         .title(create_title_with_icon(icon, title))
         .with_optional_child(
             entities.map(|entities| TopEntities::new(entities, metrics_title, threshold)),
         )
-        .with_optional_child(loading.then_some(loading_column()))
-        .with_optional_child(error.map(|err| error_message(&err.to_string())))
+        .with_optional_child((!top_entities.has_data()).then_some(loading_column()))
+        .with_optional_child(
+            top_entities
+                .error
+                .as_ref()
+                .map(|err| error_message(&err.to_string())),
+        )
 }
diff --git a/ui/src/dashboard/view.rs b/ui/src/dashboard/view.rs
index bcb277ed..fb46064c 100644
--- a/ui/src/dashboard/view.rs
+++ b/ui/src/dashboard/view.rs
@@ -103,37 +103,19 @@ fn render_widget(
     statistics: SharedState<LoadResult<TaskStatistics, Error>>,
     refresh_config: RefreshConfig,
 ) -> Html {
-    let status = status.read();
-    let top_entities = top_entities.read();
-    let statistics = statistics.read();
-
     let mut widget = match &item.r#type {
-        WidgetType::Nodes { remote_type } => create_node_panel(*remote_type, status.data.clone()),
-        WidgetType::Guests { guest_type } => create_guest_panel(*guest_type, status.data.clone()),
+        WidgetType::Nodes { remote_type } => create_node_panel(*remote_type, status),
+        WidgetType::Guests { guest_type } => create_guest_panel(*guest_type, status),
         WidgetType::Remotes { show_wizard } => create_remote_panel(
-            status.data.clone(),
-            show_wizard.then_some(link.callback(|_| Msg::CreateWizard(Some(RemoteType::Pve)))),
+            status,
             show_wizard.then_some(link.callback(|_| Msg::CreateWizard(Some(RemoteType::Pve)))),
+            show_wizard.then_some(link.callback(|_| Msg::CreateWizard(Some(RemoteType::Pbs)))),
         ),
-        WidgetType::PbsDatastores => create_pbs_datastores_panel(status.data.clone()),
+        WidgetType::PbsDatastores => create_pbs_datastores_panel(status),
         WidgetType::Subscription => create_subscription_panel(subscriptions),
-        WidgetType::Sdn => create_sdn_panel(status.data.clone()),
+        WidgetType::Sdn => create_sdn_panel(status),
         WidgetType::Leaderboard { leaderboard_type } => {
-            let entities = match leaderboard_type {
-                LeaderboardType::GuestCpu => top_entities
-                    .data
-                    .as_ref()
-                    .map(|entities| entities.guest_cpu.clone()),
-                LeaderboardType::NodeCpu => top_entities
-                    .data
-                    .as_ref()
-                    .map(|entities| entities.node_cpu.clone()),
-                LeaderboardType::NodeMemory => top_entities
-                    .data
-                    .as_ref()
-                    .map(|entities| entities.node_memory.clone()),
-            };
-            create_top_entities_panel(entities, top_entities.error.as_ref(), *leaderboard_type)
+            create_top_entities_panel(top_entities, *leaderboard_type)
         }
         WidgetType::TaskSummary { grouping } => {
             let remotes = match grouping {
@@ -141,13 +123,7 @@ fn render_widget(
                 TaskSummaryGrouping::Remote => Some(5),
             };
             let (hours, since) = get_task_options(refresh_config.task_last_hours);
-            create_task_summary_panel(
-                statistics.data.clone(),
-                statistics.error.as_ref(),
-                remotes,
-                hours,
-                since,
-            )
+            create_task_summary_panel(statistics, remotes, hours, since)
         }
     };
 
-- 
2.47.3



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


  parent reply	other threads:[~2025-10-31 12:48 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-31 12:43 [pdm-devel] [PATCH datacenter-manager v3 00/21] prepare ui for customizable views Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 01/21] ui: dashboard: refactor guest panel creation to its own module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 02/21] ui: dashboard: refactor creating the node panel into " Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 03/21] ui: dashboard: node panel: make remote type optional Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 04/21] ui: dashboard: refactor remote panel creation into its own module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 05/21] ui: dashboard: remote panel: make wizard menu optional Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 06/21] ui: dashboard: refactor sdn panel creation into its own module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 07/21] ui: dashboard: refactor task summary panel creation to " Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 08/21] ui: dashboard: task summary: disable virtual scrolling Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 09/21] ui: dashboard: refactor subscription panel creation to its own module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 10/21] ui: dashboard: refactor top entities " Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 11/21] ui: dashboard: refactor DashboardConfig editing/constants to their module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 12/21] ui: dashboard: factor out task parameter calculation Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 13/21] ui: dashboard: pbs datastores panel: refactor creation into own module Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 14/21] ui: dashboard: remove unused remote list Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 15/21] ui: dashboard: status row: make loading less jarring Dominik Csapak
2025-10-31 12:43 ` [pdm-devel] [PATCH datacenter-manager v3 16/21] ui: introduce `LoadResult` helper type Dominik Csapak
2025-10-31 12:44 ` [pdm-devel] [PATCH datacenter-manager v3 17/21] ui: dashboard: implement 'View' Dominik Csapak
2025-10-31 12:44 ` [pdm-devel] [PATCH datacenter-manager v3 18/21] ui: dashboard: use 'View' instead of the Dashboard Dominik Csapak
2025-10-31 12:44 ` [pdm-devel] [PATCH datacenter-manager v3 19/21] ui: dashboard: subscription info: move subscription loading to view Dominik Csapak
2025-10-31 12:44 ` Dominik Csapak [this message]
2025-10-31 12:44 ` [pdm-devel] [PATCH datacenter-manager v3 21/21] ui: dashboard: enable editing view Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251031124822.2739685-21-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal