Files
nis2-agile/public/admin/index.html
T
DevEnv nis2-agileandClaude Opus 4.8 7c711b727a [FEAT] Rischi: matrice sempre visibile, rimosse viste FAIR e KRI (ticket #500 fase 1)
Ticket #500 (Simon Fattori) - semplificazione pagina Gestione Rischi:
- matrice 5x5 ora sempre visibile sotto la tabella (rimosso bottone 'Matrice' e il view-toggle)
- rimosse le viste 'Quantitativo (FAIR)' e 'KRI' con relativi bottoni, blocchi HTML e JS
- help.js allineato (rimosse sezioni FAIR/KRI), cache-buster help.js -> 20260727a
- version.json -> 1.25.2
Rimandato a fase 2 (richiede modello dati): rischi derivati dai requisiti LEX&ISO
+ codifica RSK-<misura>Com.N + rimozione bottoni 'Nuovo Rischio'/'AI Suggerisci'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:15:22 +02:00

252 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Pannello Amministrazione · NIS2 Agile</title>
<!-- UI V3: Inter self-host + design system v3.css. Pagina in /admin/: asset con path ASSOLUTI. -->
<link rel="stylesheet" href="/vendor/inter/inter.css?v=20260627">
<link rel="stylesheet" href="/css/v3.css?v=20260627">
<style>
/* ── Grafico distribuzione piani (non coperto da v3.css) ────────── */
.chart-container { padding:8px 4px 4px; }
.bar-chart { display:flex; align-items:flex-end; gap:32px; height:200px; padding:0 20px; border-bottom:2px solid var(--line); }
.bar-chart-item { display:flex; flex-direction:column; align-items:center; flex:1; height:100%; justify-content:flex-end; }
.bar-chart-bar { width:60px; border-radius:6px 6px 0 0; transition:height .8s ease; min-height:4px; position:relative; }
.bar-chart-bar.free { background:#94A3B8; }
.bar-chart-bar.professional { background:var(--c-blue); }
.bar-chart-bar.enterprise { background:#7C4DD6; }
.bar-chart-value { font-size:.875rem; font-weight:700; color:var(--ink); margin-bottom:8px; }
.bar-chart-label { font-size:.75rem; color:var(--muted); margin-top:12px; font-weight:600; text-transform:uppercase; letter-spacing:.03em; }
@media (prefers-reduced-motion:reduce) { .bar-chart-bar{transition:none;} }
/* Quick links */
.quick-links-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; }
.quick-link-card { display:flex; align-items:center; gap:16px; padding:18px 20px; background:#fff; border:1px solid var(--line); border-radius:var(--radius); cursor:pointer; transition:border-color .12s, box-shadow .12s, transform .12s; text-decoration:none; color:inherit; }
.quick-link-card:hover { border-color:#9FBEDD; box-shadow:0 6px 16px rgba(16,40,80,.10); transform:translateY(-1px); }
.quick-link-icon { width:46px; height:46px; border-radius:11px; background:#EAF1FA; color:var(--c-blue); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.quick-link-icon .ic { width:22px; height:22px; }
.quick-link-info h4 { font-size:.9375rem; font-weight:700; color:var(--ink); margin:0 0 2px; }
.quick-link-info p { font-size:.8125rem; color:var(--muted); margin:0; }
@media (max-width:1024px) { .grid3 { grid-template-columns:repeat(2,1fr); } }
@media (max-width:768px) { .grid3 { grid-template-columns:1fr; } .quick-links-grid { grid-template-columns:1fr; } .bar-chart { gap:16px; } .bar-chart-bar { width:40px; } }
</style>
<!-- PWA:start -->
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#0066CC">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="NIS2 Agile">
<meta name="application-name" content="NIS2 Agile">
<script src="/js/pwa.js?v=20260614" defer></script>
<!-- PWA:end -->
</head>
<body>
<!-- Chrome V3 (appbar + mega-menu) iniettata da topnav-v3.js in questo host -->
<div id="topnav"></div>
<main class="wrap">
<nav class="crumb" aria-label="breadcrumb">
<i data-lucide="home" class="ic"></i><a href="/dashboard.html">Home</a>
<i data-lucide="chevron-right" class="ic"></i><span>Amministrazione</span>
</nav>
<div class="page-head">
<div>
<h1>Pannello Amministrazione</h1>
<p class="sub">Panoramica della piattaforma e gestione globale</p>
</div>
<span class="badge-t red">Super Admin</span>
</div>
<div class="stack">
<!-- Stats Row 1 -->
<div class="grid3" id="stats-row-1" style="grid-template-columns:repeat(4,1fr)">
<div class="card card-pad stat blue">
<div class="ibox"><i data-lucide="building-2" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-total-orgs">Totale Organizzazioni</div>
<div class="val" id="stat-total-orgs" aria-labelledby="lbl-total-orgs">--</div>
</div>
</div>
<div class="card card-pad stat green">
<div class="ibox"><i data-lucide="check-circle" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-active-orgs">Organizzazioni Attive</div>
<div class="val" id="stat-active-orgs" aria-labelledby="lbl-active-orgs">--</div>
</div>
</div>
<div class="card card-pad stat blue">
<div class="ibox"><i data-lucide="users" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-total-users">Totale Utenti</div>
<div class="val" id="stat-total-users" aria-labelledby="lbl-total-users">--</div>
</div>
</div>
<div class="card card-pad stat green">
<div class="ibox"><i data-lucide="user-check" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-active-users">Utenti Attivi</div>
<div class="val" id="stat-active-users" aria-labelledby="lbl-active-users">--</div>
</div>
</div>
</div>
<!-- Stats Row 2 -->
<div class="grid3" id="stats-row-2" style="grid-template-columns:repeat(4,1fr)">
<div class="card card-pad stat blue">
<div class="ibox"><i data-lucide="clipboard-check" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-assessments">Assessment Completati</div>
<div class="val" id="stat-assessments" aria-labelledby="lbl-assessments">--</div>
</div>
</div>
<div class="card card-pad stat amber">
<div class="ibox"><i data-lucide="alert-triangle" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-incidents">Incidenti Aperti</div>
<div class="val" id="stat-incidents" aria-labelledby="lbl-incidents">--</div>
</div>
</div>
<div class="card card-pad stat amber">
<div class="ibox"><i data-lucide="shield-alert" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-risks">Totale Rischi</div>
<div class="val" id="stat-risks" aria-labelledby="lbl-risks">--</div>
</div>
</div>
<div class="card card-pad stat blue">
<div class="ibox"><i data-lucide="sparkles" class="ic"></i></div>
<div style="flex:1;min-width:0">
<div class="lbl" id="lbl-ai">Interazioni AI</div>
<div class="val" id="stat-ai" aria-labelledby="lbl-ai">--</div>
</div>
</div>
</div>
<!-- Plans Distribution -->
<section class="card card-pad">
<div class="sec-head"><h3>Distribuzione Piani</h3></div>
<div class="chart-container">
<div class="bar-chart" id="plans-chart" role="img" aria-label="Grafico a barre: distribuzione delle organizzazioni per piano di sottoscrizione">
<div class="v3-loading" style="flex:1"><span class="v3-spin"></span>Caricamento…</div>
</div>
</div>
</section>
<!-- Quick Links -->
<section class="card card-pad">
<div class="sec-head"><h3>Gestione Piattaforma</h3></div>
<nav class="quick-links-grid" aria-label="Collegamenti rapidi gestione piattaforma">
<a href="/admin/organizations.html" class="quick-link-card">
<div class="quick-link-icon" aria-hidden="true"><i data-lucide="building-2" class="ic"></i></div>
<div class="quick-link-info">
<h4>Gestisci Organizzazioni</h4>
<p>Visualizza e gestisci tutte le organizzazioni registrate sulla piattaforma.</p>
</div>
</a>
<a href="/admin/users.html" class="quick-link-card">
<div class="quick-link-icon" aria-hidden="true"><i data-lucide="users" class="ic"></i></div>
<div class="quick-link-info">
<h4>Gestisci Utenti</h4>
<p>Visualizza e gestisci tutti gli utenti registrati sulla piattaforma.</p>
</div>
</a>
</nav>
</section>
</div>
</main>
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
<script src="/js/api.js?v=20260627u"></script>
<script src="/js/common.js?v=20260627t"></script>
<script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260727a"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
if (typeof I18n !== 'undefined') I18n.init();
if (typeof HelpSystem !== 'undefined') HelpSystem.init();
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
// ── Admin check ──────────────────────────────────────────
(async function checkAdmin() {
try {
const me = await api.getMe();
if (!me.success || !me.data || me.data.role !== 'super_admin') {
showNotification('Accesso non autorizzato. Solo gli amministratori possono accedere a questa pagina.', 'error');
setTimeout(() => { window.location.href = '/dashboard.html'; }, 1500);
return;
}
loadAdminStats();
} catch (e) {
window.location.href = '/dashboard.html';
}
})();
// ── Load Stats ───────────────────────────────────────────
async function loadAdminStats() {
try {
const result = await api.request('GET', '/admin/stats');
if (result.success && result.data) {
const d = result.data;
document.getElementById('stat-total-orgs').textContent = d.total_organizations ?? 0;
document.getElementById('stat-active-orgs').textContent = d.active_organizations ?? 0;
document.getElementById('stat-total-users').textContent = d.total_users ?? 0;
document.getElementById('stat-active-users').textContent = d.active_users ?? 0;
document.getElementById('stat-assessments').textContent = d.completed_assessments ?? 0;
document.getElementById('stat-incidents').textContent = d.open_incidents ?? 0;
document.getElementById('stat-risks').textContent = d.total_risks ?? 0;
document.getElementById('stat-ai').textContent = d.ai_interactions ?? 0;
renderPlansChart(d.plans_distribution || []);
} else {
showNotification('Impossibile caricare le statistiche.', 'error');
}
} catch (e) {
console.error('Errore caricamento stats:', e);
showNotification('Errore di connessione.', 'error');
}
}
// ── Plans Chart ──────────────────────────────────────────
function renderPlansChart(distribution) {
const container = document.getElementById('plans-chart');
const planLabels = { free: 'Free', professional: 'Professional', enterprise: 'Enterprise' };
const plans = { free: 0, professional: 0, enterprise: 0 };
distribution.forEach(p => {
const key = (p.subscription_plan || 'free').toLowerCase();
if (plans.hasOwnProperty(key)) {
plans[key] = parseInt(p.count) || 0;
}
});
const maxVal = Math.max(1, ...Object.values(plans));
let html = '';
for (const [key, count] of Object.entries(plans)) {
const heightPct = (count / maxVal) * 100;
html += `
<div class="bar-chart-item">
<div class="bar-chart-value">${count}</div>
<div class="bar-chart-bar ${key}" style="height:${Math.max(heightPct, 3)}%;"></div>
<div class="bar-chart-label">${planLabels[key]}</div>
</div>`;
}
container.innerHTML = html;
}
</script>
</body>
</html>