Skip to content

Commit cc750ac

Browse files
committed
mirchecker in frontend
1 parent 5ef3495 commit cc750ac

File tree

4 files changed

+328
-3
lines changed

4 files changed

+328
-3
lines changed

app/[nsfront]/[nsbehind]/[name]/[version]/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ export default function Layout({
1414

1515
// 判断当前页面是否为SenseLeak页面
1616
const isSenseLeakPage = pathname.includes('/senseleak');
17-
17+
const isMircheckerPage = pathname.includes('/mirchecker');
1818
return (
1919
<div className="mb-0">
2020
{/* 仅在非SenseLeak页面显示导航栏 */}
21-
{!isSenseLeakPage && (
21+
{!isSenseLeakPage && !isMircheckerPage && (
2222
<CrateNav
2323
nsfront={params.nsfront as string}
2424
nsbehind={params.nsbehind as string}
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
'use client';
2+
import React, { useEffect, useState, useRef } from 'react';
3+
import Link from 'next/link';
4+
import { useParams } from 'next/navigation';
5+
import Image from 'next/image';
6+
7+
interface MircheckerRes {
8+
run_state: boolean;
9+
exist: boolean;
10+
res: string;
11+
}
12+
13+
const MircheckerPage = () => {
14+
const params = useParams();
15+
const [versionsList, setVersionsList] = useState<string[]>([]);
16+
const [mircheckerData, setMircheckerData] = useState<MircheckerRes | null>(null);
17+
const [loading, setLoading] = useState(true);
18+
const [error, setError] = useState<string | null>(null);
19+
const [searchTerm, setSearchTerm] = useState('');
20+
21+
// 添加滚动条状态和引用
22+
const [isScrolling, setIsScrolling] = useState(false);
23+
const scrollTimeoutRef = useRef<NodeJS.Timeout>();
24+
const containerRef = useRef<HTMLDivElement>(null);
25+
26+
// 添加滚动位置状态
27+
const [scrollPosition, setScrollPosition] = useState(0);
28+
29+
// 获取版本列表和Mirchecker数据
30+
useEffect(() => {
31+
const fetchData = async () => {
32+
try {
33+
// 获取版本列表
34+
const versionsResponse = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}`);
35+
if (!versionsResponse.ok) {
36+
throw new Error(`HTTP error! status: ${versionsResponse.status}`);
37+
}
38+
const versionsData = await versionsResponse.json();
39+
setVersionsList(versionsData.versions || []);
40+
41+
// 获取 Mirchecker 数据
42+
const mircheckerResponse = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/mirchecker`);
43+
if (!mircheckerResponse.ok) {
44+
throw new Error(`HTTP error! status: ${mircheckerResponse.status}`);
45+
}
46+
const mircheckerData = await mircheckerResponse.json();
47+
setMircheckerData(mircheckerData);
48+
49+
setLoading(false);
50+
} catch (error) {
51+
console.error('Error fetching data:', error);
52+
setError('获取数据时出错');
53+
setLoading(false);
54+
}
55+
};
56+
57+
fetchData();
58+
}, [params.nsfront, params.nsbehind, params.name, params.version]);
59+
60+
// 当前选中的版本
61+
const currentVersion = params.version as string;
62+
63+
// 添加过滤后的版本列表计算
64+
const filteredVersions = versionsList.filter(version =>
65+
version.toLowerCase().includes(searchTerm.toLowerCase())
66+
);
67+
68+
// 处理滚动事件
69+
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
70+
setScrollPosition(e.currentTarget.scrollTop);
71+
setIsScrolling(true);
72+
73+
if (scrollTimeoutRef.current) {
74+
clearTimeout(scrollTimeoutRef.current);
75+
}
76+
77+
scrollTimeoutRef.current = setTimeout(() => {
78+
setIsScrolling(false);
79+
}, 1500);
80+
};
81+
82+
// 处理搜索输入
83+
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
84+
setSearchTerm(e.target.value);
85+
if (containerRef.current) {
86+
containerRef.current.scrollTop = 0;
87+
setScrollPosition(0);
88+
}
89+
};
90+
91+
// 清理定时器
92+
useEffect(() => {
93+
return () => {
94+
if (scrollTimeoutRef.current) {
95+
clearTimeout(scrollTimeoutRef.current);
96+
}
97+
};
98+
}, []);
99+
100+
if (loading) return (
101+
<div className="min-h-screen flex items-center justify-center">
102+
<p className="text-lg">加载中...</p>
103+
</div>
104+
);
105+
106+
if (error) return (
107+
<div className="min-h-screen flex items-center justify-center">
108+
<p className="text-red-500 text-lg">错误: {error}</p>
109+
</div>
110+
);
111+
112+
return (
113+
<div className="min-h-screen bg-[#F9F9F9] flex">
114+
{/* 左侧边栏 - 版本列表 */}
115+
<div className="w-[300px] h-screen sticky top-0 flex-shrink-0 bg-white shadow-[0_0_12px_0_#2b58dd17] backdrop-blur-[200px] flex flex-col">
116+
{/* Logo 和搜索框区域 - 固定不动 */}
117+
<div className="p-4 space-y-4 flex-shrink-0">
118+
{/* Logo */}
119+
<div className="flex justify-center">
120+
<Image
121+
src="/images/homepage/logo-top.png"
122+
alt="CratesPro Logo"
123+
width={150}
124+
height={75}
125+
className="mb-2"
126+
/>
127+
</div>
128+
129+
{/* 搜索框 */}
130+
<div className="relative">
131+
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
132+
<Image
133+
src="/images/homepage/senseleak-search.png"
134+
alt="Search Icon"
135+
width={16}
136+
height={16}
137+
/>
138+
</div>
139+
<input
140+
type="text"
141+
placeholder="Search Files"
142+
value={searchTerm}
143+
onChange={handleSearch}
144+
className="w-[250px] h-[36px] flex-shrink-0 pl-10 pr-4 rounded-[18px] border border-[#333333] bg-white shadow-[0_0_12px_0_#2b58dd17] text-[14px] font-['HarmonyOS_Sans_SC'] text-[#999999] focus:outline-none focus:ring-1 focus:ring-[#4B68FF] focus:border-[#4B68FF]"
145+
/>
146+
</div>
147+
</div>
148+
149+
{/* 版本列表区域 - 可滚动 */}
150+
<div
151+
ref={containerRef}
152+
className="flex-1 overflow-y-auto relative [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']"
153+
onScroll={handleScroll}
154+
>
155+
{/* 自定义滚动条 */}
156+
<div
157+
className={`absolute right-1 w-[4px] transition-opacity duration-150 ${isScrolling || containerRef.current?.scrollTop !== 0 ? 'opacity-100' : 'opacity-0'}`}
158+
style={{
159+
height: '60px',
160+
top: containerRef.current
161+
? `${Math.min(
162+
(scrollPosition /
163+
(containerRef.current.scrollHeight - containerRef.current.clientHeight)) *
164+
(containerRef.current.clientHeight - 60),
165+
containerRef.current.clientHeight - 60
166+
)}px`
167+
: '0',
168+
background: '#4B68FF',
169+
borderRadius: '3px',
170+
pointerEvents: 'none',
171+
transition: 'top 0.1s linear, opacity 0.15s ease-in-out',
172+
}}
173+
/>
174+
175+
<div className="p-4">
176+
<div className="space-y-2">
177+
{filteredVersions.map((version, index) => (
178+
<Link
179+
key={index}
180+
href={`/${params.nsfront}/${params.nsbehind}/${params.name}/${version}/mirchecker`}
181+
>
182+
<div className={`transition-colors cursor-pointer ${version === currentVersion
183+
? 'bg-[#4b68ff] w-[278px] h-[37px] flex items-center text-white rounded-l-full rounded-r-none'
184+
: 'hover:bg-[#F5F7FF] text-[#333333] p-3'
185+
}`}>
186+
<div className={`flex items-center ${version === currentVersion ? 'pl-3' : ''}`}>
187+
<div className="mr-2">
188+
<Image
189+
src="/images/homepage/senseleak-file.png"
190+
alt="Version Icon"
191+
width={16}
192+
height={16}
193+
className={version === currentVersion ? 'brightness-0 invert' : ''}
194+
/>
195+
</div>
196+
<span className="font-['HarmonyOS_Sans_SC'] text-[16px]">Version-{version}</span>
197+
</div>
198+
</div>
199+
</Link>
200+
))}
201+
{filteredVersions.length === 0 && (
202+
<div className="text-center py-4 text-[#999999] font-['HarmonyOS_Sans_SC']">
203+
没有找到匹配的版本
204+
</div>
205+
)}
206+
</div>
207+
</div>
208+
</div>
209+
</div>
210+
211+
{/* 右侧内容区域 - Mirchecker数据 */}
212+
<div className="flex-1 py-8 px-12">
213+
<div className="max-w-[1500px] mx-auto">
214+
<div className="mb-6 flex items-center gap-3">
215+
<div className="w-[4px] h-[24px] flex-shrink-0 rounded-[2px] bg-[#4B68FF]"></div>
216+
<h1 className="text-[24px] font-bold text-[#333333] tracking-[0.96px] font-['HarmonyOS_Sans_SC']">
217+
Mirchecker Analysis: {params.name}/{params.version}
218+
</h1>
219+
</div>
220+
221+
<div className="bg-white rounded-2xl p-6 shadow-[0_0_12px_0_rgba(43,88,221,0.09)]">
222+
{mircheckerData ? (
223+
mircheckerData.run_state ? (
224+
mircheckerData.exist ? (
225+
<pre className="whitespace-pre-wrap font-['HarmonyOS_Sans_SC'] text-[14px] leading-relaxed text-[#333333] p-4 bg-[#F8F9FC] rounded-lg overflow-x-auto">
226+
{mircheckerData.res}
227+
</pre>
228+
) : (
229+
<div className="flex flex-col items-center p-8">
230+
<Image
231+
src="/images/homepage/miss.png"
232+
alt="No data"
233+
width={140}
234+
height={140}
235+
className="mb-4"
236+
/>
237+
<p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">
238+
no result
239+
</p>
240+
</div>
241+
)
242+
) : (
243+
<div className="flex flex-col items-center p-8">
244+
<Image
245+
src="/images/homepage/miss.png"
246+
alt="Run failed"
247+
width={140}
248+
height={140}
249+
className="mb-4"
250+
/>
251+
<p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">
252+
run failed
253+
</p>
254+
</div>
255+
)
256+
) : (
257+
<div className="flex flex-col items-center p-8">
258+
<Image
259+
src="/images/homepage/miss.png"
260+
alt="No data"
261+
width={140}
262+
height={140}
263+
className="mb-4"
264+
/>
265+
<p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">
266+
加载中...
267+
</p>
268+
</div>
269+
)}
270+
</div>
271+
</div>
272+
</div>
273+
</div>
274+
);
275+
};
276+
277+
export default MircheckerPage;

app/[nsfront]/[nsbehind]/[name]/[version]/page.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ const CratePage = () => {
580580
</div>
581581
<div className="space-y-4">
582582
<div>
583-
{/* <h3 className="text-[14px] text-[#333333] font-['HarmonyOS_Sans_SC'] font-normal mb-2">Documentation URL</h3> */}
584583
<a
585584
href={basePath + '/senseleak'}
586585
className="text-[#4B68FF] text-[14px] font-['HarmonyOS_Sans_SC'] font-normal hover:underline break-all"
@@ -592,6 +591,33 @@ const CratePage = () => {
592591
</div>
593592
</div>
594593
</div>
594+
{/* Mirchecker 部分 - 新增 */}
595+
<div>
596+
<div className="flex items-center gap-3 mb-6">
597+
<Image
598+
src="/images/homepage/1.png"
599+
alt="icon"
600+
width={16}
601+
height={16}
602+
className="flex-shrink-0 rounded-[16.05px] border-[1.6px] border-[#333333]"
603+
/>
604+
<h2 className="text-[18px] font-bold text-[#333333] tracking-[0.72px] font-['HarmonyOS_Sans_SC']">
605+
Mirchecker
606+
</h2>
607+
</div>
608+
<div className="space-y-4">
609+
<div>
610+
<a
611+
href={basePath + '/mirchecker'}
612+
className="text-[#4B68FF] text-[14px] font-['HarmonyOS_Sans_SC'] font-normal hover:underline break-all"
613+
target="_blank"
614+
rel="noopener noreferrer"
615+
>
616+
{basePath + '/mirchecker' || 'No results available'}
617+
</a>
618+
</div>
619+
</div>
620+
</div>
595621
{/* OpenSSF Scorecard */}
596622
<div>
597623
<div className="flex items-center gap-3 mb-4">
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
type Params = Promise<{ nsfront: string, nsbehind: string, cratename: string, version: string }>
3+
export async function GET(req: NextRequest, props: { params: Params }) {
4+
try {
5+
const params = await props.params
6+
const { nsfront, nsbehind, cratename, version } = params;
7+
const endpoint = process.env.CRATES_PRO_INTERNAL_HOST;
8+
9+
const externalApiUrl = `${endpoint}/api/crates/${nsfront}/${nsbehind}/${cratename}/${version}/mirchecker`;
10+
const externalRes = await fetch(externalApiUrl);
11+
if (!externalRes.ok) {
12+
throw new Error('Failed to fetch external data');
13+
}
14+
const externalData = await externalRes.json();
15+
console.log('External API Response:', externalData);
16+
return NextResponse.json(externalData);
17+
} catch (error) {
18+
console.error('Error:', error);
19+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
20+
21+
}
22+
}

0 commit comments

Comments
 (0)