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-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
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
  To: pdm-devel

this series adds a node status panel to the administration menu of
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 to the Administration menu and
remove a suproflous macro.

the last two commits are sent as RFC, as i am not sure whether we want
to have the panel in that menu or should maybe treat it as a widget for
the new view feature. while the last commit is just a clean up
suggestion i came accross while implementing this.

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 | 244 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 286 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                  | 15 ++++++++++++++-
 7 files changed, 41 insertions(+), 2 deletions(-)
 create mode 100644 server/src/api/nodes/status.rs


Summary over all repositories:
  20 files changed, 839 insertions(+), 2 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] 7+ messages in thread

* [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate
  2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ 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] 7+ messages in thread

* [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs
  2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 src/node_info.rs | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

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] 7+ 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-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
  To: pdm-devel

it also allows shutting down or reloading the node.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 Cargo.toml               |   1 +
 src/lib.rs               |   3 +
 src/node_status_panel.rs | 244 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 248 insertions(+)
 create mode 100644 src/node_status_panel.rs

diff --git a/Cargo.toml b/Cargo.toml
index 235aaea..12421b0 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/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..da21feb
--- /dev/null
+++ b/src/node_status_panel.rs
@@ -0,0 +1,244 @@
+use std::future::Future;
+use std::rc::Rc;
+
+use anyhow::Error;
+use html::IntoPropValue;
+use pwt::css::{AlignItems, ColorScheme};
+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()
+            .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] 7+ messages in thread

* [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status
  2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (2 preceding siblings ...)
  2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 2/3] ui: add NodeStatusPanel to the administration menu Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
  5 siblings, 0 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@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..88e3802 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 }
 
 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..860612c 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] 7+ messages in thread

* [pdm-devel] [RFC PATCH datacenter-manager 2/3] ui: add NodeStatusPanel to the administration menu
  2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (3 preceding siblings ...)
  2025-10-28 16:44 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro Shannon Sterz
  5 siblings, 0 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
might make sense to implement this as a widget for the new views feature
instead of using the panel here.

 ui/src/administration/mod.rs | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/ui/src/administration/mod.rs b/ui/src/administration/mod.rs
index 38411a9..f245396 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]
@@ -67,6 +69,17 @@ impl Component for PdmServerAdministration {
                 |_| Services::new().into(),
             )
             */
+            .with_item_builder(
+                TabBarItem::new()
+                    .key("status")
+                    .label(tr!("Status"))
+                    .icon_class("fa fa-book"),
+                move |_| {
+                    NodeStatusPanel::new()
+                        .status_base_url("/nodes/localhost/status")
+                        .into()
+                },
+            )
             .with_item_builder(
                 TabBarItem::new()
                     .key("updates")
--
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] 7+ messages in thread

* [pdm-devel] [RFC PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro
  2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
                   ` (4 preceding siblings ...)
  2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 2/3] ui: add NodeStatusPanel to the administration menu Shannon Sterz
@ 2025-10-28 16:44 ` Shannon Sterz
  5 siblings, 0 replies; 7+ messages in thread
From: Shannon Sterz @ 2025-10-28 16:44 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>
---
just a suggestion feel free to ignore

 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] 7+ messages in thread

end of thread, other threads:[~2025-10-28 16:44 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-28 16:44 [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 1/2] node info: extend NodeStatus enum to include NodeStatus from proxmox-rs Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [PATCH yew-comp 2/2] node status panel: add a panel that show the current status of a node Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [PATCH datacenter-manager 1/3] api-types/api: add endpoints for querying the node's status Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 2/3] ui: add NodeStatusPanel to the administration menu Shannon Sterz
2025-10-28 16:44 ` [pdm-devel] [RFC PATCH datacenter-manager 3/3] nodes: remove unnecessary rustfmt::skip macro 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