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
- Org Bootstrap -- Where the web UI is used (admin tool)
- Monitoring and Self-Observation -- The org-hosted dashboard (same stack)