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:
@@ -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' }}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user