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:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user