]> git.giorgioravera.it Git - network-manager.git/commitdiff
removed inline call in login
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Wed, 18 Feb 2026 21:19:34 +0000 (22:19 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Wed, 18 Feb 2026 21:19:34 +0000 (22:19 +0100)
frontend/js/login.js
frontend/login.html

index c1c11856224dbecec8cc37943274157663ed2ceb..b031f050ff712cc5cc39c43cd1311846c8d5a2e7 100644 (file)
+document.addEventListener('DOMContentLoaded', () => {
+    const form = document.getElementById('loginForm');
+    if (!form) return;
+    
+    // Un solo AbortController per submit in corso
+    let inFlightController = null;
+    
+    form.addEventListener('submit', async (e) => {
+        // Prevent default form submission
+        e.preventDefault();
 
-// -----------------------------
-// Login function (UX migliorata)
-// -----------------------------
-async function handleLogin(e) {
-    // Prevent default form submission
-    e.preventDefault();
-
-    // Riferimenti UI
-    const form = document.getElementById("loginForm");
-    const btn = form.querySelector(".btn-login");
-    const userEl = document.getElementById("username");
-    const passEl = document.getElementById("password");
-    const errorBox = document.getElementById("loginError");
-
-    // Pulizia stato precedente
-    errorBox.classList.add("d-none");
-    errorBox.textContent = "";
-    userEl.removeAttribute("aria-invalid");
-    passEl.removeAttribute("aria-invalid");
-
-    // Normalizza input
-    const user = userEl.value.trim();
-    const pass = passEl.value;
-
-    // Evita submit multipli
-    if (btn.disabled) return;
-
-    // Disabilita UI + spinner
-    const originalBtnHTML = btn.innerHTML;
-    btn.disabled = true;
-    btn.setAttribute("aria-busy", "true");
-    btn.innerHTML = `
-        <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
-        Accesso...
-    `;
-
-    try {
-        const res = await fetch("/api/login", {
-            method: "POST",
-            headers: { "Content-Type": "application/json" },
-            credentials: "include",
-            body: JSON.stringify({ username: user, password: pass })
-        });
-
-        // Prova a leggere JSON in modo resiliente
-        let data = {};
-        try {
-            data = await res.json();
-        } catch {
-            // Se il server non risponde con JSON valido
-            data = { status: "error", error: "Risposta non valida dal server." };
-        }
+        // Riferimenti UI
+        const currentForm = e.currentTarget;
+        const btn      = currentForm.querySelector('.btn-login');
+        const userEl   = currentForm.querySelector('#username');
+        const passEl   = currentForm.querySelector('#password');
+        const errorBox = document.getElementById('loginError'); // può essere fuori dal form
+
+        // Helpers sicuri e localizzati
+        const hideError = () => {
+            // Pulizia stato precedente
+           if (errorBox && !errorBox.classList.contains('d-none')) {
+                errorBox.classList.add('d-none');
+                errorBox.textContent = '';
+           }
+            userEl?.removeAttribute('aria-invalid');
+            passEl?.removeAttribute('aria-invalid');
+            userEl?.classList.remove('is-invalid');
+            passEl?.classList.remove('is-invalid');
+        };
+
+        const showError = (message, { focus = 'username', markUser = false, markPass = false } = {}) => {
+            if(errorBox) {
+                // textContent = OK (niente HTML injection)
+                errorBox.textContent = message ?? 'Errore';
+                errorBox.classList.remove('d-none');
+           }
+
+            // metti invalid sui campi solo se sono vuoti o se credenziali errate
+            if (markUser) userEl?.setAttribute('aria-invalid', 'true');
+            if (markPass) passEl?.setAttribute('aria-invalid', 'true');
+            // in caso di credenziali errate, metti focus
+            if (focus === 'username') userEl?.focus();
+            else if (focus === 'password') passEl?.focus();
+            
+            // aggiungi classe is-invalid se vuoi la resa Bootstrap
+            if (markUser) userEl?.classList.add('is-invalid');
+            if (markPass) passEl?.classList.add('is-invalid');
+        };
+
+        // Pulizia
+        hideError();
+    
+        // Normalizza input
+        const user = userEl?.value?.trim() ?? '';
+        const pass = passEl?.value ?? '';
 
-        // Gestione HTTP non-OK come errore
-        if (!res.ok) {
-            const errMsg = data?.error || `Errore ${res.status} durante il login.`;
-            throw new Error(errMsg);
+        // Validazione rapida lato client
+        if (!user || !pass) {
+          showError('Compila tutti i campi.', {
+            focus: !user ? 'username' : 'password',
+            markUser: !user,
+            markPass: !pass
+          });
+          return;
         }
+       
+        // Evita submit multipli
+        if (btn?.disabled) return;
 
-        // Gestione logica dell'API
-        if (data.status === "ok") {
-            // Redirect alla pagina host
-            window.location.href = "/hosts";
-            return;
-        } else {
-            const msg = data.error || "Credenziali non valide.";
-            showError(msg, { highlight: true });
-            return;
+        // Annulla eventuale richiesta precedente
+        inFlightController?.abort();
+        inFlightController = new AbortController();
+    
+        // Disabilita UI + spinner
+        const originalBtnHTML = btn.innerHTML;
+       if(btn) {
+            btn.disabled = true;
+            btn.setAttribute('aria-busy', 'true');
+            btn.innerHTML = `
+                <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
+                Accesso...
+            `;
         }
-    } catch (err) {
-        // Errori di rete o eccezioni
-        const msg = err?.message || "Errore di connessione. Riprova.";
-        showError(msg, { highlight: true });
-    } finally {
-        // Ripristina il bottone
-        btn.disabled = false;
-        btn.removeAttribute("aria-busy");
-        btn.innerHTML = originalBtnHTML;
-    }
-
-    // ---- helpers locali ----
-    function showError(message, opts = {}) {
-        errorBox.textContent = message;
-        errorBox.classList.remove("d-none");
-        // evidenziazione campi e focus
-        if (opts.highlight) {
-            // metti invalid sui campi solo se sono vuoti o se credenziali errate
-            if (!user) userEl.setAttribute("aria-invalid", "true");
-            if (!pass) passEl.setAttribute("aria-invalid", "true");
-            // in caso di credenziali errate, metti focus allo username
-            userEl.focus();
-            // opzionale: aggiungi classe is-invalid se vuoi la resa Bootstrap
-            // userEl.classList.add("is-invalid");
-            // passEl.classList.add("is-invalid");
+
+
+        try {
+            const fetchPromise = fetch('/api/login', {
+                method: 'POST',
+                headers: { 'Content-Type': 'application/json' },
+                credentials: 'include',
+                body: JSON.stringify({ username: user, password: pass }),
+                signal: inFlightController.signal
+            });
+
+
+            // error after 5s?
+            // await Promise.race([ fetchPromise, timeout(5000, inFlightController.signal) ]);
+            const res = await fetchPromise;
+
+            // Parse JSON resiliente
+            let data = {};
+            try {
+                data = await res.json();
+            } catch {
+                // Se il server non risponde con JSON valido
+                data = { status: 'error', error: 'Risposta non valida dal server.' };
+            }
+
+            // Gestione HTTP non-OK come errore
+            if (!res.ok) {
+                // Usa messaggio server se presente, altrimenti status
+                const errMsg = (typeof data?.error === 'string' && data.error.trim())
+                    ? data.error.trim()
+                    : `Errore ${res.status} durante il login.`;
+                throw new Error(errMsg);
+            }
+
+            // Gestione logica dell'API
+            if (data?.status === 'ok') {
+                // Redirect alla pagina host
+                location.assign('/hosts');
+                return;
+            } else {
+                const msg = (typeof data?.error === 'string' && data.error.trim())
+                    ? data.error.trim()
+                    : 'Credenziali non valide.';
+                // Credenziali errate -> metti focus su username, marca entrambi
+                showError(msg, { focus: 'username', markUser: true, markPass: true });
+                return;
+            }
+        } catch (err) {
+          // Se è stato abortito, non mostrare errori
+          //if (inFlightController?.signal.aborted) return;
+          // Errori di rete o eccezioni
+          const msg = err?.message || 'Errore di connessione. Riprova.';
+          // Se è un timeout personalizzato, messaggio ad hoc
+          if (msg === 'Timeout di rete') {
+               showError('Il server non risponde. Riprova tra poco.', { focus: 'username' });
+          } else {
+              showError(msg, { focus: 'username' });
+          }
+        } finally {
+            // Ripristina bottone
+            if(btn) {
+                 btn.disabled = false;
+                 btn.removeAttribute('aria-busy');
+                 btn.innerHTML = originalBtnHTML ?? 'Entra';
+           }
+            // controller consumato
+            inFlightController = null;
         }
-    }
-}
-
-// (Opzionale) Reset errore on input: nasconde alert quando l’utente modifica i campi
-document.addEventListener("DOMContentLoaded", () => {
-    const errorBox = document.getElementById("loginError");
-    const userEl = document.getElementById("username");
-    const passEl = document.getElementById("password");
-
-    function clearError() {
-        if (!errorBox.classList.contains("d-none")) {
-            errorBox.classList.add("d-none");
-            errorBox.textContent = "";
+    });
+
+    // Reset error on input
+    const errorBox = document.getElementById('loginError');
+    const userEl = document.getElementById('username');
+    const passEl = document.getElementById('password');
+
+    const clearError = () => {
+        if (errorBox && !errorBox.classList.contains('d-none')) {
+            errorBox.classList.add('d-none');
+            errorBox.textContent = '';
         }
-        userEl.removeAttribute("aria-invalid");
-        passEl.removeAttribute("aria-invalid");
-        // userEl.classList.remove("is-invalid");
-        // passEl.classList.remove("is-invalid");
-    }
-
-    userEl.addEventListener("input", clearError);
-    passEl.addEventListener("input", clearError);
+        userEl?.removeAttribute('aria-invalid');
+        passEl?.removeAttribute('aria-invalid');
+        userEl?.classList.remove('is-invalid');
+        passEl?.classList.remove('is-invalid');
+    };
+
+    userEl?.addEventListener('input', clearError);
+    passEl?.addEventListener('input', clearError);
 });
index af7f8ba895f78fdce0645b859f3f344722652050..5f6e337060c250084cade18ab5cbc65056793f9e 100644 (file)
 
             <h2 class="mb-3">Login</h2>
 
-            <form id="loginForm" onsubmit="return handleLogin(event)">
+            <form id="loginForm" novalidate>
                 <div class="mb-3">
                     <!--<label for="username" class="form-label">Username</label>-->
                     <input
                         type="text"
                         id="username"
+                        name="username"
                         class="form-control"
                         autocomplete="username"
                         placeholder="username"
@@ -58,6 +59,7 @@
                     <input
                         type="password"
                         id="password"
+                        name="password"
                         class="form-control"
                         autocomplete="current-password"
                         placeholder="password"
@@ -76,7 +78,7 @@
     </div>
 
        <!-- Scripts -->
-    <script src="js/login.js"></script>
+    <script type="module" src="js/login.js"></script>
 
     <!-- Bootstrap JS -->
     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"