]> git.giorgioravera.it Git - network-manager.git/commitdiff
updated loadhost with error handler and dhcp&dns reload buttons
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 30 Jan 2026 20:47:50 +0000 (21:47 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 30 Jan 2026 20:47:50 +0000 (21:47 +0100)
TODO.md
backend/main.py
backend/routes/dhcp.py [new file with mode: 0644]
backend/routes/dns.py [new file with mode: 0644]
frontend/js/hosts.js

diff --git a/TODO.md b/TODO.md
index 4cf50c033f864b6db20ff1e313a6c54e6262aa10..f6b8a928d50a33c72fb9b2d5cc77bb35231436bb 100644 (file)
--- a/TODO.md
+++ b/TODO.md
 - [ ] Remote Git repository backup
 - [ ] Backup of generated configurations
 
+### 🌍 Language
+- [ ] Localization
+
 ---
 
 # 👥 User Management (RBAC)
index 147877a6d7d5e91044dd89a13f8a736b2647903f..6cc004716d459e5e96754594fd88da6d72770d3a 100644 (file)
@@ -12,6 +12,8 @@ from backend.routes.about import router as about_router
 from backend.routes.health import router as health_router
 from backend.routes.login import router as login_router
 from backend.routes.hosts import router as hosts_router
+from backend.routes.dns import router as dns_router
+from backend.routes.dhcp import router as dhcp_router
 # Import Security
 from backend.security import is_logged_in, apply_session
 # Import Settings
@@ -95,6 +97,8 @@ app.include_router(about_router)
 app.include_router(health_router)
 app.include_router(login_router)
 app.include_router(hosts_router)
+app.include_router(dns_router)
+app.include_router(dhcp_router)
 
 # ------------------------------------------------------------------------------
 # CORS
diff --git a/backend/routes/dhcp.py b/backend/routes/dhcp.py
new file mode 100644 (file)
index 0000000..3d53e0d
--- /dev/null
@@ -0,0 +1,36 @@
+# backend/routes/dhcp.py
+
+# import standard modules
+from fastapi import APIRouter, Request, Response
+from fastapi.responses import FileResponse, RedirectResponse
+import asyncio
+import os
+import ipaddress
+import time
+
+# Import Settings
+from settings.settings import settings
+
+# Create Router
+router = APIRouter()
+
+# ---------------------------------------------------------
+# API ENDPOINTS
+# ---------------------------------------------------------
+@router.get("/api/dhcp/reload")
+async def apt_dhcp_reload(request: Request):
+    start_ns = time.monotonic_ns()
+
+    await asyncio.sleep(0.2)
+
+    end_ns = time.monotonic_ns()
+    took_ms = (end_ns - start_ns)
+
+    return {   
+        "code": "DHCP_RELOAD_OK",
+        "status": "success",
+        "message": "DHCP configuration reload successfully",
+        "details": {
+            "took_ms": took_ms
+        }
+    }
diff --git a/backend/routes/dns.py b/backend/routes/dns.py
new file mode 100644 (file)
index 0000000..cdd9efe
--- /dev/null
@@ -0,0 +1,36 @@
+# backend/routes/dns.py
+
+# import standard modules
+from fastapi import APIRouter, Request, Response
+from fastapi.responses import FileResponse, RedirectResponse
+import asyncio
+import os
+import ipaddress
+import time
+
+# Import Settings
+from settings.settings import settings
+
+# Create Router
+router = APIRouter()
+
+# ---------------------------------------------------------
+# API ENDPOINTS
+# ---------------------------------------------------------
+@router.get("/api/dns/reload")
+async def apt_dns_reload(request: Request):
+    start_ns = time.monotonic_ns()
+
+    await asyncio.sleep(0.2)
+
+    end_ns = time.monotonic_ns()
+    took_ms = (end_ns - start_ns)
+
+    return {   
+        "code": "DNS_RELOAD_OK",
+        "status": "success",
+        "message": "DNS configuration reload successfully",
+        "details": {
+            "took_ms": took_ms
+        }
+    }
index d9321ee3090c1afc42508b63c44e6e02d244abea..28d4ed43c13313acc3498ffee07963fc76cbb207 100644 (file)
@@ -56,104 +56,182 @@ function isValidMAC(mac) {
 // LOAD ALL HOSTS INTO THE TABLE
 // -----------------------------
 async function loadHosts() {
-    const res = await fetch("/api/hosts");
-    if (!res.ok) {
-        showToast("Errore nel caricamento degli host", false);
-        return;
+    let hosts = [];
+    try {
+        const res = await fetch("/api/hosts");
+        if (!res.ok) {
+            const err = new Error(`Error loading hosts: ${res.status} ${res.statusText}`);
+            err.status = res.status;
+            throw err;
+        }
+        // check content-type to avoid parsing errors
+        const contentType = res.headers.get("content-type") || "";
+        if (!contentType.includes("application/json")) {
+            throw new Error("Answer is not JSON");
+        }
+
+        // parse data
+        const data = await res.json();
+        hosts = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+        // debug log
+        //console.log("Hosts:", hosts);
+
+    } catch (err) {
+        console.error(err?.message || "Error loading hosts");
+        showToast(err?.message || "Error loading hosts", false);
+        hosts = [];
     }
-    const hosts = await res.json();
 
+    // DOM Reference
     const tbody = document.querySelector("#hosts-table tbody");
+    if (!tbody) {
+        console.warn('Element "#hosts-table tbody" not found in DOM.');
+    return;
+    }
+
+    // Svuota la tabella
     tbody.innerHTML = "";
 
+    // if no hosts, show an empty row
+    if (!hosts.length) {
+        const trEmpty = document.createElement("tr");
+        const tdEmpty = document.createElement("td");
+        tdEmpty.colSpan = 6;
+        tdEmpty.textContent = "No hosts available.";
+        tdEmpty.style.textAlign = "center";
+        trEmpty.appendChild(tdEmpty);
+        tbody.appendChild(trEmpty);
+        return;
+    }
+
+    // fragment per performance
+    const frag = document.createDocumentFragment();
+
     hosts.forEach(h => {
         const tr = document.createElement("tr");
 
         // Name
-        const tdName = document.createElement("td");
-        const nameVal = (h.name ?? "").toString();
-        tdName.textContent = nameVal;
-        if (nameVal) tdName.setAttribute("data-value", nameVal.toLowerCase());
-        tr.appendChild(tdName);
+        {
+            const td = document.createElement("td");
+            const val = (h.name ?? "").toString();
+            td.textContent = val;
+            if (val) td.setAttribute("data-value", val.toLowerCase());
+            tr.appendChild(td);
+        }
 
         // IPv4
-        const tdIPv4 = document.createElement("td");
-        const ipv4Raw = (h.ipv4 ?? "").toString().trim();
-        tdIPv4.textContent = ipv4Raw;
-        if (ipv4Raw) tdIPv4.setAttribute("data-value", ipv4Raw);
-        tr.appendChild(tdIPv4);
+        {
+            const td = document.createElement("td");
+            const raw = (h.ipv4 ?? "").toString().trim();
+            td.textContent = raw;
+            if (raw) td.setAttribute("data-value", raw);
+            tr.appendChild(td);
+        }
 
         // IPv6
-        const tdIPv6 = document.createElement("td");
-        const ipv6Raw = (h.ipv6 ?? "").toString().trim();
-        tdIPv6.textContent = ipv6Raw;
-        if (ipv6Raw) tdIPv6.setAttribute("data-value", ipv6Raw.toLowerCase());
-        tr.appendChild(tdIPv6);
+        {
+            const td = document.createElement("td");
+            const raw = (h.ipv6 ?? "").toString().trim();
+            td.textContent = raw;
+            if (raw) td.setAttribute("data-value", raw.toLowerCase());
+            tr.appendChild(td);
+        }
 
         // MAC
-        const tdMAC = document.createElement("td");
-        const macRaw = (h.mac ?? "").toString().trim();
-        tdMAC.textContent = macRaw;
-        const macNorm = macRaw.toLowerCase().replace(/[\s:\-\.]/g, "");
-        if (macNorm) tdMAC.setAttribute("data-value", macNorm);
-        tr.appendChild(tdMAC);
+        {
+            const td = document.createElement("td");
+            const raw = (h.mac ?? "").toString().trim();
+            td.textContent = raw;
+            const norm = raw.toLowerCase().replace(/[\s:\-\.]/g, "");
+            if (norm) td.setAttribute("data-value", norm);
+            tr.appendChild(td);
+        }
 
         // Note
-        const tdNote = document.createElement("td");
-        const noteVal = (h.note ?? "").toString();
-        tdNote.textContent = noteVal;
-        if (noteVal) tdNote.setAttribute("data-value", noteVal.toLowerCase());
-        tr.appendChild(tdNote);
+        {
+            const td = document.createElement("td");
+            const val = (h.note ?? "").toString();
+            td.textContent = val;
+            if (val) td.setAttribute("data-value", val.toLowerCase());
+            tr.appendChild(td);
+        }
 
         // SSL (icon)
-        const tdSSL = document.createElement("td");
-        const sslEnabled = !!h.ssl_enabled;
-        tdSSL.setAttribute("data-value", sslEnabled ? "true" : "false");
-        tdSSL.setAttribute("aria-label", sslEnabled ? "SSL attivo" : "SSL non attivo");
-        // center icon
-        tdSSL.style.textAlign = "center";
-        tdSSL.style.verticalAlign = "middle";
-        if (sslEnabled) {
-            const icon = document.createElement("i");
-            icon.className = "bi bi-shield-lock-fill icon icon-static";
-            icon.setAttribute("aria-hidden", "true");
-            tdSSL.appendChild(icon);
+        {
+            const td = document.createElement("td");
+            const sslEnabled = !!h.ssl_enabled;
+            td.setAttribute("data-value", sslEnabled ? "true" : "false");
+            td.setAttribute("aria-label", sslEnabled ? "SSL attivo" : "SSL non attivo");
+            td.style.textAlign = "center";
+            td.style.verticalAlign = "middle";
+            if (sslEnabled) {
+                const icon = document.createElement("i");
+                icon.className = "bi bi-shield-lock-fill icon icon-static";
+                icon.setAttribute("aria-hidden", "true");
+                td.appendChild(icon);
+            }
+            tr.appendChild(td);
         }
-        tr.appendChild(tdSSL);
 
-        // Actions
-        const tdActions = document.createElement("td");
-        tdActions.className = "actions";
+    // Actions
+    {
+        const td = document.createElement("td");
+        td.className = "actions";
+        td.style.textAlign = "center";
+        td.style.verticalAlign = "middle";
+
         const id = Number(h.id);
-        // center icons
-        tdActions.style.textAlign = "center";
-        tdActions.style.verticalAlign = "middle";
-        tdActions.innerHTML = `
-            <span class="action-icon"
-                    role="button" tabindex="0"
-                    title="Edit host" aria-label="Edit host"
-                    data-bs-toggle="modal" data-bs-target="#addHostModal"
-                    data-action="edit"
-                    data-host-id="${id}">
-                <i class="bi bi-pencil-fill icon icon-action" aria-hidden="true"></i>
-            </span>
-            <span class="action-icon"
-                    role="button" tabindex="0"
-                    title="Delete host" aria-label="Delete host"
-                    aria-label="Delete host"
-                    data-action="delete"
-                    data-host-id="${id}">
-                <i class="bi bi-trash-fill icon icon-action" aria-hidden="true"></i>
-            </span>
-        `;
-        tr.appendChild(tdActions);
-
-        tbody.appendChild(tr);
+
+        // Usa elementi reali invece di innerHTML con entity
+        const editSpan = document.createElement("span");
+        editSpan.className = "action-icon";
+        editSpan.setAttribute("role", "button");
+        editSpan.tabIndex = 0;
+        editSpan.title = "Edit host";
+        editSpan.setAttribute("aria-label", "Edit host");
+        editSpan.setAttribute("data-bs-toggle", "modal");
+        editSpan.setAttribute("data-bs-target", "#addHostModal");
+        editSpan.setAttribute("data-action", "edit");
+        editSpan.setAttribute("data-host-id", String(id));
+        {
+            const i = document.createElement("i");
+            i.className = "bi bi-pencil-fill icon icon-action";
+            i.setAttribute("aria-hidden", "true");
+            editSpan.appendChild(i);
+        }
+
+        const delSpan = document.createElement("span");
+        delSpan.className = "action-icon";
+        delSpan.setAttribute("role", "button");
+        delSpan.tabIndex = 0;
+        delSpan.title = "Delete host";
+        delSpan.setAttribute("aria-label", "Delete host");
+        delSpan.setAttribute("data-action", "delete");
+        delSpan.setAttribute("data-host-id", String(id));
+        {
+            const i = document.createElement("i");
+            i.className = "bi bi-trash-fill icon icon-action";
+            i.setAttribute("aria-hidden", "true");
+            delSpan.appendChild(i);
+        }
+
+        td.appendChild(editSpan);
+        td.appendChild(delSpan);
+        tr.appendChild(td);
+    }
+
+    frag.appendChild(tr);
     });
 
-    if (lastSort) {
-        sortDirection[lastSort.colIndex] = !lastSort.ascending;
-        sortTable(lastSort.colIndex);
+    // publish all rows
+    tbody.appendChild(frag);
+
+    // apply last sorting
+    if (typeof lastSort === "object" && lastSort && Array.isArray(sortDirection)) {
+        if (Number.isInteger(lastSort.colIndex)) {
+            sortDirection[lastSort.colIndex] = !lastSort.ascending;
+            sortTable(lastSort.colIndex);
+        }
     }
 }
 
@@ -336,32 +414,68 @@ async function handleDeleteHost(e, el) {
 }
 
 // -----------------------------
-// Handle delete host action
+// Handle reload DNS action
 // -----------------------------
 async function handleReloadDNS() {
-    // Execute delete
     try {
-        //showToast("DNS reloaded successfully");
-        console.warn("DNS reload not yet implemented");
-        showToast("DNS reload not jet implemented", false);
+        const res = await fetch(`/api/dns/reload`, { method: "GET" });
+        if (!res.ok) {
+            const err = new Error(`Error reloading DNS: ${res.status} ${res.statusText}`);
+            err.status = res.status;
+            throw err;
+        }
+        // check content-type to avoid parsing errors
+        const contentType = res.headers.get("content-type") || "";
+        if (!contentType.includes("application/json")) {
+            throw new Error("Answer is not JSON");
+        }
+
+        const data = await res.json();
+        if(data.code !== "DNS_RELOAD_OK"){
+            const err = new Error(`Error reloading DNS: ${data.code} ${data.message}`);
+            err.status = data.code;
+            throw err;
+        }
+
+        //console.info("DNS Reload:", data);
+        showToast(data.message, true);
+
     } catch (err) {
-        console.error("Error reloading DNS:", err);
-        showToast("Error reloading DNS", false);
+        console.error(err?.message || "Error reloading DNS");
+        showToast(err?.message || "Error reloading DNS", false);
     }
 }
 
 // -----------------------------
-// Handle delete host action
+// Handle reload DHCP action
 // -----------------------------
 async function handleReloadDHCP() {
-    // Execute delete
     try {
-        //showToast("DHCP reloaded successfully");
-        console.warn("DHCP reload not yet implemented");
-        showToast("DHCP reload not jet implemented", false);
+        const res = await fetch(`/api/dhcp/reload`, { method: "GET" });
+        if (!res.ok) {
+            const err = new Error(`Error reloading DHCP: ${res.status} ${res.statusText}`);
+            err.status = res.status;
+            throw err;
+        }
+        // check content-type to avoid parsing errors
+        const contentType = res.headers.get("content-type") || "";
+        if (!contentType.includes("application/json")) {
+            throw new Error("Answer is not JSON");
+        }
+
+        const data = await res.json();
+        if(data.code !== "DHCP_RELOAD_OK"){
+            const err = new Error(`Error reloading DHCP: ${data.code} ${data.message}`);
+            err.status = data.code;
+            throw err;
+        }
+
+        //console.info("DHCP Reload:", data);
+        showToast(data.message, true);
+
     } catch (err) {
-        console.error("Error reloading DHCP:", err);
-        showToast("Error reloading DHCP", false);
+        console.error(err?.message || "Error reloading DHCP");
+        showToast(err?.message || "Error reloading DHCP", false);
     }
 }
 
@@ -805,7 +919,7 @@ document.addEventListener("DOMContentLoaded", async () => {
             try {
                 await handler(e, el);
             } catch (err) {
-                console.error('Action error:', err);
+                console.error(err?.message || 'Action error', false);
                 showToast(err?.message || 'Action error', false);
             }
         });