Vervain (botanical): An old herb of altars, said to grant clear sight. Mascot: a long-eared hare at a slim purple stalk. Tagline: See clearly.
  • Rust 97.4%
  • CSS 1%
  • Just 0.9%
  • Nushell 0.4%
  • JavaScript 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-08 03:58:24 +02:00
.forgejo/workflows ci: run the check job on the dev runner label 2026-08-01 19:36:13 -04:00
assets Merge branch 'main' into feat/browser-terminal-vapp-5 2026-06-06 18:10:27 -04:00
ci-build fix(ci): move the syntax directive to line 1 so the frontend pin applies (VAPP-59 review) 2026-06-30 21:53:02 +02:00
common@7397c2a043 chore(hooks): inherit common's pre-commit instead of redefining it 2026-08-07 20:44:59 -04:00
docker/template/vervain-apps fix(apps): build browser relay/control URLs from public origin 2026-06-28 19:23:25 -04:00
src ci: run the check job on the dev runner label 2026-08-01 19:36:13 -04:00
.gitignore chore: Sync .gitignore 2026-06-06 13:13:24 -04:00
.gitmodules chore(build): vendor common submodule and sync Cargo.lock on release 2026-07-04 09:58:36 -04:00
Cargo.lock chore(build): vendor common submodule and sync Cargo.lock on release 2026-07-04 09:58:36 -04:00
Cargo.toml feat(terminal): add Detach into a separate window like Desktop 2026-07-07 21:51:35 -04:00
Dioxus.toml rename: binary -> vervain-app, image -> vervain-web 2026-05-20 06:51:27 -04:00
justfile chore(hooks): inherit common's pre-commit instead of redefining it 2026-08-07 20:44:59 -04:00
package.json Release v0.2.0 2026-07-04 08:31:33 -04:00
README.md feat(desktop): prompt for server URL on first run, support baked branded builds 2026-06-30 20:42:39 -04:00
tailwind.config.js feat: initial Dioxus 0.7 console skeleton 2026-05-20 05:24:06 -04:00

Vervain

Dioxus 0.7 fullstack frontend for vervain-server. Runs as a web app and as a wry desktop window from a single codebase.

Artifact Identifier Where it ships
Cargo crate / bin vervain-app target/release/vervain-app, Generic Packages registry
Container image vervain-web Forgejo Container Registry: dev.a8n.run/<owner>/vervain-web

This is the Rust port of the Vervain web UI. It does not port the handlebars views (../vervain-server/views/) or any of the bundled JS in ../vervain-server/public/scripts/. Designed from scratch with information density as the priority. Two built-in themes, switchable in Settings → Appearance:

  • Pro - dense, dark sidebar, square corners, monospace-friendly.
  • Soft - light sidebar, rounded cards, same row density.

Layout

vervain-apps/
  Cargo.toml          Dioxus 0.7 fullstack crate
  Dioxus.toml         dx serve config
  tailwind.config.js  custom font scale, ink/accent palette
  package.json        tailwindcss build dep (bun, run in Docker by `just build-css`)
  justfile            dev / build / check recipes
  assets/
    tailwind.css      @tailwind directives
    tailwind-built.css (generated, gitignored)
    main.css          theme variables + shared primitives
  src/
    main.rs           launch + root context providers
    theme.rs          Theme / Density enums
    routes.rs         Routable enum
    models/           Device, Group, User, Event, ...
    server_fns/       #[server] fns proxying vervain-server (control channel + HTTP)
    components/
      layout.rs       sidebar + topbar + statusbar shell
      sidebar.rs      grouped nav (Overview / Fleet / Admin)
      topbar.rs       title + breadcrumb + global search
      statusbar.rs    connection state + theme/density toggles
      pages/
        dashboard.rs
        devices.rs
        device_detail.rs
        groups.rs
        users.rs
        events.rs
        plugins.rs
        relays.rs
        settings.rs

Routes

Path Page
/ Dashboard
/devices Device list
/devices/:device_id Device detail
/groups Groups
/users Users
/events Event log
/plugins Plugins
/relays Relays
/settings Settings

Develop

One-time setup:

rustup target add wasm32-unknown-unknown
cargo install dioxus-cli   # provides `dx`

Tailwind is built by just build-css inside a pinned oven/bun Docker image, so the only host requirements for the CSS build (and the recipes that depend on it, including the just pre-commit hook) are Docker and git. No host bun install is needed. just install remains for running host bun directly if you prefer it.

Run the web build with hot reload (in two terminals so Tailwind rebuilds CSS on change):

just watch-css          # term 1
just dev-web            # term 2

Run the desktop build (single window, embedded server):

just dev-desktop

Checks

just check              # cargo check (server + wasm) + clippy + fmt
just fmt
just test

Where the real data comes from

Every page reads from a #[server] fn in src/server_fns/mod.rs. Those fns proxy vervain-server on behalf of the browser session: list/data actions go over the /control.ashx WebSocket control channel (src/server_fns/control.rs), auth and setup go over plain HTTP (POST /login, /api/setup). The vervain-server session token is held in an HTTP-only vervain_session cookie on this app's origin and forwarded as the vervain_session cookie vervain-server expects. VERVAIN_SERVER_INTERNAL_URL points the server side of this app at vervain-server for those server-to-server hops; VERVAIN_SERVER_PUBLIC_URL is the browser-reachable origin used to build the relay and control WebSocket URLs the browser opens directly. It must be the external https:// origin, not the internal address (VAPP-41): the scheme is derived from it, so an http:// value yields insecure ws:// sockets that a secure (https) SPA page is mixed-content-blocked from opening, leaving the terminal/files/desktop relays dead and the statusbar stuck on "disconnected" (VAPP-45). The corresponding server-side modules are in ../vervain-server/crates/vervain-web/src/.

The desktop build has no container to set those env vars on, so it resolves its server URL at startup in order (VAPP-58): a build-time baked value first, then a value the user persisted, then a first-run prompt. Set VERVAIN_BAKED_SERVER_URL at compile time to ship a branded, pre-configured build (it is read via option_env!, baked into the binary, and both suppresses the first-run prompt and makes the Settings "Server URL" read-only). Leave it unset for the generic self-hosted build: on first launch the login screen prompts for the server URL, persists it to the OS config dir (src/desktop_config.rs), and reuses it on later launches. Settings lets a self-hosted user change or clear that URL to point the app at a different instance without reinstalling.

Caveats that fall out of the server's current API surface:

  • serverstats and plugins are admin-only control actions and the server silently ignores them otherwise; affected widgets render a dash or an explanatory error for non-admins.
  • Node docs carry no OS string, so the Devices table shows the agent's platform type (Desktop, Laptop, Server, ...) instead.
  • There is no per-relay listing; the Relays page shows aggregate counts from serverstats.