import React from 'react';
import { useNavigate } from 'react-router-dom';
import type {
HealthScore,
PostureSummary,
BacklogStats,
ConfidenceDistribution,
ScannerQuality,
HotSink,
OwaspBucket,
LanguageHealth,
SuppressionHygiene,
BaselineInfo,
WeightedFile,
OverviewCount,
} from '../../api/types';
import { truncPath } from '../../utils/truncPath';
// ── HealthScoreCard ─────────────────────────────────────────────────────────
export function HealthScoreCard({
health,
posture,
}: {
health: HealthScore;
posture?: PostureSummary;
}) {
const gradeClass = `grade-${health.grade.toLowerCase()}`;
const gradeAccent =
health.grade === 'A' || health.grade === 'B'
? 'var(--green)'
: health.grade === 'C'
? 'var(--amber)'
: 'var(--red)';
return (
Health Score
{health.grade}
{health.score}
/ 100
{posture && (
{posture.message}
)}
{health.components.map((c) => {
const barColor =
c.score >= 70
? 'var(--green)'
: c.score >= 40
? 'var(--amber)'
: 'var(--red)';
return (
);
})}
);
}
// ── PostureBanner ──────────────────────────────────────────────────────────
export function PostureBanner({ posture }: { posture: PostureSummary }) {
return (
{posture.message}
);
}
// ── BacklogCard ────────────────────────────────────────────────────────────
export function BacklogCard({ backlog }: { backlog: BacklogStats }) {
const total = backlog.age_buckets.reduce((s, b) => s + b.count, 0);
const noHistory =
backlog.oldest_open_days == null && backlog.age_buckets.length === 0;
if (noHistory) {
return null;
}
return (
Backlog Age
{backlog.oldest_open_days != null
? `${backlog.oldest_open_days}d`
: '–'}
Oldest open
{backlog.median_age_days != null
? `${backlog.median_age_days}d`
: '–'}
Median age
{backlog.stale_count}
Older than 30 days
{total > 0 && (
)}
);
}
function BucketBar({ buckets }: { buckets: OverviewCount[] }) {
const total = buckets.reduce((s, b) => s + b.count, 0);
if (total === 0) return null;
const colors = [
'var(--accent)',
'var(--green)',
'var(--amber)',
'var(--red)',
'var(--muted)',
];
return (
`${b.name}: ${b.count}`).join(' · ')}
>
{buckets.map((b, i) => (
))}
);
}
// ── ConfidenceDistributionChart ────────────────────────────────────────────
export function ConfidenceDistributionChart({
dist,
}: {
dist: ConfidenceDistribution;
}) {
const total = dist.high + dist.medium + dist.low + dist.none;
if (total === 0) {
return (
);
}
const segments = [
{ label: 'High', value: dist.high, color: 'var(--green)' },
{ label: 'Medium', value: dist.medium, color: 'var(--amber)' },
{ label: 'Low', value: dist.low, color: 'var(--muted)' },
{ label: 'None', value: dist.none, color: 'var(--subtle)' },
];
return (
{segments.map((s) =>
s.value > 0 ? (
) : null,
)}
{segments.map((s) => (
{s.label}
{s.value}
))}
);
}
// ── ScannerQualityPanel ────────────────────────────────────────────────────
export function ScannerQualityPanel({
quality,
crossFileRatio,
}: {
quality: ScannerQuality;
crossFileRatio?: number;
}) {
const symexAttempted = Object.entries(quality.symex_breakdown || {})
.filter(([k]) => k !== 'not_attempted')
.reduce((s, [, v]) => s + v, 0);
const symexTotal = Object.values(quality.symex_breakdown || {}).reduce(
(s, v) => s + v,
0,
);
const totalFiles = quality.files_scanned + quality.files_skipped;
const filesValue = totalFiles.toLocaleString();
const filesDetail =
quality.files_skipped > 0
? `${quality.files_scanned.toLocaleString()} fresh · ${quality.files_skipped.toLocaleString()} from cache`
: quality.files_scanned > 0
? `${quality.files_scanned.toLocaleString()} freshly indexed`
: undefined;
const dynamic = quality.dynamic_verification ?? {
total: 0,
confirmed: 0,
partially_confirmed: 0,
not_confirmed: 0,
inconclusive: 0,
unsupported: 0,
};
const dynamicDetail =
dynamic.total > 0
? `${dynamic.total.toLocaleString()} verdicts · ${dynamic.partially_confirmed.toLocaleString()} partially confirmed · ${dynamic.not_confirmed.toLocaleString()} not confirmed · ${dynamic.inconclusive.toLocaleString()} inconclusive · ${dynamic.unsupported.toLocaleString()} unsupported`
: 'no dynamic verdicts in latest scan';
const rows: Array<{
label: string;
hint: string;
value: string;
detail?: string;
}> = [
{
label: 'Files',
hint: 'Files the scanner saw on this run.',
value: filesValue,
detail: filesDetail,
},
{
label: 'Functions analyzed',
hint: 'Function bodies the call graph saw.',
value: quality.functions_analyzed.toLocaleString(),
},
{
label: 'Call edges resolved',
hint: 'Share of call sites that the scanner resolved to a known callee. The remainder are typically external/library calls.',
value: `${(quality.call_resolution_rate * 100).toFixed(1)}%`,
detail:
quality.unresolved_calls > 0
? `${quality.unresolved_calls.toLocaleString()} unresolved`
: undefined,
},
{
label: 'Cross-file flows',
hint: 'Findings whose taint path crosses a file boundary.',
value:
crossFileRatio != null ? `${(crossFileRatio * 100).toFixed(1)}%` : '0%',
detail: 'of findings',
},
{
label: 'Symbolic verification',
hint: 'Taint findings the symbolic engine attempted to verify (confirmed, infeasible, or inconclusive).',
value:
symexTotal > 0
? `${(quality.symex_verified_rate * 100).toFixed(1)}%`
: 'n/a',
detail:
symexTotal > 0
? `${symexAttempted} of ${symexTotal} taint findings`
: 'no taint findings',
},
{
label: 'Dynamic verification',
hint: 'Findings re-run in generated harnesses against the dynamic payload corpus.',
value:
dynamic.total > 0
? `${dynamic.confirmed.toLocaleString()} confirmed`
: 'not run',
detail: dynamicDetail,
},
];
return (
{rows.map((r) => (
-
{r.label}
-
{r.value}
{r.detail && {r.detail}
}
))}
);
}
// ── HotSinksList ───────────────────────────────────────────────────────────
export function HotSinksList({ sinks }: { sinks: HotSink[] }) {
if (!sinks.length) {
return (
);
}
return (
| Sink |
Findings |
{sinks.map((s) => (
| {s.callee} |
{s.count} |
))}
);
}
// ── OwaspChart ─────────────────────────────────────────────────────────────
export function OwaspChart({ buckets }: { buckets: OwaspBucket[] }) {
if (!buckets.length) {
return (
);
}
const max = Math.max(...buckets.map((b) => b.count), 1);
return (
{buckets.map((b) => (
-
{b.code}
{b.label}
{b.count}
))}
);
}
// ── WeightedTopFiles ───────────────────────────────────────────────────────
export function WeightedTopFiles({
files,
onRowClick,
}: {
files: WeightedFile[];
onRowClick?: (name: string) => void;
}) {
if (!files.length) {
return (
);
}
return (
| File |
Severity |
Score |
{files.map((f) => (
onRowClick(f.name) : undefined}
>
| {truncPath(f.name, 45)} |
|
{f.score} |
))}
);
}
function SeverityStack({
high,
medium,
low,
}: {
high: number;
medium: number;
low: number;
}) {
const total = high + medium + low;
if (total === 0) return null;
return (
{high > 0 && (
{high}
)}
{medium > 0 && (
{medium}
)}
{low > 0 && (
{low}
)}
);
}
// ── LanguageHealthTable ────────────────────────────────────────────────────
export function LanguageHealthTable({ rows }: { rows: LanguageHealth[] }) {
if (!rows.length) {
return (
);
}
return (
| Language |
Findings |
Severity |
{rows.map((r) => (
| {r.language} |
{r.findings} |
|
))}
);
}
// ── SuppressionHygieneCard ─────────────────────────────────────────────────
export function SuppressionHygieneCard({
hygiene,
}: {
hygiene: SuppressionHygiene;
}) {
const total =
hygiene.fingerprint_level +
hygiene.rule_level +
hygiene.file_level +
hygiene.rule_in_file_level;
const blanket =
hygiene.rule_level + hygiene.file_level + hygiene.rule_in_file_level;
const blanketDisplay =
total > 0 ? `${(hygiene.blanket_rate * 100).toFixed(0)}%` : 'n/a';
const blanketDetail =
total > 0
? `${blanket} of ${total} suppressions are not pinned to a specific finding`
: 'No suppressions yet';
return (
-
Blanket rate
Lower is better
-
{blanketDisplay}
{blanketDetail}
-
By fingerprint
Most specific
-
{hygiene.fingerprint_level}
-
By rule in a file
-
{hygiene.rule_in_file_level}
-
By rule
-
{hygiene.rule_level}
-
By file
Least specific
-
{hygiene.file_level}
);
}
// ── BaselinePinControl ─────────────────────────────────────────────────────
interface BaselinePinControlProps {
baseline?: BaselineInfo;
latestScanId?: string;
onPin: (scanId: string) => void;
onUnpin: () => void;
isPending: boolean;
}
export function BaselinePinControl({
baseline,
latestScanId,
onPin,
onUnpin,
isPending,
}: BaselinePinControlProps) {
const navigate = useNavigate();
if (baseline) {
const net = baseline.drift_new - baseline.drift_fixed;
const driftClass =
net > 0
? 'baseline-drift-bad'
: net < 0
? 'baseline-drift-good'
: 'baseline-drift-flat';
return (
Baseline:
drift: +{baseline.drift_new} new / -{baseline.drift_fixed} fixed (
{net >= 0 ? '+' : ''}
{net})
);
}
if (!latestScanId) return null;
return (
No baseline pinned.
);
}