fix: silent autosave, pending dedup, domain/cell_name pending, containers access

- Settings: remove Save buttons; autosave is silent (no toast on success, error only)
- Settings: loadAll() resets dirty flags to prevent stale autosave after discard
- app.py: fix domain/ip_range "actually changed" check — full identity is always
  sent on save so these were triggering pending on every keystroke regardless
- app.py: _dedup_changes handles port-change format "service field: old → new"
  (split on ':' not ' changed') so dns_port changed twice shows one entry
- app.py: domain + cell_name changes now go through pending restart banner;
  apply_domain/apply_cell_name write files immediately (reload=False) and set
  pending; Discard restores zone files + Caddyfile to pre-change state
- app.py: _set_pending_restart captures pre-change snapshot BEFORE config writes
  (was snapshotting after, making Discard a no-op)
- app.py: is_local_request reads /proc/net/route to allow the actual Docker
  bridge subnet (172.0.0.0/24) which is not RFC-1918; fixes Containers page 403
- container_manager: get_container_logs raises instead of swallowing exceptions
  so nonexistent container returns 500+error not 200+empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 07:16:13 -04:00
parent 4215e03ac6
commit 2bd6545f0e
5 changed files with 184 additions and 103 deletions
+24 -19
View File
@@ -5,6 +5,7 @@ Handles DNS, DHCP, and NTP functionality
"""
import os
import re
import json
import subprocess
import logging
@@ -383,8 +384,11 @@ class NetworkManager(BaseServiceManager):
return {'restarted': restarted, 'warnings': warnings}
def apply_domain(self, domain: str) -> Dict[str, Any]:
"""Update domain across dnsmasq, Corefile, and zone file; reload DNS + DHCP."""
def apply_domain(self, domain: str, reload: bool = True) -> Dict[str, Any]:
"""Update domain across dnsmasq, Corefile, and zone file; reload DNS + DHCP.
reload=False writes config files only — use when deferring container restart.
"""
restarted = []
warnings = []
@@ -400,8 +404,9 @@ class NetworkManager(BaseServiceManager):
]
with open(dhcp_conf, 'w') as f:
f.writelines(lines)
self._reload_dhcp_service()
restarted.append('cell-dhcp (reloaded)')
if reload:
self._reload_dhcp_service()
restarted.append('cell-dhcp (reloaded)')
except Exception as e:
warnings.append(f"dnsmasq domain update failed: {e}")
@@ -424,8 +429,6 @@ class NetworkManager(BaseServiceManager):
dns_data = os.path.join(self.data_dir, 'dns')
if os.path.isdir(dns_data):
dst = os.path.join(dns_data, f'{domain}.zone')
# Find the best source: prefer a non-target zone (old domain) so we
# can migrate its content; fall back to the target zone itself.
zone_files = [
os.path.join(dns_data, f)
for f in os.listdir(dns_data)
@@ -443,7 +446,6 @@ class NetworkManager(BaseServiceManager):
r'^\$ORIGIN\s+\S+', f'$ORIGIN {domain}.', zone_content, flags=re.MULTILINE)
with open(dst, 'w') as f:
f.write(zone_content)
# Remove every zone file that is not the current domain's file
for zone_path in zone_files:
if zone_path != dst:
try:
@@ -453,17 +455,21 @@ class NetworkManager(BaseServiceManager):
except Exception as e:
warnings.append(f"zone file domain update failed: {e}")
# 4. Reload CoreDNS
try:
self._reload_dns_service()
restarted.append('cell-dns (reloaded)')
except Exception as e:
warnings.append(f"CoreDNS reload failed: {e}")
# 4. Reload CoreDNS (only when not deferring to Apply)
if reload:
try:
self._reload_dns_service()
restarted.append('cell-dns (reloaded)')
except Exception as e:
warnings.append(f"CoreDNS reload failed: {e}")
return {'restarted': restarted, 'warnings': warnings}
def apply_cell_name(self, old_name: str, new_name: str) -> Dict[str, Any]:
"""Update the cell hostname record in the primary DNS zone file."""
def apply_cell_name(self, old_name: str, new_name: str, reload: bool = True) -> Dict[str, Any]:
"""Update the cell hostname record in the primary DNS zone file.
reload=False writes the zone file only — use when deferring container restart.
"""
restarted = []
warnings = []
if not old_name or not new_name or old_name == new_name:
@@ -476,8 +482,6 @@ class NetworkManager(BaseServiceManager):
zone_file = os.path.join(dns_data, fname)
with open(zone_file) as f:
content = f.read()
# Replace hostname record: old_name IN A ...
import re
content = re.sub(
rf'^{re.escape(old_name)}(\s+IN\s+A\s+)',
f'{new_name}\\1',
@@ -486,8 +490,9 @@ class NetworkManager(BaseServiceManager):
with open(zone_file, 'w') as f:
f.write(content)
break
self._reload_dns_service()
restarted.append('cell-dns (reloaded)')
if reload:
self._reload_dns_service()
restarted.append('cell-dns (reloaded)')
except Exception as e:
warnings.append(f"cell_name DNS update failed: {e}")
return {'restarted': restarted, 'warnings': warnings}