Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

.env

# vercel
.vercel

.env
# typescript
*.tsbuildinfo
next-env.d.ts


/public/robots.txt
/public/sitemap.xml
/public/sitemap-0.xml
image.tar
# Sentry Config File
.env.sentry-build-plugin
217 changes: 213 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"dotenv": "^16.5.0",
"eslint-config-prettier": "^9.1.2",
"framer-motion": "^12.17.3",
Expand All @@ -62,12 +64,14 @@
"react-hook-form": "^7.57.0",
"react-markdown": "^9.0.3",
"react-use-measure": "^2.1.7",
"recharts": "^3.7.0",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'recharts' package is added as a dependency but the only imports from it (Label, Pie, PieChart) are unused in the code. If these components are not needed, consider removing the 'recharts' dependency to reduce bundle size. If they will be needed in future implementations, add a TODO comment explaining the planned usage.

Suggested change
"recharts": "^3.7.0",

Copilot uses AI. Check for mistakes.
"remark-gemoji": "^8.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx-disable-explicit-jsx": "^0.1.0",
"sass": "^1.89.2",
"sharp": "^0.34.5",
"swagger-ui-react": "^5.31.0",
"swr": "^2.4.0",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"three": "^0.181.2",
Expand Down
55 changes: 55 additions & 0 deletions src/components/reachabilityAnalysis/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ColumnDef } from '@tanstack/react-table'
import { ArrowUpDown } from 'lucide-react'
import Link from 'next/link'

export type Package = {
purl: string
type: string
name: string
}

export const columns: ColumnDef<Package>[] = [
{
accessorKey: 'purl',
header: 'Package URL',
size: 150,
cell: ({ row }) => {
const purl = row.getValue('purl') as string
return (
<Link href={`/reachability-analysis/${encodeURIComponent(purl)}`}>
<div className="w-32">{purl}</div>
</Link>
)
},
},
{
accessorKey: 'type',
header: 'Package Type',
size: 600,
cell: ({ row }) => {
const type = row.getValue('type') as string
return <div className="w-108">{type}</div>
},
},
{
accessorKey: 'name',
size: 150,
header: ({ column }) => {
return (
<div
className="flex cursor-pointer items-center hover:text-accent-foreground"
onClick={() =>
column.toggleSorting(column.getIsSorted() === 'asc')
}
>
Package Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</div>
)
},
cell: ({ row }) => {
const name = row.getValue('name') as string
return <div className="w-32">{name}</div>
},
},
]
43 changes: 43 additions & 0 deletions src/components/reachabilityAnalysis/reachability-analysis-hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Input } from 'src/components/ui/input'


interface HeroProps {
searchTerm: string
setSearchTerm: (value: string) => void
onSearch: () => void
}

export default function ReachabilityAnalysisHero({searchTerm,setSearchTerm,onSearch}: HeroProps){
return (
<>
<div className="mx-auto max-w-7xl ml-16 pb-40 pt-32">
<div className="px-6">
<div className="mx-auto max-w-2xl">
<div className="max-w-lg">
<h1 className="mt-10 text-pretty text-5xl text-center font-semibold tracking-tight text-white sm:text-7xl">
Reachability Analysis
</h1>
<p className="mt-4 text-pretty text-center text-lg font-large text-gray-400 sm:text-xl/8">
Lorem Ipsum
</p>
<form
onSubmit={(e) => {
e.preventDefault()
onSearch()
}}
>
<Input
className="mt-8 !text-2xl py-6 text-center rounded-full"
value={searchTerm}
onChange={(e) =>
setSearchTerm(e.target.value)
}
/>
</form>
</div>
</div>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { useState } from 'react'
import { Container } from '../top-level-pages/container'
import { ReachabilityAnalysisResponse } from 'src/components/reachabilityAnalysis/reachability-types'
import { Skeleton } from '@/components/ui/skeleton'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import { ExternalLink } from 'lucide-react'
import { fetcher } from 'src/lib/fetcher'
import useSWR from 'swr'



export default function ReachabilityAnalysisPackageDetails({ purl }: { purl?: string }) {
const { data, error, isLoading } = useSWR<ReachabilityAnalysisResponse>(
purl ? `http://localhost:8080/api/v1/vulndb/reachability/${purl}` : null,
fetcher,
)

const [isVulnerabilitiesOpen, setIsVulnerabilitiesOpen] = useState(false)
const [isComponentsOpen, setIsComponentsOpen] = useState(false)

if (isLoading) {
return (
<Container>
<div className="mb-6 grid gap-4 lg:grid-cols-3">
<div className="relative lg:col-span-2">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] p-8">
<div className="flex items-start justify-between">
<div className="space-y-3">
<Skeleton className="h-9 w-96" />
</div>
</div>
</div>
</div>
</div>

<div className="relative mb-6">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] p-7">
<Skeleton className="h-6 w-40" />
</div>
</div>

<div className="relative mb-4">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] p-7">
<Skeleton className="h-6 w-48"/>
</div>
</div>
</Container>
)
}

