[pdm-devel] [PATCH datacenter-manager 2/3] ui: dashboard/views: add subscription notice in status row

Dominik Csapak d.csapak at proxmox.com
Wed Dec 3 10:59:38 CET 2025


This adds a short subscription notice to the status row.
It will be loaded together with the normal refresh.

It currently only shows the following states:
* No remotes configured (warning)
* No valid subscriptions (error)
* Valid subscriptions (good)

It reuses the pdm backend subscription check for this.
If there is a message or an error from the api call, show that as
tooltip.

A click on the notice routes to the subscriptions panel of PDM, where
more info about the statistics is outlined.

Downside of this approach is that we might load the subscription info
twice on a fresh login, once for the login popup check, and once for the
dashboard/view if one is open after login.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 ui/Cargo.toml                  |  1 +
 ui/src/dashboard/status_row.rs | 86 ++++++++++++++++++++++++++++++++--
 2 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index 12c1ff45..d5124340 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -39,6 +39,7 @@ proxmox-client = "1"
 proxmox-human-byte = "1"
 proxmox-login = "1"
 proxmox-schema = "5"
+proxmox-subscription = { version = "1.0.1", features = ["api-types"], default-features = false }
 proxmox-rrd-api-types = "1"
 proxmox-node-status = "1"
 pbs-api-types = { version = "1.0.3", features = [ "enum-fallback" ] }
diff --git a/ui/src/dashboard/status_row.rs b/ui/src/dashboard/status_row.rs
index e1af6697..582a9b87 100644
--- a/ui/src/dashboard/status_row.rs
+++ b/ui/src/dashboard/status_row.rs
@@ -1,19 +1,27 @@
+use anyhow::Error;
 use gloo_timers::callback::Interval;
 use yew::html::IntoPropValue;
 use yew::{Component, Properties};
+use yew_router::prelude::RouterScopeExt;
+use yew_router::AnyRoute;
 
-use pwt::css;
 use pwt::prelude::*;
 use pwt::state::SharedState;
+use pwt::{css, AsyncPool};
 use pwt::{
     css::AlignItems,
     widget::{ActionIcon, Container, Row, Tooltip},
 };
 use pwt_macros::{builder, widget};
 
+use proxmox_subscription::SubscriptionStatus;
+use proxmox_yew_comp::subscription_icon;
 use proxmox_yew_comp::utils::render_epoch;
 
+use pdm_api_types::subscription::PdmSubscriptionInfo;
+
 use crate::dashboard::view::EditingMessage;
+use crate::LoadResult;
 
 #[widget(comp=PdmDashboardStatusRow)]
 #[derive(Properties, PartialEq, Clone)]
@@ -51,6 +59,7 @@ impl DashboardStatusRow {
 pub enum Msg {
     /// The bool denotes if the reload comes from the click or the timer.
     Reload(bool),
+    SubscriptionInfoLoaded(Result<PdmSubscriptionInfo, Error>),
     Edit(EditingMessage),
 }
 
@@ -59,6 +68,9 @@ pub struct PdmDashboardStatusRow {
     _interval: Interval,
     loading: bool,
     edit: bool,
+
+    async_pool: AsyncPool,
+    subscription_info: LoadResult<PdmSubscriptionInfo, Error>,
 }
 
 impl PdmDashboardStatusRow {
@@ -73,6 +85,13 @@ impl PdmDashboardStatusRow {
 
         _interval
     }
+
+    fn load_subscription(&self, ctx: &yew::Context<Self>) {
+        self.async_pool.send_future(ctx.link().clone(), async move {
+            let res = proxmox_yew_comp::http_get("/nodes/localhost/subscription", None).await;
+            Msg::SubscriptionInfoLoaded(res)
+        });
+    }
 }
 
 impl Component for PdmDashboardStatusRow {
@@ -80,11 +99,15 @@ impl Component for PdmDashboardStatusRow {
     type Properties = DashboardStatusRow;
 
     fn create(ctx: &yew::Context<Self>) -> Self {
-        Self {
+        let this = Self {
             _interval: Self::create_interval(ctx),
             loading: false,
             edit: false,
-        }
+            async_pool: AsyncPool::new(),
+            subscription_info: LoadResult::new(),
+        };
+        this.load_subscription(ctx);
+        this
     }
 
     fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
@@ -92,6 +115,7 @@ impl Component for PdmDashboardStatusRow {
         match msg {
             Msg::Reload(clicked) => {
                 props.on_reload.emit(clicked);
+                self.load_subscription(ctx);
                 self.loading = true;
                 true
             }
@@ -102,6 +126,10 @@ impl Component for PdmDashboardStatusRow {
                 }
                 true
             }
+            Msg::SubscriptionInfoLoaded(res) => {
+                self.subscription_info.update(res);
+                true
+            }
         }
     }
 
@@ -116,7 +144,8 @@ impl Component for PdmDashboardStatusRow {
 
     fn view(&self, ctx: &yew::Context<Self>) -> yew::Html {
         let props = ctx.props();
-        let is_loading = props.last_refresh.is_none() || self.loading;
+        let is_loading =
+            props.last_refresh.is_none() || self.loading || !self.subscription_info.has_data();
         let on_settings_click = props.on_settings_click.clone();
         Row::new()
             .gap(1)
@@ -141,6 +170,18 @@ impl Component for PdmDashboardStatusRow {
                 }
                 None => tr!("Now refreshing"),
             }))
+            .with_optional_child(create_subscription_notice(&self.subscription_info).map(
+                |element| {
+                    element.class("pwt-pointer").onclick({
+                        let link = ctx.link().clone();
+                        move |_| {
+                            if let Some(nav) = link.navigator() {
+                                nav.push(&AnyRoute::new("/subscription"));
+                            }
+                        }
+                    })
+                },
+            ))
             .with_flex_spacer()
             .with_optional_child(props.editing_state.clone().and_then(|_| {
                 (!self.edit).then_some({
@@ -190,3 +231,40 @@ impl Component for PdmDashboardStatusRow {
             .into()
     }
 }
+
+fn create_subscription_notice(
+    subscriptions: &LoadResult<PdmSubscriptionInfo, Error>,
+) -> Option<Tooltip> {
+    if !subscriptions.has_data() {
+        return None;
+    }
+    let mut text = tr!("No valid subscriptions");
+    let mut icon = subscription_icon(&SubscriptionStatus::NotFound.to_string());
+    let mut tooltip = None;
+
+    if let Some(subscriptions) = &subscriptions.data {
+        if subscriptions.statistics.total_nodes == 0 {
+            text = tr!("No remotes configured");
+            icon = subscription_icon("unknown");
+        } else if let SubscriptionStatus::Active = subscriptions.info.status {
+            text = tr!("Valid subscriptions");
+            icon = subscription_icon(&subscriptions.info.status.to_string());
+        } else if let Some(msg) = &subscriptions.info.message {
+            tooltip = Some(msg.clone());
+        }
+    } else if let Some(err) = &subscriptions.error {
+        tooltip = Some(err.to_string())
+    }
+
+    Some(
+        Tooltip::new(
+            Row::new()
+                .padding_x(2)
+                .gap(2)
+                .class(css::AlignItems::Center)
+                .with_child(icon.large())
+                .with_child(Container::new().with_child(text)),
+        )
+        .tip(tooltip),
+    )
+}
-- 
2.47.3





More information about the pdm-devel mailing list