"use client"; import React, { useMemo, useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Code2, Search, AlertTriangle, FileCode2, CheckCircle2, AlertOctagon, AlertCircle, Info, ChevronRight, ChevronDown, GitBranch, ArrowLeft, Sparkles, FileText, Layers, } from "lucide-react"; import { Card, CardBody, CardHeader, CardTitle } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { Badge, ArtifactTypeBadge } from "@/components/ui/Badge"; import { EmptyState } from "@/components/ui/EmptyState"; import { artifacts as apiArtifacts, projects } from "@/lib/api"; import { cn, fmtDate, shortId, truncate } from "@/lib/utils"; import type { Artifact, ArtifactType, Project } from "@/lib/types"; type Scope = "all" | "source_file" | "smell"; type Severity = "all" | "low" | "medium" | "high" | "critical"; const SEVERITY_ORDER: Record = { critical: 0, high: 1, medium: 2, low: 3 }; const SEVERITY_META: Record, { icon: typeof AlertTriangle; tone: "red" | "amber" | "brand" | "neutral"; label: string }> = { critical: { icon: AlertOctagon, tone: "red", label: "CRITICAL" }, high: { icon: AlertTriangle, tone: "amber", label: "HIGH" }, medium: { icon: AlertCircle, tone: "brand", label: "MEDIUM" }, low: { icon: Info, tone: "neutral", label: "LOW" }, }; const DEMO_ROWS: Artifact[] = [ { id: "src-1", tenant_id: "t1", project_id: "p1", type: "source_file", external_id: "SRC-DEMO-1", title: "app/middleware/auth.py", status: "active", source: "git", content: "def authorize(request):\n return check_jwt(request.headers.get('authorization'))\n", metadata: { language: "python", path: "app/middleware/auth.py", loc: 134, repo: "asf", branch: "main" }, created_at: "2025-11-01T09:00:00Z", updated_at: "2025-11-05T11:00:00Z", }, { id: "sm-1", tenant_id: "t1", project_id: "p1", type: "ai_analysis", external_id: "CS-DEMO-1", title: "SQL injection in user lookup (no ORM)", status: "open", source: "bandit", content: "Line 22 uses f-string on cursor.execute().", metadata: { kind: "code_smell", language: "python", severity: "high", rule_id: "B608", engine: "bandit", lines: [20, 21, 22] }, created_at: "2025-11-08T09:00:00Z", updated_at: "2025-11-08T09:00:00Z", }, ]; function isSmell(a: Artifact): boolean { const md = a.metadata ?? ({} as Record); return typeof md === "object" && (md as Record)["kind"] === "code_smell"; } function isSourceFile(a: Artifact): boolean { return a.type === "source_file"; } function getLanguage(a: Artifact): string { const md = a.metadata ?? ({} as Record); const v = (md as Record)["language"]; return typeof v === "string" ? v : "—"; } function getSeverity(a: Artifact): "low" | "medium" | "high" | "critical" | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["severity"]; if (typeof v === "string" && (v === "low" || v === "medium" || v === "high" || v === "critical")) return v; return null; } function getRule(a: Artifact): string | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["rule_id"]; return typeof v === "string" ? v : null; } function getEngine(a: Artifact): string | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["engine"]; return typeof v === "string" ? v : null; } function getPath(a: Artifact): string | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["path"]; return typeof v === "string" ? v : null; } function getLines(a: Artifact): number[] | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["lines"]; return Array.isArray(v) && v.every((x) => typeof x === "number") ? (v as number[]) : null; } function getLOC(a: Artifact): number | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["loc"]; return typeof v === "number" ? v : null; } function getRepo(a: Artifact): string | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["repo"]; return typeof v === "string" ? v : null; } function getBranch(a: Artifact): string | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["branch"]; return typeof v === "string" ? v : null; } function getConfidence(a: Artifact): number | null { const md = a.metadata ?? ({} as Record); const v = (md as Record)["confidence_pct"]; return typeof v === "number" ? v : null; } export default function CodePage() { const [scope, setScope] = useState("all"); const [projectFilter, setProjectFilter] = useState("all"); const [languageFilter, setLanguageFilter] = useState("all"); const [severityFilter, setSeverityFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); const [q, setQ] = useState(""); const [detailId, setDetailId] = useState(""); const projectsQ = useQuery({ queryKey: ["code-projects"], queryFn: () => projects.list({ limit: 50 }), retry: false, refetchOnWindowFocus: false, }); const apiResolved = projectsQ.isFetched && projectsQ.data !== undefined; const projectRows: Project[] = apiResolved ? projectsQ.data! : []; const projectNameById = useMemo(() => { const m = new Map(); for (const p of projectRows) m.set(String(p.id), p.name); return m; }, [projectRows]); const sourceQ = useQuery({ queryKey: ["code-source", projectFilter, q.trim()], queryFn: () => apiArtifacts.list({ type: "source_file" as ArtifactType, project_id: projectFilter !== "all" ? projectFilter : undefined, search: q.trim() || undefined, limit: 200, }), retry: false, refetchOnWindowFocus: false, }); const smellsQ = useQuery({ queryKey: ["code-smells", projectFilter, q.trim()], queryFn: () => apiArtifacts.list({ type: "ai_analysis" as ArtifactType, project_id: projectFilter !== "all" ? projectFilter : undefined, search: q.trim() || undefined, limit: 200, }), retry: false, refetchOnWindowFocus: false, }); const sourceResolved = sourceQ.isFetched && sourceQ.data !== undefined; const smellsResolved = smellsQ.isFetched && smellsQ.data !== undefined; const isMock = !sourceResolved || !smellsResolved; const rawRows: Artifact[] = useMemo(() => { const allSources = sourceResolved ? sourceQ.data! : DEMO_ROWS.filter(isSourceFile); const allAnalyses = smellsResolved ? smellsQ.data! : DEMO_ROWS.filter(isSmell); const smellsOnly = allAnalyses.filter(isSmell); const combined = [...allSources, ...smellsOnly]; // apply language + severity + status filters (server can't filter metadata) return combined.filter((a) => { if (scope === "source_file" && !isSourceFile(a)) return false; if (scope === "smell" && !isSmell(a)) return false; if (languageFilter !== "all" && getLanguage(a) !== languageFilter) return false; if (severityFilter !== "all") { if (!isSmell(a)) return false; if (getSeverity(a) !== severityFilter) return false; } if (statusFilter !== "all" && (a.status ?? "") !== statusFilter) return false; return true; }).sort((b, a) => { // severity first (only if smell), then updated_at desc const sa = isSmell(a) ? (SEVERITY_ORDER[getSeverity(a) ?? "low"] ?? 9) : 99; const sb = isSmell(b) ? (SEVERITY_ORDER[getSeverity(b) ?? "low"] ?? 9) : 99; if (sa !== sb) return sa - sb; return (a.updated_at ?? "").localeCompare(b.updated_at ?? ""); }); }, [sourceResolved, smellsResolved, sourceQ.data, smellsQ.data, scope, languageFilter, severityFilter, statusFilter]); const languages = useMemo(() => { const s = new Set(); for (const r of rawRows) { const l = getLanguage(r); if (l && l !== "—") s.add(l); } return Array.from(s).sort(); }, [rawRows]); const qc = useQueryClient(); const markFixedM = useMutation({ mutationFn: async ({ id, status }: { id: string; status: string }) => apiArtifacts.update(id, { status } as Partial), onSuccess: () => { qc.invalidateQueries({ queryKey: ["code-source"] }); qc.invalidateQueries({ queryKey: ["code-smells"] }); }, }); const stats = useMemo(() => { const src = rawRows.filter(isSourceFile).length; const sm = rawRows.filter(isSmell); const smBySev = { low: 0, medium: 0, high: 0, critical: 0 } as Record<"low" | "medium" | "high" | "critical", number>; for (const s of sm) { const sv = getSeverity(s); if (sv) smBySev[sv] += 1; } return { src, smells: sm.length, smBySev }; }, [rawRows]); const effectiveDetail = useMemo(() => { if (!detailId) return null; return rawRows.find((r) => String(r.id) === String(detailId)) ?? null; }, [detailId, rawRows]); if (effectiveDetail) { return ( setDetailId("")} onMarkFixed={(status) => markFixedM.mutate({ id: String(effectiveDetail.id), status }) } markFixedPending={markFixedM.isPending} /> ); } return (

Code Analysis {isMock && Demo data}

Source files indexed from your repositories + automated code smells detected by ASF AI Analyzer (Ruff / ESLint / Bandit …). Click any row to see the detail with highlighted lines.

Inventory {rawRows.length}
setQ(e.target.value)} />
{rawRows.length === 0 ? (
) : (
{rawRows.map((a) => ( setDetailId(String(a.id))} className="group cursor-pointer hover:bg-slate-50/80 transition-colors" > ))}
Kind Artifact Lang Severity Status Project Updated
{isSourceFile(a) ? ( file ) : ( smell )}
{a.external_id ?? shortId(a.id)} {isSmell(a) && getRule(a) && ( {getRule(a)} )} {isSourceFile(a) && getLOC(a) != null && ( {getLOC(a)} LOC )}
{a.title}
{getPath(a) && (
{getPath(a)}
)} {isSmell(a) && getEngine(a) && (
scanner: {getEngine(a)}
)}
{getLanguage(a)} {isSmell(a) && getSeverity(a) ? ( ) : ( — )} {a.status ? {a.status} : —} {projectNameById.get(String(a.project_id)) ?? shortId(a.project_id)} {fmtDate(a.updated_at)}
)}
); } function SeverityBadge({ s }: { s: "low" | "medium" | "high" | "critical" }) { const m = SEVERITY_META[s]; const Icon = m.icon; return ( {m.label} ); } function StatCard({ label, value, tone, icon: Icon, }: { label: string; value: number; tone: "brand" | "amber" | "red" | "neutral" | "green" | "blue"; icon: React.ComponentType<{ className?: string }>; }) { const toneBg: Record = { brand: "from-brand-500 to-brand-600", amber: "from-amber-400 to-amber-500", red: "from-rose-500 to-red-600", neutral: "from-slate-400 to-slate-500", green: "from-emerald-400 to-emerald-600", blue: "from-sky-400 to-blue-600", }; return (
{label}
{value}
); } function CodeDetailView({ artifact, projectName, onBack, onMarkFixed, markFixedPending, }: { artifact: Artifact; projectName: string; onBack: () => void; onMarkFixed?: (status: string) => void; markFixedPending?: boolean; }) { const lang = getLanguage(artifact); const sev = getSeverity(artifact); const path = getPath(artifact); const rule = getRule(artifact); const engine = getEngine(artifact); const repo = getRepo(artifact); const branch = getBranch(artifact); const lines = getLines(artifact); const confidence = getConfidence(artifact); const smell = isSmell(artifact); const source = isSourceFile(artifact); const linesToHighlight = useMemo(() => { const set = new Set(); if (lines) { for (const n of lines) set.add(n); } return set; }, [lines]); const contentLines = useMemo(() => { const raw = artifact.content ?? ""; return raw.split("\n"); }, [artifact.content]); return (
{smell ? : }
{smell ? ( code_smell ) : ( source_file )} {sev && } {rule && rule {rule}} {lang && {lang}} {artifact.status && ( {artifact.status} )} {artifact.external_id ?? shortId(artifact.id)}

{artifact.title}

{path && (
{path}
)}
Project: {projectName} {engine && Scanner: {engine}} {repo && ( {truncate(repo, 40)}{branch ? ` @ ${branch}` : ""} )} {confidence != null && Confidence: {confidence}%} Updated: {fmtDate(artifact.updated_at)} {lines && Affected lines: {lines.join(", ")}} {source && getLOC(artifact) != null && Lines of code: {getLOC(artifact)!}}
{smell && onMarkFixed && (
{artifact.status === "fixed" ? ( Resolved ) : ( )}
)}
{smell ? "Finding details" : "Source preview"} {contentLines.length === 1 && contentLines[0] === "" ? (
) : (
                {contentLines.map((ln, i) => {
                  const n = i + 1;
                  const hi = linesToHighlight.has(n);
                  return (
                    
{n} {ln || " "}
); })}
)}
{smell ? : } {smell ? "Remediation guidance" : "File index info"} {smell ? ( <> 1 ? "s" : ""}.` : "Inspect the highlighted rows in the source pane.") } tone="amber" />
) : ( <>
)}
); } function SuggestionBlock({ title, body, tone, }: { title: string; body: string; tone: "brand" | "amber" | "red" | "green" | "blue" }) { const toneCls: Record = { brand: "bg-brand-50 border-brand-100 text-brand-900", amber: "bg-amber-50 border-amber-100 text-amber-900", red: "bg-rose-50 border-rose-100 text-rose-900", green: "bg-emerald-50 border-emerald-100 text-emerald-900", blue: "bg-sky-50 border-sky-100 text-sky-900", }; return (
{title}

{body}

); } function InfoCell({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); }