public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christoph Heiss <c.heiss@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager] ui: auto-installer: allow multi-select of installation entries
Date: Thu, 27 Aug 2026 14:40:27 +0200	[thread overview]
Message-ID: <20260827124033.871256-1-c.heiss@proxmox.com> (raw)

Depending on the environment/usage, a lot of entries of past
installations can accumulate here over time.

Having the possibility for bulk deletion makes it easier to deal with
this.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
 .../auto_installer/installations_panel.rs     | 38 +++++++++++--------
 1 file changed, 23 insertions(+), 15 deletions(-)

diff --git a/ui/src/remotes/auto_installer/installations_panel.rs b/ui/src/remotes/auto_installer/installations_panel.rs
index 2bb52278..c40628e8 100644
--- a/ui/src/remotes/auto_installer/installations_panel.rs
+++ b/ui/src/remotes/auto_installer/installations_panel.rs
@@ -22,7 +22,7 @@ use pwt::{
     tr,
     widget::{
         Button, Toolbar,
-        data_table::{DataTable, DataTableColumn, DataTableHeader},
+        data_table::{DataTable, DataTableColumn, DataTableHeader, MultiSelectMode},
         form::TextArea,
     },
 };
@@ -77,8 +77,9 @@ impl LoadableComponent for InstallationsPanelComponent {
     type ViewState = ViewState;
 
     fn create(ctx: &LoadableComponentContext<Self>) -> Self {
-        let selection =
-            Selection::new().on_select(ctx.link().callback(|_| Message::SelectionChange));
+        let selection = Selection::new()
+            .multiselect(true)
+            .on_select(ctx.link().callback(|_| Message::SelectionChange));
 
         let store =
             Store::with_extract_key(|record: &Installation| Key::from(record.uuid.to_string()));
@@ -112,15 +113,18 @@ impl LoadableComponent for InstallationsPanelComponent {
             }
             Self::Message::SelectionChange => true,
             Self::Message::RemoveEntry => {
-                if let Some(key) = self.selection.selected_key() {
+                self.spawn({
                     let link = ctx.link().clone();
-                    self.spawn(async move {
-                        if let Err(err) = delete_entry(key).await {
-                            link.show_error(tr!("Unable to delete entry"), err, true);
+                    let selection = self.selection.clone();
+                    async move {
+                        for key in selection.selected_keys() {
+                            if let Err(err) = delete_entry(key).await {
+                                link.show_error(tr!("Unable to delete entry {key}"), err, true);
+                            }
                         }
                         link.send_reload();
-                    })
-                }
+                    }
+                });
                 false
             }
         }
@@ -131,11 +135,12 @@ impl LoadableComponent for InstallationsPanelComponent {
 
         let selection_has_post_hook_data = self
             .selection
-            .selected_key()
+            .selected_keys()
+            .first()
             .and_then(|key| {
                 self.store
                     .read()
-                    .lookup_record(&key)
+                    .lookup_record(key)
                     .map(|data| data.post_hook_data.is_some())
             })
             .unwrap_or(false);
@@ -146,18 +151,19 @@ impl LoadableComponent for InstallationsPanelComponent {
             .class("pwt-border-bottom")
             .with_child(
                 Button::new(tr!("System Information"))
-                    .disabled(self.selection.is_empty())
+                    .disabled(self.selection.len() != 1)
                     .onclick(link.change_view_callback(|_| Some(ViewState::ShowRawSystemInfo))),
             )
             .with_child(
                 Button::new(tr!("Post-Installation Webhook Data"))
-                    .disabled(self.selection.is_empty() || !selection_has_post_hook_data)
+                    .disabled(self.selection.len() != 1 || !selection_has_post_hook_data)
                     .onclick(link.change_view_callback(|_| Some(ViewState::ShowRawPostHookData))),
             )
             .with_spacer()
             .with_child(
                 ConfirmButton::new(tr!("Remove"))
-                    .confirm_message(tr!("Are you sure you want to remove this entry?"))
+                    .confirm_message(tr!("Are you sure you want to remove this entry?"
+                        | "Are you sure you want to remove {n} entries?" % self.selection.len()))
                     .disabled(self.selection.is_empty())
                     .on_activate(link.callback(|_| Message::RemoveEntry)),
             )
@@ -175,6 +181,7 @@ impl LoadableComponent for InstallationsPanelComponent {
         DataTable::new(self.columns.clone(), self.store.clone())
             .class(FlexFit)
             .selection(self.selection.clone())
+            .multiselect_mode(MultiSelectMode::Simple)
             .on_row_dblclick({
                 move |_: &mut _| {
                     link.change_view(Some(Self::ViewState::ShowRawSystemInfo));
@@ -193,7 +200,7 @@ impl LoadableComponent for InstallationsPanelComponent {
         let record = self
             .store
             .read()
-            .lookup_record(&self.selection.selected_key()?)?
+            .lookup_record(self.selection.selected_keys().first()?)?
             .clone();
 
         Some(match view_state {
@@ -257,6 +264,7 @@ fn render_raw_info_container(value: String) -> yew::Html {
 
 fn columns() -> Vec<DataTableHeader<Installation>> {
     vec![
+        DataTableColumn::selection_indicator().into(),
         DataTableColumn::new(tr!("Received"))
             .width("170px")
             .render(|item: &Installation| {
-- 
2.55.0





                 reply	other threads:[~2026-08-27 12:40 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20260827124033.871256-1-c.heiss@proxmox.com \
    --to=c.heiss@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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal