feat: Implement retro-futuristic UI design across frontend pages and components.
This commit is contained in:
parent
7315770a17
commit
53c8c27ee7
|
|
@ -4,48 +4,65 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 210 20% 98%;
|
||||||
--foreground: 222.2 84% 4.9%;
|
--foreground: 220 10% 10%;
|
||||||
|
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 222.2 84% 4.9%;
|
--card-foreground: 220 10% 10%;
|
||||||
|
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 222.2 84% 4.9%;
|
--popover-foreground: 220 10% 10%;
|
||||||
--primary: 222.2 47.4% 11.2%;
|
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary: 220 100% 50%;
|
||||||
--secondary: 210 40% 96.1%;
|
--primary-foreground: 0 0% 100%;
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
||||||
--muted: 210 40% 96.1%;
|
--secondary: 15 100% 50%;
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
--secondary-foreground: 0 0% 100%;
|
||||||
--accent: 210 40% 96.1%;
|
|
||||||
--accent-foreground: 222.2 47.4% 11.2%;
|
--muted: 210 20% 90%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--muted-foreground: 220 10% 40%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
|
||||||
--border: 214.3 31.8% 91.4%;
|
--accent: 180 100% 40%;
|
||||||
--input: 214.3 31.8% 91.4%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--ring: 222.2 84% 4.9%;
|
|
||||||
--radius: 0.5rem;
|
--destructive: 0 100% 50%;
|
||||||
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
|
||||||
|
--border: 0 0% 0%;
|
||||||
|
--input: 0 0% 0%;
|
||||||
|
--ring: 220 100% 50%;
|
||||||
|
|
||||||
|
--radius: 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 220 10% 10%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 20% 98%;
|
||||||
--card: 222.2 84% 4.9%;
|
|
||||||
--card-foreground: 210 40% 98%;
|
--card: 220 10% 15%;
|
||||||
--popover: 222.2 84% 4.9%;
|
--card-foreground: 210 20% 98%;
|
||||||
--popover-foreground: 210 40% 98%;
|
|
||||||
--primary: 210 40% 98%;
|
--popover: 220 10% 15%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--popover-foreground: 210 20% 98%;
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
|
||||||
--secondary-foreground: 210 40% 98%;
|
--primary: 220 100% 50%;
|
||||||
--muted: 217.2 32.6% 17.5%;
|
--primary-foreground: 0 0% 100%;
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
|
||||||
--accent: 217.2 32.6% 17.5%;
|
--secondary: 15 100% 50%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--secondary-foreground: 0 0% 100%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
|
||||||
--destructive-foreground: 210 40% 98%;
|
--muted: 220 10% 20%;
|
||||||
--border: 217.2 32.6% 17.5%;
|
--muted-foreground: 210 20% 60%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--accent: 180 100% 40%;
|
||||||
|
--accent-foreground: 0 0% 100%;
|
||||||
|
|
||||||
|
--destructive: 0 100% 50%;
|
||||||
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
|
||||||
|
--border: 0 0% 100%;
|
||||||
|
--input: 0 0% 100%;
|
||||||
|
--ring: 220 100% 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,8 +70,48 @@
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground font-mono antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@apply font-display font-bold tracking-tight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.retro-border {
|
||||||
|
@apply border-2 border-black;
|
||||||
|
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retro-card {
|
||||||
|
@apply bg-white border-2 border-black p-4;
|
||||||
|
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retro-btn {
|
||||||
|
@apply bg-primary text-white font-bold py-2 px-4 border-2 border-black transition-all;
|
||||||
|
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retro-btn:hover {
|
||||||
|
box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 1);
|
||||||
|
transform: translate(2px, 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retro-btn:active {
|
||||||
|
box-shadow: none;
|
||||||
|
transform: translate(4px, 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retro-input {
|
||||||
|
@apply bg-white border-2 border-black p-2 focus:outline-none focus:ring-2 focus:ring-primary shadow-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
GitBranch,
|
GitBranch,
|
||||||
|
|
@ -208,7 +207,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
console.log('✅ 任务创建成功:', taskId);
|
console.log('✅ 任务创建成功:', taskId);
|
||||||
|
|
||||||
// 记录用户操作
|
// 记录用户操作
|
||||||
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
|
import('@/shared/utils/logger').then(({ logger }) => {
|
||||||
logger.logUserAction('创建审计任务', {
|
logger.logUserAction('创建审计任务', {
|
||||||
taskId,
|
taskId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
|
|
@ -299,76 +298,74 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
|
||||||
<DialogHeader>
|
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||||
<DialogTitle className="flex items-center space-x-2">
|
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
|
||||||
<Shield className="w-5 h-5 text-primary" />
|
<Shield className="w-6 h-6 text-black" />
|
||||||
<span>新建审计任务</span>
|
<span>新建审计任务</span>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{/* 项目选择 */}
|
{/* 项目选择 */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-base font-medium">选择项目</Label>
|
<Label className="text-base font-bold font-mono uppercase">选择项目</Label>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs rounded-none border-black font-mono">
|
||||||
{filteredProjects.length} 个可用项目
|
{filteredProjects.length} 个可用项目
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目搜索 */}
|
{/* 项目搜索 */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索项目名称..."
|
placeholder="搜索项目名称..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-10"
|
className="pl-10 retro-input h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目列表 */}
|
{/* 项目列表 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto p-1">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="col-span-2 flex items-center justify-center py-8">
|
<div className="col-span-2 flex items-center justify-center py-8">
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
<div className="animate-spin rounded-none h-8 w-8 border-4 border-primary border-t-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
) : filteredProjects.length > 0 ? (
|
) : filteredProjects.length > 0 ? (
|
||||||
filteredProjects.map((project) => (
|
filteredProjects.map((project) => (
|
||||||
<Card
|
<div
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className={`cursor-pointer transition-all hover:shadow-md ${taskForm.project_id === project.id
|
className={`cursor-pointer transition-all border-2 p-4 relative ${taskForm.project_id === project.id
|
||||||
? 'ring-2 ring-primary bg-primary/5'
|
? 'border-primary bg-blue-50 shadow-[4px_4px_0px_0px_rgba(37,99,235,1)] translate-x-[-2px] translate-y-[-2px]'
|
||||||
: 'hover:bg-gray-50'
|
: 'border-black bg-white hover:bg-gray-50 hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px]'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setTaskForm({ ...taskForm, project_id: project.id })}
|
onClick={() => setTaskForm({ ...taskForm, project_id: project.id })}
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h4 className="font-medium text-sm">{project.name}</h4>
|
<h4 className="font-bold text-sm font-display uppercase">{project.name}</h4>
|
||||||
{project.description && (
|
{project.description && (
|
||||||
<p className="text-xs text-gray-500 mt-1 line-clamp-2">
|
<p className="text-xs text-gray-600 mt-1 line-clamp-2 font-mono">
|
||||||
{project.description}
|
{project.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-400">
|
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-500 font-mono font-bold">
|
||||||
<span>{project.repository_type?.toUpperCase() || 'OTHER'}</span>
|
<span className="uppercase">{project.repository_type?.toUpperCase() || 'OTHER'}</span>
|
||||||
<span>{project.default_branch}</span>
|
<span>{project.default_branch}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{taskForm.project_id === project.id && (
|
{taskForm.project_id === project.id && (
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
|
<div className="w-5 h-5 bg-primary border-2 border-black flex items-center justify-center">
|
||||||
<div className="w-2 h-2 rounded-full bg-white"></div>
|
<div className="w-2 h-2 bg-white"></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-2 text-center py-8 text-gray-500">
|
<div className="col-span-2 text-center py-8 text-gray-500 font-mono">
|
||||||
<FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
<FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{searchTerm ? '未找到匹配的项目' : '暂无可用项目'}
|
{searchTerm ? '未找到匹配的项目' : '暂无可用项目'}
|
||||||
|
|
@ -381,38 +378,46 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
{/* 任务配置 */}
|
{/* 任务配置 */}
|
||||||
{selectedProject && (
|
{selectedProject && (
|
||||||
<Tabs defaultValue="basic" className="w-full">
|
<Tabs defaultValue="basic" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3 bg-gray-100 border-2 border-black p-0 h-12 rounded-none">
|
||||||
<TabsTrigger value="basic" className="flex items-center space-x-2">
|
<TabsTrigger
|
||||||
|
value="basic"
|
||||||
|
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||||
|
>
|
||||||
<GitBranch className="w-4 h-4" />
|
<GitBranch className="w-4 h-4" />
|
||||||
<span>基础配置</span>
|
<span>基础配置</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="exclude" className="flex items-center space-x-2">
|
<TabsTrigger
|
||||||
|
value="exclude"
|
||||||
|
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||||
|
>
|
||||||
<FileText className="w-4 h-4" />
|
<FileText className="w-4 h-4" />
|
||||||
<span>排除规则</span>
|
<span>排除规则</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="advanced" className="flex items-center space-x-2">
|
<TabsTrigger
|
||||||
|
value="advanced"
|
||||||
|
className="flex items-center space-x-2 rounded-none data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||||
|
>
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4" />
|
||||||
<span>高级选项</span>
|
<span>高级选项</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="basic" className="space-y-4 mt-6">
|
<TabsContent value="basic" className="space-y-4 mt-6 font-mono">
|
||||||
{/* ZIP项目文件上传 */}
|
{/* ZIP项目文件上传 */}
|
||||||
{(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && (
|
{(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && (
|
||||||
<Card className="bg-amber-50 border-amber-200">
|
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{loadingZipFile ? (
|
{loadingZipFile ? (
|
||||||
<div className="flex items-center space-x-3 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
<div className="flex items-center space-x-3 p-4 bg-blue-50 border-2 border-black">
|
||||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
|
<div className="animate-spin rounded-none h-5 w-5 border-4 border-blue-600 border-t-transparent"></div>
|
||||||
<p className="text-sm text-blue-800">正在加载保存的ZIP文件...</p>
|
<p className="text-sm text-blue-800 font-bold">正在加载保存的ZIP文件...</p>
|
||||||
</div>
|
</div>
|
||||||
) : zipFile ? (
|
) : zipFile ? (
|
||||||
<div className="flex items-start space-x-3 p-4 bg-green-50 border border-green-200 rounded-lg">
|
<div className="flex items-start space-x-3 p-4 bg-green-50 border-2 border-black">
|
||||||
<Info className="w-5 h-5 text-green-600 mt-0.5" />
|
<Info className="w-5 h-5 text-green-600 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-green-900 text-sm">已准备就绪</p>
|
<p className="font-bold text-green-900 text-sm uppercase">已准备就绪</p>
|
||||||
<p className="text-xs text-green-700 mt-1">
|
<p className="text-xs text-green-700 mt-1 font-bold">
|
||||||
使用保存的ZIP文件: {zipFile.name} (
|
使用保存的ZIP文件: {zipFile.name} (
|
||||||
{zipFile.size >= 1024 * 1024
|
{zipFile.size >= 1024 * 1024
|
||||||
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
|
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
|
||||||
|
|
@ -429,6 +434,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
setZipFile(null);
|
setZipFile(null);
|
||||||
setHasLoadedZip(false);
|
setHasLoadedZip(false);
|
||||||
}}
|
}}
|
||||||
|
className="retro-btn bg-white text-black h-8 text-xs"
|
||||||
>
|
>
|
||||||
更换文件
|
更换文件
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -438,15 +444,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-amber-900 text-sm">需要上传ZIP文件</p>
|
<p className="font-bold text-amber-900 text-sm uppercase">需要上传ZIP文件</p>
|
||||||
<p className="text-xs text-amber-700 mt-1">
|
<p className="text-xs text-amber-700 mt-1 font-bold">
|
||||||
未找到保存的ZIP文件,请上传文件进行扫描
|
未找到保存的ZIP文件,请上传文件进行扫描
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="zipFile">上传ZIP文件</Label>
|
<Label htmlFor="zipFile" className="font-bold uppercase">上传ZIP文件</Label>
|
||||||
<Input
|
<Input
|
||||||
id="zipFile"
|
id="zipFile"
|
||||||
type="file"
|
type="file"
|
||||||
|
|
@ -477,37 +483,36 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
toast.success(`已选择文件: ${file.name} (${sizeText})`);
|
toast.success(`已选择文件: ${file.name} (${sizeText})`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer retro-input pt-1.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="task_type">任务类型</Label>
|
<Label htmlFor="task_type" className="font-bold uppercase">任务类型</Label>
|
||||||
<Select
|
<Select
|
||||||
value={taskForm.task_type}
|
value={taskForm.task_type}
|
||||||
onValueChange={(value: any) => setTaskForm({ ...taskForm, task_type: value })}
|
onValueChange={(value: any) => setTaskForm({ ...taskForm, task_type: value })}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<SelectItem value="repository">
|
<SelectItem value="repository">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<GitBranch className="w-4 h-4" />
|
<GitBranch className="w-4 h-4" />
|
||||||
<span>仓库审计</span>
|
<span className="font-mono">仓库审计</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="instant">
|
<SelectItem value="instant">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Zap className="w-4 h-4" />
|
<Zap className="w-4 h-4" />
|
||||||
<span>即时分析</span>
|
<span className="font-mono">即时分析</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -516,25 +521,25 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
|
|
||||||
{taskForm.task_type === "repository" && (selectedProject.repository_url) && (
|
{taskForm.task_type === "repository" && (selectedProject.repository_url) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="branch_name">目标分支</Label>
|
<Label htmlFor="branch_name" className="font-bold uppercase">目标分支</Label>
|
||||||
<Input
|
<Input
|
||||||
id="branch_name"
|
id="branch_name"
|
||||||
value={taskForm.branch_name || ""}
|
value={taskForm.branch_name || ""}
|
||||||
onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })}
|
onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })}
|
||||||
placeholder={selectedProject.default_branch || "main"}
|
placeholder={selectedProject.default_branch || "main"}
|
||||||
|
className="retro-input h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目信息展示 */}
|
{/* 项目信息展示 */}
|
||||||
<Card className="bg-blue-50 border-blue-200">
|
<div className="bg-blue-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
|
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
|
||||||
<div className="text-sm">
|
<div className="text-sm font-mono">
|
||||||
<p className="font-medium text-blue-900 mb-1">选中项目:{selectedProject.name}</p>
|
<p className="font-bold text-blue-900 mb-1 uppercase">选中项目:{selectedProject.name}</p>
|
||||||
<div className="text-blue-700 space-y-1">
|
<div className="text-blue-800 space-y-1 font-bold">
|
||||||
{selectedProject.description && (
|
{selectedProject.description && (
|
||||||
<p>描述:{selectedProject.description}</p>
|
<p>描述:{selectedProject.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -545,15 +550,14 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="exclude" className="space-y-4 mt-6">
|
<TabsContent value="exclude" className="space-y-4 mt-6 font-mono">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-base font-medium">排除模式</Label>
|
<Label className="text-base font-bold uppercase">排除模式</Label>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<p className="text-sm text-gray-500 mt-1 font-bold">
|
||||||
选择要从审计中排除的文件和目录模式
|
选择要从审计中排除的文件和目录模式
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -561,14 +565,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
{/* 常用排除模式 */}
|
{/* 常用排除模式 */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{commonExcludePatterns.map((pattern) => (
|
{commonExcludePatterns.map((pattern) => (
|
||||||
<div key={pattern.value} className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50">
|
<div key={pattern.value} className="flex items-center space-x-3 p-3 border-2 border-black bg-white hover:bg-gray-50 transition-all">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={taskForm.exclude_patterns.includes(pattern.value)}
|
checked={taskForm.exclude_patterns.includes(pattern.value)}
|
||||||
onCheckedChange={() => toggleExcludePattern(pattern.value)}
|
onCheckedChange={() => toggleExcludePattern(pattern.value)}
|
||||||
|
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm font-medium">{pattern.label}</p>
|
<p className="text-sm font-bold uppercase">{pattern.label}</p>
|
||||||
<p className="text-xs text-gray-500">{pattern.description}</p>
|
<p className="text-xs text-gray-500 font-bold">{pattern.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -576,7 +581,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
|
|
||||||
{/* 自定义排除模式 */}
|
{/* 自定义排除模式 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>自定义排除模式</Label>
|
<Label className="font-bold uppercase">自定义排除模式</Label>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="例如: *.tmp, test/**"
|
placeholder="例如: *.tmp, test/**"
|
||||||
|
|
@ -586,6 +591,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
e.currentTarget.value = '';
|
e.currentTarget.value = '';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
className="retro-input h-10"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -595,6 +601,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
addCustomPattern(input.value);
|
addCustomPattern(input.value);
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}}
|
}}
|
||||||
|
className="retro-btn bg-white text-black h-10"
|
||||||
>
|
>
|
||||||
添加
|
添加
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -604,13 +611,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
{/* 已选择的排除模式 */}
|
{/* 已选择的排除模式 */}
|
||||||
{taskForm.exclude_patterns.length > 0 && (
|
{taskForm.exclude_patterns.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>已选择的排除模式</Label>
|
<Label className="font-bold uppercase">已选择的排除模式</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{taskForm.exclude_patterns.map((pattern) => (
|
{taskForm.exclude_patterns.map((pattern) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={pattern}
|
key={pattern}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="cursor-pointer hover:bg-red-100 hover:text-red-800"
|
className="cursor-pointer hover:bg-red-100 hover:text-red-800 rounded-none border-2 border-black bg-gray-100 text-black font-mono font-bold"
|
||||||
onClick={() => removeExcludePattern(pattern)}
|
onClick={() => removeExcludePattern(pattern)}
|
||||||
>
|
>
|
||||||
{pattern} ×
|
{pattern} ×
|
||||||
|
|
@ -622,18 +629,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="advanced" className="space-y-4 mt-6">
|
<TabsContent value="advanced" className="space-y-4 mt-6 font-mono">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-base font-medium">扫描配置</Label>
|
<Label className="text-base font-bold uppercase">扫描配置</Label>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<p className="text-sm text-gray-500 mt-1 font-bold">
|
||||||
配置代码扫描的详细参数
|
配置代码扫描的详细参数
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={taskForm.scan_config.include_tests}
|
checked={taskForm.scan_config.include_tests}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
|
|
@ -642,14 +649,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
scan_config: { ...taskForm.scan_config, include_tests: !!checked }
|
scan_config: { ...taskForm.scan_config, include_tests: !!checked }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">包含测试文件</p>
|
<p className="text-sm font-bold uppercase">包含测试文件</p>
|
||||||
<p className="text-xs text-gray-500">扫描 *test*, *spec* 等测试文件</p>
|
<p className="text-xs text-gray-500 font-bold">扫描 *test*, *spec* 等测试文件</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={taskForm.scan_config.include_docs}
|
checked={taskForm.scan_config.include_docs}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
|
|
@ -658,17 +666,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
scan_config: { ...taskForm.scan_config, include_docs: !!checked }
|
scan_config: { ...taskForm.scan_config, include_docs: !!checked }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">包含文档文件</p>
|
<p className="text-sm font-bold uppercase">包含文档文件</p>
|
||||||
<p className="text-xs text-gray-500">扫描 README, docs 等文档文件</p>
|
<p className="text-xs text-gray-500 font-bold">扫描 README, docs 等文档文件</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="max_file_size">最大文件大小 (KB)</Label>
|
<Label htmlFor="max_file_size" className="font-bold uppercase">最大文件大小 (KB)</Label>
|
||||||
<Input
|
<Input
|
||||||
id="max_file_size"
|
id="max_file_size"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -684,11 +693,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
}
|
}
|
||||||
min="1"
|
min="1"
|
||||||
max="10240"
|
max="10240"
|
||||||
|
className="retro-input h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="analysis_depth">分析深度</Label>
|
<Label htmlFor="analysis_depth" className="font-bold uppercase">分析深度</Label>
|
||||||
<Select
|
<Select
|
||||||
value={taskForm.scan_config.analysis_depth}
|
value={taskForm.scan_config.analysis_depth}
|
||||||
onValueChange={(value: any) =>
|
onValueChange={(value: any) =>
|
||||||
|
|
@ -698,13 +708,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<SelectItem value="basic">基础扫描</SelectItem>
|
<SelectItem value="basic" className="font-mono">基础扫描</SelectItem>
|
||||||
<SelectItem value="standard">标准扫描</SelectItem>
|
<SelectItem value="standard" className="font-mono">标准扫描</SelectItem>
|
||||||
<SelectItem value="deep">深度扫描</SelectItem>
|
<SelectItem value="deep" className="font-mono">深度扫描</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -712,39 +722,42 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 分析深度说明 */}
|
{/* 分析深度说明 */}
|
||||||
<Card className="bg-amber-50 border-amber-200">
|
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||||
<div className="text-sm">
|
<div className="text-sm font-mono">
|
||||||
<p className="font-medium text-amber-900 mb-2">分析深度说明:</p>
|
<p className="font-bold text-amber-900 mb-2 uppercase">分析深度说明:</p>
|
||||||
<ul className="text-amber-800 space-y-1 text-xs">
|
<ul className="text-amber-800 space-y-1 text-xs font-bold">
|
||||||
<li>• <strong>基础扫描</strong>:快速检查语法错误和基本问题</li>
|
<li>• <strong>基础扫描</strong>:快速检查语法错误和基本问题</li>
|
||||||
<li>• <strong>标准扫描</strong>:包含代码质量、安全性和性能分析</li>
|
<li>• <strong>标准扫描</strong>:包含代码质量、安全性和性能分析</li>
|
||||||
<li>• <strong>深度扫描</strong>:全面分析,包含复杂度、可维护性等高级指标</li>
|
<li>• <strong>深度扫描</strong>:全面分析,包含复杂度、可维护性等高级指标</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
<div className="flex justify-end space-x-3 pt-4 border-t">
|
<div className="flex justify-end space-x-3 pt-6 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6">
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={creating}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
disabled={creating}
|
||||||
|
className="retro-btn bg-white text-black h-12 px-6 font-bold uppercase"
|
||||||
|
>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateTask}
|
onClick={handleCreateTask}
|
||||||
disabled={!taskForm.project_id || creating}
|
disabled={!taskForm.project_id || creating}
|
||||||
className="btn-primary"
|
className="retro-btn bg-primary text-white h-12 px-6 font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]"
|
||||||
>
|
>
|
||||||
{creating ? (
|
{creating ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
<div className="animate-spin rounded-none h-4 w-4 border-2 border-white border-t-transparent mr-2"></div>
|
||||||
创建中...
|
创建中...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog";
|
import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
import { Terminal, CheckCircle, XCircle, Loader2, X as XIcon } from "lucide-react";
|
import { Terminal, X as XIcon } from "lucide-react";
|
||||||
import { cn, calculateTaskProgress } from "@/shared/utils/utils";
|
import { cn, calculateTaskProgress } from "@/shared/utils/utils";
|
||||||
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
||||||
import { taskControl } from "@/shared/services/taskControl";
|
import { taskControl } from "@/shared/services/taskControl";
|
||||||
|
|
@ -112,8 +112,8 @@ export default function TerminalProgressDialog({
|
||||||
|
|
||||||
// 初始化日志
|
// 初始化日志
|
||||||
addLog("🚀 审计任务已启动", "info");
|
addLog("🚀 审计任务已启动", "info");
|
||||||
addLog(`<EFBFBD> 任务任ID: ${taskId}`, "info");
|
addLog(`任务ID: ${taskId}`, "info");
|
||||||
addLog(`<EFBFBD> 任务类D型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
|
addLog(`任务类型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
|
||||||
addLog("⏳ 正在初始化审计环境...", "info");
|
addLog("⏳ 正在初始化审计环境...", "info");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,42 +383,31 @@ export default function TerminalProgressDialog({
|
||||||
const getLogColor = (type: LogEntry["type"]) => {
|
const getLogColor = (type: LogEntry["type"]) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "success":
|
case "success":
|
||||||
return "text-emerald-400";
|
return "text-green-500";
|
||||||
case "error":
|
case "error":
|
||||||
return "text-rose-400";
|
return "text-red-500";
|
||||||
case "warning":
|
case "warning":
|
||||||
return "text-amber-400";
|
return "text-yellow-500";
|
||||||
default:
|
default:
|
||||||
return "text-gray-200";
|
return "text-gray-300";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取状态图标
|
|
||||||
const getStatusIcon = () => {
|
|
||||||
if (isFailed) {
|
|
||||||
return <XCircle className="w-5 h-5 text-rose-400" />;
|
|
||||||
}
|
|
||||||
if (isCompleted) {
|
|
||||||
return <CheckCircle className="w-5 h-5 text-emerald-400" />;
|
|
||||||
}
|
|
||||||
return <Loader2 className="w-5 h-5 text-rose-400 animate-spin" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay />
|
<DialogOverlay className="bg-black/50 backdrop-blur-sm" />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
|
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
|
||||||
"w-[90vw] aspect-[16/9]",
|
"w-[90vw] aspect-[16/9]",
|
||||||
"max-w-[1600px] max-h-[900px]",
|
"max-w-[1200px] max-h-[800px]",
|
||||||
"p-0 gap-0 rounded-lg overflow-hidden",
|
"p-0 gap-0 rounded-none overflow-hidden",
|
||||||
"bg-gradient-to-br from-gray-900 via-red-950/30 to-gray-900 border border-red-900/50",
|
"bg-black border-4 border-gray-500 shadow-[10px_10px_0px_0px_rgba(0,0,0,0.5)]",
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||||
"duration-200 shadow-2xl"
|
"duration-200"
|
||||||
)}
|
)}
|
||||||
onPointerDownOutside={(e) => e.preventDefault()}
|
onPointerDownOutside={(e) => e.preventDefault()}
|
||||||
onInteractOutside={(e) => e.preventDefault()}
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
|
|
@ -432,33 +421,46 @@ export default function TerminalProgressDialog({
|
||||||
</VisuallyHidden.Root>
|
</VisuallyHidden.Root>
|
||||||
|
|
||||||
{/* 终端头部 */}
|
{/* 终端头部 */}
|
||||||
<div className="flex items-center justify-between px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-b border-red-900/30 backdrop-blur-sm">
|
<div className="flex items-center justify-between px-4 py-2 bg-gray-300 border-b-4 border-gray-500">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Terminal className="w-5 h-5 text-rose-400" />
|
<Terminal className="w-5 h-5 text-black" />
|
||||||
<span className="text-sm font-medium text-gray-100">审计进度监控</span>
|
<span className="text-sm font-bold text-black uppercase font-display tracking-wider">TERMINAL // 审计进度监控</span>
|
||||||
{getStatusIcon()}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-emerald-500" />
|
{/* 模拟窗口控制按钮 */}
|
||||||
<div className="w-3 h-3 rounded-full bg-amber-500" />
|
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
|
||||||
|
<div className="w-2 h-0.5 bg-black"></div>
|
||||||
|
</div>
|
||||||
|
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
|
||||||
|
<div className="w-2 h-2 border border-black"></div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className="w-3 h-3 rounded-full bg-rose-500 hover:bg-rose-600 cursor-pointer transition-colors focus:outline-none"
|
className="w-4 h-4 border-2 border-black bg-primary hover:bg-red-600 cursor-pointer transition-colors focus:outline-none flex items-center justify-center"
|
||||||
onClick={() => onOpenChange(false)}
|
onClick={() => onOpenChange(false)}
|
||||||
title="关闭"
|
title="关闭"
|
||||||
aria-label="关闭"
|
aria-label="关闭"
|
||||||
/>
|
>
|
||||||
|
<XIcon className="w-3 h-3 text-white" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 终端内容 */}
|
{/* 终端内容 */}
|
||||||
<div className="p-6 bg-gradient-to-b from-gray-900/95 to-gray-950/95 overflow-y-auto h-[calc(100%-120px)] font-mono text-sm backdrop-blur-sm">
|
<div className="p-6 bg-black overflow-y-auto h-[calc(100%-100px)] font-mono text-sm relative">
|
||||||
<div className="space-y-2">
|
{/* 扫描线效果 */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%]"></div>
|
||||||
|
|
||||||
|
<div className="space-y-1 relative z-20">
|
||||||
{logs.map((log, index) => (
|
{logs.map((log, index) => (
|
||||||
<div key={index} className="flex items-start space-x-3 hover:bg-red-950/10 px-2 py-1 rounded transition-colors">
|
<div key={index} className="flex items-start space-x-3 hover:bg-white/5 px-2 py-0.5 transition-colors">
|
||||||
<span className="text-rose-800/70 text-xs flex-shrink-0 w-20">
|
<span className="text-gray-500 text-xs flex-shrink-0 w-24 font-bold">
|
||||||
[{log.timestamp}]
|
[{log.timestamp}]
|
||||||
</span>
|
</span>
|
||||||
<span className={`${getLogColor(log.type)} flex-1`}>
|
<span className={`${getLogColor(log.type)} flex-1 font-bold tracking-wide`}>
|
||||||
|
{log.type === 'info' && '> '}
|
||||||
|
{log.type === 'success' && '✓ '}
|
||||||
|
{log.type === 'error' && '✗ '}
|
||||||
|
{log.type === 'warning' && '! '}
|
||||||
{log.message}
|
{log.message}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -466,79 +468,36 @@ export default function TerminalProgressDialog({
|
||||||
|
|
||||||
{/* 光标旋转闪烁效果 */}
|
{/* 光标旋转闪烁效果 */}
|
||||||
{!isCompleted && !isFailed && (
|
{!isCompleted && !isFailed && (
|
||||||
<div className="flex items-center space-x-2 mt-4">
|
<div className="flex items-center space-x-2 mt-4 px-2">
|
||||||
<span className="text-rose-800/70 text-xs w-20">[{currentTime}]</span>
|
<span className="text-gray-500 text-xs w-24 font-bold">[{currentTime}]</span>
|
||||||
<span className="inline-block text-rose-400 animate-spinner font-bold text-base"></span>
|
<span className="inline-block text-green-500 animate-pulse font-bold text-base">_</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 添加自定义动画 */}
|
|
||||||
<style>{`
|
|
||||||
@keyframes spinner {
|
|
||||||
0% {
|
|
||||||
content: '|';
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
content: '/';
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
content: '—';
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
content: '\\\\';
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
content: '|';
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animate-spinner::before {
|
|
||||||
content: '|';
|
|
||||||
animation: spinner-content 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
.animate-spinner {
|
|
||||||
animation: spinner-opacity 0.8s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
@keyframes spinner-content {
|
|
||||||
0% { content: '|'; }
|
|
||||||
25% { content: '/'; }
|
|
||||||
50% { content: '—'; }
|
|
||||||
75% { content: '\\\\'; }
|
|
||||||
100% { content: '|'; }
|
|
||||||
}
|
|
||||||
@keyframes spinner-opacity {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
25%, 75% { opacity: 0.8; }
|
|
||||||
50% { opacity: 0.6; }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
|
|
||||||
<div ref={logsEndRef} />
|
<div ref={logsEndRef} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 底部控制和提示 */}
|
{/* 底部控制和提示 */}
|
||||||
<div className="px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-t border-red-900/30 backdrop-blur-sm">
|
<div className="px-4 py-3 bg-gray-200 border-t-4 border-gray-500 flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between text-xs">
|
|
||||||
<span className="text-gray-300">
|
|
||||||
{isCancelled ? "🛑 任务已取消,已分析的结果已保存" :
|
|
||||||
isCompleted ? "✅ 任务已完成,可以关闭此窗口" :
|
|
||||||
isFailed ? "❌ 任务失败,请检查配置后重试" :
|
|
||||||
"⏳ 审计进行中,请勿关闭窗口,过程可能较慢,请耐心等待......"}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className={`w-3 h-3 border-2 border-black ${isFailed ? 'bg-red-500' : isCompleted ? 'bg-green-500' : 'bg-yellow-400 animate-pulse'}`}></div>
|
||||||
|
<span className="text-xs font-bold text-black uppercase font-mono tracking-tight">
|
||||||
|
{isCancelled ? "STATUS: CANCELLED // 任务已取消" :
|
||||||
|
isCompleted ? "STATUS: COMPLETED // 任务已完成" :
|
||||||
|
isFailed ? "STATUS: FAILED // 任务失败" :
|
||||||
|
"STATUS: RUNNING // 审计进行中..."}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
{/* 运行中显示取消按钮 */}
|
{/* 运行中显示取消按钮 */}
|
||||||
{!isCompleted && !isFailed && !isCancelled && (
|
{!isCompleted && !isFailed && !isCancelled && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className="h-7 text-xs bg-gray-800 border-red-600 text-red-400 hover:bg-red-900 hover:text-red-200"
|
className="h-8 text-xs bg-white border-2 border-black text-black hover:bg-red-100 hover:text-red-900 font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
|
||||||
>
|
>
|
||||||
<XIcon className="w-3 h-3 mr-1" />
|
<XIcon className="w-3 h-3 mr-1" />
|
||||||
取消任务
|
取消任务
|
||||||
|
|
@ -551,7 +510,7 @@ export default function TerminalProgressDialog({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open('/logs', '_blank');
|
window.open('/logs', '_blank');
|
||||||
}}
|
}}
|
||||||
className="px-4 py-1.5 bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-500 hover:to-orange-500 text-white rounded text-xs transition-all shadow-lg shadow-yellow-900/50 font-medium"
|
className="px-4 py-1.5 bg-yellow-400 border-2 border-black text-black hover:bg-yellow-500 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
|
||||||
>
|
>
|
||||||
📋 查看日志
|
📋 查看日志
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -561,14 +520,13 @@ export default function TerminalProgressDialog({
|
||||||
{(isCompleted || isFailed || isCancelled) && (
|
{(isCompleted || isFailed || isCancelled) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onOpenChange(false)}
|
onClick={() => onOpenChange(false)}
|
||||||
className="px-4 py-1.5 bg-gradient-to-r from-rose-600 to-red-600 hover:from-rose-500 hover:to-red-500 text-white rounded text-xs transition-all shadow-lg shadow-rose-900/50 font-medium"
|
className="px-4 py-1.5 bg-primary border-2 border-black text-white hover:bg-primary/90 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
|
||||||
>
|
>
|
||||||
关闭
|
关闭窗口
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ import {
|
||||||
FileText,
|
FileText,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Github
|
Github,
|
||||||
|
Terminal
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import routes from "@/app/routes";
|
import routes from "@/app/routes";
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="fixed top-4 left-4 z-50 md:hidden bg-white shadow-md border border-gray-200 text-gray-700 hover:bg-gray-50"
|
className="fixed top-4 left-4 z-50 md:hidden bg-white border-2 border-black shadow-retro text-black hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-retro-hover transition-all"
|
||||||
onClick={() => setMobileOpen(!mobileOpen)}
|
onClick={() => setMobileOpen(!mobileOpen)}
|
||||||
>
|
>
|
||||||
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||||
|
|
@ -54,7 +55,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
{/* Overlay for mobile */}
|
{/* Overlay for mobile */}
|
||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-40 md:hidden transition-opacity"
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -62,41 +63,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside
|
<aside
|
||||||
className={`
|
className={`
|
||||||
fixed top-0 left-0 h-screen bg-white/95 backdrop-blur-xl
|
fixed top-0 left-0 h-screen bg-background
|
||||||
border-r border-gray-200/80 shadow-[4px_0_24px_-12px_rgba(0,0,0,0.1)]
|
border-r-2 border-black
|
||||||
z-40 transition-all duration-300 ease-in-out
|
z-40 transition-all duration-300 ease-in-out
|
||||||
${collapsed ? "w-20" : "w-64"}
|
${collapsed ? "w-20" : "w-64"}
|
||||||
${mobileOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"}
|
${mobileOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full relative">
|
||||||
|
{/* Decorative Grid Background */}
|
||||||
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||||
|
|
||||||
{/* Logo Section */}
|
{/* Logo Section */}
|
||||||
<div className="relative flex items-center h-[72px] px-4 border-b border-gray-100">
|
<div className="relative flex items-center h-[72px] px-4 border-b-2 border-black bg-white z-10">
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className={`flex items-center space-x-3 group overflow-hidden transition-all duration-300 ${collapsed ? 'justify-center w-full' : ''}`}
|
className={`flex items-center space-x-3 group overflow-hidden transition-all duration-300 ${collapsed ? 'justify-center w-full' : ''}`}
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
>
|
>
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0 border-2 border-black bg-primary p-1 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="absolute inset-0 bg-red-500/20 blur-lg rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
<Terminal className="w-6 h-6 text-white" />
|
||||||
<img
|
|
||||||
src="/logo_xcodereviewer.png"
|
|
||||||
alt="XCodeReviewer Logo"
|
|
||||||
className="w-9 h-9 rounded-xl shadow-sm relative z-10 group-hover:scale-105 transition-transform duration-300"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`transition-all duration-300 ${collapsed ? 'w-0 opacity-0 overflow-hidden' : 'w-auto opacity-100'}`}>
|
<div className={`transition-all duration-300 ${collapsed ? 'w-0 opacity-0 overflow-hidden' : 'w-auto opacity-100'}`}>
|
||||||
<span className="text-lg font-bold bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-700 group-hover:from-red-700 group-hover:to-red-500 whitespace-nowrap">
|
<span className="text-lg font-display font-bold text-black tracking-tighter uppercase">
|
||||||
XCodeReviewer
|
XCode<span className="text-primary">Reviewer</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Collapse button for desktop - Absolute Positioned */}
|
{/* Collapse button for desktop */}
|
||||||
<Button
|
<button
|
||||||
variant="ghost"
|
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 bg-white border-2 border-black items-center justify-center hover:bg-primary hover:text-white transition-colors z-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
|
||||||
size="icon"
|
|
||||||
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white text-gray-400 hover:text-gray-900 border border-gray-200 shadow-sm z-50 hover:bg-gray-50"
|
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
>
|
>
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
|
|
@ -104,12 +101,12 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
) : (
|
) : (
|
||||||
<ChevronLeft className="w-3 h-3" />
|
<ChevronLeft className="w-3 h-3" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="flex-1 overflow-y-auto py-6 px-3">
|
<nav className="flex-1 overflow-y-auto py-6 px-3 z-10">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-2">
|
||||||
{visibleRoutes.map((route) => {
|
{visibleRoutes.map((route) => {
|
||||||
const isActive = location.pathname === route.path;
|
const isActive = location.pathname === route.path;
|
||||||
return (
|
return (
|
||||||
|
|
@ -117,39 +114,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
key={route.path}
|
key={route.path}
|
||||||
to={route.path}
|
to={route.path}
|
||||||
className={`
|
className={`
|
||||||
flex items-center space-x-3 px-3 py-2.5 rounded-xl
|
flex items-center space-x-3 px-3 py-3
|
||||||
transition-all duration-200 group relative overflow-hidden
|
transition-all duration-200 group relative
|
||||||
|
border-2
|
||||||
${isActive
|
${isActive
|
||||||
? "bg-red-50 text-red-700 font-medium shadow-sm ring-1 ring-red-100"
|
? "bg-primary border-black shadow-retro text-white"
|
||||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
: "bg-transparent border-transparent hover:border-black hover:bg-white hover:shadow-retro-hover text-gray-600 hover:text-black"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
title={collapsed ? route.name : undefined}
|
title={collapsed ? route.name : undefined}
|
||||||
>
|
>
|
||||||
{/* Active Indicator Bar */}
|
|
||||||
{isActive && (
|
|
||||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-red-600 rounded-r-full opacity-0 md:opacity-100 transition-opacity" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<span className={`
|
<span className={`
|
||||||
flex-shrink-0 transition-colors duration-200
|
flex-shrink-0 transition-colors duration-200
|
||||||
${isActive ? "text-red-600" : "text-gray-400 group-hover:text-gray-600"}
|
${isActive ? "text-white" : "text-black group-hover:text-black"}
|
||||||
`}>
|
`}>
|
||||||
{routeIcons[route.path] || <LayoutDashboard className="w-5 h-5" />}
|
{routeIcons[route.path] || <LayoutDashboard className="w-5 h-5" />}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Label */}
|
{/* Label */}
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span className="whitespace-nowrap z-10">
|
<span className="font-mono font-bold tracking-tight uppercase text-sm">
|
||||||
{route.name}
|
{route.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hover Effect Background (Subtle) */}
|
{/* Glitch Effect on Hover (Optional) */}
|
||||||
{!isActive && (
|
{!isActive && !collapsed && (
|
||||||
<div className="absolute inset-0 bg-gray-100 opacity-0 group-hover:opacity-100 transition-opacity duration-200 -z-0 rounded-xl" />
|
<span className="absolute right-2 opacity-0 group-hover:opacity-100 text-xs font-bold text-primary animate-pulse">
|
||||||
|
</>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
@ -158,23 +153,23 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Footer with GitHub Link */}
|
{/* Footer with GitHub Link */}
|
||||||
<div className="p-4 border-t border-gray-100 bg-gray-50/50">
|
<div className="p-4 border-t-2 border-black bg-white z-10">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/lintsinghua/XCodeReviewer"
|
href="https://github.com/lintsinghua/XCodeReviewer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={`
|
className={`
|
||||||
flex items-center space-x-3 px-3 py-2.5 rounded-xl
|
flex items-center space-x-3 px-3 py-2.5
|
||||||
text-gray-500 hover:bg-white hover:text-gray-900 hover:shadow-sm hover:ring-1 hover:ring-gray-200
|
text-black border-2 border-transparent hover:border-black hover:shadow-retro-hover hover:bg-white
|
||||||
transition-all duration-200 group
|
transition-all duration-200 group
|
||||||
`}
|
`}
|
||||||
title={collapsed ? "GitHub Repository" : undefined}
|
title={collapsed ? "GitHub 仓库" : undefined}
|
||||||
>
|
>
|
||||||
<Github className="w-5 h-5 flex-shrink-0 text-gray-400 group-hover:text-gray-900 transition-colors" />
|
<Github className="w-5 h-5 flex-shrink-0" />
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium text-sm">GitHub</span>
|
<span className="font-mono font-bold text-sm uppercase">GitHub</span>
|
||||||
<span className="text-xs text-gray-400 group-hover:text-gray-500">View Source</span>
|
<span className="text-xs text-gray-500 font-mono">v1.0.0</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -78,18 +78,18 @@ export default function ExportReportDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[600px]">
|
<DialogContent className="sm:max-w-[600px] bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
|
||||||
<DialogHeader>
|
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||||
<DialogTitle className="flex items-center space-x-2">
|
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
|
||||||
<Download className="w-5 h-5 text-primary" />
|
<Download className="w-6 h-6 text-black" />
|
||||||
<span>导出审计报告</span>
|
<span>导出审计报告</span>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription className="font-mono text-xs text-gray-500 mt-2">
|
||||||
选择报告格式并导出完整的代码审计结果
|
选择报告格式并导出完整的代码审计结果
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="py-6">
|
<div className="p-6">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={selectedFormat}
|
value={selectedFormat}
|
||||||
onValueChange={(value) => setSelectedFormat(value as ExportFormat)}
|
onValueChange={(value) => setSelectedFormat(value as ExportFormat)}
|
||||||
|
|
@ -108,29 +108,27 @@ export default function ExportReportDialog({
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={format.value}
|
htmlFor={format.value}
|
||||||
className={`flex items-start space-x-4 p-4 rounded-lg border-2 cursor-pointer transition-all ${isSelected
|
className={`flex items-start space-x-4 p-4 border-2 cursor-pointer transition-all rounded-none font-mono ${isSelected
|
||||||
? `${format.borderColor} ${format.bgColor} shadow-md`
|
? "border-black bg-gray-100 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] translate-x-[-2px] translate-y-[-2px]"
|
||||||
: "border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"
|
: "border-black bg-white hover:bg-gray-50 hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-12 h-12 rounded-lg flex items-center justify-center ${isSelected ? format.bgColor : "bg-gray-50"
|
className={`w-12 h-12 border-2 border-black flex items-center justify-center rounded-none ${isSelected ? "bg-black text-white" : "bg-white text-black"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className={`w-6 h-6 ${isSelected ? format.color : "text-gray-400"}`} />
|
<Icon className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<h4 className={`font-semibold ${isSelected ? format.color : "text-gray-900"}`}>
|
<h4 className="font-bold uppercase text-black">
|
||||||
{format.label}
|
{format.label}
|
||||||
</h4>
|
</h4>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<div className={`w-5 h-5 rounded-full ${format.bgColor} flex items-center justify-center`}>
|
<div className="w-4 h-4 bg-black border-2 border-black" />
|
||||||
<div className={`w-2.5 h-2.5 rounded-full ${format.color.replace("text-", "bg-")}`} />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600">{format.description}</p>
|
<p className="text-xs text-gray-600 font-bold">{format.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,32 +137,32 @@ export default function ExportReportDialog({
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
{/* 报告预览信息 */}
|
{/* 报告预览信息 */}
|
||||||
<div className="mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
<div className="mt-6 p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<h4 className="font-medium text-gray-900 mb-3">报告内容预览</h4>
|
<h4 className="font-bold text-black uppercase mb-3 font-display border-b-2 border-black pb-2 w-fit">报告内容预览</h4>
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-xs font-mono">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">项目名称:</span>
|
<span className="text-gray-600 font-bold">项目名称:</span>
|
||||||
<span className="font-medium text-gray-900">{task.project?.name || "未知"}</span>
|
<span className="font-bold text-black">{task.project?.name || "未知"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">质量评分:</span>
|
<span className="text-gray-600 font-bold">质量评分:</span>
|
||||||
<span className="font-medium text-gray-900">{task.quality_score.toFixed(1)}/100</span>
|
<span className="font-bold text-black">{task.quality_score.toFixed(1)}/100</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">扫描文件:</span>
|
<span className="text-gray-600 font-bold">扫描文件:</span>
|
||||||
<span className="font-medium text-gray-900">{task.scanned_files}/{task.total_files}</span>
|
<span className="font-bold text-black">{task.scanned_files}/{task.total_files}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">发现问题:</span>
|
<span className="text-gray-600 font-bold">发现问题:</span>
|
||||||
<span className="font-medium text-orange-600">{issues.length}</span>
|
<span className="font-bold text-orange-600">{issues.length}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">代码行数:</span>
|
<span className="text-gray-600 font-bold">代码行数:</span>
|
||||||
<span className="font-medium text-gray-900">{task.total_lines.toLocaleString()}</span>
|
<span className="font-bold text-black">{task.total_lines.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||||
<span className="text-gray-600">严重问题:</span>
|
<span className="text-gray-600 font-bold">严重问题:</span>
|
||||||
<span className="font-medium text-red-600">
|
<span className="font-bold text-red-600">
|
||||||
{issues.filter(i => i.severity === "critical").length}
|
{issues.filter(i => i.severity === "critical").length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -172,18 +170,19 @@ export default function ExportReportDialog({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter className="p-6 border-t-2 border-black bg-gray-50 flex justify-end gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onOpenChange(false)}
|
onClick={() => onOpenChange(false)}
|
||||||
disabled={isExporting}
|
disabled={isExporting}
|
||||||
|
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase"
|
||||||
>
|
>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
disabled={isExporting}
|
disabled={isExporting}
|
||||||
className="bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800"
|
className="retro-btn bg-primary text-white border-2 border-black hover:bg-primary/90 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
|
||||||
>
|
>
|
||||||
{isExporting ? (
|
{isExporting ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
|
|
@ -310,8 +308,8 @@ export function SystemConfig() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div>
|
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
|
||||||
<p className="text-gray-600">正在从后端加载配置...</p>
|
<p className="text-black font-mono font-bold uppercase">正在从后端加载配置...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -320,98 +318,98 @@ export function SystemConfig() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 配置状态提示 */}
|
{/* 配置状态提示 */}
|
||||||
<Alert>
|
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||||
<AlertDescription className="flex items-center justify-between">
|
<div className="flex-1 flex items-center justify-between">
|
||||||
<div>
|
<div className="font-mono text-sm text-blue-800">
|
||||||
<strong>当前配置来源:</strong>
|
<strong className="uppercase">当前配置来源:</strong>
|
||||||
{configSource === 'runtime' ? (
|
{configSource === 'runtime' ? (
|
||||||
<Badge variant="default" className="ml-2">运行时配置</Badge>
|
<Badge variant="default" className="ml-2 rounded-none border-blue-800 bg-blue-600 text-white font-bold uppercase">运行时配置</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant="outline" className="ml-2">构建时配置</Badge>
|
<Badge variant="outline" className="ml-2 rounded-none border-blue-800 text-blue-800 font-bold uppercase">构建时配置</Badge>
|
||||||
)}
|
)}
|
||||||
<span className="ml-4 text-sm">
|
<span className="ml-4 text-sm font-bold">
|
||||||
{isConfigured ? (
|
{isConfigured ? (
|
||||||
<span className="text-green-600 flex items-center gap-1">
|
<span className="text-green-600 flex items-center gap-1">
|
||||||
<CheckCircle2 className="h-3 w-3" /> LLM 已配置
|
<CheckCircle2 className="h-4 w-4" /> LLM 已配置
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-orange-600 flex items-center gap-1">
|
<span className="text-orange-600 flex items-center gap-1">
|
||||||
<AlertCircle className="h-3 w-3" /> 未配置 LLM
|
<AlertCircle className="h-4 w-4" /> 未配置 LLM
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<Button onClick={saveConfig} size="sm">
|
<Button onClick={saveConfig} size="sm" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-8 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-3 h-3 mr-2" />
|
||||||
保存配置
|
保存配置
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{configSource === 'runtime' && (
|
{configSource === 'runtime' && (
|
||||||
<Button onClick={resetConfig} variant="outline" size="sm">
|
<Button onClick={resetConfig} variant="outline" size="sm" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-8 font-bold uppercase">
|
||||||
<RotateCcw className="w-4 h-4 mr-2" />
|
<RotateCcw className="w-3 h-3 mr-2" />
|
||||||
重置
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</div>
|
||||||
</Alert>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue="llm" className="w-full">
|
<Tabs defaultValue="llm" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
<TabsList className="grid w-full grid-cols-4 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
|
||||||
<TabsTrigger value="llm">
|
<TabsTrigger value="llm" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
<Zap className="w-4 h-4 mr-2" />
|
<Zap className="w-3 h-3 mr-2" />
|
||||||
LLM 配置
|
LLM 配置
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="platforms">
|
<TabsTrigger value="platforms" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
<Key className="w-4 h-4 mr-2" />
|
<Key className="w-3 h-3 mr-2" />
|
||||||
平台密钥
|
平台密钥
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="analysis">
|
<TabsTrigger value="analysis" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<Settings className="w-3 h-3 mr-2" />
|
||||||
分析参数
|
分析参数
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="other">
|
<TabsTrigger value="other" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
<Globe className="w-4 h-4 mr-2" />
|
<Globe className="w-3 h-3 mr-2" />
|
||||||
其他配置
|
其他配置
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* LLM 基础配置 */}
|
{/* LLM 基础配置 */}
|
||||||
<TabsContent value="llm" className="space-y-6">
|
<TabsContent value="llm" className="space-y-6">
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>LLM 提供商配置</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">LLM 提供商配置</h3>
|
||||||
<CardDescription>选择和配置大语言模型服务</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">选择和配置大语言模型服务</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>当前使用的 LLM 提供商</Label>
|
<Label className="font-bold uppercase">当前使用的 LLM 提供商</Label>
|
||||||
<Select
|
<Select
|
||||||
value={config.llmProvider}
|
value={config.llmProvider}
|
||||||
onValueChange={(value) => updateConfig('llmProvider', value)}
|
onValueChange={(value) => updateConfig('llmProvider', value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="retro-card border-2 border-black rounded-none">
|
||||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground">国际平台</div>
|
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase">国际平台</div>
|
||||||
{LLM_PROVIDERS.filter(p => p.category === 'international').map(provider => (
|
{LLM_PROVIDERS.filter(p => p.category === 'international').map(provider => (
|
||||||
<SelectItem key={provider.value} value={provider.value}>
|
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||||
{provider.icon} {provider.label}
|
{provider.icon} {provider.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2">国内平台</div>
|
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2">国内平台</div>
|
||||||
{LLM_PROVIDERS.filter(p => p.category === 'domestic').map(provider => (
|
{LLM_PROVIDERS.filter(p => p.category === 'domestic').map(provider => (
|
||||||
<SelectItem key={provider.value} value={provider.value}>
|
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||||
{provider.icon} {provider.label}
|
{provider.icon} {provider.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2">本地部署</div>
|
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2">本地部署</div>
|
||||||
{LLM_PROVIDERS.filter(p => p.category === 'local').map(provider => (
|
{LLM_PROVIDERS.filter(p => p.category === 'local').map(provider => (
|
||||||
<SelectItem key={provider.value} value={provider.value}>
|
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||||
{provider.icon} {provider.label}
|
{provider.icon} {provider.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -420,58 +418,62 @@ export function SystemConfig() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>通用 API Key(可选)</Label>
|
<Label className="font-bold uppercase">通用 API Key(可选)</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
type={showApiKeys['llm'] ? 'text' : 'password'}
|
type={showApiKeys['llm'] ? 'text' : 'password'}
|
||||||
value={config.llmApiKey}
|
value={config.llmApiKey}
|
||||||
onChange={(e) => updateConfig('llmApiKey', e.target.value)}
|
onChange={(e) => updateConfig('llmApiKey', e.target.value)}
|
||||||
placeholder="留空则使用平台专用 API Key"
|
placeholder="留空则使用平台专用 API Key"
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => toggleShowApiKey('llm')}
|
onClick={() => toggleShowApiKey('llm')}
|
||||||
|
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||||
>
|
>
|
||||||
{showApiKeys['llm'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
{showApiKeys['llm'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
如果设置,将优先使用此 API Key;否则使用下方对应平台的专用 API Key
|
如果设置,将优先使用此 API Key;否则使用下方对应平台的专用 API Key
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>模型名称(可选)</Label>
|
<Label className="font-bold uppercase">模型名称(可选)</Label>
|
||||||
<Input
|
<Input
|
||||||
value={config.llmModel}
|
value={config.llmModel}
|
||||||
onChange={(e) => updateConfig('llmModel', e.target.value)}
|
onChange={(e) => updateConfig('llmModel', e.target.value)}
|
||||||
placeholder={`默认:${DEFAULT_MODELS[config.llmProvider as keyof typeof DEFAULT_MODELS] || '自动'}`}
|
placeholder={`默认:${DEFAULT_MODELS[config.llmProvider as keyof typeof DEFAULT_MODELS] || '自动'}`}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
留空使用默认模型
|
留空使用默认模型
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>API 基础 URL(推荐配置)</Label>
|
<Label className="font-bold uppercase">API 基础 URL(推荐配置)</Label>
|
||||||
<Input
|
<Input
|
||||||
value={config.llmBaseUrl}
|
value={config.llmBaseUrl}
|
||||||
onChange={(e) => updateConfig('llmBaseUrl', e.target.value)}
|
onChange={(e) => updateConfig('llmBaseUrl', e.target.value)}
|
||||||
placeholder="例如:https://api.example.com/v1"
|
placeholder="例如:https://api.example.com/v1"
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<div className="text-xs text-muted-foreground space-y-1">
|
<div className="text-xs text-gray-500 font-mono space-y-1">
|
||||||
<p>💡 <strong>使用 API 中转站?</strong>在这里填入中转站地址。配置保存后会在实际使用时自动验证。</p>
|
<p>💡 <strong>使用 API 中转站?</strong>在这里填入中转站地址。配置保存后会在实际使用时自动验证。</p>
|
||||||
<details className="cursor-pointer">
|
<details className="cursor-pointer">
|
||||||
<summary className="text-primary hover:underline">查看常见 API 中转示例</summary>
|
<summary className="text-primary hover:underline font-bold">查看常见 API 中转示例</summary>
|
||||||
<div className="mt-2 p-3 bg-muted rounded space-y-1 text-xs">
|
<div className="mt-2 p-3 bg-gray-100 border-2 border-black rounded-none space-y-1 text-xs shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p><strong>OpenAI 兼容格式:</strong></p>
|
<p><strong>OpenAI 兼容格式:</strong></p>
|
||||||
<p>• https://your-proxy.com/v1</p>
|
<p>• https://your-proxy.com/v1</p>
|
||||||
<p>• https://api.openai-proxy.org/v1</p>
|
<p>• https://api.openai-proxy.org/v1</p>
|
||||||
<p className="pt-2"><strong>其他中转格式:</strong></p>
|
<p className="pt-2"><strong>其他中转格式:</strong></p>
|
||||||
<p>• https://your-api-gateway.com/openai</p>
|
<p>• https://your-api-gateway.com/openai</p>
|
||||||
<p>• https://custom-endpoint.com/api</p>
|
<p>• https://custom-endpoint.com/api</p>
|
||||||
<p className="pt-2 text-orange-600">⚠️ 确保中转站支持你选择的 LLM 平台</p>
|
<p className="pt-2 text-orange-600 font-bold">⚠️ 确保中转站支持你选择的 LLM 平台</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -479,15 +481,16 @@ export function SystemConfig() {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>超时时间(毫秒)</Label>
|
<Label className="font-bold uppercase">超时时间(毫秒)</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={config.llmTimeout}
|
value={config.llmTimeout}
|
||||||
onChange={(e) => updateConfig('llmTimeout', Number(e.target.value))}
|
onChange={(e) => updateConfig('llmTimeout', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>温度参数(0-2)</Label>
|
<Label className="font-bold uppercase">温度参数(0-2)</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
|
|
@ -495,49 +498,53 @@ export function SystemConfig() {
|
||||||
max="2"
|
max="2"
|
||||||
value={config.llmTemperature}
|
value={config.llmTemperature}
|
||||||
onChange={(e) => updateConfig('llmTemperature', Number(e.target.value))}
|
onChange={(e) => updateConfig('llmTemperature', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>最大 Tokens</Label>
|
<Label className="font-bold uppercase">最大 Tokens</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={config.llmMaxTokens}
|
value={config.llmMaxTokens}
|
||||||
onChange={(e) => updateConfig('llmMaxTokens', Number(e.target.value))}
|
onChange={(e) => updateConfig('llmMaxTokens', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 pt-4 border-t">
|
<div className="space-y-2 pt-4 border-t-2 border-black border-dashed">
|
||||||
<Label>自定义请求头(高级,可选)</Label>
|
<Label className="font-bold uppercase">自定义请求头(高级,可选)</Label>
|
||||||
<Input
|
<Input
|
||||||
value={config.llmCustomHeaders}
|
value={config.llmCustomHeaders}
|
||||||
onChange={(e) => updateConfig('llmCustomHeaders', e.target.value)}
|
onChange={(e) => updateConfig('llmCustomHeaders', e.target.value)}
|
||||||
placeholder='{"X-Custom-Header": "value", "Another-Header": "value2"}'
|
placeholder='{"X-Custom-Header": "value", "Another-Header": "value2"}'
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
JSON 格式,用于某些中转站或自建服务的特殊要求。例如:<code className="bg-muted px-1 py-0.5 rounded">{"X-API-Version": "v1"}</code>
|
JSON 格式,用于某些中转站或自建服务的特殊要求。例如:<code className="bg-gray-200 px-1 py-0.5 border border-black">{"X-API-Version": "v1"}</code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 平台专用密钥 */}
|
{/* 平台专用密钥 */}
|
||||||
<TabsContent value="platforms" className="space-y-6">
|
<TabsContent value="platforms" className="space-y-6">
|
||||||
<Alert>
|
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||||
<Key className="h-4 w-4" />
|
<Key className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||||
<AlertDescription>
|
<div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1 font-mono text-sm text-blue-800">
|
||||||
<p>配置各平台的 API Key,方便快速切换。如果设置了通用 API Key,将优先使用通用配置。</p>
|
<p className="font-bold uppercase">配置各平台的 API Key</p>
|
||||||
<p className="text-xs text-muted-foreground pt-1">
|
<p>方便快速切换。如果设置了通用 API Key,将优先使用通用配置。</p>
|
||||||
|
<p className="text-xs text-blue-700 pt-1 font-bold">
|
||||||
💡 <strong>使用 API 中转站的用户注意:</strong>这里填入的应该是<strong>中转站提供的 API Key</strong>,而不是官方 Key。
|
💡 <strong>使用 API 中转站的用户注意:</strong>这里填入的应该是<strong>中转站提供的 API Key</strong>,而不是官方 Key。
|
||||||
中转站地址请在「LLM 配置」标签页的「API 基础 URL」中填写。
|
中转站地址请在「LLM 配置」标签页的「API 基础 URL」中填写。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</div>
|
||||||
</Alert>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{[
|
{[
|
||||||
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方:https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
|
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方:https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
|
||||||
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方:https://platform.openai.com/api-keys | 或使用中转站 Key' },
|
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方:https://platform.openai.com/api-keys | 或使用中转站 Key' },
|
||||||
|
|
@ -550,223 +557,234 @@ export function SystemConfig() {
|
||||||
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方:https://www.minimaxi.com/ | 或使用中转站 Key' },
|
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方:https://www.minimaxi.com/ | 或使用中转站 Key' },
|
||||||
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方:https://console.volcengine.com/ark | 或使用中转站 Key' },
|
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方:https://console.volcengine.com/ark | 或使用中转站 Key' },
|
||||||
].map(({ key, label, icon, hint }) => (
|
].map(({ key, label, icon, hint }) => (
|
||||||
<Card key={key}>
|
<div key={key} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="text-base flex items-center gap-2">
|
<h3 className="text-sm font-display font-bold uppercase flex items-center gap-2">
|
||||||
<span>{icon}</span>
|
<span>{icon}</span>
|
||||||
{label}
|
{label}
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription className="text-xs">{hint}</CardDescription>
|
<p className="text-[10px] text-gray-500 font-mono mt-1 truncate" title={hint}>{hint}</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
type={showApiKeys[key] ? 'text' : 'password'}
|
type={showApiKeys[key] ? 'text' : 'password'}
|
||||||
value={config[key as keyof SystemConfigData] as string}
|
value={config[key as keyof SystemConfigData] as string}
|
||||||
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
|
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
|
||||||
placeholder={`输入 ${label}`}
|
placeholder={`输入 ${label}`}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => toggleShowApiKey(key)}
|
onClick={() => toggleShowApiKey(key)}
|
||||||
|
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10 flex-shrink-0"
|
||||||
>
|
>
|
||||||
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="text-base flex items-center gap-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||||
<span>🖥️</span>
|
<span>🖥️</span>
|
||||||
Ollama 基础 URL
|
Ollama 基础 URL
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription className="text-xs">本地 Ollama 服务的 API 端点</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">本地 Ollama 服务的 API 端点</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6">
|
||||||
<Input
|
<Input
|
||||||
value={config.ollamaBaseUrl}
|
value={config.ollamaBaseUrl}
|
||||||
onChange={(e) => updateConfig('ollamaBaseUrl', e.target.value)}
|
onChange={(e) => updateConfig('ollamaBaseUrl', e.target.value)}
|
||||||
placeholder="http://localhost:11434/v1"
|
placeholder="http://localhost:11434/v1"
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 分析参数配置 */}
|
{/* 分析参数配置 */}
|
||||||
<TabsContent value="analysis" className="space-y-6">
|
<TabsContent value="analysis" className="space-y-6">
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>代码分析参数</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">代码分析参数</h3>
|
||||||
<CardDescription>调整代码分析的行为和性能</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">调整代码分析的行为和性能</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>最大分析文件数</Label>
|
<Label className="font-bold uppercase">最大分析文件数</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={config.maxAnalyzeFiles}
|
value={config.maxAnalyzeFiles}
|
||||||
onChange={(e) => updateConfig('maxAnalyzeFiles', Number(e.target.value))}
|
onChange={(e) => updateConfig('maxAnalyzeFiles', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
单次分析任务最多处理的文件数量
|
单次分析任务最多处理的文件数量
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>LLM 并发请求数</Label>
|
<Label className="font-bold uppercase">LLM 并发请求数</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={config.llmConcurrency}
|
value={config.llmConcurrency}
|
||||||
onChange={(e) => updateConfig('llmConcurrency', Number(e.target.value))}
|
onChange={(e) => updateConfig('llmConcurrency', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
同时发送给 LLM 的请求数量(降低可避免速率限制)
|
同时发送给 LLM 的请求数量(降低可避免速率限制)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>请求间隔(毫秒)</Label>
|
<Label className="font-bold uppercase">请求间隔(毫秒)</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={config.llmGapMs}
|
value={config.llmGapMs}
|
||||||
onChange={(e) => updateConfig('llmGapMs', Number(e.target.value))}
|
onChange={(e) => updateConfig('llmGapMs', Number(e.target.value))}
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
每个 LLM 请求之间的延迟时间
|
每个 LLM 请求之间的延迟时间
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>输出语言</Label>
|
<Label className="font-bold uppercase">输出语言</Label>
|
||||||
<Select
|
<Select
|
||||||
value={config.outputLanguage}
|
value={config.outputLanguage}
|
||||||
onValueChange={(value) => updateConfig('outputLanguage', value)}
|
onValueChange={(value) => updateConfig('outputLanguage', value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="retro-card border-2 border-black rounded-none">
|
||||||
<SelectItem value="zh-CN">🇨🇳 中文</SelectItem>
|
<SelectItem value="zh-CN" className="font-mono focus:bg-primary/20 focus:text-black">🇨🇳 中文</SelectItem>
|
||||||
<SelectItem value="en-US">🇺🇸 English</SelectItem>
|
<SelectItem value="en-US" className="font-mono focus:bg-primary/20 focus:text-black">🇺🇸 English</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 其他配置 */}
|
{/* 其他配置 */}
|
||||||
<TabsContent value="other" className="space-y-6">
|
<TabsContent value="other" className="space-y-6">
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>GitHub 集成</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">GitHub 集成</h3>
|
||||||
<CardDescription>配置 GitHub Personal Access Token 以访问私有仓库</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">配置 GitHub Personal Access Token 以访问私有仓库</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>GitHub Token(可选)</Label>
|
<Label className="font-bold uppercase">GitHub Token(可选)</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
type={showApiKeys['github'] ? 'text' : 'password'}
|
type={showApiKeys['github'] ? 'text' : 'password'}
|
||||||
value={config.githubToken}
|
value={config.githubToken}
|
||||||
onChange={(e) => updateConfig('githubToken', e.target.value)}
|
onChange={(e) => updateConfig('githubToken', e.target.value)}
|
||||||
placeholder="ghp_xxxxxxxxxxxx"
|
placeholder="ghp_xxxxxxxxxxxx"
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => toggleShowApiKey('github')}
|
onClick={() => toggleShowApiKey('github')}
|
||||||
|
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||||
>
|
>
|
||||||
{showApiKeys['github'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
{showApiKeys['github'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
获取:https://github.com/settings/tokens
|
获取:https://github.com/settings/tokens
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>GitLab 集成</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">GitLab 集成</h3>
|
||||||
<CardDescription>配置 GitLab Personal Access Token 以访问私有仓库</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">配置 GitLab Personal Access Token 以访问私有仓库</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>GitLab Token(可选)</Label>
|
<Label className="font-bold uppercase">GitLab Token(可选)</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
type={showApiKeys['gitlab'] ? 'text' : 'password'}
|
type={showApiKeys['gitlab'] ? 'text' : 'password'}
|
||||||
value={config.gitlabToken}
|
value={config.gitlabToken}
|
||||||
onChange={(e) => updateConfig('gitlabToken', e.target.value)}
|
onChange={(e) => updateConfig('gitlabToken', e.target.value)}
|
||||||
placeholder="glpat-xxxxxxxxxxxx"
|
placeholder="glpat-xxxxxxxxxxxx"
|
||||||
|
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => toggleShowApiKey('gitlab')}
|
onClick={() => toggleShowApiKey('gitlab')}
|
||||||
|
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||||
>
|
>
|
||||||
{showApiKeys['gitlab'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
{showApiKeys['gitlab'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-bold">
|
||||||
获取:https://gitlab.com/-/profile/personal_access_tokens
|
获取:https://gitlab.com/-/profile/personal_access_tokens
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>配置说明</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">配置说明</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Database className="h-5 w-5 text-primary mt-0.5" />
|
<Database className="h-5 w-5 text-primary mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">用户配置</p>
|
<p className="font-bold text-black uppercase">用户配置</p>
|
||||||
<p>
|
<p className="text-xs mt-1">
|
||||||
配置保存在后端数据库中,与用户账号绑定。
|
配置保存在后端数据库中,与用户账号绑定。
|
||||||
可以在不重新构建 Docker 镜像的情况下修改配置,配置会在所有分析任务中生效。
|
可以在不重新构建 Docker 镜像的情况下修改配置,配置会在所有分析任务中生效。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Settings className="h-5 w-5 text-green-600 mt-0.5" />
|
<Settings className="h-5 w-5 text-green-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">配置优先级</p>
|
<p className="font-bold text-black uppercase">配置优先级</p>
|
||||||
<p>
|
<p className="text-xs mt-1">
|
||||||
运行时配置 > 构建时配置。如果设置了运行时配置,将覆盖构建时的环境变量。
|
运行时配置 > 构建时配置。如果设置了运行时配置,将覆盖构建时的环境变量。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Key className="h-5 w-5 text-orange-600 mt-0.5" />
|
<Key className="h-5 w-5 text-orange-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">安全提示</p>
|
<p className="font-bold text-black uppercase">安全提示</p>
|
||||||
<p>
|
<p className="text-xs mt-1">
|
||||||
API Keys 存储在浏览器本地,其他网站无法访问。但清除浏览器数据会删除所有配置。
|
API Keys 存储在浏览器本地,其他网站无法访问。但清除浏览器数据会删除所有配置。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* 底部操作按钮 */}
|
{/* 底部操作按钮 */}
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<div className="fixed bottom-6 right-6 flex gap-3 bg-background border rounded-lg shadow-lg p-4">
|
<div className="fixed bottom-6 right-6 flex gap-3 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 z-50">
|
||||||
<Button onClick={saveConfig} size="lg">
|
<Button onClick={saveConfig} size="lg" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-12 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
保存所有更改
|
保存所有更改
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={loadConfig} variant="outline" size="lg">
|
<Button onClick={loadConfig} variant="outline" size="lg" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-12 font-bold uppercase">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import {
|
import {
|
||||||
HardDrive,
|
HardDrive,
|
||||||
|
|
@ -97,10 +95,10 @@ export default function AdminDashboard() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||||
<div className="space-y-4 text-center">
|
<div className="space-y-4 text-center">
|
||||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-primary mx-auto"></div>
|
<div className="animate-spin rounded-none h-16 w-16 border-8 border-black border-t-transparent mx-auto"></div>
|
||||||
<p className="text-muted-foreground">加载数据库信息...</p>
|
<p className="text-black font-mono font-bold uppercase">加载数据库信息...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -109,17 +107,17 @@ export default function AdminDashboard() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 pb-8">
|
<div className="space-y-6 pb-8">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-3">
|
||||||
<Settings className="h-8 w-8 text-primary" />
|
<Settings className="h-8 w-8 text-black" />
|
||||||
系统管理
|
系统管理
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 mt-2">
|
<p className="text-gray-600 mt-2 font-mono border-l-2 border-primary pl-2">
|
||||||
管理系统配置、LLM设置、数据库和存储使用情况
|
管理系统配置、LLM设置、数据库和存储使用情况
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" onClick={loadStats}>
|
<Button variant="outline" onClick={loadStats} className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<RefreshCw className="w-4 h-4 mr-2" />
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
刷新数据
|
刷新数据
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -127,12 +125,12 @@ export default function AdminDashboard() {
|
||||||
|
|
||||||
{/* 主要内容标签页 */}
|
{/* 主要内容标签页 */}
|
||||||
<Tabs defaultValue="config" className="w-full">
|
<Tabs defaultValue="config" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-5">
|
<TabsList className="grid w-full grid-cols-5 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
|
||||||
<TabsTrigger value="config">系统配置</TabsTrigger>
|
<TabsTrigger value="config" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">系统配置</TabsTrigger>
|
||||||
<TabsTrigger value="overview">数据概览</TabsTrigger>
|
<TabsTrigger value="overview" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">数据概览</TabsTrigger>
|
||||||
<TabsTrigger value="storage">存储管理</TabsTrigger>
|
<TabsTrigger value="storage" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">存储管理</TabsTrigger>
|
||||||
<TabsTrigger value="operations">数据操作</TabsTrigger>
|
<TabsTrigger value="operations" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">数据操作</TabsTrigger>
|
||||||
<TabsTrigger value="settings">高级设置</TabsTrigger>
|
<TabsTrigger value="settings" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">高级设置</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* 系统配置 */}
|
{/* 系统配置 */}
|
||||||
|
|
@ -144,19 +142,19 @@ export default function AdminDashboard() {
|
||||||
<TabsContent value="overview" className="space-y-6">
|
<TabsContent value="overview" className="space-y-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* 任务完成率 */}
|
{/* 任务完成率 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||||
<TrendingUp className="h-5 w-5" />
|
<TrendingUp className="h-5 w-5" />
|
||||||
任务完成率
|
任务完成率
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription>审计任务的完成情况统计</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">审计任务的完成情况统计</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm font-bold">
|
||||||
<span>已完成</span>
|
<span>已完成</span>
|
||||||
<span className="font-medium">
|
<span>
|
||||||
{stats.totalTasks > 0
|
{stats.totalTasks > 0
|
||||||
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
|
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
|
||||||
: 0}%
|
: 0}%
|
||||||
|
|
@ -167,35 +165,36 @@ export default function AdminDashboard() {
|
||||||
? (stats.completedTasks / stats.totalTasks) * 100
|
? (stats.completedTasks / stats.totalTasks) * 100
|
||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
|
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-green-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">总任务数</p>
|
<p className="text-xs text-gray-500 uppercase font-bold">总任务数</p>
|
||||||
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">已完成</p>
|
<p className="text-xs text-gray-500 uppercase font-bold">已完成</p>
|
||||||
<p className="text-2xl font-bold text-green-600">{stats.completedTasks}</p>
|
<p className="text-2xl font-bold text-green-600">{stats.completedTasks}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 问题解决率 */}
|
{/* 问题解决率 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5" />
|
<CheckCircle2 className="h-5 w-5" />
|
||||||
问题解决率
|
问题解决率
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription>代码问题的解决情况统计</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">代码问题的解决情况统计</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm font-bold">
|
||||||
<span>已解决</span>
|
<span>已解决</span>
|
||||||
<span className="font-medium">
|
<span>
|
||||||
{stats.totalIssues > 0
|
{stats.totalIssues > 0
|
||||||
? Math.round((stats.resolvedIssues / stats.totalIssues) * 100)
|
? Math.round((stats.resolvedIssues / stats.totalIssues) * 100)
|
||||||
: 0}%
|
: 0}%
|
||||||
|
|
@ -206,104 +205,104 @@ export default function AdminDashboard() {
|
||||||
? (stats.resolvedIssues / stats.totalIssues) * 100
|
? (stats.resolvedIssues / stats.totalIssues) * 100
|
||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
className="bg-orange-100"
|
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-orange-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">总问题数</p>
|
<p className="text-xs text-gray-500 uppercase font-bold">总问题数</p>
|
||||||
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">已解决</p>
|
<p className="text-xs text-gray-500 uppercase font-bold">已解决</p>
|
||||||
<p className="text-2xl font-bold text-green-600">{stats.resolvedIssues}</p>
|
<p className="text-2xl font-bold text-green-600">{stats.resolvedIssues}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 数据库表统计 */}
|
{/* 数据库表统计 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||||
<Package className="h-5 w-5" />
|
<Package className="h-5 w-5" />
|
||||||
数据库表统计
|
数据库表统计
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription>各数据表的记录数量</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">各数据表的记录数量</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6 font-mono">
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
<div className="p-4 border rounded-lg">
|
<div className="p-4 border-2 border-black bg-blue-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FolderOpen className="h-8 w-8 text-primary" />
|
<FolderOpen className="h-8 w-8 text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">项目</p>
|
<p className="text-xs text-gray-600 uppercase font-bold">项目</p>
|
||||||
<p className="text-2xl font-bold">{stats.totalProjects}</p>
|
<p className="text-2xl font-bold">{stats.totalProjects}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border rounded-lg">
|
<div className="p-4 border-2 border-black bg-green-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Clock className="h-8 w-8 text-green-600" />
|
<Clock className="h-8 w-8 text-green-600" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">审计任务</p>
|
<p className="text-xs text-gray-600 uppercase font-bold">审计任务</p>
|
||||||
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border rounded-lg">
|
<div className="p-4 border-2 border-black bg-orange-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<AlertTriangle className="h-8 w-8 text-orange-600" />
|
<AlertTriangle className="h-8 w-8 text-orange-600" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">问题</p>
|
<p className="text-xs text-gray-600 uppercase font-bold">问题</p>
|
||||||
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 存储管理 */}
|
{/* 存储管理 */}
|
||||||
<TabsContent value="storage" className="space-y-6">
|
<TabsContent value="storage" className="space-y-6">
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||||
<HardDrive className="h-5 w-5" />
|
<HardDrive className="h-5 w-5" />
|
||||||
存储空间使用情况
|
存储空间使用情况
|
||||||
</CardTitle>
|
</h3>
|
||||||
<CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">
|
||||||
浏览器 IndexedDB 存储空间的使用详情
|
浏览器 IndexedDB 存储空间的使用详情
|
||||||
</CardDescription>
|
</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-6">
|
<div className="p-6 space-y-6 font-mono">
|
||||||
{storageDetails ? (
|
{storageDetails ? (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm font-bold">
|
||||||
<span>已使用空间</span>
|
<span>已使用空间</span>
|
||||||
<span className="font-medium">{storageDetails.percentage}%</span>
|
<span>{storageDetails.percentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={storageDetails.percentage} />
|
<Progress value={storageDetails.percentage} className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
<div className="flex items-center justify-between text-xs text-gray-500 font-bold">
|
||||||
<span>{stats.storageUsed} 已使用</span>
|
<span>{stats.storageUsed} 已使用</span>
|
||||||
<span>{stats.storageQuota} 总配额</span>
|
<span>{stats.storageQuota} 总配额</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4">
|
||||||
<div className="p-4 bg-muted rounded-lg">
|
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-sm text-muted-foreground mb-1">已使用</p>
|
<p className="text-xs text-gray-600 uppercase font-bold mb-1">已使用</p>
|
||||||
<p className="text-xl font-bold">{stats.storageUsed}</p>
|
<p className="text-xl font-bold">{stats.storageUsed}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-muted rounded-lg">
|
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-sm text-muted-foreground mb-1">总配额</p>
|
<p className="text-xs text-gray-600 uppercase font-bold mb-1">总配额</p>
|
||||||
<p className="text-xl font-bold">{stats.storageQuota}</p>
|
<p className="text-xl font-bold">{stats.storageQuota}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-muted rounded-lg">
|
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-sm text-muted-foreground mb-1">剩余空间</p>
|
<p className="text-xs text-gray-600 uppercase font-bold mb-1">剩余空间</p>
|
||||||
<p className="text-xl font-bold">
|
<p className="text-xl font-bold">
|
||||||
{((storageDetails.quota - storageDetails.usage) / 1024 / 1024).toFixed(2)} MB
|
{((storageDetails.quota - storageDetails.usage) / 1024 / 1024).toFixed(2)} MB
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -311,59 +310,65 @@ export default function AdminDashboard() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{storageDetails.percentage > 80 && (
|
{storageDetails.percentage > 80 && (
|
||||||
<Alert variant="destructive">
|
<div className="bg-red-50 border-2 border-red-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5" />
|
||||||
<AlertDescription>
|
<div>
|
||||||
|
<p className="font-bold text-red-800 uppercase">警告</p>
|
||||||
|
<p className="text-sm text-red-700 font-medium">
|
||||||
存储空间使用率已超过 80%,建议清理不需要的数据或导出备份后清空数据库。
|
存储空间使用率已超过 80%,建议清理不需要的数据或导出备份后清空数据库。
|
||||||
</AlertDescription>
|
</p>
|
||||||
</Alert>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Alert>
|
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||||
<AlertDescription>
|
<div>
|
||||||
|
<p className="font-bold text-blue-800 uppercase">提示</p>
|
||||||
|
<p className="text-sm text-blue-700 font-medium">
|
||||||
无法获取存储空间信息。您的浏览器可能不支持 Storage API。
|
无法获取存储空间信息。您的浏览器可能不支持 Storage API。
|
||||||
</AlertDescription>
|
</p>
|
||||||
</Alert>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>存储优化建议</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">存储优化建议</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-3">
|
<div className="p-6 space-y-3 font-mono">
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">定期导出备份</p>
|
<p className="font-bold text-black uppercase text-sm">定期导出备份</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-600 font-medium">
|
||||||
建议定期导出数据为 JSON 文件,防止数据丢失
|
建议定期导出数据为 JSON 文件,防止数据丢失
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">清理旧数据</p>
|
<p className="font-bold text-black uppercase text-sm">清理旧数据</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-600 font-medium">
|
||||||
删除不再需要的项目和任务可以释放存储空间
|
删除不再需要的项目和任务可以释放存储空间
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">监控存储使用</p>
|
<p className="font-bold text-black uppercase text-sm">监控存储使用</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-600 font-medium">
|
||||||
定期检查存储使用情况,避免超出浏览器限制
|
定期检查存储使用情况,避免超出浏览器限制
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 数据操作 */}
|
{/* 数据操作 */}
|
||||||
|
|
@ -373,63 +378,66 @@ export default function AdminDashboard() {
|
||||||
|
|
||||||
{/* 设置 */}
|
{/* 设置 */}
|
||||||
<TabsContent value="settings" className="space-y-6">
|
<TabsContent value="settings" className="space-y-6">
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>数据库设置</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">数据库设置</h3>
|
||||||
<CardDescription>配置数据库行为和性能选项</CardDescription>
|
<p className="text-xs text-gray-500 font-mono mt-1">配置数据库行为和性能选项</p>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<Alert>
|
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||||
<AlertDescription>
|
<div>
|
||||||
<strong>当前数据库模式:</strong> {
|
<p className="font-bold text-blue-800 uppercase text-sm">当前数据库模式</p>
|
||||||
|
<p className="text-sm text-blue-700 font-medium mt-1">
|
||||||
|
{
|
||||||
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
|
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
|
||||||
dbMode === 'local' ? '本地 IndexedDB' :
|
dbMode === 'local' ? '本地 IndexedDB' :
|
||||||
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
|
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
|
||||||
'演示模式'
|
'演示模式'
|
||||||
}
|
}
|
||||||
</AlertDescription>
|
</p>
|
||||||
</Alert>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 pt-4">
|
<div className="space-y-4 pt-4">
|
||||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">自动备份</p>
|
<p className="font-bold text-black uppercase text-sm">自动备份</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-500 font-medium">
|
||||||
定期自动导出数据备份(开发中)
|
定期自动导出数据备份(开发中)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline">即将推出</Badge>
|
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">数据压缩</p>
|
<p className="font-bold text-black uppercase text-sm">数据压缩</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-500 font-medium">
|
||||||
压缩存储数据以节省空间(开发中)
|
压缩存储数据以节省空间(开发中)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline">即将推出</Badge>
|
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">数据同步</p>
|
<p className="font-bold text-black uppercase text-sm">数据同步</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs text-gray-500 font-medium">
|
||||||
在多个设备间同步数据(开发中)
|
在多个设备间同步数据(开发中)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline">即将推出</Badge>
|
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle>关于本地数据库</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">关于本地数据库</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
|
||||||
<p>
|
<p>
|
||||||
本地数据库使用浏览器的 IndexedDB 技术存储数据,具有以下特点:
|
本地数据库使用浏览器的 IndexedDB 技术存储数据,具有以下特点:
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -440,11 +448,11 @@ export default function AdminDashboard() {
|
||||||
<li>清除浏览器数据会删除所有本地数据</li>
|
<li>清除浏览器数据会删除所有本地数据</li>
|
||||||
<li>不同浏览器的数据相互独立</li>
|
<li>不同浏览器的数据相互独立</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p className="pt-2">
|
<p className="pt-2 border-t-2 border-black border-dashed mt-4">
|
||||||
<strong>建议:</strong>定期导出数据备份,以防意外数据丢失。
|
<strong className="text-black uppercase">建议:</strong>定期导出数据备份,以防意外数据丢失。
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -121,103 +121,95 @@ export default function AuditTasks() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
|
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in font-mono">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">审计任务</h1>
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">审计任务</h1>
|
||||||
<p className="page-subtitle">查看和管理所有代码审计任务的执行状态</p>
|
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">查看和管理所有代码审计任务的执行状态</p>
|
||||||
</div>
|
</div>
|
||||||
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
|
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-6 text-lg font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all" onClick={() => setShowCreateDialog(true)}>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-5 h-5 mr-2" />
|
||||||
新建任务
|
新建任务
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">总任务数</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">总任务数</p>
|
||||||
<p className="stat-value text-xl">{tasks.length}</p>
|
<p className="text-3xl font-bold text-black font-mono">{tasks.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Activity className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-primary to-accent">
|
|
||||||
<Activity className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">已完成</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">已完成</p>
|
||||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'completed').length}</p>
|
<p className="text-3xl font-bold text-green-600 font-mono">{tasks.filter(t => t.status === 'completed').length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-emerald-500 to-emerald-600">
|
|
||||||
<CheckCircle className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">运行中</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">运行中</p>
|
||||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'running').length}</p>
|
<p className="text-3xl font-bold text-orange-600 font-mono">{tasks.filter(t => t.status === 'running').length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Clock className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-orange-500 to-orange-600">
|
|
||||||
<Clock className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">失败</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">失败</p>
|
||||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'failed').length}</p>
|
<p className="text-3xl font-bold text-red-600 font-mono">{tasks.filter(t => t.status === 'failed').length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-red-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<AlertTriangle className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-red-500 to-red-600">
|
|
||||||
<AlertTriangle className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 搜索和筛选 */}
|
{/* 搜索和筛选 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardContent className="p-4">
|
<div className="flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex-1 relative w-full">
|
||||||
<div className="flex-1 relative">
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索项目名称或任务类型..."
|
placeholder="搜索项目名称或任务类型..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-10"
|
className="pl-10 retro-input h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0">
|
||||||
<Button
|
<Button
|
||||||
variant={statusFilter === "all" ? "default" : "outline"}
|
variant={statusFilter === "all" ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setStatusFilter("all")}
|
onClick={() => setStatusFilter("all")}
|
||||||
|
className={`retro-btn h-10 ${statusFilter === "all" ? "bg-black text-white" : "bg-white text-black hover:bg-gray-100"}`}
|
||||||
>
|
>
|
||||||
全部
|
全部
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -225,6 +217,7 @@ export default function AuditTasks() {
|
||||||
variant={statusFilter === "running" ? "default" : "outline"}
|
variant={statusFilter === "running" ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setStatusFilter("running")}
|
onClick={() => setStatusFilter("running")}
|
||||||
|
className={`retro-btn h-10 ${statusFilter === "running" ? "bg-orange-500 text-white" : "bg-white text-black hover:bg-orange-100"}`}
|
||||||
>
|
>
|
||||||
运行中
|
运行中
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -232,6 +225,7 @@ export default function AuditTasks() {
|
||||||
variant={statusFilter === "completed" ? "default" : "outline"}
|
variant={statusFilter === "completed" ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setStatusFilter("completed")}
|
onClick={() => setStatusFilter("completed")}
|
||||||
|
className={`retro-btn h-10 ${statusFilter === "completed" ? "bg-green-600 text-white" : "bg-white text-black hover:bg-green-100"}`}
|
||||||
>
|
>
|
||||||
已完成
|
已完成
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -239,39 +233,37 @@ export default function AuditTasks() {
|
||||||
variant={statusFilter === "failed" ? "default" : "outline"}
|
variant={statusFilter === "failed" ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setStatusFilter("failed")}
|
onClick={() => setStatusFilter("failed")}
|
||||||
|
className={`retro-btn h-10 ${statusFilter === "failed" ? "bg-red-600 text-white" : "bg-white text-black hover:bg-red-100"}`}
|
||||||
>
|
>
|
||||||
失败
|
失败
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 任务列表 */}
|
{/* 任务列表 */}
|
||||||
{filteredTasks.length > 0 ? (
|
{filteredTasks.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{filteredTasks.map((task) => (
|
{filteredTasks.map((task) => (
|
||||||
<Card key={task.id} className="card-modern group">
|
<div key={task.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
<div className="flex items-center justify-between mb-6 border-b-2 border-dashed border-gray-300 pb-4">
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
|
<div className={`w-12 h-12 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
|
||||||
task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
|
task.status === 'running' ? 'bg-orange-100 text-orange-600' :
|
||||||
task.status === 'running' ? 'bg-red-50 text-red-600' :
|
|
||||||
task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'
|
task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'
|
||||||
}`}>
|
}`}>
|
||||||
{getStatusIcon(task.status)}
|
{getStatusIcon(task.status)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-lg text-gray-900 group-hover:text-primary transition-colors">
|
<h3 className="font-bold text-xl text-black font-display uppercase">
|
||||||
{task.project?.name || '未知项目'}
|
{task.project?.name || '未知项目'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-600 font-mono font-bold">
|
||||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={getStatusColor(task.status)}>
|
<Badge className={`rounded-none border-2 border-black font-bold uppercase ${getStatusColor(task.status)}`}>
|
||||||
{task.status === 'completed' ? '已完成' :
|
{task.status === 'completed' ? '已完成' :
|
||||||
task.status === 'running' ? '运行中' :
|
task.status === 'running' ? '运行中' :
|
||||||
task.status === 'failed' ? '失败' :
|
task.status === 'failed' ? '失败' :
|
||||||
|
|
@ -279,48 +271,48 @@ export default function AuditTasks() {
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 font-mono">
|
||||||
<div className="text-center p-4 bg-gradient-to-br from-blue-50 to-blue-100/30 rounded-xl border border-blue-200">
|
<div className="text-center p-3 bg-blue-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
|
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
|
||||||
<p className="text-xs text-blue-700 font-medium">文件数</p>
|
<p className="text-xs text-blue-800 font-bold uppercase">文件数</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-4 bg-gradient-to-br from-purple-50 to-purple-100/30 rounded-xl border border-purple-200">
|
<div className="text-center p-3 bg-purple-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
|
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
|
||||||
<p className="text-xs text-purple-700 font-medium">代码行数</p>
|
<p className="text-xs text-purple-800 font-bold uppercase">代码行数</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-4 bg-gradient-to-br from-orange-50 to-orange-100/30 rounded-xl border border-orange-200">
|
<div className="text-center p-3 bg-orange-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
|
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
|
||||||
<p className="text-xs text-orange-700 font-medium">发现问题</p>
|
<p className="text-xs text-orange-800 font-bold uppercase">发现问题</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-4 bg-gradient-to-br from-green-50 to-green-100/30 rounded-xl border border-green-200">
|
<div className="text-center p-3 bg-green-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
|
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
|
||||||
<p className="text-xs text-green-700 font-medium">质量评分</p>
|
<p className="text-xs text-green-800 font-bold uppercase">质量评分</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 扫描进度 */}
|
{/* 扫描进度 */}
|
||||||
<div className="mb-6">
|
<div className="mb-6 font-mono">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-gray-700">扫描进度</span>
|
<span className="text-sm font-bold text-black uppercase">扫描进度</span>
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm font-bold text-gray-600">
|
||||||
{task.scanned_files || 0} / {task.total_files || 0} 文件
|
{task.scanned_files || 0} / {task.total_files || 0} 文件
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
<div className="w-full bg-gray-200 h-4 border-2 border-black">
|
||||||
<div
|
<div
|
||||||
className="bg-gradient-to-r from-primary to-accent h-2 rounded-full transition-all duration-300"
|
className="bg-primary h-full transition-all duration-300 border-r-2 border-black"
|
||||||
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
|
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right mt-1">
|
<div className="text-right mt-1">
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs font-bold text-gray-600">
|
||||||
{calculateTaskProgress(task.scanned_files, task.total_files)}% 完成
|
{calculateTaskProgress(task.scanned_files, task.total_files)}% 完成
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
<div className="flex items-center justify-between pt-4 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6 mt-6">
|
||||||
<div className="flex items-center space-x-6 text-sm text-gray-500">
|
<div className="flex items-center space-x-6 text-sm text-gray-600 font-mono font-bold">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
{formatDate(task.created_at)}
|
{formatDate(task.created_at)}
|
||||||
|
|
@ -335,44 +327,41 @@ export default function AuditTasks() {
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Link to={`/tasks/${task.id}`}>
|
<Link to={`/tasks/${task.id}`}>
|
||||||
<Button variant="outline" size="sm" className="btn-secondary">
|
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-9">
|
||||||
<FileText className="w-4 h-4 mr-2" />
|
<FileText className="w-4 h-4 mr-2" />
|
||||||
查看详情
|
查看详情
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
{task.project && (
|
{task.project && (
|
||||||
<Link to={`/projects/${task.project.id}`}>
|
<Link to={`/projects/${task.project.id}`}>
|
||||||
<Button size="sm" className="btn-primary">
|
<Button size="sm" className="retro-btn bg-black text-white hover:bg-gray-800 h-9">
|
||||||
查看项目
|
查看项目
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-16 flex flex-col items-center justify-center text-center">
|
||||||
<CardContent className="empty-state py-16">
|
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="empty-icon">
|
<Activity className="w-10 h-10 text-gray-400" />
|
||||||
<Activity className="w-8 h-8 text-primary" />
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
|
||||||
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
|
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6 max-w-md">
|
<p className="text-gray-500 mb-8 max-w-md font-mono">
|
||||||
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
|
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
|
||||||
</p>
|
</p>
|
||||||
{!searchTerm && statusFilter === "all" && (
|
{!searchTerm && statusFilter === "all" && (
|
||||||
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
|
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-8 text-lg font-bold uppercase" onClick={() => setShowCreateDialog(true)}>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-5 h-5 mr-2" />
|
||||||
创建任务
|
创建任务
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 新建任务对话框 */}
|
{/* 新建任务对话框 */}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
|
@ -11,13 +11,13 @@ import {
|
||||||
import {
|
import {
|
||||||
Activity, AlertTriangle, Clock, Code,
|
Activity, AlertTriangle, Clock, Code,
|
||||||
FileText, GitBranch, Shield, TrendingUp, Zap,
|
FileText, GitBranch, Shield, TrendingUp, Zap,
|
||||||
BarChart3, Target, ArrowUpRight, Calendar
|
BarChart3, Target, ArrowUpRight, Calendar, Terminal
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { api, dbMode, isDemoMode } from "@/shared/config/database";
|
import { api, dbMode, isDemoMode } from "@/shared/config/database";
|
||||||
import type { Project, AuditTask, ProjectStats } from "@/shared/types";
|
import type { Project, AuditTask, ProjectStats } from "@/shared/types";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
||||||
import { Info } from "lucide-react";
|
import { Info } from "lucide-react";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
|
|
@ -85,7 +85,7 @@ export default function Dashboard() {
|
||||||
.sort((a, b) => new Date(a.completed_at!).getTime() - new Date(b.completed_at!).getTime())
|
.sort((a, b) => new Date(a.completed_at!).getTime() - new Date(b.completed_at!).getTime())
|
||||||
.slice(-6); // 最近6个任务
|
.slice(-6); // 最近6个任务
|
||||||
|
|
||||||
const trendData = tasksByDate.map((task, index) => ({
|
const trendData = tasksByDate.map((task) => ({
|
||||||
date: new Date(task.completed_at!).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }),
|
date: new Date(task.completed_at!).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }),
|
||||||
score: task.quality_score
|
score: task.quality_score
|
||||||
}));
|
}));
|
||||||
|
|
@ -152,38 +152,44 @@ export default function Dashboard() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[60vh]">
|
<div className="flex items-center justify-center min-h-[60vh] font-mono">
|
||||||
<div className="text-center space-y-4">
|
<div className="text-center space-y-4">
|
||||||
<div className="relative w-16 h-16 mx-auto">
|
<div className="relative w-16 h-16 mx-auto">
|
||||||
<div className="absolute inset-0 border-4 border-blue-200 rounded-full"></div>
|
<div className="absolute inset-0 border-4 border-gray-200 rounded-none"></div>
|
||||||
<div className="absolute inset-0 border-4 border-blue-600 rounded-full border-t-transparent animate-spin"></div>
|
<div className="absolute inset-0 border-4 border-primary rounded-none border-t-transparent animate-spin"></div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600">加载仪表盘数据...</p>
|
<p className="text-gray-600 uppercase font-bold">加载仪表盘数据...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-6 p-6 bg-background min-h-screen font-mono relative overflow-hidden">
|
||||||
{/* Simplified Header */}
|
{/* Decorative Background */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">仪表盘</h1>
|
<h1 className="text-4xl font-display font-bold uppercase tracking-tighter mb-2">
|
||||||
<p className="page-subtitle">
|
系统<span className="text-primary">_仪表盘</span>
|
||||||
实时监控项目状态,掌握代码质量动态
|
</h1>
|
||||||
{isDemoMode && <Badge variant="outline" className="ml-2">演示模式</Badge>}
|
<p className="text-gray-500 font-mono text-sm flex items-center gap-2">
|
||||||
|
<Terminal className="w-4 h-4" />
|
||||||
|
// 监控状态 // 代码质量概览
|
||||||
|
{isDemoMode && <Badge variant="outline" className="ml-2 border-black bg-yellow-100 text-yellow-800 rounded-none">演示模式</Badge>}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Link to="/instant-analysis">
|
<Link to="/instant-analysis">
|
||||||
<Button className="btn-primary">
|
<Button className="retro-btn h-12">
|
||||||
<Zap className="w-4 h-4 mr-2" />
|
<Zap className="w-4 h-4 mr-2" />
|
||||||
即时分析
|
即时分析
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/projects">
|
<Link to="/projects">
|
||||||
<Button variant="outline" className="btn-secondary">
|
<Button variant="outline" className="retro-btn bg-white text-black hover:bg-gray-100 h-12">
|
||||||
<GitBranch className="w-4 h-4 mr-2" />
|
<GitBranch className="w-4 h-4 mr-2" />
|
||||||
新建项目
|
新建项目
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -193,88 +199,80 @@ export default function Dashboard() {
|
||||||
|
|
||||||
{/* 数据库模式提示 */}
|
{/* 数据库模式提示 */}
|
||||||
{isDemoMode && (
|
{isDemoMode && (
|
||||||
<Alert>
|
<div className="relative z-10 bg-yellow-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] flex items-start gap-3">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-5 w-5 text-black mt-0.5" />
|
||||||
<AlertDescription>
|
<div className="text-sm font-mono text-black">
|
||||||
当前使用<strong>演示模式</strong>,显示的是模拟数据。
|
当前使用<strong>演示模式</strong>,显示的是模拟数据。
|
||||||
配置数据库后将显示真实数据。
|
配置数据库后将显示真实数据。
|
||||||
<Link to="/admin" className="ml-2 text-primary hover:underline">
|
<Link to="/admin" className="ml-2 text-primary font-bold hover:underline uppercase">
|
||||||
前往数据库管理 →
|
前往数据库管理 >
|
||||||
</Link>
|
</Link>
|
||||||
</AlertDescription>
|
</div>
|
||||||
</Alert>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 relative z-10">
|
||||||
<Card className="stat-card group">
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">总项目数</p>
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">总项目数</p>
|
||||||
<p className="stat-value">{stats?.total_projects || 0}</p>
|
<p className="font-display text-3xl font-bold">{stats?.total_projects || 0}</p>
|
||||||
<p className="text-xs text-gray-500 mt-1">活跃 {stats?.active_projects || 0} 个</p>
|
<p className="text-xs font-mono mt-1 border-l-2 border-primary pl-2">活跃: {stats?.active_projects || 0}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-primary flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Code className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-primary to-accent group-hover:scale-110 transition-transform">
|
|
||||||
<Code className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card group">
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">审计任务</p>
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">审计任务</p>
|
||||||
<p className="stat-value">{stats?.total_tasks || 0}</p>
|
<p className="font-display text-3xl font-bold">{stats?.total_tasks || 0}</p>
|
||||||
<p className="text-xs text-gray-500 mt-1">已完成 {stats?.completed_tasks || 0} 个</p>
|
<p className="text-xs font-mono mt-1 border-l-2 border-green-500 pl-2">已完成: {stats?.completed_tasks || 0}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-green-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Activity className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-emerald-500 to-emerald-600 group-hover:scale-110 transition-transform">
|
|
||||||
<Activity className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card group">
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">发现问题</p>
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">发现问题</p>
|
||||||
<p className="stat-value">{stats?.total_issues || 0}</p>
|
<p className="font-display text-3xl font-bold">{stats?.total_issues || 0}</p>
|
||||||
<p className="text-xs text-gray-500 mt-1">已解决 {stats?.resolved_issues || 0} 个</p>
|
<p className="text-xs font-mono mt-1 border-l-2 border-orange-500 pl-2">已解决: {stats?.resolved_issues || 0}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-orange-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<AlertTriangle className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-orange-500 to-orange-600 group-hover:scale-110 transition-transform">
|
|
||||||
<AlertTriangle className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card group">
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">平均质量分</p>
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">平均质量分</p>
|
||||||
<p className="stat-value">
|
<p className="font-display text-3xl font-bold">
|
||||||
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
|
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
|
||||||
</p>
|
</p>
|
||||||
{stats?.avg_quality_score ? (
|
{stats?.avg_quality_score ? (
|
||||||
<div className="flex items-center text-xs text-emerald-600 font-medium mt-1">
|
<div className="flex items-center text-xs font-mono font-bold text-emerald-600 mt-1">
|
||||||
<TrendingUp className="w-3 h-3 mr-1" />
|
<TrendingUp className="w-3 h-3 mr-1" />
|
||||||
<span>持续改进中</span>
|
<span>持续改进</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-gray-500 mt-1">暂无数据</p>
|
<p className="text-xs font-mono text-gray-500 mt-1">暂无数据</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-purple-500 to-purple-600 group-hover:scale-110 transition-transform">
|
<div className="w-12 h-12 border-2 border-black bg-purple-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Target className="w-6 h-6 text-white" />
|
<Target className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content - 重新设计为更紧凑的布局 */}
|
{/* Main Content - 重新设计为更紧凑的布局 */}
|
||||||
|
|
@ -282,60 +280,62 @@ export default function Dashboard() {
|
||||||
{/* 左侧主要内容区 */}
|
{/* 左侧主要内容区 */}
|
||||||
<div className="xl:col-span-3 space-y-4">
|
<div className="xl:col-span-3 space-y-4">
|
||||||
{/* 图表区域 - 使用更紧凑的网格布局 */}
|
{/* 图表区域 - 使用更紧凑的网格布局 */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
{/* 图表区域 - 使用更紧凑的网格布局 */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* 质量趋势图 */}
|
{/* 质量趋势图 */}
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardHeader className="pb-3">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<CardTitle className="flex items-center text-lg">
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<TrendingUp className="w-5 h-5 mr-2 text-primary" />
|
<TrendingUp className="w-5 h-5 mr-2 text-primary" />
|
||||||
代码质量趋势
|
代码质量趋势
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div>
|
||||||
{qualityTrendData.length > 0 ? (
|
{qualityTrendData.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
<LineChart data={qualityTrendData}>
|
<LineChart data={qualityTrendData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||||
<XAxis dataKey="date" stroke="#6b7280" fontSize={12} />
|
<XAxis dataKey="date" stroke="#000" fontSize={12} tick={{ fontFamily: 'Space Mono' }} />
|
||||||
<YAxis stroke="#6b7280" fontSize={12} domain={[0, 100]} />
|
<YAxis stroke="#000" fontSize={12} domain={[0, 100]} tick={{ fontFamily: 'Space Mono' }} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'white',
|
backgroundColor: '#fff',
|
||||||
border: 'none',
|
border: '2px solid #000',
|
||||||
borderRadius: '8px',
|
borderRadius: '0px',
|
||||||
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
|
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||||
|
fontFamily: 'Space Mono'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="step"
|
||||||
dataKey="score"
|
dataKey="score"
|
||||||
stroke="hsl(var(--primary))"
|
stroke="#000"
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
dot={{ fill: 'hsl(var(--primary))', strokeWidth: 2, r: 4 }}
|
dot={{ fill: '#fff', stroke: '#000', strokeWidth: 2, r: 4 }}
|
||||||
activeDot={{ r: 6 }}
|
activeDot={{ r: 6, fill: '#000' }}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-[250px] text-gray-400">
|
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">暂无质量趋势数据</p>
|
<p className="text-sm font-mono uppercase">暂无质量趋势数据</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 问题分布图 */}
|
{/* 问题分布图 */}
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardHeader className="pb-3">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<CardTitle className="flex items-center text-lg">
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<BarChart3 className="w-5 h-5 mr-2 text-accent" />
|
<BarChart3 className="w-5 h-5 mr-2 text-black" />
|
||||||
问题类型分布
|
问题类型分布
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div>
|
||||||
{issueTypeData.length > 0 ? (
|
{issueTypeData.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
|
|
@ -343,106 +343,116 @@ export default function Dashboard() {
|
||||||
data={issueTypeData}
|
data={issueTypeData}
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="50%"
|
||||||
labelLine={false}
|
labelLine={true}
|
||||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
|
stroke="#000"
|
||||||
|
strokeWidth={2}
|
||||||
>
|
>
|
||||||
{issueTypeData.map((entry, index) => (
|
{issueTypeData.map((entry) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${entry.name}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip />
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
border: '2px solid #000',
|
||||||
|
borderRadius: '0px',
|
||||||
|
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||||
|
fontFamily: 'Space Mono'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-[250px] text-gray-400">
|
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">暂无问题分布数据</p>
|
<p className="text-sm font-mono uppercase">暂无问题分布数据</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目概览 */}
|
{/* 项目概览 */}
|
||||||
<Card className="card-modern">
|
{/* 项目概览 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardTitle className="flex items-center text-lg">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<FileText className="w-5 h-5 mr-2 text-primary" />
|
<FileText className="w-5 h-5 mr-2 text-primary" />
|
||||||
项目概览
|
项目概览
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{recentProjects.length > 0 ? (
|
{recentProjects.length > 0 ? (
|
||||||
recentProjects.map((project) => (
|
recentProjects.map((project) => (
|
||||||
<Link
|
<Link
|
||||||
key={project.id}
|
key={project.id}
|
||||||
to={`/projects/${project.id}`}
|
to={`/projects/${project.id}`}
|
||||||
className="block p-4 rounded-lg border border-gray-200 hover:border-primary/30 hover:bg-primary/5 transition-all group"
|
className="block p-4 border-2 border-black bg-gray-50 hover:bg-white hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] transition-all group"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-2">
|
<div className="flex items-start justify-between mb-2">
|
||||||
<h4 className="font-medium text-gray-900 group-hover:text-primary transition-colors truncate">
|
<h4 className="font-mono font-bold text-black group-hover:text-primary transition-colors truncate uppercase">
|
||||||
{project.name}
|
{project.name}
|
||||||
</h4>
|
</h4>
|
||||||
<Badge
|
<Badge
|
||||||
variant={project.is_active ? "default" : "secondary"}
|
variant="outline"
|
||||||
className="ml-2 flex-shrink-0"
|
className={`ml-2 flex-shrink-0 border-black rounded-none ${project.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
|
||||||
>
|
>
|
||||||
{project.is_active ? '活跃' : '暂停'}
|
{project.is_active ? '活跃' : '暂停'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 line-clamp-2 mb-2">
|
<p className="text-xs text-gray-600 font-mono line-clamp-2 mb-3 border-l-2 border-gray-300 pl-2">
|
||||||
{project.description || '暂无描述'}
|
{project.description || '暂无描述'}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center text-xs text-gray-400">
|
<div className="flex items-center text-xs text-gray-500 font-mono">
|
||||||
<Calendar className="w-3 h-3 mr-1" />
|
<Calendar className="w-3 h-3 mr-1" />
|
||||||
{new Date(project.created_at).toLocaleDateString('zh-CN')}
|
{new Date(project.created_at).toLocaleDateString('zh-CN')}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-full text-center py-8 text-gray-500">
|
<div className="col-span-full text-center py-8 text-gray-500 border-2 border-dashed border-black">
|
||||||
<Code className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<Code className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">暂无项目</p>
|
<p className="text-sm font-mono uppercase">暂无项目</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 最近任务 */}
|
{/* 最近任务 */}
|
||||||
<Card className="card-modern">
|
{/* 最近任务 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="pb-3 border-b-2 border-black mb-4 flex items-center justify-between">
|
||||||
<CardTitle className="flex items-center text-lg">
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<Clock className="w-5 h-5 mr-2 text-emerald-600" />
|
<Clock className="w-5 h-5 mr-2 text-green-600" />
|
||||||
最近任务
|
最近任务
|
||||||
</CardTitle>
|
</h3>
|
||||||
<Link to="/audit-tasks">
|
<Link to="/audit-tasks">
|
||||||
<Button variant="ghost" size="sm" className="hover:bg-emerald-50 hover:text-emerald-700">
|
<Button variant="ghost" size="sm" className="hover:bg-green-50 hover:text-green-700 font-mono uppercase text-xs">
|
||||||
查看全部
|
查看全部
|
||||||
<ArrowUpRight className="w-3 h-3 ml-1" />
|
<ArrowUpRight className="w-3 h-3 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
<div>
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentTasks.length > 0 ? (
|
{recentTasks.length > 0 ? (
|
||||||
recentTasks.slice(0, 6).map((task) => (
|
recentTasks.slice(0, 6).map((task) => (
|
||||||
<Link
|
<Link
|
||||||
key={task.id}
|
key={task.id}
|
||||||
to={`/tasks/${task.id}`}
|
to={`/tasks/${task.id}`}
|
||||||
className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 transition-colors group"
|
className="flex items-center justify-between p-3 border-2 border-transparent hover:border-black hover:bg-gray-50 transition-all group"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
|
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
|
||||||
task.status === 'running' ? 'bg-red-50 text-red-600' :
|
task.status === 'running' ? 'bg-blue-100 text-blue-600' :
|
||||||
'bg-red-100 text-red-600'
|
'bg-red-100 text-red-600'
|
||||||
}`}>
|
}`}>
|
||||||
{task.status === 'completed' ? <Activity className="w-4 h-4" /> :
|
{task.status === 'completed' ? <Activity className="w-4 h-4" /> :
|
||||||
|
|
@ -450,113 +460,115 @@ export default function Dashboard() {
|
||||||
<AlertTriangle className="w-4 h-4" />}
|
<AlertTriangle className="w-4 h-4" />}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-sm text-gray-900 group-hover:text-primary transition-colors">
|
<p className="font-mono font-bold text-sm text-black group-hover:text-primary transition-colors uppercase">
|
||||||
{task.project?.name || '未知项目'}
|
{task.project?.name || '未知项目'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500 font-mono">
|
||||||
质量分: {task.quality_score?.toFixed(1) || '0.0'}
|
质量分: <span className="font-bold text-black">{task.quality_score?.toFixed(1) || '0.0'}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={getStatusColor(task.status)}>
|
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
|
||||||
{task.status === 'completed' ? '完成' :
|
{task.status === 'completed' ? '完成' :
|
||||||
task.status === 'running' ? '运行中' : '失败'}
|
task.status === 'running' ? '运行中' : '失败'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Link>
|
</Link>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-gray-500">
|
<div className="text-center py-8 text-gray-500 border-2 border-dashed border-black">
|
||||||
<Activity className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<Activity className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">暂无任务</p>
|
<p className="text-sm font-mono uppercase">暂无任务</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧边栏 - 紧凑设计 */}
|
{/* 右侧边栏 - 紧凑设计 */}
|
||||||
<div className="xl:col-span-1 space-y-4">
|
<div className="xl:col-span-1 space-y-4">
|
||||||
{/* 快速操作 */}
|
{/* 快速操作 */}
|
||||||
<Card className="card-modern bg-gradient-to-br from-red-50/30 via-background to-red-50/20 border border-red-100/50">
|
{/* 快速操作 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardTitle className="text-lg flex items-center">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<Zap className="w-5 h-5 mr-2 text-indigo-600" />
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
|
<Zap className="w-5 h-5 mr-2 text-primary" />
|
||||||
快速操作
|
快速操作
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Link to="/instant-analysis" className="block">
|
<Link to="/instant-analysis" className="block">
|
||||||
<Button className="w-full justify-start btn-primary">
|
<Button className="w-full justify-start retro-btn h-10">
|
||||||
<Zap className="w-4 h-4 mr-2" />
|
<Zap className="w-4 h-4 mr-2" />
|
||||||
即时代码分析
|
即时代码分析
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/projects" className="block">
|
<Link to="/projects" className="block">
|
||||||
<Button variant="outline" className="w-full justify-start btn-secondary">
|
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||||
<GitBranch className="w-4 h-4 mr-2" />
|
<GitBranch className="w-4 h-4 mr-2" />
|
||||||
创建新项目
|
创建新项目
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/audit-tasks" className="block">
|
<Link to="/audit-tasks" className="block">
|
||||||
<Button variant="outline" className="w-full justify-start btn-secondary">
|
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||||
<Shield className="w-4 h-4 mr-2" />
|
<Shield className="w-4 h-4 mr-2" />
|
||||||
启动审计任务
|
启动审计任务
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 系统状态 */}
|
{/* 系统状态 */}
|
||||||
<Card className="card-modern">
|
{/* 系统状态 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardTitle className="text-lg flex items-center">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<Activity className="w-5 h-5 mr-2 text-emerald-600" />
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
|
<Activity className="w-5 h-5 mr-2 text-green-600" />
|
||||||
系统状态
|
系统状态
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="space-y-4 font-mono">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">数据库模式</span>
|
<span className="text-sm text-gray-600 uppercase">数据库模式</span>
|
||||||
<Badge className={
|
<Badge className={`rounded-none border border-black ${dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
|
||||||
dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
|
|
||||||
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
|
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
|
||||||
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
|
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
|
||||||
'bg-gray-100 text-gray-700'
|
'bg-gray-100 text-gray-700'
|
||||||
}>
|
}`}>
|
||||||
{dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
|
{dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">活跃项目</span>
|
<span className="text-sm text-gray-600 uppercase">活跃项目</span>
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-yellow-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{stats?.active_projects || 0}
|
{stats?.active_projects || 0}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">运行中任务</span>
|
<span className="text-sm text-gray-600 uppercase">运行中任务</span>
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-blue-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{recentTasks.filter(t => t.status === 'running').length}
|
{recentTasks.filter(t => t.status === 'running').length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">待解决问题</span>
|
<span className="text-sm text-gray-600 uppercase">待解决问题</span>
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-red-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{stats ? stats.total_issues - stats.resolved_issues : 0}
|
{stats ? stats.total_issues - stats.resolved_issues : 0}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 最新活动 */}
|
{/* 最新活动 */}
|
||||||
<Card className="card-modern">
|
{/* 最新活动 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardTitle className="text-lg flex items-center">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<Clock className="w-5 h-5 mr-2 text-orange-600" />
|
<Clock className="w-5 h-5 mr-2 text-orange-600" />
|
||||||
最新活动
|
最新活动
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentTasks.length > 0 ? (
|
{recentTasks.length > 0 ? (
|
||||||
recentTasks.slice(0, 3).map((task) => {
|
recentTasks.slice(0, 3).map((task) => {
|
||||||
const timeAgo = (() => {
|
const timeAgo = (() => {
|
||||||
|
|
@ -573,28 +585,10 @@ export default function Dashboard() {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const bgColor =
|
const bgColor =
|
||||||
task.status === 'completed' ? 'bg-emerald-50 border-emerald-200' :
|
task.status === 'completed' ? 'bg-green-50 border-black' :
|
||||||
task.status === 'running' ? 'bg-blue-50 border-blue-200' :
|
task.status === 'running' ? 'bg-blue-50 border-black' :
|
||||||
task.status === 'failed' ? 'bg-red-50 border-red-200' :
|
task.status === 'failed' ? 'bg-red-50 border-black' :
|
||||||
'bg-gray-50 border-gray-200';
|
'bg-gray-50 border-black';
|
||||||
|
|
||||||
const textColor =
|
|
||||||
task.status === 'completed' ? 'text-emerald-900' :
|
|
||||||
task.status === 'running' ? 'text-blue-900' :
|
|
||||||
task.status === 'failed' ? 'text-red-900' :
|
|
||||||
'text-gray-900';
|
|
||||||
|
|
||||||
const descColor =
|
|
||||||
task.status === 'completed' ? 'text-emerald-700' :
|
|
||||||
task.status === 'running' ? 'text-blue-700' :
|
|
||||||
task.status === 'failed' ? 'text-red-700' :
|
|
||||||
'text-gray-700';
|
|
||||||
|
|
||||||
const timeColor =
|
|
||||||
task.status === 'completed' ? 'text-emerald-600' :
|
|
||||||
task.status === 'running' ? 'text-blue-600' :
|
|
||||||
task.status === 'failed' ? 'text-red-600' :
|
|
||||||
'text-gray-600';
|
|
||||||
|
|
||||||
const statusText =
|
const statusText =
|
||||||
task.status === 'completed' ? '任务完成' :
|
task.status === 'completed' ? '任务完成' :
|
||||||
|
|
@ -606,53 +600,52 @@ export default function Dashboard() {
|
||||||
<Link
|
<Link
|
||||||
key={task.id}
|
key={task.id}
|
||||||
to={`/tasks/${task.id}`}
|
to={`/tasks/${task.id}`}
|
||||||
className={`block p-3 rounded-lg border ${bgColor} hover:shadow-sm transition-shadow`}
|
className={`block p-3 border-2 ${bgColor} hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all`}
|
||||||
>
|
>
|
||||||
<p className={`text-sm font-medium ${textColor}`}>{statusText}</p>
|
<p className="text-sm font-bold font-mono uppercase text-black">{statusText}</p>
|
||||||
<p className={`text-xs ${descColor} mt-1 line-clamp-1`}>
|
<p className="text-xs text-gray-700 mt-1 line-clamp-1 font-mono">
|
||||||
项目 "{task.project?.name || '未知项目'}"
|
项目 "{task.project?.name || '未知项目'}"
|
||||||
{task.status === 'completed' && task.issues_count > 0 &&
|
{task.status === 'completed' && task.issues_count > 0 &&
|
||||||
` - 发现 ${task.issues_count} 个问题`
|
` - 发现 ${task.issues_count} 个问题`
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
<p className={`text-xs ${timeColor} mt-1`}>{timeAgo}</p>
|
<p className="text-xs text-gray-500 mt-1 font-mono">{timeAgo}</p>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-gray-400">
|
<div className="text-center py-8 text-gray-400 border-2 border-dashed border-black">
|
||||||
<Clock className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<Clock className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">暂无活动记录</p>
|
<p className="text-sm font-mono uppercase">暂无活动记录</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 使用技巧 */}
|
{/* 使用技巧 */}
|
||||||
<Card className="card-modern bg-gradient-to-br from-purple-50 to-pink-50 border border-purple-100/50">
|
{/* 使用技巧 */}
|
||||||
<CardHeader className="pb-3">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardTitle className="text-lg flex items-center">
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
|
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||||
<Target className="w-5 h-5 mr-2 text-purple-600" />
|
<Target className="w-5 h-5 mr-2 text-purple-600" />
|
||||||
使用技巧
|
使用技巧
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="space-y-3 font-mono text-sm">
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||||
<p className="text-sm text-gray-700">定期运行代码审计可以及早发现潜在问题</p>
|
<p className="text-gray-700">定期运行代码审计可以及早发现潜在问题</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||||
<p className="text-sm text-gray-700">使用即时分析功能快速检查代码片段</p>
|
<p className="text-gray-700">使用即时分析功能快速检查代码片段</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||||
<p className="text-sm text-gray-700">关注质量评分趋势,持续改进代码质量</p>
|
<p className="text-gray-700">关注质量评分趋势,持续改进代码质量</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
@ -397,28 +397,28 @@ class UserManager {
|
||||||
return { task: tempTask, issues: tempIssues };
|
return { task: tempTask, issues: tempIssues };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染问题的函数,使用紧凑样式
|
// 渲染问题的函数,使用复古样式
|
||||||
const renderIssue = (issue: any, index: number) => (
|
const renderIssue = (issue: any, index: number) => (
|
||||||
<div key={index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
|
<div key={index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 mb-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3 pb-3 border-b-2 border-dashed border-gray-300">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${issue.severity === 'critical' ? 'bg-red-500 text-white' :
|
||||||
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
issue.severity === 'high' ? 'bg-orange-500 text-white' :
|
||||||
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
issue.severity === 'medium' ? 'bg-yellow-400 text-black' :
|
||||||
'bg-blue-100 text-blue-600'
|
'bg-blue-400 text-white'
|
||||||
}`}>
|
}`}>
|
||||||
{getTypeIcon(issue.type)}
|
{getTypeIcon(issue.type)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
|
<h4 className="font-bold text-base text-black mb-1 font-mono uppercase">{issue.title}</h4>
|
||||||
<div className="flex items-center space-x-1 text-xs text-gray-600">
|
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
|
||||||
<span>📍</span>
|
<span>📍</span>
|
||||||
<span>第 {issue.line} 行</span>
|
<span>第 {issue.line} 行</span>
|
||||||
{issue.column && <span>,第 {issue.column} 列</span>}
|
{issue.column && <span>,第 {issue.column} 列</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
|
<Badge className={`rounded-none border-2 border-black ${getSeverityColor(issue.severity)} font-bold uppercase`}>
|
||||||
{issue.severity === 'critical' ? '严重' :
|
{issue.severity === 'critical' ? '严重' :
|
||||||
issue.severity === 'high' ? '高' :
|
issue.severity === 'high' ? '高' :
|
||||||
issue.severity === 'medium' ? '中等' : '低'}
|
issue.severity === 'medium' ? '中等' : '低'}
|
||||||
|
|
@ -426,46 +426,43 @@ class UserManager {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{issue.description && (
|
{issue.description && (
|
||||||
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
|
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono text-xs">
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1 border-b-2 border-black pb-1 w-fit">
|
||||||
<Info className="w-3 h-3 text-gray-600 mr-1" />
|
<Info className="w-3 h-3 text-black mr-1" />
|
||||||
<span className="font-medium text-gray-800 text-xs">问题详情</span>
|
<span className="font-bold text-black uppercase">问题详情</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-700 text-xs leading-relaxed">
|
<p className="text-gray-800 leading-relaxed mt-2">
|
||||||
{issue.description}
|
{issue.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{issue.code_snippet && (
|
{issue.code_snippet && (
|
||||||
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
|
<div className="bg-black text-green-400 p-3 mb-3 border-2 border-gray-800 font-mono text-xs relative overflow-hidden">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="absolute top-0 right-0 bg-gray-800 text-white px-2 py-0.5 text-[10px] uppercase">Code</div>
|
||||||
|
<div className="flex items-center justify-between mb-2 border-b border-gray-800 pb-1">
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
|
<Code className="w-3 h-3 text-green-500" />
|
||||||
<Code className="w-2 h-2 text-white" />
|
<span className="text-gray-400 font-bold uppercase">问题代码</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-300 text-xs font-medium">问题代码</span>
|
<span className="text-gray-500">Line {issue.line}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-400 text-xs">第 {issue.line} 行</span>
|
<pre className="overflow-x-auto">
|
||||||
</div>
|
|
||||||
<div className="bg-black/40 rounded p-2">
|
|
||||||
<pre className="text-xs text-gray-100 overflow-x-auto">
|
|
||||||
<code>{issue.code_snippet}</code>
|
<code>{issue.code_snippet}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
{issue.suggestion && (
|
{issue.suggestion && (
|
||||||
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
|
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2">
|
||||||
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
|
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||||
<Lightbulb className="w-3 h-3 text-white" />
|
<Lightbulb className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-blue-800 text-sm">修复建议</span>
|
<span className="font-bold text-blue-900 text-sm uppercase font-mono">修复建议</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
|
<p className="text-blue-900 text-xs leading-relaxed font-mono">{issue.suggestion}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -474,44 +471,44 @@ class UserManager {
|
||||||
|
|
||||||
if (parsedExplanation) {
|
if (parsedExplanation) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
|
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2">
|
||||||
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
|
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||||
<Zap className="w-3 h-3 text-white" />
|
<Zap className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI 解释</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 text-xs">
|
<div className="space-y-2 text-xs font-mono">
|
||||||
{parsedExplanation.what && (
|
{parsedExplanation.what && (
|
||||||
<div className="border-l-2 border-red-600 pl-2">
|
<div className="border-l-4 border-red-600 pl-2">
|
||||||
<span className="font-medium text-red-700">问题:</span>
|
<span className="font-bold text-red-900 uppercase block mb-1">问题:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
|
<span className="text-gray-800">{parsedExplanation.what}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.why && (
|
{parsedExplanation.why && (
|
||||||
<div className="border-l-2 border-gray-600 pl-2">
|
<div className="border-l-4 border-gray-600 pl-2">
|
||||||
<span className="font-medium text-gray-700">原因:</span>
|
<span className="font-bold text-gray-900 uppercase block mb-1">原因:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
|
<span className="text-gray-800">{parsedExplanation.why}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.how && (
|
{parsedExplanation.how && (
|
||||||
<div className="border-l-2 border-black pl-2">
|
<div className="border-l-4 border-black pl-2">
|
||||||
<span className="font-medium text-black">方案:</span>
|
<span className="font-bold text-black uppercase block mb-1">方案:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
|
<span className="text-gray-800">{parsedExplanation.how}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.learn_more && (
|
{parsedExplanation.learn_more && (
|
||||||
<div className="border-l-2 border-red-400 pl-2">
|
<div className="border-l-4 border-blue-400 pl-2">
|
||||||
<span className="font-medium text-red-600">链接:</span>
|
<span className="font-bold text-blue-900 uppercase block mb-1">链接:</span>
|
||||||
<a
|
<a
|
||||||
href={parsedExplanation.learn_more}
|
href={parsedExplanation.learn_more}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-red-600 hover:text-red-800 hover:underline ml-1"
|
className="text-blue-700 hover:text-blue-900 hover:underline break-all"
|
||||||
>
|
>
|
||||||
{parsedExplanation.learn_more}
|
{parsedExplanation.learn_more}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -523,12 +520,12 @@ class UserManager {
|
||||||
} else {
|
} else {
|
||||||
// 如果无法解析JSON,回退到原始显示方式
|
// 如果无法解析JSON,回退到原始显示方式
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border border-red-200 rounded-lg p-3">
|
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2">
|
||||||
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
||||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI 解释</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
|
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -538,35 +535,37 @@ class UserManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in font-mono">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div>
|
<div className="border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<h1 className="page-title">即时代码分析</h1>
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">即时代码分析</h1>
|
||||||
<p className="page-subtitle">快速分析代码片段,发现潜在问题并获得修复建议</p>
|
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">快速分析代码片段,发现潜在问题并获得修复建议</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 代码输入区域 */}
|
{/* 代码输入区域 */}
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader className="pb-3">
|
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<CardTitle className="text-base">代码分析</CardTitle>
|
<Code className="w-5 h-5 mr-2" />
|
||||||
|
代码分析
|
||||||
|
</h3>
|
||||||
{result && (
|
{result && (
|
||||||
<Button variant="outline" onClick={clearAnalysis} size="sm">
|
<Button variant="outline" onClick={clearAnalysis} size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-8">
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
重新分析
|
重新分析
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
<div className="p-6 space-y-4">
|
||||||
{/* 工具栏 */}
|
{/* 工具栏 */}
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<div className="flex flex-col sm:flex-row gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select value={language} onValueChange={setLanguage}>
|
<Select value={language} onValueChange={setLanguage}>
|
||||||
<SelectTrigger className="h-9">
|
<SelectTrigger className="h-10 retro-input rounded-none border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] focus:ring-0">
|
||||||
<SelectValue placeholder="选择编程语言" />
|
<SelectValue placeholder="选择编程语言" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{supportedLanguages.map((lang) => (
|
{supportedLanguages.map((lang) => (
|
||||||
<SelectItem key={lang} value={lang}>
|
<SelectItem key={lang} value={lang}>
|
||||||
{lang.charAt(0).toUpperCase() + lang.slice(1)}
|
{lang.charAt(0).toUpperCase() + lang.slice(1)}
|
||||||
|
|
@ -579,9 +578,9 @@ class UserManager {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
size="sm"
|
className="retro-btn bg-white text-black hover:bg-gray-100 h-10"
|
||||||
>
|
>
|
||||||
<Upload className="w-3 h-3 mr-1" />
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
上传文件
|
上传文件
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
|
|
@ -594,14 +593,14 @@ class UserManager {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 快速示例 */}
|
{/* 快速示例 */}
|
||||||
<div className="flex flex-wrap gap-2 items-center">
|
<div className="flex flex-wrap gap-2 items-center p-2 bg-gray-50 border-2 border-dashed border-gray-300">
|
||||||
<span className="text-xs text-gray-600">示例:</span>
|
<span className="text-xs font-bold uppercase text-gray-600 mr-2">示例:</span>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadExampleCode('javascript')}
|
onClick={() => loadExampleCode('javascript')}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
className="h-7 px-2 text-xs"
|
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-yellow-100"
|
||||||
>
|
>
|
||||||
JavaScript
|
JavaScript
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -610,7 +609,7 @@ class UserManager {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadExampleCode('python')}
|
onClick={() => loadExampleCode('python')}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
className="h-7 px-2 text-xs"
|
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-blue-100"
|
||||||
>
|
>
|
||||||
Python
|
Python
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -619,7 +618,7 @@ class UserManager {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadExampleCode('java')}
|
onClick={() => loadExampleCode('java')}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
className="h-7 px-2 text-xs"
|
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-red-100"
|
||||||
>
|
>
|
||||||
Java
|
Java
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -628,7 +627,7 @@ class UserManager {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadExampleCode('swift')}
|
onClick={() => loadExampleCode('swift')}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
className="h-7 px-2 text-xs"
|
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-orange-100"
|
||||||
>
|
>
|
||||||
Swift
|
Swift
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -637,22 +636,25 @@ class UserManager {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadExampleCode('kotlin')}
|
onClick={() => loadExampleCode('kotlin')}
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
className="h-7 px-2 text-xs"
|
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-purple-100"
|
||||||
>
|
>
|
||||||
Kotlin
|
Kotlin
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 代码编辑器 */}
|
{/* 代码编辑器 */}
|
||||||
<div>
|
<div className="relative">
|
||||||
|
<div className="absolute top-0 right-0 bg-black text-white px-2 py-1 text-xs font-mono uppercase z-10 border-l-2 border-b-2 border-white">
|
||||||
|
Editor
|
||||||
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="粘贴代码或上传文件..."
|
placeholder="粘贴代码或上传文件..."
|
||||||
value={code}
|
value={code}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
onChange={(e) => setCode(e.target.value)}
|
||||||
className="min-h-[250px] font-mono text-sm"
|
className="min-h-[300px] font-mono text-sm retro-input bg-gray-900 text-green-400 border-2 border-black p-4 focus:ring-0 focus:border-primary"
|
||||||
disabled={analyzing}
|
disabled={analyzing}
|
||||||
/>
|
/>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1 font-mono text-right">
|
||||||
{code.length} 字符,{code.split('\n').length} 行
|
{code.length} 字符,{code.split('\n').length} 行
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -661,176 +663,174 @@ class UserManager {
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAnalyze}
|
onClick={handleAnalyze}
|
||||||
disabled={!code.trim() || !language || analyzing}
|
disabled={!code.trim() || !language || analyzing}
|
||||||
className="w-full btn-primary"
|
className="w-full retro-btn bg-primary text-white hover:bg-primary/90 h-12 text-lg font-bold uppercase"
|
||||||
>
|
>
|
||||||
{analyzing ? (
|
{analyzing ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
<div className="animate-spin rounded-none h-5 w-5 border-4 border-white border-t-transparent mr-3"></div>
|
||||||
分析中...
|
分析中...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Zap className="w-4 h-4 mr-2" />
|
<Zap className="w-5 h-5 mr-2" />
|
||||||
开始分析
|
开始分析
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 分析结果区域 */}
|
{/* 分析结果区域 */}
|
||||||
{result && (
|
{result && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
{/* 结果概览 */}
|
{/* 结果概览 */}
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader className="pb-3">
|
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<CardTitle className="flex items-center text-base">
|
|
||||||
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
|
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
|
||||||
分析结果
|
分析结果
|
||||||
</CardTitle>
|
</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
|
||||||
<Clock className="w-3 h-3 mr-1" />
|
<Clock className="w-3 h-3 mr-1" />
|
||||||
{analysisTime.toFixed(2)}s
|
{analysisTime.toFixed(2)}s
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
|
||||||
{language.charAt(0).toUpperCase() + language.slice(1)}
|
{language}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{/* 导出按钮 */}
|
{/* 导出按钮 */}
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setExportDialogOpen(true)}
|
onClick={() => setExportDialogOpen(true)}
|
||||||
className="btn-primary"
|
className="retro-btn bg-primary text-white hover:bg-primary/90 h-8"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
导出报告
|
导出报告
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
<div className="p-6">
|
||||||
<CardContent>
|
|
||||||
{/* 核心指标 */}
|
{/* 核心指标 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 font-mono">
|
||||||
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
|
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="w-12 h-12 bg-primary rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="w-12 h-12 bg-primary border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Target className="w-6 h-6 text-white" />
|
<Target className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-primary mb-1">
|
<div className="text-3xl font-bold text-primary mb-1">
|
||||||
{result.quality_score.toFixed(1)}
|
{result.quality_score.toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-medium text-primary/80 mb-2">质量评分</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-2">质量评分</p>
|
||||||
<Progress value={result.quality_score} className="h-1" />
|
<Progress value={result.quality_score} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
|
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="w-12 h-12 bg-red-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<AlertTriangle className="w-6 h-6 text-white" />
|
<AlertTriangle className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-red-600 mb-1">
|
<div className="text-3xl font-bold text-red-600 mb-1">
|
||||||
{result.summary.critical_issues + result.summary.high_issues}
|
{result.summary.critical_issues + result.summary.high_issues}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-medium text-red-700 mb-1">严重问题</p>
|
<p className="text-xs font-bold text-red-700 uppercase mb-1">严重问题</p>
|
||||||
<div className="text-xs text-red-600">需要立即处理</div>
|
<div className="text-xs text-red-600 font-bold uppercase">需要立即处理</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center p-4 bg-white rounded-lg border border-yellow-200">
|
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="w-12 h-12 bg-yellow-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="w-12 h-12 bg-yellow-400 border-2 border-black flex items-center justify-center mx-auto mb-3 text-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Info className="w-6 h-6 text-white" />
|
<Info className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-yellow-600 mb-1">
|
<div className="text-3xl font-bold text-yellow-600 mb-1">
|
||||||
{result.summary.medium_issues + result.summary.low_issues}
|
{result.summary.medium_issues + result.summary.low_issues}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-medium text-yellow-700 mb-1">一般问题</p>
|
<p className="text-xs font-bold text-yellow-700 uppercase mb-1">一般问题</p>
|
||||||
<div className="text-xs text-yellow-600">建议优化</div>
|
<div className="text-xs text-yellow-600 font-bold uppercase">建议优化</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center p-4 bg-white rounded-lg border border-green-200">
|
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="w-12 h-12 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="w-12 h-12 bg-green-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<FileText className="w-6 h-6 text-white" />
|
<FileText className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-green-600 mb-1">
|
<div className="text-3xl font-bold text-green-600 mb-1">
|
||||||
{result.issues.length}
|
{result.issues.length}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-medium text-green-700 mb-1">总问题数</p>
|
<p className="text-xs font-bold text-green-700 uppercase mb-1">总问题数</p>
|
||||||
<div className="text-xs text-green-600">已全部识别</div>
|
<div className="text-xs text-green-600 font-bold uppercase">已全部识别</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 详细指标 */}
|
{/* 详细指标 */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
<div className="bg-gray-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 mb-3 flex items-center">
|
<h3 className="text-sm font-bold text-black uppercase mb-4 flex items-center font-mono border-b-2 border-black pb-2 w-fit">
|
||||||
<TrendingUp className="w-4 h-4 mr-1" />
|
<TrendingUp className="w-4 h-4 mr-2" />
|
||||||
详细指标
|
详细指标
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.complexity}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics.complexity}</div>
|
||||||
<p className="text-xs text-gray-600 mb-2">复杂度</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">复杂度</p>
|
||||||
<Progress value={result.metrics.complexity} className="h-1" />
|
<Progress value={result.metrics.complexity} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.maintainability}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics.maintainability}</div>
|
||||||
<p className="text-xs text-gray-600 mb-2">可维护性</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">可维护性</p>
|
||||||
<Progress value={result.metrics.maintainability} className="h-1" />
|
<Progress value={result.metrics.maintainability} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.security}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics.security}</div>
|
||||||
<p className="text-xs text-gray-600 mb-2">安全性</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">安全性</p>
|
||||||
<Progress value={result.metrics.security} className="h-1" />
|
<Progress value={result.metrics.security} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.performance}</div>
|
<div className="text-xl font-bold text-black mb-1">{result.metrics.performance}</div>
|
||||||
<p className="text-xs text-gray-600 mb-2">性能</p>
|
<p className="text-xs text-gray-600 uppercase mb-2">性能</p>
|
||||||
<Progress value={result.metrics.performance} className="h-1" />
|
<Progress value={result.metrics.performance} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 问题详情 */}
|
{/* 问题详情 */}
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader className="pb-3">
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center text-base">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<Shield className="w-5 h-5 mr-2 text-orange-600" />
|
<Shield className="w-5 h-5 mr-2 text-orange-600" />
|
||||||
发现的问题 ({result.issues.length})
|
发现的问题 ({result.issues.length})
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6">
|
||||||
{result.issues.length > 0 ? (
|
{result.issues.length > 0 ? (
|
||||||
<Tabs defaultValue="all" className="w-full">
|
<Tabs defaultValue="all" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-4 mb-4">
|
<TabsList className="grid w-full grid-cols-4 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
|
||||||
<TabsTrigger value="all" className="text-xs">
|
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
全部 ({result.issues.length})
|
全部 ({result.issues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="critical" className="text-xs">
|
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
严重 ({result.issues.filter(i => i.severity === 'critical').length})
|
严重 ({result.issues.filter(i => i.severity === 'critical').length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="high" className="text-xs">
|
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
高 ({result.issues.filter(i => i.severity === 'high').length})
|
高 ({result.issues.filter(i => i.severity === 'high').length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="medium" className="text-xs">
|
<TabsTrigger value="medium" className="rounded-none data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||||
中等 ({result.issues.filter(i => i.severity === 'medium').length})
|
中等 ({result.issues.filter(i => i.severity === 'medium').length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="all" className="space-y-3 mt-4">
|
<TabsContent value="all" className="space-y-4 mt-0">
|
||||||
{result.issues.map((issue, index) => renderIssue(issue, index))}
|
{result.issues.map((issue, index) => renderIssue(issue, index))}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{['critical', 'high', 'medium'].map(severity => (
|
{['critical', 'high', 'medium'].map(severity => (
|
||||||
<TabsContent key={severity} value={severity} className="space-y-3 mt-4">
|
<TabsContent key={severity} value={severity} className="space-y-4 mt-0">
|
||||||
{result.issues.filter(issue => issue.severity === severity).length > 0 ? (
|
{result.issues.filter(issue => issue.severity === severity).length > 0 ? (
|
||||||
result.issues.filter(issue => issue.severity === severity).map((issue, index) => renderIssue(issue, index))
|
result.issues.filter(issue => issue.severity === severity).map((issue, index) => renderIssue(issue, index))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">
|
||||||
没有发现{severity === 'critical' ? '严重' : severity === 'high' ? '高优先级' : '中等优先级'}问题
|
没有发现{severity === 'critical' ? '严重' : severity === 'high' ? '高优先级' : '中等优先级'}问题
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500 font-mono">
|
||||||
代码在此级别的检查中表现良好
|
代码在此级别的检查中表现良好
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -838,47 +838,45 @@ class UserManager {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
|
||||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CheckCircle className="w-12 h-12 text-green-600" />
|
<CheckCircle className="w-12 h-12 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-green-800 mb-3">代码质量优秀!</h3>
|
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase">代码质量优秀!</h3>
|
||||||
<p className="text-green-600 text-lg mb-6">恭喜!没有发现任何问题</p>
|
<p className="text-green-700 text-lg mb-6 font-mono font-bold">恭喜!没有发现任何问题</p>
|
||||||
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
|
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-green-700 text-sm">
|
<p className="text-black text-sm font-mono">
|
||||||
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 分析进行中状态 */}
|
{/* 分析进行中状态 */}
|
||||||
{analyzing && (
|
{analyzing && (
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardContent className="py-16">
|
<div className="py-16 px-6">
|
||||||
<div ref={loadingCardRef} className="text-center">
|
<div ref={loadingCardRef} className="text-center">
|
||||||
<div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
<div className="animate-spin rounded-none h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">AI正在分析您的代码</h3>
|
<h3 className="text-2xl font-display font-bold text-black uppercase mb-3">AI正在分析您的代码</h3>
|
||||||
<p className="text-gray-600 text-lg mb-6">请稍候,这通常需要至少30秒钟...</p>
|
<p className="text-gray-600 text-lg mb-6 font-mono">请稍候,这通常需要至少30秒钟...</p>
|
||||||
<p className="text-gray-600 text-lg mb-6">分析时长取决于您的网络环境、代码长度以及使用的模型等因素</p>
|
<p className="text-gray-600 text-sm mb-6 font-mono">分析时长取决于您的网络环境、代码长度以及使用的模型等因素</p>
|
||||||
<div className="bg-red-50 rounded-lg p-6 max-w-md mx-auto">
|
<div className="bg-red-50 border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-red-700 text-sm">
|
<p className="text-red-700 text-sm font-mono font-bold">
|
||||||
正在进行安全检测、性能分析、代码风格检查等多维度评估<br />
|
正在进行安全检测、性能分析、代码风格检查等多维度评估<br />
|
||||||
请勿离开页面!
|
请勿离开页面!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 导出报告对话框 */}
|
{/* 导出报告对话框 */}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import { apiClient } from '@/shared/api/serverClient';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Terminal, Lock, Cpu } from 'lucide-react';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
|
|
@ -40,56 +41,103 @@ export default function Login() {
|
||||||
});
|
});
|
||||||
|
|
||||||
await login(response.data.access_token);
|
await login(response.data.access_token);
|
||||||
toast.success('登录成功');
|
toast.success('访问已授予');
|
||||||
// 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理
|
// 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.response?.data?.detail || '登录失败');
|
toast.error(error.response?.data?.detail || '访问被拒绝');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
<div className="min-h-screen flex items-center justify-center bg-background relative overflow-hidden">
|
||||||
<Card className="w-full max-w-md">
|
{/* Decorative Background Elements */}
|
||||||
<CardHeader className="space-y-1">
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||||
<CardTitle className="text-2xl font-bold text-center">XCodeReviewer</CardTitle>
|
|
||||||
<CardDescription className="text-center">请输入您的账号和密码登录</CardDescription>
|
<div className="absolute top-10 left-10 font-mono text-xs text-gray-400 hidden md:block">
|
||||||
</CardHeader>
|
<div>系统ID: 0x84F2</div>
|
||||||
<CardContent>
|
<div>状态: 等待输入</div>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<div>加密: AES-256</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-10 right-10 font-mono text-xs text-gray-400 hidden md:block text-right">
|
||||||
|
<div>安全连接</div>
|
||||||
|
<div>端口: 443</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Card */}
|
||||||
|
<div className="w-full max-w-md relative z-10 p-4">
|
||||||
|
<div className="mb-8 text-center">
|
||||||
|
<div className="inline-flex items-center justify-center p-4 bg-primary border-2 border-black shadow-retro mb-4">
|
||||||
|
<Terminal className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl font-display font-bold tracking-tighter uppercase">
|
||||||
|
XCode<span className="text-primary">Reviewer</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-mono text-gray-500 mt-2">输入凭据以继续</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white border-2 border-black shadow-retro p-8 relative">
|
||||||
|
{/* Decorative Corner Markers */}
|
||||||
|
<div className="absolute top-2 left-2 w-2 h-2 bg-black" />
|
||||||
|
<div className="absolute top-2 right-2 w-2 h-2 bg-black" />
|
||||||
|
<div className="absolute bottom-2 left-2 w-2 h-2 bg-black" />
|
||||||
|
<div className="absolute bottom-2 right-2 w-2 h-2 bg-black" />
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6 mt-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email">邮箱</Label>
|
<Label htmlFor="email" className="font-mono uppercase text-xs font-bold">身份 / 邮箱</Label>
|
||||||
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="name@example.com"
|
placeholder="USER@DOMAIN.COM"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
|
className="retro-input font-mono pl-10"
|
||||||
/>
|
/>
|
||||||
|
<Cpu className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="password">密码</Label>
|
<Label htmlFor="password" className="font-mono uppercase text-xs font-bold">通行码</Label>
|
||||||
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
|
placeholder="••••••••"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
|
className="retro-input font-mono pl-10"
|
||||||
/>
|
/>
|
||||||
|
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="w-full" disabled={loading}>
|
</div>
|
||||||
{loading ? '登录中...' : '登录'}
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full retro-btn text-lg h-12 mt-4"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span className="animate-spin">/</span> 认证中...
|
||||||
|
</span>
|
||||||
|
) : '初始化会话'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex flex-col space-y-2">
|
<div className="mt-6 pt-6 border-t-2 border-dashed border-gray-200 text-center">
|
||||||
<div className="text-sm text-center text-gray-500">
|
<div className="text-xs font-mono text-gray-500">
|
||||||
还没有账号? <span className="text-blue-600 cursor-pointer hover:underline" onClick={() => navigate('/register')}>立即注册</span>
|
没有访问令牌? <span className="text-primary font-bold cursor-pointer hover:underline" onClick={() => navigate('/register')}>申请访问</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import {
|
import {
|
||||||
|
|
@ -41,7 +40,6 @@ export default function ProjectDetail() {
|
||||||
const [showCreateTaskDialog, setShowCreateTaskDialog] = useState(false);
|
const [showCreateTaskDialog, setShowCreateTaskDialog] = useState(false);
|
||||||
const [showTerminalDialog, setShowTerminalDialog] = useState(false);
|
const [showTerminalDialog, setShowTerminalDialog] = useState(false);
|
||||||
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
|
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
|
||||||
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
|
|
||||||
const [editForm, setEditForm] = useState<CreateProjectForm>({
|
const [editForm, setEditForm] = useState<CreateProjectForm>({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
|
@ -50,6 +48,47 @@ export default function ProjectDetail() {
|
||||||
default_branch: "main",
|
default_branch: "main",
|
||||||
programming_languages: []
|
programming_languages: []
|
||||||
});
|
});
|
||||||
|
const [activeTab, setActiveTab] = useState("overview");
|
||||||
|
const [latestIssues, setLatestIssues] = useState<any[]>([]);
|
||||||
|
const [loadingIssues, setLoadingIssues] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'issues' && tasks.length > 0) {
|
||||||
|
loadLatestIssues();
|
||||||
|
}
|
||||||
|
}, [activeTab, tasks]);
|
||||||
|
|
||||||
|
const loadLatestIssues = async () => {
|
||||||
|
const completedTasks = tasks.filter(t => t.status === 'completed').sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||||
|
if (completedTasks.length > 0) {
|
||||||
|
setLoadingIssues(true);
|
||||||
|
try {
|
||||||
|
const issues = await api.getAuditIssues(completedTasks[0].id);
|
||||||
|
setLatestIssues(issues);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load issues:', error);
|
||||||
|
toast.error("加载问题列表失败");
|
||||||
|
} finally {
|
||||||
|
setLoadingIssues(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenSettings = () => {
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
// 初始化编辑表单
|
||||||
|
setEditForm({
|
||||||
|
name: project.name,
|
||||||
|
description: project.description || "",
|
||||||
|
repository_url: project.repository_url || "",
|
||||||
|
repository_type: project.repository_type || "github",
|
||||||
|
default_branch: project.default_branch || "main",
|
||||||
|
programming_languages: project.programming_languages ? JSON.parse(project.programming_languages) : []
|
||||||
|
});
|
||||||
|
|
||||||
|
setActiveTab("settings");
|
||||||
|
};
|
||||||
|
|
||||||
// 将小写语言名转换为显示格式
|
// 将小写语言名转换为显示格式
|
||||||
const formatLanguageName = (lang: string): string => {
|
const formatLanguageName = (lang: string): string => {
|
||||||
|
|
@ -172,22 +211,6 @@ export default function ProjectDetail() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenSettings = () => {
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
// 初始化编辑表单
|
|
||||||
setEditForm({
|
|
||||||
name: project.name,
|
|
||||||
description: project.description || "",
|
|
||||||
repository_url: project.repository_url || "",
|
|
||||||
repository_type: project.repository_type || "github",
|
|
||||||
default_branch: project.default_branch || "main",
|
|
||||||
programming_languages: project.programming_languages ? JSON.parse(project.programming_languages) : []
|
|
||||||
});
|
|
||||||
|
|
||||||
setShowSettingsDialog(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveSettings = async () => {
|
const handleSaveSettings = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|
@ -199,7 +222,6 @@ export default function ProjectDetail() {
|
||||||
try {
|
try {
|
||||||
await api.updateProject(id, editForm);
|
await api.updateProject(id, editForm);
|
||||||
toast.success("项目信息已保存");
|
toast.success("项目信息已保存");
|
||||||
setShowSettingsDialog(false);
|
|
||||||
loadProjectData();
|
loadProjectData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update project:', error);
|
console.error('Failed to update project:', error);
|
||||||
|
|
@ -258,21 +280,27 @@ export default function ProjectDetail() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen font-mono">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
|
<div className="text-center space-y-4">
|
||||||
|
<div className="relative w-16 h-16 mx-auto">
|
||||||
|
<div className="absolute inset-0 border-4 border-gray-200 rounded-none"></div>
|
||||||
|
<div className="absolute inset-0 border-4 border-primary rounded-none border-t-transparent animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 uppercase font-bold">加载项目数据...</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen font-mono">
|
||||||
<div className="text-center">
|
<div className="text-center border-2 border-black p-8 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] bg-white">
|
||||||
<AlertTriangle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
<AlertTriangle className="w-16 h-16 text-red-600 mx-auto mb-4" />
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">项目未找到</h2>
|
<h2 className="text-2xl font-bold text-black mb-2 uppercase">项目未找到</h2>
|
||||||
<p className="text-gray-600 mb-4">请检查项目ID是否正确</p>
|
<p className="text-gray-600 mb-4 uppercase">请检查项目ID是否正确</p>
|
||||||
<Link to="/projects">
|
<Link to="/projects">
|
||||||
<Button>
|
<Button className="retro-btn">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
返回项目列表
|
返回项目列表
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -282,126 +310,131 @@ export default function ProjectDetail() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 font-mono">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-start space-x-4">
|
||||||
<Link to="/projects">
|
<Link to="/projects">
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10 w-10 p-0 flex items-center justify-center">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
返回
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">{project.name}</h1>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-gray-600 mt-1">
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">{project.name}</h1>
|
||||||
|
<Badge variant="outline" className={`rounded-none border-2 border-black ${project.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}>
|
||||||
|
{project.is_active ? '活跃' : '暂停'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">
|
||||||
{project.description || '暂无项目描述'}
|
{project.description || '暂无项目描述'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Badge variant={project.is_active ? "default" : "secondary"}>
|
<Button onClick={handleRunAudit} disabled={scanning} className="retro-btn bg-primary text-white hover:bg-primary/90">
|
||||||
{project.is_active ? '活跃' : '暂停'}
|
|
||||||
</Badge>
|
|
||||||
<Button onClick={handleRunAudit} disabled={scanning}>
|
|
||||||
<Shield className="w-4 h-4 mr-2" />
|
<Shield className="w-4 h-4 mr-2" />
|
||||||
{scanning ? '正在启动...' : '启动审计'}
|
{scanning ? '正在启动...' : '启动审计'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" onClick={handleOpenSettings}>
|
<Button variant="outline" onClick={handleOpenSettings} className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目概览 */}
|
{/* ... (stats cards remain same) ... */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<Card>
|
{/* ... (stats cards content) ... */}
|
||||||
<CardContent className="p-6">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center justify-between">
|
||||||
<Activity className="h-8 w-8 text-blue-600" />
|
<div>
|
||||||
<div className="ml-4">
|
<p className="text-xs font-bold uppercase text-gray-500">审计任务</p>
|
||||||
<p className="text-sm font-medium text-muted-foreground">审计任务</p>
|
<p className="text-3xl font-display font-bold">{tasks.length}</p>
|
||||||
<p className="text-2xl font-bold">{tasks.length}</p>
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-blue-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Activity className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div>
|
||||||
<CheckCircle className="h-8 w-8 text-green-600" />
|
<p className="text-xs font-bold uppercase text-gray-500">已完成</p>
|
||||||
<div className="ml-4">
|
<p className="text-3xl font-display font-bold">
|
||||||
<p className="text-sm font-medium text-muted-foreground">已完成</p>
|
|
||||||
<p className="text-2xl font-bold">
|
|
||||||
{tasks.filter(t => t.status === 'completed').length}
|
{tasks.filter(t => t.status === 'completed').length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-green-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div>
|
||||||
<AlertTriangle className="h-8 w-8 text-orange-600" />
|
<p className="text-xs font-bold uppercase text-gray-500">发现问题</p>
|
||||||
<div className="ml-4">
|
<p className="text-3xl font-display font-bold">
|
||||||
<p className="text-sm font-medium text-muted-foreground">发现问题</p>
|
|
||||||
<p className="text-2xl font-bold">
|
|
||||||
{tasks.reduce((sum, task) => sum + task.issues_count, 0)}
|
{tasks.reduce((sum, task) => sum + task.issues_count, 0)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-orange-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<AlertTriangle className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-6">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div>
|
||||||
<Code className="h-8 w-8 text-purple-600" />
|
<p className="text-xs font-bold uppercase text-gray-500">平均质量分</p>
|
||||||
<div className="ml-4">
|
<p className="text-3xl font-display font-bold">
|
||||||
<p className="text-sm font-medium text-muted-foreground">平均质量分</p>
|
|
||||||
<p className="text-2xl font-bold">
|
|
||||||
{tasks.length > 0
|
{tasks.length > 0
|
||||||
? (tasks.reduce((sum, task) => sum + task.quality_score, 0) / tasks.length).toFixed(1)
|
? (tasks.reduce((sum, task) => sum + task.quality_score, 0) / tasks.length).toFixed(1)
|
||||||
: '0.0'
|
: '0.0'
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-12 h-12 border-2 border-black bg-purple-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Code className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 主要内容 */}
|
{/* 主要内容 */}
|
||||||
<Tabs defaultValue="overview" className="w-full">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
<TabsList className="grid w-full grid-cols-4 bg-transparent border-2 border-black p-0 h-auto gap-0">
|
||||||
<TabsTrigger value="overview">项目概览</TabsTrigger>
|
<TabsTrigger value="overview" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10">项目概览</TabsTrigger>
|
||||||
<TabsTrigger value="tasks">审计任务</TabsTrigger>
|
<TabsTrigger value="tasks" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10">审计任务</TabsTrigger>
|
||||||
<TabsTrigger value="issues">问题管理</TabsTrigger>
|
<TabsTrigger value="issues" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10">问题管理</TabsTrigger>
|
||||||
<TabsTrigger value="settings">项目设置</TabsTrigger>
|
<TabsTrigger value="settings" className="rounded-none data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10">项目设置</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="overview" className="space-y-6">
|
<TabsContent value="overview" className="space-y-6 mt-6">
|
||||||
|
{/* ... (overview content remains same) ... */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* 项目信息 */}
|
{/* 项目信息 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardHeader>
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<CardTitle>项目信息</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">项目信息</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="space-y-4 font-mono">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{project.repository_url && (
|
{project.repository_url && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium">仓库地址</span>
|
<span className="text-sm font-bold text-gray-600 uppercase">仓库地址</span>
|
||||||
<a
|
<a
|
||||||
href={project.repository_url}
|
href={project.repository_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-sm text-blue-600 hover:underline flex items-center"
|
className="text-sm text-primary hover:underline flex items-center font-bold"
|
||||||
>
|
>
|
||||||
查看仓库
|
查看仓库
|
||||||
<ExternalLink className="w-3 h-3 ml-1" />
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
|
|
@ -410,28 +443,28 @@ export default function ProjectDetail() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium">仓库类型</span>
|
<span className="text-sm font-bold text-gray-600 uppercase">仓库类型</span>
|
||||||
<Badge variant="outline">
|
<Badge variant="outline" className="rounded-none border-black bg-gray-100 text-black">
|
||||||
{project.repository_type === 'github' ? 'GitHub' :
|
{project.repository_type === 'github' ? 'GitHub' :
|
||||||
project.repository_type === 'gitlab' ? 'GitLab' : '其他'}
|
project.repository_type === 'gitlab' ? 'GitLab' : '其他'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium">默认分支</span>
|
<span className="text-sm font-bold text-gray-600 uppercase">默认分支</span>
|
||||||
<span className="text-sm text-muted-foreground">{project.default_branch}</span>
|
<span className="text-sm font-bold text-black bg-gray-100 px-2 border border-black">{project.default_branch}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium">创建时间</span>
|
<span className="text-sm font-bold text-gray-600 uppercase">创建时间</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-black">
|
||||||
{formatDate(project.created_at)}
|
{formatDate(project.created_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium">所有者</span>
|
<span className="text-sm font-bold text-gray-600 uppercase">所有者</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-black">
|
||||||
{project.owner?.full_name || project.owner?.phone || '未知'}
|
{project.owner?.full_name || project.owner?.phone || '未知'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -439,42 +472,44 @@ export default function ProjectDetail() {
|
||||||
|
|
||||||
{/* 编程语言 */}
|
{/* 编程语言 */}
|
||||||
{project.programming_languages && (
|
{project.programming_languages && (
|
||||||
<div>
|
<div className="pt-4 border-t-2 border-dashed border-gray-300">
|
||||||
<h4 className="text-sm font-medium mb-2">支持的编程语言</h4>
|
<h4 className="text-sm font-bold mb-2 uppercase text-gray-600">支持的编程语言</h4>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{JSON.parse(project.programming_languages).map((lang: string) => (
|
{JSON.parse(project.programming_languages).map((lang: string) => (
|
||||||
<Badge key={lang} variant="outline">
|
<Badge key={lang} variant="outline" className="rounded-none border-black bg-yellow-100 text-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{lang}
|
{lang}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
{/* 最近活动 */}
|
{/* 最近活动 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardHeader>
|
<div className="pb-3 border-b-2 border-black mb-4">
|
||||||
<CardTitle>最近活动</CardTitle>
|
<h3 className="text-lg font-display font-bold uppercase">最近活动</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div>
|
||||||
{tasks.length > 0 ? (
|
{tasks.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{tasks.slice(0, 5).map((task) => (
|
{tasks.slice(0, 5).map((task) => (
|
||||||
<div key={task.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
<div key={task.id} className="flex items-center justify-between p-3 border-2 border-black bg-gray-50 hover:bg-white hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="border-2 border-black p-1 bg-white">
|
||||||
{getStatusIcon(task.status)}
|
{getStatusIcon(task.status)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-bold font-mono uppercase">
|
||||||
{task.task_type === 'repository' ? '仓库审计' : '即时分析'}
|
{task.task_type === 'repository' ? '仓库审计' : '即时分析'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-gray-500 font-mono">
|
||||||
{formatDate(task.created_at)}
|
{formatDate(task.created_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={getStatusColor(task.status)}>
|
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
|
||||||
{task.status === 'completed' ? '已完成' :
|
{task.status === 'completed' ? '已完成' :
|
||||||
task.status === 'running' ? '运行中' :
|
task.status === 'running' ? '运行中' :
|
||||||
task.status === 'failed' ? '失败' : '等待中'}
|
task.status === 'failed' ? '失败' : '等待中'}
|
||||||
|
|
@ -483,20 +518,21 @@ export default function ProjectDetail() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8 border-2 border-dashed border-black">
|
||||||
<Activity className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
|
<Activity className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||||
<p className="text-muted-foreground">暂无活动记录</p>
|
<p className="text-gray-500 font-mono uppercase">暂无活动记录</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="tasks" className="space-y-6">
|
<TabsContent value="tasks" className="space-y-6 mt-6">
|
||||||
|
{/* ... (tasks content remains same) ... */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-medium">审计任务列表</h3>
|
<h3 className="text-lg font-display font-bold uppercase">审计任务列表</h3>
|
||||||
<Button onClick={handleCreateTask}>
|
<Button onClick={handleCreateTask} className="retro-btn bg-primary text-white hover:bg-primary/90">
|
||||||
<Play className="w-4 h-4 mr-2" />
|
<Play className="w-4 h-4 mr-2" />
|
||||||
新建任务
|
新建任务
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -505,96 +541,260 @@ export default function ProjectDetail() {
|
||||||
{tasks.length > 0 ? (
|
{tasks.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{tasks.map((task) => (
|
{tasks.map((task) => (
|
||||||
<Card key={task.id}>
|
<div key={task.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6">
|
||||||
<CardContent className="p-6">
|
<div className="flex items-center justify-between mb-4 pb-4 border-b-2 border-dashed border-gray-300">
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="border-2 border-black p-1 bg-white">
|
||||||
{getStatusIcon(task.status)}
|
{getStatusIcon(task.status)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium">
|
<h4 className="font-bold font-mono uppercase">
|
||||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-gray-500 font-mono">
|
||||||
创建于 {formatDate(task.created_at)}
|
创建于 {formatDate(task.created_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={getStatusColor(task.status)}>
|
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
|
||||||
{task.status === 'completed' ? '已完成' :
|
{task.status === 'completed' ? '已完成' :
|
||||||
task.status === 'running' ? '运行中' :
|
task.status === 'running' ? '运行中' :
|
||||||
task.status === 'failed' ? '失败' : '等待中'}
|
task.status === 'failed' ? '失败' : '等待中'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4 font-mono">
|
||||||
<div className="text-center">
|
<div className="text-center p-2 bg-gray-50 border border-gray-200">
|
||||||
<p className="text-2xl font-bold">{task.total_files}</p>
|
<p className="text-2xl font-bold">{task.total_files}</p>
|
||||||
<p className="text-sm text-muted-foreground">总文件数</p>
|
<p className="text-xs text-gray-500 uppercase">总文件数</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center p-2 bg-gray-50 border border-gray-200">
|
||||||
<p className="text-2xl font-bold">{task.total_lines}</p>
|
<p className="text-2xl font-bold">{task.total_lines}</p>
|
||||||
<p className="text-sm text-muted-foreground">代码行数</p>
|
<p className="text-xs text-gray-500 uppercase">代码行数</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center p-2 bg-gray-50 border border-gray-200">
|
||||||
<p className="text-2xl font-bold">{task.issues_count}</p>
|
<p className="text-2xl font-bold text-orange-600">{task.issues_count}</p>
|
||||||
<p className="text-sm text-muted-foreground">发现问题</p>
|
<p className="text-xs text-gray-500 uppercase">发现问题</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center p-2 bg-gray-50 border border-gray-200">
|
||||||
<p className="text-2xl font-bold">{task.quality_score.toFixed(1)}</p>
|
<p className="text-2xl font-bold text-primary">{task.quality_score.toFixed(1)}</p>
|
||||||
<p className="text-sm text-muted-foreground">质量评分</p>
|
<p className="text-xs text-gray-500 uppercase">质量评分</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.status === 'completed' && (
|
{task.status === 'completed' && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 mb-4">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm font-mono font-bold">
|
||||||
<span>质量评分</span>
|
<span>质量评分</span>
|
||||||
<span>{task.quality_score.toFixed(1)}/100</span>
|
<span>{task.quality_score.toFixed(1)}/100</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={task.quality_score} />
|
<Progress value={task.quality_score} className="h-3 border-2 border-black rounded-none bg-gray-100 [&>div]:bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 mt-4">
|
<div className="flex justify-end space-x-2 mt-4 pt-4 border-t-2 border-black">
|
||||||
<Link to={`/tasks/${task.id}`}>
|
<Link to={`/tasks/${task.id}`}>
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
<FileText className="w-4 h-4 mr-2" />
|
<FileText className="w-4 h-4 mr-2" />
|
||||||
查看详情
|
查看详情
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-12 flex flex-col items-center justify-center">
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
<Activity className="w-16 h-16 text-gray-400 mb-4" />
|
||||||
<Activity className="w-16 h-16 text-muted-foreground mb-4" />
|
<h3 className="text-lg font-bold text-gray-600 mb-2 uppercase">暂无审计任务</h3>
|
||||||
<h3 className="text-lg font-medium text-muted-foreground mb-2">暂无审计任务</h3>
|
<p className="text-sm text-gray-500 mb-6 font-mono">创建第一个审计任务开始代码质量分析</p>
|
||||||
<p className="text-sm text-muted-foreground mb-4">创建第一个审计任务开始代码质量分析</p>
|
<Button onClick={handleCreateTask} className="retro-btn bg-primary text-white hover:bg-primary/90">
|
||||||
<Button onClick={handleCreateTask}>
|
|
||||||
<Play className="w-4 h-4 mr-2" />
|
<Play className="w-4 h-4 mr-2" />
|
||||||
创建任务
|
创建任务
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="issues" className="space-y-6">
|
<TabsContent value="issues" className="space-y-6 mt-6">
|
||||||
<div className="text-center py-12">
|
<div className="flex items-center justify-between">
|
||||||
<AlertTriangle className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
<h3 className="text-lg font-display font-bold uppercase">最新发现的问题</h3>
|
||||||
<h3 className="text-lg font-medium text-muted-foreground mb-2">问题管理</h3>
|
{tasks.length > 0 && (
|
||||||
<p className="text-sm text-muted-foreground">此功能正在开发中</p>
|
<p className="text-sm text-gray-500 font-mono">
|
||||||
|
来自最近一次审计 ({formatDate(tasks[0].created_at)})
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{loadingIssues ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="animate-spin w-8 h-8 border-4 border-primary border-t-transparent rounded-full mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-500 font-mono">正在加载问题列表...</p>
|
||||||
|
</div>
|
||||||
|
) : latestIssues.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{latestIssues.map((issue, index) => (
|
||||||
|
<div key={index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex items-start space-x-3">
|
||||||
|
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${issue.severity === 'critical' ? 'bg-red-500 text-white' :
|
||||||
|
issue.severity === 'high' ? 'bg-orange-500 text-white' :
|
||||||
|
issue.severity === 'medium' ? 'bg-yellow-400 text-black' :
|
||||||
|
'bg-blue-400 text-white'
|
||||||
|
}`}>
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-base text-black mb-1 font-mono uppercase">{issue.title}</h4>
|
||||||
|
<div className="flex items-center space-x-2 text-xs text-gray-600 font-mono">
|
||||||
|
<span className="bg-gray-100 px-1 border border-gray-300">{issue.file_path}:{issue.line_number}</span>
|
||||||
|
<span>{issue.category}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Badge className={`rounded-none border-2 border-black ${issue.severity === 'critical' ? 'bg-red-100 text-red-800' :
|
||||||
|
issue.severity === 'high' ? 'bg-orange-100 text-orange-800' :
|
||||||
|
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-800' :
|
||||||
|
'bg-blue-100 text-blue-800'
|
||||||
|
} font-bold uppercase`}>
|
||||||
|
{issue.severity === 'critical' ? '严重' :
|
||||||
|
issue.severity === 'high' ? '高' :
|
||||||
|
issue.severity === 'medium' ? '中等' : '低'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-sm text-gray-700 font-mono border-t-2 border-dashed border-gray-200 pt-2">
|
||||||
|
{issue.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-12 flex flex-col items-center justify-center">
|
||||||
|
<CheckCircle className="w-16 h-16 text-green-500 mb-4" />
|
||||||
|
<h3 className="text-lg font-bold text-gray-600 mb-2 uppercase">未发现问题</h3>
|
||||||
|
<p className="text-sm text-gray-500 font-mono">最近一次审计未发现明显问题,或尚未进行审计。</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="settings" className="space-y-6">
|
<TabsContent value="settings" className="space-y-6 mt-6">
|
||||||
<div className="text-center py-12">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6">
|
||||||
<Edit className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
<div className="pb-4 border-b-2 border-black mb-6">
|
||||||
<h3 className="text-lg font-medium text-muted-foreground mb-2">项目编辑</h3>
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<p className="text-sm text-muted-foreground">此功能正在开发中</p>
|
<Edit className="w-5 h-5 mr-2" />
|
||||||
|
编辑项目配置
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 基本信息 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="edit-name" className="font-bold font-mono uppercase">项目名称 *</Label>
|
||||||
|
<Input
|
||||||
|
id="edit-name"
|
||||||
|
value={editForm.name}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||||
|
placeholder="输入项目名称"
|
||||||
|
className="retro-input mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="edit-description" className="font-bold font-mono uppercase">项目描述</Label>
|
||||||
|
<Textarea
|
||||||
|
id="edit-description"
|
||||||
|
value={editForm.description}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
||||||
|
placeholder="输入项目描述"
|
||||||
|
rows={3}
|
||||||
|
className="retro-input mt-1 min-h-[80px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 仓库信息 */}
|
||||||
|
<div className="space-y-4 border-t-2 border-dashed border-gray-300 pt-4">
|
||||||
|
<h3 className="text-sm font-bold font-mono uppercase text-gray-900 bg-gray-100 inline-block px-2 border border-black">仓库信息</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="edit-repo-url" className="font-bold font-mono uppercase">仓库地址</Label>
|
||||||
|
<Input
|
||||||
|
id="edit-repo-url"
|
||||||
|
value={editForm.repository_url}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
|
||||||
|
placeholder="https://github.com/username/repo"
|
||||||
|
className="retro-input mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="edit-repo-type" className="font-bold font-mono uppercase">仓库类型</Label>
|
||||||
|
<Select
|
||||||
|
value={editForm.repository_type}
|
||||||
|
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="edit-repo-type" className="retro-input mt-1 rounded-none border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] focus:ring-0">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<SelectItem value="github">GitHub</SelectItem>
|
||||||
|
<SelectItem value="gitlab">GitLab</SelectItem>
|
||||||
|
<SelectItem value="other">其他</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="edit-branch" className="font-bold font-mono uppercase">默认分支</Label>
|
||||||
|
<Input
|
||||||
|
id="edit-branch"
|
||||||
|
value={editForm.default_branch}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
|
||||||
|
placeholder="main"
|
||||||
|
className="retro-input mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 编程语言 */}
|
||||||
|
<div className="space-y-4 border-t-2 border-dashed border-gray-300 pt-4">
|
||||||
|
<h3 className="text-sm font-bold font-mono uppercase text-gray-900 bg-gray-100 inline-block px-2 border border-black">编程语言</h3>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
|
{supportedLanguages.map((lang) => (
|
||||||
|
<div
|
||||||
|
key={lang}
|
||||||
|
className={`flex items-center space-x-2 p-3 border-2 cursor-pointer transition-all hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] ${editForm.programming_languages?.includes(lang)
|
||||||
|
? 'border-black bg-yellow-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]'
|
||||||
|
: 'border-gray-300 hover:border-black'
|
||||||
|
}`}
|
||||||
|
onClick={() => handleToggleLanguage(lang)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-4 h-4 border-2 flex items-center justify-center ${editForm.programming_languages?.includes(lang)
|
||||||
|
? 'bg-black border-black'
|
||||||
|
: 'border-gray-400 bg-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{editForm.programming_languages?.includes(lang) && (
|
||||||
|
<CheckCircle className="w-3 h-3 text-white" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-bold font-mono">{lang}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3 pt-6 border-t-2 border-black">
|
||||||
|
<Button onClick={handleSaveSettings} className="retro-btn bg-primary text-white hover:bg-primary/90 w-full md:w-auto">
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
保存修改
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
@ -614,125 +814,6 @@ export default function ProjectDetail() {
|
||||||
taskId={currentTaskId}
|
taskId={currentTaskId}
|
||||||
taskType="repository"
|
taskType="repository"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 项目编辑对话框 */}
|
|
||||||
<Dialog open={showSettingsDialog} onOpenChange={setShowSettingsDialog}>
|
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>编辑项目</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-6 py-4">
|
|
||||||
{/* 基本信息 */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="edit-name">项目名称 *</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-name"
|
|
||||||
value={editForm.name}
|
|
||||||
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
|
||||||
placeholder="输入项目名称"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="edit-description">项目描述</Label>
|
|
||||||
<Textarea
|
|
||||||
id="edit-description"
|
|
||||||
value={editForm.description}
|
|
||||||
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
|
||||||
placeholder="输入项目描述"
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 仓库信息 */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-sm font-semibold text-gray-900">仓库信息</h3>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="edit-repo-url">仓库地址</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-repo-url"
|
|
||||||
value={editForm.repository_url}
|
|
||||||
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
|
|
||||||
placeholder="https://github.com/username/repo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="edit-repo-type">仓库类型</Label>
|
|
||||||
<Select
|
|
||||||
value={editForm.repository_type}
|
|
||||||
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="edit-repo-type">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="github">GitHub</SelectItem>
|
|
||||||
<SelectItem value="gitlab">GitLab</SelectItem>
|
|
||||||
<SelectItem value="other">其他</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="edit-branch">默认分支</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-branch"
|
|
||||||
value={editForm.default_branch}
|
|
||||||
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
|
|
||||||
placeholder="main"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 编程语言 */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-sm font-semibold text-gray-900">编程语言</h3>
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
|
||||||
{supportedLanguages.map((lang) => (
|
|
||||||
<div
|
|
||||||
key={lang}
|
|
||||||
className={`flex items-center space-x-2 p-3 rounded-lg border cursor-pointer transition-all ${
|
|
||||||
editForm.programming_languages?.includes(lang)
|
|
||||||
? 'border-primary bg-red-50'
|
|
||||||
: 'border-gray-200 hover:border-gray-300'
|
|
||||||
}`}
|
|
||||||
onClick={() => handleToggleLanguage(lang)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`w-4 h-4 rounded border flex items-center justify-center ${
|
|
||||||
editForm.programming_languages?.includes(lang)
|
|
||||||
? 'bg-primary border-primary'
|
|
||||||
: 'border-gray-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{editForm.programming_languages?.includes(lang) && (
|
|
||||||
<CheckCircle className="w-3 h-3 text-white" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span className="text-sm font-medium">{lang}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 pt-4 border-t">
|
|
||||||
<Button variant="outline" onClick={() => setShowSettingsDialog(false)}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleSaveSettings}>
|
|
||||||
保存修改
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Users,
|
Users,
|
||||||
Settings,
|
Settings,
|
||||||
ExternalLink,
|
|
||||||
Code,
|
Code,
|
||||||
Shield,
|
Shield,
|
||||||
Activity,
|
Activity,
|
||||||
|
|
@ -26,7 +25,8 @@ import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Trash2,
|
Trash2,
|
||||||
Edit,
|
Edit,
|
||||||
CheckCircle
|
CheckCircle,
|
||||||
|
Terminal
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { api } from "@/shared/config/database";
|
import { api } from "@/shared/config/database";
|
||||||
import { validateZipFile } from "@/features/projects/services";
|
import { validateZipFile } from "@/features/projects/services";
|
||||||
|
|
@ -119,7 +119,7 @@ export default function Projects() {
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
// 记录用户操作
|
// 记录用户操作
|
||||||
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
|
import('@/shared/utils/logger').then(({ logger }) => {
|
||||||
logger.logUserAction('创建项目', {
|
logger.logUserAction('创建项目', {
|
||||||
projectName: createForm.name,
|
projectName: createForm.name,
|
||||||
repositoryType: createForm.repository_type,
|
repositoryType: createForm.repository_type,
|
||||||
|
|
@ -204,7 +204,7 @@ export default function Projects() {
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
|
|
||||||
// 记录用户操作
|
// 记录用户操作
|
||||||
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
|
import('@/shared/utils/logger').then(({ logger }) => {
|
||||||
logger.logUserAction('上传ZIP文件创建项目', {
|
logger.logUserAction('上传ZIP文件创建项目', {
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
|
|
@ -317,7 +317,7 @@ export default function Projects() {
|
||||||
await api.deleteProject(projectToDelete.id);
|
await api.deleteProject(projectToDelete.id);
|
||||||
|
|
||||||
// 记录用户操作
|
// 记录用户操作
|
||||||
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
|
import('@/shared/utils/logger').then(({ logger }) => {
|
||||||
logger.logUserAction('删除项目', {
|
logger.logUserAction('删除项目', {
|
||||||
projectId: projectToDelete.id,
|
projectId: projectToDelete.id,
|
||||||
projectName: projectToDelete.name,
|
projectName: projectToDelete.name,
|
||||||
|
|
@ -361,98 +361,127 @@ export default function Projects() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-8 p-6 bg-background min-h-screen font-mono relative overflow-hidden">
|
||||||
{/* 页面标题和操作 */}
|
{/* Decorative Background */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">项目管理</h1>
|
<h1 className="text-4xl font-display font-bold uppercase tracking-tighter mb-2">
|
||||||
<p className="page-subtitle">管理您的代码项目,配置审计规则和查看分析结果</p>
|
项目<span className="text-primary">_管理</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-500 font-mono text-sm flex items-center gap-2">
|
||||||
|
<Terminal className="w-4 h-4" />
|
||||||
|
// 管理仓库 // 配置审计
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>
|
<Button className="retro-btn h-12 text-lg">
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-5 h-5 mr-2" />
|
||||||
新建项目
|
初始化项目
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-3xl retro-card border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] bg-white p-0 overflow-hidden">
|
||||||
<DialogHeader>
|
<DialogHeader className="bg-black text-white p-4 border-b-4 border-black">
|
||||||
<DialogTitle>创建新项目</DialogTitle>
|
<DialogTitle className="font-mono text-xl uppercase tracking-widest flex items-center gap-2">
|
||||||
|
<Terminal className="w-5 h-5" />
|
||||||
|
初始化_新_项目
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="p-6">
|
||||||
<Tabs defaultValue="repository" className="w-full">
|
<Tabs defaultValue="repository" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2 bg-gray-100 border-2 border-black p-1 h-auto">
|
||||||
<TabsTrigger value="repository">Git 仓库</TabsTrigger>
|
<TabsTrigger
|
||||||
<TabsTrigger value="upload">上传代码</TabsTrigger>
|
value="repository"
|
||||||
|
className="data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase py-2 border-2 border-transparent data-[state=active]:border-black data-[state=active]:shadow-sm transition-all"
|
||||||
|
>
|
||||||
|
Git 仓库
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="upload"
|
||||||
|
className="data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase py-2 border-2 border-transparent data-[state=active]:border-black data-[state=active]:shadow-sm transition-all"
|
||||||
|
>
|
||||||
|
上传源码
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="repository" className="space-y-4 mt-6">
|
<TabsContent value="repository" className="space-y-6 mt-6">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="name">项目名称 *</Label>
|
<Label htmlFor="name" className="font-mono font-bold uppercase text-xs">项目名称 *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
value={createForm.name}
|
value={createForm.name}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
|
||||||
placeholder="输入项目名称"
|
placeholder="输入项目名称"
|
||||||
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="repository_type">仓库类型</Label>
|
<Label htmlFor="repository_type" className="font-mono font-bold uppercase text-xs">仓库类型</Label>
|
||||||
<Select
|
<Select
|
||||||
value={createForm.repository_type}
|
value={createForm.repository_type}
|
||||||
onValueChange={(value: any) => setCreateForm({ ...createForm, repository_type: value })}
|
onValueChange={(value: any) => setCreateForm({ ...createForm, repository_type: value })}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="retro-input">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="retro-card border-2 border-black">
|
||||||
<SelectItem value="github">GitHub</SelectItem>
|
<SelectItem value="github">GITHUB</SelectItem>
|
||||||
<SelectItem value="gitlab">GitLab</SelectItem>
|
<SelectItem value="gitlab">GITLAB</SelectItem>
|
||||||
<SelectItem value="other">其他</SelectItem>
|
<SelectItem value="other">OTHER</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="description">项目描述</Label>
|
<Label htmlFor="description" className="font-mono font-bold uppercase text-xs">描述</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
value={createForm.description}
|
value={createForm.description}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
||||||
placeholder="简要描述项目内容和目标"
|
placeholder="// 项目描述..."
|
||||||
rows={3}
|
rows={3}
|
||||||
|
className="retro-input min-h-[100px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="repository_url">仓库地址</Label>
|
<Label htmlFor="repository_url" className="font-mono font-bold uppercase text-xs">仓库地址</Label>
|
||||||
<Input
|
<Input
|
||||||
id="repository_url"
|
id="repository_url"
|
||||||
value={createForm.repository_url}
|
value={createForm.repository_url}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, repository_url: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, repository_url: e.target.value })}
|
||||||
placeholder="https://github.com/user/repo"
|
placeholder="https://github.com/user/repo"
|
||||||
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="default_branch">默认分支</Label>
|
<Label htmlFor="default_branch" className="font-mono font-bold uppercase text-xs">默认分支</Label>
|
||||||
<Input
|
<Input
|
||||||
id="default_branch"
|
id="default_branch"
|
||||||
value={createForm.default_branch}
|
value={createForm.default_branch}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, default_branch: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, default_branch: e.target.value })}
|
||||||
placeholder="main"
|
placeholder="main"
|
||||||
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label>编程语言</Label>
|
<Label className="font-mono font-bold uppercase text-xs">技术栈</Label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{supportedLanguages.map((lang) => (
|
{supportedLanguages.map((lang) => (
|
||||||
<label key={lang} className="flex items-center space-x-2">
|
<label key={lang} className={`flex items-center space-x-3 p-2 border-2 cursor-pointer transition-all ${createForm.programming_languages.includes(lang)
|
||||||
|
? 'border-black bg-primary/10 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]'
|
||||||
|
: 'border-gray-200 hover:border-black'
|
||||||
|
}`}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={createForm.programming_languages.includes(lang)}
|
checked={createForm.programming_languages.includes(lang)}
|
||||||
|
|
@ -469,51 +498,57 @@ export default function Projects() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded"
|
className="rounded border-2 border-black w-4 h-4 text-primary focus:ring-0"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">{lang}</span>
|
<span className="text-sm font-mono font-bold uppercase">{lang}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 pt-4">
|
<div className="flex justify-end space-x-4 pt-4 border-t-2 border-dashed border-gray-200">
|
||||||
<Button variant="outline" onClick={() => setShowCreateDialog(false)}>
|
<Button variant="outline" onClick={() => setShowCreateDialog(false)} className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCreateProject}>
|
<Button onClick={handleCreateProject} className="retro-btn">
|
||||||
创建项目
|
执行创建
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="upload" className="space-y-4 mt-6">
|
<TabsContent value="upload" className="space-y-6 mt-6">
|
||||||
|
{/* Upload Tab Content - Similar styling */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="upload-name">项目名称 *</Label>
|
<Label htmlFor="upload-name" className="font-mono font-bold uppercase text-xs">项目名称 *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="upload-name"
|
id="upload-name"
|
||||||
value={createForm.name}
|
value={createForm.name}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
|
||||||
placeholder="输入项目名称"
|
placeholder="输入项目名称"
|
||||||
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="upload-description">项目描述</Label>
|
<Label htmlFor="upload-description" className="font-mono font-bold uppercase text-xs">描述</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="upload-description"
|
id="upload-description"
|
||||||
value={createForm.description}
|
value={createForm.description}
|
||||||
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
||||||
placeholder="简要描述项目内容和目标"
|
placeholder="// 项目描述..."
|
||||||
rows={3}
|
rows={3}
|
||||||
|
className="retro-input min-h-[100px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label>编程语言</Label>
|
<Label className="font-mono font-bold uppercase text-xs">技术栈</Label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{supportedLanguages.map((lang) => (
|
{supportedLanguages.map((lang) => (
|
||||||
<label key={lang} className="flex items-center space-x-2">
|
<label key={lang} className={`flex items-center space-x-3 p-2 border-2 cursor-pointer transition-all ${createForm.programming_languages.includes(lang)
|
||||||
|
? 'border-black bg-primary/10 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]'
|
||||||
|
: 'border-gray-200 hover:border-black'
|
||||||
|
}`}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={createForm.programming_languages.includes(lang)}
|
checked={createForm.programming_languages.includes(lang)}
|
||||||
|
|
@ -530,22 +565,21 @@ export default function Projects() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded"
|
className="rounded border-2 border-black w-4 h-4 text-primary focus:ring-0"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">{lang}</span>
|
<span className="text-sm font-mono font-bold uppercase">{lang}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 文件上传区域 */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label>上传代码文件</Label>
|
<Label className="font-mono font-bold uppercase text-xs">源代码</Label>
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
|
<div className="border-2 border-dashed border-black bg-gray-50 rounded-none p-8 text-center hover:bg-white transition-colors cursor-pointer group" onClick={() => fileInputRef.current?.click()}>
|
||||||
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
<Upload className="w-12 h-12 text-black mx-auto mb-4 group-hover:scale-110 transition-transform" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">上传 ZIP 文件</h3>
|
<h3 className="text-lg font-bold font-display uppercase mb-2">上传 ZIP 归档</h3>
|
||||||
<p className="text-sm text-gray-500 mb-4">
|
<p className="text-xs font-mono text-gray-500 mb-4">
|
||||||
支持 ZIP 格式,最大 100MB
|
最大: 100MB // 格式: .ZIP
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
|
|
@ -558,8 +592,12 @@ export default function Projects() {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
className="retro-btn bg-white text-black"
|
||||||
disabled={uploading || !createForm.name.trim()}
|
disabled={uploading || !createForm.name.trim()}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FileText className="w-4 h-4 mr-2" />
|
<FileText className="w-4 h-4 mr-2" />
|
||||||
选择文件
|
选择文件
|
||||||
|
|
@ -568,261 +606,225 @@ export default function Projects() {
|
||||||
|
|
||||||
{uploading && (
|
{uploading && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-xs font-mono">
|
||||||
<span>上传并分析中...</span>
|
<span>上传并分析中...</span>
|
||||||
<span>{uploadProgress}%</span>
|
<span>{uploadProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={uploadProgress} />
|
<Progress value={uploadProgress} className="h-4 border-2 border-black rounded-none bg-white [&>div]:bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
<div className="bg-yellow-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<AlertCircle className="w-5 h-5 text-primary mt-0.5" />
|
<AlertCircle className="w-5 h-5 text-black mt-0.5" />
|
||||||
<div className="text-sm text-red-800">
|
<div className="text-xs font-mono text-black">
|
||||||
<p className="font-medium mb-1">上传说明:</p>
|
<p className="font-bold mb-1 uppercase">上传协议:</p>
|
||||||
<ul className="space-y-1 text-xs">
|
<ul className="space-y-1">
|
||||||
<li>• 请确保 ZIP 文件包含完整的项目代码</li>
|
<li>> 确保完整的项目代码</li>
|
||||||
<li>• 系统会自动排除 node_modules、.git 等目录</li>
|
<li>> 排除: node_modules, .git</li>
|
||||||
<li>• ZIP 文件会保存,只需上传一次</li>
|
<li>> 归档将被存储</li>
|
||||||
<li>• 创建后可在项目详情页启动多次审计</li>
|
<li>> 支持多次审计</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 pt-4">
|
<div className="flex justify-end space-x-4 pt-4 border-t-2 border-dashed border-gray-200">
|
||||||
<Button variant="outline" onClick={() => setShowCreateDialog(false)} disabled={uploading}>
|
<Button variant="outline" onClick={() => setShowCreateDialog(false)} disabled={uploading} className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 搜索和筛选 */}
|
{/* Stats Section */}
|
||||||
<Card>
|
{projects.length > 0 && (
|
||||||
<CardContent className="p-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 relative z-10">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">项目总数</p>
|
||||||
|
<p className="font-display text-2xl font-bold">{projects.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 border-2 border-black bg-primary flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Code className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">活跃</p>
|
||||||
|
<p className="font-display text-2xl font-bold">{projects.filter(p => p.is_active).length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 border-2 border-black bg-green-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Activity className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">GitHub</p>
|
||||||
|
<p className="font-display text-2xl font-bold">{projects.filter(p => p.repository_type === 'github').length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 border-2 border-black bg-gray-800 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<GitBranch className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-xs font-bold uppercase text-gray-500">GitLab</p>
|
||||||
|
<p className="font-display text-2xl font-bold">{projects.filter(p => p.repository_type === 'gitlab').length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 border-2 border-black bg-orange-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Shield className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Search and Filter */}
|
||||||
|
<div className="retro-card p-4 flex items-center gap-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] relative z-10">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索项目名称或描述..."
|
placeholder="搜索项目..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-10"
|
className="retro-input pl-10 w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline">
|
<Button variant="outline" className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<Settings className="w-4 h-4 mr-2" />
|
||||||
筛选
|
筛选选项
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 项目列表 */}
|
{/* Project List */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 relative z-10">
|
||||||
{filteredProjects.length > 0 ? (
|
{filteredProjects.length > 0 ? (
|
||||||
filteredProjects.map((project) => (
|
filteredProjects.map((project) => (
|
||||||
<Card key={project.id} className="card-modern group">
|
<div key={project.id} className="retro-card bg-white border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[10px_10px_0px_0px_rgba(0,0,0,1)] transition-all group flex flex-col h-full">
|
||||||
<CardHeader className="pb-4">
|
<div className="p-4 border-b-2 border-black bg-gray-50 flex justify-between items-start">
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center text-white text-lg">
|
<div className="w-10 h-10 border-2 border-black bg-white flex items-center justify-center text-2xl shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{getRepositoryIcon(project.repository_type)}
|
{getRepositoryIcon(project.repository_type)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-lg group-hover:text-primary transition-colors">
|
<h3 className="font-display font-bold text-lg leading-tight group-hover:text-primary transition-colors">
|
||||||
<Link to={`/projects/${project.id}`}>
|
<Link to={`/projects/${project.id}`}>
|
||||||
{project.name}
|
{project.name}
|
||||||
</Link>
|
</Link>
|
||||||
</CardTitle>
|
</h3>
|
||||||
{project.description && (
|
<div className="flex items-center mt-1 space-x-2">
|
||||||
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
|
<Badge variant="outline" className={`text-[10px] font-mono border-black ${project.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Badge variant={project.is_active ? "default" : "secondary"} className="flex-shrink-0">
|
|
||||||
{project.is_active ? '活跃' : '暂停'}
|
{project.is_active ? '活跃' : '暂停'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<CardContent className="space-y-4">
|
<div className="p-4 flex-1 space-y-4">
|
||||||
{/* 项目信息 */}
|
{project.description && (
|
||||||
<div className="space-y-3">
|
<p className="text-sm text-gray-600 font-mono line-clamp-2 border-l-2 border-gray-300 pl-2">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
{project.repository_url && (
|
{project.repository_url && (
|
||||||
<div className="flex items-center text-sm text-gray-500">
|
<div className="flex items-center text-xs font-mono text-gray-500 bg-gray-100 p-1 border border-gray-300">
|
||||||
<GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
|
<GitBranch className="w-3 h-3 mr-2 flex-shrink-0" />
|
||||||
<a
|
<a
|
||||||
href={project.repository_url}
|
href={project.repository_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="hover:text-primary transition-colors flex items-center truncate"
|
className="hover:text-primary transition-colors truncate hover:underline"
|
||||||
>
|
>
|
||||||
<span className="truncate">{project.repository_url.replace('https://', '')}</span>
|
{project.repository_url.replace('https://', '')}
|
||||||
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex justify-between items-center text-xs font-mono text-gray-500">
|
||||||
<div className="flex items-center">
|
<span className="flex items-center"><Calendar className="w-3 h-3 mr-1" /> {formatDate(project.created_at)}</span>
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<span className="flex items-center"><Users className="w-3 h-3 mr-1" /> {project.owner?.full_name || '未知'}</span>
|
||||||
{formatDate(project.created_at)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Users className="w-4 h-4 mr-2" />
|
|
||||||
{project.owner?.full_name || '未知'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编程语言 */}
|
|
||||||
{project.programming_languages && (
|
{project.programming_languages && (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-1">
|
||||||
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
|
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
|
||||||
<Badge key={lang} variant="outline" className="text-xs">
|
<span key={lang} className="text-[10px] font-mono font-bold border border-black px-1 bg-yellow-100">
|
||||||
{lang}
|
{lang.toUpperCase()}
|
||||||
</Badge>
|
</span>
|
||||||
))}
|
))}
|
||||||
{JSON.parse(project.programming_languages).length > 4 && (
|
{JSON.parse(project.programming_languages).length > 4 && (
|
||||||
<Badge variant="outline" className="text-xs">
|
<span className="text-[10px] font-mono font-bold border border-black px-1 bg-gray-100">
|
||||||
+{JSON.parse(project.programming_languages).length - 4}
|
+{JSON.parse(project.programming_languages).length - 4}
|
||||||
</Badge>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 快速操作 */}
|
<div className="p-4 border-t-2 border-black bg-gray-50 grid grid-cols-2 gap-2">
|
||||||
<div className="flex gap-2 pt-2">
|
<Link to={`/projects/${project.id}`} className="col-span-2">
|
||||||
<Link to={`/projects/${project.id}`} className="flex-1">
|
<Button variant="outline" className="w-full retro-btn bg-white text-black h-8 text-xs">
|
||||||
<Button variant="outline" size="sm" className="w-full btn-secondary">
|
<Code className="w-3 h-3 mr-2" />
|
||||||
<Code className="w-4 h-4 mr-2" />
|
|
||||||
查看详情
|
查看详情
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button size="sm" className="retro-btn h-8 text-xs" onClick={() => handleCreateTask(project.id)}>
|
||||||
size="sm"
|
<Shield className="w-3 h-3 mr-2" />
|
||||||
className="btn-primary"
|
审计
|
||||||
onClick={() => handleCreateTask(project.id)}
|
|
||||||
>
|
|
||||||
<Shield className="w-4 h-4 mr-2" />
|
|
||||||
新建任务
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<div className="grid grid-cols-2 gap-2">
|
||||||
size="sm"
|
<Button size="sm" variant="outline" className="retro-btn bg-white text-black h-8 px-0" onClick={() => handleEditClick(project)}>
|
||||||
variant="outline"
|
<Edit className="w-3 h-3" />
|
||||||
className="text-blue-600 hover:text-blue-700 hover:bg-blue-50"
|
|
||||||
onClick={() => handleEditClick(project)}
|
|
||||||
>
|
|
||||||
<Edit className="w-4 h-4" />
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button size="sm" variant="outline" className="retro-btn bg-white text-red-600 border-red-600 h-8 px-0 hover:bg-red-50" onClick={() => handleDeleteClick(project)}>
|
||||||
size="sm"
|
<Trash2 className="w-3 h-3" />
|
||||||
variant="outline"
|
|
||||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
||||||
onClick={() => handleDeleteClick(project)}
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
<Card className="card-modern">
|
<div className="retro-card border-2 border-black p-16 text-center bg-white border-dashed">
|
||||||
<CardContent className="empty-state py-16">
|
<Code className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<div className="empty-icon">
|
<h3 className="text-xl font-display font-bold text-gray-900 mb-2">
|
||||||
<Code className="w-8 h-8 text-primary" />
|
{searchTerm ? '未找到匹配项' : '未初始化项目'}
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
||||||
{searchTerm ? '未找到匹配的项目' : '暂无项目'}
|
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6 max-w-md">
|
<p className="text-gray-500 font-mono mb-6">
|
||||||
{searchTerm ? '尝试调整搜索条件' : '创建您的第一个项目开始代码审计'}
|
{searchTerm ? '调整搜索参数' : '初始化第一个项目以开始'}
|
||||||
</p>
|
</p>
|
||||||
{!searchTerm && (
|
{!searchTerm && (
|
||||||
<Button onClick={() => setShowCreateDialog(true)} className="btn-primary">
|
<Button onClick={() => setShowCreateDialog(true)} className="retro-btn">
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
创建项目
|
初始化项目
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目统计 */}
|
{/* Create Task Dialog */}
|
||||||
{projects.length > 0 && (
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
<Card className="stat-card">
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="stat-label">总项目数</p>
|
|
||||||
<p className="stat-value text-xl">{projects.length}</p>
|
|
||||||
</div>
|
|
||||||
<div className="stat-icon from-primary to-accent">
|
|
||||||
<Code className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="stat-label">活跃项目</p>
|
|
||||||
<p className="stat-value text-xl">{projects.filter(p => p.is_active).length}</p>
|
|
||||||
</div>
|
|
||||||
<div className="stat-icon from-emerald-500 to-emerald-600">
|
|
||||||
<Activity className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="stat-label">GitHub</p>
|
|
||||||
<p className="stat-value text-xl">{projects.filter(p => p.repository_type === 'github').length}</p>
|
|
||||||
</div>
|
|
||||||
<div className="stat-icon from-purple-500 to-purple-600">
|
|
||||||
<GitBranch className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="stat-label">GitLab</p>
|
|
||||||
<p className="stat-value text-xl">{projects.filter(p => p.repository_type === 'gitlab').length}</p>
|
|
||||||
</div>
|
|
||||||
<div className="stat-icon from-orange-500 to-orange-600">
|
|
||||||
<Shield className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 创建任务对话框 */}
|
|
||||||
<CreateTaskDialog
|
<CreateTaskDialog
|
||||||
open={showCreateTaskDialog}
|
open={showCreateTaskDialog}
|
||||||
onOpenChange={setShowCreateTaskDialog}
|
onOpenChange={setShowCreateTaskDialog}
|
||||||
|
|
@ -830,100 +832,97 @@ export default function Projects() {
|
||||||
preselectedProjectId={selectedProjectForTask}
|
preselectedProjectId={selectedProjectForTask}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 编辑项目对话框 */}
|
{/* Edit Dialog */}
|
||||||
<Dialog open={showEditDialog} onOpenChange={setShowEditDialog}>
|
<Dialog open={showEditDialog} onOpenChange={setShowEditDialog}>
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl retro-card border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] bg-white p-0">
|
||||||
<DialogHeader>
|
<DialogHeader className="bg-black text-white p-4 border-b-4 border-black">
|
||||||
<DialogTitle>编辑项目</DialogTitle>
|
<DialogTitle className="font-mono text-xl uppercase tracking-widest flex items-center gap-2">
|
||||||
|
<Edit className="w-5 h-5" />
|
||||||
|
编辑项目配置
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-6 py-4">
|
<div className="p-6 space-y-6 max-h-[70vh] overflow-y-auto">
|
||||||
{/* 基本信息 */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="edit-name">项目名称 *</Label>
|
<Label htmlFor="edit-name" className="font-mono font-bold uppercase text-xs">项目名称 *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="edit-name"
|
id="edit-name"
|
||||||
value={editForm.name}
|
value={editForm.name}
|
||||||
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||||
placeholder="输入项目名称"
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="edit-description">项目描述</Label>
|
<Label htmlFor="edit-description" className="font-mono font-bold uppercase text-xs">描述</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="edit-description"
|
id="edit-description"
|
||||||
value={editForm.description}
|
value={editForm.description}
|
||||||
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
||||||
placeholder="输入项目描述"
|
|
||||||
rows={3}
|
rows={3}
|
||||||
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 仓库信息 */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-sm font-semibold text-gray-900">仓库信息</h3>
|
<h3 className="font-mono font-bold uppercase text-sm border-b-2 border-black pb-1">仓库信息</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="edit-repo-url">仓库地址</Label>
|
<Label htmlFor="edit-repo-url" className="font-mono font-bold uppercase text-xs">仓库地址</Label>
|
||||||
<Input
|
<Input
|
||||||
id="edit-repo-url"
|
id="edit-repo-url"
|
||||||
value={editForm.repository_url}
|
value={editForm.repository_url}
|
||||||
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
|
||||||
placeholder="https://github.com/username/repo"
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="edit-repo-type">仓库类型</Label>
|
<Label htmlFor="edit-repo-type" className="font-mono font-bold uppercase text-xs">仓库类型</Label>
|
||||||
<Select
|
<Select
|
||||||
value={editForm.repository_type}
|
value={editForm.repository_type}
|
||||||
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
|
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="edit-repo-type">
|
<SelectTrigger id="edit-repo-type" className="retro-input">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent className="retro-card border-2 border-black">
|
||||||
<SelectItem value="github">GitHub</SelectItem>
|
<SelectItem value="github">GITHUB</SelectItem>
|
||||||
<SelectItem value="gitlab">GitLab</SelectItem>
|
<SelectItem value="gitlab">GITLAB</SelectItem>
|
||||||
<SelectItem value="other">其他</SelectItem>
|
<SelectItem value="other">OTHER</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="edit-branch">默认分支</Label>
|
<Label htmlFor="edit-default-branch" className="font-mono font-bold uppercase text-xs">默认分支</Label>
|
||||||
<Input
|
<Input
|
||||||
id="edit-branch"
|
id="edit-default-branch"
|
||||||
value={editForm.default_branch}
|
value={editForm.default_branch}
|
||||||
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
|
||||||
placeholder="main"
|
className="retro-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编程语言 */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-sm font-semibold text-gray-900">编程语言</h3>
|
<h3 className="font-mono font-bold uppercase text-sm border-b-2 border-black pb-1">技术栈</h3>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
{supportedLanguages.map((lang) => (
|
{supportedLanguages.map((lang) => (
|
||||||
<div
|
<div
|
||||||
key={lang}
|
key={lang}
|
||||||
className={`flex items-center space-x-2 p-3 rounded-lg border cursor-pointer transition-all ${
|
className={`flex items-center space-x-2 p-2 border-2 cursor-pointer transition-all ${editForm.programming_languages?.includes(lang)
|
||||||
editForm.programming_languages?.includes(lang)
|
? 'border-black bg-primary/10 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]'
|
||||||
? 'border-primary bg-red-50'
|
: 'border-gray-200 hover:border-black'
|
||||||
: 'border-gray-200 hover:border-gray-300'
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleToggleLanguage(lang)}
|
onClick={() => handleToggleLanguage(lang)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-4 h-4 rounded border flex items-center justify-center ${
|
className={`w-4 h-4 border-2 flex items-center justify-center ${editForm.programming_languages?.includes(lang)
|
||||||
editForm.programming_languages?.includes(lang)
|
? 'bg-primary border-black'
|
||||||
? 'bg-primary border-primary'
|
|
||||||
: 'border-gray-300'
|
: 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -931,51 +930,56 @@ export default function Projects() {
|
||||||
<CheckCircle className="w-3 h-3 text-white" />
|
<CheckCircle className="w-3 h-3 text-white" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium">{lang}</span>
|
<span className="text-sm font-mono font-bold uppercase">{lang}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 pt-4 border-t">
|
<div className="flex justify-end space-x-3 p-4 border-t-2 border-black bg-gray-50">
|
||||||
<Button variant="outline" onClick={() => setShowEditDialog(false)}>
|
<Button variant="outline" onClick={() => setShowEditDialog(false)} className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSaveEdit}>
|
<Button onClick={handleSaveEdit} className="retro-btn">
|
||||||
保存修改
|
保存更改
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* 删除确认对话框 */}
|
{/* Delete Dialog */}
|
||||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent className="retro-card border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] bg-white p-0">
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader className="bg-red-600 text-white p-4 border-b-4 border-black">
|
||||||
<AlertDialogTitle>移到回收站</AlertDialogTitle>
|
<AlertDialogTitle className="font-mono text-xl uppercase tracking-widest flex items-center gap-2">
|
||||||
<AlertDialogDescription>
|
<Trash2 className="w-5 h-5" />
|
||||||
您确定要删除项目 <span className="font-semibold text-gray-900">"{projectToDelete?.name}"</span> 吗?
|
确认删除
|
||||||
<br />
|
</AlertDialogTitle>
|
||||||
<br />
|
<AlertDialogDescription className="text-white/90 font-mono">
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 my-3">
|
您确定要移动 <span className="font-bold underline">"{projectToDelete?.name}"</span> 到回收站吗?
|
||||||
<p className="text-blue-800 font-semibold mb-2">💡 温馨提示</p>
|
|
||||||
<ul className="list-disc list-inside text-blue-700 space-y-1 text-sm">
|
|
||||||
<li>项目将被移到<span className="font-semibold">回收站</span>,不会立即删除</li>
|
|
||||||
<li>您可以在回收站中随时恢复此项目</li>
|
|
||||||
<li>相关的审计任务和报告将会保留</li>
|
|
||||||
<li>如需永久删除,请在回收站中操作</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<div className="p-6">
|
||||||
|
<div className="bg-blue-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<p className="text-blue-900 font-bold mb-2 font-mono uppercase">系统通知:</p>
|
||||||
|
<ul className="list-disc list-inside text-blue-800 space-y-1 text-xs font-mono">
|
||||||
|
<li>> 项目移至回收站</li>
|
||||||
|
<li>> 可恢复</li>
|
||||||
|
<li>> 审计数据保留</li>
|
||||||
|
<li>> 在回收站中永久删除</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AlertDialogFooter className="p-4 border-t-2 border-black bg-gray-50">
|
||||||
|
<AlertDialogCancel className="retro-btn bg-white text-black hover:bg-gray-100 border-2 border-black">取消</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={handleConfirmDelete}
|
onClick={handleConfirmDelete}
|
||||||
className="bg-orange-600 hover:bg-orange-700 focus:ring-orange-600"
|
className="retro-btn bg-red-600 text-white hover:bg-red-700 border-2 border-black"
|
||||||
>
|
>
|
||||||
移到回收站
|
确认删除
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -113,10 +113,10 @@ export default function RecycleBin() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
|
||||||
<p className="text-gray-500">加载中...</p>
|
<p className="text-black font-mono font-bold uppercase">加载中...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -125,72 +125,68 @@ export default function RecycleBin() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title flex items-center gap-2">
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-2">
|
||||||
<Trash2 className="w-8 h-8 text-gray-400" />
|
<Trash2 className="w-8 h-8 text-black" />
|
||||||
回收站
|
回收站
|
||||||
</h1>
|
</h1>
|
||||||
<p className="page-subtitle">管理已删除的项目,可以恢复或永久删除</p>
|
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">管理已删除的项目,可以恢复或永久删除</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 搜索 */}
|
{/* 搜索 */}
|
||||||
<Card>
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索已删除的项目..."
|
placeholder="搜索已删除的项目..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-10"
|
className="pl-10 retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 项目列表 */}
|
{/* 项目列表 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{filteredProjects.length > 0 ? (
|
{filteredProjects.length > 0 ? (
|
||||||
filteredProjects.map((project) => (
|
filteredProjects.map((project) => (
|
||||||
<Card key={project.id} className="card-modern group opacity-75 hover:opacity-100 transition-opacity">
|
<div key={project.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
|
||||||
<CardHeader className="pb-4">
|
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-start justify-between">
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-gray-400 to-gray-500 flex items-center justify-center text-white text-lg">
|
<div className="w-10 h-10 border-2 border-black bg-white flex items-center justify-center text-black text-lg shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
{getRepositoryIcon(project.repository_type)}
|
{getRepositoryIcon(project.repository_type)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-lg">
|
<h3 className="text-lg font-bold font-display uppercase truncate max-w-[150px]">
|
||||||
{project.name}
|
{project.name}
|
||||||
</CardTitle>
|
</h3>
|
||||||
{project.description && (
|
{project.description && (
|
||||||
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
|
<p className="text-xs text-gray-500 mt-1 line-clamp-1 font-mono">
|
||||||
{project.description}
|
{project.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700">
|
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700 border-2 border-black rounded-none font-bold uppercase text-xs">
|
||||||
已删除
|
已删除
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="space-y-4">
|
<div className="p-4 space-y-4 font-mono">
|
||||||
{/* 项目信息 */}
|
{/* 项目信息 */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{project.repository_url && (
|
{project.repository_url && (
|
||||||
<div className="flex items-center text-sm text-gray-500">
|
<div className="flex items-center text-xs text-gray-600 font-bold">
|
||||||
<GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
|
<GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
|
||||||
<a
|
<a
|
||||||
href={project.repository_url}
|
href={project.repository_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="hover:text-primary transition-colors flex items-center truncate"
|
className="hover:text-primary transition-colors flex items-center truncate hover:underline"
|
||||||
>
|
>
|
||||||
<span className="truncate">{project.repository_url.replace('https://', '')}</span>
|
<span className="truncate">{project.repository_url.replace('https://', '')}</span>
|
||||||
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
|
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
|
||||||
|
|
@ -198,7 +194,7 @@ export default function RecycleBin() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex items-center justify-between text-xs text-gray-500 font-medium">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
删除于 {formatDate(project.updated_at)}
|
删除于 {formatDate(project.updated_at)}
|
||||||
|
|
@ -214,12 +210,12 @@ export default function RecycleBin() {
|
||||||
{project.programming_languages && (
|
{project.programming_languages && (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
|
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
|
||||||
<Badge key={lang} variant="outline" className="text-xs">
|
<Badge key={lang} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||||
{lang}
|
{lang}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
{JSON.parse(project.programming_languages).length > 4 && (
|
{JSON.parse(project.programming_languages).length > 4 && (
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||||
+{JSON.parse(project.programming_languages).length - 4}
|
+{JSON.parse(project.programming_languages).length - 4}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
@ -227,11 +223,11 @@ export default function RecycleBin() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2 border-t-2 border-black mt-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1 text-green-600 hover:text-green-700 hover:bg-green-50"
|
className="flex-1 text-green-700 hover:text-white hover:bg-green-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
||||||
onClick={() => handleRestoreClick(project)}
|
onClick={() => handleRestoreClick(project)}
|
||||||
>
|
>
|
||||||
<RotateCcw className="w-4 h-4 mr-2" />
|
<RotateCcw className="w-4 h-4 mr-2" />
|
||||||
|
|
@ -240,52 +236,55 @@ export default function RecycleBin() {
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50"
|
className="flex-1 text-red-700 hover:text-white hover:bg-red-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
||||||
onClick={() => handlePermanentDeleteClick(project)}
|
onClick={() => handlePermanentDeleteClick(project)}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
永久删除
|
永久删除
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardContent className="empty-state py-16">
|
<div className="py-16 flex flex-col items-center justify-center text-center">
|
||||||
<div className="empty-icon">
|
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<Inbox className="w-8 h-8 text-primary" />
|
<Inbox className="w-10 h-10 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
|
||||||
{searchTerm ? '未找到匹配的项目' : '回收站为空'}
|
{searchTerm ? '未找到匹配的项目' : '回收站为空'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6 max-w-md">
|
<p className="text-gray-500 font-mono max-w-md">
|
||||||
{searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'}
|
{searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 恢复项目确认对话框 */}
|
{/* 恢复项目确认对话框 */}
|
||||||
<AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}>
|
<AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||||
<AlertDialogTitle>确认恢复项目</AlertDialogTitle>
|
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2">
|
||||||
<AlertDialogDescription>
|
<RotateCcw className="w-6 h-6 text-green-600" />
|
||||||
您确定要恢复项目 <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span> 吗?
|
确认恢复项目
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
|
||||||
|
您确定要恢复项目 <span className="font-bold text-black">"{selectedProject?.name}"</span> 吗?
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
恢复后,该项目将重新出现在项目列表中,您可以继续使用该项目的所有功能。
|
恢复后,该项目将重新出现在项目列表中,您可以继续使用该项目的所有功能。
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter className="p-6 bg-white flex gap-3">
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase">取消</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={handleConfirmRestore}
|
onClick={handleConfirmRestore}
|
||||||
className="bg-green-600 hover:bg-green-700 focus:ring-green-600"
|
className="retro-btn bg-green-600 text-white border-2 border-black hover:bg-green-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
|
||||||
>
|
>
|
||||||
确认恢复
|
确认恢复
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
|
|
@ -295,19 +294,22 @@ export default function RecycleBin() {
|
||||||
|
|
||||||
{/* 永久删除确认对话框 */}
|
{/* 永久删除确认对话框 */}
|
||||||
<AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}>
|
<AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader className="p-6 border-b-2 border-black bg-red-50">
|
||||||
<AlertDialogTitle className="flex items-center gap-2 text-red-600">
|
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2 text-red-600">
|
||||||
<AlertTriangle className="w-5 h-5" />
|
<AlertTriangle className="w-6 h-6" />
|
||||||
警告:永久删除项目
|
警告:永久删除项目
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
|
||||||
您确定要<span className="font-semibold text-red-600">永久删除</span>项目 <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span> 吗?
|
您确定要<span className="font-bold text-red-600 uppercase">永久删除</span>项目 <span className="font-bold text-black">"{selectedProject?.name}"</span> 吗?
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 my-3">
|
<div className="bg-red-100 border-2 border-red-500 p-4 my-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
|
||||||
<p className="text-red-800 font-semibold mb-2">⚠️ 此操作不可撤销!</p>
|
<p className="text-red-800 font-bold mb-2 uppercase flex items-center">
|
||||||
<ul className="list-disc list-inside text-red-700 space-y-1 text-sm">
|
<AlertTriangle className="w-4 h-4 mr-2" />
|
||||||
|
此操作不可撤销!
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside text-red-700 space-y-1 text-xs font-bold">
|
||||||
<li>项目数据将被永久删除</li>
|
<li>项目数据将被永久删除</li>
|
||||||
<li>相关的审计任务可能会受影响</li>
|
<li>相关的审计任务可能会受影响</li>
|
||||||
<li>无法通过任何方式恢复</li>
|
<li>无法通过任何方式恢复</li>
|
||||||
|
|
@ -315,11 +317,11 @@ export default function RecycleBin() {
|
||||||
</div>
|
</div>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter className="p-6 bg-white flex gap-3">
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase">取消</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={handleConfirmPermanentDelete}
|
onClick={handleConfirmPermanentDelete}
|
||||||
className="bg-red-600 hover:bg-red-700 focus:ring-red-600"
|
className="retro-btn bg-red-600 text-white border-2 border-black hover:bg-red-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
|
||||||
>
|
>
|
||||||
确认永久删除
|
确认永久删除
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
|
@ -21,8 +20,7 @@ import {
|
||||||
Code,
|
Code,
|
||||||
Lightbulb,
|
Lightbulb,
|
||||||
Info,
|
Info,
|
||||||
Zap,
|
Zap
|
||||||
X
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { api } from "@/shared/config/database";
|
import { api } from "@/shared/config/database";
|
||||||
import type { AuditTask, AuditIssue } from "@/shared/types";
|
import type { AuditTask, AuditIssue } from "@/shared/types";
|
||||||
|
|
@ -79,10 +77,10 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
const lowIssues = issues.filter(issue => issue.severity === 'low');
|
const lowIssues = issues.filter(issue => issue.severity === 'low');
|
||||||
|
|
||||||
const renderIssue = (issue: AuditIssue, index: number) => (
|
const renderIssue = (issue: AuditIssue, index: number) => (
|
||||||
<div key={issue.id || index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
|
<div key={issue.id || index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
<div className={`w - 10 h - 10 border - 2 border - black flex items - center justify - center shadow - [2px_2px_0px_0px_rgba(0, 0, 0, 1)] ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
||||||
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
||||||
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
||||||
'bg-blue-100 text-blue-600'
|
'bg-blue-100 text-blue-600'
|
||||||
|
|
@ -90,13 +88,13 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
{getTypeIcon(issue.issue_type)}
|
{getTypeIcon(issue.issue_type)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
|
<h4 className="font-bold text-lg text-black mb-1 group-hover:text-primary transition-colors font-display uppercase">{issue.title}</h4>
|
||||||
<div className="flex items-center space-x-1 text-xs text-gray-600">
|
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
|
||||||
<FileText className="w-3 h-3" />
|
<FileText className="w-3 h-3" />
|
||||||
<span className="font-medium">{issue.file_path}</span>
|
<span className="font-bold">{issue.file_path}</span>
|
||||||
</div>
|
</div>
|
||||||
{issue.line_number && (
|
{issue.line_number && (
|
||||||
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1">
|
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1 font-mono">
|
||||||
<span>📍</span>
|
<span>📍</span>
|
||||||
<span>第 {issue.line_number} 行</span>
|
<span>第 {issue.line_number} 行</span>
|
||||||
{issue.column_number && <span>,第 {issue.column_number} 列</span>}
|
{issue.column_number && <span>,第 {issue.column_number} 列</span>}
|
||||||
|
|
@ -104,7 +102,7 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
|
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 2 py - 1 text - xs ${getSeverityColor(issue.severity)} `}>
|
||||||
{issue.severity === 'critical' ? '严重' :
|
{issue.severity === 'critical' ? '严重' :
|
||||||
issue.severity === 'high' ? '高' :
|
issue.severity === 'high' ? '高' :
|
||||||
issue.severity === 'medium' ? '中等' : '低'}
|
issue.severity === 'medium' ? '中等' : '低'}
|
||||||
|
|
@ -112,48 +110,48 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{issue.description && (
|
{issue.description && (
|
||||||
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
|
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono">
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1 border-b-2 border-gray-200 pb-1">
|
||||||
<Info className="w-3 h-3 text-gray-600 mr-1" />
|
<Info className="w-3 h-3 text-black mr-1" />
|
||||||
<span className="font-medium text-gray-800 text-xs">问题详情</span>
|
<span className="font-bold text-black text-xs uppercase">问题详情</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-700 text-xs leading-relaxed">
|
<p className="text-gray-700 text-xs leading-relaxed mt-1">
|
||||||
{issue.description}
|
{issue.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{issue.code_snippet && (
|
{issue.code_snippet && (
|
||||||
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
|
<div className="bg-gray-900 p-3 mb-3 border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2 border-b border-gray-700 pb-1">
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
|
<div className="w-4 h-4 bg-red-600 flex items-center justify-center">
|
||||||
<Code className="w-2 h-2 text-white" />
|
<Code className="w-2 h-2 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-300 text-xs font-medium">问题代码</span>
|
<span className="text-green-400 text-xs font-bold font-mono uppercase">CODE_SNIPPET</span>
|
||||||
</div>
|
</div>
|
||||||
{issue.line_number && (
|
{issue.line_number && (
|
||||||
<span className="text-gray-400 text-xs">第 {issue.line_number} 行</span>
|
<span className="text-gray-400 text-xs font-mono">LINE: {issue.line_number}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-black/40 rounded p-2">
|
<div className="bg-black/40 p-2 border border-gray-700">
|
||||||
<pre className="text-xs text-gray-100 overflow-x-auto">
|
<pre className="text-xs text-green-400 font-mono overflow-x-auto">
|
||||||
<code>{issue.code_snippet}</code>
|
<code>{issue.code_snippet}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
{issue.suggestion && (
|
{issue.suggestion && (
|
||||||
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
|
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2 border-b-2 border-blue-200 pb-1">
|
||||||
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
|
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||||
<Lightbulb className="w-3 h-3 text-white" />
|
<Lightbulb className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-blue-800 text-sm">修复建议</span>
|
<span className="font-bold text-blue-800 text-sm uppercase font-display">修复建议</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
|
<p className="text-blue-900 text-xs leading-relaxed font-mono font-medium">{issue.suggestion}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -162,44 +160,44 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
|
|
||||||
if (parsedExplanation) {
|
if (parsedExplanation) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
|
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
|
||||||
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
|
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||||
<Zap className="w-3 h-3 text-white" />
|
<Zap className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
<span className="font-bold text-red-800 text-sm uppercase font-display">AI 解释</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 text-xs">
|
<div className="space-y-2 text-xs font-mono">
|
||||||
{parsedExplanation.what && (
|
{parsedExplanation.what && (
|
||||||
<div className="border-l-2 border-red-600 pl-2">
|
<div className="border-l-4 border-red-600 pl-2">
|
||||||
<span className="font-medium text-red-700">问题:</span>
|
<span className="font-bold text-red-700 uppercase">问题:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
|
<span className="text-gray-800 ml-1">{parsedExplanation.what}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.why && (
|
{parsedExplanation.why && (
|
||||||
<div className="border-l-2 border-gray-600 pl-2">
|
<div className="border-l-4 border-gray-600 pl-2">
|
||||||
<span className="font-medium text-gray-700">原因:</span>
|
<span className="font-bold text-gray-700 uppercase">原因:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
|
<span className="text-gray-800 ml-1">{parsedExplanation.why}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.how && (
|
{parsedExplanation.how && (
|
||||||
<div className="border-l-2 border-black pl-2">
|
<div className="border-l-4 border-black pl-2">
|
||||||
<span className="font-medium text-black">方案:</span>
|
<span className="font-bold text-black uppercase">方案:</span>
|
||||||
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
|
<span className="text-gray-800 ml-1">{parsedExplanation.how}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsedExplanation.learn_more && (
|
{parsedExplanation.learn_more && (
|
||||||
<div className="border-l-2 border-red-400 pl-2">
|
<div className="border-l-4 border-blue-400 pl-2">
|
||||||
<span className="font-medium text-red-600">链接:</span>
|
<span className="font-bold text-blue-600 uppercase">链接:</span>
|
||||||
<a
|
<a
|
||||||
href={parsedExplanation.learn_more}
|
href={parsedExplanation.learn_more}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-red-600 hover:text-red-800 hover:underline ml-1"
|
className="text-blue-600 hover:text-blue-800 hover:underline ml-1 font-bold"
|
||||||
>
|
>
|
||||||
{parsedExplanation.learn_more}
|
{parsedExplanation.learn_more}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -211,12 +209,12 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
} else {
|
} else {
|
||||||
// 如果无法解析JSON,回退到原始显示方式
|
// 如果无法解析JSON,回退到原始显示方式
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border border-red-200 rounded-lg p-3">
|
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
|
||||||
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
||||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
<span className="font-bold text-red-800 text-sm uppercase font-display">AI 解释</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
|
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -227,14 +225,14 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
|
|
||||||
if (issues.length === 0) {
|
if (issues.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
|
||||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<CheckCircle className="w-12 h-12 text-green-600" />
|
<CheckCircle className="w-12 h-12 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-green-800 mb-3">代码质量优秀!</h3>
|
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase">代码质量优秀!</h3>
|
||||||
<p className="text-green-600 text-lg mb-6">恭喜!没有发现任何问题</p>
|
<p className="text-green-700 text-lg mb-6 font-mono font-bold">恭喜!没有发现任何问题</p>
|
||||||
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
|
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<p className="text-green-700 text-sm">
|
<p className="text-black text-sm font-mono">
|
||||||
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -244,72 +242,72 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="all" className="w-full">
|
<Tabs defaultValue="all" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-5 mb-6">
|
<TabsList className="grid w-full grid-cols-5 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
|
||||||
<TabsTrigger value="all" className="text-sm">
|
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
全部 ({issues.length})
|
全部 ({issues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="critical" className="text-sm">
|
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
严重 ({criticalIssues.length})
|
严重 ({criticalIssues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="high" className="text-sm">
|
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||||
高 ({highIssues.length})
|
高 ({highIssues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="medium" className="text-sm">
|
<TabsTrigger value="medium" className="rounded-none border-r-2 border-black data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||||
中等 ({mediumIssues.length})
|
中等 ({mediumIssues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="low" className="text-sm">
|
<TabsTrigger value="low" className="rounded-none data-[state=active]:bg-blue-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||||
低 ({lowIssues.length})
|
低 ({lowIssues.length})
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="all" className="space-y-4 mt-6">
|
<TabsContent value="all" className="space-y-4 mt-0">
|
||||||
{issues.map((issue, index) => renderIssue(issue, index))}
|
{issues.map((issue, index) => renderIssue(issue, index))}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="critical" className="space-y-4 mt-6">
|
<TabsContent value="critical" className="space-y-4 mt-0">
|
||||||
{criticalIssues.length > 0 ? (
|
{criticalIssues.length > 0 ? (
|
||||||
criticalIssues.map((issue, index) => renderIssue(issue, index))
|
criticalIssues.map((issue, index) => renderIssue(issue, index))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现严重问题</h3>
|
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现严重问题</h3>
|
||||||
<p className="text-gray-500">代码在严重级别的检查中表现良好</p>
|
<p className="text-gray-500 font-mono">代码在严重级别的检查中表现良好</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="high" className="space-y-4 mt-6">
|
<TabsContent value="high" className="space-y-4 mt-0">
|
||||||
{highIssues.length > 0 ? (
|
{highIssues.length > 0 ? (
|
||||||
highIssues.map((issue, index) => renderIssue(issue, index))
|
highIssues.map((issue, index) => renderIssue(issue, index))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现高优先级问题</h3>
|
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现高优先级问题</h3>
|
||||||
<p className="text-gray-500">代码在高优先级检查中表现良好</p>
|
<p className="text-gray-500 font-mono">代码在高优先级检查中表现良好</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="medium" className="space-y-4 mt-6">
|
<TabsContent value="medium" className="space-y-4 mt-0">
|
||||||
{mediumIssues.length > 0 ? (
|
{mediumIssues.length > 0 ? (
|
||||||
mediumIssues.map((issue, index) => renderIssue(issue, index))
|
mediumIssues.map((issue, index) => renderIssue(issue, index))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现中等优先级问题</h3>
|
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现中等优先级问题</h3>
|
||||||
<p className="text-gray-500">代码在中等优先级检查中表现良好</p>
|
<p className="text-gray-500 font-mono">代码在中等优先级检查中表现良好</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="low" className="space-y-4 mt-6">
|
<TabsContent value="low" className="space-y-4 mt-0">
|
||||||
{lowIssues.length > 0 ? (
|
{lowIssues.length > 0 ? (
|
||||||
lowIssues.map((issue, index) => renderIssue(issue, index))
|
lowIssues.map((issue, index) => renderIssue(issue, index))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现低优先级问题</h3>
|
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现低优先级问题</h3>
|
||||||
<p className="text-gray-500">代码在低优先级检查中表现良好</p>
|
<p className="text-gray-500 font-mono">代码在低优先级检查中表现良好</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
@ -416,32 +414,32 @@ export default function TaskDetail() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
|
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in font-mono">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Link to="/audit-tasks">
|
<Link to="/audit-tasks">
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
返回任务列表
|
返回任务列表
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardContent className="empty-state py-16">
|
<div className="py-16 flex flex-col items-center justify-center text-center">
|
||||||
<div className="empty-icon">
|
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<AlertTriangle className="w-8 h-8 text-red-500" />
|
<AlertTriangle className="w-10 h-10 text-red-500" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">任务不存在</h3>
|
||||||
|
<p className="text-gray-500 font-mono">请检查任务ID是否正确</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">任务不存在</h3>
|
|
||||||
<p className="text-gray-500">请检查任务ID是否正确</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -450,24 +448,24 @@ export default function TaskDetail() {
|
||||||
const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files);
|
const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in font-mono">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Link to="/audit-tasks">
|
<Link to="/audit-tasks">
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
返回任务列表
|
返回任务列表
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">任务详情</h1>
|
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">任务详情</h1>
|
||||||
<p className="page-subtitle">{task.project?.name || '未知项目'} - 审计任务</p>
|
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">{task.project?.name || '未知项目'} - 审计任务</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Badge className={getStatusColor(task.status)}>
|
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 3 py - 1 text - sm ${getStatusColor(task.status)} `}>
|
||||||
{getStatusIcon(task.status)}
|
{getStatusIcon(task.status)}
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
{task.status === 'completed' ? '已完成' :
|
{task.status === 'completed' ? '已完成' :
|
||||||
|
|
@ -481,7 +479,7 @@ export default function TaskDetail() {
|
||||||
{task.status === 'completed' && (
|
{task.status === 'completed' && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="btn-primary"
|
className="retro-btn bg-primary text-white hover:bg-primary/90 h-10"
|
||||||
onClick={() => setExportDialogOpen(true)}
|
onClick={() => setExportDialogOpen(true)}
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
|
|
@ -492,101 +490,93 @@ export default function TaskDetail() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 任务概览 */}
|
{/* 任务概览 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div className="w-full">
|
||||||
<p className="stat-label">扫描进度</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">扫描进度</p>
|
||||||
<p className="stat-value text-xl">{progressPercentage}%</p>
|
<p className="text-3xl font-bold text-black mb-2">{progressPercentage}%</p>
|
||||||
<Progress value={progressPercentage} className="mt-2" />
|
<Progress value={progressPercentage} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ml-4">
|
||||||
|
<Activity className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-primary to-accent">
|
|
||||||
<Activity className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">发现问题</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">发现问题</p>
|
||||||
<p className="stat-value text-xl text-orange-600">{task.issues_count}</p>
|
<p className="text-3xl font-bold text-orange-600">{task.issues_count}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<Bug className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-orange-500 to-orange-600">
|
|
||||||
<Bug className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">质量评分</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">质量评分</p>
|
||||||
<p className="stat-value text-xl text-primary">{task.quality_score.toFixed(1)}</p>
|
<p className="text-3xl font-bold text-green-600">{task.quality_score.toFixed(1)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<TrendingUp className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-emerald-500 to-emerald-600">
|
|
||||||
<TrendingUp className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="stat-card">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-label">代码行数</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">代码行数</p>
|
||||||
<p className="stat-value text-xl">{task.total_lines.toLocaleString()}</p>
|
<p className="text-3xl font-bold text-purple-600">{task.total_lines.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-purple-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
|
<FileText className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-icon from-purple-500 to-purple-600">
|
|
||||||
<FileText className="w-5 h-5 text-white" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 任务信息 */}
|
{/* 任务信息 */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center space-x-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<Shield className="w-5 h-5 text-primary" />
|
<Shield className="w-5 h-5 mr-2 text-primary" />
|
||||||
<span>任务信息</span>
|
任务信息
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">任务类型</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">任务类型</p>
|
||||||
<p className="text-base">
|
<p className="text-base font-bold">
|
||||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">目标分支</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">目标分支</p>
|
||||||
<p className="text-base flex items-center">
|
<p className="text-base font-bold flex items-center">
|
||||||
<GitBranch className="w-4 h-4 mr-1" />
|
<GitBranch className="w-4 h-4 mr-1" />
|
||||||
{task.branch_name || '默认分支'}
|
{task.branch_name || '默认分支'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">创建时间</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">创建时间</p>
|
||||||
<p className="text-base flex items-center">
|
<p className="text-base font-bold flex items-center">
|
||||||
<Calendar className="w-4 h-4 mr-1" />
|
<Calendar className="w-4 h-4 mr-1" />
|
||||||
{formatDate(task.created_at)}
|
{formatDate(task.created_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{task.completed_at && (
|
{task.completed_at && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">完成时间</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">完成时间</p>
|
||||||
<p className="text-base flex items-center">
|
<p className="text-base font-bold flex items-center">
|
||||||
<CheckCircle className="w-4 h-4 mr-1" />
|
<CheckCircle className="w-4 h-4 mr-1" />
|
||||||
{formatDate(task.completed_at)}
|
{formatDate(task.completed_at)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -597,10 +587,10 @@ export default function TaskDetail() {
|
||||||
{/* 排除模式 */}
|
{/* 排除模式 */}
|
||||||
{task.exclude_patterns && (
|
{task.exclude_patterns && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500 mb-2">排除模式</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-2">排除模式</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{JSON.parse(task.exclude_patterns).map((pattern: string) => (
|
{JSON.parse(task.exclude_patterns).map((pattern: string) => (
|
||||||
<Badge key={pattern} variant="outline" className="text-xs">
|
<Badge key={pattern} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||||
{pattern}
|
{pattern}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
|
|
@ -611,51 +601,51 @@ export default function TaskDetail() {
|
||||||
{/* 扫描配置 */}
|
{/* 扫描配置 */}
|
||||||
{task.scan_config && (
|
{task.scan_config && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500 mb-2">扫描配置</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-2">扫描配置</p>
|
||||||
<div className="bg-gray-50 rounded-lg p-3">
|
<div className="bg-gray-900 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||||
<pre className="text-xs text-gray-600">
|
<pre className="text-xs text-green-400 font-mono">
|
||||||
{JSON.stringify(JSON.parse(task.scan_config), null, 2)}
|
{JSON.stringify(JSON.parse(task.scan_config), null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center space-x-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<FileText className="w-5 h-5 text-primary" />
|
<FileText className="w-5 h-5 mr-2 text-primary" />
|
||||||
<span>项目信息</span>
|
项目信息
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent className="space-y-4">
|
<div className="p-6 space-y-4 font-mono">
|
||||||
{task.project ? (
|
{task.project ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">项目名称</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">项目名称</p>
|
||||||
<Link to={`/projects/${task.project.id}`} className="text-base text-primary hover:underline">
|
<Link to={`/ projects / ${task.project.id} `} className="text-base font-bold text-primary hover:underline hover:text-primary/80">
|
||||||
{task.project.name}
|
{task.project.name}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{task.project.description && (
|
{task.project.description && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">项目描述</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">项目描述</p>
|
||||||
<p className="text-sm text-gray-600">{task.project.description}</p>
|
<p className="text-sm text-gray-800 font-medium">{task.project.description}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500">仓库类型</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-1">仓库类型</p>
|
||||||
<p className="text-base">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
|
<p className="text-base font-bold">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
|
||||||
</div>
|
</div>
|
||||||
{task.project.programming_languages && (
|
{task.project.programming_languages && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-500 mb-2">编程语言</p>
|
<p className="text-xs font-bold text-gray-600 uppercase mb-2">编程语言</p>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{JSON.parse(task.project.programming_languages).map((lang: string) => (
|
{JSON.parse(task.project.programming_languages).map((lang: string) => (
|
||||||
<Badge key={lang} variant="secondary" className="text-xs">
|
<Badge key={lang} variant="secondary" className="text-xs rounded-none border-2 border-black bg-white text-black font-bold uppercase">
|
||||||
{lang}
|
{lang}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
|
|
@ -664,26 +654,26 @@ export default function TaskDetail() {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-gray-500">项目信息不可用</p>
|
<p className="text-gray-500 font-bold">项目信息不可用</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 问题列表 */}
|
{/* 问题列表 */}
|
||||||
{issues.length > 0 && (
|
{issues.length > 0 && (
|
||||||
<Card className="card-modern">
|
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||||
<CardHeader>
|
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||||
<CardTitle className="flex items-center space-x-2">
|
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||||
<Bug className="w-6 h-6 text-orange-600" />
|
<Bug className="w-6 h-6 mr-2 text-orange-600" />
|
||||||
<span>发现的问题 ({issues.length})</span>
|
发现的问题 ({issues.length})
|
||||||
</CardTitle>
|
</h3>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="p-6">
|
||||||
<IssuesList issues={issues} />
|
<IssuesList issues={issues} />
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 导出报告对话框 */}
|
{/* 导出报告对话框 */}
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@ export const api = {
|
||||||
completed_tasks: number;
|
completed_tasks: number;
|
||||||
total_issues: number;
|
total_issues: number;
|
||||||
resolved_issues: number;
|
resolved_issues: number;
|
||||||
|
avg_quality_score: number;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.get('/projects/stats');
|
const res = await apiClient.get('/projects/stats');
|
||||||
|
|
@ -216,7 +217,8 @@ export const api = {
|
||||||
total_tasks: 0,
|
total_tasks: 0,
|
||||||
completed_tasks: 0,
|
completed_tasks: 0,
|
||||||
total_issues: 0,
|
total_issues: 0,
|
||||||
resolved_issues: 0
|
resolved_issues: 0,
|
||||||
|
avg_quality_score: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
mono: ['"Space Mono"', '"Courier New"', 'monospace'],
|
||||||
|
sans: ['"Inter"', 'system-ui', 'sans-serif'],
|
||||||
|
display: ['"Orbitron"', 'sans-serif'],
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
borderColor: {
|
|
||||||
border: 'hsl(var(--border))',
|
|
||||||
},
|
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
|
|
@ -55,127 +57,62 @@ export default {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))',
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
},
|
},
|
||||||
// 专业深红色调色板
|
// Cassette Futurism Palette
|
||||||
crimson: {
|
retro: {
|
||||||
50: '#fef2f2',
|
orange: '#FF3D00',
|
||||||
100: '#fee2e2',
|
teal: '#00E5FF',
|
||||||
200: '#fecaca',
|
pink: '#FF00FF',
|
||||||
300: '#fca5a5',
|
yellow: '#FFEA00',
|
||||||
400: '#f87171',
|
dark: '#1A1A1A',
|
||||||
500: '#ef4444',
|
light: '#F5F7FA',
|
||||||
600: '#dc2626',
|
}
|
||||||
700: '#b91c1c',
|
|
||||||
800: '#991b1b',
|
|
||||||
900: '#7f1d1d',
|
|
||||||
950: '#450a0a',
|
|
||||||
},
|
|
||||||
burgundy: {
|
|
||||||
50: '#fdf2f8',
|
|
||||||
100: '#fce7f3',
|
|
||||||
200: '#fbcfe8',
|
|
||||||
300: '#f9a8d4',
|
|
||||||
400: '#f472b6',
|
|
||||||
500: '#ec4899',
|
|
||||||
600: '#db2777',
|
|
||||||
700: '#be185d',
|
|
||||||
800: '#9d174d',
|
|
||||||
900: '#831843',
|
|
||||||
950: '#500724',
|
|
||||||
},
|
|
||||||
success: 'hsl(var(--success))',
|
|
||||||
warning: 'hsl(var(--warning))',
|
|
||||||
info: 'hsl(var(--info))',
|
|
||||||
sidebar: {
|
|
||||||
DEFAULT: 'hsl(var(--sidebar-background))',
|
|
||||||
foreground: 'hsl(var(--sidebar-foreground))',
|
|
||||||
primary: 'hsl(var(--sidebar-primary))',
|
|
||||||
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
|
|
||||||
accent: 'hsl(var(--sidebar-accent))',
|
|
||||||
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
|
|
||||||
border: 'hsl(var(--sidebar-border))',
|
|
||||||
ring: 'hsl(var(--sidebar-ring))',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
},
|
none: '0',
|
||||||
backgroundImage: {
|
|
||||||
'gradient-primary': 'var(--gradient-primary)',
|
|
||||||
'gradient-card': 'var(--gradient-card)',
|
|
||||||
'gradient-background': 'var(--gradient-background)',
|
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
card: 'var(--shadow-card)',
|
'retro': '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||||
hover: 'var(--shadow-hover)',
|
'retro-hover': '2px 2px 0px 0px rgba(0,0,0,1)',
|
||||||
|
'neon': '0 0 5px theme("colors.primary.DEFAULT"), 0 0 20px theme("colors.primary.DEFAULT")',
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'accordion-down': {
|
'accordion-down': {
|
||||||
from: {
|
from: { height: '0' },
|
||||||
height: '0',
|
to: { height: 'var(--radix-accordion-content-height)' },
|
||||||
},
|
|
||||||
to: {
|
|
||||||
height: 'var(--radix-accordion-content-height)',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
'accordion-up': {
|
'accordion-up': {
|
||||||
from: {
|
from: { height: 'var(--radix-accordion-content-height)' },
|
||||||
height: 'var(--radix-accordion-content-height)',
|
to: { height: '0' },
|
||||||
},
|
},
|
||||||
to: {
|
'glitch': {
|
||||||
height: '0',
|
'0%, 100%': { transform: 'translate(0)' },
|
||||||
},
|
'20%': { transform: 'translate(-2px, 2px)' },
|
||||||
},
|
'40%': { transform: 'translate(-2px, -2px)' },
|
||||||
'fade-in': {
|
'60%': { transform: 'translate(2px, 2px)' },
|
||||||
from: {
|
'80%': { transform: 'translate(2px, -2px)' },
|
||||||
opacity: '0',
|
|
||||||
transform: 'translateY(10px)',
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
opacity: '1',
|
|
||||||
transform: 'translateY(0)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'slide-in': {
|
|
||||||
from: {
|
|
||||||
opacity: '0',
|
|
||||||
transform: 'translateX(-20px)',
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
opacity: '1',
|
|
||||||
transform: 'translateX(0)',
|
|
||||||
},
|
},
|
||||||
|
'scanline': {
|
||||||
|
'0%': { transform: 'translateY(-100%)' },
|
||||||
|
'100%': { transform: 'translateY(100%)' },
|
||||||
},
|
},
|
||||||
|
'pulse-slow': {
|
||||||
|
'0%, 100%': { opacity: '1' },
|
||||||
|
'50%': { opacity: '0.5' },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
'fade-in': 'fade-in 0.5s ease-out',
|
'glitch': 'glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite',
|
||||||
'slide-in': 'slide-in 0.5s ease-out',
|
'scanline': 'scanline 8s linear infinite',
|
||||||
|
'pulse-slow': 'pulse-slow 3s ease-in-out infinite',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require('tailwindcss-animate'),
|
require('tailwindcss-animate'),
|
||||||
function ({ addUtilities }) {
|
|
||||||
addUtilities(
|
|
||||||
{
|
|
||||||
'.border-t-solid': { 'border-top-style': 'solid' },
|
|
||||||
'.border-r-solid': { 'border-right-style': 'solid' },
|
|
||||||
'.border-b-solid': { 'border-bottom-style': 'solid' },
|
|
||||||
'.border-l-solid': { 'border-left-style': 'solid' },
|
|
||||||
'.border-t-dashed': { 'border-top-style': 'dashed' },
|
|
||||||
'.border-r-dashed': { 'border-right-style': 'dashed' },
|
|
||||||
'.border-b-dashed': { 'border-bottom-style': 'dashed' },
|
|
||||||
'.border-l-dashed': { 'border-left-style': 'dashed' },
|
|
||||||
'.border-t-dotted': { 'border-top-style': 'dotted' },
|
|
||||||
'.border-r-dotted': { 'border-right-style': 'dotted' },
|
|
||||||
'.border-b-dotted': { 'border-bottom-style': 'dotted' },
|
|
||||||
'.border-l-dotted': { 'border-left-style': 'dotted' },
|
|
||||||
},
|
|
||||||
['responsive']
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue