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
+31
View File
@@ -19,6 +19,7 @@ from daemon.iface import (
POST_DNSMASQ_APPLY,
POST_DNSMASQ_CONFIG,
POST_DNSMASQ_DNS_RECORD_ADD,
POST_DNSMASQ_DOMAIN,
POST_DNSMASQ_RANGES_ADD,
POST_DNSMASQ_STATIC_LEASE_ADD,
)
@@ -306,6 +307,36 @@ def add_dns_record_bp():
return _error(str(exc), 500)
# ---------------------------------------------------------------------------
# DNS domain
# ---------------------------------------------------------------------------
@bp.route("/domain", methods=["POST"])
def set_domain_bp():
"""POST /api/dhcp/domain — Set or clear the DNS search domain.
Args:
request: JSON body with `domain` field (string or null to clear).
Returns:
JSON response with success status or an error.
"""
body = request.get_json(silent=True) or {}
if not isinstance(body, dict):
return _error("Request body must be a JSON object", 400)
try:
post(POST_DNSMASQ_DOMAIN, {"domain": body.get("domain")})
logger.info("DNS domain updated via API: %s", body.get("domain"))
return _ok(None)
except BadRequest as exc:
logger.info("Set DNS domain rejected: %s", exc)
return _error(str(exc), 400)
except RuntimeError as exc:
logger.error("Failed to set DNS domain: %s", exc)
return _error(str(exc), 500)
@bp.route("/dns-record/<name>", methods=["DELETE"])
def remove_dns_record_bp(name):
"""DELETE /api/dhcp/dns-record/<name> — Remove a DNS record by name.