diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx new file mode 100644 index 0000000..cae7841 --- /dev/null +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx @@ -0,0 +1,60 @@ +// Dependencies 页面 +"use client"; +import React, { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; + +// import DependencyTable from "@/components/DependencyTable"; +import DependencyGraph from "@/components/DependencyGraph"; // 假设你已经创建了 DependencyGraph 组件 +import { dependenciesInfo } from "@/app/lib/all_interface"; + +const CratePage = () => { + const params = useParams(); + // const currentVersion = params.version; + // const crateName = params.name; + const [results, setResults] = useState(null); + console.log(results); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 新增状态:控制显示 DependencyTable 或 DependencyGraph + // const [showTable, setShowTable] = useState(true); + + useEffect(() => { + const fetchCrateData = async () => { + try { + setError(null); + const response = await fetch( + `/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/dependencies` + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + setResults(data); // 设置获取的数据 + } catch (error) { + console.log("Error fetching data:", error); + setError("Failed to fetch data."); + } finally { + setLoading(false); // 完成加载 + } + }; + fetchCrateData(); // 调用函数来获取数据 + }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 crateName 或 version 改变时重新获取数据 + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
+ + + + +
+ ); +}; + +export default CratePage; diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx new file mode 100644 index 0000000..ea9d4ca --- /dev/null +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx @@ -0,0 +1,67 @@ +// Dependencies 页面 +"use client"; +import React, { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; + +import DependencyTable from "@/components/DependencyTable"; +// import DependencyGraph from "@/components/DependencyGraph"; // 假设你已经创建了 DependencyGraph 组件 +import { dependenciesInfo } from "@/app/lib/all_interface"; +import Link from "next/link"; + +const CratePage = () => { + const params = useParams(); + // const currentVersion = params.version; + // const crateName = params.name; + const [results, setResults] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 新增状态:控制显示 DependencyTable 或 DependencyGraph + const [showTable, setShowTable] = useState(true); + + useEffect(() => { + const fetchCrateData = async () => { + try { + setError(null); + const response = await fetch( + `/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/dependencies` + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + setResults(data); // 设置获取的数据 + } catch (error) { + console.log("Error fetching data:", error); + setError("Failed to fetch data."); + } finally { + setLoading(false); // 完成加载 + } + }; + fetchCrateData(); // 调用函数来获取数据 + }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 crateName 或 version 改变时重新获取数据 + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
+ + {/* 切换按钮 */} + setShowTable(!showTable)} // 切换状态 + className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" + href={`/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/dependencies/graph`} > + {showTable ? "Show Graph" : "Show Table"} + + + + +
+ ); +}; + +export default CratePage; diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx new file mode 100644 index 0000000..694f923 --- /dev/null +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx @@ -0,0 +1,58 @@ +//Dependents页面 +"use client"; +import React, { useEffect, useState } from 'react'; + +import DependentTable from '@/components/DependentTable'; +import { dependentsInfo } from '@/app/lib/all_interface'; +import { useParams } from 'next/navigation'; + + + +const CratePage = () => { + const [results, setResults] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const params = useParams(); + + + + useEffect(() => { + const fetchCrateData = async () => { + try { + const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/dependents`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + + setResults(data); // 设置获取的数据 + + } catch (error) { + setError(null); + console.log('Error fetching data:', error); + } finally { + setLoading(false); // 完成加载 + } + }; + fetchCrateData(); // 调用函数来获取数据 + }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 crateName 或 version 改变时重新获取数据 + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + + + return ( +
+ + + +
+ ); +}; + +export default CratePage; \ No newline at end of file diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/page.tsx new file mode 100644 index 0000000..1edaba8 --- /dev/null +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/page.tsx @@ -0,0 +1,311 @@ +// Overview页面 +"use client"; +import React, { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { cratesInfo } from '@/app/lib/all_interface'; +import { useParams } from 'next/navigation'; + +const CratePage = () => { + const params = useParams(); + const [results, setResults] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchCrateData = async () => { + try { + const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setResults(data); + } catch (error) { + console.error('Error fetching data:', error); + setError('An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchCrateData(); + }, [params.name, params.version, params.nsfront, params.nsbehind]); + + if (loading) return

Loading...

; + if (error) return

Error: {error}

