import { useState, useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Progress } from "@/components/ui/progress"; import { Plus, Search, GitBranch, Calendar, Users, Settings, Code, Shield, Activity, Upload, FileText, AlertCircle, Trash2, Edit, CheckCircle, Terminal } from "lucide-react"; import { api } from "@/shared/config/database"; import { validateZipFile } from "@/features/projects/services"; import type { Project, CreateProjectForm } from "@/shared/types"; import { saveZipFile } from "@/shared/utils/zipStorage"; import { Link } from "react-router-dom"; import { toast } from "sonner"; import CreateTaskDialog from "@/components/audit/CreateTaskDialog"; import { SUPPORTED_LANGUAGES } from "@/shared/constants"; export default function Projects() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [showCreateDialog, setShowCreateDialog] = useState(false); const [showCreateTaskDialog, setShowCreateTaskDialog] = useState(false); const [selectedProjectForTask, setSelectedProjectForTask] = useState(""); const [uploadProgress, setUploadProgress] = useState(0); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [projectToDelete, setProjectToDelete] = useState(null); const [showEditDialog, setShowEditDialog] = useState(false); const [projectToEdit, setProjectToEdit] = useState(null); const [editForm, setEditForm] = useState({ name: "", description: "", repository_url: "", repository_type: "github", default_branch: "main", programming_languages: [] }); const [createForm, setCreateForm] = useState({ name: "", description: "", repository_url: "", repository_type: "github", default_branch: "main", programming_languages: [] }); const [selectedFile, setSelectedFile] = useState(null); // 将小写语言名转换为显示格式 const formatLanguageName = (lang: string): string => { const nameMap: Record = { 'javascript': 'JavaScript', 'typescript': 'TypeScript', 'python': 'Python', 'java': 'Java', 'go': 'Go', 'rust': 'Rust', 'cpp': 'C++', 'csharp': 'C#', 'php': 'PHP', 'ruby': 'Ruby', 'swift': 'Swift', 'kotlin': 'Kotlin' }; return nameMap[lang] || lang.charAt(0).toUpperCase() + lang.slice(1); }; const supportedLanguages = SUPPORTED_LANGUAGES.map(formatLanguageName); useEffect(() => { loadProjects(); }, []); const loadProjects = async () => { try { setLoading(true); const data = await api.getProjects(); setProjects(data); } catch (error) { console.error('Failed to load projects:', error); toast.error("加载项目失败"); } finally { setLoading(false); } }; const handleCreateProject = async () => { if (!createForm.name.trim()) { toast.error("请输入项目名称"); return; } try { await api.createProject({ ...createForm, // 无登录场景下不传 owner_id,由后端置为 null } as any); // 记录用户操作 import('@/shared/utils/logger').then(({ logger }) => { logger.logUserAction('创建项目', { projectName: createForm.name, repositoryType: createForm.repository_type, languages: createForm.programming_languages, }); }); toast.success("项目创建成功"); setShowCreateDialog(false); resetCreateForm(); loadProjects(); } catch (error) { console.error('Failed to create project:', error); // 记录错误并显示详细信息 import('@/shared/utils/errorHandler').then(({ handleError }) => { handleError(error, '创建项目失败'); }); const errorMessage = error instanceof Error ? error.message : '未知错误'; toast.error(`创建项目失败: ${errorMessage}`); } }; const resetCreateForm = () => { setCreateForm({ name: "", description: "", repository_url: "", repository_type: "github", default_branch: "main", programming_languages: [] }); setSelectedFile(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; // 验证文件 const validation = validateZipFile(file); if (!validation.valid) { toast.error(validation.error); return; } setSelectedFile(file); // 清空 input value 以便可以再次选择同一个文件(虽然状态已经处理了) event.target.value = ''; }; const handleUploadAndCreate = async () => { if (!selectedFile) { toast.error("请先选择ZIP文件"); return; } // 检查是否有项目名称 if (!createForm.name.trim()) { toast.error("请先输入项目名称"); return; } try { setUploading(true); setUploadProgress(0); // 模拟上传进度 const progressInterval = setInterval(() => { setUploadProgress(prev => { if (prev >= 100) { clearInterval(progressInterval); return 100; } return prev + 20; }); }, 100); // 创建项目 const project = await api.createProject({ ...createForm, repository_type: "other" } as any); // 保存ZIP文件到IndexedDB(使用项目ID作为key) try { await saveZipFile(project.id, selectedFile); } catch (error) { console.error('保存ZIP文件失败:', error); } clearInterval(progressInterval); setUploadProgress(100); // 记录用户操作 import('@/shared/utils/logger').then(({ logger }) => { logger.logUserAction('上传ZIP文件创建项目', { projectName: project.name, fileName: selectedFile.name, fileSize: selectedFile.size, }); }); // 关闭创建对话框 setShowCreateDialog(false); resetCreateForm(); loadProjects(); toast.success(`项目 "${project.name}" 已创建`, { description: 'ZIP文件已保存,您可以启动代码审计', duration: 4000 }); } catch (error: any) { console.error('Upload failed:', error); // 记录错误并显示详细信息 import('@/shared/utils/errorHandler').then(({ handleError }) => { handleError(error, '上传ZIP文件失败'); }); const errorMessage = error?.message || '未知错误'; toast.error(`上传失败: ${errorMessage}`); } finally { setUploading(false); setUploadProgress(0); } }; const filteredProjects = projects.filter(project => project.name.toLowerCase().includes(searchTerm.toLowerCase()) || project.description?.toLowerCase().includes(searchTerm.toLowerCase()) ); const getRepositoryIcon = (type?: string) => { switch (type) { case 'github': return '🐙'; case 'gitlab': return '🦊'; default: return '📁'; } }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN'); }; const handleCreateTask = (projectId: string) => { setSelectedProjectForTask(projectId); setShowCreateTaskDialog(true); }; const handleEditClick = (project: Project) => { setProjectToEdit(project); 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) : [] }); setShowEditDialog(true); }; const handleSaveEdit = async () => { if (!projectToEdit) return; if (!editForm.name.trim()) { toast.error("项目名称不能为空"); return; } try { await api.updateProject(projectToEdit.id, editForm); toast.success(`项目 "${editForm.name}" 已更新`); setShowEditDialog(false); setProjectToEdit(null); loadProjects(); } catch (error) { console.error('Failed to update project:', error); toast.error("更新项目失败"); } }; const handleToggleLanguage = (lang: string) => { const currentLanguages = editForm.programming_languages || []; const newLanguages = currentLanguages.includes(lang) ? currentLanguages.filter(l => l !== lang) : [...currentLanguages, lang]; setEditForm({ ...editForm, programming_languages: newLanguages }); }; const handleDeleteClick = (project: Project) => { setProjectToDelete(project); setShowDeleteDialog(true); }; const handleConfirmDelete = async () => { if (!projectToDelete) return; try { await api.deleteProject(projectToDelete.id); // 记录用户操作 import('@/shared/utils/logger').then(({ logger }) => { logger.logUserAction('删除项目', { projectId: projectToDelete.id, projectName: projectToDelete.name, }); }); toast.success(`项目 "${projectToDelete.name}" 已移到回收站`, { description: '您可以在回收站中恢复此项目', duration: 4000 }); setShowDeleteDialog(false); setProjectToDelete(null); loadProjects(); } catch (error) { console.error('Failed to delete project:', error); // 记录错误并显示详细信息 import('@/shared/utils/errorHandler').then(({ handleError }) => { handleError(error, '删除项目失败'); }); const errorMessage = error instanceof Error ? error.message : '未知错误'; toast.error(`删除项目失败: ${errorMessage}`); } }; const handleTaskCreated = () => { toast.success("审计任务已创建", { description: '因为网络和代码文件大小等因素,审计时长通常至少需要1分钟,请耐心等待...', duration: 5000 }); // 任务创建后会自动跳转到项目详情页面 }; if (loading) { return (
); } return (
{/* Decorative Background */}
{/* Header Section */}

项目_管理

管理代码仓库和配置审计任务

初始化_新_项目
Git 仓库 上传源码
setCreateForm({ ...createForm, name: e.target.value })} placeholder="输入项目名称" className="terminal-input" />