fix: compliance report Oracle design, polling robustness, print margins

- Oracle PDF style: decorative left strip, ORACLE brand header, red section titles, green table headers
- Cover page: removed rectangle, clean layout matching Oracle reference PDF
- Print: @page margin:0 + thead/tfoot table trick for per-page header/footer spacing without browser URL/date
- RAG remediation: fixed fallback dumping entire CIS chunk instead of just remediation section
- Findings: removed break-inside:avoid that caused empty pages, emoji replaced with [CSV] text
- Tables: break-inside:avoid on rows, section headers break-after:avoid, thead repeats on page break
- Compliance polling: useEffect-based with cleanup, auto-expand on ready, re-check on section toggle
- Explorer: silent polling during start/stop (no flickering)
- HTML report starts minimized, auto-expands on fresh generation
- Include dist/ in git for deployment
This commit is contained in:
nogueiraguh
2026-03-19 11:34:31 -03:00
parent 442e710b9f
commit 5028f632a6
8 changed files with 365 additions and 108 deletions

View File

@@ -577,6 +577,7 @@ 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('');
@@ -645,6 +646,7 @@ export default function ReportsPage() {
await loadReports();
if (p.status === 'completed') {
setSelectedRid(trackingId);
setShowIframe(true);
}
setTrackingId(null);
return true; // stop polling
@@ -693,6 +695,44 @@ 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 () => {
try {
const s = await reportsApi.complianceReportStatus(selectedRid);
if (s.ready && active) {
setComplianceReady(true);
setComplianceGenerating(false);
setShowCompliance(true);
}
} 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]);
const startComplianceGeneration = useCallback(async () => {
if (!selectedRid) return;
setComplianceGenerating(true);
setComplianceReady(false);
try {
await reportsApi.generateComplianceReport(selectedRid);
} catch { /* ignore */ }
}, [selectedRid]);
/* ── Handlers ── */
const handleRun = async () => {
if (!ociVal) {
@@ -1299,20 +1339,7 @@ export default function ReportsPage() {
/>
<div className="px-5 py-2 flex justify-end" style={{ borderTop: '1px solid var(--bd)' }}>
<button
onClick={async () => {
setComplianceGenerating(true);
setComplianceReady(false);
try {
await reportsApi.generateComplianceReport(selectedRid);
} catch { /* ignore */ }
const poll = setInterval(async () => {
try {
const s = await reportsApi.complianceReportStatus(selectedRid);
if (s.ready) { clearInterval(poll); setComplianceReady(true); setComplianceGenerating(false); }
} catch { /* keep polling */ }
}, 3000);
setTimeout(() => { clearInterval(poll); setComplianceGenerating(false); }, 300000);
}}
onClick={startComplianceGeneration}
disabled={complianceGenerating}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors"
style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}
@@ -1332,19 +1359,7 @@ export default function ReportsPage() {
<ShieldCheck size={32} style={{ color: 'var(--t4)', opacity: 0.5 }} />
<p className="text-[.78rem]" style={{ color: 'var(--t3)' }}>Report ainda não gerado</p>
<button
onClick={async () => {
setComplianceGenerating(true);
try {
await reportsApi.generateComplianceReport(selectedRid);
} catch { /* ignore */ }
const poll = setInterval(async () => {
try {
const s = await reportsApi.complianceReportStatus(selectedRid);
if (s.ready) { clearInterval(poll); setComplianceReady(true); setComplianceGenerating(false); }
} catch { /* keep polling */ }
}, 3000);
setTimeout(() => { clearInterval(poll); setComplianceGenerating(false); }, 300000);
}}
onClick={startComplianceGeneration}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-[.76rem] font-semibold cursor-pointer"
style={{ background: 'var(--gn)', color: '#fff' }}
>