feat(applications): add applications list page with host count and delete modal
- API: ApplicationWithCounts struct + get_applications_with_counts() — counts distinct hosts linked via matching ports (application_ports ↔ host_ports) - ApplicationsPage at /applications: inline add form, table with Name and Hosts columns, delete confirmation modal showing affected host count - Nav: add Applications link Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,56 @@
|
||||
// api/applications.rs — Server functions for applications and their port associations
|
||||
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::Application;
|
||||
|
||||
// Application row enriched with the number of hosts that use at least one of
|
||||
// its registered ports. Host count is computed via the join:
|
||||
// application_ports → host_ports (matched on port_number) → hosts
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ApplicationWithCounts {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
/// Distinct hosts that have at least one port matching this application.
|
||||
pub host_count: i64,
|
||||
}
|
||||
|
||||
// ─── Queries ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns all applications enriched with their associated host count.
|
||||
#[server]
|
||||
pub async fn get_applications_with_counts() -> Result<Vec<ApplicationWithCounts>, ServerFnError> {
|
||||
use sqlx::{AnyPool, Row};
|
||||
|
||||
let pool = use_context::<AnyPool>()
|
||||
.ok_or_else(|| ServerFnError::new("Database pool not found in context"))?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT
|
||||
a.id,
|
||||
a.name,
|
||||
COUNT(DISTINCT hp.host_id) AS host_count
|
||||
FROM applications a
|
||||
LEFT JOIN application_ports ap ON ap.application_id = a.id
|
||||
LEFT JOIN host_ports hp ON hp.port_number = ap.port_number
|
||||
GROUP BY a.id, a.name
|
||||
ORDER BY a.name",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|row| ApplicationWithCounts {
|
||||
id: row.get("id"),
|
||||
name: row.get("name"),
|
||||
host_count: row.get("host_count"),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns all applications ordered by name.
|
||||
#[server]
|
||||
pub async fn get_applications() -> Result<Vec<Application>, ServerFnError> {
|
||||
|
||||
Reference in New Issue
Block a user