]> git.giorgioravera.it Git - network-manager.git/commitdiff
Added api.js & improved sort without re-fetch data
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Tue, 26 May 2026 16:55:26 +0000 (18:55 +0200)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Tue, 26 May 2026 16:55:26 +0000 (18:55 +0200)
backend/app.py
frontend/js/aliases.js
frontend/js/api.js [new file with mode: 0644]
frontend/js/devices.js
frontend/js/hosts.js
frontend/js/leases.js

index 46b00106b3d9e90d74e78d7d364cb794db27abfd..cf961354eada8bfe6068b980cbbd0515992dc59e 100644 (file)
@@ -188,6 +188,10 @@ def css_layout(request: Request):
 def js_common(request: Request):
     return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/common.js"))
 
+# JS API
+def js_api(request: Request):
+    return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/api.js"))
+
 # JS Services
 def js_services(request: Request):
     return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/services.js"))
@@ -249,6 +253,7 @@ def create_app() -> FastAPI:
     app.add_api_route("/css/variables.css", css_variables, methods=["GET"])
     app.add_api_route("/css/layout.css", css_layout, methods=["GET"])
     app.add_api_route("/js/common.js", js_common, methods=["GET"])
+    app.add_api_route("/js/api.js", js_api, methods=["GET"])
     app.add_api_route("/js/services.js", js_services, methods=["GET"])
     app.add_api_route("/favicon.ico", favicon, methods=["GET"])
 
