diff --git a/src/app/plugins/[slug]/page.tsx b/src/app/plugins/[slug]/page.tsx index 8f8f345..98ad167 100644 --- a/src/app/plugins/[slug]/page.tsx +++ b/src/app/plugins/[slug]/page.tsx @@ -65,6 +65,9 @@ const DependencyGraph = dynamic(() => import("@/components/DependencyGraph"), { const PluginRatings = dynamic(() => import("@/components/PluginRatings"), { ssr: false, }); +const VirusTotalCard = dynamic(() => import("@/components/VirusTotalCard"), { + ssr: false, +}); async function getPlugin(slug: string) { const { data } = await fetchApi(`/api/v1/plugins/${slug}`, { @@ -1158,6 +1161,9 @@ export default async function PluginDetailPage({ {/* Dependency Graph */} + + {/* VirusTotal Scan */} + diff --git a/src/components/VirusTotalCard.tsx b/src/components/VirusTotalCard.tsx new file mode 100644 index 0000000..977d984 --- /dev/null +++ b/src/components/VirusTotalCard.tsx @@ -0,0 +1,224 @@ +"use client"; + +import { Shield, ExternalLink, Loader2, AlertTriangle } from "lucide-react"; + +interface VirusTotalCardProps { + version: { + virustotal?: { + status?: string | null; + malicious?: number | null; + suspicious?: number | null; + undetected?: number | null; + total?: number | null; + permalink?: string | null; + scanDate?: string | null; + } | null; + } | null; +} + +export default function VirusTotalCard({ version }: VirusTotalCardProps) { + if (!version?.virustotal) return null; + + const vt = version.virustotal; + const malicious = vt.malicious ?? 0; + const suspicious = vt.suspicious ?? 0; + + const statusColor = + vt.status === "completed" + ? malicious === 0 && suspicious === 0 + ? "var(--status-success)" + : malicious > 0 + ? "var(--status-error)" + : "var(--status-warning)" + : vt.status === "failed" + ? "var(--text-muted)" + : "var(--status-warning)"; + + const statusLabel = + vt.status === "completed" + ? malicious === 0 && suspicious === 0 + ? "Clean" + : malicious > 0 + ? "Flagged" + : "Suspicious" + : vt.status === "scanning" || vt.status === "queued" + ? "Scanning..." + : vt.status === "failed" + ? "Scan Failed" + : "Not Scanned"; + + const formattedDate = vt.scanDate + ? new Date(vt.scanDate).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }) + : null; + + return ( +
+
+

+ VirusTotal Scan +

+ {!vt.status && ( + + Pending + + )} + {(vt.status === "queued" || vt.status === "scanning") && ( + + )} +
+ + {vt.status === "completed" && ( +
+
+ Status + + {statusLabel} + +
+
+ Detection + + {malicious}/{vt.total ?? 0} engines flagged + +
+ {formattedDate && ( +
+ Scanned + {formattedDate} +
+ )} + {vt.permalink && ( + + View Full Report + + )} +
+ )} + + {(vt.status === "queued" || vt.status === "scanning") && ( +

+ + Scan in progress — this may take a few minutes. +

+ )} + + {vt.status === "failed" && ( +

+ + Scan could not be completed. It will be retried on the next + submission. +

+ )} + + {!vt.status && ( +

+ This version has not been scanned yet. +

+ )} +
+ ); +}