[FEAT] UI Fasi 4+5: Impersonate banner + Preferenze + Branding white-label (v1.6.0)

Completamento UI per gli endpoint backend già attivi (commit e4f9e91):

- admin/users.html: colonna Azioni con pulsante "Impersonate" per utenti non-super_admin
  attivi → salva token originale in sessionStorage, sostituisce con quello impersonate,
  redirige a dashboard
- js/common.js: banner persistente arancione "Modalità Impersonate" in tutte le
  pagine quando sessionStorage ha impersonate origin → pulsante "Esci impersonate"
  ripristina token originale e torna ad admin/users
- settings.html: nuovo tab "Preferenze" (lingua/tema/timezone/notifiche email+in-app)
  con form salva via PUT /auth/preferences
- settings.html: nuovo tab "Branding" (solo super_admin / consulente) con
  brand_name/logo_url/primary_color/secondary_color, PUT /branding

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-05-29 13:28:57 +02:00
co-authored by Claude Opus 4.7
parent e4f9e9179e
commit a7bd37a797
4 changed files with 251 additions and 3 deletions
+30
View File
@@ -231,6 +231,7 @@
<th>Ultimo Login</th>
<th>Attivo</th>
<th>Data Creazione</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>`;
@@ -270,6 +271,14 @@
</label>
</td>
<td>${formatDate(user.created_at)}</td>
<td>
${role !== 'super_admin' && isActive
? `<button class="btn btn-secondary btn-sm" onclick="impersonateUser(${user.id}, '${escapeHtml(user.email)}')" title="Entra come questo utente per 1 ora">
<svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align:-2px"><path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/></svg>
Impersonate
</button>`
: '<span class="text-muted">-</span>'}
</td>
</tr>`;
});
@@ -333,6 +342,27 @@
loadUsers(currentPage);
}
}
// ── Impersonate (Fase 4 / G11 — UI) ──
async function impersonateUser(userId, email) {
if (!confirm('Entrare come ' + email + ' per 1 ora?\n\nSarai loggato come quell\'utente. Tornerai alla tua sessione facendo logout.')) return;
try {
const res = await api.post('/auth/impersonate', { user_id: userId });
if (res.success && res.data && res.data.access_token) {
// Salva sessione originale per "Exit impersonate"
sessionStorage.setItem('nis2_impersonate_origin_token', api.token);
sessionStorage.setItem('nis2_impersonate_origin_refresh', api.refreshToken || '');
sessionStorage.setItem('nis2_impersonate_target_email', email);
// Sostituisce token con quello impersonate (no refresh per default)
api.setTokens(res.data.access_token, '');
window.location.href = '/dashboard.html';
} else {
showNotification(res.message || 'Impersonate fallito.', 'error');
}
} catch (e) {
showNotification('Errore di connessione.', 'error');
}
}
</script>
</body>
</html>