From 3556614d93c51317fe1906b505d8e7692051fb3c Mon Sep 17 00:00:00 2001 From: mapan-nju <2246839805@qq.com> Date: Tue, 3 Dec 2024 16:28:17 +0800 Subject: [PATCH 1/4] aaa --- app/context/CrateContext.tsx | 64 +++++++ .../[name]/[version]/dependencies/page.tsx | 101 +---------- .../[name]/[version]/dependents/page.tsx | 94 +--------- .../[nsbehind]/[name]/[version]/page.tsx | 144 ++-------------- app/homepage/[nsfront]/[nsbehind]/layout.tsx | 24 +++ app/homepage/layout.tsx | 10 +- components/HeaderWithSearch.tsx | 160 ++++++++++++++++++ next.config.mjs | 9 + 8 files changed, 287 insertions(+), 319 deletions(-) create mode 100644 app/context/CrateContext.tsx create mode 100644 app/homepage/[nsfront]/[nsbehind]/layout.tsx create mode 100644 components/HeaderWithSearch.tsx diff --git a/app/context/CrateContext.tsx b/app/context/CrateContext.tsx new file mode 100644 index 0000000..52becc4 --- /dev/null +++ b/app/context/CrateContext.tsx @@ -0,0 +1,64 @@ +// context/HeaderContext.tsx +"use client"; +import React, { createContext, useContext, useState } from 'react'; + +// 定义 cratesInfo 接口 +export interface CratesInfo { + crate_name: string; + description: string; + dependencies: { + direct: number; + indirect: number; + }; + dependents: { + direct: number; + indirect: number; + }; + cves: { + cve_id: string; + url: string; + description: string; + }[]; + versions: string[]; +} + +// 定义 HeaderContext 的类型 +interface CrateData { + crateName: string | undefined; + crateVersion: string | string[] | undefined; + results: CratesInfo | null; // 存储 cratesInfo 数据 +} + +interface HeaderContextType { + crateData: CrateData; + setCrateData: React.Dispatch>; +} + +// 创建上下文,并设置默认值为 undefined +const HeaderContext = createContext(undefined); + +// 创建提供者组件 +export const HeaderProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [crateData, setCrateData] = useState({ + crateName: undefined, + crateVersion: undefined, + results: null, + }); + + // 这里可以添加一些副作用,例如在 crateData 更新时执行某些操作 + + return ( + + {children} + + ); +}; + +// 创建一个自定义 Hook 用于使用上下文 +export const useHeaderContext = (): HeaderContextType => { + const context = useContext(HeaderContext); + if (context === undefined) { + throw new Error('useHeaderContext must be used within a HeaderProvider'); + } + return context; +}; \ No newline at end of file diff --git a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx index cdbfc35..fc966f1 100644 --- a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx +++ b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx @@ -1,21 +1,22 @@ +//Dependencies页面 "use client"; import React, { useEffect, useState } from 'react'; import { useParams } from 'next/navigation' -import Link from 'next/link'; + import DependencyTable from '../../../../../../../components/DependencyTable'; import { dependenciesInfo } from '@/app/lib/all_interface'; + const CratePage = () => { const params = useParams(); const [results, setResults] = useState(null); - const [searchQuery, setSearchQuery] = useState(''); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const crateName = params.name; // 从 URL 中获取 crate_name 参数 - const version = params.version; // 从 URL 中获取 version 参数 + useEffect(() => { const fetchCrateData = async () => { @@ -45,100 +46,12 @@ const CratePage = () => { if (error) return
Error: {error}
; // console.log('dependencyyyyyyyyyyyyyyy', results?.data); return ( -
+ <> {/* Existing header and search */} -
-
-
- -
- open - / - source - / - insights -
- -
- {crateName} -
- - {/* 这里可以添加版本选择的下拉菜单 */} -
-
-
-
- setSearchQuery(e.target.value)} // 更新搜索内容 - /> - - - -
-
- - {/* 导航栏 */} - -
+ -
); }; diff --git a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx index ab776fb..84d0386 100644 --- a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx +++ b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx @@ -1,11 +1,13 @@ +//Dependents页面 "use client"; import React, { useEffect, useState } from 'react'; -import Link from 'next/link'; + 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); @@ -13,8 +15,7 @@ const CratePage = () => { const params = useParams(); - const crateName = params.name; // 从 URL 中获取 crate_name 参数 - const version = params.version; // 从 URL 中获取 version 参数 + useEffect(() => { const fetchCrateData = async () => { @@ -43,95 +44,10 @@ const CratePage = () => { if (loading) return
Loading...
; if (error) return
Error: {error}
; - //console.log('dependencyyyyyyyyyyyyyyy', results?.data); + return (
- {/* Existing header and search */} -
-
-
- -
- open - / - source - / - insights -
- -
- {crateName} -
- - {/* 这里可以添加版本选择的下拉菜单 */} -
-
-
-
- - -
-
- - {/* 导航栏 */} - - -
- diff --git a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx index e3d2c56..1695a6e 100644 --- a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx +++ b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx @@ -1,28 +1,30 @@ +//Overview页面 "use client"; import React, { useEffect, useState } from 'react'; import Link from 'next/link'; //import { useSearchParams } from 'next/navigation'; import { cratesInfo } from '@/app/lib/all_interface'; -import { useParams } from 'next/navigation' +import { useParams } from 'next/navigation'; + + + const CratePage = () => { - const [isOpen, setIsOpen] = useState(false); // 状态管理下拉菜单的显示 + const params = useParams(); - //console.log("params:", params); - const [searchQuery, setSearchQuery] = useState(''); + + + + const [results, setResults] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // const searchParams = useSearchParams(); - // const crateName = searchParams.get('crate_name'); // 从 URL 中获取 crate_name 参数 - // const crateVersion = searchParams.get('version'); // 从 URL 中获取 version 参数 - const crateName = params.name; // 从 URL 中获取 crate_name 参数 - const crateVersion = params.version; // 从 URL 中获取 version 参数 + useEffect(() => { const fetchCrateData = async () => { try { @@ -49,15 +51,6 @@ const CratePage = () => { fetchCrateData(); // 调用函数来获取数据 }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 name 或 version 改变时重新获取数据 - - const toggleDropdown = () => { - setIsOpen(prev => !prev); - }; - - const closeDropdown = () => { - setIsOpen(false); - }; - // 渲染部分 if (loading) return

