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
26 changes: 26 additions & 0 deletions src/components/package-inspector/Hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Container } from '@/components/top-level-pages/container'
import { Shield } from 'lucide-react'

export function PackageInspectorHero() {
return (
<div className="relative">
<div className="absolute inset-14 bottom-10 rounded-xl bg-gradient-to-r from-gray-950 to-black ring-1 ring-inset ring-white/10" />
<Container className="relative">
<div className="px-0 pb-12 pt-16 text-center max-md:pl-2 sm:pt-20 md:px-12 md:pb-16 md:pt-24 2xl:px-0">
<div className="mb-6 inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm text-white/80">
<Shield className="h-4 w-4" />
Powered by DevGuard
</div>
<h1 className="text-5xl font-semibold leading-tight tracking-tight text-white/90">
Package Inspector
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg text-white/70">
Analyze the security state of open-source packages. Get
insights on maintenance, vulnerabilities, and supply
chain risks.
</p>
</div>
</Container>
</div>
)
}
57 changes: 57 additions & 0 deletions src/components/package-inspector/PackageInspectorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from 'react'
import { Searchbar } from '@/components/ui/Searchbar'
import { SearchResultCard } from './SearchResultCard'
import { PackageInspectorHero } from './Hero'
import mockData from './mock-data.json'

export function PackageInspectorPage() {
const [searchQuery, setSearchQuery] = useState('')

const [isLoading, setIsLoading] = useState(false)
Copy link
Member

Choose a reason for hiding this comment

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

There is a lot of dependent state variables. Maybe we can use reacts useReducer hook to simplify? This is pretty nitpicking


const [results, setResults] = useState<any[]>([])

const [hasSearched, setHasSearched] = useState(false)

const handleSearch = async () => {
if (!searchQuery.trim()) return

setIsLoading(true)
setHasSearched(true)

// TODO: replace with real API call when backend is ready
// simulate delay to test search button working and return mock data
await new Promise((resolve) => setTimeout(resolve, 1000))
setResults([mockData])
setIsLoading(false)
}

return (
<>
<PackageInspectorHero />
<div className="pb-16">
<Searchbar
value={searchQuery}
onChange={setSearchQuery}
onSubmit={handleSearch}
isLoading={isLoading}
placeholder={'Search by package name or PURL'}
/>

{hasSearched && (
Copy link
Member

Choose a reason for hiding this comment

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

I think the hasSearched state can be removed. You can just check if results is empty or not

<div className="mx-auto mt-8 max-w-2xl">
{results.map((result) => (
<SearchResultCard
key={result.purl}
name={result.name}
description={result.project?.description}
version={result.version}
purl={result.purl}
/>
))}
</div>
)}
</div>
</>
)
}
43 changes: 43 additions & 0 deletions src/components/package-inspector/SearchResultCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import Link from 'next/link'
import { Package } from 'lucide-react'

interface SearchResultCardProps {
name: string
description?: string
version: string
purl: string
}

export function SearchResultCard({
name,
description,
version,
purl,
}: SearchResultCardProps) {
const encodedPurl = encodeURIComponent(purl)

return (
<Link
href={`/package-inspector/${encodedPurl}`}
className="flex items-center gap-4 rounded-lg border border-gray-200 bg-white p-4 transition-colors hover:border-gray-300 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-gray-600 dark:hover:bg-gray-700"
>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700">
<Package className="h-5 w-5 text-gray-600 dark:text-gray-300" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="font-medium text-gray-900 dark:text-white">
{name}
</span>
<span className="text-sm text-gray-500">v{version}</span>
</div>
{description && (
<p className="truncate text-sm text-gray-600 dark:text-gray-400">
{description}
</p>
)}
</div>
</Link>
)
}
102 changes: 102 additions & 0 deletions src/components/package-inspector/mock-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"purl": "pkg:npm/event-stream@3.3.6",
"ecosystem": "npm",
"name": "event-stream",
"version": "3.3.6",

