* [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view
@ 2025-09-03 11:41 Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/2] apt view: allow to set task_base_url Lukas Wagner
` (8 more replies)
0 siblings, 9 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This series adds a new 'Updates' tab for PVE remotes. The existing status
overview is moved to a new 'Overview' tab, which is visible by default.
On the backend side, we add a couple new API endpoints, which simply pass
through the request to the PVE nodes, no caching for now.
GET /pve/remotes/{remote}/nodes/{node}/apt
Get list of updatable packages
GET /pve/remotes/{remote}/nodes/{node}/changelog
Get list of changelog of package
POST /pve/remotes/{remote}/nodes/{node}/apt
Update APT package database
In terms of permissions, these new API endpoints require RESOURCE_MODIFY privs on
/resource/{remote}/node/{node}/system
This was the result of a short discussion in the development chat room.
The existing APT view component is a bit large for this panel, maybe we could
hide the package description by default (but not too important for now).
Future work (some backend work already started, but can't finish before my
vacation):
- "Global Update" view that lists update status of all remote nodes
- Cache update status per node (absolutely necessary for the 'global' view),
with a task refreshing the update status every couple of hours
- Maybe send a notification about the global update availabilty (require notification
stack integration first)
- Add new API functions to pdm-client crate and CLI
- Allow package upgrade (requires web socket proxying, as far as I can see,
haven't really looked into it much)
Changes since v1:
- consistently return errors for PBS remotes
- drop already applied patches
Some of the notes from Stefan's review notes were not addressed, see my replies
for v1 for details.
proxmox-yew-comp:
Lukas Wagner (2):
apt view: allow to set task_base_url
apt view: reload if base urls have changed
src/apt_package_manager.rs | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
proxmox-datacenter-manager:
Lukas Wagner (5):
update proxmox-api-types submodule
server: add api for getting available updates/changelogs for remote
nodes
ui: pve: promote node.rs to dir-style module
ui: pve: move node overview to a new overview tab
ui: pve: node: add update tab
lib/proxmox-api-types | 2 +-
server/src/api/pve/apt.rs | 119 +++++++++++++++++++++++
server/src/api/pve/mod.rs | 3 +-
server/src/api/pve/node.rs | 1 +
server/src/lib.rs | 1 +
server/src/remote_updates.rs | 89 +++++++++++++++++
ui/src/pve/node/mod.rs | 103 ++++++++++++++++++++
ui/src/pve/{node.rs => node/overview.rs} | 31 +++---
8 files changed, 327 insertions(+), 22 deletions(-)
create mode 100644 server/src/api/pve/apt.rs
create mode 100644 server/src/remote_updates.rs
create mode 100644 ui/src/pve/node/mod.rs
rename ui/src/pve/{node.rs => node/overview.rs} (95%)
Summary over all repositories:
9 files changed, 350 insertions(+), 22 deletions(-)
--
Generated by murpp 0.9.0
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-yew-comp v2 1/2] apt view: allow to set task_base_url
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 2/2] apt view: reload if base urls have changed Lukas Wagner
` (7 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This one is used to poll the 'apt update' task progress. If we want to
use this component for *remote* nodes, we need to be able to set a
custom base url for the task API.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/apt_package_manager.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/apt_package_manager.rs b/src/apt_package_manager.rs
index 282caa9..6cd179f 100644
--- a/src/apt_package_manager.rs
+++ b/src/apt_package_manager.rs
@@ -40,6 +40,11 @@ pub struct AptPackageManager {
/// The base url for
pub base_url: AttrValue,
+ #[prop_or("/nodes/localhost/tasks".into())]
+ #[builder(IntoPropValue, into_prop_value)]
+ /// The base url for tasks
+ pub task_base_url: AttrValue,
+
/// Enable the upgrade button
#[prop_or_default]
#[builder]
@@ -193,6 +198,9 @@ impl LoadableComponent for ProxmoxAptPackageManager {
.class("pwt-border-bottom")
.with_child(Button::new(tr!("Refresh")).onclick({
let link = ctx.link();
+
+ link.task_base_url(props.task_base_url.clone());
+
let command = format!("{}/update", props.base_url);
move |_| link.start_task(&command, None, false)
}))
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-yew-comp v2 2/2] apt view: reload if base urls have changed
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/2] apt view: allow to set task_base_url Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/5] update proxmox-api-types submodule Lukas Wagner
` (6 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
If we use this view to show available node updates, we might want to
change the node afterwards (e.g. by selecting another node in a tree).
By implementing the `changed` function we can trigger a reload if the
relevant props change.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/apt_package_manager.rs | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/apt_package_manager.rs b/src/apt_package_manager.rs
index 6cd179f..3b871f7 100644
--- a/src/apt_package_manager.rs
+++ b/src/apt_package_manager.rs
@@ -256,6 +256,21 @@ impl LoadableComponent for ProxmoxAptPackageManager {
}
}
}
+
+ fn changed(
+ &mut self,
+ ctx: &LoadableComponentContext<Self>,
+ old_props: &Self::Properties,
+ ) -> bool {
+ let props = ctx.props();
+
+ if props.base_url != old_props.base_url || props.task_base_url != old_props.task_base_url {
+ ctx.link().send_reload();
+ true
+ } else {
+ false
+ }
+ }
}
impl From<AptPackageManager> for VNode {
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/5] update proxmox-api-types submodule
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/2] apt view: allow to set task_base_url Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 2/2] apt view: reload if base urls have changed Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 2/5] server: add api for getting available updates/changelogs for remote nodes Lukas Wagner
` (5 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
Notes:
New in v2.
lib/proxmox-api-types | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/proxmox-api-types b/lib/proxmox-api-types
index bdf59631..12c8baa2 160000
--- a/lib/proxmox-api-types
+++ b/lib/proxmox-api-types
@@ -1 +1 @@
-Subproject commit bdf59631077ca9051cf6845e3ea4f35d0be8b6f2
+Subproject commit 12c8baa20ebc9fbb4fd63acfd8af204946396f7c
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-datacenter-manager v2 2/5] server: add api for getting available updates/changelogs for remote nodes
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (2 preceding siblings ...)
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/5] update proxmox-api-types submodule Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 3/5] ui: pve: promote node.rs to dir-style module Lukas Wagner
` (4 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This adds new APIs for update management:
GET /pve/remotes/{remote}/nodes/{node}/apt/changelog
-> get package changelog
GET /pve/remotes/{remote}/nodes/{node}/apt/update
-> get list of updatable packages
POST /pve/remotes/{remote}/nodes/{node}/apt/update
-> refresh APT database
At this time these just pass the call through to PVE with no caching
involved on the PDM side. This should be fine for this API, but once
we have an API for 'give me a view of ALL available remote updates',
we need to introduce a cache that is periodically refreshed.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
Notes:
Changes since v1:
- remote_updates: return error for PBS remotes, instead of returning
Ok("TODO") or panicking
server/src/api/pve/apt.rs | 119 +++++++++++++++++++++++++++++++++++
server/src/api/pve/mod.rs | 3 +-
server/src/api/pve/node.rs | 1 +
server/src/lib.rs | 1 +
server/src/remote_updates.rs | 89 ++++++++++++++++++++++++++
5 files changed, 212 insertions(+), 1 deletion(-)
create mode 100644 server/src/api/pve/apt.rs
create mode 100644 server/src/remote_updates.rs
diff --git a/server/src/api/pve/apt.rs b/server/src/api/pve/apt.rs
new file mode 100644
index 00000000..f5027fb8
--- /dev/null
+++ b/server/src/api/pve/apt.rs
@@ -0,0 +1,119 @@
+use anyhow::Error;
+
+use proxmox_apt_api_types::{APTGetChangelogOptions, APTUpdateInfo};
+use proxmox_router::{list_subdirs_api_method, Permission, Router, SubdirMap};
+use proxmox_schema::api;
+use proxmox_schema::api_types::NODE_SCHEMA;
+
+use pdm_api_types::{remotes::REMOTE_ID_SCHEMA, RemoteUpid, PRIV_RESOURCE_MODIFY};
+
+use crate::{api::remotes::get_remote, remote_updates};
+
+#[api(
+ input: {
+ properties: {
+ remote: {
+ schema: REMOTE_ID_SCHEMA,
+ },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ description: "A list of packages with available updates.",
+ type: Array,
+ items: {
+ type: APTUpdateInfo
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "node", "{node}", "system"], PRIV_RESOURCE_MODIFY, false),
+ },
+)]
+/// List available APT updates for a remote PVE node.
+async fn apt_update_available(remote: String, node: String) -> Result<Vec<APTUpdateInfo>, Error> {
+ let (config, _digest) = pdm_config::remotes::config()?;
+ let remote = get_remote(&config, &remote)?;
+
+ let updates = remote_updates::list_available_updates(remote.clone(), &node).await?;
+
+ Ok(updates)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: {
+ schema: REMOTE_ID_SCHEMA,
+ },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "node", "{node}", "system"], PRIV_RESOURCE_MODIFY, false),
+ },
+)]
+/// Update the APT database of a remote PVE node.
+pub async fn apt_update_database(remote: String, node: String) -> Result<RemoteUpid, Error> {
+ let (config, _digest) = pdm_config::remotes::config()?;
+ let remote = get_remote(&config, &remote)?;
+
+ let upid = remote_updates::update_apt_database(remote, &node).await?;
+
+ Ok(upid)
+}
+
+#[api(
+ input: {
+ properties: {
+ remote: {
+ schema: REMOTE_ID_SCHEMA,
+ },
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ options: {
+ type: APTGetChangelogOptions,
+ flatten: true,
+ },
+ },
+ },
+ returns: {
+ description: "The Package changelog.",
+ type: String,
+ },
+ access: {
+ permission: &Permission::Privilege(&["resource", "{remote}", "node", "{node}", "system"], PRIV_RESOURCE_MODIFY, false),
+ },
+)]
+/// Retrieve the changelog of the specified package for a remote PVE node.
+async fn apt_get_changelog(
+ remote: String,
+ node: String,
+ options: APTGetChangelogOptions,
+) -> Result<String, Error> {
+ let (config, _digest) = pdm_config::remotes::config()?;
+ let remote = get_remote(&config, &remote)?;
+
+ remote_updates::get_changelog(remote.clone(), &node, options.name).await
+}
+
+const SUBDIRS: SubdirMap = &[
+ (
+ "changelog",
+ &Router::new().get(&API_METHOD_APT_GET_CHANGELOG),
+ ),
+ (
+ "update",
+ &Router::new()
+ .get(&API_METHOD_APT_UPDATE_AVAILABLE)
+ .post(&API_METHOD_APT_UPDATE_DATABASE),
+ ),
+];
+
+pub const ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(SUBDIRS))
+ .subdirs(SUBDIRS);
diff --git a/server/src/api/pve/mod.rs b/server/src/api/pve/mod.rs
index 2cfdc5b7..0768083d 100644
--- a/server/src/api/pve/mod.rs
+++ b/server/src/api/pve/mod.rs
@@ -31,6 +31,7 @@ use crate::connection::PveClient;
use crate::connection::{self, probe_tls_connection};
use crate::remote_tasks;
+mod apt;
mod lxc;
mod node;
mod qemu;
@@ -77,7 +78,7 @@ const RESOURCES_ROUTER: Router = Router::new().get(&API_METHOD_CLUSTER_RESOURCES
const STATUS_ROUTER: Router = Router::new().get(&API_METHOD_CLUSTER_STATUS);
// converts a remote + PveUpid into a RemoteUpid and starts tracking it
-async fn new_remote_upid(remote: String, upid: PveUpid) -> Result<RemoteUpid, Error> {
+pub async fn new_remote_upid(remote: String, upid: PveUpid) -> Result<RemoteUpid, Error> {
let remote_upid: RemoteUpid = (remote, upid.to_string()).try_into()?;
remote_tasks::track_running_task(remote_upid.clone()).await?;
Ok(remote_upid)
diff --git a/server/src/api/pve/node.rs b/server/src/api/pve/node.rs
index df96a1c3..99539d1c 100644
--- a/server/src/api/pve/node.rs
+++ b/server/src/api/pve/node.rs
@@ -13,6 +13,7 @@ pub const ROUTER: Router = Router::new()
#[sortable]
const SUBDIRS: SubdirMap = &sorted!([
+ ("apt", &super::apt::ROUTER),
("rrddata", &super::rrddata::NODE_RRD_ROUTER),
("network", &Router::new().get(&API_METHOD_GET_NETWORK)),
("storage", &Router::new().get(&API_METHOD_GET_STORAGES)),
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 3f8b7708..a58190d8 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -9,6 +9,7 @@ pub mod metric_collection;
pub mod parallel_fetcher;
pub mod remote_cache;
pub mod remote_tasks;
+pub mod remote_updates;
pub mod resource_cache;
pub mod task_utils;
diff --git a/server/src/remote_updates.rs b/server/src/remote_updates.rs
new file mode 100644
index 00000000..f833062c
--- /dev/null
+++ b/server/src/remote_updates.rs
@@ -0,0 +1,89 @@
+use anyhow::{bail, Error};
+use pdm_api_types::RemoteUpid;
+
+use proxmox_apt_api_types::APTUpdateInfo;
+
+use pdm_api_types::remotes::{Remote, RemoteType};
+
+use crate::api::pve::new_remote_upid;
+use crate::connection;
+
+/// Return a list of available updates for a given remote node.
+pub async fn list_available_updates(
+ remote: Remote,
+ node: &str,
+) -> Result<Vec<APTUpdateInfo>, Error> {
+ let updates = fetch_available_updates(remote, node.to_string()).await?;
+ Ok(updates)
+}
+
+/// Trigger `apt update` on a remote node.
+///
+/// The function returns a `[RemoteUpid]` for the started update task.
+pub async fn update_apt_database(remote: &Remote, node: &str) -> Result<RemoteUpid, Error> {
+ match remote.ty {
+ RemoteType::Pve => {
+ let client = connection::make_pve_client(remote)?;
+
+ let params = pve_api_types::AptUpdateParams {
+ notify: Some(false),
+ quiet: Some(false),
+ };
+ let upid = client.update_apt_database(node, params).await?;
+
+ new_remote_upid(remote.id.clone(), upid).await
+ }
+ RemoteType::Pbs => bail!("PBS is not supported yet"),
+ }
+}
+
+/// Get the changelog for a given package.
+pub async fn get_changelog(remote: Remote, node: &str, package: String) -> Result<String, Error> {
+ match remote.ty {
+ RemoteType::Pve => {
+ let client = connection::make_pve_client(&remote)?;
+
+ client
+ .get_package_changelog(node, package, None)
+ .await
+ .map_err(Into::into)
+ }
+ RemoteType::Pbs => bail!("PBS is not supported yet"),
+ }
+}
+
+async fn fetch_available_updates(
+ remote: Remote,
+ node: String,
+) -> Result<Vec<APTUpdateInfo>, Error> {
+ match remote.ty {
+ RemoteType::Pve => {
+ let client = connection::make_pve_client(&remote)?;
+
+ let updates = client
+ .list_available_updates(&node)
+ .await?
+ .into_iter()
+ .map(map_pve_update_info)
+ .collect();
+
+ Ok(updates)
+ }
+ RemoteType::Pbs => bail!("PBS is not supported yet"),
+ }
+}
+
+fn map_pve_update_info(info: pve_api_types::AptUpdateInfo) -> APTUpdateInfo {
+ APTUpdateInfo {
+ package: info.package,
+ title: info.title,
+ arch: info.arch,
+ description: info.description,
+ version: info.version,
+ old_version: info.old_version.unwrap_or_default(),
+ origin: info.origin,
+ priority: info.priority,
+ section: info.section,
+ extra_info: None,
+ }
+}
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-datacenter-manager v2 3/5] ui: pve: promote node.rs to dir-style module
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (3 preceding siblings ...)
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 2/5] server: add api for getting available updates/changelogs for remote nodes Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 4/5] ui: pve: move node overview to a new overview tab Lukas Wagner
` (3 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This module will house the different tabs for various views (overview,
updates, etc.).
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
ui/src/pve/{node.rs => node/mod.rs} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename ui/src/pve/{node.rs => node/mod.rs} (100%)
diff --git a/ui/src/pve/node.rs b/ui/src/pve/node/mod.rs
similarity index 100%
rename from ui/src/pve/node.rs
rename to ui/src/pve/node/mod.rs
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-datacenter-manager v2 4/5] ui: pve: move node overview to a new overview tab
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (4 preceding siblings ...)
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 3/5] ui: pve: promote node.rs to dir-style module Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 5/5] ui: pve: node: add update tab Lukas Wagner
` (2 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This allows us add other tabs later.
No functional changes for the overview component, just moving code
around and adapting as needed to make it work in a tab panel.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
ui/src/pve/node/mod.rs | 320 ++------------------------------
ui/src/pve/node/overview.rs | 358 ++++++++++++++++++++++++++++++++++++
2 files changed, 376 insertions(+), 302 deletions(-)
create mode 100644 ui/src/pve/node/overview.rs
diff --git a/ui/src/pve/node/mod.rs b/ui/src/pve/node/mod.rs
index ff24f7ec..0190611b 100644
--- a/ui/src/pve/node/mod.rs
+++ b/ui/src/pve/node/mod.rs
@@ -5,20 +5,16 @@ use yew::{
Context,
};
-use proxmox_human_byte::HumanByte;
-use proxmox_yew_comp::{RRDGraph, RRDTimeframe, RRDTimeframeSelector, Series};
use pwt::{
- css::{AlignItems, ColorScheme, FlexFit, JustifyContent},
+ css::{AlignItems, ColorScheme},
prelude::*,
props::{ContainerBuilder, WidgetBuilder},
- widget::{error_message, Column, Container, Fa, Panel, Progress, Row},
- AsyncPool,
+ widget::{Fa, Row, TabBarItem, TabPanel},
};
-use pdm_api_types::rrddata::NodeDataPoint;
-use pdm_client::types::NodeStatus;
+mod overview;
-use crate::renderer::separator;
+use overview::NodeOverviewPanel;
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct NodePanel {
@@ -27,14 +23,6 @@ pub struct NodePanel {
/// The node to show
pub node: String,
-
- #[prop_or(60_000)]
- /// The interval for refreshing the rrd data
- pub rrd_interval: u32,
-
- #[prop_or(10_000)]
- /// The interval for refreshing the status data
- pub status_interval: u32,
}
impl NodePanel {
@@ -49,160 +37,20 @@ impl Into<VNode> for NodePanel {
}
}
-pub enum Msg {
- ReloadRrd,
- ReloadStatus,
- LoadFinished(Result<Vec<NodeDataPoint>, proxmox_client::Error>),
- StatusLoadFinished(Result<NodeStatus, proxmox_client::Error>),
- UpdateRrdTimeframe(RRDTimeframe),
-}
-
-pub struct NodePanelComp {
- time_data: Rc<Vec<i64>>,
- cpu_data: Rc<Series>,
- load_data: Rc<Series>,
- mem_data: Rc<Series>,
- mem_total_data: Rc<Series>,
- status: Option<NodeStatus>,
-
- rrd_time_frame: RRDTimeframe,
-
- last_error: Option<proxmox_client::Error>,
- last_status_error: Option<proxmox_client::Error>,
-
- async_pool: AsyncPool,
- _timeout: Option<gloo_timers::callback::Timeout>,
- _status_timeout: Option<gloo_timers::callback::Timeout>,
-}
-
-impl NodePanelComp {
- async fn reload_rrd(remote: &str, node: &str, rrd_time_frame: RRDTimeframe) -> Msg {
- let res = crate::pdm_client()
- .pve_node_rrddata(remote, node, rrd_time_frame.mode, rrd_time_frame.timeframe)
- .await;
-
- Msg::LoadFinished(res)
- }
-
- async fn reload_status(remote: &str, node: &str) -> Result<NodeStatus, proxmox_client::Error> {
- let status = crate::pdm_client().pve_node_status(remote, node).await?;
- Ok(status)
- }
-}
+pub struct NodePanelComp;
impl yew::Component for NodePanelComp {
- type Message = Msg;
+ type Message = ();
type Properties = NodePanel;
- fn create(ctx: &yew::Context<Self>) -> Self {
- ctx.link().send_message(Msg::ReloadRrd);
- ctx.link().send_message(Msg::ReloadStatus);
- Self {
- time_data: Rc::new(Vec::new()),
- cpu_data: Rc::new(Series::new("", Vec::new())),
- load_data: Rc::new(Series::new("", Vec::new())),
- mem_data: Rc::new(Series::new("", Vec::new())),
- mem_total_data: Rc::new(Series::new("", Vec::new())),
- rrd_time_frame: RRDTimeframe::load(),
- status: None,
- last_error: None,
- last_status_error: None,
- async_pool: AsyncPool::new(),
- _timeout: None,
- _status_timeout: None,
- }
- }
-
- fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
- match msg {
- Msg::ReloadRrd => {
- self._timeout = None;
- let props = ctx.props();
- let remote = props.remote.clone();
- let node = props.node.clone();
- let timeframe = self.rrd_time_frame;
- self.async_pool.send_future(ctx.link().clone(), async move {
- Self::reload_rrd(&remote, &node, timeframe).await
- });
- }
- Msg::ReloadStatus => {
- self._status_timeout = None;
- let props = ctx.props();
- let remote = props.remote.clone();
- let node = props.node.clone();
- self.async_pool.send_future(ctx.link().clone(), async move {
- let res = Self::reload_status(&remote, &node).await;
- Msg::StatusLoadFinished(res)
- });
- }
- Msg::LoadFinished(res) => match res {
- Ok(data_points) => {
- self.last_error = None;
- let mut cpu_vec = Vec::with_capacity(data_points.len());
- let mut load_vec = Vec::with_capacity(data_points.len());
- let mut mem_vec = Vec::with_capacity(data_points.len());
- let mut mem_total_vec = Vec::with_capacity(data_points.len());
- let mut time_vec = Vec::with_capacity(data_points.len());
- for data in data_points {
- cpu_vec.push(data.cpu_current.unwrap_or(f64::NAN));
- load_vec.push(data.cpu_avg1.unwrap_or(f64::NAN));
- mem_vec.push(data.mem_used.unwrap_or(f64::NAN));
- mem_total_vec.push(data.mem_total.unwrap_or(f64::NAN));
- time_vec.push(data.time as i64);
- }
-
- self.cpu_data = Rc::new(Series::new(tr!("CPU"), cpu_vec));
- self.load_data = Rc::new(Series::new(tr!("Server Load"), load_vec));
- self.mem_data = Rc::new(Series::new(tr!("Used Memory"), mem_vec));
- self.mem_total_data = Rc::new(Series::new(tr!("Total Memory"), mem_total_vec));
- self.time_data = Rc::new(time_vec);
-
- let link = ctx.link().clone();
- self._timeout = Some(gloo_timers::callback::Timeout::new(
- ctx.props().rrd_interval,
- move || link.send_message(Msg::ReloadRrd),
- ))
- }
- Err(err) => self.last_error = Some(err),
- },
- Msg::StatusLoadFinished(res) => {
- match res {
- Ok(status) => {
- self.last_status_error = None;
- self.status = Some(status);
- }
- Err(err) => self.last_status_error = Some(err),
- }
- let link = ctx.link().clone();
- self._status_timeout = Some(gloo_timers::callback::Timeout::new(
- ctx.props().status_interval,
- move || link.send_message(Msg::ReloadStatus),
- ))
- }
- Msg::UpdateRrdTimeframe(rrd_time_frame) => {
- self.rrd_time_frame = rrd_time_frame;
- ctx.link().send_message(Msg::ReloadRrd);
- return false;
- }
- }
- true
+ fn create(_ctx: &yew::Context<Self>) -> Self {
+ Self
}
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
let props = ctx.props();
if props.remote != old_props.remote || props.node != old_props.node {
- self.status = None;
- self.last_status_error = None;
- self.last_error = None;
- self.time_data = Rc::new(Vec::new());
- self.cpu_data = Rc::new(Series::new("", Vec::new()));
- self.load_data = Rc::new(Series::new("", Vec::new()));
- self.mem_data = Rc::new(Series::new("", Vec::new()));
- self.mem_total_data = Rc::new(Series::new("", Vec::new()));
- self.async_pool = AsyncPool::new();
- ctx.link()
- .send_message_batch(vec![Msg::ReloadRrd, Msg::ReloadStatus]);
true
} else {
false
@@ -210,7 +58,8 @@ impl yew::Component for NodePanelComp {
}
fn view(&self, ctx: &yew::Context<Self>) -> yew::Html {
- let props = ctx.props();
+ let props = ctx.props().clone();
+
let title: Html = Row::new()
.gap(2)
.class(AlignItems::Baseline)
@@ -218,150 +67,17 @@ impl yew::Component for NodePanelComp {
.with_child(tr! {"Node '{0}'", props.node})
.into();
- let mut status_comp = Column::new().gap(2).padding(4);
- let status = self.status.as_ref();
- let cpu = status.map(|s| s.cpu).unwrap_or_default();
- let maxcpu = status.map(|s| s.cpuinfo.cpus).unwrap_or_default();
- let load = status.map(|s| s.loadavg.join(", ")).unwrap_or_default();
-
- let memory = status.map(|s| s.memory.used as u64).unwrap_or_default();
- let maxmem = status.map(|s| s.memory.total as u64).unwrap_or(1);
-
- let root = status.map(|s| s.rootfs.used as u64).unwrap_or_default();
- let maxroot = status.map(|s| s.rootfs.total as u64).unwrap_or(1);
-
- let memory_used = memory as f64 / maxmem as f64;
- let root_used = root as f64 / maxroot as f64;
-
- status_comp = status_comp
- .with_child(make_row(
- tr!("CPU usage"),
- Fa::new("cpu"),
- tr!("{0}% of {1} CPU(s)", format!("{:.2}", cpu * 100.0), maxcpu),
- Some(cpu as f32),
- ))
- .with_child(make_row(
- tr!("Load average"),
- Fa::new("line-chart"),
- load,
- None,
- ))
- .with_child(make_row(
- tr!("Memory usage"),
- Fa::new("memory"),
- tr!(
- "{0}% ({1} of {2})",
- format!("{:.2}", memory_used * 100.0),
- HumanByte::from(memory),
- HumanByte::from(maxmem),
- ),
- Some(memory_used as f32),
- ))
- .with_child(make_row(
- tr!("Root filesystem usage"),
- Fa::new("database"),
- tr!(
- "{0}% ({1} of {2})",
- format!("{:.2}", root_used * 100.0),
- HumanByte::from(root),
- HumanByte::from(maxroot),
- ),
- Some(root_used as f32),
- ))
- .with_child(Container::new().padding(1)) // spacer
- .with_child(
- Row::new()
- .with_child(tr!("Version"))
- .with_flex_spacer()
- .with_optional_child(status.map(|s| s.pveversion.as_str())),
- )
- .with_child(
- Row::new()
- .with_child(tr!("CPU Model"))
- .with_flex_spacer()
- .with_child(tr!(
- "{0} ({1} sockets)",
- status.map(|s| s.cpuinfo.model.as_str()).unwrap_or_default(),
- status.map(|s| s.cpuinfo.sockets).unwrap_or_default()
- )),
- );
-
- if let Some(err) = &self.last_status_error {
- status_comp.add_child(error_message(&err.to_string()));
- }
-
- let loading = self.status.is_none() && self.last_status_error.is_none();
- Panel::new()
- .class(FlexFit)
+ TabPanel::new()
+ .class(pwt::css::FlexFit)
.title(title)
.class(ColorScheme::Neutral)
- .with_child(
- // FIXME: add some 'visible' or 'active' property to the progress
- Progress::new()
- .value((!loading).then_some(0.0))
- .style("opacity", (!loading).then_some("0")),
- )
- .with_child(status_comp)
- .with_child(separator().padding_x(4))
- .with_child(
- Row::new()
- .padding_x(4)
- .padding_y(1)
- .class(JustifyContent::FlexEnd)
- .with_child(
- RRDTimeframeSelector::new()
- .on_change(ctx.link().callback(Msg::UpdateRrdTimeframe)),
- ),
- )
- .with_child(
- Container::new().class(FlexFit).with_child(
- Column::new()
- .padding(4)
- .gap(4)
- .with_child(
- RRDGraph::new(self.time_data.clone())
- .title(tr!("CPU Usage"))
- .render_value(|v: &f64| {
- if v.is_finite() {
- format!("{:.2}%", v * 100.0)
- } else {
- v.to_string()
- }
- })
- .serie0(Some(self.cpu_data.clone())),
- )
- .with_child(
- RRDGraph::new(self.time_data.clone())
- .title(tr!("Server load"))
- .render_value(|v: &f64| {
- if v.is_finite() {
- format!("{:.2}", v)
- } else {
- v.to_string()
- }
- })
- .serie0(Some(self.load_data.clone())),
- )
- .with_child(
- RRDGraph::new(self.time_data.clone())
- .title(tr!("Memory Usage"))
- .binary(true)
- .render_value(|v: &f64| {
- if v.is_finite() {
- proxmox_human_byte::HumanByte::from(*v as u64).to_string()
- } else {
- v.to_string()
- }
- })
- .serie0(Some(self.mem_total_data.clone()))
- .serie1(Some(self.mem_data.clone())),
- ),
- ),
+ .with_item_builder(
+ TabBarItem::new()
+ .key("status_view")
+ .label(tr!("Overview"))
+ .icon_class("fa fa-tachometer"),
+ move |_| NodeOverviewPanel::new(props.remote.clone(), props.node.clone()).into(),
)
.into()
}
}
-
-fn make_row(title: String, icon: Fa, text: String, meter_value: Option<f32>) -> Column {
- crate::renderer::status_row(title, icon, text, meter_value, false)
-}
diff --git a/ui/src/pve/node/overview.rs b/ui/src/pve/node/overview.rs
new file mode 100644
index 00000000..9ebc3e1d
--- /dev/null
+++ b/ui/src/pve/node/overview.rs
@@ -0,0 +1,358 @@
+use std::rc::Rc;
+
+use yew::{
+ virtual_dom::{VComp, VNode},
+ Context,
+};
+
+use proxmox_human_byte::HumanByte;
+use proxmox_yew_comp::{RRDGraph, RRDTimeframe, RRDTimeframeSelector, Series};
+use pwt::{
+ css::{ColorScheme, FlexFit, JustifyContent},
+ prelude::*,
+ props::{ContainerBuilder, WidgetBuilder},
+ widget::{error_message, Column, Container, Fa, Progress, Row},
+ AsyncPool,
+};
+
+use pdm_api_types::rrddata::NodeDataPoint;
+use pdm_client::types::NodeStatus;
+
+use crate::renderer::separator;
+
+#[derive(Clone, Debug, Eq, PartialEq, Properties)]
+pub struct NodeOverviewPanel {
+ /// The remote to show
+ pub remote: String,
+
+ /// The node to show
+ pub node: String,
+
+ #[prop_or(60_000)]
+ /// The interval for refreshing the rrd data
+ pub rrd_interval: u32,
+
+ #[prop_or(10_000)]
+ /// The interval for refreshing the status data
+ pub status_interval: u32,
+}
+
+impl NodeOverviewPanel {
+ pub fn new(remote: String, node: String) -> Self {
+ yew::props!(Self { remote, node })
+ }
+}
+
+impl Into<VNode> for NodeOverviewPanel {
+ fn into(self) -> VNode {
+ VComp::new::<NodeOverviewPanelComp>(Rc::new(self), None).into()
+ }
+}
+
+pub enum Msg {
+ ReloadRrd,
+ ReloadStatus,
+ LoadFinished(Result<Vec<NodeDataPoint>, proxmox_client::Error>),
+ StatusLoadFinished(Result<NodeStatus, proxmox_client::Error>),
+ UpdateRrdTimeframe(RRDTimeframe),
+}
+
+pub struct NodeOverviewPanelComp {
+ time_data: Rc<Vec<i64>>,
+ cpu_data: Rc<Series>,
+ load_data: Rc<Series>,
+ mem_data: Rc<Series>,
+ mem_total_data: Rc<Series>,
+ status: Option<NodeStatus>,
+
+ rrd_time_frame: RRDTimeframe,
+
+ last_error: Option<proxmox_client::Error>,
+ last_status_error: Option<proxmox_client::Error>,
+
+ async_pool: AsyncPool,
+ _timeout: Option<gloo_timers::callback::Timeout>,
+ _status_timeout: Option<gloo_timers::callback::Timeout>,
+}
+
+impl NodeOverviewPanelComp {
+ async fn reload_rrd(remote: &str, node: &str, rrd_time_frame: RRDTimeframe) -> Msg {
+ let res = crate::pdm_client()
+ .pve_node_rrddata(remote, node, rrd_time_frame.mode, rrd_time_frame.timeframe)
+ .await;
+
+ Msg::LoadFinished(res)
+ }
+
+ async fn reload_status(remote: &str, node: &str) -> Result<NodeStatus, proxmox_client::Error> {
+ let status = crate::pdm_client().pve_node_status(remote, node).await?;
+ Ok(status)
+ }
+}
+
+impl yew::Component for NodeOverviewPanelComp {
+ type Message = Msg;
+ type Properties = NodeOverviewPanel;
+
+ fn create(ctx: &yew::Context<Self>) -> Self {
+ ctx.link().send_message(Msg::ReloadRrd);
+ ctx.link().send_message(Msg::ReloadStatus);
+ Self {
+ time_data: Rc::new(Vec::new()),
+ cpu_data: Rc::new(Series::new("", Vec::new())),
+ load_data: Rc::new(Series::new("", Vec::new())),
+ mem_data: Rc::new(Series::new("", Vec::new())),
+ mem_total_data: Rc::new(Series::new("", Vec::new())),
+ rrd_time_frame: RRDTimeframe::load(),
+ status: None,
+ last_error: None,
+ last_status_error: None,
+ async_pool: AsyncPool::new(),
+ _timeout: None,
+ _status_timeout: None,
+ }
+ }
+
+ fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
+ match msg {
+ Msg::ReloadRrd => {
+ self._timeout = None;
+ let props = ctx.props();
+ let remote = props.remote.clone();
+ let node = props.node.clone();
+ let timeframe = self.rrd_time_frame;
+ self.async_pool.send_future(ctx.link().clone(), async move {
+ Self::reload_rrd(&remote, &node, timeframe).await
+ });
+ }
+ Msg::ReloadStatus => {
+ self._status_timeout = None;
+ let props = ctx.props();
+ let remote = props.remote.clone();
+ let node = props.node.clone();
+ self.async_pool.send_future(ctx.link().clone(), async move {
+ let res = Self::reload_status(&remote, &node).await;
+ Msg::StatusLoadFinished(res)
+ });
+ }
+ Msg::LoadFinished(res) => match res {
+ Ok(data_points) => {
+ self.last_error = None;
+ let mut cpu_vec = Vec::with_capacity(data_points.len());
+ let mut load_vec = Vec::with_capacity(data_points.len());
+ let mut mem_vec = Vec::with_capacity(data_points.len());
+ let mut mem_total_vec = Vec::with_capacity(data_points.len());
+ let mut time_vec = Vec::with_capacity(data_points.len());
+ for data in data_points {
+ cpu_vec.push(data.cpu_current.unwrap_or(f64::NAN));
+ load_vec.push(data.cpu_avg1.unwrap_or(f64::NAN));
+ mem_vec.push(data.mem_used.unwrap_or(f64::NAN));
+ mem_total_vec.push(data.mem_total.unwrap_or(f64::NAN));
+ time_vec.push(data.time as i64);
+ }
+
+ self.cpu_data = Rc::new(Series::new(tr!("CPU"), cpu_vec));
+ self.load_data = Rc::new(Series::new(tr!("Server Load"), load_vec));
+ self.mem_data = Rc::new(Series::new(tr!("Used Memory"), mem_vec));
+ self.mem_total_data = Rc::new(Series::new(tr!("Total Memory"), mem_total_vec));
+ self.time_data = Rc::new(time_vec);
+
+ let link = ctx.link().clone();
+ self._timeout = Some(gloo_timers::callback::Timeout::new(
+ ctx.props().rrd_interval,
+ move || link.send_message(Msg::ReloadRrd),
+ ))
+ }
+ Err(err) => self.last_error = Some(err),
+ },
+ Msg::StatusLoadFinished(res) => {
+ match res {
+ Ok(status) => {
+ self.last_status_error = None;
+ self.status = Some(status);
+ }
+ Err(err) => self.last_status_error = Some(err),
+ }
+ let link = ctx.link().clone();
+ self._status_timeout = Some(gloo_timers::callback::Timeout::new(
+ ctx.props().status_interval,
+ move || link.send_message(Msg::ReloadStatus),
+ ))
+ }
+ Msg::UpdateRrdTimeframe(rrd_time_frame) => {
+ self.rrd_time_frame = rrd_time_frame;
+ ctx.link().send_message(Msg::ReloadRrd);
+ return false;
+ }
+ }
+ true
+ }
+
+ fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
+ let props = ctx.props();
+
+ if props.remote != old_props.remote || props.node != old_props.node {
+ self.status = None;
+ self.last_status_error = None;
+ self.last_error = None;
+ self.time_data = Rc::new(Vec::new());
+ self.cpu_data = Rc::new(Series::new("", Vec::new()));
+ self.load_data = Rc::new(Series::new("", Vec::new()));
+ self.mem_data = Rc::new(Series::new("", Vec::new()));
+ self.mem_total_data = Rc::new(Series::new("", Vec::new()));
+ self.async_pool = AsyncPool::new();
+ ctx.link()
+ .send_message_batch(vec![Msg::ReloadRrd, Msg::ReloadStatus]);
+ true
+ } else {
+ false
+ }
+ }
+
+ fn view(&self, ctx: &yew::Context<Self>) -> yew::Html {
+ let mut status_comp = Column::new().gap(2).padding(4);
+ let status = self.status.as_ref();
+ let cpu = status.map(|s| s.cpu).unwrap_or_default();
+ let maxcpu = status.map(|s| s.cpuinfo.cpus).unwrap_or_default();
+ let load = status.map(|s| s.loadavg.join(", ")).unwrap_or_default();
+
+ let memory = status.map(|s| s.memory.used as u64).unwrap_or_default();
+ let maxmem = status.map(|s| s.memory.total as u64).unwrap_or(1);
+
+ let root = status.map(|s| s.rootfs.used as u64).unwrap_or_default();
+ let maxroot = status.map(|s| s.rootfs.total as u64).unwrap_or(1);
+
+ let memory_used = memory as f64 / maxmem as f64;
+ let root_used = root as f64 / maxroot as f64;
+
+ status_comp = status_comp
+ .with_child(make_row(
+ tr!("CPU usage"),
+ Fa::new("cpu"),
+ tr!("{0}% of {1} CPU(s)", format!("{:.2}", cpu * 100.0), maxcpu),
+ Some(cpu as f32),
+ ))
+ .with_child(make_row(
+ tr!("Load average"),
+ Fa::new("line-chart"),
+ load,
+ None,
+ ))
+ .with_child(make_row(
+ tr!("Memory usage"),
+ Fa::new("memory"),
+ tr!(
+ "{0}% ({1} of {2})",
+ format!("{:.2}", memory_used * 100.0),
+ HumanByte::from(memory),
+ HumanByte::from(maxmem),
+ ),
+ Some(memory_used as f32),
+ ))
+ .with_child(make_row(
+ tr!("Root filesystem usage"),
+ Fa::new("database"),
+ tr!(
+ "{0}% ({1} of {2})",
+ format!("{:.2}", root_used * 100.0),
+ HumanByte::from(root),
+ HumanByte::from(maxroot),
+ ),
+ Some(root_used as f32),
+ ))
+ .with_child(Container::new().padding(1)) // spacer
+ .with_child(
+ Row::new()
+ .with_child(tr!("Version"))
+ .with_flex_spacer()
+ .with_optional_child(status.map(|s| s.pveversion.as_str())),
+ )
+ .with_child(
+ Row::new()
+ .with_child(tr!("CPU Model"))
+ .with_flex_spacer()
+ .with_child(tr!(
+ "{0} ({1} sockets)",
+ status.map(|s| s.cpuinfo.model.as_str()).unwrap_or_default(),
+ status.map(|s| s.cpuinfo.sockets).unwrap_or_default()
+ )),
+ );
+
+ if let Some(err) = &self.last_status_error {
+ status_comp.add_child(error_message(&err.to_string()));
+ }
+
+ let loading = self.status.is_none() && self.last_status_error.is_none();
+ Container::new()
+ .class(FlexFit)
+ .class(ColorScheme::Neutral)
+ .with_child(
+ // FIXME: add some 'visible' or 'active' property to the progress
+ Progress::new()
+ .value((!loading).then_some(0.0))
+ .style("opacity", (!loading).then_some("0")),
+ )
+ .with_child(status_comp)
+ .with_child(separator().padding_x(4))
+ .with_child(
+ Row::new()
+ .padding_x(4)
+ .padding_y(1)
+ .class(JustifyContent::FlexEnd)
+ .with_child(
+ RRDTimeframeSelector::new()
+ .on_change(ctx.link().callback(Msg::UpdateRrdTimeframe)),
+ ),
+ )
+ .with_child(
+ Container::new().class(FlexFit).with_child(
+ Column::new()
+ .padding(4)
+ .gap(4)
+ .with_child(
+ RRDGraph::new(self.time_data.clone())
+ .title(tr!("CPU Usage"))
+ .render_value(|v: &f64| {
+ if v.is_finite() {
+ format!("{:.2}%", v * 100.0)
+ } else {
+ v.to_string()
+ }
+ })
+ .serie0(Some(self.cpu_data.clone())),
+ )
+ .with_child(
+ RRDGraph::new(self.time_data.clone())
+ .title(tr!("Server load"))
+ .render_value(|v: &f64| {
+ if v.is_finite() {
+ format!("{:.2}", v)
+ } else {
+ v.to_string()
+ }
+ })
+ .serie0(Some(self.load_data.clone())),
+ )
+ .with_child(
+ RRDGraph::new(self.time_data.clone())
+ .title(tr!("Memory Usage"))
+ .binary(true)
+ .render_value(|v: &f64| {
+ if v.is_finite() {
+ proxmox_human_byte::HumanByte::from(*v as u64).to_string()
+ } else {
+ v.to_string()
+ }
+ })
+ .serie0(Some(self.mem_total_data.clone()))
+ .serie1(Some(self.mem_data.clone())),
+ ),
+ ),
+ )
+ .into()
+ }
+}
+
+fn make_row(title: String, icon: Fa, text: String, meter_value: Option<f32>) -> Column {
+ crate::renderer::status_row(title, icon, text, meter_value, false)
+}
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] [PATCH proxmox-datacenter-manager v2 5/5] ui: pve: node: add update tab
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (5 preceding siblings ...)
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 4/5] ui: pve: move node overview to a new overview tab Lukas Wagner
@ 2025-09-03 11:41 ` Lukas Wagner
2025-09-04 9:30 ` [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Dominik Csapak
2025-09-04 12:01 ` [pdm-devel] applied: " Dominik Csapak
8 siblings, 0 replies; 11+ messages in thread
From: Lukas Wagner @ 2025-09-03 11:41 UTC (permalink / raw)
To: pdm-devel
This adds a new tab for PVE nodes where available updates are listed.
The 'Upgrade' button is disabled for now, this needs additional work.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Reviewed-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
ui/src/pve/node/mod.rs | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/ui/src/pve/node/mod.rs b/ui/src/pve/node/mod.rs
index 0190611b..2ef2aaea 100644
--- a/ui/src/pve/node/mod.rs
+++ b/ui/src/pve/node/mod.rs
@@ -1,5 +1,6 @@
use std::rc::Rc;
+use proxmox_yew_comp::AptPackageManager;
use yew::{
virtual_dom::{VComp, VNode},
Context,
@@ -67,6 +68,9 @@ impl yew::Component for NodePanelComp {
.with_child(tr! {"Node '{0}'", props.node})
.into();
+ let remote = props.remote.clone();
+ let node = props.node.clone();
+
TabPanel::new()
.class(pwt::css::FlexFit)
.title(title)
@@ -78,6 +82,22 @@ impl yew::Component for NodePanelComp {
.icon_class("fa fa-tachometer"),
move |_| NodeOverviewPanel::new(props.remote.clone(), props.node.clone()).into(),
)
+ .with_item_builder(
+ TabBarItem::new()
+ .key("update_view")
+ .label(tr!("Updates"))
+ .icon_class("fa fa-refresh"),
+ move |_| {
+ let base_url = format!("/pve/remotes/{remote}/nodes/{node}/apt");
+ let task_base_url = format!("/pve/remotes/{remote}/tasks");
+
+ AptPackageManager::new()
+ .base_url(base_url)
+ .task_base_url(task_base_url)
+ .enable_upgrade(false)
+ .into()
+ },
+ )
.into()
}
}
--
2.47.2
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (6 preceding siblings ...)
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 5/5] ui: pve: node: add update tab Lukas Wagner
@ 2025-09-04 9:30 ` Dominik Csapak
2025-09-04 9:57 ` Thomas Lamprecht
2025-09-04 12:01 ` [pdm-devel] applied: " Dominik Csapak
8 siblings, 1 reply; 11+ messages in thread
From: Dominik Csapak @ 2025-09-04 9:30 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion, Lukas Wagner,
Thomas Lamprecht
in general the code and ui LGTM (with some minor cleanup potential)
the only thing irking me is that the upgrade button is disabled
without further explanation for the user.
@Thomas
i would apply this, and send some follow ups,
especially one where we overwrite the upgrade button
to open the nodes upgrade panel on the pve ui in a new tab for now
until we can tunnel the websocket and show it locally, what do you say?
alternatively, we can hide the button or the whole taskbar, but i would
not simply disable the upgrade button without any explanation or
way to do it...
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view
2025-09-04 9:30 ` [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Dominik Csapak
@ 2025-09-04 9:57 ` Thomas Lamprecht
0 siblings, 0 replies; 11+ messages in thread
From: Thomas Lamprecht @ 2025-09-04 9:57 UTC (permalink / raw)
To: Dominik Csapak,
Proxmox Datacenter Manager development discussion, Lukas Wagner
Am 04.09.25 um 11:30 schrieb Dominik Csapak:
> in general the code and ui LGTM (with some minor cleanup potential)
>
> the only thing irking me is that the upgrade button is disabled
> without further explanation for the user.
>
> @Thomas
> i would apply this, and send some follow ups,
> especially one where we overwrite the upgrade button
> to open the nodes upgrade panel on the pve ui in a new tab for now
> until we can tunnel the websocket and show it locally, what do you say?
That's exactly what I talked with Lukas yesterday and indeed an OK
stop gap. FWIW, we also briefly discussed the option of directly
opening the target remotes xterm.js upgrade console, potentially
providing both options (e.g. as split button).
> alternatively, we can hide the button or the whole taskbar, but i would
> not simply disable the upgrade button without any explanation or
> way to do it...
>
No, some way to navigate to the target remote would be definitively
good to have.
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [pdm-devel] applied: [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
` (7 preceding siblings ...)
2025-09-04 9:30 ` [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Dominik Csapak
@ 2025-09-04 12:01 ` Dominik Csapak
8 siblings, 0 replies; 11+ messages in thread
From: Dominik Csapak @ 2025-09-04 12:01 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion, Lukas Wagner
On 9/3/25 1:41 PM, Lukas Wagner wrote:
> This series adds a new 'Updates' tab for PVE remotes. The existing status
> overview is moved to a new 'Overview' tab, which is visible by default.
>
> On the backend side, we add a couple new API endpoints, which simply pass
> through the request to the PVE nodes, no caching for now.
>
> GET /pve/remotes/{remote}/nodes/{node}/apt
> Get list of updatable packages
> GET /pve/remotes/{remote}/nodes/{node}/changelog
> Get list of changelog of package
> POST /pve/remotes/{remote}/nodes/{node}/apt
> Update APT package database
>
> In terms of permissions, these new API endpoints require RESOURCE_MODIFY privs on
> /resource/{remote}/node/{node}/system
>
> This was the result of a short discussion in the development chat room.
>
> The existing APT view component is a bit large for this panel, maybe we could
> hide the package description by default (but not too important for now).
>
> Future work (some backend work already started, but can't finish before my
> vacation):
>
> - "Global Update" view that lists update status of all remote nodes
> - Cache update status per node (absolutely necessary for the 'global' view),
> with a task refreshing the update status every couple of hours
> - Maybe send a notification about the global update availabilty (require notification
> stack integration first)
> - Add new API functions to pdm-client crate and CLI
> - Allow package upgrade (requires web socket proxying, as far as I can see,
> haven't really looked into it much)
>
> Changes since v1:
> - consistently return errors for PBS remotes
> - drop already applied patches
>
> Some of the notes from Stefan's review notes were not addressed, see my replies
> for v1 for details.
>
>
> proxmox-yew-comp:
>
> Lukas Wagner (2):
> apt view: allow to set task_base_url
> apt view: reload if base urls have changed
>
> src/apt_package_manager.rs | 23 +++++++++++++++++++++++
> 1 file changed, 23 insertions(+)
>
>
> proxmox-datacenter-manager:
>
> Lukas Wagner (5):
> update proxmox-api-types submodule
> server: add api for getting available updates/changelogs for remote
> nodes
> ui: pve: promote node.rs to dir-style module
> ui: pve: move node overview to a new overview tab
> ui: pve: node: add update tab
>
> lib/proxmox-api-types | 2 +-
> server/src/api/pve/apt.rs | 119 +++++++++++++++++++++++
> server/src/api/pve/mod.rs | 3 +-
> server/src/api/pve/node.rs | 1 +
> server/src/lib.rs | 1 +
> server/src/remote_updates.rs | 89 +++++++++++++++++
> ui/src/pve/node/mod.rs | 103 ++++++++++++++++++++
> ui/src/pve/{node.rs => node/overview.rs} | 31 +++---
> 8 files changed, 327 insertions(+), 22 deletions(-)
> create mode 100644 server/src/api/pve/apt.rs
> create mode 100644 server/src/remote_updates.rs
> create mode 100644 ui/src/pve/node/mod.rs
> rename ui/src/pve/{node.rs => node/overview.rs} (95%)
>
>
> Summary over all repositories:
> 9 files changed, 350 insertions(+), 22 deletions(-)
>
applied, thanks!
i pushed some follow ups like discussed. we still need to bump yew-comp
and update the dependency
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-09-04 12:01 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-03 11:41 [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 1/2] apt view: allow to set task_base_url Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-yew-comp v2 2/2] apt view: reload if base urls have changed Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 1/5] update proxmox-api-types submodule Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 2/5] server: add api for getting available updates/changelogs for remote nodes Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 3/5] ui: pve: promote node.rs to dir-style module Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 4/5] ui: pve: move node overview to a new overview tab Lukas Wagner
2025-09-03 11:41 ` [pdm-devel] [PATCH proxmox-datacenter-manager v2 5/5] ui: pve: node: add update tab Lukas Wagner
2025-09-04 9:30 ` [pdm-devel] [PATCH proxmox{-yew-comp, -datacenter-manager} v2 0/7] PVE node update view Dominik Csapak
2025-09-04 9:57 ` Thomas Lamprecht
2025-09-04 12:01 ` [pdm-devel] applied: " Dominik Csapak
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.