From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id A0FD21FF17E for ; Thu, 27 Nov 2025 11:45:17 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E77851AF; Thu, 27 Nov 2025 11:45:36 +0100 (CET) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Date: Thu, 27 Nov 2025 11:44:43 +0100 Message-ID: <20251127104447.162951-15-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251127104447.162951-1-l.wagner@proxmox.com> References: <20251127104447.162951-1-l.wagner@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1764240257943 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.032 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 SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH datacenter-manager 09/13] ui: remote updates: show repository status column 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" Add a status column for possible repository configuration issues. Due to space constraints this commit removes the text representation from the pre-existing update column ('Up-to-date', '42 updates pending') and replaces it with an icon only (up-to-date, checkmark) or an icon with a number (number of pending updates). The status text is moved to a tooltip, but IMO the icon only view is clear enough to understand without the tool tip. The error message that was previously shown there will be moved somewhere else in a future commit. Signed-off-by: Lukas Wagner --- ui/src/remotes/updates.rs | 176 ++++++++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 63 deletions(-) diff --git a/ui/src/remotes/updates.rs b/ui/src/remotes/updates.rs index e13e3831..1346b7f7 100644 --- a/ui/src/remotes/updates.rs +++ b/ui/src/remotes/updates.rs @@ -9,7 +9,7 @@ use yew::virtual_dom::{Key, VComp, VNode}; use yew::{html, Html, Properties}; use pdm_api_types::remote_updates::{ - NodeUpdateStatus, NodeUpdateSummary, RemoteUpdateStatus, UpdateSummary, + NodeUpdateStatus, NodeUpdateSummary, ProductRepositoryStatus, RemoteUpdateStatus, UpdateSummary, }; use pdm_api_types::remotes::RemoteType; use pwt::css::{AlignItems, FlexFit, TextAlign}; @@ -19,7 +19,7 @@ use proxmox_yew_comp::{ AptPackageManager, LoadableComponent, LoadableComponentContext, LoadableComponentMaster, }; use pwt::props::{CssBorderBuilder, CssPaddingBuilder, WidgetStyleBuilder}; -use pwt::widget::{Button, Container, Panel}; +use pwt::widget::{Button, Container, Panel, Tooltip}; use pwt::{ css, css::FontColor, @@ -59,6 +59,7 @@ struct RemoteEntry { number_of_failed_nodes: u32, number_of_nodes: u32, number_of_updatable_nodes: u32, + repo_status: ProductRepositoryStatus, poll_status: RemoteUpdateStatus, } @@ -153,17 +154,19 @@ impl UpdateTreeComponent { }, )) .into(), - DataTableColumn::new(tr!("Status")) - .flex(6) + DataTableColumn::new(tr!("Update Status")) + .flex(1) .render_cell(DataTableCellRenderer::new( - move |args: &mut DataTableCellRenderArgs| match args.record() { - UpdateTreeEntry::Root => { - html!() - } - UpdateTreeEntry::Remote(remote_info) => { - render_remote_summary(remote_info, args.is_expanded()).into() - } - UpdateTreeEntry::Node(info) => render_node_info(info).into(), + move |args: &mut DataTableCellRenderArgs| { + render_update_status_column(args.record(), args.is_expanded()) + }, + )) + .into(), + DataTableColumn::new(tr!("Repository Status")) + .flex(1) + .render_cell(DataTableCellRenderer::new( + move |args: &mut DataTableCellRenderArgs| { + render_repo_status_column(args.record(), args.is_expanded()) }, )) .into(), @@ -200,6 +203,7 @@ fn build_store_from_response(update_summary: UpdateSummary) -> SlabTree SlabTree SlabTree repo_status { + repo_status = node_summary.repository_status; + } let entry = NodeEntry { remote: remote_name.clone(), node: node_name.clone(), @@ -255,6 +263,7 @@ fn build_store_from_response(update_summary: UpdateSummary) -> SlabTree Row { +fn render_update_status_column(tree_entry: &UpdateTreeEntry, expanded: bool) -> Html { + match tree_entry { + UpdateTreeEntry::Root => { + html!() + } + UpdateTreeEntry::Remote(remote_info) => { + render_remote_update_status(remote_info, expanded).into() + } + UpdateTreeEntry::Node(node_info) => render_node_update_status(node_info).into(), + } +} + +fn render_remote_update_status(entry: &RemoteEntry, expanded: bool) -> Row { let mut row = Row::new().class(css::AlignItems::Baseline).gap(2); match entry.poll_status { RemoteUpdateStatus::Success => { @@ -479,71 +500,98 @@ fn render_remote_summary(entry: &RemoteEntry, expanded: bool) -> Row { - entry.number_of_updatable_nodes - entry.number_of_failed_nodes; - let text = if entry.number_of_nodes == up_to_date_nodes { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::UpToDate)); - tr!("All nodes up-to-date") + if entry.number_of_nodes == up_to_date_nodes { + row = row.with_child(render_status_icon(StatusIcon::Ok)); } else if entry.number_of_updatable_nodes > 0 { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::Updatable)); + row = row.with_child(render_status_icon(StatusIcon::Updatable)); if entry.number_of_failed_nodes > 0 { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::Error)); - // NOTE: This 'summary' line is only shown for remotes with multiple nodes, - // so we don't really have to consider the singular form of 'x out of y - // nodes' - tr!("Some nodes have pending updates, some nodes unavailable") - } else { - tr!("Some nodes have pending updates") + row = row.with_child(render_status_icon(StatusIcon::Error)); } } else if entry.number_of_failed_nodes > 0 { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::Error)); - tr!("Some nodes unavailable") - } else { - String::new() - }; - - row = row.with_child(text); + row = row.with_child(render_status_icon(StatusIcon::Error)); + } } } RemoteUpdateStatus::Error => { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::Error)); - row = row.with_child(tr!("Could not connect to remote")); + row = row.with_child(render_status_icon(StatusIcon::Error)); } RemoteUpdateStatus::Unknown => { - row = row.with_child(render_remote_summary_icon(RemoteSummaryIcon::Unknown)); - row = row.with_child(tr!("Update status unknown")); + row = row.with_child(render_status_icon(StatusIcon::Unknown)); } } row } -fn render_node_info(entry: &NodeEntry) -> Row { - let (icon, text) = if entry.summary.status == NodeUpdateStatus::Error { - let icon = render_remote_summary_icon(RemoteSummaryIcon::Error); - let text = if let Some(status) = &entry.summary.status_message { - tr!("Failed to retrieve update status: {}", status) +fn render_node_update_status(entry: &NodeEntry) -> Html { + let mut row = Row::new().class(css::AlignItems::Baseline).gap(2); + + let tooltip = if entry.summary.status == NodeUpdateStatus::Error { + row = row.with_child(render_status_icon(StatusIcon::Error)); + if let Some(status) = &entry.summary.status_message { + tr!("Failed to retrieve update status: {0}", status) } else { tr!("Unknown error") - }; - - (icon, text) + } } else if entry.summary.number_of_updates > 0 { - ( - render_remote_summary_icon(RemoteSummaryIcon::Updatable), - tr!("One update pending" | "{n} updates pending" % entry.summary.number_of_updates), - ) + row = row.with_child(render_status_icon(StatusIcon::Updatable)); + row = row.with_child(format!("{}", entry.summary.number_of_updates)); + tr!("One update pending" | "{n} updates pending" % entry.summary.number_of_updates) } else { - ( - render_remote_summary_icon(RemoteSummaryIcon::UpToDate), - tr!("Up-to-date"), - ) + row = row.with_child(render_status_icon(StatusIcon::Ok)); + tr!("Up-to-date") }; - Row::new() - .class(css::AlignItems::Baseline) - .gap(2) - .with_child(icon) - .with_child(text) + Tooltip::new(row).tip(tooltip).into() +} + +fn render_repo_status_column(tree_entry: &UpdateTreeEntry, expanded: bool) -> Html { + match tree_entry { + UpdateTreeEntry::Root => { + html!() + } + UpdateTreeEntry::Remote(remote_info) => { + if !expanded { + render_repo_status(remote_info.repo_status) + } else { + html!() + } + } + UpdateTreeEntry::Node(node_info) => { + render_repo_status(node_info.summary.repository_status).into() + } + } +} + +fn render_repo_status(status: ProductRepositoryStatus) -> Html { + let mut row = Row::new().class(css::AlignItems::Baseline).gap(2); + + let tooltip = match status { + ProductRepositoryStatus::Ok => { + row = row.with_child(render_status_icon(StatusIcon::Ok)); + tr!("Production-ready enterprise repository enabled") + } + ProductRepositoryStatus::NoProductRepository => { + row = row.with_child(render_status_icon(StatusIcon::Error)); + tr!("No product repository configured") + } + ProductRepositoryStatus::MissingSubscriptionForEnterprise => { + row = row.with_child(render_status_icon(StatusIcon::Warning)); + tr!("Enterprise repository configured, but missing subscription") + } + ProductRepositoryStatus::NonProductionReady => { + row = row.with_child(render_status_icon(StatusIcon::Ok)); + row = row.with_child(render_status_icon(StatusIcon::Warning)); + tr!("Non-production-ready repositories enabled") + } + ProductRepositoryStatus::Error => { + row = row.with_child(render_status_icon(StatusIcon::Error)); + tr!("Error") + } + }; + + Tooltip::new(row).tip(tooltip).into() } fn render_version_column(tree_entry: &UpdateTreeEntry, expanded: bool) -> Html { @@ -580,19 +628,21 @@ fn get_product_version(node_entry: &NodeEntry) -> Option { .map(|p| p.version.to_string()) } -enum RemoteSummaryIcon { - UpToDate, +enum StatusIcon { + Ok, Updatable, Error, + Warning, Unknown, } -fn render_remote_summary_icon(icon: RemoteSummaryIcon) -> Fa { +fn render_status_icon(icon: StatusIcon) -> Fa { let (icon_class, icon_scheme) = match icon { - RemoteSummaryIcon::UpToDate => ("check", FontColor::Success), - RemoteSummaryIcon::Error => ("times-circle", FontColor::Error), - RemoteSummaryIcon::Updatable => ("refresh", FontColor::Primary), - RemoteSummaryIcon::Unknown => ("question-circle-o", FontColor::Primary), + StatusIcon::Ok => ("check", FontColor::Success), + StatusIcon::Error => ("times-circle", FontColor::Error), + StatusIcon::Warning => ("exclamation-triangle", FontColor::Warning), + StatusIcon::Updatable => ("refresh", FontColor::Primary), + StatusIcon::Unknown => ("question-circle-o", FontColor::Primary), }; Fa::new(icon_class).class(icon_scheme) -- 2.47.3 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel