public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager
@ 2025-11-06 12:43 Shannon Sterz
  2025-11-06 12:43 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

this series adds a node status panel as a widget for a view to proxmox
datacenter manager. it allows for getting a rough overview of system
load as well as accessing the node's fingerprint and rebooting and
powering off the node.

the fist patch moves the api endpoints from proxmox-backup server to
their own proxmox-rs crate. the next two commits extend yew-comp to
allow implementing a node status panel of the newly extracted api
endpoints return types.

the next three commits first add the new api endpoints to proxmox
datacenter manager, add the ui panel as a widget type and remove a
suproflous macro.

Changelog
---------

changes since v1:

- move the node status panel to its own widget type in pdm
- properly import api feature (thanks @ Dominik Csapak)
- smaller clean ups (thanks @ Dominik Csapak)

proxmox:

Shannon Sterz (1):
  node-status: add node status crate

 Cargo.toml                               |   1 +
 proxmox-node-status/Cargo.toml           |  37 +++++
 proxmox-node-status/debian/changelog     |   5 +
 proxmox-node-status/debian/control       |  65 ++++++++
 proxmox-node-status/debian/copyright     |  18 +++
 proxmox-node-status/debian/debcargo.toml |   7 +
 proxmox-node-status/src/api.rs           | 184 +++++++++++++++++++++++
 proxmox-node-status/src/lib.rs           |  11 ++
 proxmox-node-status/src/types.rs         | 184 +++++++++++++++++++++++
 9 files changed, 512 insertions(+)
 create mode 100644 proxmox-node-status/Cargo.toml
 create mode 100644 proxmox-node-status/debian/changelog
 create mode 100644 proxmox-node-status/debian/control
 create mode 100644 proxmox-node-status/debian/copyright
 create mode 100644 proxmox-node-status/debian/debcargo.toml
 create mode 100644 proxmox-node-status/src/api.rs
 create mode 100644 proxmox-node-status/src/lib.rs
 create mode 100644 proxmox-node-status/src/types.rs


proxmox-yew-comp:

Shannon Sterz (2):
  node info: extend NodeStatus enum to include NodeStatus from
    proxmox-rs
  node status panel: add a panel that show the current status of a node

 Cargo.toml               |   1 +
 src/lib.rs               |   3 +
 src/node_info.rs         |  38 ++++++
 src/node_status_panel.rs | 246 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 288 insertions(+)
 create mode 100644 src/node_status_panel.rs


proxmox-datacenter-manager:

Shannon Sterz (3):
  api-types/api: add endpoints for querying the node's status
  ui: add NodeStatusPanel to the administration menu
  nodes: remove unnecessary rustfmt::skip macro

 Cargo.toml                                    |  2 ++
 lib/pdm-api-types/src/acl.rs                  |  2 ++
 server/Cargo.toml                             |  1 +
 server/src/api/nodes/mod.rs                   |  3 ++-
 server/src/api/nodes/status.rs                | 18 ++++++++++++++++++
 server/src/bin/proxmox-datacenter-api/main.rs |  2 ++
 ui/src/administration/mod.rs                  |  4 +++-
 ui/src/dashboard/types.rs                     |  1 +
 ui/src/dashboard/view.rs                      | 15 ++++++++++++++-
 9 files changed, 45 insertions(+), 3 deletions(-)
 create mode 100644 server/src/api/nodes/status.rs


Summary over all repositories:
  22 files changed, 845 insertions(+), 3 deletions(-)

--
Generated by git-murpp 0.8.1


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 19:41   ` [pdm-devel] applied: " Thomas Lamprecht
  2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

this includes api endpoints for querying api endpoints. the original
implementation was factored out from proxmox-backup.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
---
 Cargo.toml                               |   1 +
 proxmox-node-status/Cargo.toml           |  37 +++++
 proxmox-node-status/debian/changelog     |   5 +
 proxmox-node-status/debian/control       |  65 ++++++++
 proxmox-node-status/debian/copyright     |  18 +++
 proxmox-node-status/debian/debcargo.toml |   7 +
 proxmox-node-status/src/api.rs           | 184 +++++++++++++++++++++++
 proxmox-node-status/src/lib.rs           |  11 ++
 proxmox-node-status/src/types.rs         | 184 +++++++++++++++++++++++
 9 files changed, 512 insertions(+)
 create mode 100644 proxmox-node-status/Cargo.toml
 create mode 100644 proxmox-node-status/debian/changelog
 create mode 100644 proxmox-node-status/debian/control
 create mode 100644 proxmox-node-status/debian/copyright
 create mode 100644 proxmox-node-status/debian/debcargo.toml
 create mode 100644 proxmox-node-status/src/api.rs
 create mode 100644 proxmox-node-status/src/lib.rs
 create mode 100644 proxmox-node-status/src/types.rs

diff --git a/Cargo.toml b/Cargo.toml
index 1330fe97..37e8696c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ members = [
     "proxmox-metrics",
     "proxmox-network-api",
     "proxmox-network-types",
+    "proxmox-node-status",
     "proxmox-notify",
     "proxmox-openid",
     "proxmox-product-config",
diff --git a/proxmox-node-status/Cargo.toml b/proxmox-node-status/Cargo.toml
new file mode 100644
index 00000000..2976d6a0
--- /dev/null
+++ b/proxmox-node-status/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "proxmox-node-status"
+description = "API implementation and types for querying a nodes status."
+version = "1.0.0"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+homepage.workspace = true
+exclude.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+anyhow = { workspace = true, optional = true }
+hex = { workspace = true, optional = true }
+nix = { workspace = true, optional = true }
+openssl = { workspace = true, optional = true }
+serde = { workspace = true, features = [ "derive" ] }
+serde_json.workspace = true
+tokio = { workspace = true, optional = true }
+
+proxmox-router = { workspace = true, optional = true }
+proxmox-schema = { workspace = true, features = [ "api-macro", "api-types" ] }
+proxmox-sys = { workspace = true, optional = true }
+
+[features]
+default = []
+api = [
+    "dep:anyhow",
+    "dep:hex",
+    "dep:nix",
+    "dep:openssl",
+    "dep:proxmox-router",
+    "dep:proxmox-sys",
+    "dep:tokio"
+]
diff --git a/proxmox-node-status/debian/changelog b/proxmox-node-status/debian/changelog
new file mode 100644
index 00000000..a7050f31
--- /dev/null
+++ b/proxmox-node-status/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-node-status (1.0.0-1) trixie; urgency=medium
+
+  * Initial packaging
+
+ -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Oct 2025 14:44:26 +0200
diff --git a/proxmox-node-status/debian/control b/proxmox-node-status/debian/control
new file mode 100644
index 00000000..48067be8
--- /dev/null
+++ b/proxmox-node-status/debian/control
@@ -0,0 +1,65 @@
+Source: rust-proxmox-node-status
+Section: rust
+Priority: optional
+Build-Depends: debhelper-compat (= 13),
+ dh-sequence-cargo
+Build-Depends-Arch: cargo:native <!nocheck>,
+ rustc:native (>= 1.82) <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-proxmox-schema-5+api-macro-dev <!nocheck>,
+ librust-proxmox-schema-5+api-types-dev <!nocheck>,
+ librust-proxmox-schema-5+default-dev <!nocheck>,
+ librust-serde-1+default-dev <!nocheck>,
+ librust-serde-1+derive-dev <!nocheck>,
+ librust-serde-json-1+default-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.7.2
+Vcs-Git: git://git.proxmox.com/git/proxmox.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
+Homepage: https://proxmox.com
+X-Cargo-Crate: proxmox-node-status
+
+Package: librust-proxmox-node-status-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-proxmox-schema-5+api-macro-dev,
+ librust-proxmox-schema-5+api-types-dev,
+ librust-proxmox-schema-5+default-dev,
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev,
+ librust-serde-json-1+default-dev
+Suggests:
+ librust-proxmox-node-status+api-dev (= ${binary:Version})
+Provides:
+ librust-proxmox-node-status+default-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1+default-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0+default-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0.0-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0.0+default-dev (= ${binary:Version})
+Description: API implementation and types for querying a nodes status - Rust source code
+ Source code for Debianized Rust crate "proxmox-node-status"
+
+Package: librust-proxmox-node-status+api-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-proxmox-node-status-dev (= ${binary:Version}),
+ librust-anyhow-1+default-dev,
+ librust-hex-0.4+default-dev,
+ librust-nix-0.29+default-dev,
+ librust-openssl-0.10+default-dev,
+ librust-proxmox-router-3+default-dev (>= 3.2.2-~~),
+ librust-proxmox-sys-1+default-dev,
+ librust-tokio-1+default-dev (>= 1.6-~~)
+Provides:
+ librust-proxmox-node-status-1+api-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0+api-dev (= ${binary:Version}),
+ librust-proxmox-node-status-1.0.0+api-dev (= ${binary:Version})
+Description: API implementation and types for querying a nodes status - feature "api"
+ This metapackage enables feature "api" for the Rust proxmox-node-status crate,
+ by pulling in any additional dependencies needed by that feature.
diff --git a/proxmox-node-status/debian/copyright b/proxmox-node-status/debian/copyright
new file mode 100644
index 00000000..d6e3c304
--- /dev/null
+++ b/proxmox-node-status/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2025 Proxmox Server Solutions GmbH <support@proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-node-status/debian/debcargo.toml b/proxmox-node-status/debian/debcargo.toml
new file mode 100644
index 00000000..b7864cdb
--- /dev/null
+++ b/proxmox-node-status/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
diff --git a/proxmox-node-status/src/api.rs b/proxmox-node-status/src/api.rs
new file mode 100644
index 00000000..4d65d216
--- /dev/null
+++ b/proxmox-node-status/src/api.rs
@@ -0,0 +1,184 @@
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::sync::OnceLock;
+
+use anyhow::{bail, format_err, Error};
+
+use proxmox_schema::api;
+use proxmox_schema::api_types::NODE_SCHEMA;
+use proxmox_sys::boot_mode;
+use proxmox_sys::linux::procfs;
+
+pub use crate::types::{
+    BootModeInformation, KernelVersionInformation, NodeCpuInformation, NodeInformation,
+    NodeMemoryCounters, NodePowerCommand, NodeStatus, NodeSwapCounters, StorageStatus,
+};
+
+static TLS_CERT_PATH: OnceLock<PathBuf> = OnceLock::new();
+
+pub fn init_node_status_api<P: AsRef<Path>>(cert_path: P) -> Result<(), Error> {
+    TLS_CERT_PATH
+        .set(cert_path.as_ref().to_owned())
+        .map_err(|_e| format_err!("cannot set certificate path twice!"))
+}
+
+fn procfs_to_node_cpu_info(info: procfs::ProcFsCPUInfo) -> NodeCpuInformation {
+    NodeCpuInformation {
+        model: info.model,
+        sockets: info.sockets,
+        cpus: info.cpus,
+    }
+}
+
+fn boot_mode_to_info(bm: boot_mode::BootMode, sb: boot_mode::SecureBoot) -> BootModeInformation {
+    use boot_mode::BootMode;
+    use boot_mode::SecureBoot;
+
+    match (bm, sb) {
+        (BootMode::Efi, SecureBoot::Enabled) => BootModeInformation {
+            mode: crate::types::BootMode::Efi,
+            secureboot: true,
+        },
+        (BootMode::Efi, SecureBoot::Disabled) => BootModeInformation {
+            mode: crate::types::BootMode::Efi,
+            secureboot: false,
+        },
+        (BootMode::Bios, _) => BootModeInformation {
+            mode: crate::types::BootMode::LegacyBios,
+            secureboot: false,
+        },
+    }
+}
+
+fn certificate_fingerprint() -> Result<String, Error> {
+    let cert_path = TLS_CERT_PATH.get().ok_or_else(|| {
+        format_err!("certificate path needs to be set before calling node status endpoints")
+    })?;
+    let x509 = openssl::x509::X509::from_pem(&proxmox_sys::fs::file_get_contents(cert_path)?)?;
+    let fp = x509.digest(openssl::hash::MessageDigest::sha256())?;
+
+    Ok(hex::encode(fp)
+        .as_bytes()
+        .chunks(2)
+        .map(|v| std::str::from_utf8(v).unwrap())
+        .collect::<Vec<&str>>()
+        .join(":"))
+}
+
+#[api(
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+        },
+    },
+    returns: {
+        type: NodeStatus,
+    },
+)]
+/// Read node memory, CPU and (root) disk usage
+pub async fn get_status() -> Result<NodeStatus, Error> {
+    let meminfo: procfs::ProcFsMemInfo = procfs::read_meminfo()?;
+    let memory = NodeMemoryCounters {
+        total: meminfo.memtotal,
+        used: meminfo.memused,
+        free: meminfo.memfree,
+    };
+
+    let swap = NodeSwapCounters {
+        total: meminfo.swaptotal,
+        used: meminfo.swapused,
+        free: meminfo.swapfree,
+    };
+
+    let kstat: procfs::ProcFsStat = procfs::read_proc_stat()?;
+    let cpu = kstat.cpu;
+    let wait = kstat.iowait_percent;
+
+    let loadavg = procfs::Loadavg::read()?;
+    let loadavg = [loadavg.one(), loadavg.five(), loadavg.fifteen()];
+
+    let cpuinfo = procfs::read_cpuinfo()?;
+    let cpuinfo = procfs_to_node_cpu_info(cpuinfo);
+
+    let uname = nix::sys::utsname::uname()?;
+    let kernel_version = KernelVersionInformation::from_uname_parts(
+        uname.sysname(),
+        uname.release(),
+        uname.version(),
+        uname.machine(),
+    );
+
+    let disk = tokio::task::spawn_blocking(move || proxmox_sys::fs::fs_info(c"/"))
+        .await
+        .map_err(|err| format_err!("error waiting for fs_info call: {err}"))??;
+
+    let boot_info = boot_mode_to_info(boot_mode::BootMode::query(), boot_mode::SecureBoot::query());
+
+    Ok(NodeStatus {
+        memory,
+        swap,
+        root: StorageStatus {
+            total: disk.total,
+            used: disk.used,
+            avail: disk.available,
+        },
+        uptime: procfs::read_proc_uptime()?.0 as u64,
+        loadavg,
+        kversion: kernel_version.get_legacy(),
+        current_kernel: kernel_version,
+        cpuinfo,
+        cpu,
+        wait,
+        info: NodeInformation {
+            fingerprint: certificate_fingerprint()?,
+        },
+        boot_info,
+    })
+}
+
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            command: {
+                type: NodePowerCommand,
+            },
+        }
+    },
+)]
+/// Reboot or shutdown the node.
+pub fn reboot_or_shutdown(command: NodePowerCommand) -> Result<(), Error> {
+    let systemctl_command = match command {
+        NodePowerCommand::Reboot => "reboot",
+        NodePowerCommand::Shutdown => "poweroff",
+    };
+
+    let output = Command::new("systemctl")
+        .arg(systemctl_command)
+        .output()
+        .map_err(|err| format_err!("failed to execute systemctl - {err}"))?;
+
+    if !output.status.success() {
+        match output.status.code() {
+            Some(code) => {
+                let msg = String::from_utf8(output.stderr)
+                    .map(|m| {
+                        if m.is_empty() {
+                            String::from("no error message")
+                        } else {
+                            m
+                        }
+                    })
+                    .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
+                bail!("command failed with status code: {code} - {msg}");
+            }
+            None => bail!("systemctl terminated by signal"),
+        }
+    }
+    Ok(())
+}
diff --git a/proxmox-node-status/src/lib.rs b/proxmox-node-status/src/lib.rs
new file mode 100644
index 00000000..2372b569
--- /dev/null
+++ b/proxmox-node-status/src/lib.rs
@@ -0,0 +1,11 @@
+
+#[cfg(feature = "api")]
+mod api;
+#[cfg(feature = "api")]
+pub use crate::api::{init_node_status_api, API_METHOD_GET_STATUS, API_METHOD_REBOOT_OR_SHUTDOWN};
+
+mod types;
+pub use crate::types::{
+    BootMode, BootModeInformation, KernelVersionInformation, NodeCpuInformation, NodeInformation,
+    NodeMemoryCounters, NodePowerCommand, NodeStatus, NodeSwapCounters, StorageStatus,
+};
diff --git a/proxmox-node-status/src/types.rs b/proxmox-node-status/src/types.rs
new file mode 100644
index 00000000..cc0ba424
--- /dev/null
+++ b/proxmox-node-status/src/types.rs
@@ -0,0 +1,184 @@
+use std::ffi::OsStr;
+
+use serde::{Deserialize, Serialize};
+
+use proxmox_schema::api;
+
+#[api]
+#[derive(Serialize, Deserialize, Copy, Clone)]
+#[serde(rename_all = "kebab-case")]
+/// The possible BootModes
+pub enum BootMode {
+    /// The BootMode is EFI/UEFI
+    Efi,
+    /// The BootMode is Legacy BIOS
+    LegacyBios,
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Clone)]
+#[serde(rename_all = "lowercase")]
+/// Holds the Bootmodes
+pub struct BootModeInformation {
+    /// The BootMode, either Efi or Bios
+    pub mode: BootMode,
+    /// SecureBoot status
+    pub secureboot: bool,
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "lowercase")]
+/// The current kernel version (output of `uname`)
+pub struct KernelVersionInformation {
+    /// The systemname/nodename
+    pub sysname: String,
+    /// The kernel release number
+    pub release: String,
+    /// The kernel version
+    pub version: String,
+    /// The machine architecture
+    pub machine: String,
+}
+
+impl KernelVersionInformation {
+    pub fn from_uname_parts(
+        sysname: &OsStr,
+        release: &OsStr,
+        version: &OsStr,
+        machine: &OsStr,
+    ) -> Self {
+        KernelVersionInformation {
+            sysname: sysname.to_str().map(String::from).unwrap_or_default(),
+            release: release.to_str().map(String::from).unwrap_or_default(),
+            version: version.to_str().map(String::from).unwrap_or_default(),
+            machine: machine.to_str().map(String::from).unwrap_or_default(),
+        }
+    }
+
+    pub fn get_legacy(&self) -> String {
+        format!("{} {} {}", self.sysname, self.release, self.version)
+    }
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+/// Information about the CPU
+pub struct NodeCpuInformation {
+    /// The CPU model
+    pub model: String,
+    /// The number of CPU sockets
+    pub sockets: usize,
+    /// The number of CPU cores (incl. threads)
+    pub cpus: usize,
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+/// Contains general node information such as the fingerprint`
+pub struct NodeInformation {
+    /// The SSL Fingerprint
+    pub fingerprint: String,
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+/// Node memory usage counters
+pub struct NodeMemoryCounters {
+    /// Total memory
+    pub total: u64,
+    /// Used memory
+    pub used: u64,
+    /// Free memory
+    pub free: u64,
+}
+
+#[api(
+    properties: {
+        memory: {
+            type: NodeMemoryCounters,
+        },
+        root: {
+            type: StorageStatus,
+        },
+        swap: {
+            type: NodeSwapCounters,
+        },
+        loadavg: {
+            type: Array,
+            items: {
+                type: Number,
+                description: "the load",
+            }
+        },
+        cpuinfo: {
+            type: NodeCpuInformation,
+        },
+        info: {
+            type: NodeInformation,
+        }
+    },
+)]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// The Node status
+pub struct NodeStatus {
+    pub memory: NodeMemoryCounters,
+    pub root: StorageStatus,
+    pub swap: NodeSwapCounters,
+    /// The current uptime of the server.
+    pub uptime: u64,
+    /// Load for 1, 5 and 15 minutes.
+    pub loadavg: [f64; 3],
+    /// The current kernel version (NEW struct type).
+    pub current_kernel: KernelVersionInformation,
+    /// The current kernel version (LEGACY string type).
+    pub kversion: String,
+    /// Total CPU usage since last query.
+    pub cpu: f64,
+    /// Total IO wait since last query.
+    pub wait: f64,
+    pub cpuinfo: NodeCpuInformation,
+    pub info: NodeInformation,
+    /// Current boot mode
+    pub boot_info: BootModeInformation,
+}
+
+#[api]
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+/// Node swap usage counters
+pub struct NodeSwapCounters {
+    /// Total swap
+    pub total: u64,
+    /// Used swap
+    pub used: u64,
+    /// Free swap
+    pub free: u64,
+}
+
+#[api()]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+/// Node Power command type.
+pub enum NodePowerCommand {
+    /// Restart the server
+    Reboot,
+    /// Shutdown the server
+    Shutdown,
+}
+
+#[api()]
+#[derive(Default, Serialize, Deserialize)]
+/// Storage space usage information.
+pub struct StorageStatus {
+    /// Total space (bytes).
+    pub total: u64,
+    /// Used space (bytes).
+    pub used: u64,
+    /// Available space (bytes).
+    pub avail: u64,
+}
--
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] 13+ messages in thread

* [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  2025-11-06 12:43 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 20:44   ` [pdm-devel] applied: " Thomas Lamprecht
  2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
---
 Cargo.toml       |  1 +
 src/node_info.rs | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index 39109c0..ecce886 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,6 +84,7 @@ proxmox-apt-api-types = { version = "2.0", optional = true }
 proxmox-access-control = "1.1"
 proxmox-dns-api = { version = "1", optional = true }
 proxmox-network-api = { version = "1", optional = true }
+proxmox-node-status = { version = "1", features = [] }

 pve-api-types = "8"
 pbs-api-types = "1"
diff --git a/src/node_info.rs b/src/node_info.rs
index 17ba6cd..5604787 100644
--- a/src/node_info.rs
+++ b/src/node_info.rs
@@ -1,4 +1,5 @@
 use proxmox_human_byte::HumanByte;
+use proxmox_node_status::BootMode;
 use pwt::{prelude::*, widget::Container};

 use crate::{MeterLabel, StatusRow};
@@ -7,6 +8,7 @@ use crate::{MeterLabel, StatusRow};
 pub enum NodeStatus<'a> {
     Pve(&'a pve_api_types::NodeStatus),
     Pbs(&'a pbs_api_types::NodeStatus),
+    Common(&'a proxmox_node_status::NodeStatus),
 }

 impl<'a> From<&'a pve_api_types::NodeStatus> for NodeStatus<'a> {
@@ -29,6 +31,7 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
     let (cpu, cpus_total) = match data {
         Some(NodeStatus::Pve(node_status)) => (node_status.cpu, node_status.cpuinfo.cpus as u64),
         Some(NodeStatus::Pbs(node_status)) => (node_status.cpu, node_status.cpuinfo.cpus as u64),
+        Some(NodeStatus::Common(node_status)) => (node_status.cpu, node_status.cpuinfo.cpus as u64),
         None => (0.0, 1),
     };

@@ -39,6 +42,7 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             .and_then(|wait| wait.as_f64())
             .unwrap_or_default(),
         Some(NodeStatus::Pbs(node_status)) => node_status.wait,
+        Some(NodeStatus::Common(node_status)) => node_status.wait,
         None => 0.0,
     };

@@ -48,6 +52,9 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             node_status.memory.total as u64,
         ),
         Some(NodeStatus::Pbs(node_status)) => (node_status.memory.used, node_status.memory.total),
+        Some(NodeStatus::Common(node_status)) => {
+            (node_status.memory.used, node_status.memory.total)
+        }
         None => (0, 1),
     };

@@ -57,6 +64,10 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             "{:.2} {:.2} {:.2}",
             node_status.loadavg[0], node_status.loadavg[1], node_status.loadavg[2]
         ),
+        Some(NodeStatus::Common(node_status)) => format!(
+            "{:.2} {:.2} {:.2}",
+            node_status.loadavg[0], node_status.loadavg[1], node_status.loadavg[2]
+        ),
         None => tr!("N/A"),
     };

@@ -66,6 +77,7 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             node_status.rootfs.total as u64,
         ),
         Some(NodeStatus::Pbs(node_status)) => (node_status.root.used, node_status.root.total),
+        Some(NodeStatus::Common(node_status)) => (node_status.root.used, node_status.root.total),
         None => (0, 1),
     };

@@ -90,6 +102,7 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             }
         }
         Some(NodeStatus::Pbs(node_status)) => (node_status.swap.used, node_status.swap.total),
+        Some(NodeStatus::Common(node_status)) => (node_status.swap.used, node_status.swap.total),
         None => (0, 1),
     };

@@ -102,6 +115,10 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             node_status.cpuinfo.model.clone(),
             node_status.cpuinfo.sockets as u64,
         ),
+        Some(NodeStatus::Common(node_status)) => (
+            node_status.cpuinfo.model.clone(),
+            node_status.cpuinfo.sockets as u64,
+        ),
         None => (String::new(), 1),
     };

@@ -121,9 +138,20 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
             node_status.current_kernel.release.clone(),
             node_status.current_kernel.version.clone(),
         ),
+        Some(NodeStatus::Common(node_status)) => (
+            node_status.current_kernel.sysname.clone(),
+            node_status.current_kernel.release.clone(),
+            node_status.current_kernel.version.clone(),
+        ),
         None => (String::new(), String::new(), String::new()),
     };

+    let boot_mode = if let Some(NodeStatus::Common(node_status)) = data {
+        Some(&node_status.boot_info)
+    } else {
+        None
+    };
+
     Container::new()
         .class("pwt-d-grid pwt-gap-2 pwt-align-items-center")
         .style("grid-template-columns", "1fr 20px 1fr")
@@ -221,4 +249,14 @@ pub fn node_info(data: Option<NodeStatus>) -> Container {
                 .style("grid-column", "1/-1")
                 .status(format!("{} {} {}", k_sysname, k_release, k_version)),
         )
+        .with_optional_child(boot_mode.map(|m| {
+            let mode = match m.mode {
+                BootMode::Efi => tr!("Legacy BIOS"),
+                BootMode::LegacyBios if m.secureboot => tr!("UEFI (Secure Boot Enabled)"),
+                BootMode::LegacyBios => tr!("UEFI"),
+            };
+            StatusRow::new(tr!("Boot Mode"))
+                .style("grid-column", "1/-1")
+                .status(mode)
+        }))
 }
--
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] 13+ messages in thread

* [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  2025-11-06 12:43 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
  2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

it also allows shutting down or reloading the node.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/lib.rs               |   3 +
 src/node_status_panel.rs | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 249 insertions(+)
 create mode 100644 src/node_status_panel.rs

diff --git a/src/lib.rs b/src/lib.rs
index 3a9e32b..e097b05 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -91,6 +91,9 @@ pub use loadable_component::{
 mod node_info;
 pub use node_info::{node_info, NodeStatus};

+mod node_status_panel;
+pub use node_status_panel::NodeStatusPanel;
+
 mod notes_view;
 pub use notes_view::{NotesView, NotesWithDigest, ProxmoxNotesView};

diff --git a/src/node_status_panel.rs b/src/node_status_panel.rs
new file mode 100644
index 0000000..be14d71
--- /dev/null
+++ b/src/node_status_panel.rs
@@ -0,0 +1,246 @@
+use std::future::Future;
+use std::rc::Rc;
+
+use anyhow::Error;
+use html::IntoPropValue;
+use pwt::css::{AlignItems, ColorScheme, FlexFit};
+use pwt::widget::form::DisplayField;
+use yew::virtual_dom::{VComp, VNode};
+
+use pwt::prelude::*;
+use pwt::widget::{error_message, Fa, Panel, Row, Tooltip};
+use pwt::widget::{Button, Dialog};
+use pwt_macros::builder;
+
+use proxmox_node_status::{NodePowerCommand, NodeStatus};
+
+use crate::utils::copy_text_to_clipboard;
+use crate::{
+    http_get, http_post, node_info, ConfirmButton, LoadableComponent, LoadableComponentContext,
+    LoadableComponentMaster,
+};
+
+#[derive(Properties, Clone, PartialEq)]
+#[builder]
+pub struct NodeStatusPanel {
+    /// URL path to load the node's status from.
+    #[builder(IntoPropValue, into_prop_value)]
+    #[prop_or_default]
+    status_base_url: Option<AttrValue>,
+}
+
+impl NodeStatusPanel {
+    pub fn new() -> Self {
+        yew::props!(Self {})
+    }
+}
+
+impl Default for NodeStatusPanel {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+enum Msg {
+    Error(Error),
+    Loaded(Rc<NodeStatus>),
+    RebootOrShutdown(NodePowerCommand),
+    Reload,
+}
+
+#[derive(PartialEq)]
+enum ViewState {
+    FingerprintDialog,
+}
+
+struct ProxmoxNodeStatusPanel {
+    node_status: Option<Rc<NodeStatus>>,
+    error: Option<Error>,
+}
+
+impl ProxmoxNodeStatusPanel {
+    fn change_power_state(&self, ctx: &LoadableComponentContext<Self>, command: NodePowerCommand) {
+        let Some(url) = ctx.props().status_base_url.clone() else {
+            return;
+        };
+        let link = ctx.link().clone();
+
+        ctx.link().spawn(async move {
+            let data = Some(serde_json::json!({
+                "command": command,
+            }));
+
+            match http_post(url.as_str(), data).await {
+                Ok(()) => link.send_message(Msg::Reload),
+                Err(err) => link.send_message(Msg::Error(err)),
+            }
+        });
+    }
+
+    fn fingerprint_dialog(
+        &self,
+        ctx: &LoadableComponentContext<Self>,
+        fingerprint: &str,
+    ) -> Dialog {
+        let link = ctx.link();
+        let link_button = ctx.link();
+        let fingerprint = fingerprint.to_owned();
+
+        Dialog::new(tr!("Fingerprint"))
+            .resizable(true)
+            .min_width(500)
+            .on_close(move |_| link.change_view(None))
+            .with_child(
+                Row::new()
+                    .gap(2)
+                    .margin_start(2)
+                    .margin_end(2)
+                    .with_child(
+                        DisplayField::new()
+                            .class(pwt::css::FlexFit)
+                            .value(fingerprint.clone())
+                            .border(true),
+                    )
+                    .with_child(
+                        Tooltip::new(
+                            Button::new_icon("fa fa-clipboard")
+                                .class(ColorScheme::Primary)
+                                .on_activate(move |_| copy_text_to_clipboard(&fingerprint)),
+                        )
+                        .tip(tr!("Copy token secret to clipboard.")),
+                    ),
+            )
+            .with_child(
+                Row::new()
+                    .padding(2)
+                    .with_flex_spacer()
+                    .with_child(
+                        Button::new(tr!("OK")).on_activate(move |_| link_button.change_view(None)),
+                    )
+                    .with_flex_spacer(),
+            )
+    }
+}
+
+impl LoadableComponent for ProxmoxNodeStatusPanel {
+    type Message = Msg;
+    type ViewState = ViewState;
+    type Properties = NodeStatusPanel;
+
+    fn create(ctx: &crate::LoadableComponentContext<Self>) -> Self {
+        ctx.link().repeated_load(5000);
+
+        Self {
+            node_status: None,
+            error: None,
+        }
+    }
+
+    fn load(
+        &self,
+        ctx: &crate::LoadableComponentContext<Self>,
+    ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>> {
+        let url = ctx.props().status_base_url.clone();
+        let link = ctx.link().clone();
+
+        Box::pin(async move {
+            if let Some(url) = url {
+                match http_get(url.as_str(), None).await {
+                    Ok(res) => link.send_message(Msg::Loaded(Rc::new(res))),
+                    Err(err) => link.send_message(Msg::Error(err)),
+                }
+            }
+            Ok(())
+        })
+    }
+
+    fn update(&mut self, ctx: &crate::LoadableComponentContext<Self>, msg: Self::Message) -> bool {
+        match msg {
+            Msg::Error(err) => {
+                self.error = Some(err);
+                true
+            }
+            Msg::Loaded(status) => {
+                self.node_status = Some(status);
+                self.error = None;
+                true
+            }
+            Msg::RebootOrShutdown(command) => {
+                self.change_power_state(ctx, command);
+                false
+            }
+            Msg::Reload => true,
+        }
+    }
+
+    fn dialog_view(
+        &self,
+        ctx: &LoadableComponentContext<Self>,
+        view_state: &Self::ViewState,
+    ) -> Option<Html> {
+        if view_state == &ViewState::FingerprintDialog {
+            if let Some(ref node_status) = self.node_status {
+                return Some(
+                    self.fingerprint_dialog(&ctx, &node_status.info.fingerprint)
+                        .into(),
+                );
+            }
+        }
+        None
+    }
+
+    fn main_view(&self, ctx: &crate::LoadableComponentContext<Self>) -> Html {
+        let status = self
+            .node_status
+            .as_ref()
+            .map(|r| crate::NodeStatus::Common(r));
+
+        Panel::new()
+            .border(false)
+            .class(FlexFit)
+            .title(
+                Row::new()
+                    .class(AlignItems::Center)
+                    .gap(2)
+                    .with_child(Fa::new("book"))
+                    .with_child(tr!("Node Status"))
+                    .into_html(),
+            )
+            .with_tool(
+                ConfirmButton::new(tr!("Reboot"))
+                    .confirm_message(tr!("Are you sure you want to reboot the node?"))
+                    .on_activate(
+                        ctx.link()
+                            .callback(|_| Msg::RebootOrShutdown(NodePowerCommand::Reboot)),
+                    )
+                    .icon_class("fa fa-undo"),
+            )
+            .with_tool(
+                ConfirmButton::new(tr!("Shutdown"))
+                    .confirm_message(tr!("Are you sure you want to shut down the node?"))
+                    .on_activate(
+                        ctx.link()
+                            .callback(|_| Msg::RebootOrShutdown(NodePowerCommand::Shutdown)),
+                    )
+                    .icon_class("fa fa-power-off"),
+            )
+            .with_tool(
+                Button::new(tr!("Show Fingerprint"))
+                    .icon_class("fa fa-hashtag")
+                    .class(ColorScheme::Primary)
+                    .on_activate(
+                        ctx.link()
+                            .change_view_callback(|_| ViewState::FingerprintDialog),
+                    ),
+            )
+            .with_child(node_info(status))
+            .with_optional_child(self.error.as_ref().map(|e| error_message(&e.to_string())))
+            .into()
+    }
+}
+
+impl From<NodeStatusPanel> for VNode {
+    fn from(value: NodeStatusPanel) -> Self {
+        VComp::new::<LoadableComponentMaster<ProxmoxNodeStatusPanel>>(Rc::new(value), None).into()
+    }
+}
--
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] 13+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (2 preceding siblings ...)
  2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 20:57   ` [pdm-devel] applied: " Thomas Lamprecht
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
---
 Cargo.toml                                    |  2 ++
 lib/pdm-api-types/src/acl.rs                  |  2 ++
 server/Cargo.toml                             |  1 +
 server/src/api/nodes/mod.rs                   |  2 ++
 server/src/api/nodes/status.rs                | 18 ++++++++++++++++++
 server/src/bin/proxmox-datacenter-api/main.rs |  2 ++
 6 files changed, 27 insertions(+)
 create mode 100644 server/src/api/nodes/status.rs

diff --git a/Cargo.toml b/Cargo.toml
index 49c7583..3252ccb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -78,6 +78,7 @@ proxmox-time-api = "1"
 proxmox-network-api = "1"
 proxmox-syslog-api = "1"
 proxmox-acme-api = "1"
+proxmox-node-status = "1"

 # API types for PVE (and later PMG?)
 pve-api-types = "8.0.5"
@@ -163,6 +164,7 @@ zstd = { version = "0.13" }
 # proxmox-log = { path = "../proxmox/proxmox-log" }
 # proxmox-metrics = { path = "../proxmox/proxmox-metrics" }
 # proxmox-network-api = { path = "../proxmox/proxmox-network-api" }
+# proxmox-node-status = { path = "../proxmox/proxmox-node-status" }
 # proxmox-notify = { path = "../proxmox/proxmox-notify" }
 # proxmox-openid = { path = "../proxmox/proxmox-openid" }
 # proxmox-product-config = { path = "../proxmox/proxmox-product-config" }
diff --git a/lib/pdm-api-types/src/acl.rs b/lib/pdm-api-types/src/acl.rs
index 9e69c2f..5592102 100644
--- a/lib/pdm-api-types/src/acl.rs
+++ b/lib/pdm-api-types/src/acl.rs
@@ -26,6 +26,8 @@ constnamedbitmap! {
         PRIV_SYS_MODIFY("System.Modify");
         /// `Sys.Console` allows access to the system's console
         PRIV_SYS_CONSOLE("Sys.Console");
+        /// `Sys.PowerManagement` allows powering off or rebooting the system.
+        PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");

         /// `Resource.Audit` allows auditing guests, storages and other resources.
         PRIV_RESOURCE_AUDIT("Resource.Audit");
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 94420b4..a215aaf 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -72,6 +72,7 @@ proxmox-time-api = { workspace = true, features = [ "impl" ] }
 proxmox-network-api = { workspace = true, features = [ "impl" ] }
 proxmox-syslog-api = { workspace = true, features = [ "impl" ] }
 proxmox-acme-api = { workspace = true, features = [ "impl" ] }
+proxmox-node-status = { workspace = true, features = [ "api" ] }

 pdm-api-types.workspace = true
 pdm-buildcfg.workspace = true
diff --git a/server/src/api/nodes/mod.rs b/server/src/api/nodes/mod.rs
index 6f30ba7..f70fcaf 100644
--- a/server/src/api/nodes/mod.rs
+++ b/server/src/api/nodes/mod.rs
@@ -10,6 +10,7 @@ pub mod dns;
 pub mod journal;
 pub mod network;
 pub mod rrddata;
+pub mod status;
 pub mod syslog;
 pub mod tasks;
 pub mod termproxy;
@@ -45,6 +46,7 @@ pub const SUBDIRS: SubdirMap = &sorted!([
     ("journal", &journal::ROUTER),
     ("network", &network::ROUTER),
     ("rrdata", &rrddata::ROUTER),
+    ("status", &status::ROUTER),
     ("syslog", &syslog::ROUTER),
     ("tasks", &tasks::ROUTER),
     ("termproxy", &termproxy::ROUTER),
diff --git a/server/src/api/nodes/status.rs b/server/src/api/nodes/status.rs
new file mode 100644
index 0000000..b3bbed5
--- /dev/null
+++ b/server/src/api/nodes/status.rs
@@ -0,0 +1,18 @@
+use pdm_api_types::{PRIV_SYS_AUDIT, PRIV_SYS_POWER_MANAGEMENT};
+use proxmox_router::{ApiMethod, Permission, Router};
+
+const API_METHOD_GET_STATUS_WITH_ACCESS: ApiMethod = proxmox_node_status::API_METHOD_GET_STATUS
+    .access(
+        None,
+        &Permission::Privilege(&["system", "status"], PRIV_SYS_AUDIT, false),
+    );
+
+const API_METHOD_REBOOT_OR_SHUTDOWN_WITH_ACCESS: ApiMethod =
+    proxmox_node_status::API_METHOD_REBOOT_OR_SHUTDOWN.access(
+        None,
+        &Permission::Privilege(&["system", "status"], PRIV_SYS_POWER_MANAGEMENT, false),
+    );
+
+pub const ROUTER: Router = Router::new()
+    .get(&API_METHOD_GET_STATUS_WITH_ACCESS)
+    .post(&API_METHOD_REBOOT_OR_SHUTDOWN_WITH_ACCESS);
diff --git a/server/src/bin/proxmox-datacenter-api/main.rs b/server/src/bin/proxmox-datacenter-api/main.rs
index 420a3b4..0e80c82 100644
--- a/server/src/bin/proxmox-datacenter-api/main.rs
+++ b/server/src/bin/proxmox-datacenter-api/main.rs
@@ -391,6 +391,8 @@ fn make_tls_acceptor() -> Result<SslAcceptor, Error> {
     let key_path = configdir!("/auth/api.key");
     let cert_path = configdir!("/auth/api.pem");

+    proxmox_node_status::init_node_status_api(cert_path)?;
+
     proxmox_rest_server::connection::TlsAcceptorBuilder::new()
         .certificate_paths_pem(key_path, cert_path)
         .build()
--
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] 13+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (3 preceding siblings ...)
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 21:10   ` Thomas Lamprecht
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
  2025-11-06 12:44 ` [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  6 siblings, 1 reply; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 ui/src/administration/mod.rs |  4 +++-
 ui/src/dashboard/types.rs    |  1 +
 ui/src/dashboard/view.rs     | 15 ++++++++++++++-
 3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/ui/src/administration/mod.rs b/ui/src/administration/mod.rs
index a9f7ac6..72a4ffa 100644
--- a/ui/src/administration/mod.rs
+++ b/ui/src/administration/mod.rs
@@ -16,7 +16,9 @@ use pwt_macros::builder;
 //mod services;
 //pub use services::Services;

-use proxmox_yew_comp::{AptPackageManager, AptRepositories, ExistingProduct, Syslog, Tasks};
+use proxmox_yew_comp::{
+    AptPackageManager, AptRepositories, ExistingProduct, NodeStatusPanel, Syslog, Tasks,
+};

 #[derive(Clone, PartialEq, Properties)]
 #[builder]
diff --git a/ui/src/dashboard/types.rs b/ui/src/dashboard/types.rs
index c79c38a..ce4bbb7 100644
--- a/ui/src/dashboard/types.rs
+++ b/ui/src/dashboard/types.rs
@@ -61,6 +61,7 @@ pub enum WidgetType {
     TaskSummary {
         grouping: TaskSummaryGrouping,
     },
+    NodeStatus,
 }

 #[derive(Serialize, Deserialize, PartialEq, Clone, Copy)]
diff --git a/ui/src/dashboard/view.rs b/ui/src/dashboard/view.rs
index c781d99..b5cd722 100644
--- a/ui/src/dashboard/view.rs
+++ b/ui/src/dashboard/view.rs
@@ -3,6 +3,7 @@ use std::rc::Rc;
 use anyhow::Error;
 use futures::join;
 use js_sys::Date;
+use proxmox_yew_comp::NodeStatusPanel;
 use serde_json::json;
 use yew::virtual_dom::{VComp, VNode};

@@ -123,6 +124,11 @@ fn render_widget(
             let (hours, since) = get_task_options(refresh_config.task_last_hours);
             create_task_summary_panel(statistics, remotes, hours, since)
         }
+        WidgetType::NodeStatus => {
+            return NodeStatusPanel::new()
+                .status_base_url("/nodes/localhost/status")
+                .into()
+        }
     };

     if let Some(title) = &item.title {
@@ -215,6 +221,9 @@ fn required_api_calls(layout: &ViewLayout) -> (bool, bool, bool) {
                         }
                         WidgetType::Leaderboard { .. } => top_entities = true,
                         WidgetType::TaskSummary { .. } => task_statistics = true,
+                        WidgetType::NodeStatus => {
+                            // widget handles this by itself
+                        }
                     }
                 }
             }
@@ -465,7 +474,11 @@ async fn load_template() -> Result<ViewTemplate, Error> {
                   \"widget-type\": \"pbs-datastores\"
                 },
                 {
-                  \"flex\": 5.0,
+                  \"flex\": 2.0,
+                  \"widget-type\": \"node-status\"
+                },
+                {
+                  \"flex\": 3.0,
                   \"widget-type\": \"subscription\"
                 }
               ],
--
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] 13+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (4 preceding siblings ...)
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
@ 2025-11-06 12:43 ` Shannon Sterz
  2025-11-06 20:57   ` [pdm-devel] applied: " Thomas Lamprecht
  2025-11-06 12:44 ` [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  6 siblings, 1 reply; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:43 UTC (permalink / raw)
  To: pdm-devel

with more items being present, rustfmt formats this list correctly.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
---
 server/src/api/nodes/mod.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/src/api/nodes/mod.rs b/server/src/api/nodes/mod.rs
index f70fcaf..a0fe14a 100644
--- a/server/src/api/nodes/mod.rs
+++ b/server/src/api/nodes/mod.rs
@@ -36,7 +36,6 @@ pub const ITEM_ROUTER: Router = Router::new()
     .get(&list_subdirs_api_method!(SUBDIRS))
     .subdirs(SUBDIRS);

-#[rustfmt::skip] // it'll put both entries on 1 line...
 #[sortable]
 pub const SUBDIRS: SubdirMap = &sorted!([
     ("apt", &apt::ROUTER),
--
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] 13+ messages in thread

* Re: [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager
  2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (5 preceding siblings ...)
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
@ 2025-11-06 12:44 ` Shannon Sterz
  6 siblings, 0 replies; 13+ messages in thread
