* [pdm-devel] [PATCH yew-comp 1/2] subscription check: use more useful function signature
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 15:23 ` [pdm-devel] applied: " Thomas Lamprecht
2025-12-02 14:31 ` [pdm-devel] [PATCH yew-comp 2/2] subscriptions: expose subscription_icon Dominik Csapak
` (6 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
having a reference of an option is very unweildly at times, e.g. when
having the inner type, it's necessary to move it into an option, even if
`subscription_is_active` does not need to own the data.
using an option of a reference makes usage much more ergonomic at times.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
NOTE: this breaks current pdm, as it changes a public function
signature. Fix for pdm is the first datacenter manger patch.
src/apt_package_manager.rs | 2 +-
src/apt_repositories.rs | 2 +-
src/subscription_alert.rs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/apt_package_manager.rs b/src/apt_package_manager.rs
index 0b4ae2e..b419957 100644
--- a/src/apt_package_manager.rs
+++ b/src/apt_package_manager.rs
@@ -198,7 +198,7 @@ impl LoadableComponent for ProxmoxAptPackageManager {
let command = format!("{}/update", props.base_url);
self.async_pool.spawn(async move {
let data = crate::http_get::<Value>(url.as_str(), None).await;
- let is_active = subscription_is_active(&Some(data));
+ let is_active = subscription_is_active(Some(&data));
if is_active {
link.task_base_url(task_base_url);
diff --git a/src/apt_repositories.rs b/src/apt_repositories.rs
index 9ab8258..7157c0c 100644
--- a/src/apt_repositories.rs
+++ b/src/apt_repositories.rs
@@ -705,7 +705,7 @@ fn standard_repo_info(
impl ProxmoxAptRepositories {
fn active_subscription(&self) -> bool {
- subscription_is_active(&self.subscription_status)
+ subscription_is_active(self.subscription_status.as_ref())
}
fn create_show_subscription_dialog(
diff --git a/src/subscription_alert.rs b/src/subscription_alert.rs
index 7734123..496d258 100644
--- a/src/subscription_alert.rs
+++ b/src/subscription_alert.rs
@@ -68,7 +68,7 @@ impl From<SubscriptionAlert> for VNode {
}
/// Check if the result of the subscription check returned an active subscription
-pub fn subscription_is_active(result: &Option<Result<Value, Error>>) -> bool {
+pub fn subscription_is_active(result: Option<&Result<Value, Error>>) -> bool {
match result {
Some(Ok(data)) => {
data["status"].as_str().map(|s| s.to_lowercase()).as_deref() == Some("active")
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH yew-comp 2/2] subscriptions: expose subscription_icon
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
2025-12-02 14:31 ` [pdm-devel] [PATCH yew-comp 1/2] subscription check: use more useful function signature Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 15:23 ` [pdm-devel] applied: " Thomas Lamprecht
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 1/5] ui: adapt to signature change of subscription_is_active Dominik Csapak
` (5 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
we want to reuse this logic outside of proxmox-yew-comp too
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/lib.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/lib.rs b/src/lib.rs
index 8e43d35..6df8a32 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -187,7 +187,9 @@ mod subscription_panel;
pub use subscription_panel::{ProxmoxSubscriptionPanel, SubscriptionPanel};
mod subscription_info;
-pub use subscription_info::{subscription_note, ProxmoxSubscriptionInfo, SubscriptionInfo};
+pub use subscription_info::{
+ subscription_icon, subscription_note, ProxmoxSubscriptionInfo, SubscriptionInfo,
+};
mod syslog;
pub use syslog::{ProxmoxSyslog, Syslog};
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH datacenter-manager 1/5] ui: adapt to signature change of subscription_is_active
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
2025-12-02 14:31 ` [pdm-devel] [PATCH yew-comp 1/2] subscription check: use more useful function signature Dominik Csapak
2025-12-02 14:31 ` [pdm-devel] [PATCH yew-comp 2/2] subscriptions: expose subscription_icon Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 16:13 ` [pdm-devel] applied: " Thomas Lamprecht
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 2/5] lib/server: include subscription statistics in subscription information Dominik Csapak
` (4 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
it takes an option of a reference, not a reference of an option
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
ui/src/lib.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index 31068ab1..20dcec8b 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -241,7 +241,7 @@ pub(crate) fn locale_compare(first: String, second: &str, numeric: bool) -> std:
/// Returns true if the global subscription checks succeeded
pub async fn check_subscription() -> bool {
let data: Result<Value, _> = http_get("/nodes/localhost/subscription", None).await;
- proxmox_yew_comp::subscription_is_active(&Some(data))
+ proxmox_yew_comp::subscription_is_active(Some(&data))
}
/// Extract the version of a specific package from `RemoteUpdateSummary` for a specific node
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH datacenter-manager 2/5] lib/server: include subscription statistics in subscription information
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
` (2 preceding siblings ...)
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 1/5] ui: adapt to signature change of subscription_is_active Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 16:13 ` [pdm-devel] applied: " Thomas Lamprecht
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 3/5] ui: configuration: add subscription panel Dominik Csapak
` (3 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
so we can show that information on the subscription panel. This is
useful for users to see why the subscription is marked as invalid.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
lib/pdm-api-types/src/subscription.rs | 20 +++++++++++++++++++-
server/src/api/nodes/subscription.rs | 24 +++++++++++++-----------
2 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/lib/pdm-api-types/src/subscription.rs b/lib/pdm-api-types/src/subscription.rs
index d38611e3..ca23b8e5 100644
--- a/lib/pdm-api-types/src/subscription.rs
+++ b/lib/pdm-api-types/src/subscription.rs
@@ -4,7 +4,7 @@ use anyhow::Error;
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
-use proxmox_subscription::SubscriptionStatus;
+use proxmox_subscription::{SubscriptionInfo, SubscriptionStatus};
#[api]
// order is important here, since we use that for determining if a node has a valid subscription
@@ -156,3 +156,21 @@ pub struct SubscriptionStatistics {
/// Total number of community level subscriptions across all remotes
pub community: usize,
}
+
+#[api(
+ properties: {
+ info: {
+ type: SubscriptionInfo,
+ }
+ }
+)]
+#[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// The PDM subscription info
+pub struct PdmSubscriptionInfo {
+ #[serde(flatten)]
+ pub info: SubscriptionInfo,
+
+ /// PDM subscription statistics
+ pub statistics: SubscriptionStatistics,
+}
diff --git a/server/src/api/nodes/subscription.rs b/server/src/api/nodes/subscription.rs
index d9b44d59..d41a1794 100644
--- a/server/src/api/nodes/subscription.rs
+++ b/server/src/api/nodes/subscription.rs
@@ -11,7 +11,7 @@ use proxmox_sys::fs::CreateOptions;
use pdm_api_types::remotes::RemoteType;
use pdm_api_types::subscription::{
- NodeSubscriptionInfo, SubscriptionLevel, SubscriptionStatistics,
+ NodeSubscriptionInfo, PdmSubscriptionInfo, SubscriptionLevel, SubscriptionStatistics,
};
use pdm_api_types::PRIV_SYS_MODIFY;
@@ -78,7 +78,7 @@ fn count_subscriptions(
stats
}
-fn check_counts(stats: SubscriptionStatistics) -> Result<(), Error> {
+fn check_counts(stats: &SubscriptionStatistics) -> Result<(), Error> {
let subscribed_ratio = stats.active_subscriptions as f64 / stats.total_nodes as f64;
let community_ratio = stats.community as f64 / stats.active_subscriptions as f64;
@@ -107,26 +107,28 @@ fn check_counts(stats: SubscriptionStatistics) -> Result<(), Error> {
}
)]
/// Return subscription status
-pub async fn get_subscription() -> Result<SubscriptionInfo, Error> {
+pub async fn get_subscription() -> Result<PdmSubscriptionInfo, Error> {
let infos = get_all_subscription_infos().await?;
- let stats = count_subscriptions(&infos);
+ let statistics = count_subscriptions(&infos);
- if let Err(err) = check_counts(stats) {
- Ok(SubscriptionInfo {
+ let info = if let Err(err) = check_counts(&statistics) {
+ SubscriptionInfo {
status: SubscriptionStatus::Invalid,
message: Some(format!("{err}")),
serverid: None,
url: Some(PRODUCT_URL.into()),
..Default::default()
- })
+ }
} else {
- Ok(SubscriptionInfo {
+ SubscriptionInfo {
status: SubscriptionStatus::Active,
url: Some(PRODUCT_URL.into()),
..Default::default()
- })
- }
+ }
+ };
+
+ Ok(PdmSubscriptionInfo { info, statistics })
}
#[api(
@@ -147,7 +149,7 @@ pub async fn check_subscription() -> Result<(), Error> {
let infos = get_all_subscription_infos().await?;
let stats = count_subscriptions(&infos);
- if let Err(err) = check_counts(stats) {
+ if let Err(err) = check_counts(&stats) {
update_apt_auth(APT_AUTH_FN, apt_auth_file_opts(), APT_AUTH_URL, None, None)?;
return Err(err);
}
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH datacenter-manager 3/5] ui: configuration: add subscription panel
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
` (3 preceding siblings ...)
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 2/5] lib/server: include subscription statistics in subscription information Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 16:13 ` [pdm-devel] applied: " Thomas Lamprecht
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 4/5] ui: show pdm subscription state in top nav bar Dominik Csapak
` (2 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
it's very similar to the pve/pbs/pmg one, but only shows relevant
information. It also shows the statistics that determines the validity
state.
Note: the thresholds and calculation is copied from the backend, to
further improve this, we should move that code somwhere we can reuse it
for both.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
ui/src/configuration/mod.rs | 2 +
ui/src/configuration/subscription_panel.rs | 187 +++++++++++++++++++++
ui/src/main_menu.rs | 18 +-
3 files changed, 206 insertions(+), 1 deletion(-)
create mode 100644 ui/src/configuration/subscription_panel.rs
diff --git a/ui/src/configuration/mod.rs b/ui/src/configuration/mod.rs
index 35114336..18fc3966 100644
--- a/ui/src/configuration/mod.rs
+++ b/ui/src/configuration/mod.rs
@@ -13,6 +13,8 @@ mod permission_path_selector;
mod webauthn;
pub use webauthn::WebauthnPanel;
+pub mod subscription_panel;
+
pub mod views;
#[function_component(SystemConfiguration)]
diff --git a/ui/src/configuration/subscription_panel.rs b/ui/src/configuration/subscription_panel.rs
new file mode 100644
index 00000000..797eab0e
--- /dev/null
+++ b/ui/src/configuration/subscription_panel.rs
@@ -0,0 +1,187 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::rc::Rc;
+
+use pdm_api_types::subscription::SubscriptionStatistics;
+use serde_json::Value;
+
+use yew::virtual_dom::{VComp, VNode};
+
+use pwt::prelude::*;
+use pwt::widget::{error_message, Button, Column, Container, Row, Toolbar};
+
+use proxmox_yew_comp::{http_get, http_post, KVGrid, KVGridRow};
+use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster};
+
+const SUBSCRIPTION_URL: &str = "/nodes/localhost/subscription";
+
+#[derive(Properties, PartialEq, Clone)]
+pub struct SubscriptionPanel {}
+
+impl SubscriptionPanel {
+ pub fn new() -> Self {
+ yew::props!(Self {})
+ }
+}
+
+pub enum Msg {
+ LoadFinished(Value),
+}
+
+pub struct ProxmoxSubscriptionPanel {
+ rows: Rc<Vec<KVGridRow>>,
+ data: Option<Rc<Value>>,
+}
+
+impl LoadableComponent for ProxmoxSubscriptionPanel {
+ type Message = Msg;
+ type Properties = SubscriptionPanel;
+ type ViewState = ();
+
+ fn create(_ctx: &LoadableComponentContext<Self>) -> Self {
+ Self {
+ rows: Rc::new(rows()),
+ data: None,
+ }
+ }
+
+ fn update(&mut self, _ctx: &LoadableComponentContext<Self>, msg: Self::Message) -> bool {
+ match msg {
+ Msg::LoadFinished(value) => {
+ self.data = Some(Rc::new(value));
+ }
+ }
+ true
+ }
+
+ fn load(
+ &self,
+ _ctx: &LoadableComponentContext<Self>,
+ ) -> Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>> {
+ let link = _ctx.link().clone();
+ Box::pin(async move {
+ let info = http_get(SUBSCRIPTION_URL, None).await?;
+ link.send_message(Msg::LoadFinished(info));
+ Ok(())
+ })
+ }
+
+ fn toolbar(&self, ctx: &LoadableComponentContext<Self>) -> Option<Html> {
+ let toolbar = Toolbar::new()
+ .class("pwt-overflow-hidden")
+ .border_bottom(true)
+ .with_child(
+ Button::new(tr!("Check"))
+ .icon_class("fa fa-check-square-o")
+ .on_activate({
+ let link = ctx.link();
+
+ move |_| {
+ link.spawn({
+ let link = link.clone();
+ async move {
+ match http_post(SUBSCRIPTION_URL, None).await {
+ Ok(()) => link.send_reload(),
+ Err(err) => {
+ link.show_error(tr!("Error"), err.to_string(), true)
+ }
+ }
+ }
+ })
+ }
+ }),
+ )
+ .with_spacer()
+ .with_flex_spacer()
+ .with_child({
+ let loading = ctx.loading();
+ let link = ctx.link();
+ Button::refresh(loading).on_activate(move |_| link.send_reload())
+ });
+
+ Some(toolbar.into())
+ }
+
+ fn main_view(&self, _ctx: &LoadableComponentContext<Self>) -> Html {
+ let data = match &self.data {
+ Some(data) => data.clone(),
+ None => Rc::new(Value::Null),
+ };
+
+ KVGrid::new()
+ .class("pwt-flex-fit")
+ .data(data.clone())
+ .rows(Rc::clone(&self.rows))
+ .into()
+ }
+}
+
+impl From<SubscriptionPanel> for VNode {
+ fn from(val: SubscriptionPanel) -> Self {
+ let comp =
+ VComp::new::<LoadableComponentMaster<ProxmoxSubscriptionPanel>>(Rc::new(val), None);
+ VNode::from(comp)
+ }
+}
+
+// FIXME: ratios copied from backend
+
+// minimum ratio of nodes with active subscriptions
+const SUBSCRIPTION_THRESHOLD: f64 = 0.9;
+// max ratio of nodes with community subscriptions, among nodes with subscriptions
+const COMMUNITY_THRESHOLD: f64 = 0.4;
+
+fn rows() -> Vec<KVGridRow> {
+ vec![
+ KVGridRow::new("status", tr!("Status")).renderer(move |_name, value, record| {
+ let value = match value {
+ Value::String(data) => data,
+ Value::Null => return Container::from_tag("i").class("pwt-loading-icon").into(),
+ _ => return error_message(&tr!("invalid data")).into(),
+ };
+ match record["message"].as_str() {
+ Some(msg) => format!("{value}: {msg}").into(),
+ None => value.into(),
+ }
+ }),
+ KVGridRow::new("statistics", tr!("Statistics")).renderer(move |_name, value, _record| {
+ let statistics = serde_json::from_value::<SubscriptionStatistics>(value.clone());
+ match statistics {
+ Ok(stats) => {
+ let subscribed_ratio =
+ stats.active_subscriptions as f64 / stats.total_nodes as f64;
+ let community_ratio =
+ stats.community as f64 / stats.active_subscriptions as f64;
+
+ fn operator(a: f64, b: f64) -> &'static str {
+ if a >= b {
+ ">="
+ } else {
+ "<"
+ }
+ }
+
+ Column::new()
+ .with_child(Row::new().with_child(tr!(
+ "Subscribed Ratio: {0} ({1} {2})",
+ format!("{:.0}%", subscribed_ratio * 100.0),
+ operator(subscribed_ratio, SUBSCRIPTION_THRESHOLD),
+ format!("{:.0}%", SUBSCRIPTION_THRESHOLD * 100.0),
+ )))
+ .with_child(Row::new().with_child(tr!(
+ "Community Ratio: {0} ({1} {2})",
+ format!("{:.0}%", community_ratio * 100.0),
+ operator(community_ratio, COMMUNITY_THRESHOLD),
+ format!("{:.0}%", COMMUNITY_THRESHOLD * 100.0),
+ )))
+ .into()
+ }
+ Err(err) => error_message(&format!("api error: {err}")).into(),
+ }
+ }),
+ KVGridRow::new("url", tr!("Info URL")).renderer(|_name, value, _record| {
+ let url = value.as_str().unwrap().to_string();
+ html! { <a target="_blank" href={url.clone()}>{url}</a> }
+ }),
+ ]
+}
diff --git a/ui/src/main_menu.rs b/ui/src/main_menu.rs
index 0847c7a2..18988eaf 100644
--- a/ui/src/main_menu.rs
+++ b/ui/src/main_menu.rs
@@ -7,13 +7,14 @@ use pwt::css::{self, Display, FlexFit};
use pwt::prelude::*;
use pwt::state::{NavigationContextExt, Selection};
use pwt::widget::nav::{Menu, MenuItem, NavigationDrawer};
-use pwt::widget::{Container, Row, SelectionView, SelectionViewRenderInfo};
+use pwt::widget::{Container, Panel, Row, SelectionView, SelectionViewRenderInfo};
use proxmox_yew_comp::{AclContext, NotesView, XTermJs};
use pdm_api_types::remotes::RemoteType;
use pdm_api_types::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
+use crate::configuration::subscription_panel::SubscriptionPanel;
use crate::configuration::views::ViewGrid;
use crate::dashboard::view::View;
use crate::remotes::RemotesPanel;
@@ -266,6 +267,21 @@ impl Component for PdmMainMenu {
|_| html! {<CertificatesPanel/>},
);
+ register_view(
+ &mut config_submenu,
+ &mut content,
+ tr!("Subscription"),
+ "subscription",
+ Some("fa fa-support"),
+ |_| {
+ Panel::new()
+ .class(css::FlexFit)
+ .title(tr!("Subscription"))
+ .with_child(SubscriptionPanel::new())
+ .into()
+ },
+ );
+
register_submenu(
&mut menu,
&mut content,
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH datacenter-manager 4/5] ui: show pdm subscription state in top nav bar
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
` (4 preceding siblings ...)
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 3/5] ui: configuration: add subscription panel Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 5/5] ui: views: make Subscription panel not required anymore Dominik Csapak
2025-12-02 15:27 ` [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Thomas Lamprecht
7 siblings, 0 replies; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
Just an icon and a very high level (and short) text to not use too much
space here. A click on it navigates to the subscription panel (with more
details).
For this to work, we have to change the subscription laoding in the main
entry point a bit, since we now want to always load it, not just when
we're freshly logging in.
While the icon logic is reused from proxmox-yew-comp, the text is unique
here because it has to be short and we don't have that many possible
states currently.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
ui/Cargo.toml | 1 +
ui/src/main.rs | 56 +++++++++++++++++++++++++++----------------
ui/src/top_nav_bar.rs | 41 +++++++++++++++++++++++++++++++
3 files changed, 78 insertions(+), 20 deletions(-)
diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index d58dac37..2d347ad2 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/main.rs b/ui/src/main.rs
index 63ac1431..bc8143a6 100644
--- a/ui/src/main.rs
+++ b/ui/src/main.rs
@@ -1,6 +1,6 @@
use anyhow::Error;
use gloo_timers::callback::Timeout;
-use serde_json::json;
+use serde_json::{json, Value};
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;
@@ -16,15 +16,16 @@ use pbs_api_types::TaskListItem;
use proxmox_login::Authentication;
use proxmox_yew_comp::utils::init_task_descr_table_base;
use proxmox_yew_comp::{
- authentication_from_cookie, http_get, register_auth_observer, AclContextProvider, AuthObserver,
- LoginPanel, SubscriptionAlert,
+ authentication_from_cookie, http_get, register_auth_observer, subscription_is_active,
+ AclContextProvider, AuthObserver, LoginPanel, SubscriptionAlert,
};
//use pbs::MainMenu;
+use pdm_api_types::subscription::PdmSubscriptionInfo;
use pdm_api_types::views::ViewConfig;
use pdm_ui::{
- check_subscription, register_pve_tasks, MainMenu, RemoteList, RemoteListCacheEntry,
- SearchProvider, TopNavBar, ViewListContext,
+ register_pve_tasks, MainMenu, RemoteList, RemoteListCacheEntry, SearchProvider, TopNavBar,
+ ViewListContext,
};
type MsgRemoteList = Result<RemoteList, Error>;
@@ -40,12 +41,14 @@ enum Msg {
RemoteList(MsgRemoteList),
ViewList(MsgViewList),
UpdateViewList,
+ UpdateSubscriptionInfo(Result<Value, Error>, bool), // fresh login
}
struct DatacenterManagerApp {
_auth_observer: AuthObserver,
login_info: Option<Authentication>,
subscription_confirmed: bool,
+ subscription: Option<PdmSubscriptionInfo>,
show_subscription_alert: Option<bool>,
running_tasks: Loader<Vec<TaskListItem>>,
running_tasks_timeout: Option<Timeout>,
@@ -74,21 +77,12 @@ impl DatacenterManagerApp {
fn on_login(&mut self, ctx: &Context<Self>, fresh_login: bool) {
if let Some(info) = &self.login_info {
self.running_tasks.load();
- if fresh_login {
- if self.subscription_confirmed {
- ctx.link().send_message(Msg::ConfirmSubscription);
- } else {
- self.async_pool.send_future(ctx.link().clone(), async move {
- let is_active = check_subscription().await;
-
- if !is_active {
- Msg::ShowSubscriptionAlert
- } else {
- Msg::ConfirmSubscription
- }
- });
- }
- } else {
+ let link = ctx.link().clone();
+ self.async_pool.spawn(async move {
+ let data = http_get("/nodes/localhost/subscription", None).await;
+ link.send_message(Msg::UpdateSubscriptionInfo(data, fresh_login));
+ });
+ if !fresh_login {
ctx.link().send_message(Msg::ConfirmSubscription);
proxmox_yew_comp::http_set_auth(info.clone());
}
@@ -209,6 +203,7 @@ impl Component for DatacenterManagerApp {
_auth_observer,
login_info,
subscription_confirmed: false,
+ subscription: None,
show_subscription_alert: None,
running_tasks,
running_tasks_timeout: None,
@@ -229,6 +224,27 @@ impl Component for DatacenterManagerApp {
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
+ Msg::UpdateSubscriptionInfo(data, fresh_login) => {
+ if fresh_login {
+ let message = if self.subscription_confirmed {
+ Msg::ConfirmSubscription
+ } else {
+ let is_active = subscription_is_active(Some(&data));
+
+ if !is_active {
+ Msg::ShowSubscriptionAlert
+ } else {
+ Msg::ConfirmSubscription
+ }
+ };
+ ctx.link().send_message(message);
+ }
+ self.subscription = data
+ .map(|value| serde_json::from_value::<PdmSubscriptionInfo>(value).ok())
+ .ok()
+ .flatten();
+ true
+ }
Msg::ConfirmSubscription => {
self.subscription_confirmed = true;
self.show_subscription_alert = Some(false);
diff --git a/ui/src/top_nav_bar.rs b/ui/src/top_nav_bar.rs
index 4c63e013..a99dc7ff 100644
--- a/ui/src/top_nav_bar.rs
+++ b/ui/src/top_nav_bar.rs
@@ -10,10 +10,13 @@ use pwt::widget::menu::{Menu, MenuButton, MenuEntry, MenuEvent, MenuItem};
use pwt::AsyncAbortGuard;
use yew::html::{IntoEventCallback, IntoPropValue};
use yew::virtual_dom::{VComp, VNode};
+use yew_router::prelude::RouterScopeExt;
+use yew_router::AnyRoute;
use pwt::state::{Loader, Theme, ThemeObserver};
use pwt::widget::{Button, Container, Row, ThemeModeSelector, Tooltip};
+use proxmox_subscription::SubscriptionStatus;
use proxmox_yew_comp::utils::set_location_href;
use proxmox_yew_comp::RunningTasksButton;
use proxmox_yew_comp::{http_get, LanguageDialog, TaskViewer, ThemeDialog};
@@ -21,6 +24,7 @@ use proxmox_yew_comp::{http_get, LanguageDialog, TaskViewer, ThemeDialog};
use pwt_macros::builder;
use pbs_api_types::TaskListItem;
+use pdm_api_types::subscription::PdmSubscriptionInfo;
use pdm_api_types::RemoteUpid;
use crate::tasks::format_optional_remote_upid;
@@ -42,6 +46,11 @@ async fn load_version() -> Result<VersionInfo, Error> {
pub struct TopNavBar {
running_tasks: Loader<Vec<TaskListItem>>,
+ #[builder(IntoPropValue, into_prop_value)]
+ #[prop_or_default]
+ /// Used to show the subscription validity
+ subscription_info: Option<PdmSubscriptionInfo>,
+
#[builder_cb(IntoEventCallback, into_event_callback, ())]
#[prop_or_default]
pub on_logout: Option<Callback<()>>,
@@ -284,6 +293,18 @@ impl Component for PdmTopNavBar {
<a href="https://bugzilla.proxmox.com" target="_blank">{"BETA"}</a>
</span>
})
+ .with_child(
+ show_subscription_notice(props.subscription_info.clone())
+ .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_child(SearchBox::new())
.with_flex_spacer()
@@ -299,3 +320,23 @@ impl From<TopNavBar> for VNode {
VNode::from(comp)
}
}
+
+fn show_subscription_notice(subscriptions: Option<PdmSubscriptionInfo>) -> Row {
+ let subscription = subscriptions.unwrap_or_default();
+ let icon = proxmox_yew_comp::subscription_icon(&subscription.info.status.to_string());
+
+ // not the same as in proxmox-yew-comp, because
+ // 1. pdm does not have its own subscription
+ // 2. the text must be short for the top nav bar
+ let text = match subscription.info.status {
+ SubscriptionStatus::Active => tr!("Valid subscription"),
+ SubscriptionStatus::NotFound => tr!("No valid subscription"),
+ _ => tr!("Invalid subscription state"),
+ };
+
+ Row::new()
+ .padding_x(2)
+ .gap(2)
+ .with_child(Container::new().with_child(icon.large()))
+ .with_child(Container::new().with_child(text))
+}
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* [pdm-devel] [PATCH datacenter-manager 5/5] ui: views: make Subscription panel not required anymore
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
` (5 preceding siblings ...)
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 4/5] ui: show pdm subscription state in top nav bar Dominik Csapak
@ 2025-12-02 14:31 ` Dominik Csapak
2025-12-03 12:08 ` Thomas Lamprecht
2025-12-02 15:27 ` [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Thomas Lamprecht
7 siblings, 1 reply; 15+ messages in thread
From: Dominik Csapak @ 2025-12-02 14:31 UTC (permalink / raw)
To: pdm-devel
Since we now show the subscription state of pdm always in the top bar,
the subscription panel in views are useful, but not required anymore.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
ui/src/dashboard/view.rs | 38 +-------------------------------------
1 file changed, 1 insertion(+), 37 deletions(-)
diff --git a/ui/src/dashboard/view.rs b/ui/src/dashboard/view.rs
index 0606f1a9..f45f4622 100644
--- a/ui/src/dashboard/view.rs
+++ b/ui/src/dashboard/view.rs
@@ -12,7 +12,7 @@ use pwt::css;
use pwt::prelude::*;
use pwt::props::StorageLocation;
use pwt::state::{PersistentState, SharedState};
-use pwt::widget::{error_message, form::FormContext, Column, Container, Progress, Row};
+use pwt::widget::{error_message, form::FormContext, Column, Container, Progress};
use pwt::AsyncPool;
use crate::dashboard::refresh_config_edit::{
@@ -288,23 +288,6 @@ fn required_api_calls(layout: &ViewLayout) -> (bool, bool, bool) {
(status, top_entities, task_statistics)
}
-fn has_sub_panel(layout: Option<&ViewTemplate>) -> bool {
- match layout.map(|template| &template.layout) {
- Some(ViewLayout::Rows { rows }) => {
- for row in rows {
- for item in row {
- if item.r#type == WidgetType::Subscription {
- return true;
- }
- }
- }
- }
- None => {}
- }
-
- false
-}
-
impl Component for ViewComp {
type Message = Msg;
type Properties = View;
@@ -479,25 +462,6 @@ impl Component for ViewComp {
),
);
- if !has_sub_panel(self.template.data.as_ref()) {
- let subs = self.render_args.subscriptions.clone();
- let link = ctx.link().clone();
- view.add_child(
- Row::new()
- .padding_x(4)
- .padding_bottom(4)
- .padding_top(0)
- .class("pwt-content-spacer-colors")
- .with_child(
- create_subscription_panel(
- subs.clone(),
- link.clone()
- .callback(move |_| Msg::ShowSubscriptionsDialog(true)),
- )
- .flex(1.0),
- ),
- );
- }
match self.template.data.as_ref().map(|template| &template.layout) {
Some(ViewLayout::Rows { rows }) => {
view.add_child(
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel
2025-12-02 14:31 [pdm-devel] [PATCH datacenter-manager/yew-comp 0/7] add top bar subscription state and dedicated panel Dominik Csapak
` (6 preceding siblings ...)
2025-12-02 14:31 ` [pdm-devel] [PATCH datacenter-manager 5/5] ui: views: make Subscription panel not required anymore Dominik Csapak
@ 2025-12-02 15:27 ` Thomas Lamprecht
7 siblings, 0 replies; 15+ messages in thread
From: Thomas Lamprecht @ 2025-12-02 15:27 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion, Dominik Csapak
Am 02.12.25 um 15:32 schrieb Dominik Csapak:
> This adds a dedicated subscription panel for pdm, and adds the overall subscription
> state to the top nav bar.
Thx, will take a look.
>
> This means we can drop the required subscription panel for the views.
>
> I opted for not removing the subscription panel from the default dashboard,
> but if that's wanted it is not a big change.
good call! let's keep that.
>
> Note that the first proxmox-yew-comp patch breaks current pdm, since a
> public function signature changes.
That change does make ergonomically sense IMO.
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 15+ messages in thread