* [PATCH datacenter-manager] ui: auto-installer: allow multi-select of installation entries
@ 2026-08-27 12:40 Christoph Heiss
2026-08-28 10:57 ` Lukas Wagner
0 siblings, 1 reply; 2+ messages in thread
From: Christoph Heiss @ 2026-08-27 12:40 UTC (permalink / raw)
To: pdm-devel
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
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH datacenter-manager] ui: auto-installer: allow multi-select of installation entries
2026-08-27 12:40 [PATCH datacenter-manager] ui: auto-installer: allow multi-select of installation entries Christoph Heiss
@ 2026-08-28 10:57 ` Lukas Wagner
0 siblings, 0 replies; 2+ messages in thread
From: Lukas Wagner @ 2026-08-28 10:57 UTC (permalink / raw)
To: Christoph Heiss, pdm-devel
On Thu Aug 27, 2026 at 2:40 PM CEST, Christoph Heiss wrote:
> 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>
Hi, thanks for the patch!
I think the UX could be improved a bit. Right now, it's a bit weird that
*any* click on the row automatically selects it. For instance, when
going through entries one by one to show the "System Information", this
is a bit annoying, since one cannot press the 'System Information'
button if multiple entries are selected.
I think this could be solved in two ways:
- Require the user to explicitly enable 'bulk-select', e.g. by
pressing a button or ticking a check-box in the header. Before doing
so, the checkbox column could be hidden
- Patch 'DataTable' so that only a click on the checkbox selects the
entry, but not when clicking anywhere else. This probably needs to
be configurable, not sure if we don't break other UIs if we do that.
I think the first option could be the preferable one.
> ---
> .../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);
> + }
> }
I guess in most cases a 'bulk-remove' API endpoint is preferable, mostly
to at least *enable* atomicity at the API level. But since removing
installations ends up removing individual files any way, which is
inherently not atomic, I guess the current approach is fine as well.
If we ever end up storing installations in something that supports
atomic operations (say, a sqlite DB or a single JSON file), a
bulk-delete endpoint can be added as well.
> link.send_reload();
> - })
> - }
> + }
> + });
> false
> }
> }
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-28 10:57 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 12:40 [PATCH datacenter-manager] ui: auto-installer: allow multi-select of installation entries Christoph Heiss
2026-08-28 10:57 ` Lukas Wagner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox