feat(networks): add Networks page with create/delete and SQLite auto-setup

- Add client/networks.rs: Leptos page with ServerAction + Resource pattern
  * ActionForm for CIDR creation (auto-clears after submit)
  * delete button dispatches DeleteNetwork action per row
  * Resource re-fetches after each create/delete via action.version()
  * Suspense shows "Loading…" while Resource is pending
- Register /networks route in app.rs with temporary nav bar
- Fix db.rs: create_pool now creates the SQLite file if missing
  (AnyPool has no create_if_missing option unlike SqlitePool)
- Remove redundant directory creation from main.rs (handled in db.rs)
- Fix ActionForm import: use leptos::form::ActionForm (not leptos_router)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 22:17:47 +02:00
parent 75c13b261b
commit e902efc04f
4 changed files with 204 additions and 5 deletions

View File

@@ -35,11 +35,50 @@ pub enum DbError {
///
/// `install_default_drivers()` must be called before `AnyPool::connect`
/// to register both the SQLite and PostgreSQL drivers in the `Any` registry.
///
/// For SQLite, this function also creates the database file and its parent
/// directory if they do not exist yet. The `AnyPool` driver cannot create
/// a new SQLite file by itself — unlike the `SqlitePool` which has an
/// explicit `create_if_missing` option.
pub async fn create_pool(config: &AppConfig) -> Result<AnyPool, DbError> {
// Register SQLite and PostgreSQL drivers so `AnyPool` can dispatch
// to the correct one based on the URL scheme (sqlite:// vs postgres://).
sqlx::any::install_default_drivers();
// SQLite-specific setup: ensure the file exists before connecting.
//
// `AnyPool` does not expose `create_if_missing` like `SqlitePool` does,
// so we must touch the file ourselves.
//
// The URL is parsed the same way SQLx does it internally:
// sqlite://data/ipam.db → strip sqlite:// → path = data/ipam.db
if let DatabaseBackend::Sqlite = &config.backend {
// Strip both possible prefixes (SQLx accepts both forms)
let path_str = config
.database_url
.trim_start_matches("sqlite://")
.trim_start_matches("sqlite:");
// Skip special filenames (in-memory, shared cache)
if path_str != ":memory:" && !path_str.is_empty() {
let path = std::path::Path::new(path_str);
// Create the parent directory if it does not exist
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent)
.map_err(|e| sqlx::Error::Io(e))?;
}
}
// Create an empty file so SQLite can open it
if !path.exists() {
std::fs::File::create(path)
.map_err(|e| sqlx::Error::Io(e))?;
}
}
}
let pool = AnyPool::connect(&config.database_url).await?;
Ok(pool)
}