How it works#
Four steps. This page is the demo, written out, with the calls the interface actually makes shown beside each one.
1. Click#
You open Security → NAT, press Add forward, and a side panel slides in over the table. The table stays readable behind it; nothing in Wheelhouse uses a modal for real work.
The panel is a form: inbound interface, protocol, port, the address to translate to, a description. Two tick boxes offer the things people forget — the firewall rule that lets the forward through, and the hairpin pair for clients on the LAN who use the public address.
2. Read the commands#
Underneath the form, updating as you type, is the block the panel will stage. Not a summary of it. The commands.
set nat destination rule 100 inbound-interface name eth0
set nat destination rule 100 protocol tcp
set nat destination rule 100 destination port 443
set nat destination rule 100 translation address 10.0.10.20
set nat destination rule 100 description 'web server'
set firewall ipv4 forward filter rule 100 action accept
set firewall ipv4 forward filter rule 100 inbound-interface name eth0
set firewall ipv4 forward filter rule 100 destination address 10.0.10.20
set firewall ipv4 forward filter rule 100 description 'allow web server'There is a copy button. The spelling is the CLI's, quoting included, so the block pastes
into a configure session on the console and produces the same change. That is not a
coincidence — the renderer in the browser and the renderer in the agent are held to the
same rule, and the agent's has a test that says why: a double quote left bare is two
shell words, so description WAN "primary" previewed one way and committed another
would be a lie in the one place the product cannot afford one
(agent/staging.go,
ui/src/lib/format.ts).
Every configuration node in the tree renders the same way, whether or not you are editing it. System → Config tree will show you any subtree of the running configuration as the commands that would recreate it.
3. Stage#
Pressing Stage sends the operations to the agent and nothing else happens. No commit, no partial apply, no daemon reload.
curl -sk -X POST "$R/api/stage" \
-H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '[{"op":"set","path":["nat","destination","rule","100","protocol","tcp"]}]'{"staged": 1,
"ops": [{"op": "set", "path": ["nat","destination","rule","100","protocol","tcp"]}],
"commands": ["set nat destination rule 100 protocol tcp"]}Three things about that response are deliberate.
The working set lives on the agent, not in your browser. So a second tab sees it, and
so does a colleague signed in from another machine. So does
wheelhouse-agent apply --agent-url … and the optional reconcile loop. Every call that
changes the queue hands the whole queue back, because a client that only appended locally
could un-stage by an index that meant something different on the agent.
Only set and delete are accepted. An operation the Commit Bar could not spell
honestly is refused at the door, with the reason, rather than at commit time with an
error that does not name the request.
An empty path element is refused too. description '' would otherwise fail much
later, in the router's voice, about something you would have to work backwards to find.
4. Commit#
The Commit Bar offers two buttons and they do different things.
Commit sends the queue to VyOS' /configure endpoint as one transaction. Either all
of it lands or none of it does.
Commit-confirm (2m) sends the same transaction with a confirm_time, which arms the
router's own mechanism:
{"success": true, "data": "\nInitialized commit-confirm; 2 minutes to confirm before reboot\n"}That is the router talking. If nobody confirms inside the window, the router reboots into the previous configuration. Not a silent revert — a reboot. It is the safety property, and it is worth stating in those words wherever the button is offered, which is why the Commit Bar's countdown says it out loud.
Confirming is one more call, and the router answers:
{"success": true, "data": "Reboot timer stopped\n"}And then#
Every commit is archived. The commit history is VyOS' revision list: revision 0 is the running configuration, revision 1 is the commit before it. The interface shows what a revision changed before you take it, and rolling back loads that archived configuration the same way the CLI command does.
Every commit is audited. The audit log records the actor, their role, their source
address, the exact commands and whether the router accepted them. A request refused by
the role check is recorded too, as denied <method> <path>.
A rejected commit shows the router's own words. When VyOS refuses a change, its error text is what appears — not a translation of it.
The same four steps, from a script#
Nothing above is a UI-only path. The interface uses the API you get, with an API token in place of the browser's session cookie:
R=https://<router>:8443
T=wh_... # an API token with the operator role
curl -sk -X POST "$R/api/stage" -H "Authorization: Bearer $T" \
-H 'Content-Type: application/json' -d @ops.json
curl -sk "$R/api/staged" -H "Authorization: Bearer $T"
curl -sk -X POST "$R/api/commit" -H "Authorization: Bearer $T" \
-H 'Content-Type: application/json' -d '{"confirm_minutes":2}'
curl -sk -X POST "$R/api/commit/confirm" -H "Authorization: Bearer $T"Token requests skip the CSRF header that browser sessions must send, which is why a token is the right credential for automation and a copied cookie is not.
Read on#
- The configuration model — staging, revisions and rollback in full.
- Every click shows its commands — where else the commands appear.
- API-first, and configuration as code — the desired-state file and the reconcile loop.
- The wiki explains each concept on its own page: staging, the Commit Bar, commit-confirm and revisions and rollback.