diff --git a/src/components/audit/CreateTaskDialog.tsx b/src/components/audit/CreateTaskDialog.tsx new file mode 100644 index 0000000..9199f16 --- /dev/null +++ b/src/components/audit/CreateTaskDialog.tsx @@ -0,0 +1,537 @@ +import { useState, useEffect } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + GitBranch, + Settings, + FileText, + AlertCircle, + Info, + Zap, + Shield, + Search +} from "lucide-react"; +import { api } from "@/shared/config/database"; +import type { Project, CreateAuditTaskForm } from "@/shared/types"; +import { toast } from "sonner"; + +interface CreateTaskDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onTaskCreated: () => void; +} + +export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated }: CreateTaskDialogProps) { + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(false); + const [creating, setCreating] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + + const [taskForm, setTaskForm] = useState({ + project_id: "", + task_type: "repository", + branch_name: "main", + exclude_patterns: ["node_modules/**", ".git/**", "dist/**", "build/**", "*.log"], + scan_config: { + include_tests: true, + include_docs: false, + max_file_size: 1024, // KB + analysis_depth: "standard" + } + }); + + const commonExcludePatterns = [ + { label: "node_modules", value: "node_modules/**", description: "Node.js 依赖包" }, + { label: ".git", value: ".git/**", description: "Git 版本控制文件" }, + { label: "dist/build", value: "dist/**", description: "构建输出目录" }, + { label: "logs", value: "*.log", description: "日志文件" }, + { label: "cache", value: ".cache/**", description: "缓存文件" }, + { label: "temp", value: "temp/**", description: "临时文件" }, + { label: "vendor", value: "vendor/**", description: "第三方库" }, + { label: "coverage", value: "coverage/**", description: "测试覆盖率报告" } + ]; + + useEffect(() => { + if (open) { + loadProjects(); + } + }, [open]); + + const loadProjects = async () => { + try { + setLoading(true); + const data = await api.getProjects(); + setProjects(data.filter(p => p.is_active)); + } catch (error) { + console.error('Failed to load projects:', error); + toast.error("加载项目失败"); + } finally { + setLoading(false); + } + }; + + const handleCreateTask = async () => { + if (!taskForm.project_id) { + toast.error("请选择项目"); + return; + } + + if (taskForm.task_type === "repository" && !taskForm.branch_name?.trim()) { + toast.error("请输入分支名称"); + return; + } + + try { + setCreating(true); + + await api.createAuditTask({ + ...taskForm, + created_by: "system" // 无登录场景下使用系统用户 + } as any); + + toast.success("审计任务创建成功"); + onOpenChange(false); + resetForm(); + onTaskCreated(); + } catch (error) { + console.error('Failed to create task:', error); + toast.error("创建任务失败"); + } finally { + setCreating(false); + } + }; + + const resetForm = () => { + setTaskForm({ + project_id: "", + task_type: "repository", + branch_name: "main", + exclude_patterns: ["node_modules/**", ".git/**", "dist/**", "build/**", "*.log"], + scan_config: { + include_tests: true, + include_docs: false, + max_file_size: 1024, + analysis_depth: "standard" + } + }); + setSearchTerm(""); + }; + + const toggleExcludePattern = (pattern: string) => { + const patterns = taskForm.exclude_patterns || []; + if (patterns.includes(pattern)) { + setTaskForm({ + ...taskForm, + exclude_patterns: patterns.filter(p => p !== pattern) + }); + } else { + setTaskForm({ + ...taskForm, + exclude_patterns: [...patterns, pattern] + }); + } + }; + + const addCustomPattern = (pattern: string) => { + if (pattern.trim() && !taskForm.exclude_patterns.includes(pattern.trim())) { + setTaskForm({ + ...taskForm, + exclude_patterns: [...taskForm.exclude_patterns, pattern.trim()] + }); + } + }; + + const removeExcludePattern = (pattern: string) => { + setTaskForm({ + ...taskForm, + exclude_patterns: taskForm.exclude_patterns.filter(p => p !== pattern) + }); + }; + + const selectedProject = projects.find(p => p.id === taskForm.project_id); + const filteredProjects = projects.filter(project => + project.name.toLowerCase().includes(searchTerm.toLowerCase()) || + project.description?.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( + + + + + + 新建审计任务 + + + +
+ {/* 项目选择 */} +
+
+ + + {filteredProjects.length} 个可用项目 + +
+ + {/* 项目搜索 */} +
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+ + {/* 项目列表 */} +
+ {loading ? ( +
+
+
+ ) : filteredProjects.length > 0 ? ( + filteredProjects.map((project) => ( + setTaskForm({ ...taskForm, project_id: project.id })} + > + +
+
+

{project.name}

+ {project.description && ( +

+ {project.description} +

+ )} +
+ {project.repository_type?.toUpperCase() || 'OTHER'} + {project.default_branch} +
+
+ {taskForm.project_id === project.id && ( +
+
+
+ )} +
+
+
+ )) + ) : ( +
+ +

+ {searchTerm ? '未找到匹配的项目' : '暂无可用项目'} +

+
+ )} +
+
+ + {/* 任务配置 */} + {selectedProject && ( + + + + + 基础配置 + + + + 排除规则 + + + + 高级选项 + + + + +
+
+ + +
+ + {taskForm.task_type === "repository" && ( +
+ + setTaskForm({ ...taskForm, branch_name: e.target.value })} + placeholder={selectedProject.default_branch || "main"} + /> +
+ )} +
+ + {/* 项目信息展示 */} + + +
+ +
+

选中项目:{selectedProject.name}

+
+ {selectedProject.description && ( +

描述:{selectedProject.description}

+ )} +

默认分支:{selectedProject.default_branch}

+ {selectedProject.programming_languages && ( +

编程语言:{JSON.parse(selectedProject.programming_languages).join(', ')}

+ )} +
+
+
+
+
+
+ + +
+
+ +

+ 选择要从审计中排除的文件和目录模式 +

+
+ + {/* 常用排除模式 */} +
+ {commonExcludePatterns.map((pattern) => ( +
+ toggleExcludePattern(pattern.value)} + /> +
+

{pattern.label}

+

{pattern.description}

+
+
+ ))} +
+ + {/* 自定义排除模式 */} +
+ +
+ { + if (e.key === 'Enter') { + addCustomPattern(e.currentTarget.value); + e.currentTarget.value = ''; + } + }} + /> + +
+
+ + {/* 已选择的排除模式 */} + {taskForm.exclude_patterns.length > 0 && ( +
+ +
+ {taskForm.exclude_patterns.map((pattern) => ( + removeExcludePattern(pattern)} + > + {pattern} × + + ))} +
+
+ )} +
+
+ + +
+
+ +

+ 配置代码扫描的详细参数 +

+
+ +
+
+
+ + setTaskForm({ + ...taskForm, + scan_config: { ...taskForm.scan_config, include_tests: !!checked } + }) + } + /> +
+

