[UX+SEC] Eccellenza pre-audit: idle timeout, loading states, i18n, UX polish
- common.js: idle session timeout 30min con avviso countdown 5min prima del logout - common.js: checkAuth() attiva automaticamente il monitor di inattività - api.js: messaggi errore connessione usano i18n (IT/EN) tramite I18n.t() - risks.html: saveRisk() e aiSuggest() con setButtonLoading durante salvataggio - risks.html: deleteRisk() ricarica la matrice se si è in matrix view - incidents.html: createIncident() con setButtonLoading durante registrazione - policies.html: savePolicy() e saveAIGeneratedPolicy() con setButtonLoading - policies.html: banner AI-draft con pulsante X per dismissione Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a56401f5a9
commit
59fad22c0e
@@ -440,6 +440,7 @@ function _toggleSidebar(forceState) {
|
||||
|
||||
/**
|
||||
* Verifica che l'utente sia autenticato. Se non lo e', redirige al login.
|
||||
* Attiva automaticamente il timeout di sessione per inattivita'.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkAuth() {
|
||||
@@ -447,10 +448,110 @@ function checkAuth() {
|
||||
window.location.href = 'login.html';
|
||||
return false;
|
||||
}
|
||||
if (!_idleInitialized) {
|
||||
_idleInitialized = true;
|
||||
initIdleTimeout();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Idle Session Timeout
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
let _idleTimer = null;
|
||||
let _idleWarningTimer = null;
|
||||
let _idleCountdownInterval = null;
|
||||
let _idleInitialized = false;
|
||||
|
||||
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minuti totali
|
||||
const IDLE_WARNING_MS = 5 * 60 * 1000; // avviso 5 minuti prima della scadenza
|
||||
|
||||
/**
|
||||
* Avvia il monitoraggio di inattivita' della sessione.
|
||||
* Mostra un avviso 5 minuti prima del logout automatico.
|
||||
*/
|
||||
function initIdleTimeout() {
|
||||
_resetIdleTimer();
|
||||
['mousemove', 'keydown', 'click', 'scroll', 'touchstart'].forEach(evt => {
|
||||
document.addEventListener(evt, _resetIdleTimer, { passive: true });
|
||||
});
|
||||
}
|
||||
|
||||
function _resetIdleTimer() {
|
||||
clearTimeout(_idleTimer);
|
||||
clearTimeout(_idleWarningTimer);
|
||||
clearInterval(_idleCountdownInterval);
|
||||
|
||||
// Chiudi avviso se gia' aperto
|
||||
const existingWarning = document.getElementById('idle-warning-overlay');
|
||||
if (existingWarning) existingWarning.remove();
|
||||
|
||||
// Timer per mostrare avviso
|
||||
_idleWarningTimer = setTimeout(_showIdleWarning, IDLE_TIMEOUT_MS - IDLE_WARNING_MS);
|
||||
|
||||
// Timer per logout automatico
|
||||
_idleTimer = setTimeout(() => {
|
||||
if (typeof api !== 'undefined') api.logout();
|
||||
}, IDLE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function _showIdleWarning() {
|
||||
const existing = document.getElementById('idle-warning-overlay');
|
||||
if (existing) existing.remove();
|
||||
|
||||
let remaining = Math.floor(IDLE_WARNING_MS / 1000);
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'idle-warning-overlay';
|
||||
overlay.style.cssText = [
|
||||
'position:fixed', 'top:0', 'left:0', 'right:0', 'bottom:0', 'z-index:9999',
|
||||
'background:rgba(0,0,0,0.55)', 'display:flex', 'align-items:center', 'justify-content:center'
|
||||
].join(';');
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--card-bg,#fff);border-radius:12px;padding:32px;max-width:400px;width:90%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3);">
|
||||
<div style="width:56px;height:56px;background:#fff7ed;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
|
||||
<svg width="28" height="28" viewBox="0 0 20 20" fill="#c2410c"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>
|
||||
</div>
|
||||
<h3 style="font-size:1.1rem;font-weight:700;color:var(--gray-800,#1f2937);margin-bottom:8px;">Sessione in scadenza</h3>
|
||||
<p style="font-size:0.875rem;color:var(--gray-500,#6b7280);margin-bottom:20px;">
|
||||
Per motivi di sicurezza, verrai disconnesso tra<br>
|
||||
<strong id="idle-countdown" style="font-size:1.5rem;color:#c2410c;display:block;margin:8px 0;">5:00</strong>
|
||||
a causa di inattivita'.
|
||||
</p>
|
||||
<div style="display:flex;gap:12px;justify-content:center;">
|
||||
<button id="idle-stay-btn"
|
||||
style="padding:10px 24px;background:var(--primary,#2563eb);color:#fff;border:none;border-radius:8px;font-weight:600;cursor:pointer;font-size:0.875rem;">
|
||||
Rimani connesso
|
||||
</button>
|
||||
<button onclick="api.logout();"
|
||||
style="padding:10px 24px;background:var(--gray-100,#f3f4f6);color:var(--gray-700,#374151);border:none;border-radius:8px;font-weight:600;cursor:pointer;font-size:0.875rem;">
|
||||
Disconnetti
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
document.getElementById('idle-stay-btn').addEventListener('click', () => {
|
||||
overlay.remove();
|
||||
_resetIdleTimer();
|
||||
});
|
||||
|
||||
_idleCountdownInterval = setInterval(() => {
|
||||
remaining--;
|
||||
const min = Math.floor(remaining / 60);
|
||||
const sec = remaining % 60;
|
||||
const el = document.getElementById('idle-countdown');
|
||||
if (el) el.textContent = `${min}:${sec.toString().padStart(2, '0')}`;
|
||||
if (remaining <= 0) clearInterval(_idleCountdownInterval);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Utilities
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user