]> git.giorgioravera.it Git - network-manager.git/commitdiff
Updated js to include home and additional improvements.
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Wed, 18 Mar 2026 23:05:18 +0000 (00:05 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Wed, 18 Mar 2026 23:05:18 +0000 (00:05 +0100)
backend/app.py
backend/routes/backup.py
frontend/aliases.html
frontend/hosts.html
frontend/js/aliases.js
frontend/js/hosts.js
frontend/js/index.js [new file with mode: 0644]
frontend/js/login.js
frontend/js/services.js

index b32c37fd479f7c204ab55e268a73498f05400bba..b184faad574fac4a71048cdf218f49404d466dc5 100644 (file)
@@ -165,7 +165,11 @@ async def session_middleware(request: Request, call_next):
 # ------------------------------------------------------------------------------
 # Homepage
 def home(request: Request):
-    return FileResponse(os.path.join(settings.FRONTEND_DIR, "hosts.html"))
+    return FileResponse(os.path.join(settings.FRONTEND_DIR, "index.html"))
+
+# Homepage JS
+def js_home(request: Request):
+    return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/index.js"))
 
 # CSS variables
 def css_variables(request: Request):
@@ -232,6 +236,9 @@ def create_app() -> FastAPI:
 
     # Route per file del frontend
     app.add_api_route("/", home, methods=["GET"])
+    app.add_api_route("/home", home, methods=["GET"])
+    app.add_api_route("/index.html", home, methods=["GET"])
+    app.add_api_route("/js/index.js", js_home, methods=["GET"])
     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"])
index 8c4acc8ccf29d57e91fc60ed1dff9668497ccca1..39bd08b8079b8494d88ca927a2bc6d04ff5bd0d2 100644 (file)
@@ -45,7 +45,7 @@ async def api_backup(request: Request):
         return {
             "code": "BACKUP_OK" if not errors else "BACKUP_ERROR",
             "status": "success" if not errors else "failure",
-            "message": "BACKUP executed successfully" if not errors else "Some operations failed",
+            "message": "Backup executed successfully" if not errors else "Some operations failed",
             "took_ms": took_ms,
             "results": result,
         }
@@ -89,7 +89,7 @@ async def api_restore(request: Request):
         return {
             "code": "RESTORE_OK" if not errors else "RESTORE_ERROR",
             "status": "success" if not errors else "failure",
-            "message": "RESTORE executed successfully" if not errors else "Some operations failed",
+            "message": "Restore executed successfully" if not errors else "Some operations failed",
             "took_ms": took_ms,
             "results": result,
         }
index 680baaefeec5263cd2fa7ce9393ded45a4330335..5dbb540fa3bba984f235bdb5b376242b1603fb7c 100644 (file)
@@ -47,6 +47,7 @@
                 <!-- Menu -->
                 <div class="collapse navbar-collapse" id="menuNav">
                     <div class="navbar-nav ms-auto gap-2">
+                        <a href="/home"    id="homeBtn"    class="btn btn-primary"        aria-current="page">Dashboard</a>
                         <a href="/hosts"   id="hostsBtn"   class="btn btn-primary"        aria-current="page">Hostname</a>
                         <a href="/aliases" id="aliasesBtn" class="btn btn-primary active" aria-current="page">Alias</a>
                         <button id="logoutBtn" class="btn btn-primary">Logout</button>
@@ -67,7 +68,6 @@
                 <div class="col-12 col-md-auto">
                     <h2 class="mb-0 d-flex align-items-center gap-2 lh-1">
                         <span class="title-icon">đź–§</span>
-                        <!--<i class="bi bi-hdd-network"></i>-->
                         <span class="section-title">Aliases List</span>
                     </h2>
                 </div>
index f1a98fa28ca7c2886ce92a450c2185a5a5f189c7..6ff104abcba6384bc4ae5e6337125ded57e99ce6 100644 (file)
@@ -47,6 +47,7 @@
                 <!-- Menu -->
                 <div class="collapse navbar-collapse" id="menuNav">
                     <div class="navbar-nav ms-auto gap-2">
+                        <a href="/home"    id="homeBtn"    class="btn btn-primary"        aria-current="page">Dashboard</a>
                         <a href="/hosts"   id="hostsBtn"   class="btn btn-primary active" aria-current="page">Hostname</a>
                         <a href="/aliases" id="aliasesBtn" class="btn btn-primary"        aria-current="page">Alias</a>
                         <button id="logoutBtn" class="btn btn-primary">Logout</button>
@@ -67,7 +68,6 @@
                 <div class="col-12 col-md-auto">
                     <h2 class="mb-0 d-flex align-items-center gap-2 lh-1">
                         <span class="title-icon">đź–§</span>
-                        <!--<i class="bi bi-hdd-network"></i>-->
                         <span class="section-title">Host List</span>
                     </h2>
                 </div>
index a2cd9071b6453a53921930db191be6d803b68e12..fb8737ae1eb81da75890881106c111a1ddbbcdc4 100644 (file)
@@ -530,18 +530,38 @@ async function clearSearch() {
 // Action Handlers
 // -----------------------------
 const actionHandlers = {
-  async delete(e, el) { handleDeleteAlias(e, el); },
-
-  // edit is handled by bootstrap modal show event
-  edit(e, el) {
-    // no-op o log
-  },
-
-  // Reload DNS
-  async reloadDns(e, el) { reloadDNS(); },
-
-  // Reload DHCP
-  async reloadDhcp(e, el) { reloadDHCP(); },
+    // Delete alias
+    delete: (e, el) => {
+        handleDeleteAlias(e, el);
+    },
+    // Edit alias
+    edit: () => {
+        // handled by bootstrap modal show event
+    },
+    // Reload DNS
+    reloadDns: async () => {
+        try {
+            const result = await reloadDNS();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DNS reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DNS", false);
+        }
+    },
+    // Reload DHCP
+    reloadDhcp: async () => {
+        try {
+            const result = await reloadDHCP();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DHCP reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DHCP", false);
+        }
+    },
 };
 
 // -----------------------------
index e75f742e5724a485de4580eaed66dedaf6e9c00d..df7577bc066b9ec5f089e86c42779b858b9f3bc1 100644 (file)
@@ -563,18 +563,38 @@ async function clearSearch() {
 // Action Handlers
 // -----------------------------
 const actionHandlers = {
-  async delete(e, el) { handleDeleteHost(e, el); },
-
-  // edit is handled by bootstrap modal show event
-  edit(e, el) {
-    // no-op o log
-  },
-
-  // Reload DNS
-  async reloadDns(e, el) { reloadDNS(); },
-
-  // Reload DHCP
-  async reloadDhcp(e, el) { reloadDHCP(); },
+    // Delete host
+    delete: (e, el) => {
+        handleDeleteHost(e, el);
+    },
+    // Edit host
+    edit: () => {
+        // handled by bootstrap modal show event
+    },
+    // Reload DNS
+    reloadDns: async () => {
+        try {
+            const result = await reloadDNS();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DNS reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DNS", false);
+        }
+    },
+    // Reload DHCP
+    reloadDhcp: async () => {
+        try {
+            const result = await reloadDHCP();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DHCP reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DHCP", false);
+        }
+    },
 };
 
 // -----------------------------
diff --git a/frontend/js/index.js b/frontend/js/index.js
new file mode 100644 (file)
index 0000000..a7331a8
--- /dev/null
@@ -0,0 +1,166 @@
+// -------------------------------------------------------
+// IMPORT
+// -------------------------------------------------------
+import { showToast } from './common.js';
+import { apiCheck, reloadDNS, reloadDHCP, doBackup, doRestore } from './services.js';
+
+// -------------------------------------------------------
+// RESTORE MODAL OPEN/CLOSE
+// -------------------------------------------------------
+function openRestoreModal() {
+    const modal = document.getElementById('restoreModal');
+    const input = document.getElementById('restoreBackupId');
+    if (!modal) return;
+
+    modal.style.display = 'flex';
+    if (input) {
+        input.value = input.value?.trim() ?? '';
+        setTimeout(() => input.focus(), 50);
+    }
+}
+
+function closeRestoreModal() {
+    const modal = document.getElementById('restoreModal');
+    if (modal) modal.style.display = 'none';
+}
+
+// -------------------------------------------------------
+// Action Handlers
+// -------------------------------------------------------
+const actionHandlers = {
+    // Backup
+    startBackup: async (e, el) => {
+        const btn = el;
+
+        if (!btn) return;
+
+        const label = btn.querySelector('.label');
+        const originalLabel = label?.textContent ?? '';
+
+        btn.disabled = true;
+        label.textContent = ' Exporting…';
+
+        try {
+            const result = await doBackup();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'Backup compleated successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error performing backup", false);
+        } finally {
+            label.textContent = originalLabel;
+            btn.disabled = false;
+        }
+    },
+    // Restore
+    startRestore: async (e, el) => {
+        const btn = el;
+        const input = document.getElementById('restoreBackupId');
+        const modal = document.getElementById('restoreModal');
+
+        if (!btn || !input) return;
+
+        const id = input.value.trim();
+        if (!id) {
+            showToast('Specify a Backup ID', false);
+            input.focus();
+            return;
+        }
+
+        const label = btn.querySelector('.label');
+        const originalLabel = label?.textContent ?? '';
+
+        btn.disabled = true;
+        label.textContent = ' Restoring…';
+
+        try {
+            const result = await doRestore();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'Restore completed successfully';
+            showToast(msg, true);
+            // Close modal and reset input
+            if (modal) modal.style.display = 'none';
+            input.value = '';
+        } catch (err) {
+            showToast(err?.message || "Error performing restore", false);
+        } finally {
+            label.textContent = originalLabel;
+            btn.disabled = false;
+        }
+    },
+    openRestoreModal,       // managed by boostrap
+    closeRestoreModal,      // managed by boostrap
+    // Reload DNS
+    reloadDns: async () => {
+        try {
+            const result = await reloadDNS();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DNS reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DNS", false);
+        }
+    },
+    // Reload DHCP
+    reloadDhcp: async () => {
+        try {
+            const result = await reloadDHCP();
+            const msg = (typeof result === 'object' && result?.message)
+                        ? result.message
+                        : 'DHCP reload successfully';
+            showToast(msg, true);
+        } catch (err) {
+            showToast(err?.message || "Error reloading DHCP", false);
+        }
+    },
+    // Check API status
+    apiCheck: async () => {
+        const result = await apiCheck();
+        if(result) {
+            showToast('API status updated succesfully', true);
+        } else {
+            showToast('Error updating API status', false);
+        }
+    }
+};
+
+// -------------------------------------------------------
+// Global Click Delegation
+// -------------------------------------------------------
+document.addEventListener('click', async (e) => {
+    const el = e.target.closest('[data-action]');
+    if (!el) return;
+
+    const action = el.dataset.action;
+    const handler = actionHandlers[action];
+    if (!handler) return;
+
+    try {
+        await handler(e, el);
+    } catch (err) {
+        console.error(err?.message || 'Action error');
+        showToast(err?.message || 'Action error', false);
+    }
+});
+
+// -------------------------------------------------------
+// MODAL: ESC + BACKDROP CLOSE
+// -------------------------------------------------------
+document.addEventListener('keydown', (e) => {
+    if (e.key === 'Escape') closeRestoreModal();
+});
+
+const restoreModal = document.getElementById('restoreModal');
+if (restoreModal) {
+    restoreModal.addEventListener('click', (e) => {
+        if (e.target === restoreModal) closeRestoreModal();
+    });
+}
+
+// -------------------------------------------------------
+// KICKOFF
+// -------------------------------------------------------
+apiCheck();
index bf26deef6ef267b52fa31a046b55a20121189650..b29bc04c4761729f71a449b882cfdfd6acd8fe1f 100644 (file)
@@ -122,8 +122,8 @@ document.addEventListener('DOMContentLoaded', () => {
             }
 
             // Login Success
-            // Redirect to main page (hosts)
-            location.assign('/hosts');
+            // Redirect to main page
+            location.assign('/home');
             return;
 
         } catch (err) {
index 1cf794e08d4c78bfd4edcf716d81ed5f2e8d3762..8dd4d00dce9348651338d71b134195d845b9ad43 100644 (file)
-import { showToast } from './common.js';
+// -------------------------------------------------------
+// API Health Check
+// -------------------------------------------------------
+export async function apiCheck() {
+    const pill = document.getElementById('api-pill');
+    if (!pill) return;
+
+    try {
+        const r = await fetch('/api/health');
+        if (r.ok) {
+            pill.textContent = 'API OK';
+            pill.classList.remove('btn-outline-primary');
+            pill.classList.add('btn-primary');
+            return true;
+        } else {
+            pill.textContent = `API ${r.status}`;
+            return false;
+        }
+    } catch {
+        pill.textContent = 'API OFFLINE';
+        return false;
+    }
+}
 
 // -----------------------------
 // Reload DNS
 // -----------------------------
 export async function reloadDNS() {
+    let res;
     try {
         // Fetch data
-        const res = await fetch(`/api/dns/reload`, {
+        res = await fetch('/api/dns/reload', {
             method: 'POST',
             headers: { 'Accept': 'application/json' },
         });
+    } catch (err) {
+        const msg = 'Network error while reloading DNS' + (err?.message ? `: ${err.message}` : '');
+        throw new Error(msg, { cause: err });
+    }
 
-        // Success without JSON
-        if (res.status === 204) {
-            showToast('DNS reload successfully', true);
-            return true;
-        }
+    // Success without JSON
+    if (res.status === 204) {
+        return true;
+    }
 
-        // 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 content-type to avoid parsing errors
+    const contentType = res.headers.get("content-type") || "";
+    if (!contentType.includes("application/json")) {
+        const err = new Error(`Error reloading DNS: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+        err.status = res.status;
+        throw err;
+    }
 
-        // Check JSON
-        let data;
-        try {
-            data = await res.json();
-        } catch {
-            throw new Error('Invalid JSON payload');
-        }
+    // Check JSON
+    let data = {};
+    try {
+        data = await res.json();
+    } catch {
+        throw new Error('Error reloading DNS: Invalid JSON payload');
+    }
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error reloading DNS`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
-        }
+    // Check JSON errors
+    if (!res.ok) {
+        const serverMsg =
+            data?.detail?.message?.trim()
+            || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+            || data?.message?.trim()
+            || data?.error?.message?.trim()
+            || (typeof data?.error === 'string' ? data.error.trim() : '');
+        const err = new Error('Error reloading DNS' + (serverMsg ? `: ${serverMsg}` : ''));
+        err.status = res.status;
+        throw err;
+    }
 
+    if (res.ok && (data.status === 'success' || data.code === 'DNS_RELOAD_OK')) {
         // Success
-        showToast(data?.message || 'DNS reload successfully', true);
-        return true;
-
-    } catch (err) {
-        console.error(err?.message || "Error reloading DNS");
-        showToast(err?.message || "Error reloading DNS", false);
+        return data?.message ? { message: data.message } : true;
+    } else {
+        // Failed with JSON error message
+        return data?.message ? { message: data.message } : false;
     }
-
-    return false;
 }
 
 // -----------------------------
 // Reload DHCP action
 // -----------------------------
 export async function reloadDHCP() {
+    let res;
     try {
         // Fetch data
-        const res = await fetch(`/api/dhcp/reload`, {
+        res = await fetch(`/api/dhcp/reload`, {
             method: 'POST',
             headers: { 'Accept': 'application/json' },
         });
+    } catch (err) {
+        const msg = 'Network error while reloading DHCP' + (err?.message ? `: ${err.message}` : '');
+        throw new Error(msg, { cause: err });
+    }
 
-        // Success without JSON
-        if (res.status === 204) {
-            showToast('DHCP reload successfully', true);
-            return true;
-        }
+    // Success without JSON
+    if (res.status === 204) {
+        return true;
+    }
 
-        // 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 content-type to avoid parsing errors
+    const contentType = res.headers.get("content-type") || "";
+    if (!contentType.includes("application/json")) {
+        const err = new Error(`Error reloading DHCP: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+        err.status = res.status;
+        throw err;
+    }
 
-        // Check JSON
-        let data;
-        try {
-            data = await res.json();
-        } catch {
-            throw new Error('Error reloading DHCP: Invalid JSON payload');
-        }
+    // Check JSON
+    let data = {};
+    try {
+        data = await res.json();
+    } catch {
+        throw new Error('Error reloading DHCP: Invalid JSON payload');
+    }
 
-        // Check JSON errors
-        if (!res.ok) {
-            const serverMsg = data?.detail?.message?.trim();
-            const base = `Error reloadin DHCP`;
-            const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
-            err.status = res.status;
-            throw err;
-        }
+    // Check JSON errors
+    if (!res.ok) {
+        const serverMsg =
+            data?.detail?.message?.trim()
+            || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+            || data?.message?.trim()
+            || data?.error?.message?.trim()
+            || (typeof data?.error === 'string' ? data.error.trim() : '');
+        const err = new Error('Error reloading DHCP' + (serverMsg ? `: ${serverMsg}` : ''));
+        err.status = res.status;
+        throw err;
+    }
+
+    if (res.ok && (data.status === 'success' || data.code === 'DHCP_RELOAD_OK')) {
+        // Success
+        return data?.message ? { message: data.message } : true;
+    } else {
+        // Failed with JSON error message
+        return data?.message ? { message: data.message } : false;
+    }
+}
 
+// -------------------------------------------------------
+// Execute Backup
+// -------------------------------------------------------
+export async function doBackup() {
+    let res;
+
+    try {
+        // Fetch data
+        res = await fetch(`/api/backup`, {
+            method: 'POST',
+            headers: { 'Accept': 'application/json' },
+        });
+
+    } catch {
+        const msg = 'Network error while performing backup' + (err?.message ? `: ${err.message}` : '');
+        throw new Error(msg, { cause: err });
+    }
+
+    // Success without JSON
+    if (res.status === 204) {
+        return true;
+    }
+
+    // Check content-type to avoid parsing errors
+    const contentType = res.headers.get("content-type") || "";
+    if (!contentType.includes("application/json")) {
+        const err = new Error(`Error performing backup: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+        err.status = res.status;
+        throw err;
+    }
+
+    // Check JSON
+    let data = {};
+    try {
+        data = await res.json();
+    } catch {
+        throw new Error('Error performing backup: Invalid JSON payload');
+    }
+
+    // Check JSON errors
+    if (!res.ok) {
+        const serverMsg =
+            data?.detail?.message?.trim()
+            || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+            || data?.message?.trim()
+            || data?.error?.message?.trim()
+            || (typeof data?.error === 'string' ? data.error.trim() : '');
+        const err = new Error('Error performing backup' + (serverMsg ? `: ${serverMsg}` : ''));
+        err.status = res.status;
+        throw err;
+    }
+
+    if (res.ok && (data.status === 'success' || data.code === 'BACKUP_OK')) {
         // Success
-        showToast(data?.message || 'DHCP reload successfully', true);
+        return data?.message ? { message: data.message } : true;
+    } else {
+        // Failed with JSON error message
+        return data?.message ? { message: data.message } : false;
+    }
+}
+
+// -------------------------------------------------------
+// Execute Restore
+// -------------------------------------------------------
+export async function doRestore(id) {
+    let res;
+
+    try {
+        // Fetch data
+        res = await fetch(`/api/restore`, {
+            method: 'POST',
+            headers: { 'Accept': 'application/json' },
+            body: JSON.stringify({ backup_id: id })
+        });
+
+    } catch {
+        const msg = 'Network error while performing restore' + (err?.message ? `: ${err.message}` : '');
+        throw new Error(msg, { cause: err });
+    }
+
+    // Success without JSON
+    if (res.status === 204) {
         return true;
+    }
 
-    } catch (err) {
-        console.error(err?.message || "Error reloading DHCP");
-        showToast(err?.message || "Error reloading DHCP", false);
+    // Check content-type to avoid parsing errors
+    const contentType = res.headers.get("content-type") || "";
+    if (!contentType.includes("application/json")) {
+        const err = new Error(`Error performing restore: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+        err.status = res.status;
+        throw err;
+    }
+
+    // Check JSON
+    let data = {};
+    try {
+        data = await res.json();
+    } catch {
+        throw new Error('Error performing restore: Invalid JSON payload');
+    }
+
+    // Check JSON errors
+    if (!res.ok) {
+        const serverMsg =
+            data?.detail?.message?.trim()
+            || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+            || data?.message?.trim()
+            || data?.error?.message?.trim()
+            || (typeof data?.error === 'string' ? data.error.trim() : '');
+        const err = new Error('Error performing restore' + (serverMsg ? `: ${serverMsg}` : ''));
+        err.status = res.status;
+        throw err;
     }
 
-    return false;
+    if (res.ok && (data.status === 'success' || data.code === 'RESTORE_OK')) {
+        // Success
+        return data?.message ? { message: data.message } : true;
+    } else {
+        // Failed with JSON error message
+        return data?.message ? { message: data.message } : false;
+    }
 }