From a4342d9f74365ea2d2aa8d0828ef1590f5f7dd59 Mon Sep 17 00:00:00 2001 From: Giorgio Ravera Date: Wed, 18 Feb 2026 22:19:34 +0100 Subject: [PATCH] removed inline call in login --- frontend/js/login.js | 276 ++++++++++++++++++++++++++----------------- frontend/login.html | 6 +- 2 files changed, 169 insertions(+), 113 deletions(-) diff --git a/frontend/js/login.js b/frontend/js/login.js index c1c1185..b031f05 100644 --- a/frontend/js/login.js +++ b/frontend/js/login.js @@ -1,119 +1,173 @@ +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 = ` - - 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 = ` + + 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); }); diff --git a/frontend/login.html b/frontend/login.html index af7f8ba..5f6e337 100644 --- a/frontend/login.html +++ b/frontend/login.html @@ -39,12 +39,13 @@

Login

-
+
- +