diff --git a/.gitignore b/.gitignore index 2a0870e..376aff4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +image.tar +# Sentry Config File +.env.sentry-build-plugin diff --git a/package-lock.json b/package-lock.json index dd190df..0f15e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,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", @@ -37,12 +39,14 @@ "react-hook-form": "^7.57.0", "react-markdown": "^9.0.3", "react-use-measure": "^2.1.7", + "recharts": "^3.7.0", "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", @@ -4561,6 +4565,42 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4671,6 +4711,18 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swagger-api/apidom-ast": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.1.0.tgz", @@ -5308,6 +5360,26 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.13.10", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.10.tgz", @@ -5325,6 +5397,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.13.10", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.10.tgz", @@ -8324,6 +8409,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.19", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", @@ -8354,6 +8449,12 @@ "dev": true, "license": "MIT" }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -8851,6 +8952,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -9553,6 +9664,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -10846,6 +10963,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz", @@ -16293,7 +16420,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-markdown": { "version": "9.0.3", @@ -16505,6 +16633,36 @@ "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", "license": "MIT" }, + "node_modules/recharts": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", + "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/recma-build-jsx": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", @@ -16576,6 +16734,15 @@ "license": "MIT", "peer": true }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -18419,6 +18586,19 @@ "immutable": "^3.8.1 || ^4.0.0-rc.1" } }, + "node_modules/swr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.0.tgz", + "integrity": "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -18604,6 +18784,12 @@ "license": "MIT", "peer": true }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", @@ -19453,9 +19639,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -19555,6 +19741,28 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", diff --git a/package.json b/package.json index 3eb20a1..ad5536b 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", "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", diff --git a/src/components/reachabilityAnalysis/columns.tsx b/src/components/reachabilityAnalysis/columns.tsx new file mode 100644 index 0000000..237bd73 --- /dev/null +++ b/src/components/reachabilityAnalysis/columns.tsx @@ -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[] = [ + { + accessorKey: 'purl', + header: 'Package URL', + size: 150, + cell: ({ row }) => { + const purl = row.getValue('purl') as string + return ( + +
{purl}
+ + ) + }, + }, + { + accessorKey: 'type', + header: 'Package Type', + size: 600, + cell: ({ row }) => { + const description = row.getValue('type') as string + return
{description}
+ }, + }, + { + accessorKey: 'name', + size: 150, + header: ({ column }) => { + return ( +
+ column.toggleSorting(column.getIsSorted() === 'asc') + } + > + Package Name + +
+ ) + }, + cell: ({ row }) => { + const purl = row.getValue('name') as string + return
{purl}
+ }, + }, +] \ No newline at end of file diff --git a/src/components/reachabilityAnalysis/reachability-analysis-hero.tsx b/src/components/reachabilityAnalysis/reachability-analysis-hero.tsx new file mode 100644 index 0000000..ebace19 --- /dev/null +++ b/src/components/reachabilityAnalysis/reachability-analysis-hero.tsx @@ -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 ( + <> +
+
+
+
+

+ Reachability Analysis +

+

+ Lorem Ipsum +

+
{ + e.preventDefault() + onSearch() + }} + > + + setSearchTerm(e.target.value) + } + /> +
+
+
+
+
+ + ) +} diff --git a/src/components/reachabilityAnalysis/reachability-analysis-package-details.tsx b/src/components/reachabilityAnalysis/reachability-analysis-package-details.tsx new file mode 100644 index 0000000..a6510ea --- /dev/null +++ b/src/components/reachabilityAnalysis/reachability-analysis-package-details.tsx @@ -0,0 +1,186 @@ +import { useState } from 'react' +import { Container } from '../top-level-pages/container' +import { ReachabilityAnalysisResponse } from 'src/components/reachabilityAnalysis/reachability-types' +import { Label, Pie, PieChart } from 'recharts' +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 '@/lib/fetcher' +import useSWR from 'swr' + + + +export default function CVEDetailComponent({ purl }: { purl?: string }) { + const { data, error, isLoading } = useSWR( + purl ? `http://localhost:8080/api/v1/vulndb/reachability/${purl}` : null, + fetcher, + ) + + const [isVulnerabilitiesOpen, setIsVulnerabilitiesOpen] = useState(false) + const [isComponentsOpen, setIsComponentsOpen] = useState(false) + + const options: Intl.DateTimeFormatOptions = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + } + + if (isLoading) { + return ( + +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ + ) + } + + if (error || !data) { + return
No Package data found
+ } + + return ( + +
+
+
+
+
+
+

+ {purl} +

+
+
+
+
+
+
+
+
+ + {isComponentsOpen && ( +
+
+ {data.components.map((comp) => ( +
+

{comp.name}

+

{comp.type}

+
+ ))} +
+
+ )} +
+
+
+
+
+
+ + {isVulnerabilitiesOpen && ( +
+
+ {data.vulnerabilities.map((vuln) => ( +
+

{vuln.id}

+

{vuln['bom-ref']}

+
+ ))} +
+
+ )} +
+
+
+
+
+ +
+ +
+ + ) +} \ No newline at end of file diff --git a/src/components/reachabilityAnalysis/reachability-analysis-page.tsx b/src/components/reachabilityAnalysis/reachability-analysis-page.tsx new file mode 100644 index 0000000..69302de --- /dev/null +++ b/src/components/reachabilityAnalysis/reachability-analysis-page.tsx @@ -0,0 +1,76 @@ +import ReachabilityAnalysisHero from 'src/components/reachabilityAnalysis/reachability-analysis-hero' + +import { DataTable } from 'src/components/reachabilityAnalysis/reachability-analysis-results-table' +import { columns } from 'src/components/reachabilityAnalysis/columns' +import { Container } from 'src/components/top-level-pages/container' + +import { useState, useEffect, useRef } from 'react' +import { useRouter } from 'next/router' +import { fetcher } from 'src/lib/fetcher' +import useSWR from 'swr' + +const apiBaseURL = 'http://localhost:8080/api/v1/vulndb/reachability/' + +export default function ReachabilityAnalysisPage() { + const router = useRouter() + const queryParam = router.query.query + const pageParam = router.query.page + const [searchTerm, setSearchTerm] = useState((queryParam as string) ?? '') + const tableRef = useRef(null) + + const query = (queryParam as string)?.toUpperCase() ?? '' + const page = pageParam as string + + let url = null + if (router.isReady && pageParam) { + url = `http://localhost:8080/api/v1/vulndb/reachability?pageSize=10&page=${page}` + if (query) { + url += `&filterQuery[npm][like]=%25${query}%25` + } + } + + const { data: apiResponse, error, isLoading } = useSWR(url, fetcher) + + const data = apiResponse?.data ?? [] + const total = apiResponse?.total ?? 0 + + useEffect(() => { + if (router.isReady && router.query.query) { + setSearchTerm(router.query.query as string) + } + }, [router.isReady, router.query.query]) + + useEffect(() => { + if (apiResponse?.data && apiResponse.data.length > 0) { + tableRef.current?.scrollIntoView({ behavior: 'smooth' }) + } + }, [apiResponse]) + + const handleSearch = () => { + router.push({ query: { query: searchTerm, page: 1 } }, undefined, { + scroll: false, + }) + } + + return ( + + +
+ + router.push( + { query: { ...router.query, page } }, + undefined, + { scroll: false }, + ) + } + /> +
+
+ ) +} \ No newline at end of file diff --git a/src/components/reachabilityAnalysis/reachability-analysis-results-table.tsx b/src/components/reachabilityAnalysis/reachability-analysis-results-table.tsx new file mode 100644 index 0000000..978cfce --- /dev/null +++ b/src/components/reachabilityAnalysis/reachability-analysis-results-table.tsx @@ -0,0 +1,164 @@ + +import { Button } from 'src/components/ui/button' +import { Skeleton } from 'src/components/ui/skeleton' +import {Table,TableBody,TableCell,TableHead,TableHeader,TableRow} from 'src/components/ui/table' +import {ColumnDef,SortingState,flexRender,getCoreRowModel,getSortedRowModel,useReactTable} from '@tanstack/react-table' + +import * as React from 'react' +import { useRouter } from 'next/navigation' +import { Package } from './columns' +import { Island_Moments } from 'next/font/google' + +const PAGE_SIZE = 10 + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] + isLoading?: boolean + total?: number + page?: number + onPageChange?: (page: number) => void +} + +export function DataTable({ + columns, + data, + isLoading = false, + total = 0, + page = 1, + onPageChange, +}: DataTableProps) { + const router = useRouter() + + const [sorting, setSorting] = React.useState([ + { + id: 'purl', + desc: true, + }, + ]) + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + state: { + sorting, + }, + }) + + if (!isLoading && data.length == 0) { + return ( +
+
+ ) + } + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef + .header, + header.getContext(), + )} + + ) + })} + + ))} + + + {isLoading ? ( + Array(1) + .fill(0) + .map((_, rowIndex) => ( + + {columns.map((column, cellIndex) => ( + + + + ))} + + )) + ) : table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + router.push( + `/reachability-analysis/${encodeURIComponent((row.original as Package).purl)}`, + ) + } + className="cursor-pointer hover:bg-muted/50" + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ Showing {Math.min((page - 1) * PAGE_SIZE + 1, total)}– + {Math.min(page * PAGE_SIZE, total)} of {total} results +
+
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/reachabilityAnalysis/reachability-types.tsx b/src/components/reachabilityAnalysis/reachability-types.tsx new file mode 100644 index 0000000..26e7e62 --- /dev/null +++ b/src/components/reachabilityAnalysis/reachability-types.tsx @@ -0,0 +1,63 @@ + +export interface ReachabilityAnalysisResponse{ + bomFormat : string + specVersion : string + serialNumber : string + version : number + metadata : Metadata + components : Component[] + vulnerabilities : Vulnerability[] +} + +interface Metadata{ + timestamp : string + tools : Tool[] +} + +interface Tool{ + vendor : string + name : string + version : string +} + +interface Component { + type : string + name : string + purl : string + "bom-ref" : string +} + +interface Vulnerability{ + "bom-ref" : string + id : string + description : string + affects : Affect[] + analysis : Analysis +} + +interface Affect{ + ref : string +} + +interface Analysis { + state : string + detail : string + evidence : Evidence[] +} + +interface Evidence { + path : PathElement[] +} + + +interface PathElement{ + id: number; + cve_id: string; + signature: string; + filename: string; + start_line: number; + end_line: number; + evidence: string; + purl: string; + calls_id: number | null; +} \ No newline at end of file diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..68551b9 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..7f3502f --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/lib/fetcher.js b/src/lib/fetcher.js new file mode 100644 index 0000000..609d2d9 --- /dev/null +++ b/src/lib/fetcher.js @@ -0,0 +1 @@ +export const fetcher = (url) => fetch(url).then((res) => res.json()) \ No newline at end of file diff --git a/src/pages/_meta.ts b/src/pages/_meta.ts index ce5c0af..1902498 100644 --- a/src/pages/_meta.ts +++ b/src/pages/_meta.ts @@ -1,3 +1,5 @@ +import { title } from "process"; + export default { index: { theme: { @@ -11,6 +13,12 @@ export default { }, display: 'hidden', }, + "reachability-analysis": { + theme: { + layout: 'raw' + }, + display: 'hidden', + }, 'terms-of-use': { display: 'hidden', }, @@ -37,6 +45,11 @@ export default { type: 'page', href: '/introduction', }, + 'header-reachability':{ + title: 'Reachability', + type: 'page', + href: '/reachability-analysis' + }, '404': { theme: { layout: 'raw', diff --git a/src/pages/reachability-analysis.mdx b/src/pages/reachability-analysis.mdx new file mode 100644 index 0000000..1f58361 --- /dev/null +++ b/src/pages/reachability-analysis.mdx @@ -0,0 +1,5 @@ +import ReachabilityAnalysisPage from 'src/components/reachabilityAnalysis/reachability-analysis-page' + + + + diff --git a/src/pages/reachability-analysis/[purl].mdx b/src/pages/reachability-analysis/[purl].mdx new file mode 100644 index 0000000..76838e8 --- /dev/null +++ b/src/pages/reachability-analysis/[purl].mdx @@ -0,0 +1,35 @@ +import PackageDetailComponent from 'src/components/reachabilityAnalysis/reachability-analysis-package-details' +import { useRouter } from 'next/router' + +export const PackageDetails = () => { + const router = useRouter() + const { purl } = router.query + const packageURL = Array.isArray(purl) ? purl[0] : purl + return ( + <> + + + + ) +} + + \ No newline at end of file