From: Giorgio Ravera Date: Tue, 3 Feb 2026 22:13:42 +0000 (+0100) Subject: Improved configuration and routers X-Git-Url: http://git.giorgioravera.it/?a=commitdiff_plain;h=ea632f957b3b01077b1aa59260ccdb520c74ea8c;p=network-manager.git Improved configuration and routers --- diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index cd4b98e..c74b987 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -6,12 +6,6 @@ updates: interval: "weekly" open-pull-requests-limit: 10 - - package-ecosystem: "npm" - directory: "/frontend" - schedule: - interval: "weekly" - open-pull-requests-limit: 10 - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/README.md b/README.md index 767f67b..8170661 100644 --- a/README.md +++ b/README.md @@ -143,15 +143,13 @@ secrets: | `ADMIN_PASSWORD` | admin | Admin password (development) | | `ADMIN_PASSWORD_HASH_FILE` | /run/secrets/admin_password_hash | Admin password hash | | `SESSION_SECRET` | (auto-generated) | Session secret | -| `DNS_CFG_PATH` | /dns/etc | Bind9 Configuration folder | -| `DNS_HOST_FILE` | {DOMAIN}/hosts.inc | BIND9 Hosts file | -| `DNS_ALIAS_FILE` | {DOMAIN}/alias.inc | BIND9 Alias file | -| `DNS_REVERSE_FILE` | reverse/hosts.inc | BIND9 Reverse Hosts file | -| `DHCP_CFG_PATH` | /dns/etc | KEA Configuration folder | -| `DHCP4_HOST_FILE` | hosts-ipv4.json | KEA-DHCP4 Hosts file | -| `DHCP4_LEASES_FILE` | dhcp4.leases | KEA-DHCP4 leases file | -| `DHCP6_HOST_FILE` | hosts-ipv6.json | KEA-DHCP6 Hosts file | -| `DHCP6_LEASES_FILE` | dhcp6.leases | KEA-DHCP6 leases file | +| `DNS_HOST_FILE` | /dns/etc/{DOMAIN}/hosts.inc | BIND9 Hosts file | +| `DNS_ALIAS_FILE` | /dns/etc/{DOMAIN}/alias.inc | BIND9 Alias file | +| `DNS_REVERSE_FILE` | /dns/etc/reverse/hosts.inc | BIND9 Reverse Hosts file | +| `DHCP4_HOST_FILE` | /dhcp/etc/hosts-ipv4.json | KEA-DHCP4 Hosts file | +| `DHCP4_LEASES_FILE` | /dhcp/lib/dhcp4.leases | KEA-DHCP4 leases file | +| `DHCP6_HOST_FILE` | /dhcp/etc/hosts-ipv6.json | KEA-DHCP6 Hosts file | +| `DHCP6_LEASES_FILE` | /dhcp/lib/dhcp6.leases | KEA-DHCP6 leases file | --- diff --git a/backend/main.py b/backend/main.py index d7b7cf5..eeee807 100644 --- a/backend/main.py +++ b/backend/main.py @@ -57,12 +57,12 @@ def print_welcome(): settings.ADMIN_USER, masked_admin_pwd, masked_admin_hash, settings.ADMIN_PASSWORD_HASH_FILE ) logger.info( - "DNS: path=%s | host file=%s | alias file=%s | reverse file=%s", - settings.DNS_CFG_PATH, settings.DNS_HOST_FILE, settings.DNS_ALIAS_FILE, settings.DNS_REVERSE_FILE + "DNS: host file=%s | alias file=%s | reverse file=%s", + settings.DNS_HOST_FILE, settings.DNS_ALIAS_FILE, settings.DNS_REVERSE_FILE ) logger.info( - "DHCP: path=%s | ipv4 host file=%s | ipv6 host file=%s", - settings.DHCP_CFG_PATH, settings.DHCP4_HOST_FILE, settings.DHCP6_HOST_FILE + "DHCP: ipv4 host file=%s | ipv4 leases file=%s | ipv6 host file=%s | ipv6 leases file=%s", + settings.DHCP4_HOST_FILE, settings.DHCP4_LEASES_FILE, settings.DHCP6_HOST_FILE, settings.DHCP6_LEASES_FILE ) # ------------------------------------------------------------------------------ diff --git a/backend/routes/backup.py b/backend/routes/backup.py index e2d40b5..d0e2922 100644 --- a/backend/routes/backup.py +++ b/backend/routes/backup.py @@ -20,7 +20,7 @@ router = APIRouter() # API ENDPOINTS # --------------------------------------------------------- @router.get("/api/backup") -async def apt_dns_reload(request: Request): +async def api_dns_reload(request: Request): start_ns = time.monotonic_ns() # Inizializzazioni diff --git a/backend/routes/dhcp.py b/backend/routes/dhcp.py index 8287e93..c1712bb 100644 --- a/backend/routes/dhcp.py +++ b/backend/routes/dhcp.py @@ -4,9 +4,11 @@ from fastapi import APIRouter, Request, Response from fastapi.responses import FileResponse, JSONResponse, RedirectResponse import asyncio +import csv import json import os import ipaddress +from pathlib import Path import time # Import local modules from backend.db.hosts import get_hosts @@ -19,17 +21,13 @@ from log.log import setup_logging, get_logger router = APIRouter() # --------------------------------------------------------- -# API ENDPOINTS +# Reload # --------------------------------------------------------- -@router.get("/api/dhcp/reload") -async def apt_dhcp_reload(request: Request): - start_ns = time.monotonic_ns() +@router.post("/api/dhcp/reload") +async def api_dhcp_reload(request: Request): # Inizializzazioni - error = False - message = None - code = None - status = None + start_ns = time.monotonic_ns() kea4_hosts = [] kea6_hosts = [] @@ -54,43 +52,126 @@ async def apt_dhcp_reload(request: Request): # Save DHCP4 Configuration path = settings.DHCP4_HOST_FILE - data = {"hosts": kea4_hosts} + data = {"reservations": kea4_hosts} + full = json.dumps(data, indent=4, ensure_ascii=False) + fragment = full.strip()[1:-1].strip() + "\n" with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) + f.write(fragment) # Save DHCP6 Configuration path = settings.DHCP6_HOST_FILE - data = {"hosts": kea6_hosts} + data = {"reservations": kea6_hosts} + full = json.dumps(data, indent=4, ensure_ascii=False) + fragment = full.strip()[1:-1].strip() + "\n" with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) + f.write(fragment) + + # RELOAD DHCP + + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + payload = { + "code": "DHCP_RELOAD_OK", + "status": "success", + "message": "DHCP configuration reload successfully", + "details": {"took_ms": took_ms} + } + return JSONResponse(content=payload, status_code=200) + + except Exception as err: + get_logger("dhcp").exception("Error reloading DHCP: %s", str(err).strip()) + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + + payload = { + "code": "DHCP_RELOAD_ERROR", + "status": "failure", + "message": "Error reloading DHCP", + "details": {"took_ms": took_ms, "error": str(err).strip()} + } + return JSONResponse(content=payload, status_code=500) + +# --------------------------------------------------------- +# Get Leases +# --------------------------------------------------------- +@router.get("/api/dhcp/leases") +def api_dhcp_leases(request: Request): + + # Inizializzazioni + items = [] + + try: + path = Path(settings.DHCP4_LEASES_FILE) + if not path.exists(): + return JSONResponse( + content={"code": "DHCP_LEASES_NOT_FOUND", + "status": "failure", + "message": f"DHCP leases file not found: {str(path)}", + "details": {}}, + status_code=404 + ) + + def _to_int(v: str): + v = (v or "").strip() + if not v or v.lower() == "null": + return None + try: + return int(v) + except ValueError: + return None + + def _to_bool(v: str): + v = (v or "").strip().lower() + if v in ("true", "1", "yes", "y"): + return True + if v in ("false", "0", "no", "n"): + return False + return None + + def _norm(col: str) -> str: + col = (col or "").strip() + aliases = { + "client_id": "client-id", + "valid_lifetime": "valid-lft", + "subnet_id": "subnet-id", + "fqdn_fwd": "fqdn-fwd", + "fqdn_rev": "fqdn-rev", + "user_context": "user-context", + "pool_id": "pool-id", + } + return aliases.get(col, col) + + # Open the file in lettura (non locking): ok per kea memfile + with path.open("r", encoding="utf-8", newline="") as f: + reader = csv.DictReader(f) + if not reader.fieldnames: + return JSONResponse(content={"total": 0, "items": []}, status_code=200) + + for raw in reader: + rec = { _norm(k): (v if v is not None else "") for k, v in raw.items() } + + item = { + "address": rec.get("address", "").strip() or None, + "hwaddr": rec.get("hwaddr", "").strip() or None, + "client_id": rec.get("client-id", "").strip() or None, + "valid_lifetime": _to_int(rec.get("valid-lft", "")), + "expire": _to_int(rec.get("expire", "")), # epoch seconds + "subnet_id": _to_int(rec.get("subnet-id", "")), + "fqdn_fwd": _to_bool(rec.get("fqdn-fwd", "")), + "fqdn_rev": _to_bool(rec.get("fqdn-rev", "")), + "hostname": rec.get("hostname", "").strip() or None, + "state": _to_int(rec.get("state", "")), + "user_context": rec.get("user-context", "").strip() or None, # spesso JSON serializzato + "pool_id": _to_int(rec.get("pool-id", "")), + } + items.append(item) + + return JSONResponse(content={"total": len(items), "items": items}, status_code=200) except Exception as err: - get_logger("dhcp").exception("Error reloading DHCP: " + str(err).strip()) - error = True - #message = str(err).strip() - - if error: - code = "DHCP_RELOAD_ERROR" - # default del messaggio se vuoto o None - if not message: - message = "DHCP reload error" - status = "failure" - #http_status = 500 - else: - code = "DHCP_RELOAD_OK" - message = "DHCP configuration reload successfully" - status = "success" - #http_status = 200 - - took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 - - payload = { - "code": code, - "status": status, - "message": message, - "details": { - "took_ms": took_ms + get_logger("dhcp").exception("Error reading DHCP leases: %s", str(err).strip()) + payload = { + "code": "DHCP_LEASES_ERROR", + "status": "failure", + "message": "Error reading DHCP leases", + "details": {"error": str(err).strip()} } - } - return JSONResponse(content=payload) - #return JSONResponse(content=payload, status_code=http_status) + return JSONResponse(content=payload, status_code=500) diff --git a/backend/routes/dns.py b/backend/routes/dns.py index f068551..c1174b5 100644 --- a/backend/routes/dns.py +++ b/backend/routes/dns.py @@ -19,17 +19,13 @@ from log.log import setup_logging, get_logger router = APIRouter() # --------------------------------------------------------- -# API ENDPOINTS +# Reload # --------------------------------------------------------- -@router.get("/api/dns/reload") -async def apt_dns_reload(request: Request): - start_ns = time.monotonic_ns() +@router.post("/api/dns/reload") +async def api_dns_reload(request: Request): # Inizializzazioni - error = False - message = None - code = None - status = None + start_ns = time.monotonic_ns() try: # Get Hosts List @@ -46,40 +42,32 @@ async def apt_dns_reload(request: Request): path = settings.DNS_REVERSE_FILE with open(path, "w", encoding="utf-8") as f: for h in hosts: - ip = h.get('ipv4') + ip = h.get("ipv4") if ip: parts = ip.split(".") rev = f"{parts[-1]}.{parts[-2]}" line = f"{rev}\t\t IN PTR\t{h.get('name')}.{settings.DOMAIN}\n" f.write(line) - except Exception as err: - get_logger("dns").exception("Error reloading DNS: " + str(err).strip()) - error = True - #message = str(err).strip() + # RELOAD DNS - if error: - code = "DNS_RELOAD_ERROR" - # default del messaggio se vuoto o None - if not message: - message = "DNS reload error" - status = "failure" - #http_status = 500 - else: - code = "DNS_RELOAD_OK" - message = "DNS configuration reload successfully" - status = "success" - #http_status = 200 + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + payload = { + "code": "DNS_RELOAD_OK", + "status": "success", + "message": "DNS configuration reload successfully", + "details": {"took_ms": took_ms} + } + return JSONResponse(content=payload, status_code=200) - took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + except Exception as err: + get_logger("dns").exception("Error reloading DNS: %s", str(err).strip()) + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 - payload = { - "code": code, - "status": status, - "message": message, - "details": { - "took_ms": took_ms + payload = { + "code": "DNS_RELOAD_ERROR", + "status": "failure", + "message": "Error reloading DNS", + "details": {"took_ms": took_ms, "error": str(err).strip()} } - } - return JSONResponse(content=payload) - #return JSONResponse(content=payload, status_code=http_status) + return JSONResponse(content=payload, status_code=500) diff --git a/frontend/js/hosts.js b/frontend/js/hosts.js index e71daf1..ea82aac 100644 --- a/frontend/js/hosts.js +++ b/frontend/js/hosts.js @@ -418,7 +418,7 @@ async function handleDeleteHost(e, el) { // ----------------------------- async function handleReloadDNS() { try { - const res = await fetch(`/api/dns/reload`, { method: "GET" }); + const res = await fetch(`/api/dns/reload`, { method: "POST" }); if (!res.ok) { const err = new Error(`Error reloading DNS: ${res.status} ${res.statusText}`); err.status = res.status; @@ -451,7 +451,7 @@ async function handleReloadDNS() { // ----------------------------- async function handleReloadDHCP() { try { - const res = await fetch(`/api/dhcp/reload`, { method: "GET" }); + const res = await fetch(`/api/dhcp/reload`, { method: "POST" }); if (!res.ok) { const err = new Error(`Error reloading DHCP: ${res.status} ${res.statusText}`); err.status = res.status; diff --git a/settings/default.py b/settings/default.py index 9bb6da2..7b013b4 100644 --- a/settings/default.py +++ b/settings/default.py @@ -47,16 +47,14 @@ ADMIN_PASSWORD_HASH_FILE = "/run/secrets/admin_password_hash" # --------------------------------------------------------- # DNS # --------------------------------------------------------- -DNS_CFG_PATH="/dns/etc" -DNS_HOST_FILE=f"{DOMAIN}/hosts.inc" -DNS_ALIAS_FILE=f"{DOMAIN}/alias.inc" -DNS_REVERSE_FILE="reverse/hosts.inc" +DNS_HOST_FILE=f"/dns/etc/{DOMAIN}/hosts.inc" +DNS_ALIAS_FILE=f"/dns/etc/{DOMAIN}/alias.inc" +DNS_REVERSE_FILE="/dns/etc/reverse/hosts.inc" # --------------------------------------------------------- # DHCP # --------------------------------------------------------- -DHCP_CFG_PATH="/dhcp/etc" -DHCP4_HOST_FILE="hosts-ipv4.json" -DHCP4_LEASES_FILE="dhcp4.leases" -DHCP6_HOST_FILE="hosts-ipv6.json" -DHCP6_LEASES_FILE="dhcp6.leases" +DHCP4_HOST_FILE="/dhcp/etc/hosts-ipv4.json" +DHCP4_LEASES_FILE="/dhcp/lib/dhcp4.leases" +DHCP6_HOST_FILE="/dhcp/etc/hosts-ipv6.json" +DHCP6_LEASES_FILE="/dhcp/lib/dhcp6.leases" diff --git a/settings/settings.py b/settings/settings.py index 62e2fd4..af8c5c2 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -88,12 +88,10 @@ class Settings(BaseModel): )) # DNS - DNS_CFG_PATH: str = Field(default_factory=lambda: os.getenv("DNS_CFG_PATH", default.DNS_CFG_PATH)) DNS_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DNS_HOST_FILE", default.DNS_HOST_FILE)) DNS_ALIAS_FILE: str = Field(default_factory=lambda: os.getenv("DNS_ALIAS_FILE", default.DNS_ALIAS_FILE)) DNS_REVERSE_FILE: str = Field(default_factory=lambda: os.getenv("DNS_REVERSE_FILE", default.DNS_REVERSE_FILE)) # DHCP - DHCP_CFG_PATH: str = Field(default_factory=lambda: os.getenv("DHCP_CFG_PATH", default.DHCP_CFG_PATH)) DHCP4_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DHCP4_HOST_FILE", default.DHCP4_HOST_FILE)) DHCP4_LEASES_FILE: str = Field(default_factory=lambda: os.getenv("DHCP4_LEASES_FILE", default.DHCP4_LEASES_FILE)) DHCP6_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DHCP6_HOST_FILE", default.DHCP6_HOST_FILE)) @@ -115,16 +113,6 @@ class Settings(BaseModel): if self.DOMAIN.lower() != default.DOMAIN.lower(): self.DNS_HOST_FILE = self.DNS_HOST_FILE.replace(default.DOMAIN, self.DOMAIN) self.DNS_ALIAS_FILE = self.DNS_ALIAS_FILE.replace(default.DOMAIN, self.DOMAIN) - self.DNS_REVERSE_FILE = self.DNS_REVERSE_FILE.replace(default.DOMAIN, self.DOMAIN) - self.DNS_HOST_FILE = self.DNS_CFG_PATH + "/" + self.DNS_HOST_FILE - self.DNS_ALIAS_FILE = self.DNS_CFG_PATH + "/" + self.DNS_ALIAS_FILE - self.DNS_REVERSE_FILE = self.DNS_CFG_PATH + "/" + self.DNS_REVERSE_FILE - - # Update DHCP Files - self.DHCP4_HOST_FILE = self.DHCP_CFG_PATH + "/" + self.DHCP4_HOST_FILE - self.DHCP4_LEASES_FILE = self.DHCP_CFG_PATH + "/" + self.DHCP4_LEASES_FILE - self.DHCP6_HOST_FILE = self.DHCP_CFG_PATH + "/" + self.DHCP6_HOST_FILE - self.DHCP6_LEASES_FILE = self.DHCP_CFG_PATH + "/" + self.DHCP6_LEASES_FILE # --------------------------------------------------------- # Singleton