use crate::allocator::{find_next_subnet, usable_host_count}; use crate::models::Pool; use crate::state::AppState; use crate::templates::PoolPreviewTemplate; use askama::Template; use axum::extract::{Path, Query, State}; use axum::response::{Html, IntoResponse}; use serde::Deserialize; use tracing::error; #[derive(Deserialize, Debug)] pub struct PreviewQuery { pub prefix_length: i32, } pub async fn pool_preview( State(state): State, Path(slug): Path, Query(q): Query, ) -> impl IntoResponse { let pool: Option = sqlx::query_as("SELECT * FROM pools WHERE slug = ? AND enabled = true") .bind(&slug) .fetch_optional(&state.pool) .await .unwrap_or_default(); let pool = match pool { Some(p) => p, None => { let template = PoolPreviewTemplate { cidr: String::new(), prefix_length: q.prefix_length, address_count: 0, usable_host_count: 0, has_error: true, error: "Pool does not exist.".to_string(), }; return Html(template.render().unwrap_or_default()); } }; let active: Vec = sqlx::query_scalar( "SELECT cidr FROM allocations WHERE pool_id = ? AND status IN ('active', 'reserved')", ) .bind(pool.id) .fetch_all(&state.pool) .await .unwrap_or_default(); let allowed = pool.allowed_prefixes(); let template = match find_next_subnet(&pool.cidr, q.prefix_length, &allowed, &active) { Ok(info) => PoolPreviewTemplate { cidr: info.cidr, prefix_length: info.prefix_length, address_count: info.address_count, usable_host_count: info.usable_host_count, has_error: false, error: String::new(), }, Err(e) => PoolPreviewTemplate { cidr: String::new(), prefix_length: q.prefix_length, address_count: usable_host_count(q.prefix_length), usable_host_count: usable_host_count(q.prefix_length), has_error: true, error: e.to_string(), }, }; match template.render() { Ok(html) => Html(html), Err(e) => { error!("Preview template render error: {}", e); Html("Preview error.".to_string()) } } }