; + + return ( +
+
+
+ + {/* Security Advisories */} +
+ +
+

Security Advisories

+ + {results ? results.cves.length + results.dep_cves.length : 0} + +
+

In this package

+
+
+ {results && results.cves && results.cves.length > 0 ? ( + results.cves.map((cve, index) => ( +
+

+ {cve.id !== '' ? JSON.stringify(cve.small_desc) : 'No results available'} +

+

+ {cve.id !== '' ? JSON.stringify(cve.id) : 'No results available'} +

+ +

+

SIMILAR ADVISORIES
+ {cve.id !== '' ? JSON.stringify(cve.aliases) : 'No results available'} +

+
+ )) + ) : ( +

No results available

+ )} +
+ {/* */} +
+ + + +

In the dependencies

+
+ {results && results.cves && results.cves.length > 0 ? ( + results.dep_cves.map((dep_cves, index) => ( + <> +

+ {dep_cves.id !== '' ? JSON.stringify(dep_cves.small_desc) : 'No results available'} +

+

+ {dep_cves.id !== '' ? JSON.stringify(dep_cves.id) : 'No results available'} +

+

+

SIMILAR ADVISORIES
+ {dep_cves.id !== '' ? JSON.stringify(dep_cves.aliases) : 'No results available'} +

+ + )) + ) : ( +

No results available

+ )} +
+ +
+ + {/* Licenses */} +
+

Licenses

+ + Learn more about license information. + +
+ LICENSES +
+
+ {results ? results.license : 'No results available'} +
+
+ + {/* Dependencies */} +
+
+

Dependencies

+ + {results ? results.dependencies.direct + results.dependencies.indirect : 0} + +
+ {/* Direct */} +
+
Direct
+ + {results ? JSON.stringify(results.dependencies.direct) : 'No cves detected.'} + +
+
0 + ? (results.dependencies.direct / (results.dependencies.direct + results.dependencies.indirect)) * 100 + : 0 // 当分母为0时,条状图宽度直接设为0% + }%`, + backgroundColor: 'rgb(50,165,224)', + }} + className="h-full rounded" + >
+
+
+ {/* Indirect */} +
+
Indirect
+ + {results ? JSON.stringify(results.dependencies.indirect) : 'No results available'} + +
+
0 + ? (results.dependencies.indirect / (results.dependencies.direct + results.dependencies.indirect)) * 100 + : 0 + }%`, + backgroundColor: 'rgb(50,165,224)', + }} + className="h-full rounded" + >
+
+
+ +
+ +
+ View all dependencies +
+ +
+
+ + {/* Dependents */} +
+
+

Dependents

+ + {results ? results.dependents.direct + results.dependents.indirect : 0} + +
+ {/* Direct */} +
+
Direct
+ + {results ? JSON.stringify(results.dependents.direct) : 'No results available'} + + +
+
0 + ? (results.dependents.direct / (results.dependents.direct + results.dependents.indirect)) * 100 + : 0 // 当分母为0时,条状图宽度直接设为0% + }%`, + backgroundColor: 'rgb(50,165,224)', + }} + className="h-full rounded" + >
+
+
+ {/* Indirect */} +
+
Indirect
+ + {results ? JSON.stringify(results.dependents.indirect) : 'No results available'} + +
+
0 + ? (results.dependents.indirect / (results.dependents.direct + results.dependents.indirect)) * 100 + : 0 // 当分母为0时,将宽度设为0% + }%`, + backgroundColor: 'rgb(50,165,224)', + }} + className="h-full rounded" + >
+
+
+ +
+ +
+ View all dependents +
+ +
+
+ +
+ + {/* 新增的块: doc_url 和 github_url */} +
+ + + {/* OpenSSF scorecard */} +
+

OpenSSF scorecard

+

The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.

