public inbox for pdm-devel@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 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