WireGuard access classes, firewall nftables fixes, network sync event refactor
- WireGuard: refactor to multi-interface 'access classes' model; extract config generation and helpers into lib/wireguard.py; add per-class up/down endpoints and API routes; update UI with class management pages and QR code component - Firewall: fix zone creation with --new-zone before --set-target; skip masquerade on public zone; add masquerade propagation for nftables backend so NAT works when internal zones exit via public - Network: rename sync event subsystem 'network' -> 'networkd'; always stamp config hash even when deployment fails (fixes pending-changes detection) - DHCP: add new API endpoint and update frontend page - State/Sync: update state collectors and sync buses for new subsystems - Docs: update API and config documentation for new endpoints and schemas
This commit is contained in:
+40
-21
@@ -38,13 +38,16 @@ export default definePage({
|
||||
const zoneData = cfg.zones || {};
|
||||
|
||||
const sIface = (state.firewall.data?.state || {}).interfaces || [];
|
||||
const masqZones = new Set(
|
||||
// With nftables, masquerade is propagated to the public zone at runtime for
|
||||
// POSTROUTING to work. The config-side masquerade flag indicates which
|
||||
// zones source NAT traffic (LAN / internal), not where traffic exits (WAN).
|
||||
const lanZones = new Set(
|
||||
Object.entries(zoneData)
|
||||
.filter(([, zcfg]) => !!zcfg.masquerade)
|
||||
.map(([z]) => z)
|
||||
);
|
||||
const wanIface = sIface.filter((i) => i.zone && masqZones.has(i.zone));
|
||||
const lanIface = sIface.filter((i) => i.zone && !masqZones.has(i.zone));
|
||||
const wanIface = sIface.filter((i) => i.zone && !lanZones.has(i.zone));
|
||||
const lanIface = sIface.filter((i) => i.zone && lanZones.has(i.zone));
|
||||
|
||||
const ifaceRows = (ifaces) =>
|
||||
ifaces.map((iface) => html`<tr key=${'ii-' + iface.name}>
|
||||
@@ -60,24 +63,40 @@ export default definePage({
|
||||
<td><${Badge} text=${iface.zone || '—'} variant="secondary" /></td>
|
||||
</tr>`);
|
||||
|
||||
// Build set of non-public zones with masquerade — determines if public is auto-propagated
|
||||
const anyNonPublicMasq = Object.entries(zoneData)
|
||||
.filter(([zone]) => zone !== "public")
|
||||
.some(([, zcfg]) => !!zcfg.masquerade);
|
||||
|
||||
const masqRows = Object.entries(zoneData)
|
||||
.filter(([zone]) => zone !== "public")
|
||||
.map(([zone, zcfg]) => {
|
||||
const masq = !!zcfg.masquerade;
|
||||
return html`<tr key=${'m-' + zone}>
|
||||
<td><strong>${zone}</strong></td>
|
||||
<td><${Badge} text=${masq ? 'Enabled' : 'Disabled'} variant=${masq ? 'success' : 'info'} /></td>
|
||||
<td>
|
||||
<${ActionButton}
|
||||
url="/api/firewall/masquerade"
|
||||
cls="btn btn-sm btn-outline"
|
||||
labelOn="Disable" labelOff="Enable" condition=${masq}
|
||||
body=${() => ({ zone, enable: !masq })}
|
||||
successMsg=${'Masquerade ' + (masq ? 'disabled' : 'enabled') + ' on ' + zone}
|
||||
refresh="firewall" />
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
.map(([zone, zcfg]) => {
|
||||
const masq = !!zcfg.masquerade;
|
||||
const isPublic = zone === "public";
|
||||
// Public zone masquerade is auto-propagated when any non-public zone
|
||||
// has it enabled (nftables backend dispatches POSTROUTING to the
|
||||
// output interface's zone chain). Show it read-only with a note.
|
||||
if (isPublic) {
|
||||
const effective = masq || anyNonPublicMasq;
|
||||
return html`<tr key=${'m-' + zone}>
|
||||
<td><strong>${zone}</strong> <span class="text-muted">(auto)</span></td>
|
||||
<td><${Badge} text=${effective ? 'Enabled' : 'Disabled'} variant=${effective ? 'success' : 'info'} /></td>
|
||||
<td><span class="text-muted">${anyNonPublicMasq ? 'Propagated from other zones' : 'Not needed'}</span></td>
|
||||
</tr>`;
|
||||
}
|
||||
return html`<tr key=${'m-' + zone}>
|
||||
<td><strong>${zone}</strong></td>
|
||||
<td><${Badge} text=${masq ? 'Enabled' : 'Disabled'} variant=${masq ? 'success' : 'info'} /></td>
|
||||
<td>
|
||||
<${ActionButton}
|
||||
url="/api/firewall/masquerade"
|
||||
cls="btn btn-sm btn-outline"
|
||||
labelOn="Disable" labelOff="Enable" condition=${masq}
|
||||
body=${() => ({ zone, enable: !masq })}
|
||||
successMsg=${'Masquerade ' + (masq ? 'disabled' : 'enabled') + ' on ' + zone}
|
||||
refresh="firewall" />
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const fwRows = [];
|
||||
Object.entries(zoneData).forEach(([zone, zcfg]) => {
|
||||
@@ -109,7 +128,7 @@ export default definePage({
|
||||
title: 'WAN / External',
|
||||
columns: ['Interface', 'IPv4', 'IPv6', 'MAC', 'Zone'],
|
||||
rows: ifaceRows(wanIface),
|
||||
emptyText: 'No WAN interfaces with masquerade enabled',
|
||||
emptyText: 'No WAN interfaces',
|
||||
}),
|
||||
DataTableSection({
|
||||
title: 'Internal / LAN',
|
||||
|
||||
Reference in New Issue
Block a user