Loading...

; if (error) return

Error: {error}

; @@ -67,121 +60,6 @@ const CratePage = () => { return (
- {/* Existing header and search */} -
-
-
- -
- open - / - source - / - insights -
- -
- {crateName} - {/*版本列表*/} -
- - {isOpen && ( -
- {/* 遮罩层 */} -
-
-
    - {results?.versions.map((version, index) => ( - -
  • - {version} -
  • - - ))} -
-
-
- )} -
- - -
-
-
- setSearchQuery(e.target.value)} // 更新搜索内容 - /> - - - -
-
- - {/* 导航栏 */} - -
- {/* cve */}
diff --git a/app/homepage/[nsfront]/[nsbehind]/layout.tsx b/app/homepage/[nsfront]/[nsbehind]/layout.tsx new file mode 100644 index 0000000..4b5052a --- /dev/null +++ b/app/homepage/[nsfront]/[nsbehind]/layout.tsx @@ -0,0 +1,24 @@ +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/homepage/layout.tsx b/app/homepage/layout.tsx index 30e72fa..d80a618 100644 --- a/app/homepage/layout.tsx +++ b/app/homepage/layout.tsx @@ -1,5 +1,5 @@ import '@/app/ui/global.css'; - +import { HeaderProvider } from '../context/CrateContext'; export const metadata = { title: 'cratespro', @@ -12,8 +12,12 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - - {children} + + + + {children} + + ) } diff --git a/components/HeaderWithSearch.tsx b/components/HeaderWithSearch.tsx new file mode 100644 index 0000000..bcbe1d7 --- /dev/null +++ b/components/HeaderWithSearch.tsx @@ -0,0 +1,160 @@ +// components/Header.js +'use client'; +import React, { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { useHeaderContext } from '../app/context/CrateContext'; +import { useParams } from 'next/navigation'; +// import { cratesInfo } from '@/app/lib/all_interface'; + +const Header = () => { + const params = useParams(); + + 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 + // 定义导航项数据 + const navItems = [ + { name: 'Overview', path: '' }, + { name: 'Dependencies', path: '/dependencies' }, + { name: 'Dependents', path: '/dependents' }, + ]; + + + // 使用 useEffect 从 API 获取数据 + useEffect(() => { + const fetchData = async () => { + // 如果 crateData.results 为空,说明数据还未加载 + if (!crateData.results) { + const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}`); + const data = await response.json(); + console.log('dataaaaaaaaaaaaaa:', data); + setCrateData({ + crateName: data.crate_name, + crateVersion: params.version, + results: data, + }); + + } + }; + + fetchData(); + }, [params.nsfront, params.nsbehind, params.name, params.version, setCrateData, crateData.crateVersion, crateData.results]); // 添加 crateData 作为依赖项 + + const toggleDropdown = () => { + setIsOpen((prev) => !prev); + }; + + const closeDropdown = () => { + setIsOpen(false); + + }; + + + + + + 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} + +
  • + + ))} +
