feat: comprehensive security, performance, UX and deployment audit
Security:
- CORS restricted to explicit methods/headers, configurable via CORS_ORIGINS env
- Auth added to /reports/{rid}/html and /compliance-report endpoints
- Ownership check on report downloads
- Rate limiting on login (10 attempts/5min per IP with threading.Lock)
- Non-root container user (agent via gosu entrypoint)
- Nginx security headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy)
- .gitignore and .dockerignore for secret leak prevention
- .env.example with documentation
Performance:
- 16 SQLite indexes on foreign keys and frequently queried columns
- Pagination on chat messages (100), reports (50), audit log (100)
- subprocess.run wrapped in run_in_executor (3 async handlers)
- asyncio.wait_for timeouts on GenAI calls (300s) and Chromium PDF (120s)
- Thread pool reduced to 10 workers
- Code splitting with React.lazy (bundle: 1.3MB -> ~550KB initial)
- React.memo on TreeItem (recursive compartment tree)
Error handling:
- 13 bare except clauses replaced with logged Exception handlers
- Graceful shutdown handler (terminates subprocesses + executor)
- File upload validation (50MB max, extension whitelist per endpoint)
- Health check expanded (version, uptime, db_ok)
- Cache-Control on /api/genai/models (1h)
- Auto-cleanup audit_log > 30 days
Dead code removed:
- DownloadsPage.tsx, StubPage.tsx, MfaPage.tsx (moved to UsersPage)
- Legacy frontend/ directory
- 19 unused i18n keys, dist/ removed from git tracking
UX & i18n:
- 8 alert() calls replaced with styled error states (TerraformPage)
- 50+ hardcoded Portuguese strings localized to i18n (pt/en) across 11 files
- aria-label on all icon-only buttons (Chat, Explorer, Sidebar)
- focus-visible CSS for keyboard navigation
- Responsive grid fix (360px -> 280px for mobile)
- aria-hidden on decorative SVGs
Deployment:
- Docker resource limits (backend 4G, frontend 512M)
- Log rotation (json-file, 10MB x 3)
- Terraform version parameterized via ARG
This commit is contained in:
@@ -536,45 +536,59 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
|
||||
|
||||
export default function ReportsPage() {
|
||||
const { t } = useI18n();
|
||||
const ociCfg = useAppStore((s) => s.ociCfg);
|
||||
const ociRegions = useAppStore((s) => s.ociRegions);
|
||||
const adbCfg = useAppStore((s) => s.adbCfg);
|
||||
const store = useAppStore();
|
||||
const ociCfg = store.ociCfg;
|
||||
const ociRegions = store.ociRegions;
|
||||
const adbCfg = store.adbCfg;
|
||||
|
||||
/* ── Reports list ── */
|
||||
const [reports, setReports] = useState<Report[]>([]);
|
||||
const [reportsLoading, setReportsLoading] = useState(true);
|
||||
|
||||
/* ── Form state ── */
|
||||
const [formOpen, setFormOpen] = useState(true);
|
||||
const [ociVal, setOciVal] = useState('');
|
||||
const [level, setLevel] = useState<1 | 2>(2);
|
||||
const [selectedRegions, setSelectedRegions] = useState<string[]>([]);
|
||||
const [obp, setObp] = useState(true);
|
||||
const [raw, setRaw] = useState(false);
|
||||
const [redact, setRedact] = useState(true);
|
||||
/* ── Form state (from store) ── */
|
||||
const formOpen = store.rptFormOpen;
|
||||
const setFormOpen = store.setRptFormOpen;
|
||||
const ociVal = store.rptOciVal;
|
||||
const setOciVal = store.setRptOciVal;
|
||||
const level = store.rptLevel;
|
||||
const setLevel = store.setRptLevel;
|
||||
const selectedRegions = store.rptSelectedRegions;
|
||||
const setSelectedRegions = store.setRptSelectedRegions;
|
||||
const obp = store.rptObp;
|
||||
const setObp = store.setRptObp;
|
||||
const raw = store.rptRaw;
|
||||
const setRaw = store.setRptRaw;
|
||||
const redact = store.rptRedact;
|
||||
const setRedact = store.setRptRedact;
|
||||
|
||||
/* ── Tracking / progress ── */
|
||||
const [trackingId, setTrackingId] = useState<string | null>(null);
|
||||
/* ── Tracking / progress (from store) ── */
|
||||
const trackingId = store.rptTrackingId;
|
||||
const setTrackingId = store.setRptTrackingId;
|
||||
const [progress, setProgress] = useState<ReportProgress | null>(null);
|
||||
const [logExpanded, setLogExpanded] = useState(false);
|
||||
|
||||
/* ── Selected report + KPI ── */
|
||||
const [selectedRid, setSelectedRid] = useState('');
|
||||
/* ── Selected report + KPI (from store) ── */
|
||||
const selectedRid = store.rptSelectedRid;
|
||||
const setSelectedRid = store.setRptSelectedRid;
|
||||
const [kpi, setKpi] = useState<ReportSummary | null>(null);
|
||||
const [kpiLoading, setKpiLoading] = useState(false);
|
||||
|
||||
/* ── Report files ── */
|
||||
const [files, setFiles] = useState<ReportFile[]>([]);
|
||||
|
||||
/* ── Embedding state ── */
|
||||
const [embedAdb, setEmbedAdb] = useState('');
|
||||
const [embedTable, setEmbedTable] = useState('');
|
||||
/* ── Embedding state (from store) ── */
|
||||
const embedAdb = store.rptEmbedAdb;
|
||||
const setEmbedAdb = store.setRptEmbedAdb;
|
||||
const embedTable = store.rptEmbedTable;
|
||||
const setEmbedTable = store.setRptEmbedTable;
|
||||
const [embedLoading, setEmbedLoading] = useState(false);
|
||||
const [embedMsg, setEmbedMsg] = useState<{ type: 's' | 'e'; text: string } | null>(null);
|
||||
|
||||
/* ── HTML iframe ── */
|
||||
const [showIframe, setShowIframe] = useState(false);
|
||||
const [showCompliance, setShowCompliance] = useState(false);
|
||||
/* ── HTML iframe (from store) ── */
|
||||
const showIframe = store.rptShowIframe;
|
||||
const setShowIframe = store.setRptShowIframe;
|
||||
const showCompliance = store.rptShowCompliance;
|
||||
const setShowCompliance = store.setRptShowCompliance;
|
||||
const [complianceReady, setComplianceReady] = useState(false);
|
||||
const [complianceGenerating, setComplianceGenerating] = useState(false);
|
||||
|
||||
@@ -1305,16 +1319,16 @@ export default function ReportsPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
{showCompliance ? <Eye size={14} style={{ color: 'var(--gn)' }} /> : <EyeOff size={14} style={{ color: 'var(--t4)' }} />}
|
||||
<span className="text-[.76rem] font-semibold" style={{ color: 'var(--t2)' }}>
|
||||
LAD A-Team CIS Compliance Report
|
||||
{t('rpt.complianceTitle')}
|
||||
</span>
|
||||
{complianceReady && (
|
||||
<span className="text-[.6rem] px-2 py-0.5 rounded-full font-semibold" style={{ background: 'var(--gnl)', color: 'var(--gn)' }}>
|
||||
+ Remediation
|
||||
{t('rpt.complianceRemediation')}
|
||||
</span>
|
||||
)}
|
||||
{complianceGenerating && (
|
||||
<span className="text-[.6rem] px-2 py-0.5 rounded-full font-semibold flex items-center gap-1" style={{ background: 'var(--bll)', color: 'var(--bl)' }}>
|
||||
<Loader2 size={10} className="animate-spin" /> Gerando...
|
||||
<Loader2 size={10} className="animate-spin" /> {t('rpt.complianceGeneratingShort')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -1338,7 +1352,7 @@ export default function ReportsPage() {
|
||||
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
|
||||
<Download size={12} /> {t('rpt.complianceDownloadZip')}
|
||||
</a>
|
||||
<button
|
||||
onClick={startComplianceGeneration}
|
||||
@@ -1346,26 +1360,26 @@ export default function ReportsPage() {
|
||||
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)' }}
|
||||
>
|
||||
<RefreshCw size={12} /> Regenerar
|
||||
<RefreshCw size={12} /> {t('rpt.complianceRegenerate')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : complianceGenerating ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
<Loader2 size={32} className="animate-spin" style={{ color: 'var(--gn)' }} />
|
||||
<p className="text-[.82rem] font-medium" style={{ color: 'var(--t2)' }}>Gerando Compliance Report...</p>
|
||||
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>Buscando remediações na Knowledge Base CIS</p>
|
||||
<p className="text-[.82rem] font-medium" style={{ color: 'var(--t2)' }}>{t('rpt.complianceGenerating')}</p>
|
||||
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>{t('rpt.complianceFetchingKB')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-3">
|
||||
<ShieldCheck size={32} style={{ color: 'var(--t4)', opacity: 0.5 }} />
|
||||
<p className="text-[.78rem]" style={{ color: 'var(--t3)' }}>Report ainda não gerado</p>
|
||||
<p className="text-[.78rem]" style={{ color: 'var(--t3)' }}>{t('rpt.complianceNotGenerated')}</p>
|
||||
<button
|
||||
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' }}
|
||||
>
|
||||
<ShieldCheck size={14} /> Gerar Compliance Report
|
||||
<ShieldCheck size={14} /> {t('rpt.complianceGenerate')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user