if (error || !data) {
return <div>No Package data found</div>
}

return (
<Container>
<div className="mb-6 grid gap-4 lg:grid-cols-3 w-3/4">
<div className="relative lg:col-span-2">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)] p-8">
<div className="flex items-start justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-950 dark:text-white">
{purl}
</h1>
</div>
</div>
</div>
</div>
</div>
<div className="relative mt-6">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)]">
<button
onClick={() => setIsComponentsOpen(!isComponentsOpen)}
className="flex w-full items-center justify-between p-6 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-700/50"
>
<h2 className="text-lg font-medium text-gray-950 dark:text-white">
Components (
{data.components.length})
</h2>
<svg
className={`h-5 w-5 transition-transform ${isComponentsOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
{isComponentsOpen && (
<div className="px-8 pb-8">
<div>
{data.components.map((comp) => (
<div className='flex'>
<p className='px-4 mx-8 my-4 py-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50'>{comp.name}</p>
<p className='px-4 mx-8 my-4 py-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50'>{comp.type}</p>
</div>
))}
</div>
</div>
)}
</div>
<div className="pointer-events-none absolute inset-px rounded-lg shadow outline outline-1 outline-black/5 dark:outline-white/15" />
</div>
<div className="relative mt-6">
<div className="absolute inset-px rounded-lg bg-white dark:bg-gray-800" />
<div className="relative flex flex-col overflow-hidden rounded-[calc(theme(borderRadius.lg)+1px)]">
<button
onClick={() => setIsVulnerabilitiesOpen(!isVulnerabilitiesOpen)}
className="flex w-full items-center justify-between p-6 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-700/50"
>
<h2 className="text-lg font-medium text-gray-950 dark:text-white">
Vulnerabilities (
{data.vulnerabilities.length})
</h2>
<svg
className={`h-5 w-5 transition-transform ${isVulnerabilitiesOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
{isVulnerabilitiesOpen && (
<div className="px-8 pb-8">
<div>
{data.vulnerabilities.map((vuln) => (
<div className='flex'>
<p className='px-4 mx-8 my-4 py-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50'>{vuln.id}</p>
<p className='px-4 mx-8 my-4 py-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50'>{vuln['bom-ref']}</p>
</div>
))}
</div>
</div>
)}
</div>
<div className="pointer-events-none absolute inset-px rounded-lg shadow outline outline-1 outline-black/5 dark:outline-white/15" />
</div>
<div className="mt-8 flex gap-3">
<div>
<Button>
<Link href="/reachability-analysis/">Get Back</Link>
</Button>
</div>
<div>
<Button>
<a
target="_blank"
href="https://main.devguard.org/"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
Try DevGuard
<ExternalLink size={16} />
</a>
</Button>
</div>
</div>
</Container>
)
}
Loading