From: Shannon Sterz @ 2025-11-06 12:44 UTC (permalink / raw)
  To: Shannon Sterz; +Cc: pdm-devel

ah, forgot to add the v2 in the subject line for all of these. sorry. v1
is here: https://lore.proxmox.com/pdm-devel/20251028164435.576642-1-s.sterz@proxmox.com/

On Thu Nov 6, 2025 at 1:43 PM CET, Shannon Sterz wrote:
> this series adds a node status panel as a widget for a view to proxmox
> datacenter manager. it allows for getting a rough overview of system
> load as well as accessing the node's fingerprint and rebooting and
> powering off the node.
>
> the fist patch moves the api endpoints from proxmox-backup server to
> their own proxmox-rs crate. the next two commits extend yew-comp to
> allow implementing a node status panel of the newly extracted api
> endpoints return types.
>
> the next three commits first add the new api endpoints to proxmox
> datacenter manager, add the ui panel as a widget type and remove a
> suproflous macro.
>
> Changelog
> ---------
>
> changes since v1:
>
> - move the node status panel to its own widget type in pdm
> - properly import api feature (thanks @ Dominik Csapak)
> - smaller clean ups (thanks @ Dominik Csapak)
>
> proxmox:
>
> Shannon Sterz (1):
>   node-status: add node status crate
>
>  Cargo.toml                               |   1 +
>  proxmox-node-status/Cargo.toml           |  37 +++++
>  proxmox-node-status/debian/changelog     |   5 +
>  proxmox-node-status/debian/control       |  65 ++++++++
>  proxmox-node-status/debian/copyright     |  18 +++
>  proxmox-node-status/debian/debcargo.toml |   7 +
>  proxmox-node-status/src/api.rs           | 184 +++++++++++++++++++++++
>  proxmox-node-status/src/lib.rs           |  11 ++
>  proxmox-node-status/src/types.rs         | 184 +++++++++++++++++++++++
>  9 files changed, 512 insertions(+)
>  create mode 100644 proxmox-node-status/Cargo.toml
>  create mode 100644 proxmox-node-status/debian/changelog
>  create mode 100644 proxmox-node-status/debian/control
>  create mode 100644 proxmox-node-status/debian/copyright
>  create mode 100644 proxmox-node-status/debian/debcargo.toml
>  create mode 100644 proxmox-node-status/src/api.rs
>  create mode 100644 proxmox-node-status/src/lib.rs
>  create mode 100644 proxmox-node-status/src/types.rs
>
>
> proxmox-yew-comp:
>
> Shannon Sterz (2):
>   node info: extend NodeStatus enum to include NodeStatus from
>     proxmox-rs
>   node status panel: add a panel that show the current status of a node
>
>  Cargo.toml               |   1 +
>  src/lib.rs               |   3 +
>  src/node_info.rs         |  38 ++++++
>  src/node_status_panel.rs | 246 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 288 insertions(+)
>  create mode 100644 src/node_status_panel.rs
>
>
> proxmox-datacenter-manager:
>
> Shannon Sterz (3):
>   api-types/api: add endpoints for querying the node's status
>   ui: add NodeStatusPanel to the administration menu
>   nodes: remove unnecessary rustfmt::skip macro
>
>  Cargo.toml                                    |  2 ++
>  lib/pdm-api-types/src/acl.rs                  |  2 ++
>  server/Cargo.toml                             |  1 +
>  server/src/api/nodes/mod.rs                   |  3 ++-
>  server/src/api/nodes/status.rs                | 18 ++++++++++++++++++
>  server/src/bin/proxmox-datacenter-api/main.rs |  2 ++
>  ui/src/administration/mod.rs                  |  4 +++-
>  ui/src/dashboard/types.rs                     |  1 +
>  ui/src/dashboard/view.rs                      | 15 ++++++++++++++-
>  9 files changed, 45 insertions(+), 3 deletions(-)
>  create mode 100644 server/src/api/nodes/status.rs
>
>
> Summary over all repositories:
>   22 files changed, 845 insertions(+), 3 deletions(-)
>
> --
> Generated by git-murpp 0.8.1



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pdm-devel] applied: [PATCH proxmox 1/1] node-status: add node status crate
  2025-11-06 12:43 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
