GUIDE · PREVIEW
GUIDE / CON.43
source: docs/guide/concepts/Web UI Stack.md
Concepts

Web UI Stack

The Problem

FortrOS needs a web UI for the admin tool (org setup, invite management, node dashboard) and eventually for the org-hosted admin interface. The web UI is a high-value target -- it's where org keys are generated, invites are created, and nodes are managed. The stack must be:

  • Auditable: The admin should be able to read every dependency
  • Minimal attack surface: Fewer dependencies = fewer CVEs
  • Modern-looking: Users expect a contemporary interface
  • No supply chain risk: No npm, no node_modules, no transitive deps

How Others Do It

React/Vue/Svelte + npm (most web apps)

The standard approach: pick a JS framework, install hundreds of npm packages, bundle with webpack/vite, deploy static files.

Why it's popular: Rich ecosystem, component libraries, fast development.

Why it's wrong for FortrOS: npm has had catastrophic supply chain attacks (event-stream, ua-parser-js, colors, faker). A single compromised transitive dependency in node_modules can exfiltrate secrets at build time. FortrOS's admin tool generates org CA keys -- a supply chain attack here compromises every node in the org. The 1000+ transitive dependencies in a typical React app are unauditable.

Tauri (Rust + web frontend)

Desktop app framework: Rust backend + webview frontend. Uses the system webview (no bundled Chromium like Electron).

Pros: Rust backend, small binary, native feel. Cons: Still needs a JS frontend build pipeline (typically npm). Desktop- only (can't serve to other devices on the network). Adds the webview as an attack surface.

Go + templ (server-rendered HTML)

Server-rendered HTML with Go templates. No JS build pipeline. Used by some Go infrastructure tools.

Pros: Simple, no JS dependencies, fast server-side rendering. Cons: FortrOS is Rust, not Go. Adding a Go dependency breaks the single- toolchain principle.

Rust + htmx (chosen)

Server-rendered HTML from a Rust backend (axum or similar). htmx handles interactivity -- it extends HTML with attributes for AJAX, WebSocket, and partial page updates. No JS framework, no build step, no bundler.

Pros:

  • Same toolchain as FortrOS (Rust)
  • htmx is a single 14KB JS file, vendored and auditable
  • No npm, no node_modules, no transitive JS dependencies
  • Server-rendered HTML is simple to reason about and test
  • WebSocket support for live updates (node dashboard)
  • Looks modern with any CSS approach (see below)

Cons:

  • Less rich client-side interactivity than a full SPA
  • Server round-trips for every interaction (mitigated by htmx's partial page swaps -- feels instant on localhost)
  • Fewer off-the-shelf UI components

The Stack

Browser
  |
  +-- HTML pages (server-rendered by Rust)
  +-- htmx.js (single file, vendored, 14KB)
  +-- CSS (Tailwind standalone or hand-written)
  |
Rust backend (axum)
  |
  +-- Routes: serve HTML, handle form posts, WebSocket
  +-- Templates: server-side HTML generation
  +-- Org logic: key generation, image building, provisioner

htmx

htmx extends HTML with attributes:

<button hx-post="/invite/create"
        hx-target="#invite-list"
        hx-swap="beforeend">
  Create Invite
</button>

Clicking the button sends a POST, the server returns an HTML fragment, htmx swaps it into the target element. No JS needed beyond the htmx library. Forms, tables, live dashboards -- all work with this pattern.

For real-time updates (node joining gossip, health changes), htmx supports WebSocket and Server-Sent Events natively:

<div hx-ws="connect:/ws/nodes">
  <!-- server pushes HTML fragments as nodes update -->
</div>

CSS

Two options, both npm-free:

  • Tailwind standalone CLI: A single binary that processes Tailwind classes. No npm needed. Download the binary, run it against the HTML templates, get a CSS file. Modern utility-first styling.
  • Hand-written CSS: For maximum control and minimum tooling. A single CSS file with custom properties for theming. More work upfront but zero external dependencies.

Tailwind standalone is the recommended starting point -- it produces a professional-looking UI with minimal effort and no supply chain risk.

Why Not _______?

Stack Why not
React/Vue/Svelte npm supply chain risk. 1000+ transitive deps unauditable.
Electron Bundles Chromium (100MB+). Desktop-only.
Tauri Still needs npm for the frontend. Desktop-only.
Wasm (Yew/Leptos) Adds complexity. Rust-to-Wasm is powerful but overkill for forms + tables. Server-rendered HTML is simpler.
Plain CGI/PHP Wrong language. FortrOS is Rust.
No web UI (CLI only) Bad UX for target audience. Setup wizards and dashboards need visual affordances.

Security Properties

  • Zero npm dependencies: No node_modules directory. No package.json. No transitive dependency tree to audit.
  • Vendored htmx: The htmx.js file is checked into the repo. Its SHA256 is verifiable against the official release. Updates are manual and reviewed.
  • Server-rendered HTML: No client-side state to XSS. The Rust backend controls what HTML is produced. Templating uses auto-escaping.
  • Same-origin only: The admin tool serves on localhost (or via tunnel). No CORS needed. No cross-origin requests.

Links