+ View information about checks and how to fix failures. +
+
8.3/10
+
Scorecard as of November 11, 2024.
+
+
+
Code-Review10/10
+
Maintained10/10
+
CI/Best-Practices0/10
+
License10/10
+
Dangerous-Workflow10/10
+
Security-Policy10/10
+
Token-Permissions10/10
+
Binary-Artifacts10/10
+
Pinned-Dependencies0/10
+
+
+
+
+
+ ); +}; + +export default CratePage; \ No newline at end of file diff --git a/app/[nsfront]/[nsbehind]/layout.tsx b/app/[nsfront]/[nsbehind]/layout.tsx new file mode 100644 index 0000000..1ad9cf3 --- /dev/null +++ b/app/[nsfront]/[nsbehind]/layout.tsx @@ -0,0 +1,26 @@ +import '@/app/ui/global.css'; +import { HeaderProvider } from '../../context/CrateContext'; +import Header from '@/components/HeaderWithSearch'; + + +export const metadata = { + title: 'cratespro', + description: 'Generated by Next.js', +} + +export default function layout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + +
+ {children} + + + + ) +} diff --git a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx index 1339d4b..1e466e1 100644 --- a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx +++ b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx @@ -5,15 +5,17 @@ export async function GET(req: NextRequest, props: { params: Params }) { try { const params = await props.params const { nsfront, nsbehind, cratename, version } = params; + const endpoint = process.env.CRATES_PRO_INTERNAL_HOST; const externalApiUrl = `${endpoint}/api/crates/${nsfront}/${nsbehind}/${cratename}/${version}/dependencies`; // 替换为你的外部 API URL + const externalRes = await fetch(externalApiUrl); if (!externalRes.ok) { throw new Error('Failed to fetch external data'); } const externalData = await externalRes.json(); - console.log('External API Response:', externalData); + console.log('dependenciesssssssss:', externalData); return NextResponse.json(externalData); } catch (error) { console.error('Error:', error); diff --git a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx index 46a2dba..bbc85f6 100644 --- a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx +++ b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx @@ -13,7 +13,7 @@ export async function GET(req: NextRequest, props: { params: Params }) { throw new Error('Failed to fetch external data'); } const externalData = await externalRes.json(); - console.log('crateinfo 11111111', externalData); + console.log('crateinfo in api', externalData); return NextResponse.json(externalData); } catch (error) { console.error('Error:', error); diff --git a/app/api/graph/[cratename]/[version]/direct/route.tsx b/app/api/graph/[cratename]/[version]/direct/route.tsx new file mode 100644 index 0000000..5f03c2a --- /dev/null +++ b/app/api/graph/[cratename]/[version]/direct/route.tsx @@ -0,0 +1,23 @@ + +import { NextRequest, NextResponse } from "next/server"; +type Params = Promise<{ nsfront: string, nsbehind: string, cratename: string, version: string }> +export async function GET(req: NextRequest, props: { params: Params }) { + try { + const params = await props.params + const { nsfront, nsbehind, cratename, version } = params; + console.log('graph data:', nsfront, nsbehind, cratename, version); + const externalApiUrl = `http://210.28.134.203:6888/api/graph/${cratename}/${version}/direct`; + + const externalRes = await fetch(externalApiUrl); + if (!externalRes.ok) { + throw new Error('Failed to fetch external data'); + } + const externalData = await externalRes.json(); + console.log('graph dataaaaaaaaaa:', externalData); + return NextResponse.json(externalData); + } catch (error) { + console.error('Error:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + + } +} \ No newline at end of file diff --git a/app/context/navContext.tsx b/app/context/navContext.tsx new file mode 100644 index 0000000..e3e5765 --- /dev/null +++ b/app/context/navContext.tsx @@ -0,0 +1,19 @@ +// // MyContext.js +// import React, { createContext, useContext, useState } from 'react'; + +// // 创建上下文 +// const MyContext = createContext(); + +// // 创建提供者组件 +// export const MyProvider = ({ children }) => { +// const [activeTable, setactiveTable] = useState("Initial Value"); + +// return ( +// +// {children} +// +// ); +// }; + +// // 创建自定义钩子以便于使用上下文 +// export const useMyContext = () => useContext(MyContext); \ No newline at end of file diff --git a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx index ce980c4..c2c7df3 100644 --- a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx +++ b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx @@ -42,7 +42,7 @@ const CratePage = () => {

Security Advisories

- cve: {results && results.cves && results.cves.length > 0 && results.cves[0] !== '' ? JSON.stringify(results.cves) : 'No results available'} + cve: {results && results.cves && results.cves.length > 0 && true ? JSON.stringify(results.cves) : 'No results available'}

{/* Licenses */} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..e1db2f3 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,23 @@ +import '@/app/ui/global.css'; +import { HeaderProvider } from './context/CrateContext'; + +export const metadata = { + title: 'cratespro', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + ) +} diff --git a/app/lib/all_interface.ts b/app/lib/all_interface.ts index b6c6891..cd7dac4 100644 --- a/app/lib/all_interface.ts +++ b/app/lib/all_interface.ts @@ -31,7 +31,24 @@ export interface cratesInfo { "indirect": number }, - "cves": string[], + "cves": [ + { + "id": string, + "cratename": string, + "patched": string, + "aliases": string[], + "small_desc": string, + } + ], + "dep_cves": [ + { + "id": string, + "cratename": string, + "patched": string, + "aliases": string[], + "small_desc": string, + } + ], "license": string, "github_url": string, "doc_url": string, diff --git a/app/newpage/layout.tsx b/app/newpage/layout.tsx deleted file mode 100644 index b06395b..0000000 --- a/app/newpage/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import '@/app/ui/global.css'; - - -export const metadata = { - title: 'cratespro', - description: 'Generated by Next.js', -} - -export default function Layout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) -} diff --git a/app/newpage/page.tsx b/app/newpage/page.tsx deleted file mode 100644 index 0e61643..0000000 --- a/app/newpage/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import '@/app/ui/global.css'; - - -const HomePage = () => { - return ( - //紫色渐变 - //
- // {/* 头部和搜索部分 */} - //
- //
- //
open/source/insights
- // - //
- //
- //

Understand your dependencies

- //

Your software and your users rely not only on the code you write, but also on the code your code depends on, the code that code depends on, and so on.

- //
- // - // - //
- //
- //
- - - //绿色渐变 - < div className="min-h-screen bg-gray-900 text-white" > -
-
open/source/insights
- -
- {/* 搜索部分 */} -
-

Understand your dependencies

-

Your software and your users rely not only on the code you write, but also on the code your code depends on, the code that code depends on, and so on.

-
- - -
-
- - - {/* 分割线部分 */} -
- - {/* 一些介绍 */} -
-
-

New features in the deps.dev API

-

The deps.dev API, which provides free access to the data that powers this website, now has experimental batch and pull support, as well as a new version that comes with a stability guarantee and deprecation policy.

- -

Learn more about the new features on our blog, or get started with the API documentation, and code examples.

- -
- -
-

Seeing the big picture can be difficult—but it shouldn't be

-

The Open Source Insights page for each package shows the full dependency graph and updates it every day. The information provided can help you make informed decisions about using, building, and maintaining your software.

- -

With Open Source Insights, you can actually see the dependency graph for a package, then isolate the paths to a particular dependency. Or see whether a vulnerability in a dependency might affect your code. Or compare two versions of a package to see how the dependencies have changed in a new release.

- -
- -
-

How it works

- -

The service repeatedly examines sites such as github.com, npmjs.com, and pkg.go.dev to find up-to-date information about open source software packages. Using that information, it builds for each package the full dependency graph from scratch—not just from package lock files—connecting it to the packages it depends on and to those that depend on it. This transitive dependency graph allows problems in any package to be made visible to the owners and users of any software they affect.

- -
-
-
- ); -} - -export default HomePage; diff --git a/app/newpage/search/page.tsx b/app/newpage/search/page.tsx deleted file mode 100644 index 267b632..0000000 --- a/app/newpage/search/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -"use client"; - -import { useState } from 'react'; - -export default function Home() { - //const [query,] = useState(''); //const [query, setQuery] = useState(''); - // 使用假数据进行测试,const [results, setResults] = useState([ - const [results,] = useState([ - { crate_name: "tokio", version: "1.41.1", date: "2023-01-01" }, - { crate_name: "tokio", version: "0.1.2", date: "2023-02-01" }, - ]); - - // const search = async () => { - // // 待替换api - // const apiUrl = 'api'; - - // try { - // const response = await fetch(apiUrl, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify({ query }), - // }); - - // const data = await response.json(); - // setResults(data.results); - // } catch (error) { - // console.error('Error fetching data:', error); - // } - // }; - - return ( - //页面顶部和搜索框 -
-
-
-
- open - / - source - / - insights -
-
- - -
-
-
- - {/*搜索数据展示 */} -
-
- {results.map((item, index) => ( -
- {item.crate_name} -
Crate {item.version}Published {item.date}
-
- ))} -
-
-
- ); -} \ No newline at end of file diff --git a/app/newpage/styles.css b/app/newpage/styles.css deleted file mode 100644 index e69de29..0000000 diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..4f4a051 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,133 @@ +'use client'; +import React, { useState } from 'react'; +import '@/app/ui/global.css'; +import Link from 'next/link'; +// import VulnerabilityList from '@/components/CveList'; +import { message } from 'antd'; +import Image from 'next/image'; + +const HomePage: React.FC = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [messageApi, contextHolder] = message.useMessage();//antd-message的hooks调用 + const handleKeyPress = (e: { key: string; }) => { + // 检查是否按下了回车键 + if (e.key === 'Enter') { + // 如果是回车键,执行搜索 + performSearch(); + } + }; + + const performSearch = () => { + if (!searchQuery || searchQuery.trim() === '') { + + messageApi.warning('请输入搜索内容'); + //alert("请输入搜索内容"); // 可选:提示用户输入内容 + } + if (searchQuery.trim()) { + // 使用 Link 跳转到搜索页面 + window.location.href = `/search?crate_name=${searchQuery}`; + } + }; + + + + return ( + //绿色渐变 + <> + {contextHolder} + < div className=" min-h-screen bg-gray-900 text-white" > +
+ +
{/* items-center 确保垂直居中,space-x-4 添加间距 */} +
CratesPro
+
+ Rust Logo + {/* Rust Logo */} +
+
+ + + +
+ {/* 搜索部分 */} +
+

