[FEAT] Simulatore v2: auto-reset, UX migliorata, 6 scenari (lg231-inspired)

- simulate-nis2.php: autoResetDemo() via PDO pulisce dati demo prima di ogni
  run (SIM-01→05), skip per SIM-06 indipendente. Rimuove tutte le tabelle
  org_id>4 eccetto audit_logs (trigger immutabile).
- simulate.html v2.0: rimosso pulsante "Reset Dati Demo" (chiamava endpoint
  inesistente /api/admin/reset-demo). Aggiunti: confirm dialog con lista
  aziende + durata, spinner sul bottone, nota auto-reset visibile, run history
  localStorage (ultimi 5), card SIM-06 B2B License Provisioning, console
  phase-banner stile lg231.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-03-10 10:15:41 +01:00
co-authored by Claude Sonnet 4.6
parent ca8f077a7a
commit e8b74a7cb7
2 changed files with 364 additions and 103 deletions
+98
View File
@@ -606,6 +606,95 @@ function getAssessmentResponses(string $sector): array
return array_merge($base, $overrides);
}
// ────────────────────────────────────────────────────────────────────────────
// Auto-Reset Demo Data — eseguito prima di ogni run SIM-01→05
// Usa PDO con nis2_user (NO SUPER privilege → non tocca audit_logs/trigger)
// ────────────────────────────────────────────────────────────────────────────
function autoResetDemo(): void
{
$host = readEnvValue('DB_HOST', 'localhost');
$port = readEnvValue('DB_PORT', '3306');
$name = readEnvValue('DB_NAME', 'nis2_agile_db');
$user = readEnvValue('DB_USER', 'nis2_user');
$pass = readEnvValue('DB_PASS', '');
try {
$pdo = new PDO(
"mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4",
$user,
$pass,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 10]
);
} catch (\Throwable $e) {
simLog('Auto-reset: connessione DB fallita — ' . $e->getMessage(), 'warn');
return;
}
$pdo->exec('SET FOREIGN_KEY_CHECKS=0');
// Tabelle con organization_id (ordine: dipendenti prima dei parent)
$orgTables = [
'incident_timeline' => 'incident_id IN (SELECT id FROM incidents WHERE organization_id > 4)',
'incidents' => 'organization_id > 4',
'risk_treatments' => 'risk_id IN (SELECT id FROM risks WHERE organization_id > 4)',
'risks' => 'organization_id > 4',
'assessment_responses' => 'assessment_id IN (SELECT id FROM assessments WHERE organization_id > 4)',
'assessments' => 'organization_id > 4',
'policies' => 'organization_id > 4',
'suppliers' => 'organization_id > 4',
'training_assignments' => 'course_id IN (SELECT id FROM training_courses WHERE organization_id > 4)',
'training_courses' => 'organization_id > 4',
'assets' => 'organization_id > 4',
'compliance_controls' => 'organization_id > 4',
'evidence_files' => 'organization_id > 4',
'capa_actions' => 'ncr_id IN (SELECT id FROM non_conformities WHERE organization_id > 4)',
'non_conformities' => 'organization_id > 4',
'whistleblowing_timeline' => 'report_id IN (SELECT id FROM whistleblowing_reports WHERE organization_id > 4)',
'whistleblowing_reports' => 'organization_id > 4',
'normative_ack' => 'organization_id > 4',
'webhook_deliveries' => 'subscription_id IN (SELECT id FROM webhook_subscriptions WHERE organization_id > 4)',
'webhook_subscriptions' => 'organization_id > 4',
'api_keys' => 'organization_id > 4',
'feedback_reports' => 'organization_id > 4',
'ai_interactions' => 'organization_id > 4',
// audit_logs: trigger immutabile blocca DELETE — skip (dati storici accettabili)
];
$deleted = 0;
foreach ($orgTables as $table => $where) {
try {
$n = $pdo->exec("DELETE FROM `{$table}` WHERE {$where}");
$deleted += ($n ?: 0);
} catch (\Throwable $e) {
simLog(" skip {$table}: " . $e->getMessage(), 'skip');
}
}
// Email log (no organization_id)
try { $pdo->exec("DELETE FROM email_log WHERE recipient LIKE '%.demo%'"); } catch (\Throwable) {}
// User organizations + refresh tokens demo
try { $pdo->exec('DELETE FROM user_organizations WHERE organization_id > 4'); } catch (\Throwable) {}
try {
$pdo->exec("DELETE rt FROM refresh_tokens rt
JOIN users u ON rt.user_id = u.id
WHERE u.email LIKE '%.demo%'");
} catch (\Throwable) {}
// Utenti demo
try { $n = $pdo->exec("DELETE FROM users WHERE email LIKE '%.demo%'"); $deleted += ($n ?: 0); } catch (\Throwable) {}
// Organizzazioni demo
try { $n = $pdo->exec('DELETE FROM organizations WHERE id > 4'); $deleted += ($n ?: 0); } catch (\Throwable) {}
$pdo->exec('SET FOREIGN_KEY_CHECKS=1');
// Verifica org rimaste
$orgCount = $pdo->query('SELECT COUNT(*) FROM organizations')->fetchColumn();
simLog("Auto-reset completato — {$deleted} record rimossi, {$orgCount} org base mantenute", 'ok');
}
// ────────────────────────────────────────────────────────────────────────────
// FASE 0 — Health Check
// ────────────────────────────────────────────────────────────────────────────
@@ -623,6 +712,15 @@ if ($code === 200 && !empty($hRes['status'])) {
warn("API health check degradato (HTTP $code) — continuo comunque");
}
// ────────────────────────────────────────────────────────────────────────────
// FASE 0b — Auto-Reset Dati Demo Precedenti
// (skip se si lancia solo SIM-06 che è indipendente)
// ────────────────────────────────────────────────────────────────────────────
if ($SIM_FILTER !== 'SIM06') {
simPhase(0, 'Auto-Reset Dati Demo Precedenti');
autoResetDemo();
}
// ────────────────────────────────────────────────────────────────────────────
// SIM-01→05 — skip se si esegue solo SIM-06
// ────────────────────────────────────────────────────────────────────────────