Product Overview

HAProxy UI turns an haproxy.cfg into a graph — frontends, backends and servers as nodes — and ships the assembled configuration either to a running HAProxy through the Dataplane API, or to a git repository. The graph is the mental model; the config file stops being something you edit by hand.

Why it exists

HAProxy's configuration language is powerful and famously hard to read. The engineers who can write one fluently are scarce; everyone else learns the DSL or pays for it in incidents. HAProxy UI turns the configuration into something a team can argue about the way they argue about a network diagram, and removes the "who SSH'd in and changed the backend last week" problem by writing through a versioned, diffed, reviewed path.

Data flow

  1. Read. The client fetches the live configuration from the Dataplane v3 API (GET /v3/services/haproxy/configuration) and normalizes it into CanonicalConfig — the app's own Zod-validated model, not the API's raw shape.

  2. Render. CanonicalConfig becomes a graph. Nodes are click-to-edit; the inspector panel focuses a node without leaving the canvas.

  3. Edit. Edits mutate a working draft. Nothing is sent yet. The pending-changes bar shows every unsaved diff, and the diff panel shows red/green against the live configuration.

  4. Validate. Before anything leaves the machine the draft is checked — duplicate frontend and backend names, frontends with no bind line, and other typed errors surface immediately rather than as a failed apply.

  5. Apply. The draft is assembled back into Dataplane JSON, written inside a transaction scoped by transaction id (not by a stale version), and committed. A graceful reload follows.

The core model

CanonicalConfig is the single source of truth. It deliberately does not mirror the Dataplane API shape — it is a cleaner model that happens to serialize losslessly in both directions. Three modules own the conversion in each direction:

Module

Direction

Responsibility

normalize.ts

Dataplane → canonical

Flatten the API payload, drop defaults that mean “inherits from defaults”, populate balanceFull only when there are algorithm arguments

toDataplane.ts

canonical → Dataplane

Re-inflate for the write path

parseHaproxyCfg.ts

text → canonical

Parse a pasted or GitOps haproxy.cfg

assemble.ts

canonical → text

Emit the config file for GitOps

The parse and assemble halves are inverses of each other, and that round-trip is covered by tests — see Spec coverage gate.

Two destinations, one canvas

The same draft can be written two ways, and both are first-class:

  • Live — versioned writes to the Dataplane API, followed by a graceful reload. For teams that own the cluster.

  • GitOps — the assembled haproxy.cfg committed and pushed to a git remote. For teams with compliance requirements that need config changes to go through review.

See GitOps Mode.

Unmodeled directives are preserved

Not every directive in a real haproxy.cfg has a widget. Directives the app does not model — custom userlist and resolvers sections, peers, Lua loads, hand-tuned tune.* knobs — are carried verbatim through rawLines and rawSections.

This matters more than it sounds. A GitOps apply that silently dropped unmodeled directives would delete configuration somebody wrote by hand, on the theory that it was unused. It is not; it is there because it is needed.

Secret handling

In the desktop build, Rust brokers every outbound call — Dataplane API, git, OS keychain — so the webview never performs a fetch() or shells out. API tokens are resolved from the OS keychain inside Rust and never cross the webview boundary on a request. This is what answers the “is this UI exfiltrating my bearer token?” question, and it also means the desktop build has no CORS constraints at all.

See Secrets & keychain.

What ships in the box

  • Graph canvas with snap-to-16px grid, pinch zoom, multi-select, minimap and fit-view.

  • Inspectors for global, frontend, backend and server, with operator-facing help text on every field.

  • Paste-import of an existing haproxy.cfg with per-name opt-out and a change preview.

  • Diff, validation, versioned apply, conflict detection, reject-confirm, snapshots and rollback.

  • Runtime stats dashboard, reload banner and GitOps sync-state badge.

  • Multi-instance management, OS keychain integration and signed auto-updates on desktop.

Status: phases 1–11 shipped; v1 feature-complete including the desktop build and GitOps mode.

What’s next


Was this article helpful?