包含测试文件

+

扫描 *test*, *spec* 等测试文件

+
+
+ +
+ + setTaskForm({ + ...taskForm, + scan_config: { ...taskForm.scan_config, include_docs: !!checked } + }) + } + /> +
+

包含文档文件

+

扫描 README, docs 等文档文件

+
+
+
+ +
+
+ + + setTaskForm({ + ...taskForm, + scan_config: { + ...taskForm.scan_config, + max_file_size: parseInt(e.target.value) || 1024 + } + }) + } + min="1" + max="10240" + /> +
+ +
+ + +
+
+
+ + {/* 分析深度说明 */} + + +
+ +
+

分析深度说明:

+
    +
  • 基础扫描:快速检查语法错误和基本问题
  • +
  • 标准扫描:包含代码质量、安全性和性能分析
  • +
  • 深度扫描:全面分析,包含复杂度、可维护性等高级指标
  • +
+
+
+
+
+
+
+
+ )} + + {/* 操作按钮 */} +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/AuditTasks.tsx b/src/pages/AuditTasks.tsx index 7fb22d5..99d4015 100644 --- a/src/pages/AuditTasks.tsx +++ b/src/pages/AuditTasks.tsx @@ -9,20 +9,22 @@ import { CheckCircle, Clock, Search, - Play, FileText, - Calendar + Calendar, + Plus } from "lucide-react"; import { api } from "@/shared/config/database"; import type { AuditTask } from "@/shared/types"; import { Link } from "react-router-dom"; import { toast } from "sonner"; +import CreateTaskDialog from "@/components/audit/CreateTaskDialog"; export default function AuditTasks() { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); + const [showCreateDialog, setShowCreateDialog] = useState(false); useEffect(() => { loadTasks(); @@ -92,8 +94,8 @@ export default function AuditTasks() {

审计任务

查看和管理所有代码审计任务的执行状态

- @@ -235,28 +237,43 @@ export default function AuditTasks() { -
-
-

{task.total_files}

-

文件数

+
+
+
{task.total_files}
+

文件数

-
-

{task.total_lines}

-

代码行数

+
+
{task.total_lines.toLocaleString()}
+

代码行数

-
-

{task.issues_count}

-

发现问题

+
+
{task.issues_count}
+

发现问题

-
-

{task.quality_score.toFixed(1)}

-

质量评分

+
+
{task.quality_score.toFixed(1)}
+

质量评分

-
-

- {Math.round((task.scanned_files / task.total_files) * 100)}% -

-

扫描进度

+
+ + {/* 扫描进度 */} +
+
+ 扫描进度 + + {task.scanned_files} / {task.total_files} 文件 + +
+
+
+
+
+ + {Math.round((task.scanned_files / task.total_files) * 100)}% 完成 +
@@ -307,14 +324,21 @@ export default function AuditTasks() { {searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}

{!searchTerm && statusFilter === "all" && ( - )} )} + + {/* 新建任务对话框 */} +
); } \ No newline at end of file diff --git a/src/pages/TaskDetail.tsx b/src/pages/TaskDetail.tsx index b1890d5..5d0cd3d 100644 --- a/src/pages/TaskDetail.tsx +++ b/src/pages/TaskDetail.tsx @@ -6,78 +6,35 @@ import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { - ArrowLeft, + ArrowLeft, Activity, AlertTriangle, CheckCircle, Clock, FileText, + Calendar, + GitBranch, Shield, + Bug, + TrendingUp, + Download, Code, - Zap, + Lightbulb, Info, - Lightbulb + Zap } from "lucide-react"; import { api } from "@/shared/config/database"; import type { AuditTask, AuditIssue } from "@/shared/types"; import { toast } from "sonner"; -export default function TaskDetail() { - const { id } = useParams<{ id: string }>(); - const [task, setTask] = useState(null); - const [issues, setIssues] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - if (id) { - loadTaskData(); - } - }, [id]); - - const loadTaskData = async () => { - if (!id) return; - - try { - setLoading(true); - const [taskData, issuesData] = await Promise.all([ - api.getAuditTaskById(id), - api.getAuditIssues(id) - ]); - - setTask(taskData); - setIssues(issuesData); - } catch (error) { - console.error('Failed to load task data:', error); - toast.error("加载任务数据失败"); - } finally { - setLoading(false); - } - }; - - const getStatusColor = (status: string) => { - switch (status) { - case 'completed': return 'bg-green-100 text-green-800'; - case 'running': return 'bg-red-50 text-red-800'; - case 'failed': return 'bg-red-100 text-red-800'; - default: return 'bg-gray-100 text-gray-800'; - } - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case 'completed': return ; - case 'running': return ; - case 'failed': return ; - default: return ; - } - }; - +// 问题列表组件 +function IssuesList({ issues }: { issues: AuditIssue[] }) { const getSeverityColor = (severity: string) => { switch (severity) { case 'critical': return 'bg-red-100 text-red-800 border-red-200'; case 'high': return 'bg-orange-100 text-orange-800 border-orange-200'; case 'medium': return 'bg-yellow-100 text-yellow-800 border-yellow-200'; - case 'low': return 'bg-red-50 text-red-800 border-red-200'; + case 'low': return 'bg-blue-100 text-blue-800 border-blue-200'; default: return 'bg-gray-100 text-gray-800 border-gray-200'; } }; @@ -93,6 +50,229 @@ export default function TaskDetail() { } }; + const criticalIssues = issues.filter(issue => issue.severity === 'critical'); + const highIssues = issues.filter(issue => issue.severity === 'high'); + const mediumIssues = issues.filter(issue => issue.severity === 'medium'); + const lowIssues = issues.filter(issue => issue.severity === 'low'); + + const renderIssue = (issue: AuditIssue, index: number) => ( +
+
+
+
+ {getTypeIcon(issue.issue_type)} +
+
+

