import { useNavigate } from 'react-router-dom'; import { useOverview, useOverviewTrends } from '../api/queries/overview'; import { usePinBaseline, useUnpinBaseline } from '../api/mutations/baseline'; import { StatCard } from '../components/ui/StatCard'; import { LoadingState } from '../components/ui/LoadingState'; import { ErrorState } from '../components/ui/ErrorState'; import { HorizontalBarChart } from '../components/charts/HorizontalBarChart'; import { LineChart } from '../components/charts/LineChart'; import { OverviewIcon } from '../components/icons/Icons'; import { truncPath } from '../utils/truncPath'; import { HealthScoreCard, BacklogCard, ConfidenceDistributionChart, ScannerQualityPanel, HotSinksList, OwaspChart, WeightedTopFiles, LanguageHealthTable, SuppressionHygieneCard, BaselinePinControl, } from '../components/overview/OverviewWidgets'; import type { OverviewCount, ScanSummary, Insight } from '../api/types'; import { usePageTitle } from '../hooks/usePageTitle'; export function OverviewPage() { usePageTitle('Overview'); const navigate = useNavigate(); const { data: overview, isLoading, error, refetch } = useOverview(); const { data: trends } = useOverviewTrends(); const pinBaseline = usePinBaseline(); const unpinBaseline = useUnpinBaseline(); if (isLoading) { return ; } if (error) { return ( refetch()} /> ); } if (!overview) { return ; } // Empty state if (overview.state === 'empty') { return (

Welcome to Nyx

Run your first scan to see security findings and analytics.

); } const netDelta = overview.new_since_last - overview.fixed_since_last; const categoryItems = (overview.issue_categories || []) .slice(0, 8) .map((b) => ({ label: b.label, value: b.count, color: 'var(--accent)' })); const trendData = (trends || []).map((t) => ({ label: t.timestamp, value: t.total, })); const hotSinks = overview.hot_sinks || []; return (
{/* Baseline strip */} pinBaseline.mutate(id)} onUnpin={() => unpinBaseline.mutate()} isPending={pinBaseline.isPending || unpinBaseline.isPending} /> {overview.health && ( )} {/* Fresh banner */} {overview.state === 'fresh' && (
Scan completed {overview.total_findings} finding {overview.total_findings === 1 ? '' : 's'} detected {overview.latest_scan_duration_secs != null ? ` in ${overview.latest_scan_duration_secs.toFixed(1)}s` : ''} . { e.preventDefault(); navigate('/findings'); }} > View all findings →
)} {/* Stat cards kept lean: 5 cards, severity stacks live in Top Files and Per-Language. Cross-file / Symex moved into Scanner Quality. */}
0 ? 'var(--sev-high)' : undefined} /> 0 ? 'var(--success)' : undefined} />
{/* Charts — 3-col: Findings (col1 span2) | OWASP+Confidence (col2) | Categories (col3 span2) */}
Findings Over Time
{trendData.length >= 2 ? ( ) : (

Run a second scan to see trends.

)}
Issue Categories
Confidence Distribution
{overview.confidence_distribution ? ( ) : (

No data

)}
OWASP Top 10 (2021)
{overview.owasp_buckets && overview.owasp_buckets.length > 0 ? ( ) : (

No OWASP-mapped findings.

)}
{/* Per-language + Top Files */}
Per-Language Posture
Top Affected Files (severity-weighted)
navigate(`/findings?search=${encodeURIComponent(name)}`) } />
{/* Top Rules + Top Directories (or Hot Sinks when taint findings exist) */}
Top Rules Triggered
Top Directories
{hotSinks.length > 0 && (
Hot Sinks (taint flow)
)} {overview.backlog && } {/* Scanner Quality + Hygiene */}
Scanner Quality
{overview.scanner_quality ? ( ) : (

No engine metrics available

)}
Suppression Hygiene
{overview.suppression_hygiene ? ( ) : (

No suppressions

)}
{/* Recent scans */}
Recent Scans
navigate(`/scans/${scan.id}`)} onPinBaseline={(scanId) => pinBaseline.mutate(scanId)} />
Insights
{overview.insights.length > 0 ? (
{overview.insights.map((insight, i) => ( ))}
) : (

Nothing to flag.

)}
); } // ── Sub-components ────────────────────────────────────────────────────────── interface CompactTableProps { items: OverviewCount[]; nameLabel: string; countLabel: string; truncate?: boolean; onRowClick?: (item: OverviewCount) => void; } function CompactTable({ items, nameLabel, countLabel, truncate, onRowClick, }: CompactTableProps) { if (!items || items.length === 0) { return (

No data

); } return ( {items.map((item) => { const displayName = truncate ? truncPath(item.name, 45) : item.name; return ( onRowClick(item) : undefined} title={item.name} > ); })}
{nameLabel} {countLabel}
{displayName} {item.count}
); } interface RecentScansTableProps { scans: ScanSummary[]; currentBaselineId?: string; onRowClick: (scan: ScanSummary) => void; onPinBaseline?: (scanId: string) => void; } function RecentScansTable({ scans, currentBaselineId, onRowClick, onPinBaseline, }: RecentScansTableProps) { if (!scans || scans.length === 0) { return (

No scans yet

); } return ( {scans.slice(0, 5).map((scan) => { const isBaseline = scan.id === currentBaselineId; const canPin = !isBaseline && onPinBaseline && scan.status === 'completed'; return ( onRowClick(scan)} > ); })}
Status Duration Findings Time
{scan.status} {scan.duration_secs != null ? `${scan.duration_secs.toFixed(1)}s` : '-'} {scan.finding_count ?? '-'} {scan.started_at ? new Date(scan.started_at).toLocaleString() : '-'} e.stopPropagation()}> {isBaseline ? ( baseline ) : canPin ? ( ) : null}
); } interface InsightCardProps { insight: Insight; } function InsightCard({ insight }: InsightCardProps) { const navigate = useNavigate(); return (
{insight.message} {insight.action_url && ( { e.preventDefault(); navigate(insight.action_url!); }} > View → )}
); }