CratesPro - Rust Crate Analysis and Recommendation Platform

+

CratesPro is a sophisticated platform designed to analyze and evaluate Rust crates.

+
+ setSearchQuery(e.target.value)} // 更新搜索内容 + onKeyDown={handleKeyPress} + /> + {/* */} + + {/* */} +
+
+ + + {/* 分割线部分 */} +
+ + {/* */} + + + {/* 一些介绍 */} +
+
+ {/* Overview */} +
+
+

AI-Powered Multi-Dimensional Analysis

+

CratesPro leverages advanced AI models to provide in-depth, multi-dimensional analysis of Rust crates. By analyzing a variety of data points—including code structure, dependencies, and historical vulnerability patterns—the platform offers intelligent recommendations to optimize crates, enhance security, and reduce the risk of bugs or inefficiencies in your code.

+ {/*

Learn more about the new features on our blog, or get started with the API documentation, and code examples.

*/} +
+ + +
+

Vulnerability Detection and Risk Assessment

+

CratesPro automatically detects potential vulnerabilities in the crates, including logic flaws, security risks, and performance bottlenecks. Using intelligent pattern recognition, it scans crate code and their dependencies to identify possible threats or weaknesses. These findings are presented with suggested fixes and recommendations.

+ {/*

With Open Source Insights, you can actually see the dependency graph for a package, then isolate the paths to a particular dependency. Or see whether a vulnerability in a dependency might affect your code. Or compare two versions of a package to see how the dependencies have changed in a new release.

*/} +
+ +
+

