* [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; 10+ 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] 10+ 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 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, 0 replies; 10+ 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] 10+ 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 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, 0 replies; 10+ 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] 10+ 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; 10+ 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] 10+ 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 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ 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] 10+ 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 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, 0 replies; 10+ 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] 10+ 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 12:44 ` [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
6 siblings, 0 replies; 10+ 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] 10+ 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; 10+ 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] 10+ messages in thread
* Re: [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate
2025-10-28 16:44 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
@ 2025-11-03 12:50 ` Dominik Csapak
0 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2025-11-03 12:50 UTC (permalink / raw)
To: Proxmox Datacenter Manager development discussion, Shannon Sterz
nit: would be nice to have in the commit message where this is from
(proxmox-backup in this case) and that we might want
to change from the implementation there to this.
Aside from that and the nit inline, looks good to me,
consider it:
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
On 10/28/25 5:45 PM, Shannon Sterz wrote:
> this includes api endpoints for querying api endpoints
>
> Signed-off-by: Shannon Sterz <s.sterz@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 8091bf70..18c29afa 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..93f6ae12
> --- /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 initialize acl tree config twice!"))
nit: the string is wrong, seems to be a leftover of a copy&paste
> +}
> +
> +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,
> +}
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate
2025-10-28 16:44 Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
2025-11-03 12:50 ` Dominik Csapak
0 siblings, 1 reply; 10+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
To: pdm-devel
this includes api endpoints for querying api endpoints
Signed-off-by: Shannon Sterz <s.sterz@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 8091bf70..18c29afa 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..93f6ae12
--- /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 initialize acl tree config 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] 10+ messages in thread
end of thread, other threads:[~2025-11-06 12:44 UTC | newest]
Thread overview: 10+ 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 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 ` [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 12:43 ` [pdm-devel] [PATCH datacenter-manager 2/3] ui: make NodeStatusPanel available as a widget Shannon Sterz
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
-- strict thread matches above, loose matches on Subject: below --
2025-10-28 16:44 Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
2025-11-03 12:50 ` Dominik Csapak
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox