[FIX] Completamento UI: metodo controlsMonitoring, OpenAPI ingest endpoints, i18n format, help monitoraggio
Il commit 372ccb5 aveva incluso versioni con Edit falliti (ancore errate):
- AuditController::controlsMonitoring ora effettivamente presente (era 501 in prod)
- ServicesController::openapi ora espone incidents-ingest/evidence-ingest/assets-ingest/controls-monitoring
- i18n.js: chiavi nel formato corretto {it,en} (risks.fair_tab/kri_tab, assets.import_btn, audit.monitoring_tab)
- help.js: sezione Monitoraggio Continuo in reports
Verificato in prod: openapi 4/4, controlsMonitoring/fairRegister/kri tutti 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e3e4aae1e4
commit
dbc1784955
@ -406,4 +406,68 @@ class AuditController extends BaseController
|
||||
header('Content-Disposition: attachment; filename="nis2_audit_certified_' . date('Y-m-d') . '.json"');
|
||||
$this->jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/controlsMonitoring
|
||||
* Continuous Control Monitoring (P1): stato/freschezza dei controlli
|
||||
* alimentato dalle evidenze automatiche. Versione JWT per la UI.
|
||||
*/
|
||||
public function controlsMonitoring(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
try {
|
||||
Database::query(
|
||||
"UPDATE compliance_controls cc
|
||||
LEFT JOIN (
|
||||
SELECT t.control_code, t.valid_until
|
||||
FROM control_evidence_auto t
|
||||
JOIN (SELECT control_code, MAX(collected_at) mx FROM control_evidence_auto
|
||||
WHERE organization_id = ? GROUP BY control_code) m
|
||||
ON m.control_code = t.control_code AND m.mx = t.collected_at
|
||||
WHERE t.organization_id = ?
|
||||
) last ON last.control_code = cc.control_code
|
||||
SET cc.monitoring_status = 'stale'
|
||||
WHERE cc.organization_id = ?
|
||||
AND cc.monitoring_status NOT IN ('not_monitored')
|
||||
AND last.valid_until IS NOT NULL AND last.valid_until < NOW()",
|
||||
[$orgId, $orgId, $orgId]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[CCM] ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT control_code, title, status, monitoring_status, last_checked_at, freshness_days, implementation_percentage
|
||||
FROM compliance_controls WHERE organization_id = ? ORDER BY control_code',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
$summary = ['healthy' => 0, 'warning' => 0, 'stale' => 0, 'failing' => 0, 'not_monitored' => 0];
|
||||
foreach ($rows as $r) {
|
||||
$ms = $r['monitoring_status'] ?? 'not_monitored';
|
||||
if (isset($summary[$ms])) $summary[$ms]++;
|
||||
}
|
||||
$monitored = count($rows) - $summary['not_monitored'];
|
||||
|
||||
$recent = [];
|
||||
try {
|
||||
$recent = Database::fetchAll(
|
||||
'SELECT control_code, source, source_system, status, summary, collected_at, valid_until
|
||||
FROM control_evidence_auto WHERE organization_id = ?
|
||||
ORDER BY collected_at DESC LIMIT 20',
|
||||
[$orgId]
|
||||
);
|
||||
} catch (Throwable $e) { /* ignore */ }
|
||||
|
||||
$this->jsonSuccess([
|
||||
'total_controls' => count($rows),
|
||||
'monitored' => $monitored,
|
||||
'coverage_percent' => count($rows) ? (int) round($monitored * 100 / count($rows)) : 0,
|
||||
'summary' => $summary,
|
||||
'controls' => $rows,
|
||||
'recent_evidence' => $recent,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,6 +467,15 @@ const HelpSystem = (function () {
|
||||
'Report disponibili: stato di compliance, registro rischi, incidenti, formazione.',
|
||||
'I report possono essere personalizzati per periodo e ambito.'
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: 'Monitoraggio Continuo dei Controlli (tab "Monitoraggio Continuo")',
|
||||
items: [
|
||||
'Mostra lo stato di <strong>freschezza</strong> di ogni controllo alimentato dalle evidenze raccolte automaticamente dai connettori (Evidence Automation).',
|
||||
'Semafori: <strong>Sano</strong> (evidenza valida), <strong>Attenzione</strong>, <strong>Evidenza scaduta</strong> (oltre la soglia di freschezza), <strong>Non conforme</strong> (ultimo check fallito), <strong>Non monitorato</strong>.',
|
||||
'La <strong>copertura</strong> indica la quota di controlli alimentati da evidenze automatiche: avvicina il modello alla compliance continua (vs assessment puntuale).',
|
||||
'Le evidenze arrivano via API <code>POST /api/services/evidence-ingest</code> (scope ingest:evidence) dai collector M365/AWS/EDR/SIEM.'
|
||||
]
|
||||
}
|
||||
],
|
||||
references: [
|
||||
|
||||
@ -61,6 +61,8 @@ const I18n = (function () {
|
||||
|
||||
// ── Rischi ─────────────────────────────────────
|
||||
'risks.title': { it: 'Gestione Rischi', en: 'Risk Management' },
|
||||
'risks.fair_tab': { it: 'Quantitativo (FAIR)', en: 'Quantitative (FAIR)' },
|
||||
'risks.kri_tab': { it: 'KRI', en: 'KRI' },
|
||||
'risks.new': { it: 'Nuovo Rischio', en: 'New Risk' },
|
||||
'risks.matrix': { it: 'Matrice Rischi', en: 'Risk Matrix' },
|
||||
'risks.likelihood': { it: 'Probabilita\'', en: 'Likelihood' },
|
||||
@ -110,6 +112,7 @@ const I18n = (function () {
|
||||
|
||||
// ── Asset ──────────────────────────────────────
|
||||
'assets.title': { it: 'Inventario Asset', en: 'Asset Inventory' },
|
||||
'assets.import_btn': { it: 'Importa', en: 'Import' },
|
||||
'assets.new': { it: 'Nuovo Asset', en: 'New Asset' },
|
||||
'assets.dependency_map': { it: 'Mappa Dipendenze', en: 'Dependency Map' },
|
||||
'assets.hardware': { it: 'Hardware', en: 'Hardware' },
|
||||
@ -119,6 +122,7 @@ const I18n = (function () {
|
||||
|
||||
// ── Audit ──────────────────────────────────────
|
||||
'audit.title': { it: 'Audit & Report', en: 'Audit & Reports' },
|
||||
'audit.monitoring_tab': { it: 'Monitoraggio Continuo', en: 'Continuous Monitoring' },
|
||||
'audit.controls': { it: 'Controlli di Compliance', en: 'Compliance Controls' },
|
||||
'audit.evidence': { it: 'Evidenze', en: 'Evidence' },
|
||||
'audit.logs': { it: 'Log di Audit', en: 'Audit Logs' },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user