public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: Proxmox Datacenter Manager development discussion
	<pdm-devel@lists.proxmox.com>,
	Shannon Sterz <s.sterz@proxmox.com>
Subject: Re: [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate
Date: Mon, 3 Nov 2025 13:50:24 +0100	[thread overview]
Message-ID: <54dd2b06-86c1-43af-8a99-7d1f75be984c@proxmox.com> (raw)
In-Reply-To: <20251028164435.576642-2-s.sterz@proxmox.com>

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


  reply	other threads:[~2025-11-03 12:50 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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-11-03 12:50   ` Dominik Csapak [this message]
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-11-03 12:52   ` Dominik Csapak
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-11-03 13:11   ` Dominik Csapak
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
2025-11-03 13:15 ` [pdm-devel] [PATCH datacenter-manager/proxmox/yew-comp 0/6] add node status panel to proxmox datacenter manager Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=54dd2b06-86c1-43af-8a99-7d1f75be984c@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    --cc=s.sterz@proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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