@ 2025-11-06 19:41   ` Thomas Lamprecht
  0 siblings, 0 replies; 13+ messages in thread
From: Thomas Lamprecht @ 2025-11-06 19:41 UTC (permalink / raw)
  To: pdm-devel, Shannon Sterz

On Thu, 06 Nov 2025 13:43:29 +0100, Shannon Sterz wrote:
> this includes api endpoints for querying api endpoints. the original
> implementation was factored out from proxmox-backup.
> 
> 

Applied and uploaded to the devel repo, thanks!

[1/1] node-status: add node status crate
      commit: fe863e169e9c1a775a4065dd007ab9ceed4ce07b


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pdm-devel] applied: [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs
  2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
@ 2025-11-06 20:44   ` Thomas Lamprecht
  0 siblings, 0 replies; 13+ messages in thread
From: Thomas Lamprecht @ 2025-11-06 20:44 UTC (permalink / raw)
  To: Proxmox Datacenter Manager development discussion, Shannon Sterz

Am 06.11.25 um 13:43 schrieb Shannon Sterz:
> Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
> Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
> Tested-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  Cargo.toml       |  1 +
>  src/node_info.rs | 38 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 39 insertions(+)
> 
>

applied the two yew-comp patches and bumped the package, thanks!


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pdm-devel] applied: [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
@ 2025-11-06 20:57   ` Thomas Lamprecht
  0 siblings, 0 replies; 13+ messages in thread
From: Thomas Lamprecht @ 2025-11-06 20:57 UTC (permalink / raw)
  To: pdm-devel, Shannon Sterz

On Thu, 06 Nov 2025 13:43:32 +0100, Shannon Sterz wrote:
> 


Applied, thanks!

[1/3] api-types/api: add endpoints for querying the node's status
      commit: 5502c3c308b6e448d0ba8ebfefd3ac7ef9236546


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pdm-devel] applied: [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
@ 2025-11-06 20:57   ` Thomas Lamprecht
  0 siblings, 0 replies; 13+ messages in thread
From: Thomas Lamprecht @ 2025-11-06 20:57 UTC (permalink / raw)
  To: pdm-devel, Shannon Sterz

On Thu, 06 Nov 2025 13:43:34 +0100, Shannon Sterz wrote:
> with more items being present, rustfmt formats this list correctly.
> 
> 

Applied, thanks!

[3/3] nodes: remove unnecessary rustfmt::skip macro
      commit: e1db65d5bcbe0502831e959eea0b1b1b460a7f2d


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget
  2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
@ 2025-11-06 21:10   ` Thomas Lamprecht
  0 siblings, 0 replies; 13+ messages in thread
From: Thomas Lamprecht @ 2025-11-06 21:10 UTC (permalink / raw)
  To: Proxmox Datacenter Manager development discussion, Shannon Sterz

Am 06.11.25 um 13:43 schrieb Shannon Sterz:
> Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
> ---
>  ui/src/administration/mod.rs |  4 +++-
>  ui/src/dashboard/types.rs    |  1 +
>  ui/src/dashboard/view.rs     | 15 ++++++++++++++-
>  3 files changed, 18 insertions(+), 2 deletions(-)
> 
> diff --git a/ui/src/administration/mod.rs b/ui/src/administration/mod.rs
> index a9f7ac6..72a4ffa 100644
> --- a/ui/src/administration/mod.rs
> +++ b/ui/src/administration/mod.rs
> @@ -16,7 +16,9 @@ use pwt_macros::builder;
>  //mod services;
>  //pub use services::Services;
> 
> -use proxmox_yew_comp::{AptPackageManager, AptRepositories, ExistingProduct, Syslog, Tasks};
> +use proxmox_yew_comp::{
> +    AptPackageManager, AptRepositories, ExistingProduct, NodeStatusPanel, Syslog, Tasks,
> +};
> 
>  #[derive(Clone, PartialEq, Properties)]
>  #[builder]
> diff --git a/ui/src/dashboard/types.rs b/ui/src/dashboard/types.rs
> index c79c38a..ce4bbb7 100644
> --- a/ui/src/dashboard/types.rs
> +++ b/ui/src/dashboard/types.rs
> @@ -61,6 +61,7 @@ pub enum WidgetType {
>      TaskSummary {
>          grouping: TaskSummaryGrouping,
>      },
> +    NodeStatus,
>  }
> 
>  #[derive(Serialize, Deserialize, PartialEq, Clone, Copy)]
> diff --git a/ui/src/dashboard/view.rs b/ui/src/dashboard/view.rs
> index c781d99..b5cd722 100644
> --- a/ui/src/dashboard/view.rs
> +++ b/ui/src/dashboard/view.rs
> @@ -3,6 +3,7 @@ use std::rc::Rc;
>  use anyhow::Error;
>  use futures::join;
>  use js_sys::Date;
> +use proxmox_yew_comp::NodeStatusPanel;
>  use serde_json::json;
>  use yew::virtual_dom::{VComp, VNode};
> 
> @@ -123,6 +124,11 @@ fn render_widget(
>              let (hours, since) = get_task_options(refresh_config.task_last_hours);
>              create_task_summary_panel(statistics, remotes, hours, since)
>          }
> +        WidgetType::NodeStatus => {
> +            return NodeStatusPanel::new()
> +                .status_base_url("/nodes/localhost/status")
> +                .into()
> +        }
>      };
> 

While it might be OK to have as widget for remote node status in the dashboard, I
would not add the one from PDM here, that's IMO confusing, especially if it's
nowhere stated that it is in fact the one from PDM itself, given that all other
widgets show the status of remote resources.
I.e., the PBS dashboard is for the PBS instance itself, the PDM one is mainly for
the remote resources, they need to be handled a bit differently when deciding what
to add where, or at least ensure that it's clear that some info is from PDM itself.

But rather lets add a new tab to the Administration panel, there we can then also
add metrics graphs (memory, cpu, ...) for the local PDM itself in the future.
Could be still nice to implement a PDM specific overview for the dashboard, but
that should be probably something new with the specifics of PDM in mind, and can
be done separately.
Reboot/shutdown could then go in the toolbar there.

It also might be more useful to not show the "Show Fingerprint" button by default.
Adding that in this card was a bit of a stop-gap for PBS on it's own that I
implemented only because knowing the fingerprint is quite often a must-have for
being able to add a PBS as storage to a PVE cluster. As we do not can add a PDM
somewhere (in our projects) at all for now, there is really not such a need there.

FWIW, for PBS we have reduced the need for having this in the node status directly
too, as there we now got the "connection info" button/window available on the
Datastore summary page, which also includes the full datastore repository string,
and is thus more convenient than the dashboard one.
That's also why I'd not show this button by default at all, at least in the newer
Yew UI.


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2025-11-06 21:10 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-06 12:43 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
2025-11-06 12:43 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
2025-11-06 19:41   ` [pdm-devel] applied: " Thomas Lamprecht
2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
2025-11-06 20:44   ` [pdm-devel] applied: " Thomas Lamprecht
2025-11-06 12:43 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
2025-11-06 20:57   ` [pdm-devel] applied: " Thomas Lamprecht
2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
2025-11-06 21:10   ` Thomas Lamprecht
2025-11-06 12:43 ` [pdm-devel] [PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
2025-11-06 20:57   ` [pdm-devel] applied: " Thomas Lamprecht
2025-11-06 12:44 ` [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal