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:
2026-07-20 03:57:16 +00:00
parent dadabd7954
commit 04417cf05c
19 changed files with 2688 additions and 455 deletions
+28 -1
View File
@@ -172,6 +172,30 @@ export default definePage({
</td>
</tr>`);
const _setDomain = async (domain) => {
const res = await apiFetch('/api/dhcp/domain', {
method: 'POST',
body: { domain },
});
if (res.ok) {
toast('DNS domain updated', 'success');
modelFetch('dnsmasq');
} else {
toast(res.error || 'Failed to update', 'error');
}
};
const currentDomain = dnsCfg.domain || null;
const domainSection = html`<div class="domain-config" style="margin-bottom: 1rem;">
<label style="font-weight: 600;">Search Domain</label>
<p class="text-sm" style="margin: 0.25rem 0 0.5rem;">${currentDomain ? esc(currentDomain) : '<span class="text-muted">(not set)</span>'}</p>
<div class="form-inline" style="display: flex; gap: 0.5rem; align-items: center;">
<input id="domain-input" class="input" placeholder="example.local" />
<button class="btn btn-outline" onClick=${() => _setDomain(($val('domain-input') || '').trim())}>Set</button>
<button class="btn btn-outline" onClick=${() => _setDomain(null)}>Clear</button>
</div>
</div>`;
const tabNames = ['ranges', 'leases', 'dns', 'active'];
const actions = ActionGroup(
html`<button class="btn btn-primary" onClick=${() => makeAddRange(state.firewall.data?.zones?.active, state.firewall.data?.interfaces)(state)}>Add Range</button>`,
@@ -222,7 +246,10 @@ export default definePage({
state.activeTab === 'leases'
? Table({ columns: ['MAC', 'IP', 'Hostname', 'Action'], rows: leaseRows, emptyText: 'No static leases' }) : null,
state.activeTab === 'dns'
? Table({ columns: ['Name', 'Address', 'Action'], rows: dnsRows, emptyText: 'No custom DNS records' }) : null,
? [
domainSection,
Table({ columns: ['Name', 'Address', 'Action'], rows: dnsRows, emptyText: 'No custom DNS records' })
] : null,
leaseTable,
];
},