Fix dashboard template bugs, acme date parsing, wireguard sudoers match, and stale docs

- dashboard.html: Fix zones, leases, wg_status, cert key names, add services var
- server.py: Pass services to dashboard template via _get_service_status()
- lib/acme.py: Fix dead third date format (%Y%m%d%H%M%z) using astimezone(UTC)
- lib/wireguard.py: Add -- separator to cp command to match sudoers rule
- lib/nginx.py: Replace shallow dict.copy() with {**...} for DEFAULT_SSL
- AGENTS.md: Update test count 149 -> 154
- docs/api.md: Rename cert field expiry -> expires_at
This commit is contained in:
2026-05-08 19:11:54 +00:00
parent e2f56b8cc8
commit 65741644a3
26 changed files with 384 additions and 200 deletions
+19 -9
View File
@@ -30,10 +30,7 @@ def _error(msg, code=400):
def _ok(data=None):
body = {"ok": True}
if data is not None:
body["data"] = data
return jsonify(body)
return jsonify({"ok": True, "data": data})
def _deep_merge(base, overrides):
@@ -66,7 +63,7 @@ def post_config():
return _error("Request body must be a JSON object", 400)
try:
save_config(body)
return _ok(body)
return _ok(None)
except RuntimeError as exc:
return _error(str(exc), 500)
@@ -80,7 +77,7 @@ def patch_config():
current = get_config()
merged = _deep_merge(current, body)
save_config(merged)
return _ok(merged)
return _ok(None)
except RuntimeError as exc:
return _error(str(exc), 500)
@@ -89,7 +86,7 @@ def patch_config():
def apply_bp():
try:
apply_config()
return _ok({"message": "dnsmasq configuration applied"})
return _ok(None)
except RuntimeError as exc:
return _error(str(exc), 500)
@@ -132,9 +129,16 @@ def remove_static_lease_bp():
mac = request.args.get("mac", "").strip()
if not mac:
return _error("Query parameter 'mac' is required", 400)
current = get_config()
found = any(
lease["mac"].lower() == mac.lower()
for lease in current.get("dhcp", {}).get("static_leases", [])
)
if not found:
return _error(f"No static lease found for MAC '{mac}'", 404)
try:
remove_static_lease(mac)
return _ok({"mac": mac})
return _ok(None)
except RuntimeError as exc:
return _error(str(exc), 500)
@@ -164,8 +168,14 @@ def remove_dns_record_bp():
name = request.args.get("name", "").strip()
if not name:
return _error("Query parameter 'name' is required", 400)
current = get_config()
found = any(
r["name"] == name for r in current.get("dns", {}).get("custom_records", [])
)
if not found:
return _error(f"No DNS record found for '{name}'", 404)
try:
remove_dns_record(name)
return _ok({"name": name})
return _ok(None)
except RuntimeError as exc:
return _error(str(exc), 500)