"project": {
"projectKey": "github.com/dominictarr/event-stream",
"starsCount": 1400,
"forksCount": 220,
"openIssuesCount": 12,
"homepage": "https://github.com/dominictarr/event-stream",
"license": "MIT",
"description": "Toolkit to make creating and working with streams easy",

"scoreCard": {
"checks": [
{
"name": "Maintained",
"score": 2,
"reason": "No commit activity in the last 12 months"
},
{
"name": "Code-Review",
"score": 3,
"reason": "Most commits are directly pushed to main without review"
},
{
"name": "Signed-Releases",
"score": 0,
"reason": "No signed tags or releases detected"
},
{
"name": "Signed-Commits",
"score": 1,
"reason": "Only a small subset of commits are signed"
},
{
"name": "Branch-Protection",
"score": 0,
"reason": "Main branch allows force-pushes and direct commits"
},
{
"name": "CI-Tests",
"score": 4,
"reason": "Basic CI exists but coverage is minimal"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "No fuzzing configuration detected"
},
{
"name": "Dependency-Update-Tool",
"score": 2,
"reason": "Dependabot is configured but updates are often ignored"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "SECURITY.md not found"
},
{
"name": "Vulnerabilities",
"score": 3,
"reason": "Known historical vulnerabilities with no documented remediation"
},
{
"name": "Packaging",
"score": 1,
"reason": "Package includes postinstall scripts without sandboxing"
},
{
"name": "Binary-Artifacts",
"score": 0,
"reason": "Prebuilt binaries committed to the repository"
}
],
"metadata": {
"tool": "openssf-scorecard",
"toolVersion": "5.0.0",
"generatedAt": "2024-09-30T11:22:00Z"
}
},

"scoreCardScore": 2.8,
"updatedAt": "2024-11-01T14:00:00Z"
},

"maliciousPackage": {
"id": "MAL-2024-000123",
"summary": "Malicious package targeting CI environments",
"details": "The package executes a post-install script that exfiltrates environment variables and SSH keys.",
"published": "2024-10-12T08:15:30Z",
"modified": "2024-11-01T14:42:10Z",
"affectedComponent": {
"id": "mac-001",
"versionRange": "<=3.3.6",
"firstObserved": "2024-10-10T09:00:00Z"
}
}
}
50 changes: 50 additions & 0 deletions src/components/ui/Searchbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button } from './button'
import { Search } from 'lucide-react'

interface SearchbarProps {
value: string
onChange: (value: string) => void
onSubmit: () => void
isLoading?: boolean
placeholder?: string
// TODO: make button name customizable
}

export function Searchbar({
value,
onChange,
onSubmit,
isLoading = false,
placeholder,
}: SearchbarProps) {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
onSubmit()
}

return (
<div className="relative left-1/2 right-1/2 -mx-[50vw] w-screen px-4">
<div className="mx-auto max-w-2xl">
<form onSubmit={handleSubmit}>
<div className="relative">
<Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" />
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-full rounded-xl border border-gray-300 bg-white py-4 pl-12 pr-32 text-gray-900 placeholder-gray-500 focus:border-primary focus:outline-none focus:ring-primary dark:border-gray-800 dark:bg-gray-950 dark:text-white dark:focus:border-primary"
placeholder={placeholder}
/>
<Button
type="submit"
className="absolute right-2 top-1/2 -translate-y-1/2"
disabled={isLoading}
>
{isLoading ? 'Searching...' : 'Inspect'}
</Button>
</div>
</form>
</div>
</div>
)
}
40 changes: 8 additions & 32 deletions src/pages/_meta.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,22 @@
export default {
index: {
theme: {
layout: 'raw',
},
display: 'hidden',
},
pricing: {
theme: {
layout: 'raw',
},
display: 'hidden',
},
'terms-of-use': {
display: 'hidden',
},
'privacy-policy': {
display: 'hidden',
},
index: { theme: { layout: 'raw' }, display: 'hidden' },
pricing: { theme: { layout: 'raw' }, display: 'hidden' },
'package-inspector': { theme: { layout: 'raw' }, display: 'hidden' },
'terms-of-use': { display: 'hidden' },
'privacy-policy': { display: 'hidden' },
introduction: { title: 'Introduction' },
'getting-started': { title: 'Getting Started' },
'how-to-guides': { title: 'How-to Guides' },
tutorials: { title: 'Tutorials' },
explanations: { title: 'Explanations' },
reference: { title: 'Reference' },
contributing: { title: 'Contributing' },
other: {
title: 'Other',
},
'header-pricing': {
title: 'Pricing',
type: 'page',
href: '/pricing',
},
other: { title: 'Other' },
'header-pricing': { title: 'Pricing', type: 'page', href: '/pricing' },
'header-docs': {
title: 'Documentation',
type: 'page',
href: '/introduction',
},
'404': {
theme: {
layout: 'raw',
},
display: 'hidden',
},
'404': { theme: { layout: 'raw' }, display: 'hidden' },
}
7 changes: 7 additions & 0 deletions src/pages/package-inspector.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Package Inspector
description: Analyze the security state of open-source packages. Get insights on maintenance, vulnerabilities, and supply chain risks.
---
import { PackageInspectorPage } from '@/components/package-inspector/PackageInspectorPage'

<PackageInspectorPage />
1 change: 1 addition & 0 deletions src/pages/package-inspector/[...purl].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//results page coming soon
1 change: 1 addition & 0 deletions src/pages/package-inspector/_meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default { theme: { layout: 'raw' }, display: 'hidden' }