+
+
+ )} +
+
+
+
+ setSearchQuery(e.target.value)} + /> + + + +
+
+ +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 8ed419e..2264c4b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -3,6 +3,7 @@ // next.config.mjs export default { + async redirects() { return [ { @@ -13,3 +14,11 @@ export default { ]; }, }; +// /** @type {import('next').NextConfig} */ +// const nextConfig = { +// experimental: { +// ppr: 'incremental', +// }, +// } + +// module.exports = nextConfig \ No newline at end of file From 71a4e37191f1f7c8a29e7f73464c49ef5d8194c0 Mon Sep 17 00:00:00 2001 From: mapan-nju <2246839805@qq.com> Date: Tue, 10 Dec 2024 18:54:57 +0800 Subject: [PATCH 2/4] solved some bugs --- Dockerfile | 29 ++++ .../[nsbehind]/[name]/[version]/page.tsx | 152 +++++++----------- app/homepage/page.tsx | 2 +- app/homepage/search/page.tsx | 57 ++++--- app/homepage/styles.css | 5 + app/lib/all_interface.ts | 11 +- app/newpage/layout.tsx | 2 +- app/programs/layout.tsx | 2 +- app/ui/global.css | 5 + next.config.mjs | 17 +- 10 files changed, 144 insertions(+), 138 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b0a845f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM node:22.12.0-alpine3.19 AS builder +WORKDIR /app + +COPY package*.json ./ + +RUN npm ci + +COPY . . + +RUN npm run build + + + + + +# 使用轻量级的 Node.js 镜像 +FROM node:22.12.0-alpine3.19 AS runner + +# 设置工作目录 +WORKDIR /app + +# 复制构建好的文件 +COPY --from=builder /app ./ + +# 暴露应用运行的端口 +EXPOSE 3000 + +# 启动应用 +CMD ["npm", "start"] \ 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 1695a6e..ce980c4 100644 --- a/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx +++ b/app/homepage/[nsfront]/[nsbehind]/[name]/[version]/page.tsx @@ -1,78 +1,55 @@ -//Overview页面 +// Overview页面 "use client"; import React, { useEffect, useState } from 'react'; import Link from 'next/link'; -//import { useSearchParams } from 'next/navigation'; 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(); - console.log('overviewwwwwwwwwwwwww:', data); - - setResults(data); // 设置获取的数据 - + setResults(data); } catch (error) { - console.log('Error fetching data:', error); + console.error('Error fetching data:', error); setError('An error occurred'); } finally { - setLoading(false); // 完成加载 + setLoading(false); } }; + fetchCrateData(); + }, [params.name, params.version, params.nsfront, params.nsbehind]); - fetchCrateData(); // 调用函数来获取数据 - }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 name 或 version 改变时重新获取数据 - - // 渲染部分 if (loading) return

Loading...

; if (error) return

Error: {error}

; - - - return (
- {/* cve */}
{/* Security Advisories */}

Security Advisories

-

cve: {results ? JSON.stringify(results.cves) : 'No results available'}

+

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

{/* Licenses */}

Licenses

- LICENSES: MIT + LICENSES: {results ? results.license : 'No results available'}
DEPENDENCY LICENSES: @@ -80,7 +57,6 @@ const CratePage = () => {
  • Apache-2.0 OR MIT (116)
  • MIT (27)
  • MIT OR Uniclicense (7)
  • - {/* Add more dependency licenses */}
    @@ -88,17 +64,13 @@ const CratePage = () => {

    Dependencies

    - Direct: {results ? JSON.stringify(results.dependencies.direct) : 'No results available'} + Direct: {results ? JSON.stringify(results.dependencies.direct) : 'No cves detected.'}
    Indirect: {results ? JSON.stringify(results.dependencies.indirect) : 'No results available'}
    - -
    - View all dependencies -
    + +
    View all dependencies
    {/* Dependents */} @@ -110,66 +82,64 @@ const CratePage = () => {
    Indirect: {results ? JSON.stringify(results.dependents.indirect) : 'No results available'}
    - -
    - View all dependencies -
    + +
    View all dependents
    -
    -

    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-Review - 10/10 -
    -
    - Maintained - 10/10 -
    -
    - CI/Best-Practices - 0/10 -
    -
    - License - 10/10 -
    -
    - Dangerous-Workflow - 10/10 -
    -
    - Security-Policy - 10/10 + + {/* 新增的块: doc_url 和 github_url */} +
    +
    +

    Documentation & GitHub Links

    + -
    - Token-Permissions - 10/10 + -
    - Binary-Artifacts - 10/10 +
    + + {/* 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.
    -
    - Pinned-Dependencies - 0/10 +
    +
    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
    -
    +
    ); }; diff --git a/app/homepage/page.tsx b/app/homepage/page.tsx index b209110..90c562a 100644 --- a/app/homepage/page.tsx +++ b/app/homepage/page.tsx @@ -84,7 +84,7 @@ const HomePage: React.FC = () => { {/* 一些介绍 */} -
    +

    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.

    diff --git a/app/homepage/search/page.tsx b/app/homepage/search/page.tsx index bb3bc55..0696f43 100644 --- a/app/homepage/search/page.tsx +++ b/app/homepage/search/page.tsx @@ -8,6 +8,7 @@ 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'); @@ -18,6 +19,7 @@ const Search = () => { }, [name, currentPage]); // 当 name 或 currentPage 改变时重新运行 const fetchResults = async (query: string, page: number) => { + setLoading(true); // 开始加载数据 try { const response = await fetch('/api/search', { method: 'POST', @@ -40,6 +42,8 @@ const Search = () => { setResults(data); // 假设返回的数据data字段 } catch (error) { console.error('Error fetching data:', error); + } finally { + setLoading(false); // 数据加载完成 } }; @@ -57,12 +61,14 @@ const Search = () => { console.log("results:", results?.data.items); return ( -
    +
    -
    +
    - {results ? ( - results.data.total_page > 0 ? ( + {loading ? ( +

    Loading...

    + ) : results ? ( + results.data.total_page > 0 && results.data.items.length > 0 ? ( results.data.items.map((item, index) => ( {

    Loading...

    )}
    +
    - {/* 显示当前页数和总页数 */} + {/* 当前页数在按钮上方 */} +
    {results && ( -
    +

    - 当前页: {currentPage} / 总页数: {results.data.total_page} + Current page: {currentPage} / Total page: {results.data.total_page}

    )} - -
    - - -
    + {results && results.data.total_page > 0 && ( +
    + + +
    + )}
    ); diff --git a/app/homepage/styles.css b/app/homepage/styles.css index e69de29..7a30764 100644 --- a/app/homepage/styles.css +++ b/app/homepage/styles.css @@ -0,0 +1,5 @@ +/* styles.css */ +.custom-margin-left { + margin-left: 100px; + /* 自定义的左边距 */ +} \ No newline at end of file diff --git a/app/lib/all_interface.ts b/app/lib/all_interface.ts index d6eb8cd..b6c6891 100644 --- a/app/lib/all_interface.ts +++ b/app/lib/all_interface.ts @@ -31,13 +31,10 @@ export interface cratesInfo { "indirect": number }, - "cves": [ - { - "cve_id": string, - "url": string, - "description": string - } - ], + "cves": string[], + "license": string, + "github_url": string, + "doc_url": string, "versions": string[], } diff --git a/app/newpage/layout.tsx b/app/newpage/layout.tsx index 30e72fa..b06395b 100644 --- a/app/newpage/layout.tsx +++ b/app/newpage/layout.tsx @@ -6,7 +6,7 @@ export const metadata = { description: 'Generated by Next.js', } -export default function RootLayout({ +export default function Layout({ children, }: { children: React.ReactNode diff --git a/app/programs/layout.tsx b/app/programs/layout.tsx index 8aa80cf..a146b7b 100644 --- a/app/programs/layout.tsx +++ b/app/programs/layout.tsx @@ -2,7 +2,7 @@ import '@/app/ui/global.css'; import { inter } from '@/app/ui/fonts'; import SideNav from '@/app/ui/programs/sidenav'; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default function Layout({ children }: { children: React.ReactNode }) { return ( diff --git a/app/ui/global.css b/app/ui/global.css index c06d6d6..8a291b5 100644 --- a/app/ui/global.css +++ b/app/ui/global.css @@ -16,3 +16,8 @@ input[type='number']::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } + +.custom-margin-left { + margin-left: 500px; + /* 自定义的左边距 */ +} \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 2264c4b..32c05ad 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,24 +1,15 @@ /** @type {import('next').NextConfig} */ // next.config.mjs - export default { - async redirects() { return [ { source: '/', - destination: '/programs', - permanent: true, // 301 redirection + //destination: '/programs', + destination: '/homepage', + permanent: true, }, ]; }, -}; -// /** @type {import('next').NextConfig} */ -// const nextConfig = { -// experimental: { -// ppr: 'incremental', -// }, -// } - -// module.exports = nextConfig \ No newline at end of file +}; \ No newline at end of file From 888c4532546cd60ab5891ea928041da6c07b13cf Mon Sep 17 00:00:00 2001 From: mapan-nju <2246839805@qq.com> Date: Sat, 28 Dec 2024 17:23:27 +0800 Subject: [PATCH 3/4] dependencies graph --- .../[version]/dependencies/graph/page.tsx | 60 ++++ .../[name]/[version]/dependencies/page.tsx | 67 ++++ .../[name]/[version]/dependents/page.tsx | 58 ++++ .../[nsbehind]/[name]/[version]/page.tsx | 311 ++++++++++++++++++ app/[nsfront]/[nsbehind]/layout.tsx | 26 ++ .../[version]/dependencies/route.tsx | 5 +- .../[cratename]/[version]/route.tsx | 2 +- .../[cratename]/[version]/direct/route.tsx | 23 ++ app/context/navContext.tsx | 19 ++ .../[nsbehind]/[name]/[version]/page.tsx | 2 +- app/layout.tsx | 23 ++ app/lib/all_interface.ts | 19 +- app/newpage/layout.tsx | 19 -- app/newpage/page.tsx | 95 ------ app/newpage/search/page.tsx | 74 ----- app/newpage/styles.css | 0 app/page.tsx | 133 ++++++++ app/search/layout.tsx | 22 ++ app/search/page.tsx | 132 ++++++++ app/styles.css | 5 + components/DependencyGraph.tsx | 62 ++-- components/DependencyTable.tsx | 17 +- components/DependentTable.tsx | 2 +- components/HeaderWithSearch.tsx | 242 ++++++++------ components/NewHeader.tsx | 49 ++- next.config.mjs | 20 +- public/rust.svg | 54 +++ 27 files changed, 1190 insertions(+), 351 deletions(-) create mode 100644 app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx create mode 100644 app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx create mode 100644 app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx create mode 100644 app/[nsfront]/[nsbehind]/[name]/[version]/page.tsx create mode 100644 app/[nsfront]/[nsbehind]/layout.tsx create mode 100644 app/api/graph/[cratename]/[version]/direct/route.tsx create mode 100644 app/context/navContext.tsx create mode 100644 app/layout.tsx delete mode 100644 app/newpage/layout.tsx delete mode 100644 app/newpage/page.tsx delete mode 100644 app/newpage/search/page.tsx delete mode 100644 app/newpage/styles.css create mode 100644 app/page.tsx create mode 100644 app/search/layout.tsx create mode 100644 app/search/page.tsx create mode 100644 app/styles.css create mode 100644 public/rust.svg 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 7d2f549..f7c3afd 100644 --- a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx +++ b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/dependencies/route.tsx @@ -5,14 +5,13 @@ export async function GET(req: NextRequest, props: { params: Params }) { try { const params = await props.params const { nsfront, nsbehind, cratename, version } = params; - - const externalApiUrl = `http://210.28.134.203:6888/api/crates/${nsfront}/${nsbehind}/${cratename}/${version}/dependencies`; // 替换为你的外部 API URL + const externalApiUrl = `http://210.28.134.203:6888/api/crates/${nsfront}/${nsbehind}/${cratename}/${version}/dependencies`; 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 753055e..708f8af 100644 --- a/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx +++ b/app/api/crates/[nsfront]/[nsbehind]/[cratename]/[version]/route.tsx @@ -12,7 +12,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 @@ + + + + + From 951dd80e87e20cbc31d4cdde3be5c1d3c12edce1 Mon Sep 17 00:00:00 2001 From: mapan-nju <2246839805@qq.com> Date: Fri, 3 Jan 2025 21:47:48 +0800 Subject: [PATCH 4/4] version-page --- .env | 4 +- .../[version]/dependencies/graph/page.tsx | 3 +- .../[name]/[version]/versions/page.tsx | 58 ++++++++++ app/api/search/route.tsx | 1 - components/DependencyGraph.tsx | 29 ++++- components/HeaderWithSearch.tsx | 1 + components/VersionsTable.tsx | 108 ++++++++++++++++++ 7 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx create mode 100644 components/VersionsTable.tsx diff --git a/.env b/.env index f45a4bc..cf6ed3e 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ # add CRATES_PRO_HOST, CRATES_PRO_INTERNAL_HOST to your enviroment for development -CRATES_PRO_HOST=$CRATES_PRO_HOST -CRATES_PRO_INTERNAL_HOST=$CRATES_PRO_INTERNAL_HOST +CRATES_PRO_HOST=http://210.28.134.203:6888 +CRATES_PRO_INTERNAL_HOST=http://210.28.134.203:6888 SECRET_KEY=$YOUR_SECRET_KEY #(not prefixed with NEXT_PUBLIC_ ) \ No newline at end of file diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx index cae7841..4b9e5f2 100644 --- a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/graph/page.tsx @@ -48,7 +48,8 @@ const CratePage = () => { if (error) return
    Error: {error}
    ; return ( -
    +
    + diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx new file mode 100644 index 0000000..bfacd59 --- /dev/null +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx @@ -0,0 +1,58 @@ +//Dependents页面 +"use client"; +import React, { useEffect, useState } from 'react'; + +import VersionsTable from '@/components/VersionsTable'; +import { cratesInfo } 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}`); + + 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/api/search/route.tsx b/app/api/search/route.tsx index eb7c4fb..be6fea1 100644 --- a/app/api/search/route.tsx +++ b/app/api/search/route.tsx @@ -2,7 +2,6 @@ import { NextResponse } from 'next/server'; export async function POST(request: Request) { const endpoint = process.env.CRATES_PRO_INTERNAL_HOST; - const apiUrl = `${endpoint}/api/search`; const requestBody = await request.json(); console.log("Request Body:", requestBody); diff --git a/components/DependencyGraph.tsx b/components/DependencyGraph.tsx index fee2b92..a265e06 100644 --- a/components/DependencyGraph.tsx +++ b/components/DependencyGraph.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; import * as d3 from 'd3'; -// import { useParams } from 'next/navigation'; export interface GraphDependency { crate_name: string; @@ -26,7 +25,6 @@ export interface DependencyGraphProps { const DependencyGraph: React.FC = ({ crateName, currentVersion }) => { const [graphDependencies, setGraphDependencies] = useState(null); const d3Container = useRef(null); - // const params = useParams(); useEffect(() => { async function fetchDependencyTree(name: string, version: string, visited: Set): Promise { @@ -112,10 +110,11 @@ const DependencyGraph: React.FC = ({ crateName, currentVer const nodes = Array.from(nodesMap.values()); + // Remove the forceCenter to allow free movement of nodes const simulation = d3.forceSimulation(nodes) .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)) + // Remove center force to prevent auto centering .force('collide', d3.forceCollide().radius(50)); const link = svg.append('g') @@ -191,11 +190,31 @@ const DependencyGraph: React.FC = ({ crateName, currentVer d.fy = null; } + // Zoom functionality + // const zoom = d3.zoom() + // .scaleExtent([0.1, 10]) // Minimum and maximum zoom scales + // .on('zoom', (event) => { + // svg.selectAll('g') + // .attr('transform', event.transform); + // }); + + // svg.call(zoom as any); + + // }, [graphDependencies]); + const zoom = d3.zoom() + .scaleExtent([0.1, 10]) // Minimum and maximum zoom scales + .on('zoom', (event) => { + svg.selectAll('g') + .attr('transform', event.transform); + }); + + svg.call(zoom); // 不再使用 any + }, [graphDependencies]); return ( -
    -
    +
    +
    ); }; diff --git a/components/HeaderWithSearch.tsx b/components/HeaderWithSearch.tsx index 9070e76..057fbe5 100644 --- a/components/HeaderWithSearch.tsx +++ b/components/HeaderWithSearch.tsx @@ -27,6 +27,7 @@ const Header = () => { { name: 'Overview', path: '' }, { name: 'Dependencies', path: '/dependencies' }, { name: 'Dependents', path: '/dependents' }, + { name: 'Versions', path: '/versions' }, ]; diff --git a/components/VersionsTable.tsx b/components/VersionsTable.tsx new file mode 100644 index 0000000..b48af74 --- /dev/null +++ b/components/VersionsTable.tsx @@ -0,0 +1,108 @@ +'use client'; +import React, { useEffect, useState, useCallback } from 'react'; +import { Table } from 'antd'; +import { useParams } from 'next/navigation'; + +// 假设后端接口返回的类型 +interface VersionInfo { + version: string; + publishDay: string; + dependentsNumber: number; +} + +interface VersionsTableProps { + data: string[] | undefined; // 传入的版本号数组 +} + +const VersionsTable: React.FC = ({ data }) => { + const [versionsData, setVersionsData] = useState([]); + const [loading, setLoading] = useState(false); + const params = useParams(); + + // 获取版本发布日的函数 + const fetchPublishDay = useCallback(async (version: string) => { + try { + const response = await fetch(`/api/publish-day/${version}`); + if (!response.ok) { + throw new Error('Failed to fetch publish day'); + } + const data = await response.json(); + return data.publishDay || 'N/A'; // 如果没有返回值,则默认返回 'N/A' + } catch (error) { + console.error('Error fetching publish day:', error); + return 'N/A'; // 请求失败时返回默认值 + } + }, []); + + // 获取版本依赖数的函数 + const fetchDependentsNumber = useCallback(async (version: string) => { + try { + const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${version}/dependents`); + if (!response.ok) { + throw new Error('Failed to fetch dependents number'); + } + const data = await response.json(); + return data.direct_count + data.indirect_count || 0; // 如果没有返回值,则默认返回 0 + } catch (error) { + console.error('Error fetching dependents number:', error); + return 0; // 请求失败时返回默认值 + } + }, [params.nsfront, params.nsbehind, params.name]); + + // 请求版本的发布日和依赖数 + const fetchVersionDetails = useCallback(async (version: string) => { + const publishDay = await fetchPublishDay(version); + const dependentsNumber = await fetchDependentsNumber(version); + return { version, publishDay, dependentsNumber }; + }, [fetchPublishDay, fetchDependentsNumber]); + + useEffect(() => { + const fetchData = async () => { + if (data && data.length > 0) { + setLoading(true); + const versionDetails = await Promise.all( + data.map(async (version) => { + return await fetchVersionDetails(version); + }) + ); + setVersionsData(versionDetails); + setLoading(false); + } + }; + + fetchData(); + }, [data, fetchVersionDetails]); + + const columns = [ + { + title: 'Version', + dataIndex: 'version', + key: 'version', + render: (text: string) => {text}, + }, + { + title: 'Publish Day', + dataIndex: 'publishDay', + key: 'publishDay', + render: (text: string) => {text}, + }, + { + title: 'Dependents Number', + dataIndex: 'dependentsNumber', + key: 'dependentsNumber', + render: (text: number) => {text}, + }, + ]; + + return ( +
    + ); +}; + +export default VersionsTable; \ No newline at end of file