{issue.title}

+

{issue.file_path}

+ {issue.line_number && ( +

+ 第 {issue.line_number} 行 + {issue.column_number && `, 第 ${issue.column_number} 列`} +

+ )} +
+
+ + {issue.severity === 'critical' ? '严重' : + issue.severity === 'high' ? '高' : + issue.severity === 'medium' ? '中等' : '低'} + +
+ + {issue.description && ( +

+ {issue.description} +

+ )} + + {issue.code_snippet && ( +
+
+ 问题代码 + {issue.line_number && ( + 第 {issue.line_number} 行 + )} +
+
+            {issue.code_snippet}
+          
+
+ )} + +
+ {issue.suggestion && ( +
+
+ + 修复建议 +
+

{issue.suggestion}

+
+ )} + + {issue.ai_explanation && ( +
+
+ + AI 解释 +
+

{issue.ai_explanation}

+
+ )} +
+
+ ); + + if (issues.length === 0) { + return ( +
+
+ +
+

代码质量优秀!

+

恭喜!没有发现任何问题

+
+

+ 您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。 +

+
+
+ ); + } + + return ( + + + + 全部 ({issues.length}) + + + 严重 ({criticalIssues.length}) + + + 高 ({highIssues.length}) + + + 中等 ({mediumIssues.length}) + + + 低 ({lowIssues.length}) + + + + + {issues.map((issue, index) => renderIssue(issue, index))} + + + + {criticalIssues.length > 0 ? ( + criticalIssues.map((issue, index) => renderIssue(issue, index)) + ) : ( +
+ +

没有发现严重问题

+

代码在严重级别的检查中表现良好

+
+ )} +
+ + + {highIssues.length > 0 ? ( + highIssues.map((issue, index) => renderIssue(issue, index)) + ) : ( +
+ +

没有发现高优先级问题

+

代码在高优先级检查中表现良好

+
+ )} +
+ + + {mediumIssues.length > 0 ? ( + mediumIssues.map((issue, index) => renderIssue(issue, index)) + ) : ( +
+ +

没有发现中等优先级问题

+

代码在中等优先级检查中表现良好

+
+ )} +
+ + + {lowIssues.length > 0 ? ( + lowIssues.map((issue, index) => renderIssue(issue, index)) + ) : ( +
+ +

没有发现低优先级问题

+

代码在低优先级检查中表现良好

+
+ )} +
+
+ ); +} + +export default function TaskDetail() { + const { id } = useParams<{ id: string }>(); + const [task, setTask] = useState(null); + const [issues, setIssues] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (id) { + loadTaskDetail(); + } + }, [id]); + + const loadTaskDetail = async () => { + if (!id) return; + + try { + setLoading(true); + const [taskData, issuesData] = await Promise.all([ + api.getAuditTaskById(id), + api.getAuditIssues(id) + ]); + + setTask(taskData); + setIssues(issuesData); + } catch (error) { + console.error('Failed to load task detail:', error); + toast.error("加载任务详情失败"); + } finally { + setLoading(false); + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': return 'bg-green-100 text-green-800'; + case 'running': return 'bg-red-50 text-red-800'; + case 'failed': return 'bg-red-100 text-red-900'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'completed': return ; + case 'running': return ; + case 'failed': return ; + default: return ; + } + }; + + + const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN', { year: 'numeric', @@ -106,52 +286,56 @@ export default function TaskDetail() { if (loading) { return (
-
+
); } if (!task) { return ( -
-
- -

任务未找到

-

请检查任务ID是否正确

- -
+ + +
+ +
+

任务不存在

+

请检查任务ID是否正确

+
+
); } + const progressPercentage = Math.round((task.scanned_files / task.total_files) * 100); + return ( -
+
{/* 页面标题 */}
- +
-

- {task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'} -

-

- 项目:{task.project?.name || '未知项目'} -

+

任务详情

+

{task.project?.name || '未知项目'} - 审计任务

- + {getStatusIcon(task.status)} {task.status === 'completed' ? '已完成' : @@ -159,320 +343,209 @@ export default function TaskDetail() { task.status === 'failed' ? '失败' : '等待中'} + {task.status === 'completed' && ( + + )}
{/* 任务概览 */} -
- - -
- -
-

扫描文件

-

{task.scanned_files}/{task.total_files}

+
+ + +
+
+

扫描进度

+

{progressPercentage}%

+ +
+
+
- - -
- -
-

代码行数

-

{task.total_lines.toLocaleString()}

+ + +
+
+

发现问题

+

{task.issues_count}

+
+
+
- - -
- -
-

发现问题

-

{task.issues_count}

+ + +
+
+

质量评分

+

{task.quality_score.toFixed(1)}

+
+
+
- - -
- -
-

质量评分

-

{task.quality_score.toFixed(1)}

+ + +
+
+

代码行数

+

{task.total_lines.toLocaleString()}

+
+
+
- {/* 主要内容 */} - - - 任务概览 - 问题详情 - 配置信息 - - - -
- {/* 任务信息 */} - - - 任务信息 - - -
-
- 任务类型 - - {task.task_type === 'repository' ? '仓库审计' : '即时分析'} - -
- -
- 创建时间 - - {formatDate(task.created_at)} - -
- - {task.started_at && ( -
- 开始时间 - - {formatDate(task.started_at)} - -
- )} - - {task.completed_at && ( -
- 完成时间 - - {formatDate(task.completed_at)} - -
- )} - -
- 创建者 - - {task.creator?.full_name || task.creator?.phone || '未知'} - -
-
- - {/* 扫描进度 */} - {task.status === 'running' && ( -
-
- 扫描进度 - {task.scanned_files}/{task.total_files} -
- -
- )} - - {/* 质量评分 */} - {task.status === 'completed' && ( -
-
- 代码质量评分 - {task.quality_score.toFixed(1)}/100 -
- -
- )} -
-
- - {/* 问题统计 */} - - - 问题统计 - - - {issues.length > 0 ? ( -
-
-
-

- {issues.filter(i => i.severity === 'critical').length} -

-

严重问题

-
-
-

- {issues.filter(i => i.severity === 'high').length} -

-

高优先级

-
-
-

- {issues.filter(i => i.severity === 'medium').length} -

-

中等优先级

-
-
-

- {issues.filter(i => i.severity === 'low').length} -

-

低优先级

-
-
- - {/* 问题类型分布 */} -
-

问题类型分布

- {['security', 'bug', 'performance', 'style', 'maintainability'].map(type => { - const count = issues.filter(i => i.issue_type === type).length; - const percentage = issues.length > 0 ? (count / issues.length) * 100 : 0; - return ( -
-
- {getTypeIcon(type)} - {type} -
-
-
-
-
- {count} -
-
- ); - })} -
-
- ) : ( -
- -

未发现问题

-

代码质量良好

-
- )} -
-
-
-
- - - {issues.length > 0 ? ( -
- {issues.map((issue, index) => ( - - -
-
- {getTypeIcon(issue.issue_type)} -
-

{issue.title}

-

- {issue.file_path}:{issue.line_number} -

-
-
- - {issue.severity} - -
- -

- {issue.description} -

- - {issue.code_snippet && ( -
-

代码片段:

-
-                          {issue.code_snippet}
-                        
-
- )} - - {issue.suggestion && ( -
-

- - 修复建议: -

-

{issue.suggestion}

-
- )} - - {issue.ai_explanation && ( -
-

- AI 解释: -

-

{issue.ai_explanation}

-
- )} -
-
- ))} -
- ) : ( - - - -

代码质量良好!

-

未发现任何问题

-
-
- )} -
- - - + {/* 任务信息 */} +
+
+ - 扫描配置 + + + 任务信息 +
-

分支信息

-

+

任务类型

+

+ {task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'} +

+
+
+

目标分支

+

+ {task.branch_name || '默认分支'}

-

排除模式

-
- {JSON.parse(task.exclude_patterns || '[]').length > 0 ? ( - JSON.parse(task.exclude_patterns).map((pattern: string, index: number) => ( - - {pattern} - - )) - ) : ( -

无排除模式

- )} +

创建时间

+

+ + {formatDate(task.created_at)} +

+
+ {task.completed_at && ( +
+

完成时间

+

+ + {formatDate(task.completed_at)} +

+
+ )} +
+ + {/* 排除模式 */} + {task.exclude_patterns && ( +
+

排除模式

+
+ {JSON.parse(task.exclude_patterns).map((pattern: string) => ( + + {pattern} + + ))}
-
- -
-

扫描配置

-
-                  {JSON.stringify(JSON.parse(task.scan_config || '{}'), null, 2)}
-                
-
+ )} + + {/* 扫描配置 */} + {task.scan_config && ( +
+

扫描配置

+
+
+                      {JSON.stringify(JSON.parse(task.scan_config), null, 2)}
+                    
+
+
+ )}
- - +
+ +
+ + + + + 项目信息 + + + + {task.project ? ( + <> +
+

项目名称

+ + {task.project.name} + +
+ {task.project.description && ( +
+

项目描述

+

{task.project.description}

+
+ )} +
+

仓库类型

+

{task.project.repository_type?.toUpperCase() || 'OTHER'}

+
+ {task.project.programming_languages && ( +
+

编程语言

+
+ {JSON.parse(task.project.programming_languages).map((lang: string) => ( + + {lang} + + ))} +
+
+ )} + + ) : ( +

项目信息不可用

+ )} +
+
+
+
+ + {/* 问题列表 */} + {issues.length > 0 && ( + + + + + 发现的问题 ({issues.length}) + + + + + + + )}
); } \ No newline at end of file