CVE Tracking and Vulnerability Propagation

+

CratesPro tracks CVE (Common Vulnerabilities and Exposures) related to Rust crates and their dependencies. The platform monitors crate versions and their updates, correlating them with known CVE databases to provide real-time updates on newly discovered vulnerabilities. This helps developers stay informed about potential threats to their projects and take proactive measures.

+
+ +
+

User-Uploaded Crates for Comprehensive Analysis

+

CratesPro supports users who wish to upload their custom crates for comprehensive analysis. After uploading, CratesPro performs a full evaluation of the crate, analyzing dependencies, potential vulnerabilities, and even compatibility with other crates in the Rust ecosystem. This feature empowers developers to ensure the security and reliability of their own creations before publication.

+
+ +
+

Dependency Tracking and Visualization

+

CratesPro offers detailed tracking of crate dependencies, visualizing the relationships between crates and how vulnerabilities propagate across them. By modeling dependencies and interactions, developers can see how a vulnerability in one crate may affect others in the ecosystem, offering a clearer understanding of potential risks.

+
+
+ + + ); +} + +export default HomePage; diff --git a/app/search/layout.tsx b/app/search/layout.tsx new file mode 100644 index 0000000..011b515 --- /dev/null +++ b/app/search/layout.tsx @@ -0,0 +1,22 @@ +"use client"; +import '@/app/ui/global.css'; +import NewHeader from '@/components/NewHeader'; + + + + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + ) +} diff --git a/app/search/page.tsx b/app/search/page.tsx new file mode 100644 index 0000000..7d2db28 --- /dev/null +++ b/app/search/page.tsx @@ -0,0 +1,132 @@ +"use client"; +import { useEffect, useState, Suspense } from 'react'; +import { useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +// import NewHeader from '@/components/NewHeader'; +import { searchResult } from '@/app/lib/all_interface'; + +const Search = () => { + const [results, setResults] = useState(null); + const [currentPage, setCurrentPage] = useState(1); // 添加当前页码状态 + const [loading, setLoading] = useState(false); // 添加加载状态 + const searchParams = useSearchParams(); + const name = searchParams.get('crate_name'); + + useEffect(() => { + if (name) { + fetchResults(name, currentPage); // 使用 name 和当前页发起请求 + } + }, [name, currentPage]); // 当 name 或 currentPage 改变时重新运行 + + const fetchResults = async (query: string, page: number) => { + setLoading(true); // 开始加载数据 + try { + const response = await fetch('/api/search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query, + pagination: { + page, // 使用传入的页码 + per_page: 20 // 每页条数 + } + }), + }); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const data1 = await response.json(); + const data = data1.data; + setResults(data); // 假设返回的数据data字段 + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); // 数据加载完成 + } + }; + + const handleNextPage = () => { + if (results && currentPage < results.data.total_page) { + setCurrentPage(prevPage => prevPage + 1); // 增加页码 + } + }; + + const handlePreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(prevPage => prevPage - 1); // 减少页码 + } + }; + + console.log("results:", results?.data.items); + return ( +
+ {/* */} +
+
+ {loading ? ( +

Loading...

+ ) : results ? ( + results.data.total_page > 0 && results.data.items.length > 0 ? ( + results.data.items.map((item, index) => ( + +
+ {item.name} +
+ Crate • {item.version} • {item.nsfront}/{item.nsbehind} +
+
+ + )) + ) : ( +

No items found.

+ ) + ) : ( +

Loading...

+ )} +
+
+ {/* Package•1.0.4•Published July 19, 2019 */} + {/* 当前页数在按钮上方 */} +
+ {results && ( +
+

+ Current page: {currentPage} / Total page: {results.data.total_page} +

+
+ )} + {results && results.data.total_page > 0 && ( +
+ + +
+ )} +
+
+ ); +} + +export default function SearchPage() { + return ( + Loading...}> + + + ); +} \ No newline at end of file diff --git a/app/styles.css b/app/styles.css new file mode 100644 index 0000000..7a30764 --- /dev/null +++ b/app/styles.css @@ -0,0 +1,5 @@ +/* styles.css */ +.custom-margin-left { + margin-left: 100px; + /* 自定义的左边距 */ +} \ No newline at end of file diff --git a/components/DependencyGraph.tsx b/components/DependencyGraph.tsx index 8b50649..fee2b92 100644 --- a/components/DependencyGraph.tsx +++ b/components/DependencyGraph.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useRef, useState } from 'react'; import * as d3 from 'd3'; -import { useRouter } from 'next/navigation'; +// import { useParams } from 'next/navigation'; export interface GraphDependency { - name: string; + crate_name: string; version: string; dependencies?: GraphDependency[]; } @@ -21,40 +21,42 @@ interface DependencyLink extends d3.SimulationLinkDatum { export interface DependencyGraphProps { crateName: string; currentVersion: string; - // dependencies?: GraphDependency; -} - -interface SubDep { - name: string; - version: string; } const DependencyGraph: React.FC = ({ crateName, currentVersion }) => { const [graphDependencies, setGraphDependencies] = useState(null); const d3Container = useRef(null); - const router = useRouter(); + // const params = useParams(); useEffect(() => { - async function fetchDependencyTree(name: string, version: string): Promise { - const response = await fetch(`/api/crates/${name}/${version}`); - const versionData = await response.json(); + async function fetchDependencyTree(name: string, version: string, visited: Set): Promise { + const nodeId = `${name}@${version}`; + if (visited.has(nodeId)) { + return { crate_name: name, version, dependencies: [] }; + } + visited.add(nodeId); - const dependencies = versionData.dependencies || []; + const url = `/api/graph/${name}/${version}/direct`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } - const dependenciesDetails = await Promise.all(dependencies.map(async (subDep: SubDep) => { - return fetchDependencyTree(subDep.name, subDep.version); + const dependencies = await response.json(); + const dependenciesDetails = await Promise.all(dependencies.map(async (dep: { name: string; version: string; }) => { + return fetchDependencyTree(dep.name, dep.version, visited); })); return { - name, + crate_name: name, version, dependencies: dependenciesDetails }; } async function loadDependencies() { - const graphDep = await fetchDependencyTree(crateName, currentVersion); - console.log(graphDep); + const visited = new Set(); + const graphDep = await fetchDependencyTree(crateName, currentVersion, visited); setGraphDependencies(graphDep); } @@ -78,7 +80,7 @@ const DependencyGraph: React.FC = ({ crateName, currentVer svg.append('defs').append('marker') .attr('id', 'arrowhead') .attr('viewBox', '0 -5 10 10') - .attr('refX', 20) // 增加 refX 以使箭头远离节点 + .attr('refX', 20) .attr('refY', 0) .attr('orient', 'auto') .attr('markerWidth', 7) @@ -88,13 +90,11 @@ const DependencyGraph: React.FC = ({ crateName, currentVer .attr('fill', '#333') .style('stroke', 'none'); - - const nodesMap = new Map(); const links: DependencyLink[] = []; function processDependencies(dep: GraphDependency, parent?: DependencyNode) { - const nodeId = `${dep.name}@${dep.version}`; + const nodeId = `${dep.crate_name}@${dep.version}`; let node = nodesMap.get(nodeId); if (!node) { node = { id: nodeId, color: parent ? '#69b3a2' : 'red' }; @@ -113,10 +113,10 @@ const DependencyGraph: React.FC = ({ crateName, currentVer const nodes = Array.from(nodesMap.values()); const simulation = d3.forceSimulation(nodes) - .force('link', d3.forceLink(links).id(d => d.id).distance(100)) // 增加距离 - .force('charge', d3.forceManyBody().strength(-500)) // 增加排斥力 + .force('link', d3.forceLink(links).id(d => d.id).distance(100)) + .force('charge', d3.forceManyBody().strength(-500)) .force('center', d3.forceCenter(width / 2, height / 2)) - .force('collide', d3.forceCollide().radius(50)); // 增加碰撞半径 + .force('collide', d3.forceCollide().radius(50)); const link = svg.append('g') .selectAll('line') @@ -124,7 +124,7 @@ const DependencyGraph: React.FC = ({ crateName, currentVer .enter().append('line') .attr('stroke-width', 2) .attr('stroke', '#333') - .attr('marker-end', 'url(#arrowhead)'); // 确保引用正确 + .attr('marker-end', 'url(#arrowhead)'); const node = svg.append('g') .selectAll('circle') @@ -134,10 +134,6 @@ const DependencyGraph: React.FC = ({ crateName, currentVer .attr('fill', d => d.color) .attr('stroke', '#333') .attr('stroke-width', 1.5) - .on('click', (event, d) => { - const [name, version] = d.id.split('@'); - router.push(`/programs/${name}/${version}`); - }) .call(d3.drag() .on('start', dragstarted) .on('drag', dragged) @@ -185,8 +181,8 @@ const DependencyGraph: React.FC = ({ crateName, currentVer } function dragged(event: d3.D3DragEvent, d: DependencyNode) { - d.fx = Math.max(10, Math.min(width - 10, event.x)); - d.fy = Math.max(10, Math.min(height - 10, event.y)); + d.fx = event.x; + d.fy = event.y; } function dragended(event: d3.D3DragEvent, d: DependencyNode) { @@ -195,7 +191,7 @@ const DependencyGraph: React.FC = ({ crateName, currentVer d.fy = null; } - }, [graphDependencies, router]); + }, [graphDependencies]); return (
diff --git a/components/DependencyTable.tsx b/components/DependencyTable.tsx index c9894ae..0da59cd 100644 --- a/components/DependencyTable.tsx +++ b/components/DependencyTable.tsx @@ -20,7 +20,6 @@ const DependencyTable: React.FC = ({ data }) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState(null); - console.log('data in compnent dcyyyyyyyyyyyy:', data); const x = 1; if (x <= 0) { setSortColumn(null); @@ -95,13 +94,15 @@ const DependencyTable: React.FC = ({ data }) => { // setSortDirection(null); // } return ( - handleSort(sorter, sorter.order)} - // rowKey={(record) => record.Crate} - /> +
+
handleSort(sorter, sorter.order)} + // rowKey={(record) => record.Crate} + /> + ); }; diff --git a/components/DependentTable.tsx b/components/DependentTable.tsx index 046567c..04cbaac 100644 --- a/components/DependentTable.tsx +++ b/components/DependentTable.tsx @@ -19,7 +19,7 @@ const DependencyTable: React.FC = ({ data }) => { const [sortDirection, setSortDirection] = useState(null); - console.log('data in compnent detttttttttttttttttttttts:', data); + // console.log('data in compnent detttttttttttttttttttttts:', data); const y = 1; if (y <= 0) { setSortColumn(null); diff --git a/components/HeaderWithSearch.tsx b/components/HeaderWithSearch.tsx index bcbe1d7..9070e76 100644 --- a/components/HeaderWithSearch.tsx +++ b/components/HeaderWithSearch.tsx @@ -4,19 +4,24 @@ import React, { useEffect, useState } from 'react'; import Link from 'next/link'; import { useHeaderContext } from '../app/context/CrateContext'; import { useParams } from 'next/navigation'; +import { message } from 'antd'; +import { useRouter } from 'next/navigation'; // import { cratesInfo } from '@/app/lib/all_interface'; const Header = () => { + const router = useRouter(); + console.log('paramsssssssssssss:', router.refresh); const params = useParams(); - + const [messageApi, contextHolder] = message.useMessage();//antd-message的hooks调用 const [searchQuery, setSearchQuery] = useState(''); const { crateData, setCrateData } = useHeaderContext(); const [isOpen, setIsOpen] = useState(false); - //const [currentVersion, setCurrentVersion] = useState(); // 存储当前选中的版本 + const [currentVersion, setCurrentVersion] = useState(params.version); // 存储当前选中的版本 const [activeTab, setActiveTab] = useState('overview'); // 默认选中 Overview + // console.log('pathhhhhhhhhhhhhhhhhhhhhhhhhhhh:', params); // 定义导航项数据 const navItems = [ { name: 'Overview', path: '' }, @@ -25,6 +30,43 @@ const Header = () => { ]; + const handleKeyPress = (e: { key: string; }) => { + // 检查是否按下了回车键 + if (e.key === 'Enter') { + // 如果是回车键,执行搜索 + performSearch(); + } + }; + + const performSearch = () => { + if (!searchQuery || searchQuery.trim() === '') { + + messageApi.warning('请输入搜索内容'); + //alert("请输入搜索内容"); // 可选:提示用户输入内容 + } + if (searchQuery.trim()) { + // 使用 Link 跳转到搜索页面 + window.location.href = `/search?crate_name=${searchQuery}`; + } + }; + useEffect(() => { + const path = params.path || ''; // 确保有值 + console.log('path:', path); + switch (path) { + case '': + setActiveTab('overview'); + break; + case 'dependencies': + setActiveTab('dependencies'); + break; + case 'dependents': + setActiveTab('dependents'); + break; + default: + setActiveTab('overview'); + break; + } + }, [params.path]); // 依赖于 params.path // 使用 useEffect 从 API 获取数据 useEffect(() => { const fetchData = async () => { @@ -54,106 +96,116 @@ const Header = () => { }; - - - - return ( -
-
-
- -
- open - / - source - / - insights -
- -
- {params.name} -
- - {isOpen && ( -
-
-
-
    - {crateData.results?.versions.map((version, index) => ( - setCurrentVersion(version)} - href={`/homepage/${params.nsfront}/${params.nsbehind}/${crateData.results?.crate_name}/${version}`} - > -
  • - - {version} - -
  • - - ))} -
+ {/* {crateData.crateVersion || 'Select Version'} */} + {currentVersion || 'Select Version'} + + + + + {isOpen && ( +
+
+
+
    + {crateData.results?.versions.map((version, index) => ( + { + setCurrentVersion(version); + setActiveTab('overview'); // 更新导航条为 Overview + }} + href={`/${params.nsfront}/${params.nsbehind}/${crateData.results?.crate_name}/${version}`} + > +
  • + + {version} + +
  • + + ))} +
+
-
- )} + )} +
-
-
- setSearchQuery(e.target.value)} - /> - + setSearchQuery(e.target.value)} // 更新搜索内容 + onKeyDown={handleKeyPress} + /> + {/* - - -
-
- -
+ Search + {/* */} + + + + + + + ); }; diff --git a/components/NewHeader.tsx b/components/NewHeader.tsx index d22f7ed..ce8ea55 100644 --- a/components/NewHeader.tsx +++ b/components/NewHeader.tsx @@ -1,20 +1,45 @@ import React, { useState } from "react"; import Link from "next/link"; +import { message } from "antd"; + const NewHeader = () => { const [searchQuery, setSearchQuery] = useState(''); + const [messageApi, contextHolder] = message.useMessage();//antd-message的hooks调用 + console.log('sss:', contextHolder); + const handleKeyPress = (e: { key: string; }) => { + // 检查是否按下了回车键 + if (e.key === 'Enter') { + // 如果是回车键,执行搜索 + performSearch(); + } + }; + + const performSearch = () => { + if (!searchQuery || searchQuery.trim() === '') { + + messageApi.warning('请输入搜索内容'); + //alert("请输入搜索内容"); // 可选:提示用户输入内容 + } + if (searchQuery.trim()) { + // 使用 Link 跳转到搜索页面 + window.location.href = `/search?crate_name=${searchQuery}`; + } + }; + return (
- +
- open - / - source - / - insights + open + / + source + / + insights
+
{ className="p-2 border-none rounded-md text-gray-800 w-80 max-w-2xl" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} // 更新搜索内容 + onKeyDown={handleKeyPress} /> - - - + }}> */} + + {/* */}
diff --git a/next.config.mjs b/next.config.mjs index 9e01be7..ce23bbc 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -4,14 +4,14 @@ export default { output: "standalone", reactStrictMode: true, - async redirects() { - return [ - { - source: '/', - //destination: '/programs', - destination: '/homepage', - permanent: true, - }, - ]; - }, + // async redirects() { + // return [ + // { + // source: '/', + + // destination: '/hompage', + // permanent: false, + // }, + // ]; + // }, }; \ No newline at end of file diff --git a/public/rust.svg b/public/rust.svg new file mode 100644 index 0000000..71b9f86 --- /dev/null +++ b/public/rust.svg @@ -0,0 +1,54 @@ + + + + +