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 184241FF16B for ; Tue, 9 Sep 2025 09:38:02 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C41519FD; Tue, 9 Sep 2025 09:38:04 +0200 (CEST) From: Dominik Csapak To: pdm-devel@lists.proxmox.com Date: Tue, 9 Sep 2025 09:37:55 +0200 Message-ID: <20250909073801.661423-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.2 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.022 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 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. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches 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. [statistics.data, mod.rs] Subject: [pdm-devel] [PATCH datacenter-manager 1/2] ui: dashboard: show finished loading data immediately 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" instead of waiting for all data to have come in, already show loaded data as soon as it's available Signed-off-by: Dominik Csapak --- ui/src/dashboard/mod.rs | 220 ++++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 99 deletions(-) diff --git a/ui/src/dashboard/mod.rs b/ui/src/dashboard/mod.rs index 0659dc0..8d68dc2 100644 --- a/ui/src/dashboard/mod.rs +++ b/ui/src/dashboard/mod.rs @@ -25,7 +25,7 @@ use pwt::{ }; use pdm_api_types::{ - resource::{GuestStatusCount, NodeStatusCount, ResourcesStatus}, + resource::{NodeStatusCount, ResourcesStatus}, TaskStatistics, }; use pdm_client::types::TopEntity; @@ -103,11 +103,12 @@ pub struct DashboardConfig { task_last_hours: Option, } -pub type LoadingResult = ( - Result, - Result, - Result, -); +pub enum LoadingResult { + Resources(Result), + TopEntities(Result), + TaskStatistics(Result), + All, +} pub enum Msg { LoadingFinished(LoadingResult), @@ -126,7 +127,7 @@ struct StatisticsOptions { } pub struct PdmDashboard { - status: ResourcesStatus, + status: Option, last_error: Option, top_entities: Option, last_top_entities_error: Option, @@ -152,46 +153,47 @@ impl PdmDashboard { .into() } - fn create_node_panel( - &self, - ctx: &yew::Context, - icon: &str, - title: String, - status: &NodeStatusCount, - ) -> Panel { + fn create_node_panel(&self, ctx: &yew::Context, icon: &str, title: String) -> Panel { let mut search_terms = vec![SearchTerm::new("node").category(Some("type"))]; - let (status_icon, text): (Fa, String) = match status { - NodeStatusCount { - online, - offline, - unknown, - } if *offline > 0 => { - search_terms.push(SearchTerm::new("offline").category(Some("status"))); - ( - Status::Error.into(), - tr!( - "{0} of {1} nodes are offline", + let (status_icon, text): (Fa, String) = match &self.status { + Some(status) => { + match status.pve_nodes { + NodeStatusCount { + online, offline, - online + offline + unknown, + unknown, + } if offline > 0 => { + search_terms.push(SearchTerm::new("offline").category(Some("status"))); + ( + Status::Error.into(), + tr!( + "{0} of {1} nodes are offline", + offline, + online + offline + unknown, + ), + ) + } + NodeStatusCount { unknown, .. } if unknown > 0 => { + search_terms.push(SearchTerm::new("unknown").category(Some("status"))); + ( + Status::Warning.into(), + tr!("{0} nodes have an unknown status", unknown), + ) + } + // FIXME, get more detailed status about the failed remotes (name, type, error)? + NodeStatusCount { online, .. } if status.failed_remotes > 0 => ( + Status::Unknown.into(), + tr!("{0} of an unknown number of nodes online", online), ), - ) - } - NodeStatusCount { unknown, .. } if *unknown > 0 => { - search_terms.push(SearchTerm::new("unknown").category(Some("status"))); - ( - Status::Warning.into(), - tr!("{0} nodes have an unknown status", unknown), - ) - } - // FIXME, get more detailed status about the failed remotes (name, type, error)? - NodeStatusCount { online, .. } if self.status.failed_remotes > 0 => ( - Status::Unknown.into(), - tr!("{0} of an unknown number of nodes online", online), - ), - NodeStatusCount { online, .. } => { - (Status::Success.into(), tr!("{0} nodes online", online)) + NodeStatusCount { online, .. } => { + (Status::Success.into(), tr!("{0} nodes online", online)) + } + } } + None => (Status::Unknown.into(), String::new()), }; + + let loading = self.status.is_none(); let search = Search::with_terms(search_terms); Panel::new() .flex(1.0) @@ -217,29 +219,34 @@ impl PdmDashboard { .class(AlignItems::Center) .class(JustifyContent::Center) .gap(2) - .with_child(if self.loading { + .with_child(if loading { html! {} } else { status_icon.large_4x().into() }) - .with_optional_child((!self.loading).then_some(text)), + .with_optional_child((!loading).then_some(text)), ) } - fn create_guest_panel(&self, guest_type: GuestType, status: &GuestStatusCount) -> Panel { - let (icon, title) = match guest_type { - GuestType::Qemu => ("desktop", tr!("Virtual Machines")), - GuestType::Lxc => ("cubes", tr!("Linux Container")), + fn create_guest_panel(&self, guest_type: GuestType) -> Panel { + let (icon, title, status) = match guest_type { + GuestType::Qemu => ( + "desktop", + tr!("Virtual Machines"), + self.status.as_ref().map(|s| s.qemu.clone()), + ), + GuestType::Lxc => ( + "cubes", + tr!("Linux Container"), + self.status.as_ref().map(|s| s.lxc.clone()), + ), }; Panel::new() .flex(1.0) .width(300) .title(self.create_title_with_icon(icon, title)) .border(true) - .with_child(GuestPanel::new( - guest_type, - (!self.loading).then_some(status.clone()), - )) + .with_child(GuestPanel::new(guest_type, status)) } fn create_task_summary_panel( @@ -323,8 +330,21 @@ impl PdmDashboard { self.async_pool.spawn(async move { let client = crate::pdm_client(); - let top_entities_future = client.get_top_entities(); - let status_future = http_get("/resources/status", Some(json!({"max-age": max_age}))); + let top_entities_future = { + let link = link.clone(); + async move { + let res = client.get_top_entities().await; + link.send_message(Msg::LoadingFinished(LoadingResult::TopEntities(res))); + } + }; + let status_future = { + let link = link.clone(); + async move { + let res: Result = + http_get("/resources/status", Some(json!({"max-age": max_age}))).await; + link.send_message(Msg::LoadingFinished(LoadingResult::Resources(res))); + } + }; let params = Some(json!({ "since": since, @@ -332,16 +352,16 @@ impl PdmDashboard { })); // TODO replace with pdm client call - let statistics_future = http_get("/remote-tasks/statistics", params); - - let (top_entities_res, status_res, statistics_res) = - join!(top_entities_future, status_future, statistics_future); - - link.send_message(Msg::LoadingFinished(( - status_res, - top_entities_res, - statistics_res, - ))); + let statistics_future = { + let link = link.clone(); + async move { + let res: Result = + http_get("/remote-tasks/statistics", params).await; + link.send_message(Msg::LoadingFinished(LoadingResult::TaskStatistics(res))); + } + }; + join!(top_entities_future, status_future, statistics_future); + link.send_message(Msg::LoadingFinished(LoadingResult::All)); }); } @@ -367,7 +387,7 @@ impl Component for PdmDashboard { .expect("No Remote list context provided"); let mut this = Self { - status: ResourcesStatus::default(), + status: None, last_error: None, top_entities: None, last_top_entities_error: None, @@ -393,36 +413,41 @@ impl Component for PdmDashboard { fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { match msg { - Msg::LoadingFinished((resources_status, top_entities, task_statistics)) => { - match resources_status { - Ok(status) => { - self.last_error = None; - self.status = status; - } - Err(err) => self.last_error = Some(err), - } - match top_entities { - Ok(data) => { - self.last_top_entities_error = None; - self.top_entities = Some(data); - } - Err(err) => self.last_top_entities_error = Some(err), - } - match task_statistics { - Ok(statistics) => { - self.statistics.error = None; - self.statistics.data = Some(statistics); + Msg::LoadingFinished(res) => { + match res { + LoadingResult::Resources(resources_status) => match resources_status { + Ok(status) => { + self.last_error = None; + self.status = Some(status); + } + Err(err) => self.last_error = Some(err), + }, + LoadingResult::TopEntities(top_entities) => match top_entities { + Ok(data) => { + self.last_top_entities_error = None; + self.top_entities = Some(data); + } + Err(err) => self.last_top_entities_error = Some(err), + }, + + LoadingResult::TaskStatistics(task_statistics) => match task_statistics { + Ok(statistics) => { + self.statistics.error = None; + self.statistics.data = Some(statistics); + } + Err(err) => self.statistics.error = Some(err), + }, + LoadingResult::All => { + self.loading = false; + if !self.loaded_once { + self.loaded_once = true; + // immediately trigger a "normal" reload after the first load with the + // configured or default max-age to ensure users sees more current data. + ctx.link().send_message(Msg::Reload); + } + self.load_finished_time = Some(Date::now() / 1000.0); } - Err(err) => self.statistics.error = Some(err), - } - self.loading = false; - if !self.loaded_once { - self.loaded_once = true; - // immediately trigger a "normal" reload after the first load with the - // configured or default max-age to ensure users sees more current data. - ctx.link().send_message(Msg::Reload); } - self.load_finished_time = Some(Date::now() / 1000.0); true } Msg::RemoteListChanged(remote_list) => { @@ -504,18 +529,15 @@ impl Component for PdmDashboard { .icon_class("fa fa-plus-circle") .on_activate(ctx.link().callback(|_| Msg::CreateWizard(true))), ) - .with_child(RemotePanel::new( - (!self.loading).then_some(self.status.clone()), - )), + .with_child(RemotePanel::new(self.status.clone())), ) .with_child(self.create_node_panel( ctx, "building", tr!("Virtual Environment Nodes"), - &self.status.pve_nodes, )) - .with_child(self.create_guest_panel(GuestType::Qemu, &self.status.qemu)) - .with_child(self.create_guest_panel(GuestType::Lxc, &self.status.lxc)) + .with_child(self.create_guest_panel(GuestType::Qemu)) + .with_child(self.create_guest_panel(GuestType::Lxc)) // FIXME: add PBS support //.with_child(self.create_node_panel( // "building-o", -- 2.47.2 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel