feat(networks): add delete confirmation modal with host count warning
Show a modal before deleting a network. If the network has hosts, display a warning with the exact count since they will be cascade-deleted. Host count comes from the existing NetworkWithCounts data (no extra query). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use crate::models::Network;
|
||||
// Network row augmented with pre-computed counts.
|
||||
// Defined here (not in models.rs) because it is a presentation model
|
||||
// specific to the Networks page, not a pure domain entity.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct NetworkWithCounts {
|
||||
pub id: i64,
|
||||
pub cidr: String,
|
||||
|
||||
@@ -16,7 +16,59 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::form::ActionForm;
|
||||
|
||||
use crate::api::networks::{CreateNetwork, DeleteNetwork, get_networks_with_counts};
|
||||
use crate::api::networks::{CreateNetwork, DeleteNetwork, NetworkWithCounts, get_networks_with_counts};
|
||||
|
||||
// ─── Delete confirmation modal ────────────────────────────────────────────────
|
||||
|
||||
#[component]
|
||||
fn DeleteConfirmModal(
|
||||
network: NetworkWithCounts,
|
||||
delete_action: ServerAction<DeleteNetwork>,
|
||||
pending_delete: RwSignal<Option<NetworkWithCounts>>,
|
||||
) -> impl IntoView {
|
||||
let id = network.id;
|
||||
let cidr = network.cidr.clone();
|
||||
let host_count = network.host_count;
|
||||
|
||||
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 network"</h2>
|
||||
</div>
|
||||
|
||||
<div class="modal__body">
|
||||
<p>"Delete network " <strong>{cidr}</strong> "?"</p>
|
||||
{(host_count > 0).then(|| view! {
|
||||
<p class="warning">
|
||||
"Warning: "
|
||||
{host_count}
|
||||
{if host_count == 1 { " host" } else { " hosts" }}
|
||||
" belonging to this network will also be deleted."
|
||||
</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(DeleteNetwork { id });
|
||||
}
|
||||
>"Delete"</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}.into_any()
|
||||
}
|
||||
|
||||
// ─── Page ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[component]
|
||||
pub fn NetworksPage() -> impl IntoView {
|
||||
@@ -28,6 +80,16 @@ pub fn NetworksPage() -> impl IntoView {
|
||||
let create_action = ServerAction::<CreateNetwork>::new();
|
||||
let delete_action = ServerAction::<DeleteNetwork>::new();
|
||||
|
||||
// Stores the network pending deletion; Some = modal open, None = closed.
|
||||
let pending_delete: RwSignal<Option<NetworkWithCounts>> = RwSignal::new(None);
|
||||
|
||||
// Close the modal automatically after a successful deletion.
|
||||
Effect::new(move |_| {
|
||||
if let Some(Ok(_)) = delete_action.value().get() {
|
||||
pending_delete.set(None);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Data resource ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// `Resource::new(source, fetcher)`:
|
||||
@@ -45,6 +107,15 @@ pub fn NetworksPage() -> impl IntoView {
|
||||
<div class="networks-page">
|
||||
<h1>"Networks"</h1>
|
||||
|
||||
// ── Delete confirmation modal ──────────────────────────────────────
|
||||
{move || pending_delete.get().map(|network| view! {
|
||||
<DeleteConfirmModal
|
||||
network=network
|
||||
delete_action=delete_action
|
||||
pending_delete=pending_delete
|
||||
/>
|
||||
})}
|
||||
|
||||
// ── Add form ──────────────────────────────────────────────────────
|
||||
//
|
||||
// `<ActionForm action=create_action>` submits the form to the server
|
||||
@@ -123,7 +194,7 @@ pub fn NetworksPage() -> impl IntoView {
|
||||
{list
|
||||
.into_iter()
|
||||
.map(|network| {
|
||||
let id = network.id;
|
||||
let network_clone = network.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{network.cidr}</td>
|
||||
@@ -131,8 +202,7 @@ pub fn NetworksPage() -> impl IntoView {
|
||||
<td class="col-count">{network.application_count}</td>
|
||||
<td class="col-actions">
|
||||
<button on:click=move |_| {
|
||||
delete_action
|
||||
.dispatch(DeleteNetwork { id });
|
||||
pending_delete.set(Some(network_clone.clone()));
|
||||
}>
|
||||
"Delete"
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user