index dc8887bf494f72fbd3ed4f1dad388df804dd149b..b0c657e7801567614e0bb1c675c3b9c68947d2dd 100644 (file)
@@ -1,18 +1,20 @@
 // Import common js
 import { loadModals, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
 import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
 
 // -----------------------------
 // State variables
 // -----------------------------
+let allAliases = [];
+let viewAliases = [];
 let editingAliasId = null;
 const sortState = { sortDirection: {}, lastSort: null };
 
 // -----------------------------
 // Load all aliases into the table
 // -----------------------------
-async function loadAliases() {
-    let aliases = [];
+async function loadAliases(refresh = true) {
     const loader = document.getElementById("loader");
     const container = document.getElementById("devices-container");
     const dataTable = document.getElementById("dataTable");
@@ -20,49 +22,23 @@ async function loadAliases() {
     // hide table during loading to avoid flickering and show loader
     dataTable.classList.add("d-none");
 
-    try {
-        // Show loader
-        loader.style.display = "block";
-
-        // Fetch data
-        const res = await fetch(`/api/aliases`, {
-            headers: { Accept: 'application/json' },
-        });
-
-        // Check content-type to avoid parsing errors
-        const contentType = res.headers.get("content-type") || "";
-        if (!contentType.includes("application/json")) {
-            const err = new Error(`${res.status}: ${res.statusText}`);
-            err.status = res.status;
-            throw err;
-        }
-
-        // Check JSON
-        let data;
+    if(refresh) {
         try {
-            data = await res.json();
-            aliases = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+            // Show loader
+            loader.style.display = "block";
 
-        } catch {
-            throw new Error('Invalid JSON payload');
-        }
+            // Fetch devices
+            allAliases = await fetchData(apiMap.aliases);
+            viewAliases = [...allAliases];
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error loading aliases`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
+        } catch (err) {
+          console.error(err?.message || "Error loading aliases");
+          showToast(err?.message || "Error loading aliase", false);
+          allAliases = [];
+          // hide loader and show table
+          loader.style.display = "none";
+          dataTable.classList.remove("d-none");
         }
-
-    } catch (err) {
-        console.error(err?.message || "Error loading aliases");
-        showToast(err?.message || "Error loading aliase", false);
-        aliases = [];
-        // hide loader and show table
-        loader.style.display = "none";
-        dataTable.classList.remove("d-none");
     }
 
     // DOM Reference
@@ -76,7 +52,7 @@ async function loadAliases() {
     tbody.innerHTML = "";
 
     // if no aliases, show an empty row
-    if (!aliases.length) {
+    if (!allAliases.length) {
         const trEmpty = document.createElement("tr");
         const tdEmpty = document.createElement("td");
         tdEmpty.colSpan = 7;
@@ -93,7 +69,7 @@ async function loadAliases() {
     // fragment per performance
     const frag = document.createDocumentFragment();
 
-    aliases.forEach(h => {
+    allAliases.forEach(h => {
 
         const id = Number(h.id);
         const tr = document.createElement("tr");
@@ -542,9 +518,12 @@ function filterAliases() {
 // -----------------------------
 async function clearSearch() {
     const input = document.getElementById("searchInput");
-    input.value = "";
-    input.blur();
-    await loadAliases();
+    if (input) {
+        input.value = "";
+        input.blur();
+    }
+    viewAliases = [...allAliases];
+    await loadAliases(false);
 }
 
 // -----------------------------
@@ -642,10 +621,10 @@ function initSearch() {
     // Escape management when focus is in the input
     input.addEventListener("keydown", (e) => {
         if (e.key === "Escape") {
-            e.preventDefault();     // evita side-effect (es. chiusure di modali del browser)
-            e.stopPropagation();    // evita che arrivi al listener globale
+            e.preventDefault();       // evita side-effect (es. chiusure di modali del browser)
+            e.stopPropagation();      // evita che arrivi al listener globale
             resetSorting(sortState);
-            clearSearch();          // svuota input e ricarica tabella (come definito nella tua funzione)
+            clearSearch();            // svuota input e ricarica tabella (come definito nella tua funzione)
             filterAliases('');        // ripristina tabella
         }
     });
diff --git a/frontend/js/api.js b/frontend/js/api.js
new file mode 100644 (file)
index 0000000..0b6c4d2
--- /dev/null
@@ -0,0 +1,60 @@
+// -----------------------------
+// API Endpoints
+// -----------------------------
+export const apiMap = {
+    hosts: {
+        url: "/api/hosts",
+        name: "Hosts"
+    },
+    aliases: {
+        url: "/api/aliases",
+        name: "Aliases"
+    },
+    leases: {
+        url: "/api/dhcp/leases",
+        name: "Leases"
+    },
+    devices: {
+        url: "/api/devices",
+        name: "Devices"
+    }
+};
+
+// -----------------------------
+// Fetch Data functions
+// -----------------------------
+export async function fetchData(api) {
+    let items = [];
+
+    // Fetch data
+    const res = await fetch(api.url, {
+        headers: { Accept: 'application/json' },
+    });
+
+    // Check content-type to avoid parsing errors
+    const contentType = res.headers.get("content-type") || "";
+    if (!contentType.includes("application/json")) {
+        const err = new Error(`${res.status}: ${res.statusText}`);
+        err.status = res.status;
+        throw err;
+    }
+
+    // Check JSON
+    let data;
+    try {
+        data = await res.json();
+        items = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+    } catch {
+        throw new Error('Invalid JSON payload');
+    }
+
+    // Check JSON errors
+    if (!res.ok) {
+        const serverMsg = data?.detail?.message?.trim();
+        const base = `Error loading ${api.name}`;
+        const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+        err.status = res.status;
+        throw err;
+    }
+    return items;
+}
\ No newline at end of file
index 72224c3055ff7fc9f85eddcccd6721594cf35320..78db81f0a6572e8c383455aaf128f16e39ecce4d 100644 (file)
@@ -1,18 +1,20 @@
 // Import common js
 import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
 import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
 
 // -----------------------------
 // State variables
 // -----------------------------
+let allDevices = [];
+let viewDevices = [];
 let editingHostId = null;
 const sortState = { sortDirection: {}, lastSort: null };
 
 // -----------------------------
 // Load all devices into the table
 // -----------------------------
-async function loadDevices() {
-    let devices = [];
+async function loadDevices(refresh = true) {
     const loader = document.getElementById("loader");
     const container = document.getElementById("devices-container");
     const dataTable = document.getElementById("dataTable");
@@ -20,49 +22,23 @@ async function loadDevices() {
     // hide table during loading to avoid flickering and show loader
     dataTable.classList.add("d-none");
 
-    try {
-        // Show loader
-        loader.style.display = "block";
-
-        // Fetch data
-        const res = await fetch(`/api/devices`, {
-            headers: { Accept: 'application/json' },
-        });
-
-        // Check content-type to avoid parsing errors
-        const contentType = res.headers.get("content-type") || "";
-        if (!contentType.includes("application/json")) {
-            const err = new Error(`${res.status}: ${res.statusText}`);
-            err.status = res.status;
-            throw err;
-        }
-
-        // Check JSON
-        let data;
+    if(refresh) {
         try {
-            data = await res.json();
-            devices = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+            // Show loader
+            loader.style.display = "block";
 
-        } catch {
-            throw new Error('Invalid JSON payload');
-        }
+            // Fetch devices
+            allDevices = await fetchData(apiMap.devices);
+            viewDevices = [...allDevices];
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error loading devices`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
+        } catch (err) {
+            console.error(err?.message || "Error loading devices");
+            showToast(err?.message || "Error loading devices", false);
+            allDevices = [];
+            // hide loader and show table
+            loader.style.display = "none";
+            dataTable.classList.remove("d-none");
         }
-
-    } catch (err) {
-        console.error(err?.message || "Error loading devices");
-        showToast(err?.message || "Error loading devices", false);
-        devices = [];
-        // hide loader and show table
-        loader.style.display = "none";
-        dataTable.classList.remove("d-none");
     }
 
     // DOM Reference
@@ -76,7 +52,7 @@ async function loadDevices() {
     tbody.innerHTML = "";
 
     // if no devices, show an empty row
-    if (!devices.length) {
+    if (!allDevices.length) {
         const trEmpty = document.createElement("tr");
         const tdEmpty = document.createElement("td");
         tdEmpty.colSpan = 7;
@@ -93,7 +69,7 @@ async function loadDevices() {
     // fragment per performance
     const frag = document.createDocumentFragment();
 
-    devices.forEach(d => {
+    allDevices.forEach(d => {
 
         //const mixedId = d.id;
         //const id = mixedId.slice(2);
@@ -659,9 +635,12 @@ function filterDevices() {
 // -----------------------------
 async function clearSearch() {
     const input = document.getElementById("searchInput");
-    input.value = "";
-    input.blur();
-    await loadDevices();
+    if (input) {
+        input.value = "";
+        input.blur();
+    }
+    viewDevices = [...allDevices];
+    await loadDevices(false);
 }
 
 // -----------------------------
index 93ff0fc8b6b537d505a681a5dbb7cc65baebffd1..2efdd77d958c6fd19c608f1e27e06cbbdb517d34 100644 (file)
@@ -1,18 +1,20 @@
 // Import common js
 import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
 import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
 
 // -----------------------------
 // State variables
 // -----------------------------
+let allHosts = [];
+let viewHosts = [];
 let editingHostId = null;
 const sortState = { sortDirection: {}, lastSort: null };
 
 // -----------------------------
 // Load all hosts into the table
 // -----------------------------
-async function loadHosts() {
-    let hosts = [];
+async function loadHosts(refresh = true) {
     const loader = document.getElementById("loader");
     const container = document.getElementById("devices-container");
     const dataTable = document.getElementById("dataTable");
@@ -20,49 +22,23 @@ async function loadHosts() {
     // hide table during loading to avoid flickering and show loader
     dataTable.classList.add("d-none");
 
-    try {
-        // Show loader
-        loader.style.display = "block";
-
-        // Fetch data
-        const res = await fetch(`/api/hosts`, {
-            headers: { Accept: 'application/json' },
-        });
-
-        // Check content-type to avoid parsing errors
-        const contentType = res.headers.get("content-type") || "";
-        if (!contentType.includes("application/json")) {
-            const err = new Error(`${res.status}: ${res.statusText}`);
-            err.status = res.status;
-            throw err;
-        }
-
-        // Check JSON
-        let data;
+    if(refresh) {
         try {
-            data = await res.json();
-            hosts = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+            // Show loader
+            loader.style.display = "block";
 
-        } catch {
-            throw new Error('Invalid JSON payload');
-        }
+            // Fetch hosts
+            allHosts = await fetchData(apiMap.hosts);
+            viewHosts = [...allHosts];
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error loading hosts`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
+        } catch (err) {
+            console.error(err?.message || "Error loading hosts");
+            showToast(err?.message || "Error loading hosts", false);
+            allHosts = [];
+            // hide loader and show table
+            loader.style.display = "none";
+            dataTable.classList.remove("d-none");
         }
-
-    } catch (err) {
-        console.error(err?.message || "Error loading hosts");
-        showToast(err?.message || "Error loading hosts", false);
-        hosts = [];
-        // hide loader and show table
-        loader.style.display = "none";
-        dataTable.classList.remove("d-none");
     }
 
     // DOM Reference
@@ -76,7 +52,7 @@ async function loadHosts() {
     tbody.innerHTML = "";
 
     // if no hosts, show an empty row
-    if (!hosts.length) {
+    if (!allHosts.length) {
         const trEmpty = document.createElement("tr");
         const tdEmpty = document.createElement("td");
         tdEmpty.colSpan = 7;
@@ -93,7 +69,7 @@ async function loadHosts() {
     // fragment per performance
     const frag = document.createDocumentFragment();
 
-    hosts.forEach(h => {
+    allHosts.forEach(h => {
 
         const id = Number(h.id);
         const tr = document.createElement("tr");
@@ -566,9 +542,12 @@ function filterHosts() {
 // -----------------------------
 async function clearSearch() {
     const input = document.getElementById("searchInput");
-    input.value = "";
-    input.blur();
-    await loadHosts();
+    if (input) {
+        input.value = "";
+        input.blur();
+    }
+    viewHosts = [...allHosts];
+    await loadHosts(false);
 }
 
 // -----------------------------
@@ -666,11 +645,11 @@ function initSearch() {
     // Escape management when focus is in the input
     input.addEventListener("keydown", (e) => {
         if (e.key === "Escape") {
-            e.preventDefault();     // evita side-effect (es. chiusure di modali del browser)
-            e.stopPropagation();    // evita che arrivi al listener globale
+            e.preventDefault();       // evita side-effect (es. chiusure di modali del browser)
+            e.stopPropagation();      // evita che arrivi al listener globale
             resetSorting(sortState);
-            clearSearch();          // svuota input e ricarica tabella (come definito nella tua funzione)
-            filterHosts('');        // ripristina tabella
+            clearSearch();            // svuota input e ricarica tabella (come definito nella tua funzione)
+            filterHosts('');          // ripristina tabella
         }
     });
 }
index 289f691f51b532f16855ca7094251b4ff58acd25..dd2db98f3b8165990aba9d1ce92e9d0ecb14f7e5 100644 (file)
@@ -1,17 +1,19 @@
 // Import common js
 import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
 import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
 
 // -----------------------------
 // State variables
 // -----------------------------
+let allLeases = [];
+let viewLeases = [];
 const sortState = { sortDirection: {}, lastSort: null };
 
 // -----------------------------
 // Load all leases into the table
 // -----------------------------
-async function loadLeases() {
-    let leases = [];
+async function loadLeases(refresh = true) {
     const loader = document.getElementById("loader");
     const container = document.getElementById("devices-container");
     const dataTable = document.getElementById("dataTable");
@@ -19,49 +21,23 @@ async function loadLeases() {
     // hide table during loading to avoid flickering and show loader
     dataTable.classList.add("d-none");
 
-    try {
-        // Show loader
-        loader.style.display = "block";
-
-        // Fetch data
-        const res = await fetch(`/api/dhcp/leases`, {
-            headers: { Accept: 'application/json' },
-        });
-
-        // Check content-type to avoid parsing errors
-        const contentType = res.headers.get("content-type") || "";
-        if (!contentType.includes("application/json")) {
-            const err = new Error(`${res.status}: ${res.statusText}`);
-            err.status = res.status;
-            throw err;
-        }
-
-        // Check JSON
-        let data;
+    if(refresh) {
         try {
-            data = await res.json();
-            leases = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+            // Show loader
+            loader.style.display = "block";
 
-        } catch {
-            throw new Error('Invalid JSON payload');
-        }
+            // Fetch leases
+            allLeases = await fetchData(apiMap.leases);
+            viewLeases = [...allLeases];
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error loading leases`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
+        } catch (err) {
+            console.error(err?.message || "Error loading leases");
+            showToast(err?.message || "Error loading leases", false);
+            allLeases = [];
+            // hide loader and show table
+            loader.style.display = "none";
+            dataTable.classList.remove("d-none");
         }
-
-    } catch (err) {
-        console.error(err?.message || "Error loading leases");
-        showToast(err?.message || "Error loading leases", false);
-        leases = [];
-        // hide loader and show table
-        loader.style.display = "none";
-        dataTable.classList.remove("d-none");
     }
 
     // DOM Reference
@@ -75,7 +51,7 @@ async function loadLeases() {
     tbody.innerHTML = "";
 
     // if no leases, show an empty row
-    if (!leases.length) {
+    if (!allLeases.length) {
         const trEmpty = document.createElement("tr");
         const tdEmpty = document.createElement("td");
         tdEmpty.colSpan = 7;
@@ -92,7 +68,7 @@ async function loadLeases() {
     // fragment per performance
     const frag = document.createDocumentFragment();
 
-    leases.forEach(l => {
+    allLeases.forEach(l => {
 
         const id = Number(l.id);
         const tr = document.createElement("tr");
@@ -530,9 +506,12 @@ function filterLeases() {
 // -----------------------------
 async function clearSearch() {
     const input = document.getElementById("searchInput");
-    input.value = "";
-    input.blur();
-    await loadLeases();
+    if (input) {
+        input.value = "";
+        input.blur();
+    }
+    viewLeases = [...allLeases];
+    await loadLeases(false);
 }
 
 // -----------------------------
@@ -630,11 +609,11 @@ function initSearch() {
     // Escape management when focus is in the input
     input.addEventListener("keydown", (e) => {
         if (e.key === "Escape") {
-            e.preventDefault();     // evita side-effect (es. chiusure di modali del browser)
-            e.stopPropagation();    // evita che arrivi al listener globale
+            e.preventDefault();       // evita side-effect (es. chiusure di modali del browser)
+            e.stopPropagation();      // evita che arrivi al listener globale
             resetSorting(sortState);
-            clearSearch();          // svuota input e ricarica tabella (come definito nella tua funzione)
-            filterLeases('');        // ripristina tabella
+            clearSearch();            // svuota input e ricarica tabella (come definito nella tua funzione)
+            filterLeases('');         // ripristina tabella
         }
     });
 }
@@ -738,7 +717,7 @@ function handleKeyboard(e) {
 
     // Button event delegation (Enter, Space)
     const isEnter = e.key === 'Enter';
-    const isSpace = e.key === ' ' || e.key === 'Spacebar';
+    const isSpace = e.key === ' ';
     if (!isEnter && !isSpace) return;
 
     const el = e.target.closest('[data-action]');