feat: replace Dynamic DNS YAML editor with a web form

Adds structured form fields for nsupdate_bin, rndc_key, and dyndomains
(comma-separated list). Wires list-type editable fields through the
generic stageFormSection path and adds DNS support to
apply_structured_section in configio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Andreas Wrede
2026-05-13 07:12:44 -04:00
parent 8b2b0fd9d0
commit 69b5b410ed
4 changed files with 33 additions and 6 deletions
+6
View File
@@ -88,6 +88,12 @@ def apply_structured_section(data, section: str, values: dict) -> None:
for key in _SERVER_KEYS: for key in _SERVER_KEYS:
if key in values: if key in values:
data[key] = values[key] data[key] = values[key]
elif section == "dns":
for key in _DNS_KEYS:
if key in values:
data[key] = values[key]
else:
data.pop(key, None)
elif section == "users": elif section == "users":
data["users"] = values data["users"] = values
elif section == "hosts": elif section == "hosts":
+9 -3
View File
@@ -1304,9 +1304,15 @@ async def start(
attrs.pop("client_secret", None) attrs.pop("client_secret", None)
data["oauth"] = new_oauth data["oauth"] = new_oauth
for section in ("notification_channels", "dns"): if "notification_channels" in payload:
if section in payload: configio_mod.apply_yaml_section(data, "notification_channels", payload["notification_channels"])
configio_mod.apply_yaml_section(data, section, payload[section])
if "dns" in payload:
dns_payload = payload["dns"]
if isinstance(dns_payload, str):
configio_mod.apply_yaml_section(data, "dns", dns_payload)
else:
configio_mod.apply_structured_section(data, "dns", dns_payload)
if "thresholds" in payload: if "thresholds" in payload:
tc = payload["thresholds"] tc = payload["thresholds"]
+11 -3
View File
@@ -398,10 +398,18 @@ def get_settings_sections(config: dict, threshold_checker=None) -> list:
{ {
"id": "dns", "id": "dns",
"title": "Dynamic DNS", "title": "Dynamic DNS",
"description": "nsupdate-based DNS registration — edit raw YAML.", "description": "nsupdate-based DNS registration via nsupdate(8).",
"section_mode": "yaml", "section_mode": "form",
"api_section": "dns", "api_section": "dns",
"fields": [], "fields": [
field("nsupdate_bin", "nsupdate binary", "path",
"Path to the nsupdate binary.", editable=True),
field("rndc_key", "RNDC key file", "path",
"Path to the rndc key file used to authenticate DNS updates.", editable=True),
field("dyndomains", "Dynamic domains", "list",
"Domains updated via nsupdate when a host with dyndns: true reports in.",
editable=True),
],
}, },
{ {
"id": "users", "id": "users",
+7
View File
@@ -820,6 +820,11 @@
<input type="number" class="field-input" <input type="number" class="field-input"
data-key="{{ f.key }}" data-type="{{ f.type }}" data-section="{{ section.api_section }}" data-key="{{ f.key }}" data-type="{{ f.type }}" data-section="{{ section.api_section }}"
value="{{ f.raw if f.raw is not none else '' }}"> value="{{ f.raw if f.raw is not none else '' }}">
{% elif f.type == 'list' %}
<input type="text" class="field-input"
data-key="{{ f.key }}" data-type="list" data-section="{{ section.api_section }}"
value="{{ f.value | join(', ') if f.value else '' }}"
placeholder="comma-separated">
{% else %} {% else %}
<input type="text" class="field-input" <input type="text" class="field-input"
data-key="{{ f.key }}" data-section="{{ section.api_section }}" data-key="{{ f.key }}" data-section="{{ section.api_section }}"
@@ -1023,6 +1028,8 @@
} else if (el.dataset.type === 'number' || el.dataset.type === 'port') { } else if (el.dataset.type === 'number' || el.dataset.type === 'port') {
const v = parseInt(el.value, 10); const v = parseInt(el.value, 10);
_staged[apiSection][key] = isNaN(v) ? null : v; _staged[apiSection][key] = isNaN(v) ? null : v;
} else if (el.dataset.type === 'list') {
_staged[apiSection][key] = el.value.split(',').map(s => s.trim()).filter(Boolean);
} else { } else {
_staged[apiSection][key] = el.value; _staged[apiSection][key] = el.value;
} }