[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:
DevEnv nis2-agile
2026-05-30 10:02:47 +02:00
co-authored by Claude Opus 4.8
parent e3e4aae1e4
commit dbc1784955
3 changed files with 77 additions and 0 deletions
@@ -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,
]);
}
}