- API: add get_network(id) server function
- NetworkDetailPage at /networks/:id — network name + CIDR header, paginated
host table (Name, IP, Ports, Apps) linking to /hosts/:id?back=/networks/:id
- Networks list: make network name a link to its detail page
- HostDetailPage: read ?back= query param to show "← Network" or "← Hosts"
and navigate to the correct destination
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Save changes / Add port: add btn-primary class for consistent blue accent
- Back button: stacked above page title, styled as a small bordered button
- Port list: remove row background, replace full border with bottom separator only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 25 common ports (SSH, HTTP/S, SMTP, PostgreSQL, etc.) to the ports
catalog and assigns realistic open ports to each seeded host based on its
role (web server, database, NAS, VPN gateway, etc.).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resource::new() with SSR returns None during hydration outside <Suspense>,
causing dropdowns to stay empty on direct page load. LocalResource fetches
client-side only, bypassing the hydration mismatch entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The field-hint span made the port field taller than others; with
align-items: end on the grid, the input was offset upward.
The placeholder now carries the same information.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without bin-target, cargo-leptos fails when multiple binaries exist
(rust-ipam + seed). Specifying the main server binary fixes the issue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sed replacement during the network name feature didn't update
find_network's SELECT, causing a ColumnNotFound panic on host creation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Network dropdowns now show "Name - CIDR" in both filter bar and add modal
- Port filter accepts comma-separated ports (e.g. "80, 443"); a host must
have ALL listed ports open to match (AND semantics)
- Add host modal has a new "Open ports" field (comma-separated); ports are
registered in the catalog and linked to the host on creation
- Port conditions are inlined as validated integers in SQL (no injection risk)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 0007: ALTER TABLE networks ADD COLUMN name TEXT NOT NULL DEFAULT ''
- Network model, repository, and API updated to include name
- Networks page: name input in the add form, Name column as first column in table
- Delete modal now shows "Name (CIDR)" for clarity
- Hosts page: network dropdowns now show network name instead of CIDR
- Seeds updated with names (LAN, DMZ, Corporate, VPN)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates a self-contained `seed` binary (cargo run --features ssr --bin seed)
that loads realistic test data into the database. Idempotent: safe to run
multiple times without creating duplicates.
Data: 4 networks (LAN, DMZ, corporate, VPN) and 17 hosts spread across them.
Both SQLite and PostgreSQL seed files are provided.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- Add bin-features/lib-features so cargo-leptos enables ssr/hydrate
correctly (server was exiting immediately with empty main otherwise)
- Add style-file so the CSS bundle is no longer empty
- Replace #[cfg(target_arch = "wasm32")] with #[cfg(feature = "hydrate")]
in theme.rs to match when web-sys is actually available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The add-host form is now opened via a "+ Add host" button in the page
header. The modal closes on Cancel, backdrop click, × button, or
automatically after a successful creation.
Adds modal CSS with backdrop blur and entry animation, .btn-primary /
.btn-secondary shared button styles, and a .page-header flex layout
reusable across list pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements task #7. The Hosts page provides:
- Name/network/port/application filters (sentinel values instead of
Option<T> to avoid server function serialization issues)
- Configurable page size (15 default, 25/50/100/All)
- Prev/next navigation with total host count
- Add host form with network selector
- Delete action per row
Sub-components (AddHostForm, FilterBar, PaginationBar, HostTable) each
call .into_any() to erase their concrete view types. This breaks the
deeply-nested generic type that caused "queries overflow the depth
limit!" without requiring an unbounded recursion_limit increase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds NetworkWithCounts presentation model and get_networks_with_counts()
server function using a single SQL query with correlated subqueries.
Networks table now shows host count, application count, and has the
Actions column header properly right-aligned to match the Delete button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Home page now shows one clickable summary card per entity type
(Networks, Hosts, Applications). Each card displays the total count
fetched from the database via a single get_summary() server function,
then navigates to the corresponding page on click.
Counts are pre-rendered server-side via <Suspense> so the page is
useful even before the WASM bundle loads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a global design system using CSS custom properties as
design tokens. Light and dark themes are defined via [data-theme]
attribute on <html>; the system preference (prefers-color-scheme)
is the default when no explicit choice is stored.
ThemeToggle component (Auto → Light → Dark cycle) persists the
choice to localStorage and applies it on hydration without flash.
New themes can be added by defining a [data-theme="name"] CSS block
and adding a variant to ThemeChoice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Add src/api/ module with server functions for networks, hosts, applications
- Each server function retrieves the pool via use_context::<AnyPool>()
- Pool is injected via provide_context in two places in main.rs:
* leptos_routes_with_context: for SSR renders and inline server fn calls
* handle_server_fns_with_context on /api/*fn_name: for WASM client calls
- create_host validates IP against network CIDR before inserting
- create_network validates CIDR format before inserting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add server/repository/ module with networks, hosts, ports, applications
- Use sqlx::query() + manual row mapping (no compile-time DB required)
- Handle unique-constraint conflicts with is_unique_violation() for
cross-database compatibility (SQLite + PostgreSQL via AnyPool)
- add_port_to_host auto-registers the port in the catalog (prevents FK errors)
- application_ports has no FK to ports (intentional: loose association)
- Add DbError::NotFound variant for missing-record cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add sqlx 0.8 (AnyPool, runtime-tokio, sqlite, postgres, migrate)
- Create 6 migration files for both SQLite and PostgreSQL backends
- Add server/db.rs: create_pool and run_migrations helpers
- Add server/state.rs: AppState with LeptosOptions + AnyPool
- Run migrations at server startup before accepting requests
- Fix Port model: remove host_id (ports are now a global catalog)
- Add HostPort join struct for the host_ports many-to-many table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add convention rule: all generated code and comments must follow
standard conventions and be written in English.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add shared models (Network, Host, Port, Application, ApplicationPort)
with serde derives for Leptos server function serialization.
Add server/validation.rs with valider_ip_dans_reseau() and 5 unit tests.
Gate SSR-only modules (config, validation) with #[cfg(feature = "ssr")].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AppConfig loaded from .env via dotenvy. DATABASE_URL prefix
determines the backend (sqlite:// → SQLite, postgresql:// → PostgreSQL).
ConfigError via thiserror gives clear messages on missing or unknown URLs.
Server logs the chosen backend at startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set dist=target/site/pkg so trunk outputs WASM alongside where Axum
serves /pkg/. Disable filehash so HydrationScripts can resolve
rust-ipam.js and rust-ipam_bg.wasm without content hash suffixes.
Add data-target-name to index.html to disambiguate lib from bin target.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Shell component wrapping the full HTML document (DOCTYPE, head, body)
required by leptos_meta. Add [package.metadata.leptos] to Cargo.toml and
switch get_configuration to Some("Cargo.toml"). Server now returns valid
HTML with title injection and WASM hydration scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sets up the full project skeleton: Cargo.toml with ssr/hydrate features,
Axum server entry point, shared Leptos lib, root App component with router,
server/client module split, and Trunk config for WASM build.
Both `cargo check --features ssr` and `cargo check --features hydrate --target wasm32-unknown-unknown` pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>