feat: remove legacy frontend, root routing, compliance ZIP with Chromium PDF, chat markdown styling

- Legacy frontend removed: React SPA served at / (no /app/ prefix)
- Compliance report ZIP: Chromium headless --print-to-pdf (identical to browser) + CSV findings
- Server-side generation state: .compliance_generating marker survives page navigation
- RAG enriched: Description + Rationale + Remediation + Audit extracted from CIS vector chunks
- Chat markdown: syntax highlighting (Catppuccin), styled headings/lists/tables/blockquotes/code
- Default model auto-selected on Chat Agent load
This commit is contained in:
nogueiraguh
2026-03-20 07:21:41 -03:00
parent 5028f632a6
commit 61fbb3861d
18 changed files with 471 additions and 200 deletions

View File

@@ -577,7 +577,6 @@ export default function ReportsPage() {
const [showCompliance, setShowCompliance] = useState(false);
const [complianceReady, setComplianceReady] = useState(false);
const [complianceGenerating, setComplianceGenerating] = useState(false);
const compliancePollRef = useRef<ReturnType<typeof setInterval> | null>(null);
/* ── Error ── */
const [error, setError] = useState('');
@@ -625,12 +624,17 @@ export default function ReportsPage() {
if (running && !trackingId) {
setTrackingId(running.id);
}
// Auto-select most recent completed report if none selected
if (!selectedRid && data.length > 0) {
const completed = data.find((r) => r.status === 'completed');
if (completed) setSelectedRid(completed.id);
}
} catch (err) {
setError(err instanceof Error ? err.message : t('rpt.errorLoadReports'));
} finally {
setReportsLoading(false);
}
}, [trackingId]);
}, [trackingId, selectedRid]);
useEffect(() => { loadReports(); }, []); // eslint-disable-line react-hooks/exhaustive-deps
@@ -672,14 +676,14 @@ export default function ReportsPage() {
Promise.all([
reportsApi.summary(selectedRid),
reportsApi.files(selectedRid),
reportsApi.complianceReportStatus(selectedRid).catch(() => ({ ready: false })),
reportsApi.complianceReportStatus(selectedRid).catch(() => ({ ready: false, generating: false } as { ready: boolean; generating: boolean })),
])
.then(([summaryData, filesData, compStatus]) => {
if (!cancelled) {
setKpi(summaryData);
setFiles(filesData);
setComplianceReady(compStatus.ready);
setComplianceGenerating(false);
setComplianceReady(!!compStatus.ready);
setComplianceGenerating(!!compStatus.generating);
}
})
.catch(() => {
@@ -695,43 +699,34 @@ export default function ReportsPage() {
return () => { cancelled = true; };
}, [selectedRid]);
/* ── Compliance report polling (useEffect-based, survives re-renders + tab throttle) ── */
useEffect(() => {
if (!complianceGenerating || !selectedRid) return;
let active = true;
const check = async () => {
/* ── Compliance report polling (uses same usePolling as CIS report) ── */
usePolling(
async () => {
if (!complianceGenerating || !selectedRid) return false;
try {
const s = await reportsApi.complianceReportStatus(selectedRid);
if (s.ready && active) {
if (s.ready) {
setComplianceReady(true);
setComplianceGenerating(false);
setShowCompliance(true);
return true; // stop polling
}
} catch { /* keep polling */ }
};
check();
const id = setInterval(check, 3000);
compliancePollRef.current = id;
const timeout = setTimeout(() => { clearInterval(id); if (active) setComplianceGenerating(false); }, 300000);
return () => { active = false; clearInterval(id); clearTimeout(timeout); compliancePollRef.current = null; };
}, [complianceGenerating, selectedRid]);
/* ── Re-check compliance status when section is expanded ── */
useEffect(() => {
if (!showCompliance || !selectedRid || complianceReady || complianceGenerating) return;
reportsApi.complianceReportStatus(selectedRid)
.then((s) => { if (s.ready) setComplianceReady(true); })
.catch(() => {});
}, [showCompliance, selectedRid, complianceReady, complianceGenerating]);
return false;
},
3000,
complianceGenerating && !!selectedRid,
);
const startComplianceGeneration = useCallback(async () => {
if (!selectedRid) return;
setComplianceGenerating(true);
if (!selectedRid || complianceGenerating) return;
setComplianceReady(false);
setComplianceGenerating(true);
try {
await reportsApi.generateComplianceReport(selectedRid);
} catch { /* ignore */ }
}, [selectedRid]);
// polling is already running via usePolling above
}, [selectedRid, complianceGenerating]);
/* ── Handlers ── */
const handleRun = async () => {
@@ -1337,7 +1332,14 @@ export default function ReportsPage() {
style={{ height: 700, background: '#fff' }}
title="LAD A-Team CIS Compliance Report"
/>
<div className="px-5 py-2 flex justify-end" style={{ borderTop: '1px solid var(--bd)' }}>
<div className="px-5 py-2 flex justify-end gap-2" style={{ borderTop: '1px solid var(--bd)' }}>
<a
href={reportsApi.complianceReportZipUrl(selectedRid)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors no-underline"
style={{ background: 'var(--ac)', color: '#fff' }}
>
<Download size={12} /> Download ZIP
</a>
<button
onClick={startComplianceGeneration}
disabled={complianceGenerating}