Files
nixos-router/pkgs/router-webui/templates/network.html
T
nilsandClaude Sonnet 5.5 b1fa7b1396 Add token expiry, edit notice, log allowlist, local-zone test; merge PLAN.md into SPECS.md
get_logs needs a unit from an allowlist (router-*, kea, unbound, networkd,
nftables, ddclient, acme, sshd, kernel). API tokens are valid for 1, 7, 30
(default) or 90 days, or without limit. The overview and the Changes page
say which token made the unapplied changes.

options.json (router://options) no longer holds the options' source paths:
they differ between builds of a release, so a router rebuilding itself
offline had to build the file and failed (found by the update test).

Web UI: panels flow into columns on wide screens and the width is no longer
capped; on phones the navigation is a menu button; the Advanced page uses
panels; VLAN dropdowns show their chosen names; the duplicate WireGuard
heading is gone.

basic: a subtest for wan -> local rules.

SPECS.md is now the only document (decisions, how each part works, and what
is not done yet); PLAN.md is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
2026-11-01 19:06:27 +01:00

119 lines
6.8 KiB
HTML

{{define "content"}}
{{with .Data}}
{{$e := .Errors}}
<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>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>
<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}}">
<fieldset>
<legend>System</legend>
<label>Host name <input name="hostName" value="{{.Form.HostName}}"></label>
{{with index $e "hostName"}}<span class="field-error">{{.}}</span>{{end}}
<label>Local domain <input name="domain" value="{{.Form.Domain}}" placeholder="home.example.org" spellcheck="false"></label>
{{with index $e "domain"}}<span class="field-error">{{.}}</span>{{end}}
<p class="hint">Optional. The router answers for it, publishes DHCP reservations in it, and clients get it as
their search domain, so <code>nas</code> finds <code>nas.home.example.org</code>.</p>
</fieldset>
<fieldset>
<legend>Uplink (WAN)</legend>
<label>Interface
<select name="wan.interface">{{$wan := .Form.WAN.Interface}}
{{range choices $.Data.Devices $wan}}<option value="{{.}}" {{if eq . $wan}}selected{{end}}>{{devLabel $.Data.Devices .}}</option>{{end}}
</select>
</label>
{{with index $e "wan.interface"}}<span class="field-error">{{.}}</span>{{end}}
<label>Type
<select name="wan.type">
<option value="dhcp" {{if eq .Form.WAN.Type "dhcp"}}selected{{end}}>DHCP</option>
<option value="pppoe" {{if eq .Form.WAN.Type "pppoe"}}selected{{end}}>PPPoE</option>
</select>
</label>
<label><input type="checkbox" name="wan.ipv6" {{if .Form.WAN.IPv6}}checked{{end}}> IPv6 (prefix delegation from the provider; also tick it on the LANs below)</label>
<div class="sub">
<p class="hint">PPPoE only:</p>
<label>Username <input name="wan.pppoe.username" value="{{.Form.WAN.PPPoEUser}}" autocomplete="off"></label>
{{with index $e "wan.pppoe.username"}}<span class="field-error">{{.}}</span>{{end}}
<label>Password <input type="password" name="wan.pppoe.password" autocomplete="new-password"
placeholder="{{if .Form.WAN.PasswordSet}}stored — leave empty to keep{{end}}"></label>
{{with index $e "wan.pppoe.password"}}<span class="field-error">{{.}}</span>{{end}}
</div>
</fieldset>
<fieldset>
<legend>LANs</legend>
{{with index $e "lans"}}<p class="field-error">{{.}}</p>{{end}}
<input type="hidden" name="lans" value="{{len .Form.LANs}}">
<table>
<tr><th>Name</th><th>Interface</th><th>Address</th><th>Prefix</th><th>Internet access</th><th>IPv6</th><th>DHCP</th><th>Pool start</th><th>Pool end</th><th>Remove</th></tr>
{{range $i, $l := .Form.LANs}}
<tr>
<td><input name="lan.{{$i}}.name" value="{{$l.Name}}" size="8" placeholder="new"></td>
<td><select name="lan.{{$i}}.interface">{{$cur := $l.Interface}}
{{if not $cur}}<option value="" selected></option>{{end}}
{{range choices $.Data.Devices $cur}}<option value="{{.}}" {{if eq . $cur}}selected{{end}}>{{devLabel $.Data.Devices .}}</option>{{end}}
</select></td>
<td><input name="lan.{{$i}}.address" value="{{$l.Address}}" size="13"></td>
<td><input name="lan.{{$i}}.prefix" value="{{$l.Prefix}}" size="2" inputmode="numeric" placeholder="24"></td>
<td><input type="checkbox" name="lan.{{$i}}.internet" {{if $l.Internet}}checked{{end}}></td>
<td><input type="checkbox" name="lan.{{$i}}.ipv6pd" {{if $l.IPv6PD}}checked{{end}}></td>
<td><input type="checkbox" name="lan.{{$i}}.dhcp" {{if $l.DHCP}}checked{{end}}></td>
<td><input name="lan.{{$i}}.poolStart" value="{{$l.PoolStart}}" size="13"></td>
<td><input name="lan.{{$i}}.poolEnd" value="{{$l.PoolEnd}}" size="13"></td>
<td>{{if $l.Name}}<input type="checkbox" name="lan.{{$i}}.remove">{{end}}</td>
</tr>
{{range $f := list "name" "interface" "address" "prefix" "poolStart" "poolEnd"}}{{with index $e (printf "lans[%d].%s" $i $f)}}<tr><td colspan="10" class="field-error">{{$f}}: {{.}}</td></tr>{{end}}{{end}}
{{end}}
</table>
<p class="hint">Fill in the last row to add a LAN. Other LAN settings (zones, a fixed IPv6 address) are kept; edit them under Advanced.</p>
</fieldset>
<button>Save</button>
</form>
{{end}}
{{end}}