Add the appliance web UI look, interface names, MCP and REST API, local zone

Web UI: dark/light themes (appliance layout, sidebar, switch checkboxes),
theme preference on the Advanced page, Changes page with the pending diff.

Interface names: router.interfaces.<name>.mac, lans.<l>.deviceName and
pppoe.vlanName; a rename is staged for the next reboot by router-apply.

Remote access: an MCP server (POST /mcp) and an OpenAPI REST API
(/api/v1, /api/openapi.json) with the same tokens, scopes, limits and audit
log; tools to read status, settings, leases, logs (get_logs), and to edit
and check the saved settings (never applied). Tokens on the API access page.

Firewall: a reserved `local` zone (the router itself) as the destination of
rules, so the web UI or SSH can be allowed from the WAN.

PLAN.md lists what is still left.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-11-01 16:56:59 +01:00
co-authored by Claude Sonnet 5.5
parent 95ab7223b9
commit 586274af4e
41 changed files with 3138 additions and 152 deletions
+41 -14
View File
@@ -1,23 +1,50 @@
{{define "content"}}
{{with .Data}}
{{$e := .Errors}}
<form method="post" action="/vlan">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<fieldset>
<legend>Interfaces</legend>
<fieldset>
<legend>Interfaces</legend>
<form method="post" action="/interfaces">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<input type="hidden" name="rows" value="{{len .Devices}}">
<button class="default" tabindex="-1" aria-hidden="true">Save names</button>
<div class="scroll">
<table>
<tr><th>Interface</th><th>Kind</th><th>Used as</th><th></th></tr>
{{range .Devices}}<tr><td>{{.Name}}</td><td>{{.Kind}}</td><td>{{if .Role}}{{.Role}}{{else}}<span class="hint">unused</span>{{end}}</td>
<td>{{if .Removable}}<button name="remove" value="{{.Name}}" class="link">remove</button>{{end}}</td></tr>{{end}}
<tr><th>Interface</th><th>MAC address</th><th title="The name Linux gives it: the web UI, the firewall and the logs use it">Name</th><th>Kind</th><th>Used as</th><th></th></tr>
{{range $i, $d := .Devices}}<tr>
<td>{{if $d.Kernel}}{{$d.Kernel}}{{else}}{{$d.Name}}{{end}}</td>
<td>{{if $d.MAC}}<code>{{$d.MAC}}</code>{{end}}</td>
<td>
<input type="hidden" name="dev.{{$i}}" value="{{$d.Name}}">
<input type="hidden" name="mac.{{$i}}" value="{{$d.MAC}}">
{{if eq $d.Kind "VLAN"}}
<input type="hidden" name="from.{{$i}}" value="{{$d.Custom}}">
<input name="name.{{$i}}" value="{{$d.Custom}}" size="12" placeholder="{{$d.Name}}" spellcheck="false" {{if not $d.Role}}disabled{{end}}>
{{else}}
<input type="hidden" name="from.{{$i}}" value="{{$d.Name}}">
<input name="name.{{$i}}" value="{{$d.Name}}" size="12" spellcheck="false" {{if not $d.MAC}}disabled{{end}}>
{{if and $d.Kernel (ne $d.Kernel $d.Name)}}<span class="hint">after the next reboot</span>{{end}}
{{end}}
{{with index $e (printf "name[%d]" $i)}}<span class="field-error">{{.}}</span>{{end}}
</td>
<td>{{$d.Kind}}</td><td>{{if $d.Role}}{{$d.Role}}{{else}}<span class="hint">unused</span>{{end}}</td>
<td>{{if $d.Removable}}<button formaction="/vlan" name="remove" value="{{$d.Name}}" class="link">remove</button>{{end}}</td>
</tr>{{end}}
</table>
<div class="sub">
<p class="hint">Add a VLAN (a tagged network on one of the interfaces); it is then offered below.</p>
<label>On <select name="parent">{{range .Devices}}{{if eq .Kind "physical"}}<option value="{{.Name}}">{{.Name}}</option>{{end}}{{end}}</select></label>
<label>VLAN ID <input name="id" size="5" inputmode="numeric"></label>
<button name="add" value="1">Add VLAN</button>
</div>
</fieldset>
</form>
<p class="hint">A name is what Linux calls the interface: <code>wan</code> or <code>lan</code> instead of <code>enp2s0</code>,
<code>dmz</code> instead of <code>lan.1002</code>. Cards are found by their MAC address. Linux cannot rename an
interface that is in use: after you save and apply, the router starts using the new names at its next reboot.
Clear a name to go back to the default.</p>
<button>Save names</button>
</form>
<form method="post" action="/vlan" class="sub">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<p class="hint">Add a VLAN (a tagged network on one of the interfaces); it is then offered below.</p>
<label>On <select name="parent">{{range .Devices}}{{if eq .Kind "physical"}}<option value="{{.Name}}">{{.Name}}</option>{{end}}{{end}}</select></label>
<label>VLAN ID <input name="id" size="5" inputmode="numeric"></label>
<button name="add" value="1">Add VLAN</button>
</form>
</fieldset>
<form method="post" action="/network">
<input type="hidden" name="csrf" value="{{$.CSRF}}">