- Add scans panel with finding details, verdict badges, and filters - Add corpus panel for managing knowledge sources - Add scan cache for API state management - Update sidebar navigation with new routes - Extend API types for scans and corpus endpoints - Add .aphoria/ to gitignore (contains project keys) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
168 lines
6.8 KiB
TypeScript
168 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import type { FindingDto } from "@/lib/api";
|
|
import { VerdictBadge } from "./verdict-badge";
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetDescription,
|
|
} from "@/components/ui/sheet";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { FileCode, AlertTriangle, BookOpen, Shield } from "lucide-react";
|
|
|
|
interface FindingDetailSheetProps {
|
|
finding: FindingDto | null;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function FindingDetailSheet({ finding, isOpen, onClose }: FindingDetailSheetProps) {
|
|
if (!finding) return null;
|
|
|
|
return (
|
|
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
|
<SheetContent className="overflow-y-auto">
|
|
<SheetHeader className="pb-4">
|
|
<div className="flex items-center gap-2">
|
|
<VerdictBadge verdict={finding.verdict} />
|
|
</div>
|
|
<SheetTitle className="text-lg font-semibold break-words">
|
|
{finding.concept_path}
|
|
</SheetTitle>
|
|
<SheetDescription className="text-sm">
|
|
<span className="font-mono">.{finding.predicate}</span>
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<div className="space-y-6">
|
|
{/* Code Location */}
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
|
|
<FileCode className="h-4 w-4" />
|
|
Source Location
|
|
</h3>
|
|
<div className="rounded-md bg-muted p-3 space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">File:</span>
|
|
<code className="font-mono text-foreground">{finding.file}</code>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">Line:</span>
|
|
<span className="font-mono text-foreground">{finding.line}</span>
|
|
</div>
|
|
<Separator className="my-2" />
|
|
<div>
|
|
<span className="text-sm text-muted-foreground block mb-1">Value in code:</span>
|
|
<code className="block bg-background p-2 rounded font-mono text-sm break-all">
|
|
{finding.code_value}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Conflict Details */}
|
|
{finding.conflicts.length > 0 && (
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
|
|
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
|
Conflicting Sources ({finding.conflicts.length})
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{finding.conflicts.map((conflict, index) => (
|
|
<div
|
|
key={index}
|
|
className="rounded-md border border-border bg-card p-3 space-y-2"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<Badge variant="outline" className="text-xs">
|
|
{conflict.source_class}
|
|
</Badge>
|
|
{conflict.policy_source && (
|
|
<span className="text-xs text-muted-foreground">
|
|
via {conflict.policy_source.pack_name} v{conflict.policy_source.pack_version}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="text-sm">
|
|
<span className="text-muted-foreground">Expected: </span>
|
|
<code className="font-mono bg-muted px-1 rounded">{conflict.value}</code>
|
|
</div>
|
|
{conflict.citation && (
|
|
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
|
<BookOpen className="h-3 w-3 mt-0.5 flex-shrink-0" />
|
|
<span>{conflict.citation}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Acknowledgment */}
|
|
{finding.acknowledgment && (
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-sm font-medium text-foreground mb-3">
|
|
<Shield className="h-4 w-4 text-blue-500" />
|
|
Acknowledged
|
|
</h3>
|
|
<div className="rounded-md bg-blue-500/10 border border-blue-500/20 p-3 space-y-2">
|
|
<div className="text-sm">
|
|
<span className="text-muted-foreground">By: </span>
|
|
<span className="text-foreground">{finding.acknowledgment.by}</span>
|
|
</div>
|
|
<div className="text-sm">
|
|
<span className="text-muted-foreground">Reason: </span>
|
|
<span className="text-foreground">{finding.acknowledgment.reason}</span>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{finding.acknowledgment.timestamp}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Debug Trace */}
|
|
{finding.trace && (
|
|
<section>
|
|
<h3 className="text-sm font-medium text-foreground mb-3">
|
|
Resolution Trace
|
|
</h3>
|
|
<div className="rounded-md bg-muted p-3 space-y-2 text-xs font-mono">
|
|
<div>
|
|
<span className="text-muted-foreground">Code claim: </span>
|
|
<span className="text-foreground">{finding.trace.code_claim}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Authority match: </span>
|
|
<span className="text-foreground">{finding.trace.authority_match}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Authority tier: </span>
|
|
<span className="text-foreground">{finding.trace.authority_tier}</span>
|
|
</div>
|
|
<Separator className="my-2" />
|
|
<div>
|
|
<span className="text-muted-foreground">Resolution: </span>
|
|
<span className="text-foreground">{finding.trace.resolution}</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Conflict Score */}
|
|
<section>
|
|
<div className="flex justify-between items-center text-sm">
|
|
<span className="text-muted-foreground">Conflict Score</span>
|
|
<span className="font-mono">{(finding.conflict_score * 100).toFixed(1)}%</span>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|