Refactor UI assets and harden quality checks

This commit is contained in:
StasonJatham committed 2026-06-17 23:18:00 +02:00
1 parent 23ab466c52
commit 4ecc8eb874
62 files changed
+3314 -3206

No files matched your search

+112 -49
View File
@@ -31,6 +31,35 @@ class UpdateOutcome:
source_ip: str
@dataclass(frozen=True, slots=True)
class UpdateIpParams:
myip: str | None = None
ipaddr: str | None = None
myipv6: str | None = None
ip6addr: str | None = None
@property
def initial_plan_args(self) -> tuple[str | None, str | None, str | None, str | None]:
return self.myip, self.ipaddr, self.myipv6, self.ip6addr
def with_body_values(self, body_values: Mapping[str, str]) -> UpdateIpParams:
return UpdateIpParams(
myip=self.myip or body_values.get("myip"),
ipaddr=self.ipaddr or body_values.get("ipaddr"),
myipv6=self.myipv6 or body_values.get("myipv6"),
ip6addr=self.ip6addr or body_values.get("ip6addr"),
)
@dataclass(frozen=True, slots=True)
class InitialDnsPlan:
ipv4: str | None
ipv6: str | None
update_ipv4: bool
update_ipv6: bool
detail: str
class BadIpError(ValueError):
"""Raised when a supplied DynDNS IP parameter is invalid."""
@@ -71,36 +100,21 @@ class DdnsService:
self,
request: Request,
hostname: str,
myip: str | None,
ipaddr: str | None,
myipv6: str | None,
ip6addr: str | None,
ip_params: UpdateIpParams,
) -> bool:
if not dns_backend_configured(self.settings):
return False
source_ip = client_ip(request, self.settings)
source_fallback = source_ip if _is_global_ip(source_ip) else ""
supplied_ip = any(value and value.strip() for value in (myip, ipaddr, myipv6, ip6addr))
ipv4: str | None
ipv6: str | None
if not supplied_ip and not source_fallback:
ipv4 = INITIAL_PLACEHOLDER_IPV4
ipv6 = None
update_ipv4 = True
update_ipv6 = False
detail = "initial placeholder dns registered"
else:
ipv4, ipv6, update_ipv4, update_ipv6 = parse_update_ips(
source_fallback,
myip,
ipaddr,
myipv6,
ip6addr,
)
detail = "initial dns registered"
plan = _initial_dns_plan(source_ip, *ip_params.initial_plan_args)
lock = self._lock_for(hostname)
with lock:
planned = self.store.preview_update(hostname, ipv4, ipv6, update_ipv4=update_ipv4, update_ipv6=update_ipv6)
planned = self.store.preview_update(
hostname,
plan.ipv4,
plan.ipv6,
update_ipv4=plan.update_ipv4,
update_ipv6=plan.update_ipv6,
)
if planned.changed:
try:
publish_dns(planned, self.settings)
@@ -113,7 +127,12 @@ class DdnsService:
type(exc).__name__,
)
self.store.log_update_event(
hostname, ipv4, ipv6, DNS_RECONCILE_FAILED, f"{type(exc).__name__}: {exc}", source_ip
hostname,
plan.ipv4,
plan.ipv6,
DNS_RECONCILE_FAILED,
f"{type(exc).__name__}: {exc}",
source_ip,
)
raise
except DnsProviderError as exc:
@@ -125,21 +144,23 @@ class DdnsService:
type(exc).__name__,
)
self.store.log_update_event(
hostname, ipv4, ipv6, DNS_FAILED, f"{type(exc).__name__}: {exc}", source_ip
hostname,
plan.ipv4,
plan.ipv6,
DNS_FAILED,
f"{type(exc).__name__}: {exc}",
source_ip,
)
raise
result = self.store.commit_update_result(planned)
self.store.log_update_event(hostname, result.ipv4, result.ipv6, UPDATED, detail, source_ip)
self.store.log_update_event(hostname, result.ipv4, result.ipv6, UPDATED, plan.detail, source_ip)
return True
def apply_update(
self,
request: Request,
hostname: str,
myip: str | None,
ipaddr: str | None,
myipv6: str | None,
ip6addr: str | None,
ip_params: UpdateIpParams,
) -> UpdateOutcome:
started_at = time.perf_counter()
is_success = False
@@ -150,10 +171,7 @@ class DdnsService:
try:
ipv4, ipv6, update_ipv4, update_ipv6 = parse_update_ips(
source_ip,
myip,
ipaddr,
myipv6,
ip6addr,
*ip_params.initial_plan_args,
)
lock = self._lock_for(hostname)
with lock:
@@ -248,13 +266,8 @@ class DdnsService:
def normalize_ip(value: str | None, version: int | None = None) -> str | None:
if not value or not value.strip():
return None
try:
ip = ipaddress.ip_address(value.strip())
except ValueError:
return None
if version and ip.version != version:
ip = _optional_ip(value)
if ip is None or (version and ip.version != version):
return None
return str(ip)
@@ -280,6 +293,42 @@ def parse_update_ips(
return ipv4, ipv6, update_ipv4, update_ipv6
def _initial_dns_plan(
source_ip: str,
myip: str | None,
ipaddr: str | None,
myipv6: str | None,
ip6addr: str | None,
) -> InitialDnsPlan:
source_fallback = source_ip if _is_global_ip(source_ip) else ""
if not _has_supplied_ip(myip, ipaddr, myipv6, ip6addr) and not source_fallback:
return InitialDnsPlan(
ipv4=INITIAL_PLACEHOLDER_IPV4,
ipv6=None,
update_ipv4=True,
update_ipv6=False,
detail="initial placeholder dns registered",
)
ipv4, ipv6, update_ipv4, update_ipv6 = parse_update_ips(
source_fallback,
myip,
ipaddr,
myipv6,
ip6addr,
)
return InitialDnsPlan(
ipv4=ipv4,
ipv6=ipv6,
update_ipv4=update_ipv4,
update_ipv6=update_ipv6,
detail="initial dns registered",
)
def _has_supplied_ip(*values: str | None) -> bool:
return any(value and value.strip() for value in values)
def _is_global_ip(value: str | None) -> bool:
if not value:
return False
@@ -294,17 +343,31 @@ def _first_valid_ip(params: tuple[tuple[str, str | None], ...], version: int) ->
for name, value in params:
if value is None or not value.strip():
continue
try:
ip = ipaddress.ip_address(value.strip())
except ValueError as exc:
raise BadIpError(f"{name} must be a valid IPv{version} address") from exc
if ip.version != version:
raise BadIpError(f"{name} must be an IPv{version} address")
ip_value = _required_ip_param(name, value, version)
if selected is None:
selected = str(ip)
selected = ip_value
return selected
def _optional_ip(value: str | None) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
if not value or not value.strip():
return None
try:
return ipaddress.ip_address(value.strip())
except ValueError:
return None
def _required_ip_param(name: str, value: str, version: int) -> str:
try:
ip = ipaddress.ip_address(value.strip())
except ValueError as exc:
raise BadIpError(f"{name} must be a valid IPv{version} address") from exc
if ip.version != version:
raise BadIpError(f"{name} must be an IPv{version} address")
return str(ip)
def normalize_hostname(value: str, settings: DdnsSettings, expand_suffix: bool = True) -> str:
hostname = value.strip().lower().rstrip(".")
suffix = settings.hostname_suffix.strip().lower().strip(".")