- Host detail: application names are now clickable links to
/applications/:id?back=/hosts/:host_id
- Application detail: back button reads the ?back query param and
returns to the originating host page (label "← Host") or falls
back to /applications ("← Applications")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New page /applications/:id with identity (editable name), associated
ports (add/remove), linked hosts (read-only via shared ports), and
delete with confirmation modal
- Add get_application_detail and update_application server functions
- Add ApplicationDetail and HostRef types in api/applications
- Add update_application to the repository layer
- Application names in the list are now clickable links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The add-application modal now accepts a comma-separated list of port
numbers (same UX as the add-host form). Ports are associated with the
new application atomically in create_application on the server side.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closing a modal by clicking the backdrop, Cancel, or × called
show_modal.set(false) synchronously inside a wasm-bindgen closure.
Leptos immediately unmounts the modal, freeing all its closures
while the click handler is still on the call stack, which causes
wasm-bindgen to panic with "closure invoked after being dropped".
Fix: introduce a close() helper that defers set(false) to the next
microtask via spawn_local, so the closure returns to wasm-bindgen
before the modal is unmounted.
Also switch autofocus from Effect+get() to spawn_local+get_untracked()
to avoid subscribing NodeRef as a reactive dependency, which would
re-trigger during unmount and risk the same panic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the add-modal auto-close Effect from each modal component to its
parent page component. This prevents the stale-value re-trigger bug
where the Effect would immediately close the modal on second open
because action.value() still held the previous Ok result.
Also add autofocus on the first input field of each add modal using
NodeRef<Input> so the user can start typing immediately on open.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Modals rendered inside <Suspense> were unmounted each time the host
resource re-fetched, killing their reactive subscriptions and preventing
them from reopening. Moving them to the <div> level above <Suspense>
keeps them alive across re-fetches.
The auto-close Effect for the add-app modal is also moved from
AddAppModal to HostDetailPage so it is never recreated across
open/close cycles, avoiding the stale-value re-trigger bug.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the direct dispatch on the Delete button with a pending_delete
signal (id + name). A DeleteHostModal identical to the one in host detail
opens for confirmation before the action is dispatched. The modal closes
automatically after a successful deletion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The view! macro misparses `disabled=move || expr >= other` because >= without
braces is ambiguous — the rest of the expression renders as text content.
Fix: `disabled={move || ...}`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>