feat(hosts): add delete confirmation modal on hosts list page

Replace the direct dispatch on the Delete button with a pending_delete
signal (id + name). A DeleteHostModal identical to the one in host detail
opens for confirmation before the action is dispatched. The modal closes
automatically after a successful deletion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 02:45:33 +02:00
parent 7274157a80
commit 62e9609fe8

View File

@@ -27,6 +27,47 @@ const PER_PAGE_OPTIONS: &[(i64, &str)] = &[
(0, "All"), (0, "All"),
]; ];
// ─── Delete host modal ────────────────────────────────────────────────────────
#[component]
fn DeleteHostModal(
host_name: String,
host_id: i64,
delete_action: ServerAction<DeleteHost>,
pending_delete: RwSignal<Option<(i64, String)>>,
) -> impl IntoView {
view! {
<div class="modal-backdrop" on:click=move |_| pending_delete.set(None)>
<div class="modal" on:click=move |e| e.stop_propagation()>
<div class="modal__header">
<h2>"Delete host"</h2>
<button class="modal__close" type="button" aria-label="Close"
on:click=move |_| pending_delete.set(None)>
"×"
</button>
</div>
<div class="modal__body">
<p class="warning">
"Are you sure you want to delete "
<strong>{host_name}</strong>
"? All port associations will also be removed."
</p>
</div>
<div class="modal__actions">
<button class="btn-secondary" type="button"
on:click=move |_| pending_delete.set(None)>
"Cancel"
</button>
<button class="btn-danger" type="button"
on:click=move |_| { delete_action.dispatch(DeleteHost { id: host_id }); }>
"Delete"
</button>
</div>
</div>
</div>
}.into_any()
}
// ─── Add host modal ─────────────────────────────────────────────────────────── // ─── Add host modal ───────────────────────────────────────────────────────────
#[component] #[component]
@@ -247,7 +288,7 @@ fn PaginationBar(
#[component] #[component]
fn HostTable( fn HostTable(
hosts: Resource<Result<HostsPageData, ServerFnError>>, hosts: Resource<Result<HostsPageData, ServerFnError>>,
delete_action: ServerAction<DeleteHost>, pending_delete: RwSignal<Option<(i64, String)>>,
) -> impl IntoView { ) -> impl IntoView {
view! { view! {
<Suspense fallback=|| view! { <p class="empty">"Loading hosts…"</p> }> <Suspense fallback=|| view! { <p class="empty">"Loading hosts…"</p> }>
@@ -276,6 +317,7 @@ fn HostTable(
<tbody> <tbody>
{rows.into_iter().map(|host| { {rows.into_iter().map(|host| {
let id = host.id; let id = host.id;
let delete_name = host.name.clone();
view! { view! {
<tr> <tr>
<td> <td>
@@ -293,7 +335,7 @@ fn HostTable(
<td class="col-count">{host.application_count}</td> <td class="col-count">{host.application_count}</td>
<td class="col-actions"> <td class="col-actions">
<button on:click=move |_| { <button on:click=move |_| {
delete_action.dispatch(DeleteHost { id }); pending_delete.set(Some((id, delete_name.clone())));
}> }>
"Delete" "Delete"
</button> </button>
@@ -319,6 +361,16 @@ pub fn HostsPage() -> impl IntoView {
let show_modal = RwSignal::new(false); let show_modal = RwSignal::new(false);
// None = no modal, Some((id, name)) = delete confirmation open.
let pending_delete: RwSignal<Option<(i64, String)>> = RwSignal::new(None);
// Close the delete modal automatically after a successful deletion.
Effect::new(move |_| {
if let Some(Ok(_)) = delete_action.value().get() {
pending_delete.set(None);
}
});
// Filter signals ("" / 0 = no filter) // Filter signals ("" / 0 = no filter)
let name_filter = RwSignal::new(String::new()); let name_filter = RwSignal::new(String::new());
let network_id_filter = RwSignal::new(0i64); let network_id_filter = RwSignal::new(0i64);
@@ -369,6 +421,15 @@ pub fn HostsPage() -> impl IntoView {
/> />
})} })}
{move || pending_delete.get().map(|(host_id, host_name)| view! {
<DeleteHostModal
host_name=host_name
host_id=host_id
delete_action=delete_action
pending_delete=pending_delete
/>
})}
<FilterBar <FilterBar
networks_res=networks_res networks_res=networks_res
applications_res=applications_res applications_res=applications_res
@@ -387,7 +448,7 @@ pub fn HostsPage() -> impl IntoView {
<PaginationBar total=total page=page per_page=per_page total_pages=total_pages/> <PaginationBar total=total page=page per_page=per_page total_pages=total_pages/>
<HostTable hosts=hosts delete_action=delete_action/> <HostTable hosts=hosts pending_delete=pending_delete/>
</section> </section>
</div> </div>
}.into_any() }.into_any()