From: "Shannon Sterz" <s.sterz@proxmox.com>
To: "Dominik Csapak" <d.csapak@proxmox.com>
Cc: Proxmox Datacenter Manager development discussion
<pdm-devel@lists.proxmox.com>
Subject: Re: [pdm-devel] [RFC PATCH datacenter-manager v2 16/16] ui: dashboard: use 'View' instead of the Dashboard
Date: Thu, 23 Oct 2025 13:19:56 +0200 [thread overview]
Message-ID: <DDPNTDSROMCZ.C5XNUI7XW1JL@proxmox.com> (raw)
In-Reply-To: <20251023083253.1038119-17-d.csapak@proxmox.com>
On Thu Oct 23, 2025 at 10:28 AM CEST, Dominik Csapak wrote:
> this uses our new `View` with a (currently) static configuration to
> replicate our Dashboard. Since all functionality of that is available
> in the View, the `Dashboard` struct can be removed.
>
> This should be functionally the same as before.
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> changes from v1:
> * correctly mark as RFC
>
> ui/src/dashboard/mod.rs | 486 +--------------------------------------
> ui/src/dashboard/view.rs | 61 ++++-
> ui/src/lib.rs | 2 +-
> ui/src/main_menu.rs | 5 +-
> 4 files changed, 76 insertions(+), 478 deletions(-)
>
> diff --git a/ui/src/dashboard/mod.rs b/ui/src/dashboard/mod.rs
> index 8a0bdad0..8e979417 100644
> --- a/ui/src/dashboard/mod.rs
> +++ b/ui/src/dashboard/mod.rs
> @@ -1,27 +1,6 @@
> -use std::rc::Rc;
> -
> -use anyhow::Error;
> -use futures::join;
> -use js_sys::Date;
> -use serde_json::json;
> -use yew::{
> - virtual_dom::{VComp, VNode},
> - Component,
> -};
> -
> -use proxmox_yew_comp::http_get;
> -use pwt::{
> - css::{AlignItems, FlexDirection, FlexFit, FlexWrap, JustifyContent},
> - prelude::*,
> - props::StorageLocation,
> - state::PersistentState,
> - widget::{form::FormContext, Column, Container, Fa, Panel, Row},
> - AsyncPool,
> -};
> -
> -use pdm_api_types::{remotes::RemoteType, resource::ResourcesStatus, TaskStatistics};
> -
> -use crate::{pve::GuestType, remotes::AddWizard};
> +use pwt::css;
> +use pwt::prelude::*;
> +use pwt::widget::{Column, Fa, Row};
>
> mod top_entities;
> pub use top_entities::create_top_entities_panel;
> @@ -39,477 +18,36 @@ mod guest_panel;
> pub use guest_panel::create_guest_panel;
>
> mod sdn_zone_panel;
> -use sdn_zone_panel::create_sdn_panel;
> +pub use sdn_zone_panel::create_sdn_panel;
>
> mod status_row;
> -use status_row::DashboardStatusRow;
> +pub use status_row::DashboardStatusRow;
>
> mod filtered_tasks;
>
> mod tasks;
> -use tasks::{create_task_summary_panel, get_task_options};
> +pub use tasks::create_task_summary_panel;
>
> pub mod types;
>
> pub mod view;
>
> mod refresh_config_edit;
> -pub use refresh_config_edit::{
> - create_refresh_config_edit_window, refresh_config_id, RefreshConfig,
> -};
> -use refresh_config_edit::{
> - DEFAULT_MAX_AGE_S, DEFAULT_REFRESH_INTERVAL_S, FORCE_RELOAD_MAX_AGE_S, INITIAL_MAX_AGE_S,
> -};
> -
> -#[derive(Properties, PartialEq)]
> -pub struct Dashboard {}
> -
> -impl Dashboard {
> - pub fn new() -> Self {
> - yew::props!(Self {})
> - }
> -}
> -
> -impl Default for Dashboard {
> - fn default() -> Self {
> - Self::new()
> - }
> -}
> -
> -pub enum LoadingResult {
> - Resources(Result<ResourcesStatus, Error>),
> - TopEntities(Result<pdm_client::types::TopEntities, proxmox_client::Error>),
> - TaskStatistics(Result<TaskStatistics, Error>),
> - All,
> -}
> -
> -pub enum Msg {
> - LoadingFinished(LoadingResult),
> - CreateWizard(Option<RemoteType>),
> - Reload,
> - ForceReload,
> - UpdateConfig(RefreshConfig),
> - ConfigWindow(bool),
> -}
> -
> -struct StatisticsOptions {
> - data: Option<TaskStatistics>,
> - error: Option<Error>,
> -}
> -
> -pub struct PdmDashboard {
> - status: Option<ResourcesStatus>,
> - last_error: Option<Error>,
> - top_entities: Option<pdm_client::types::TopEntities>,
> - last_top_entities_error: Option<proxmox_client::Error>,
> - statistics: StatisticsOptions,
> - load_finished_time: Option<f64>,
> - show_wizard: Option<RemoteType>,
> - show_config_window: bool,
> - async_pool: AsyncPool,
> - config: PersistentState<RefreshConfig>,
> -}
> -
> -impl PdmDashboard {
> - fn reload(&mut self, ctx: &yew::Context<Self>) {
> - let max_age = if self.load_finished_time.is_some() {
> - self.config.max_age.unwrap_or(DEFAULT_MAX_AGE_S)
> - } else {
> - INITIAL_MAX_AGE_S
> - };
> - self.do_reload(ctx, max_age)
> - }
> -
> - fn do_reload(&mut self, ctx: &yew::Context<Self>, max_age: u64) {
> - let link = ctx.link().clone();
> - let (_, since) = get_task_options(self.config.task_last_hours);
> -
> - self.async_pool.spawn(async move {
> - let client = crate::pdm_client();
> -
> - let top_entities_future = {
> - let link = link.clone();
> - async move {
> - let res = client.get_top_entities().await;
> - link.send_message(Msg::LoadingFinished(LoadingResult::TopEntities(res)));
> - }
> - };
> - let status_future = {
> - let link = link.clone();
> - async move {
> - let res: Result<ResourcesStatus, _> =
> - http_get("/resources/status", Some(json!({"max-age": max_age}))).await;
> - link.send_message(Msg::LoadingFinished(LoadingResult::Resources(res)));
> - }
> - };
> -
> - let params = Some(json!({
> - "since": since,
> - "limit": 0,
> - }));
> -
> - // TODO replace with pdm client call
> - let statistics_future = {
> - let link = link.clone();
> - async move {
> - let res: Result<TaskStatistics, _> =
> - http_get("/remote-tasks/statistics", params).await;
> - link.send_message(Msg::LoadingFinished(LoadingResult::TaskStatistics(res)));
> - }
> - };
> - join!(top_entities_future, status_future, statistics_future);
> - link.send_message(Msg::LoadingFinished(LoadingResult::All));
> - });
> - }
> -}
> -
> -impl Component for PdmDashboard {
> - type Message = Msg;
> - type Properties = Dashboard;
> -
> - fn create(ctx: &yew::Context<Self>) -> Self {
> - let config: PersistentState<RefreshConfig> =
> - PersistentState::new(StorageLocation::local(refresh_config_id("dashboard")));
> - let async_pool = AsyncPool::new();
> -
> - let mut this = Self {
> - status: None,
> - last_error: None,
> - top_entities: None,
> - last_top_entities_error: None,
> - statistics: StatisticsOptions {
> - data: None,
> - error: None,
> - },
> - load_finished_time: None,
> - show_wizard: None,
> - show_config_window: false,
> - async_pool,
> - config,
> - };
> -
> - this.reload(ctx);
> -
> - this
> - }
> -
> - fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
> - match msg {
> - Msg::LoadingFinished(res) => {
> - match res {
> - LoadingResult::Resources(resources_status) => match resources_status {
> - Ok(status) => {
> - self.last_error = None;
> - self.status = Some(status);
> - }
> - Err(err) => self.last_error = Some(err),
> - },
> - LoadingResult::TopEntities(top_entities) => match top_entities {
> - Ok(data) => {
> - self.last_top_entities_error = None;
> - self.top_entities = Some(data);
> - }
> - Err(err) => self.last_top_entities_error = Some(err),
> - },
> -
> - LoadingResult::TaskStatistics(task_statistics) => match task_statistics {
> - Ok(statistics) => {
> - self.statistics.error = None;
> - self.statistics.data = Some(statistics);
> - }
> - Err(err) => self.statistics.error = Some(err),
> - },
> - LoadingResult::All => {
> - if self.load_finished_time.is_none() {
> - // immediately trigger a "normal" reload after the first load with the
> - // configured or default max-age to ensure users sees more current data.
> - ctx.link().send_message(Msg::Reload);
> - }
> - self.load_finished_time = Some(Date::now() / 1000.0);
> - }
> - }
> - true
> - }
> - Msg::CreateWizard(remote_type) => {
> - self.show_wizard = remote_type;
> - true
> - }
> - Msg::Reload => {
> - self.reload(ctx);
> - true
> - }
> - Msg::ForceReload => {
> - self.do_reload(ctx, FORCE_RELOAD_MAX_AGE_S);
> - true
> - }
> - Msg::ConfigWindow(show) => {
> - self.show_config_window = show;
> - true
> - }
> - Msg::UpdateConfig(dashboard_config) => {
> - let (old_hours, _) = get_task_options(self.config.task_last_hours);
> - self.config.update(dashboard_config);
> - let (new_hours, _) = get_task_options(self.config.task_last_hours);
> -
> - if old_hours != new_hours {
> - self.reload(ctx);
> - }
> -
> - self.show_config_window = false;
> - true
> - }
> - }
> - }
> -
> - fn view(&self, ctx: &yew::Context<Self>) -> yew::Html {
> - let (hours, since) = get_task_options(self.config.task_last_hours);
> - let content = Column::new()
> - .class(FlexFit)
> - .with_child(
> - Container::new()
> - .class("pwt-content-spacer-padding")
> - .class("pwt-content-spacer-colors")
> - .style("color", "var(--pwt-color)")
> - .style("background-color", "var(--pwt-color-background)")
> - .with_child(DashboardStatusRow::new(
> - self.load_finished_time,
> - self.config
> - .refresh_interval
> - .unwrap_or(DEFAULT_REFRESH_INTERVAL_S),
> - ctx.link()
> - .callback(|force| if force { Msg::ForceReload } else { Msg::Reload }),
> - ctx.link().callback(|_| Msg::ConfigWindow(true)),
> - )),
> - )
> - .with_child(
> - Container::new()
> - .class("pwt-content-spacer")
> - .class(FlexDirection::Row)
> - .class(FlexWrap::Wrap)
> - .padding_top(0)
> - .with_child(
> - create_remote_panel(
> - self.status.clone(),
> - Some(
> - ctx.link()
> - .callback(|_| Msg::CreateWizard(Some(RemoteType::Pve))),
> - ),
> - Some(
> - ctx.link()
> - .callback(|_| Msg::CreateWizard(Some(RemoteType::Pbs))),
> - ),
> - )
> - .flex(1.0)
> - .width(300)
> - .min_height(175),
> - )
> - .with_child(
> - create_node_panel(Some(RemoteType::Pve), self.status.clone())
> - .flex(1.0)
> - .width(300),
> - )
> - .with_child(
> - create_guest_panel(Some(GuestType::Qemu), self.status.clone())
> - .flex(1.0)
> - .width(300),
> - )
> - .with_child(
> - create_guest_panel(Some(GuestType::Lxc), self.status.clone())
> - .flex(1.0)
> - .width(300),
> - )
> - // FIXME: add PBS support
this patch removes these FIXMEs without really addressing the issue. i
understand that this is just due to showcasing how the new view feature
can replace the dashboard, but it'd be nice to preserve them somewhere
imo.
> - //.with_child(self.create_node_panel(
> - // "building-o",
> - // tr!("Backup Server Nodes"),
> - // &self.status.pbs_nodes,
> - //))
> - //.with_child(
> - // Panel::new()
> - // .flex(1.0)
> - // .width(300)
> - // .title(create_title_with_icon(
> - // "floppy-o",
> - // tr!("Backup Server Datastores"),
> - // ))
> - // .border(true)
> - // .with_child(if self.loading {
> - // Column::new()
> - // .padding(4)
> - // .class(FlexFit)
> - // .class(JustifyContent::Center)
> - // .class(AlignItems::Center)
> - // .with_child(html! {<i class={"pwt-loading-icon"} />})
> - // } else {
> - // Column::new()
> - // .padding(4)
> - // .class(FlexFit)
> - // .class(JustifyContent::Center)
> - // .gap(2)
> - // // FIXME: show more detailed status (usage?)
> - // .with_child(
> - // Row::new()
> - // .gap(2)
> - // .with_child(
> - // StorageState::Available.to_fa_icon().fixed_width(),
> - // )
> - // .with_child(tr!("available"))
> - // .with_flex_spacer()
> - // .with_child(
> - // Container::from_tag("span").with_child(
> - // self.status.pbs_datastores.available,
> - // ),
> - // ),
> - // )
> - // .with_optional_child(
> - // (self.status.pbs_datastores.unknown > 0).then_some(
> - // Row::new()
> - // .gap(2)
> - // .with_child(
> - // StorageState::Unknown
> - // .to_fa_icon()
> - // .fixed_width(),
> - // )
> - // .with_child(tr!("unknown"))
> - // .with_flex_spacer()
> - // .with_child(
> - // Container::from_tag("span").with_child(
> - // self.status.pbs_datastores.unknown,
> - // ),
> - // ),
> - // ),
> - // )
> - // }),
> - //)
> - .with_child(
> - create_subscription_panel()
> - .flex(1.0)
> - .width(500)
> - .min_height(150),
> - ),
> - )
> - .with_child(
> - Container::new()
> - .class("pwt-content-spacer")
> - .class(FlexDirection::Row)
> - .class("pwt-align-content-start")
> - .padding_top(0)
> - .class(FlexWrap::Wrap)
> - //.min_height(175)
> - .with_child(
> - create_top_entities_panel(
> - self.top_entities.as_ref().map(|e| e.guest_cpu.clone()),
> - self.last_top_entities_error.as_ref(),
> - types::LeaderboardType::GuestCpu,
> - )
> - .flex(1.0)
> - .width(500)
> - .min_width(400),
> - )
> - .with_child(
> - create_top_entities_panel(
> - self.top_entities.as_ref().map(|e| e.node_cpu.clone()),
> - self.last_top_entities_error.as_ref(),
> - types::LeaderboardType::NodeCpu,
> - )
> - .flex(1.0)
> - .width(500)
> - .min_width(400),
> - )
> - .with_child(
> - create_top_entities_panel(
> - self.top_entities.as_ref().map(|e| e.node_memory.clone()),
> - self.last_top_entities_error.as_ref(),
> - types::LeaderboardType::NodeCpu,
> - )
> - .flex(1.0)
> - .width(500)
> - .min_width(400),
> - ),
> - )
> - .with_child(
> - Container::new()
> - .class("pwt-content-spacer")
> - .class(FlexDirection::Row)
> - .class("pwt-align-content-start")
> - .style("padding-top", "0")
> - .class(pwt::css::Flex::Fill)
> - .class(FlexWrap::Wrap)
> - .with_child(
> - create_task_summary_panel(
> - self.statistics.data.clone(),
> - self.statistics.error.as_ref(),
> - None,
> - hours,
> - since,
> - )
> - .flex(1.0)
> - .width(500),
> - )
> - .with_child(
> - create_task_summary_panel(
> - self.statistics.data.clone(),
> - self.statistics.error.as_ref(),
> - Some(5),
> - hours,
> - since,
> - )
> - .flex(1.0)
> - .width(500),
> - )
> - .with_child(create_sdn_panel(self.status.clone()).flex(1.0).width(200)),
> - );
> -
> - Panel::new()
> - .class(FlexFit)
> - .with_child(content)
> - .with_optional_child(self.show_wizard.map(|remote_type| {
> - AddWizard::new(remote_type)
> - .on_close(ctx.link().callback(|_| Msg::CreateWizard(None)))
> - .on_submit(move |ctx| crate::remotes::create_remote(ctx, remote_type))
> - }))
> - .with_optional_child(
> - self.show_config_window.then_some(
> - create_refresh_config_edit_window("dashboard")
> - .on_close(ctx.link().callback(|_| Msg::ConfigWindow(false)))
> - .on_submit({
> - let link = ctx.link().clone();
> - move |ctx: FormContext| {
> - let link = link.clone();
> - async move {
> - let data: RefreshConfig =
> - serde_json::from_value(ctx.get_submit_data())?;
> - link.send_message(Msg::UpdateConfig(data));
> - Ok(())
> - }
> - }
> - }),
> - ),
> - )
> - .into()
> - }
> -}
> -
> -impl From<Dashboard> for VNode {
> - fn from(val: Dashboard) -> Self {
> - let comp = VComp::new::<PdmDashboard>(Rc::new(val), None);
> - VNode::from(comp)
> - }
> -}
> +pub use refresh_config_edit::create_refresh_config_edit_window;
>
> fn loading_column() -> Column {
> Column::new()
> .padding(4)
> - .class(FlexFit)
> - .class(JustifyContent::Center)
> - .class(AlignItems::Center)
> + .class(css::FlexFit)
> + .class(css::JustifyContent::Center)
> + .class(css::AlignItems::Center)
> .with_child(html! {<i class={"pwt-loading-icon"} />})
> }
>
> /// Create a consistent title component for the given title and icon
> -pub fn create_title_with_icon(icon: &str, title: String) -> Html {
> +fn create_title_with_icon(icon: &str, title: String) -> Html {
> Row::new()
> - .class(AlignItems::Center)
> + .class(css::AlignItems::Center)
> .gap(2)
> .with_child(Fa::new(icon))
> .with_child(title)
> diff --git a/ui/src/dashboard/view.rs b/ui/src/dashboard/view.rs
> index 7159d4e8..6cea9f87 100644
> --- a/ui/src/dashboard/view.rs
> +++ b/ui/src/dashboard/view.rs
> @@ -446,7 +446,66 @@ async fn load_template() -> Result<ViewTemplate, Error> {
> \"description\": \"some description\",
> \"layout\": {
> \"layout-type\": \"rows\",
> - \"rows\": []
> + \"rows\": [
> + [
> + {
> + \"flex\": 3.0,
> + \"widget-type\": \"remotes\",
> + \"show-wizard\": true
> + },
> + {
> + \"flex\": 3.0,
> + \"widget-type\": \"nodes\",
> + \"remote-type\": \"pve\"
> + },
> + {
> + \"flex\": 3.0,
> + \"widget-type\": \"guests\",
> + \"guest-type\": \"qemu\"
> + },
> + {
> + \"flex\": 3.0,
> + \"widget-type\": \"guests\",
> + \"guest-type\": \"lxc\"
> + },
> + {
> + \"flex\": 5.0,
> + \"widget-type\": \"subscription\"
> + }
> + ],
> + [
> + {
> + \"widget-type\": \"leaderboard\",
> + \"leaderboard-type\": \"guest-cpu\"
> + },
> + {
> + \"widget-type\": \"leaderboard\",
> + \"leaderboard-type\": \"node-cpu\"
> + },
> + {
> + \"widget-type\": \"leaderboard\",
> + \"leaderboard-type\": \"node-memory\"
> + }
> + ],
> + [
> + {
> + \"flex\": 5.0,
> + \"widget-type\": \"task-summary\",
> + \"grouping\": \"category\",
> + \"sorting\": \"default\"
> + },
> + {
> + \"flex\": 5.0,
> + \"widget-type\": \"task-summary\",
> + \"grouping\": \"remote\",
> + \"sorting\": \"failed-tasks\"
> + },
> + {
> + \"flex\": 2.0,
> + \"widget-type\": \"sdn\"
> + }
> + ]
> + ]
> }
> }
> ";
> diff --git a/ui/src/lib.rs b/ui/src/lib.rs
> index de76e1c0..f9af023d 100644
> --- a/ui/src/lib.rs
> +++ b/ui/src/lib.rs
> @@ -27,7 +27,7 @@ mod search_provider;
> pub use search_provider::SearchProvider;
>
> mod dashboard;
> -pub use dashboard::Dashboard;
> +
> use yew_router::prelude::RouterScopeExt;
>
> mod widget;
> diff --git a/ui/src/main_menu.rs b/ui/src/main_menu.rs
> index 7650b63f..b5044169 100644
> --- a/ui/src/main_menu.rs
> +++ b/ui/src/main_menu.rs
> @@ -13,11 +13,12 @@ use proxmox_yew_comp::{NotesView, XTermJs};
>
> use pdm_api_types::remotes::RemoteType;
>
> +use crate::dashboard::view::View;
> use crate::remotes::RemotesPanel;
> use crate::sdn::evpn::EvpnPanel;
> use crate::sdn::ZoneTree;
> use crate::{
> - AccessControl, CertificatesPanel, Dashboard, RemoteListCacheEntry, ServerAdministration,
> + AccessControl, CertificatesPanel, RemoteListCacheEntry, ServerAdministration,
> SystemConfiguration,
> };
>
> @@ -141,7 +142,7 @@ impl Component for PdmMainMenu {
> tr!("Dashboard"),
> "dashboard",
> Some("fa fa-tachometer"),
> - move |_| Dashboard::new().into(),
> + move |_| View::new("dashboard").into(),
> );
>
> register_view(
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
next prev parent reply other threads:[~2025-10-23 11:20 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-23 8:28 [pdm-devel] [PATCH datacenter-manager v2 00/16] prepare ui for customizable views Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 01/16] ui: dashboard: refactor guest panel creation to its own module Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 02/16] ui: dashboard: refactor creating the node panel into " Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 03/16] ui: dashboard: refactor remote panel creation " Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 04/16] ui: dashboard: remote panel: make wizard menu optional Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 05/16] ui: dashboard: refactor sdn panel creation into its own module Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 06/16] ui: dashboard: refactor task summary panel creation to " Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 07/16] ui: dashboard: task summary: disable virtual scrolling Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 08/16] ui: dashboard: refactor subscription panel creation to its own module Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 09/16] ui: dashboard: refactor top entities " Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 10/16] ui: dashboard: refactor DashboardConfig editing/constants to their module Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 11/16] ui: dashboard: factor out task parameter calculation Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 12/16] ui: dashboard: remove unused remote list Dominik Csapak
2025-10-23 8:28 ` [pdm-devel] [PATCH datacenter-manager v2 13/16] ui: dashboard: status row: make loading less jarring Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [RFC PATCH datacenter-manager v2 14/16] ui: introduce `LoadResult` helper type Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [RFC PATCH datacenter-manager v2 15/16] ui: dashboard: implement 'View' Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz
2025-10-23 11:44 ` Dominik Csapak
2025-10-23 11:48 ` Dominik Csapak
2025-10-24 10:17 ` Shannon Sterz
2025-10-23 8:28 ` [pdm-devel] [RFC PATCH datacenter-manager v2 16/16] ui: dashboard: use 'View' instead of the Dashboard Dominik Csapak
2025-10-23 11:19 ` Shannon Sterz [this message]
2025-10-23 11:20 ` [pdm-devel] [PATCH datacenter-manager v2 00/16] prepare ui for customizable views Shannon Sterz
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=DDPNTDSROMCZ.C5XNUI7XW1JL@proxmox.com \
--to=s.sterz@proxmox.com \
--cc=d.csapak@proxmox.com \
--cc=pdm-devel@lists.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