[FEAT] Gestione Rischi: tabella 12 colonne (inerente+residuo) — integrazione prototipo "Modulo Azioni" (Simon)
Integra la spec di Simon per la pagina Rischi con le correzioni normative (parere nis2-expert + iso-27001-expert) applicate come eccezioni motivate: - 12 colonne: Codice (link → Misure e Requisiti), Descrizione (RISCHIO DEFAULT = cfg_nis2_rischi.risk_descr), VALUTAZIONE RISCHIO INERENTE (P/I/PxI), Alert, Rischio Inerente, VALUTAZIONE RISCHIO RESIDUO (P/I/PxI), Esito residuo, Rischio Residuo. - Editing INLINE di Probabilità/Impatto (1-5), salvataggio automatico. - Alert a bande (15-25 Prioritaria / 9-14 Critica / 3-8 Necessaria / 1-2 Suggerita) → popup azioni + "crea azione" (tabella requisito_actions, seme Modulo Azioni). - Eccezione #1: il rischio RESIDUO NON aggiorna in automatico lo STATO DI CONFORMITÀ del requisito (piani distinti). Col.11 "Esito residuo" = flag Rivalutare/Adeguato; conformità esplicita. - Eccezione #3: banda 13-14 inclusa in "Critica" (9-14). "Danno €" → "Impatto" (1-5). Soglie = metodo interno (ISO 27001 §6.1.2), non obbligo (help/UI lo dichiarano). Backend: mig 067; derivedList (+risk_descr +residuo), setState (+residuo), requisitoActions list/create. help.js + i18n IT/EN. Cache-buster api/i18n/help 20260730a. Prossima mig=068. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cf224480cc
commit
8a0bc5ca39
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* migrate_067_requisito_residual_and_actions.php — Integrazione "Modulo Azioni" (Simon), Fase 1.
|
||||||
|
* (1) org_requisito_state += residual_likelihood, residual_impact (TINYINT NULL, scala 1-5).
|
||||||
|
* (2) requisito_actions (azioni collegate al rischio-da-requisito).
|
||||||
|
* Additiva e IDEMPOTENTE. Nessun dato esistente toccato.
|
||||||
|
*
|
||||||
|
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_067_requisito_residual_and_actions.php
|
||||||
|
*/
|
||||||
|
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||||
|
require_once __DIR__ . '/../config/env.php';
|
||||||
|
require_once __DIR__ . '/../config/database.php';
|
||||||
|
|
||||||
|
$pdo = Database::getInstance();
|
||||||
|
|
||||||
|
function colExists(PDO $pdo, string $table, string $col): bool {
|
||||||
|
$st = $pdo->prepare(
|
||||||
|
"SELECT 1 FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||||
|
);
|
||||||
|
$st->execute([$table, $col]);
|
||||||
|
return (bool) $st->fetchColumn();
|
||||||
|
}
|
||||||
|
function tblExists(PDO $pdo, string $table): bool {
|
||||||
|
$st = $pdo->prepare(
|
||||||
|
"SELECT 1 FROM information_schema.TABLES
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
|
||||||
|
);
|
||||||
|
$st->execute([$table]);
|
||||||
|
return (bool) $st->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
$adds = [
|
||||||
|
'residual_likelihood' => "ADD COLUMN residual_likelihood TINYINT NULL COMMENT 'Probabilita residua 1-5' AFTER impact",
|
||||||
|
'residual_impact' => "ADD COLUMN residual_impact TINYINT NULL COMMENT 'Impatto residuo 1-5' AFTER residual_likelihood",
|
||||||
|
];
|
||||||
|
foreach ($adds as $col => $clause) {
|
||||||
|
if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; }
|
||||||
|
$pdo->exec("ALTER TABLE org_requisito_state $clause");
|
||||||
|
echo " + aggiunta colonna org_requisito_state.$col\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tblExists($pdo, 'requisito_actions')) {
|
||||||
|
echo " - tabella requisito_actions gia presente, skip\n";
|
||||||
|
} else {
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE requisito_actions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
organization_id INT NOT NULL,
|
||||||
|
requisito_id INT NOT NULL,
|
||||||
|
risk_code VARCHAR(64) NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
status ENUM('bozza','in_corso','archiviata') NOT NULL DEFAULT 'bozza',
|
||||||
|
created_by INT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_reqact_org_req (organization_id, requisito_id, created_at),
|
||||||
|
INDEX idx_reqact_org (organization_id, created_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
||||||
|
);
|
||||||
|
echo " + creata tabella requisito_actions\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Migrazione 067 — residuo + requisito_actions OK. Prossima mig=068.\n";
|
||||||
@@ -207,24 +207,29 @@ class FrameworkController extends BaseController
|
|||||||
};
|
};
|
||||||
$likelihood = array_key_exists('likelihood', $body) ? $scale($body['likelihood']) : null;
|
$likelihood = array_key_exists('likelihood', $body) ? $scale($body['likelihood']) : null;
|
||||||
$impact = array_key_exists('impact', $body) ? $scale($body['impact']) : null;
|
$impact = array_key_exists('impact', $body) ? $scale($body['impact']) : null;
|
||||||
|
// Rischio RESIDUO (mig 067) — dopo il trattamento. Il client invia sempre la riga intera.
|
||||||
|
$resLikelihood = array_key_exists('residual_likelihood', $body) ? $scale($body['residual_likelihood']) : null;
|
||||||
|
$resImpact = array_key_exists('residual_impact', $body) ? $scale($body['residual_impact']) : null;
|
||||||
|
|
||||||
// upsert org-scoped (UNIQUE org+requisito)
|
// upsert org-scoped (UNIQUE org+requisito)
|
||||||
Database::query(
|
Database::query(
|
||||||
'INSERT INTO org_requisito_state
|
'INSERT INTO org_requisito_state
|
||||||
(organization_id, requisito_id, stato, valutazione_rischio, note, likelihood, impact, updated_by)
|
(organization_id, requisito_id, stato, valutazione_rischio, note, likelihood, impact, residual_likelihood, residual_impact, updated_by)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
stato = VALUES(stato),
|
stato = VALUES(stato),
|
||||||
valutazione_rischio = VALUES(valutazione_rischio),
|
valutazione_rischio = VALUES(valutazione_rischio),
|
||||||
note = VALUES(note),
|
note = VALUES(note),
|
||||||
likelihood = VALUES(likelihood),
|
likelihood = VALUES(likelihood),
|
||||||
impact = VALUES(impact),
|
impact = VALUES(impact),
|
||||||
|
residual_likelihood = VALUES(residual_likelihood),
|
||||||
|
residual_impact = VALUES(residual_impact),
|
||||||
updated_by = VALUES(updated_by)',
|
updated_by = VALUES(updated_by)',
|
||||||
[$orgId, $reqId, $stato ?? 'da_valutare', $valutazione, $note, $likelihood, $impact, $this->getCurrentUserId()]
|
[$orgId, $reqId, $stato ?? 'da_valutare', $valutazione, $note, $likelihood, $impact, $resLikelihood, $resImpact, $this->getCurrentUserId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
$row = Database::fetchOne(
|
$row = Database::fetchOne(
|
||||||
'SELECT requisito_id, stato, valutazione_rischio, note, likelihood, impact, updated_at
|
'SELECT requisito_id, stato, valutazione_rischio, note, likelihood, impact, residual_likelihood, residual_impact, updated_at
|
||||||
FROM org_requisito_state WHERE organization_id = ? AND requisito_id = ?',
|
FROM org_requisito_state WHERE organization_id = ? AND requisito_id = ?',
|
||||||
[$orgId, $reqId]
|
[$orgId, $reqId]
|
||||||
);
|
);
|
||||||
@@ -235,6 +240,8 @@ class FrameworkController extends BaseController
|
|||||||
'note' => $row['note'],
|
'note' => $row['note'],
|
||||||
'likelihood' => $row['likelihood'] !== null ? (int) $row['likelihood'] : null,
|
'likelihood' => $row['likelihood'] !== null ? (int) $row['likelihood'] : null,
|
||||||
'impact' => $row['impact'] !== null ? (int) $row['impact'] : null,
|
'impact' => $row['impact'] !== null ? (int) $row['impact'] : null,
|
||||||
|
'residual_likelihood' => $row['residual_likelihood'] !== null ? (int) $row['residual_likelihood'] : null,
|
||||||
|
'residual_impact' => $row['residual_impact'] !== null ? (int) $row['residual_impact'] : null,
|
||||||
'updated_at' => $row['updated_at'],
|
'updated_at' => $row['updated_at'],
|
||||||
], 'Valutazione salvata');
|
], 'Valutazione salvata');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,13 @@ class RiskController extends BaseController
|
|||||||
$rows = Database::fetchAll(
|
$rows = Database::fetchAll(
|
||||||
"SELECT q.id AS requisito_id, q.n, q.misura_code, q.requisito_code,
|
"SELECT q.id AS requisito_id, q.n, q.misura_code, q.requisito_code,
|
||||||
q.requisito_descr, q.applies_important, q.applies_essential,
|
q.requisito_descr, q.applies_important, q.applies_essential,
|
||||||
|
q.risk_code AS default_risk_code, rk.risk_descr AS default_risk_descr,
|
||||||
m.misura_descr, m.nist_code, m.area_politica, m.ord,
|
m.misura_descr, m.nist_code, m.area_politica, m.ord,
|
||||||
s.stato, s.valutazione_rischio, s.note, s.likelihood, s.impact
|
s.stato, s.valutazione_rischio, s.note, s.likelihood, s.impact,
|
||||||
|
s.residual_likelihood, s.residual_impact
|
||||||
FROM cfg_nis2_requisiti q
|
FROM cfg_nis2_requisiti q
|
||||||
JOIN cfg_nis2_misure m ON m.misura_code = q.misura_code
|
JOIN cfg_nis2_misure m ON m.misura_code = q.misura_code
|
||||||
|
LEFT JOIN cfg_nis2_rischi rk ON rk.risk_code = q.risk_code
|
||||||
LEFT JOIN org_requisito_state s
|
LEFT JOIN org_requisito_state s
|
||||||
ON s.requisito_id = q.id AND s.organization_id = ?
|
ON s.requisito_id = q.id AND s.organization_id = ?
|
||||||
ORDER BY m.ord ASC, q.n ASC, q.id ASC",
|
ORDER BY m.ord ASC, q.n ASC, q.id ASC",
|
||||||
@@ -100,15 +103,24 @@ class RiskController extends BaseController
|
|||||||
'requisito_code' => $q['requisito_code'],
|
'requisito_code' => $q['requisito_code'],
|
||||||
'comma' => $commaN,
|
'comma' => $commaN,
|
||||||
'requisito_descr' => $q['requisito_descr'],
|
'requisito_descr' => $q['requisito_descr'],
|
||||||
|
// RISCHIO(DEFAULT) del framework (cfg_nis2_rischi.risk_descr); fallback = requisito_descr.
|
||||||
|
'risk_descr' => ($q['default_risk_descr'] !== null && $q['default_risk_descr'] !== '')
|
||||||
|
? $q['default_risk_descr'] : $q['requisito_descr'],
|
||||||
'applies_important' => (int) $q['applies_important'],
|
'applies_important' => (int) $q['applies_important'],
|
||||||
'applies_essential' => (int) $q['applies_essential'],
|
'applies_essential' => (int) $q['applies_essential'],
|
||||||
'stato' => $q['stato'] ?? 'da_valutare',
|
'stato' => $q['stato'] ?? 'da_valutare',
|
||||||
'valutazione_rischio' => $q['valutazione_rischio'] ?? null,
|
'valutazione_rischio' => $q['valutazione_rischio'] ?? null,
|
||||||
'note' => $q['note'] ?? null,
|
'note' => $q['note'] ?? null,
|
||||||
|
// Rischio INERENTE (prima del trattamento) = likelihood/impact (mig 066)
|
||||||
'likelihood' => isset($q['likelihood']) && $q['likelihood'] !== null ? (int) $q['likelihood'] : null,
|
'likelihood' => isset($q['likelihood']) && $q['likelihood'] !== null ? (int) $q['likelihood'] : null,
|
||||||
'impact' => isset($q['impact']) && $q['impact'] !== null ? (int) $q['impact'] : null,
|
'impact' => isset($q['impact']) && $q['impact'] !== null ? (int) $q['impact'] : null,
|
||||||
'score' => (isset($q['likelihood'], $q['impact']) && $q['likelihood'] !== null && $q['impact'] !== null)
|
'score' => (isset($q['likelihood'], $q['impact']) && $q['likelihood'] !== null && $q['impact'] !== null)
|
||||||
? (int) $q['likelihood'] * (int) $q['impact'] : null,
|
? (int) $q['likelihood'] * (int) $q['impact'] : null,
|
||||||
|
// Rischio RESIDUO (dopo il trattamento) = residual_* (mig 067)
|
||||||
|
'residual_likelihood' => isset($q['residual_likelihood']) && $q['residual_likelihood'] !== null ? (int) $q['residual_likelihood'] : null,
|
||||||
|
'residual_impact' => isset($q['residual_impact']) && $q['residual_impact'] !== null ? (int) $q['residual_impact'] : null,
|
||||||
|
'residual_score' => (isset($q['residual_likelihood'], $q['residual_impact']) && $q['residual_likelihood'] !== null && $q['residual_impact'] !== null)
|
||||||
|
? (int) $q['residual_likelihood'] * (int) $q['residual_impact'] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,4 +623,57 @@ class RiskController extends BaseController
|
|||||||
{
|
{
|
||||||
return ($v === null || $v === '') ? null : (float) $v;
|
return ($v === null || $v === '') ? null : (float) $v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/risks/requisitoActions?requisito_id=NN
|
||||||
|
* Azioni collegate al rischio-da-requisito (seme del Modulo Azioni). Piu' recenti prima.
|
||||||
|
*/
|
||||||
|
public function requisitoActionsList(): void
|
||||||
|
{
|
||||||
|
$this->requireOrgAccess();
|
||||||
|
$orgId = $this->getCurrentOrgId();
|
||||||
|
$reqId = (int) $this->getParam('requisito_id', 0);
|
||||||
|
if ($reqId <= 0) { $this->jsonSuccess(['actions' => []]); return; }
|
||||||
|
|
||||||
|
$rows = Database::fetchAll(
|
||||||
|
'SELECT id, risk_code, description, status, created_at
|
||||||
|
FROM requisito_actions
|
||||||
|
WHERE organization_id = ? AND requisito_id = ?
|
||||||
|
ORDER BY created_at DESC, id DESC',
|
||||||
|
[$orgId, $reqId]
|
||||||
|
);
|
||||||
|
$this->jsonSuccess(['actions' => $rows]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/risks/requisitoActions {requisito_id*, description*, risk_code?}
|
||||||
|
* Crea un'azione collegata al rischio-da-requisito. NON tocca lo stato di conformita'
|
||||||
|
* del requisito (org_requisito_state.stato): la conformita' resta atto di attuazione+evidenza.
|
||||||
|
*/
|
||||||
|
public function requisitoActionsCreate(): void
|
||||||
|
{
|
||||||
|
$this->requireOrgRole(['super_admin', 'org_admin', 'compliance_manager']);
|
||||||
|
$orgId = $this->getCurrentOrgId();
|
||||||
|
$reqId = (int) $this->getParam('requisito_id', 0);
|
||||||
|
$descr = trim((string) $this->getParam('description', ''));
|
||||||
|
$riskCode = $this->getParam('risk_code');
|
||||||
|
if ($reqId <= 0 || $descr === '') {
|
||||||
|
$this->jsonError('requisito_id e description sono obbligatori', 422);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$id = Database::insert('requisito_actions', [
|
||||||
|
'organization_id' => $orgId,
|
||||||
|
'requisito_id' => $reqId,
|
||||||
|
'risk_code' => $riskCode ? substr((string) $riskCode, 0, 64) : null,
|
||||||
|
'description' => $descr,
|
||||||
|
'status' => 'bozza',
|
||||||
|
'created_by' => $this->getCurrentUserId(),
|
||||||
|
]);
|
||||||
|
$this->logAudit('requisito_action_created', 'requisito_action', $id);
|
||||||
|
$row = Database::fetchOne(
|
||||||
|
'SELECT id, risk_code, description, status, created_at FROM requisito_actions WHERE id = ?',
|
||||||
|
[$id]
|
||||||
|
);
|
||||||
|
$this->jsonSuccess(['action' => $row], 'Azione creata');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- 067_requisito_residual_and_actions.sql — Integrazione prototipo "Modulo Azioni" (Simon), Fase 1.
|
||||||
|
-- (1) org_requisito_state += valutazione del RISCHIO RESIDUO (dopo il trattamento):
|
||||||
|
-- residual_likelihood / residual_impact (1-5). L'inerente resta likelihood/impact (mig 066).
|
||||||
|
-- (2) requisito_actions: azioni collegate al rischio-da-requisito (seme del modulo Azioni).
|
||||||
|
-- NB normativo: NON tocca lo stato di conformità del requisito (org_requisito_state.stato),
|
||||||
|
-- che resta un atto esplicito di attuazione+evidenza. Vedi eccezione #1 (parere nis2-expert).
|
||||||
|
-- Additiva e idempotente. Runner: application/cli/migrate_067_requisito_residual_and_actions.php
|
||||||
|
|
||||||
|
ALTER TABLE org_requisito_state
|
||||||
|
ADD COLUMN residual_likelihood TINYINT NULL COMMENT 'Probabilita residua 1-5 (dopo trattamento)' AFTER impact,
|
||||||
|
ADD COLUMN residual_impact TINYINT NULL COMMENT 'Impatto residuo 1-5 (dopo trattamento)' AFTER residual_likelihood;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS requisito_actions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
organization_id INT NOT NULL,
|
||||||
|
requisito_id INT NOT NULL,
|
||||||
|
risk_code VARCHAR(64) NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
status ENUM('bozza','in_corso','archiviata') NOT NULL DEFAULT 'bozza',
|
||||||
|
created_by INT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_reqact_org_req (organization_id, requisito_id, created_at),
|
||||||
|
INDEX idx_reqact_org (organization_id, created_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
|
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script src="js/common.js?v=20260729b"></script>
|
<script src="js/common.js?v=20260729b"></script>
|
||||||
<script src="js/common-bi.js?v=20260625d"></script>
|
<script src="js/common-bi.js?v=20260625d"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -162,12 +162,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -85,12 +85,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -92,12 +92,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -1122,12 +1122,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
loadSidebar(); // = loadTopnavV3()
|
loadSidebar(); // = loadTopnavV3()
|
||||||
|
|||||||
@@ -270,12 +270,12 @@
|
|||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica
|
||||||
pagina (wizard GAP) resta inline: usa gli stessi id/funzioni di prima. -->
|
pagina (wizard GAP) resta inline: usa gli stessi id/funzioni di prima. -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
+3
-3
@@ -377,12 +377,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ─────────────────────────────────────────
|
// ── Auth & Init ─────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -190,12 +190,12 @@
|
|||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica del
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica del
|
||||||
calendario (aggregatore read-only) resta inline e usa gli stessi id/funzioni. -->
|
calendario (aggregatore read-only) resta inline e usa gli stessi id/funzioni. -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -197,12 +197,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → help → init pagina -->
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|
||||||
|
|||||||
@@ -192,12 +192,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts V3. competenze.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
<!-- Scripts V3. competenze.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/competenze.js?v=20260627"></script>
|
<script src="/js/competenze.js?v=20260627"></script>
|
||||||
<script>
|
<script>
|
||||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||||
|
|||||||
@@ -98,12 +98,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/connettori-discovery.js?v=20260627"></script>
|
<script src="/js/connettori-discovery.js?v=20260627"></script>
|
||||||
<script>
|
<script>
|
||||||
if (typeof checkAuth === 'function' && !checkAuth()) {
|
if (typeof checkAuth === 'function' && !checkAuth()) {
|
||||||
|
|||||||
@@ -155,12 +155,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
|
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
|
||||||
|
|||||||
@@ -222,12 +222,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
|
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
|
||||||
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
|
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
|
||||||
|
|||||||
@@ -112,12 +112,12 @@
|
|||||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||||
i18n → help → dashboard.js -->
|
i18n → help → dashboard.js -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/dashboard.js?v=20260724a"></script>
|
<script src="/js/dashboard.js?v=20260724a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
|
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
const form = document.getElementById('forgot-form');
|
const form = document.getElementById('forgot-form');
|
||||||
const err = document.getElementById('err');
|
const err = document.getElementById('err');
|
||||||
|
|||||||
+3
-3
@@ -1177,12 +1177,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
||||||
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
|
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
|
||||||
|
|||||||
@@ -359,12 +359,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ──────────────────────────────────────────────
|
// ── Auth & Init ──────────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -344,6 +344,8 @@ $actionMap = [
|
|||||||
'GET:kri' => 'listKri', // P2: dashboard KRI
|
'GET:kri' => 'listKri', // P2: dashboard KRI
|
||||||
'POST:kri' => 'createKri',
|
'POST:kri' => 'createKri',
|
||||||
'PUT:kri/{subId}' => 'updateKri',
|
'PUT:kri/{subId}' => 'updateKri',
|
||||||
|
'GET:requisitoActions' => 'requisitoActionsList', // #500/Modulo Azioni: azioni sul rischio-da-requisito
|
||||||
|
'POST:requisitoActions' => 'requisitoActionsCreate',
|
||||||
],
|
],
|
||||||
|
|
||||||
// ── IncidentController ──────────────────────────
|
// ── IncidentController ──────────────────────────
|
||||||
|
|||||||
@@ -931,12 +931,12 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
loadSidebar(); // = loadTopnavV3()
|
loadSidebar(); // = loadTopnavV3()
|
||||||
|
|||||||
@@ -160,12 +160,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
+3
-3
@@ -199,12 +199,12 @@
|
|||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → isms.js.
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → isms.js.
|
||||||
isms.js si auto-inizializza su DOMContentLoaded (checkAuth/I18n/loadSidebar/Help/render). -->
|
isms.js si auto-inizializza su DOMContentLoaded (checkAuth/I18n/loadSidebar/Help/render). -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/isms.js?v=20260627v3"></script>
|
<script src="/js/isms.js?v=20260627v3"></script>
|
||||||
<script>
|
<script>
|
||||||
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
|
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
|
||||||
|
|||||||
@@ -451,6 +451,8 @@ class NIS2API {
|
|||||||
deleteRisk(id) { return this.del(`/risks/${id}`); }
|
deleteRisk(id) { return this.del(`/risks/${id}`); }
|
||||||
getRiskMatrix() { return this.get('/risks/matrix'); }
|
getRiskMatrix() { return this.get('/risks/matrix'); }
|
||||||
derivedRisks() { return this.get('/risks/derived'); }
|
derivedRisks() { return this.get('/risks/derived'); }
|
||||||
|
requisitoActions(requisitoId) { return this.get('/risks/requisitoActions?requisito_id=' + encodeURIComponent(requisitoId)); }
|
||||||
|
createRequisitoAction(d) { return this.post('/risks/requisitoActions', d || {}); }
|
||||||
aiSuggestRisks() { return this.post('/risks/ai-suggest', {}); }
|
aiSuggestRisks() { return this.post('/risks/ai-suggest', {}); }
|
||||||
// FAIR quantitativo + KRI (P2)
|
// FAIR quantitativo + KRI (P2)
|
||||||
computeFair(id, data) { return this.post(`/risks/${id}/fair`, data); }
|
computeFair(id, data) { return this.post(`/risks/${id}/fair`, data); }
|
||||||
|
|||||||
+6
-3
@@ -606,9 +606,12 @@ const HelpSystem = (function () {
|
|||||||
items: [
|
items: [
|
||||||
'La <strong>prima tabella</strong> elenca i rischi <strong>derivati dai requisiti</strong> del framework di sicurezza: ogni voce e\' il <strong>rischio di non conformita\'</strong> legato al mancato o parziale soddisfacimento di un requisito, <strong>non il requisito in se\'</strong>. E\' un elenco in <strong>sola lettura</strong>.',
|
'La <strong>prima tabella</strong> elenca i rischi <strong>derivati dai requisiti</strong> del framework di sicurezza: ogni voce e\' il <strong>rischio di non conformita\'</strong> legato al mancato o parziale soddisfacimento di un requisito, <strong>non il requisito in se\'</strong>. E\' un elenco in <strong>sola lettura</strong>.',
|
||||||
'Il codice <strong>RSK-‹misura›Com.‹n›</strong> e\' una <strong>codifica interna di NIS2 Agile</strong>; ‹misura› (es. GV.OC-04) e\' il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. <strong>Non</strong> e\' un codice o numero ufficiale ACN.',
|
'Il codice <strong>RSK-‹misura›Com.‹n›</strong> e\' una <strong>codifica interna di NIS2 Agile</strong>; ‹misura› (es. GV.OC-04) e\' il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. <strong>Non</strong> e\' un codice o numero ufficiale ACN.',
|
||||||
'La colonna <strong>Conformita\'</strong> mostra lo stato di valutazione del requisito (Da valutare, Non conforme, Parziale, Conforme). La tabella elenca <strong>solo i requisiti applicabili alla tua classe</strong> (importante → Allegato 1, essenziale → Allegato 2 della Determinazione ACN 164179/2025): quelli non pertinenti non vengono mostrati. Se l\'elenco e\' lungo, il riquadro <strong>scorre verticalmente</strong> con l\'intestazione fissa.',
|
'La tabella ha <strong>12 colonne</strong>: <strong>Codice rischio</strong> (cliccabile, apre la voce in Misure e Requisiti), <strong>Descrizione rischio</strong> (il rischio di default associato al requisito nel framework), e due valutazioni affiancate — <strong>RISCHIO INERENTE</strong> (prima del trattamento) e <strong>RISCHIO RESIDUO</strong> (dopo il trattamento). Ogni valutazione ha Probabilità, Impatto e Valutazione (= Probabilità × Impatto, scala 1–25).',
|
||||||
'La seconda tabella, <strong>Rischi aggiuntivi (approccio multirischio / all-hazard)</strong>, permette ai <strong>profili amministrativi</strong> (org_admin, super_admin) di aggiungere rischi non coperti da un singolo requisito (minacce fisiche, catena di fornitura, eventi naturali): copre l\'approccio multirischio richiesto dall\'art. 24 D.Lgs. 138/2024.',
|
'I profili <strong>Amministratore</strong>, <strong>Compliance Manager</strong> e <strong>super admin</strong> compilano <strong>direttamente in tabella</strong> Probabilità e Impatto (1–5): la Valutazione si calcola da sola e il salvataggio è automatico. La <strong>Probabilità</strong> è la probabilità che la minaccia sfrutti la carenza del requisito; l\'<strong>Impatto</strong> (scala 1–5, non euro) è la severità della non conformità o del conseguente incidente.',
|
||||||
'I profili <strong>Amministratore</strong>, <strong>Compliance Manager</strong> e <strong>super admin</strong> possono <strong>valutare</strong> ogni rischio-da-requisito con il pulsante <strong>Valuta</strong>: si impostano lo <strong>stato di conformità</strong>, la <strong>Probabilità</strong> e l\'<strong>Impatto</strong> (scala 1–5). La <strong>Probabilità</strong> è la probabilità che la minaccia si concretizzi <strong>sfruttando la carenza</strong> del requisito; l\'<strong>Impatto</strong> è l\'impatto della <strong>non conformità o del conseguente incidente</strong> — non la "probabilità del requisito". Il punteggio Rischio (Probabilità × Impatto) compare nella tabella e alimenta la matrice.'
|
'La colonna <strong>Alert</strong> compare in base al punteggio inerente: <em>Azione Prioritaria</em> (15–25), <em>Azione Critica</em> (9–14), <em>Azione Necessaria</em> (3–8), <em>Azione Suggerita</em> (1–2). Le soglie sono una <strong>scelta metodologica</strong> dell\'organizzazione (rif. ISO/IEC 27001 §6.1.2), <strong>non un obbligo di legge</strong>. Cliccando l\'Alert si apre l\'elenco delle azioni passate su quel rischio e si può <strong>creare una nuova azione</strong>.',
|
||||||
|
'La colonna <strong>Esito residuo</strong> segnala se il rischio residuo va <strong>rivalutato</strong> (punteggio 9–25) o è <strong>adeguato</strong> (1–8). <strong>Importante</strong>: il punteggio di rischio residuo <strong>non</strong> determina automaticamente lo <em>stato di conformità</em> del requisito. La <strong>conformità</strong> dipende dall\'attuazione della misura e dalle evidenze e si aggiorna in modo esplicito dal link "stato" nella colonna Rischio Residuo. Sono due piani distinti (punteggio di rischio ≠ conformità al requisito).',
|
||||||
|
'La tabella elenca <strong>solo i requisiti applicabili alla tua classe</strong> (importante → Allegato 1, essenziale → Allegato 2 della Determinazione ACN 164179/2025). Se l\'elenco e\' lungo, il riquadro <strong>scorre verticalmente</strong> con l\'intestazione fissa.',
|
||||||
|
'La seconda tabella, <strong>Rischi aggiuntivi (approccio multirischio / all-hazard)</strong>, permette ai <strong>profili amministrativi</strong> di aggiungere rischi non coperti da un singolo requisito (minacce fisiche, catena di fornitura, eventi naturali): copre l\'approccio multirischio richiesto dall\'art. 24 D.Lgs. 138/2024.'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+8
-2
@@ -171,8 +171,14 @@ const I18n = (function () {
|
|||||||
'risks.avoid': { it: 'Evitare', en: 'Avoid' },
|
'risks.avoid': { it: 'Evitare', en: 'Avoid' },
|
||||||
'risks.derived_heading': { it: 'Rischi dai requisiti del framework di sicurezza', en: 'Risks from security framework requirements' },
|
'risks.derived_heading': { it: 'Rischi dai requisiti del framework di sicurezza', en: 'Risks from security framework requirements' },
|
||||||
'risks.derived_code_legend': { it: 'RSK-‹misura›Com.‹n› è una codifica interna di NIS2 Agile; ‹misura› (es. GV.OC-04) è il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. Elenco in sola lettura.', en: 'RSK-‹measure›Com.‹n› is an internal NIS2 Agile code; ‹measure› (e.g. GV.OC-04) is the National Framework measure code referenced by ACN Determination 164179/2025. Read-only list.' },
|
'risks.derived_code_legend': { it: 'RSK-‹misura›Com.‹n› è una codifica interna di NIS2 Agile; ‹misura› (es. GV.OC-04) è il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. Elenco in sola lettura.', en: 'RSK-‹measure›Com.‹n› is an internal NIS2 Agile code; ‹measure› (e.g. GV.OC-04) is the National Framework measure code referenced by ACN Determination 164179/2025. Read-only list.' },
|
||||||
'risks.col_code': { it: 'Codice', en: 'Code' },
|
'risks.col_code': { it: 'Codice rischio', en: 'Risk code' },
|
||||||
'risks.col_risk': { it: 'Rischio', en: 'Risk' },
|
'risks.col_risk': { it: 'Descrizione rischio', en: 'Risk description' },
|
||||||
|
'risks.grp_inherent': { it: 'VALUTAZIONE RISCHIO INERENTE', en: 'INHERENT RISK ASSESSMENT' },
|
||||||
|
'risks.grp_residual': { it: 'VALUTAZIONE RISCHIO RESIDUO', en: 'RESIDUAL RISK ASSESSMENT' },
|
||||||
|
'risks.col_alert': { it: 'Alert', en: 'Alert' },
|
||||||
|
'risks.col_inherent': { it: 'Rischio Inerente', en: 'Inherent risk' },
|
||||||
|
'risks.col_res_outcome': { it: 'Esito residuo', en: 'Residual outcome' },
|
||||||
|
'risks.col_residual': { it: 'Rischio Residuo', en: 'Residual risk' },
|
||||||
'risks.col_measure': { it: 'Misura', en: 'Measure' },
|
'risks.col_measure': { it: 'Misura', en: 'Measure' },
|
||||||
'risks.col_conformity': { it: 'Conformità', en: 'Conformity' },
|
'risks.col_conformity': { it: 'Conformità', en: 'Conformity' },
|
||||||
'risks.col_evaluation': { it: 'Valutazione', en: 'Evaluation' },
|
'risks.col_evaluation': { it: 'Valutazione', en: 'Evaluation' },
|
||||||
|
|||||||
+3
-3
@@ -175,12 +175,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → kb.js -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → kb.js -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/kb.js"></script>
|
<script src="/js/kb.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Gate auth + chrome come le altre pagine V3.
|
// Gate auth + chrome come le altre pagine V3.
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ body { background: var(--bg-main); }
|
|||||||
|
|
||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
|
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ══════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════
|
||||||
// CONFIG
|
// CONFIG
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
|
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script src="js/common.js?v=20260729b"></script>
|
<script src="js/common.js?v=20260729b"></script>
|
||||||
<script>
|
<script>
|
||||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||||
|
|||||||
@@ -147,12 +147,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -175,12 +175,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -137,12 +137,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth + chrome + i18n + help ──────────────────────────
|
// ── Auth + chrome + i18n + help ──────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -494,7 +494,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
|
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script src="js/common.js?v=20260729b"></script>
|
<script src="js/common.js?v=20260729b"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ──────────────────────────────────────────────────
|
// ── Auth check ──────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -151,12 +151,12 @@
|
|||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → organigramma.js.
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → organigramma.js.
|
||||||
organigramma.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
organigramma.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/organigramma.js?v=20260627"></script>
|
<script src="/js/organigramma.js?v=20260627"></script>
|
||||||
<script>
|
<script>
|
||||||
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
|
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
|
||||||
|
|||||||
@@ -340,12 +340,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ─────────────────────────────────────────
|
// ── Auth & Init ─────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
+3
-3
@@ -141,12 +141,12 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Scripts V3. raci.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
<!-- Scripts V3. raci.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script src="/js/raci.js?v=20260627"></script>
|
<script src="/js/raci.js?v=20260627"></script>
|
||||||
<script>
|
<script>
|
||||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||||
|
|||||||
@@ -268,7 +268,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Stessa logica della register.html: ZERO modifiche backend -->
|
<!-- Stessa logica della register.html: ZERO modifiche backend -->
|
||||||
<script src="js/api.js?v=20260729b"></script>
|
<script src="js/api.js?v=20260730a"></script>
|
||||||
<script src="js/common.js?v=20260729b"></script>
|
<script src="js/common.js?v=20260729b"></script>
|
||||||
<script>
|
<script>
|
||||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||||
|
|||||||
+3
-3
@@ -232,12 +232,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ─────────────────────────────────────────
|
// ── Auth & Init ─────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -157,12 +157,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
+243
-62
@@ -306,10 +306,44 @@
|
|||||||
}
|
}
|
||||||
#view-derived .table-container.scroll-box thead th {
|
#view-derived .table-container.scroll-box thead th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
|
||||||
background: var(--gray-50);
|
background: var(--gray-50);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
/* Tabella 12 colonne (spec Simon): 2 righe di intestazione, gruppi inerente/residuo */
|
||||||
|
table.derived-12 thead tr:first-child th { top: 0; }
|
||||||
|
table.derived-12 thead tr:nth-child(2) th { top: 30px; }
|
||||||
|
table.derived-12 th, table.derived-12 td {
|
||||||
|
font-size: 0.8rem; padding: 6px 8px; text-align: center; vertical-align: middle;
|
||||||
|
}
|
||||||
|
table.derived-12 th.grp {
|
||||||
|
font-size: 0.66rem; letter-spacing: 0.04em; font-weight: 700; color: #fff;
|
||||||
|
}
|
||||||
|
table.derived-12 th.grp-inh { background: #b45309; }
|
||||||
|
table.derived-12 th.grp-res { background: #0e7490; }
|
||||||
|
table.derived-12 td.cell-code, table.derived-12 td.cell-desc { text-align: left; }
|
||||||
|
.rk-eval { display:inline-flex; align-items:center; justify-content:center; min-width:26px; height:22px;
|
||||||
|
padding:0 6px; border-radius:6px; font-weight:800; font-size:0.78rem; color:#fff; }
|
||||||
|
.rk-low { background:#16a34a; } .rk-med { background:#d97706; }
|
||||||
|
.rk-high { background:#ea580c; } .rk-crit { background:#dc2626; }
|
||||||
|
.rk-sel { width:52px; padding:2px 4px; font-size:0.78rem; border:1px solid var(--gray-300);
|
||||||
|
border-radius:6px; background:#fff; text-align:center; }
|
||||||
|
.rk-sel:focus { border-color: var(--primary); outline:none; }
|
||||||
|
.alert-btn { border:0; border-radius:16px; padding:4px 10px; font-size:0.7rem; font-weight:800;
|
||||||
|
letter-spacing:.02em; cursor:pointer; white-space:nowrap; }
|
||||||
|
.alert-pri { background:#dc2626; color:#fff; }
|
||||||
|
.alert-cri { background:#f87171; color:#fff; }
|
||||||
|
.alert-nec { background:#f59e0b; color:#fff; }
|
||||||
|
.alert-sug { background:#16a34a; color:#fff; }
|
||||||
|
.flag-riv { display:inline-block; padding:3px 9px; border-radius:14px; font-size:0.7rem; font-weight:800;
|
||||||
|
background:#fee2e2; color:#b91c1c; }
|
||||||
|
.flag-adeg { display:inline-block; padding:3px 9px; border-radius:14px; font-size:0.7rem; font-weight:800;
|
||||||
|
background:#dcfce7; color:#15803d; }
|
||||||
|
.rk-valutato { font-size:0.72rem; font-weight:700; color:#15803d; }
|
||||||
|
.rk-empty { color: var(--gray-300); }
|
||||||
|
.act-row { display:flex; align-items:flex-start; gap:10px; padding:10px 4px; border-bottom:1px solid var(--gray-100); }
|
||||||
|
.act-row .act-when { font-size:0.72rem; color:var(--gray-500); white-space:nowrap; min-width:74px; }
|
||||||
|
.act-row .act-desc { flex:1; font-size:0.82rem; color:var(--gray-800); }
|
||||||
|
.act-row .act-st { font-size:0.68rem; font-weight:700; text-transform:uppercase; color:var(--gray-500); }
|
||||||
</style>
|
</style>
|
||||||
<!-- PWA:start -->
|
<!-- PWA:start -->
|
||||||
<link rel="manifest" href="/manifest.webmanifest">
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
@@ -367,23 +401,30 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="table-container scroll-box">
|
<div class="table-container scroll-box">
|
||||||
<table>
|
<table class="derived-12">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" data-i18n="risks.col_code">Codice</th>
|
<th scope="col" rowspan="2" data-i18n="risks.col_code">Codice rischio</th>
|
||||||
<th scope="col" data-i18n="risks.col_risk">Rischio</th>
|
<th scope="col" rowspan="2" data-i18n="risks.col_risk">Descrizione rischio</th>
|
||||||
<th scope="col" data-i18n="risks.col_measure">Misura</th>
|
<th scope="colgroup" colspan="3" class="grp grp-inh" data-i18n="risks.grp_inherent">VALUTAZIONE RISCHIO INERENTE</th>
|
||||||
<th scope="col" data-i18n="risks.col_conformity">Conformità</th>
|
<th scope="col" rowspan="2" data-i18n="risks.col_alert">Alert</th>
|
||||||
|
<th scope="col" rowspan="2" data-i18n="risks.col_inherent">Rischio Inerente</th>
|
||||||
|
<th scope="colgroup" colspan="3" class="grp grp-res" data-i18n="risks.grp_residual">VALUTAZIONE RISCHIO RESIDUO</th>
|
||||||
|
<th scope="col" rowspan="2" data-i18n="risks.col_res_outcome">Esito residuo</th>
|
||||||
|
<th scope="col" rowspan="2" data-i18n="risks.col_residual">Rischio Residuo</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" data-i18n="risks.col_probability">Probabilità</th>
|
||||||
|
<th scope="col" data-i18n="risks.col_impact">Impatto</th>
|
||||||
|
<th scope="col" data-i18n="risks.col_evaluation">Valutazione</th>
|
||||||
<th scope="col" data-i18n="risks.col_probability">Probabilità</th>
|
<th scope="col" data-i18n="risks.col_probability">Probabilità</th>
|
||||||
<th scope="col" data-i18n="risks.col_impact">Impatto</th>
|
<th scope="col" data-i18n="risks.col_impact">Impatto</th>
|
||||||
<th scope="col" data-i18n="risks.col_risk_level">Rischio (P×I)</th>
|
|
||||||
<th scope="col" data-i18n="risks.col_evaluation">Valutazione</th>
|
<th scope="col" data-i18n="risks.col_evaluation">Valutazione</th>
|
||||||
<th scope="col" id="derived-th-actions" data-i18n="risks.col_actions" style="display:none;">Azioni</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="derived-table-body">
|
<tbody id="derived-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5">
|
<td colspan="12">
|
||||||
<div class="spinner" style="margin:40px auto;"></div>
|
<div class="spinner" style="margin:40px auto;"></div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -499,12 +540,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ──────────────────────────────────────────────
|
// ── Auth & Init ──────────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
@@ -634,16 +675,44 @@
|
|||||||
return 'score-low';
|
return 'score-low';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Livello del punteggio 1-25 (classe colore del riquadro "Valutazione") ──
|
||||||
|
function evalClass(p) {
|
||||||
|
if (p >= 15) return 'rk-crit';
|
||||||
|
if (p >= 9) return 'rk-high';
|
||||||
|
if (p >= 3) return 'rk-med';
|
||||||
|
return 'rk-low';
|
||||||
|
}
|
||||||
|
// ── Banda ALERT sul punteggio INERENTE (prodotto P×I). Eccezione #3: la banda
|
||||||
|
// 13-14 (scoperta nella spec) è inclusa in "Critica" → 9-14 Critica, 15-25 Prioritaria. ──
|
||||||
|
function alertBand(p) {
|
||||||
|
if (p == null) return null;
|
||||||
|
if (p >= 15) return { cls: 'alert-pri', label: 'Azione Prioritaria' };
|
||||||
|
if (p >= 9) return { cls: 'alert-cri', label: 'Azione Critica' };
|
||||||
|
if (p >= 3) return { cls: 'alert-nec', label: 'Azione Necessaria' };
|
||||||
|
return { cls: 'alert-sug', label: 'Azione Suggerita' };
|
||||||
|
}
|
||||||
|
// ── Esito del RESIDUO (col.11). Eccezione #1: NON è un verdetto di conformità,
|
||||||
|
// è un flag di rivalutazione. ≥9 → "Rivalutare"; 1-8 → "Adeguato". ──
|
||||||
|
function residualOutcome(p) {
|
||||||
|
if (p == null) return null;
|
||||||
|
return p >= 9
|
||||||
|
? { cls: 'flag-riv', label: 'Rivalutare' }
|
||||||
|
: { cls: 'flag-adeg', label: 'Adeguato' };
|
||||||
|
}
|
||||||
|
function scoreSel(id, val, editable) {
|
||||||
|
if (!editable) return (val >= 1 && val <= 5) ? (val + '/5') : '<span class="rk-empty">—</span>';
|
||||||
|
let o = `<option value="">—</option>`;
|
||||||
|
for (let v = 1; v <= 5; v++) o += `<option value="${v}" ${v == val ? 'selected' : ''}>${v}</option>`;
|
||||||
|
return `<select class="rk-sel" id="${id}" onchange="onScoreChange(${id.split('-')[1]})">${o}</select>`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderDerivedTable(rows) {
|
function renderDerivedTable(rows) {
|
||||||
const tbody = document.getElementById('derived-table-body');
|
const tbody = document.getElementById('derived-table-body');
|
||||||
const colspan = 8 + (canEvaluate ? 1 : 0);
|
// Ticket #501/2: mostra SOLO i requisiti applicabili alla classe del soggetto.
|
||||||
// Ticket #501/2: mostra SOLO i requisiti applicabili alla classe del soggetto
|
|
||||||
// (importante → Allegato 1, essenziale → Allegato 2 della Det. ACN 164179/2025).
|
|
||||||
// Le righe non applicabili non vengono più elencate (prima erano solo attenuate).
|
|
||||||
rows = (rows || []).filter(isDerivedApplicable);
|
rows = (rows || []).filter(isDerivedApplicable);
|
||||||
if (!rows || rows.length === 0) {
|
if (!rows || rows.length === 0) {
|
||||||
tbody.innerHTML = `
|
tbody.innerHTML = `
|
||||||
<tr><td colspan="${colspan}">
|
<tr><td colspan="12">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944z" clip-rule="evenodd"/></svg>
|
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944z" clip-rule="evenodd"/></svg>
|
||||||
<h4>Nessun requisito applicabile</h4>
|
<h4>Nessun requisito applicabile</h4>
|
||||||
@@ -655,66 +724,178 @@
|
|||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
const applicable = isDerivedApplicable(r);
|
const reqId = r.requisito_id;
|
||||||
const st = DERIVED_STATUS_BADGE[r.stato] || DERIVED_STATUS_BADGE.da_valutare;
|
const editable = canEvaluate && r.stato !== 'non_applicabile';
|
||||||
const measureCode = r.misura_code || '-';
|
const measureCode = r.misura_code || '-';
|
||||||
const reqCode = r.requisito_code || '';
|
const reqCode = r.requisito_code || '';
|
||||||
const reqDescr = r.requisito_descr || '';
|
const riskDescr = r.risk_descr || r.requisito_descr || '';
|
||||||
const measureDescr = r.misura_descr || '';
|
const st = DERIVED_STATUS_BADGE[r.stato] || DERIVED_STATUS_BADGE.da_valutare;
|
||||||
const evalVal = (r.valutazione_rischio !== null && r.valutazione_rischio !== undefined && r.valutazione_rischio !== '')
|
const riskTooltip = `Rischio di non conformità legato al requisito ${measureCode} ${reqCode}`.trim();
|
||||||
? escapeHtml(String(r.valutazione_rischio)) : '—';
|
|
||||||
const rowStyle = applicable ? '' : 'style="opacity:0.5;"';
|
|
||||||
const riskTooltip = `Rischio derivante dal mancato o parziale soddisfacimento del requisito ${measureCode} ${reqCode}`.trim();
|
|
||||||
|
|
||||||
// Colonna "Rischio": etichetta + requisito come OGGETTO del rischio (mai il requisito nudo).
|
// INERENTE
|
||||||
const conformityCell = applicable
|
const iL = (r.likelihood >= 1 && r.likelihood <= 5) ? parseInt(r.likelihood) : null;
|
||||||
? `<span class="badge ${st.cls}">${escapeHtml(st.label)}</span>`
|
const iI = (r.impact >= 1 && r.impact <= 5) ? parseInt(r.impact) : null;
|
||||||
: `<span class="badge badge-neutral">Non applicabile alla tua classe</span>`;
|
const iP = (iL != null && iI != null) ? iL * iI : null;
|
||||||
|
// RESIDUO
|
||||||
|
const rL = (r.residual_likelihood >= 1 && r.residual_likelihood <= 5) ? parseInt(r.residual_likelihood) : null;
|
||||||
|
const rI = (r.residual_impact >= 1 && r.residual_impact <= 5) ? parseInt(r.residual_impact) : null;
|
||||||
|
const rP = (rL != null && rI != null) ? rL * rI : null;
|
||||||
|
|
||||||
// Probabilità / Impatto / Rischio: solo per righe applicabili e non "non applicabili".
|
const iEval = iP != null ? `<span class="rk-eval ${evalClass(iP)}" id="iv-${reqId}">${iP}</span>` : `<span class="rk-empty" id="iv-${reqId}">—</span>`;
|
||||||
let probCell = '<td>—</td>', impCell = '<td>—</td>', scoreCell = '<td>—</td>';
|
const rEval = rP != null ? `<span class="rk-eval ${evalClass(rP)}" id="rv-${reqId}">${rP}</span>` : `<span class="rk-empty" id="rv-${reqId}">—</span>`;
|
||||||
if (applicable && r.stato !== 'non_applicabile') {
|
|
||||||
const l = (r.likelihood >= 1 && r.likelihood <= 5) ? parseInt(r.likelihood) : null;
|
|
||||||
const i = (r.impact >= 1 && r.impact <= 5) ? parseInt(r.impact) : null;
|
|
||||||
probCell = `<td>${l != null ? l + '/5' : '—'}</td>`;
|
|
||||||
impCell = `<td>${i != null ? i + '/5' : '—'}</td>`;
|
|
||||||
if (r.score != null && r.score !== '' && !isNaN(r.score)) {
|
|
||||||
const sc = parseInt(r.score);
|
|
||||||
scoreCell = `<td><span class="risk-score ${getDerivedScoreClass(sc)}" title="P${l != null ? l : '?'} × I${i != null ? i : '?'} = ${sc}">${sc}</span></td>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Colonna Azioni (solo per i ruoli che possono valutare).
|
const ab = alertBand(iP);
|
||||||
let actionsCell = '';
|
const alertCell = ab
|
||||||
if (canEvaluate) {
|
? `<button class="alert-btn ${ab.cls}" onclick="openActionsPopup(${reqId})" title="Vedi/crea azioni">${ab.label}</button>`
|
||||||
actionsCell = applicable
|
: `<span class="rk-empty" id="al-${reqId}">—</span>`;
|
||||||
? `<td><button class="btn btn-ghost btn-sm" onclick="openEvaluateModal(${r.requisito_id})" title="Valuta rischio"><i data-lucide="sliders-horizontal" class="ic"></i> Valuta</button></td>`
|
const inhOutcome = iP != null ? '<span class="rk-valutato">Valutato</span>' : '<span class="rk-empty">—</span>';
|
||||||
: '<td><span class="text-muted">—</span></td>';
|
|
||||||
}
|
const ro = residualOutcome(rP);
|
||||||
|
const resOutcomeCell = ro ? `<span class="${ro.cls}">${ro.label}</span>` : '<span class="rk-empty">—</span>';
|
||||||
|
// Col.12 — Rischio Residuo: "Valutato" + stato requisito ESPLICITO (eccezione #1: non automatico).
|
||||||
|
const resCell = rP != null
|
||||||
|
? `<span class="rk-valutato">Valutato</span>${editable ? `<div style="margin-top:3px;"><a href="javascript:void(0)" onclick="openStateModal(${reqId})" style="font-size:0.68rem;color:var(--primary);">stato: ${escapeHtml(st.label)}</a></div>` : `<div style="font-size:0.68rem;color:var(--gray-500);margin-top:3px;">${escapeHtml(st.label)}</div>`}`
|
||||||
|
: '<span class="rk-empty">—</span>';
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<tr ${rowStyle}>
|
<tr>
|
||||||
<td><code style="font-size:0.78rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code></td>
|
<td class="cell-code">
|
||||||
<td title="${escapeHtml(riskTooltip)}">
|
<a href="misure-requisiti.html?req=${reqId}" title="Apri in Misure e Requisiti"><code style="font-size:0.75rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code></a>
|
||||||
<div style="font-size:0.68rem;text-transform:uppercase;letter-spacing:0.04em;font-weight:600;color:var(--gray-500);margin-bottom:2px;">Rischio di non conformità</div>
|
<div style="font-size:0.66rem;color:var(--gray-500);margin-top:2px;">${escapeHtml(measureCode)} ${escapeHtml(reqCode)}</div>
|
||||||
<div style="font-size:0.86rem;color:var(--gray-800);">${escapeHtml(reqDescr)}</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="cell-desc" title="${escapeHtml(riskTooltip)}">
|
||||||
<code style="font-size:0.75rem;color:var(--gray-600);">${escapeHtml(measureCode)}</code>
|
<div style="font-size:0.64rem;text-transform:uppercase;letter-spacing:0.04em;font-weight:600;color:var(--gray-500);">Rischio di non conformità</div>
|
||||||
${measureDescr ? `<div style="font-size:0.78rem;color:var(--gray-500);margin-top:2px;">${escapeHtml(measureDescr)}</div>` : ''}
|
<div style="font-size:0.82rem;color:var(--gray-800);">${escapeHtml(riskDescr)}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>${conformityCell}</td>
|
<td>${scoreSel('il-' + reqId, iL, editable)}</td>
|
||||||
${probCell}
|
<td>${scoreSel('ii-' + reqId, iI, editable)}</td>
|
||||||
${impCell}
|
<td>${iEval}</td>
|
||||||
${scoreCell}
|
<td>${alertCell}</td>
|
||||||
<td>${evalVal}</td>
|
<td>${inhOutcome}</td>
|
||||||
${actionsCell}
|
<td>${scoreSel('rl-' + reqId, rL, editable)}</td>
|
||||||
|
<td>${scoreSel('ri-' + reqId, rI, editable)}</td>
|
||||||
|
<td>${rEval}</td>
|
||||||
|
<td>${resOutcomeCell}</td>
|
||||||
|
<td>${resCell}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
tbody.innerHTML = html;
|
tbody.innerHTML = html;
|
||||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Editing inline: legge i 4 punteggi della riga, salva l'intera valutazione, ri-render. ──
|
||||||
|
async function onScoreChange(reqId) {
|
||||||
|
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||||
|
if (!r) return;
|
||||||
|
const rd = id => { const el = document.getElementById(id); if (!el) return null; const v = el.value; return v === '' ? null : parseInt(v); };
|
||||||
|
r.likelihood = rd('il-' + reqId);
|
||||||
|
r.impact = rd('ii-' + reqId);
|
||||||
|
r.score = (r.likelihood && r.impact) ? r.likelihood * r.impact : null;
|
||||||
|
r.residual_likelihood = rd('rl-' + reqId);
|
||||||
|
r.residual_impact = rd('ri-' + reqId);
|
||||||
|
r.residual_score = (r.residual_likelihood && r.residual_impact) ? r.residual_likelihood * r.residual_impact : null;
|
||||||
|
renderDerivedTable(derivedData);
|
||||||
|
loadMatrix();
|
||||||
|
try {
|
||||||
|
await api.frameworkSetState({
|
||||||
|
requisito_id: parseInt(reqId),
|
||||||
|
stato: r.stato || 'da_valutare',
|
||||||
|
valutazione_rischio: r.valutazione_rischio || '',
|
||||||
|
likelihood: r.likelihood, impact: r.impact,
|
||||||
|
residual_likelihood: r.residual_likelihood, residual_impact: r.residual_impact,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(e.message || 'Errore nel salvataggio', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Popup Azioni (col.6 Alert): elenco azioni passate + "crea azione" ──
|
||||||
|
async function openActionsPopup(reqId) {
|
||||||
|
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||||
|
if (!r) return;
|
||||||
|
let actions = [];
|
||||||
|
try {
|
||||||
|
const res = await api.requisitoActions(reqId);
|
||||||
|
if (res && res.success && res.data) actions = res.data.actions || [];
|
||||||
|
} catch (e) { /* mostra comunque il popup */ }
|
||||||
|
|
||||||
|
const today = new Date().toLocaleDateString('it-IT');
|
||||||
|
let list = `
|
||||||
|
<div class="act-row" style="background:var(--gray-50);border-radius:8px;">
|
||||||
|
<div class="act-when"><b>${today}</b></div>
|
||||||
|
<div class="act-desc"><input type="text" class="form-input" id="new-act-descr" placeholder="Descrivi la nuova azione…" style="width:100%;"></div>
|
||||||
|
<button class="btn btn-primary btn-sm" onclick="createReqAction(${reqId})">Crea azione</button>
|
||||||
|
</div>`;
|
||||||
|
if (actions.length) {
|
||||||
|
list += actions.map(a => {
|
||||||
|
const when = a.created_at ? new Date(a.created_at.replace(' ', 'T')).toLocaleDateString('it-IT') : '—';
|
||||||
|
return `<div class="act-row"><div class="act-when">${when}</div><div class="act-desc">${escapeHtml(a.description || '')}</div><div class="act-st">${escapeHtml(a.status || '')}</div></div>`;
|
||||||
|
}).join('');
|
||||||
|
} else {
|
||||||
|
list += `<div style="padding:14px 4px;color:var(--gray-500);font-size:0.82rem;">Nessuna azione precedente su questo rischio.</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div style="padding:10px 12px;background:var(--gray-50);border:1px solid var(--gray-200);border-radius:8px;margin-bottom:14px;">
|
||||||
|
<code style="font-size:0.78rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code>
|
||||||
|
<div style="font-size:0.82rem;color:var(--gray-800);margin-top:3px;">${escapeHtml(r.risk_descr || r.requisito_descr || '')}</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted" style="font-size:0.74rem;margin:0 0 10px;">La soglia di trattamento è una scelta metodologica dell'organizzazione (rif. ISO/IEC 27001 §6.1.2), non un obbligo di legge.</p>
|
||||||
|
${list}`;
|
||||||
|
showModal('Azioni sul rischio', content, {
|
||||||
|
size: 'lg',
|
||||||
|
footer: `<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createReqAction(reqId) {
|
||||||
|
const el = document.getElementById('new-act-descr');
|
||||||
|
const descr = (el && el.value || '').trim();
|
||||||
|
if (!descr) { showNotification('Inserisci una descrizione', 'error'); return; }
|
||||||
|
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||||
|
try {
|
||||||
|
const res = await api.createRequisitoAction({ requisito_id: parseInt(reqId), description: descr, risk_code: r ? r.risk_code : null });
|
||||||
|
if (res && res.success) {
|
||||||
|
showNotification('Azione creata', 'success');
|
||||||
|
openActionsPopup(reqId);
|
||||||
|
} else {
|
||||||
|
showNotification((res && res.message) || 'Errore', 'error');
|
||||||
|
}
|
||||||
|
} catch (e) { showNotification(e.message || 'Errore', 'error'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Aggiorna ESPLICITAMENTE lo stato di conformità del requisito (eccezione #1) ──
|
||||||
|
function openStateModal(reqId) {
|
||||||
|
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||||
|
if (!r || !canEvaluate) return;
|
||||||
|
const opts = DERIVED_STATUS_OPTIONS.map(([v, l]) =>
|
||||||
|
`<option value="${v}" ${v === (r.stato || 'da_valutare') ? 'selected' : ''}>${escapeHtml(l)}</option>`).join('');
|
||||||
|
const content = `
|
||||||
|
<p class="text-muted" style="font-size:0.8rem;line-height:1.5;">Lo stato di <b>conformità</b> dipende dall'<b>attuazione della misura e dalle evidenze</b>, non dal punteggio di rischio. Aggiornalo qui in modo esplicito.</p>
|
||||||
|
<div class="form-group"><label class="form-label">Stato del requisito</label><select class="form-select" id="st-sel">${opts}</select></div>`;
|
||||||
|
showModal('Stato del requisito', content, {
|
||||||
|
size: 'md',
|
||||||
|
footer: `<button class="btn btn-secondary" onclick="closeModal()">Annulla</button><button class="btn btn-primary" onclick="saveState(${reqId})">Salva stato</button>`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function saveState(reqId) {
|
||||||
|
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||||
|
if (!r) return;
|
||||||
|
const stato = document.getElementById('st-sel').value;
|
||||||
|
try {
|
||||||
|
await api.frameworkSetState({
|
||||||
|
requisito_id: parseInt(reqId), stato: stato,
|
||||||
|
valutazione_rischio: r.valutazione_rischio || '',
|
||||||
|
likelihood: r.likelihood, impact: r.impact,
|
||||||
|
residual_likelihood: r.residual_likelihood, residual_impact: r.residual_impact,
|
||||||
|
});
|
||||||
|
r.stato = stato;
|
||||||
|
closeModal();
|
||||||
|
showNotification('Stato aggiornato', 'success');
|
||||||
|
renderDerivedTable(derivedData);
|
||||||
|
} catch (e) { showNotification(e.message || 'Errore', 'error'); }
|
||||||
|
}
|
||||||
|
|
||||||
// ── Modale "Valuta" (opzione B) — valutazione rischio-da-requisito ──
|
// ── Modale "Valuta" (opzione B) — valutazione rischio-da-requisito ──
|
||||||
const DERIVED_STATUS_OPTIONS = [
|
const DERIVED_STATUS_OPTIONS = [
|
||||||
['da_valutare', 'Da valutare'],
|
['da_valutare', 'Da valutare'],
|
||||||
|
|||||||
@@ -735,12 +735,12 @@
|
|||||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||||
i18n → help → init pagina (inline, invariato). -->
|
i18n → help → init pagina (inline, invariato). -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth check ───────────────────────────────────────────
|
// ── Auth check ───────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -215,12 +215,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -210,12 +210,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -542,12 +542,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init ─────────────────────────────────────────
|
// ── Auth & Init ─────────────────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -190,12 +190,12 @@
|
|||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali+showModal/showNotification) →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali+showModal/showNotification) →
|
||||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help.
|
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help.
|
||||||
La logica della pagina e' inline (nessun training.js): preservata invariata. -->
|
La logica della pagina e' inline (nessun training.js): preservata invariata. -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth & Init (ordine V3) ─────────────────────────────
|
// ── Auth & Init (ordine V3) ─────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
@@ -258,12 +258,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
loadSidebar();
|
loadSidebar();
|
||||||
|
|||||||
@@ -143,12 +143,12 @@
|
|||||||
|
|
||||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina. -->
|
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina. -->
|
||||||
<script src="/js/api.js?v=20260729b"></script>
|
<script src="/js/api.js?v=20260730a"></script>
|
||||||
<script src="/js/common.js?v=20260729b"></script>
|
<script src="/js/common.js?v=20260729b"></script>
|
||||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||||
<script src="/js/i18n.js?v=20260729"></script>
|
<script src="/js/i18n.js?v=20260730a"></script>
|
||||||
<script src="/js/help.js?v=20260729b"></script>
|
<script src="/js/help.js?v=20260730a"></script>
|
||||||
<script>
|
<script>
|
||||||
// ── Auth + chrome + i18n + help ──────────────────────────────
|
// ── Auth + chrome + i18n + help ──────────────────────────────
|
||||||
if (!checkAuth()) throw new Error('Not authenticated');
|
if (!checkAuth()) throw new Error('Not authenticated');
|
||||||
|
|||||||
Reference in New Issue
Block a user