Zone-based firewall page, per-sensor temperatures, 15 minute default

The firewall page now manages zones (assign each LAN or tunnel, add and
remove zones) and rules with zone, address and port on both sides plus the
protocol. router.firewall.zones declares zones without members and rules
take sourcePorts. The temperature chart draws every thermal zone and hwmon
sensor; the overview defaults to 15 minutes, 1h stays an option.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-10-30 09:26:25 +01:00
co-authored by Claude Sonnet 5.5
parent e2986f1418
commit eda7080d94
14 changed files with 713 additions and 149 deletions
+44 -10
View File
@@ -1,35 +1,69 @@
{{define "content"}}
{{with .Data}}
{{$e := .Errors}}
<p class="hint">Traffic between zones is denied unless a rule allows it. A rule works in one direction only
(replies are always allowed), so letting two networks talk to each other takes two rules.
Traffic inside a zone is always allowed. A zone is a LAN or a WireGuard tunnel (named on the WireGuard page).</p>
<p class="hint">Every LAN and tunnel belongs to a zone. Traffic inside a zone is allowed; between zones it is denied
unless a rule allows it. A rule works in one direction (replies are always allowed), so letting two zones talk
to each other takes two rules.</p>
<form method="post" action="/firewall">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<fieldset>
<legend>Allow rules</legend>
<input type="hidden" name="rows" value="{{len .Rows}}">
<legend>Zones</legend>
<input type="hidden" name="members" value="{{len .Members}}">
<table>
<tr><th>From</th><th>To</th><th>Protocol</th><th>Ports</th><th>Only to address</th><th>Remove</th></tr>
<tr><th>Interface</th><th>Device</th><th>Zone</th></tr>
{{range $i, $m := .Members}}
<tr>
<td>{{if eq $m.Kind "lan"}}LAN{{else}}Tunnel{{end}} <strong>{{$m.Name}}</strong>
<input type="hidden" name="member.{{$i}}.kind" value="{{$m.Kind}}">
<input type="hidden" name="member.{{$i}}.name" value="{{$m.Name}}">
<input type="hidden" name="member.{{$i}}.device" value="{{$m.Device}}"></td>
<td><code>{{$m.Device}}</code></td>
<td><select name="member.{{$i}}.zone">
{{range $.Data.Zones}}<option value="{{.}}" {{if eq . $m.Zone}}selected{{end}}>{{.}}</option>{{end}}
</select></td>
</tr>
{{with index $e (printf "member[%d]" $i)}}<tr><td colspan="3" class="field-error">{{.}}</td></tr>{{end}}
{{end}}
</table>
<p class="hint">Several interfaces can share a zone. The uplink is the zone <code>wan</code>; rules from it publish services.</p>
{{range .Empty}}
<p>Zone <strong>{{.}}</strong> has no interface. <label class="inline"><input type="checkbox" name="rmzone" value="{{.}}"> remove</label>
{{with index $e (printf "zone.%s" .)}}<span class="field-error">{{.}}</span>{{end}}</p>
{{end}}
<label>New zones <input name="newzones" value="{{.New}}" size="24" placeholder="guests, iot"></label>
{{with index $e "newzone"}}<span class="field-error">{{.}}</span>{{end}}
<p class="hint">A new zone can be picked above (and in the rules) after saving.</p>
</fieldset>
<fieldset>
<legend>Rules</legend>
<input type="hidden" name="rows" value="{{len .Rows}}">
<div class="scroll">
<table>
<tr><th>From zone</th><th>From address</th><th>From port</th><th>To zone</th><th>To address</th><th>To port</th><th>Protocol</th><th>Remove</th></tr>
{{range $i, $r := .Rows}}
<tr>
<td><select name="rule.{{$i}}.from"><option value=""></option>
{{range $.Data.Zones}}<option value="{{.}}" {{if eq . $r.From}}selected{{end}}>{{.}}</option>{{end}}
</select></td>
<td><input name="rule.{{$i}}.sources" value="{{$r.Sources}}" size="16" spellcheck="false" placeholder="any"></td>
<td><input name="rule.{{$i}}.sourceports" value="{{$r.SourcePorts}}" size="8" placeholder="any"></td>
<td><select name="rule.{{$i}}.to"><option value=""></option>
{{range $.Data.Zones}}<option value="{{.}}" {{if eq . $r.To}}selected{{end}}>{{.}}</option>{{end}}
</select></td>
<td><input name="rule.{{$i}}.destination" value="{{$r.Destination}}" size="16" spellcheck="false" placeholder="whole zone"></td>
<td><input name="rule.{{$i}}.ports" value="{{$r.Ports}}" size="10" placeholder="all"></td>
<td><select name="rule.{{$i}}.protocol">
{{range $p := list "any" "tcp" "udp" "icmp"}}<option value="{{$p}}" {{if eq $p $r.Protocol}}selected{{end}}>{{$p}}</option>{{end}}
</select></td>
<td><input name="rule.{{$i}}.ports" value="{{$r.Ports}}" size="14" placeholder="all"></td>
<td><input name="rule.{{$i}}.destination" value="{{$r.Destination}}" size="16" spellcheck="false" placeholder="whole zone"></td>
<td>{{if or $r.From $r.To}}<input type="checkbox" name="rule.{{$i}}.remove">{{end}}</td>
</tr>
{{range $f := list "from" "to" "protocol" "ports" "destination"}}{{with index $e (printf "rule[%d].%s" $i $f)}}<tr><td colspan="6" class="field-error">{{$f}}: {{.}}</td></tr>{{end}}{{end}}
{{range $f := list "from" "sources" "sourcePorts" "to" "destination" "ports" "protocol"}}{{with index $e (printf "rule[%d].%s" $i $f)}}<tr><td colspan="8" class="field-error">{{$f}}: {{.}}</td></tr>{{end}}{{end}}
{{end}}
</table>
<p class="hint">Fill in the last row to add a rule. Ports apply to tcp and udp: <code>22, 443, 5000-5100</code>.</p>
</div>
<p class="hint">Fill in the last row to add a rule. Addresses (several separated by commas) are IPv4 or IPv6, single or CIDR;
to address is one address or network. Ports (tcp and udp only): <code>22, 443, 5000-5100</code>.</p>
</fieldset>
<button>Save</button>
</form>