/** * Dashboard Page * Cyberpunk Terminal Aesthetic */ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"; import { Activity, AlertTriangle, Clock, Code, FileText, GitBranch, Shield, TrendingUp, Zap, BarChart3, Target, ArrowUpRight, Calendar, MessageSquare, Bot, Cpu, Terminal } from "lucide-react"; import { api, dbMode, isDemoMode } from "@/shared/config/database"; import type { Project, AuditTask, ProjectStats } from "@/shared/types"; import { Link } from "react-router-dom"; import { toast } from "sonner"; import { getRuleSets } from "@/shared/api/rules"; import { getPromptTemplates } from "@/shared/api/prompts"; export default function Dashboard() { const [stats, setStats] = useState(null); const [recentProjects, setRecentProjects] = useState([]); const [recentTasks, setRecentTasks] = useState([]); const [loading, setLoading] = useState(true); const [issueTypeData, setIssueTypeData] = useState>([]); const [qualityTrendData, setQualityTrendData] = useState>([]); const [ruleStats, setRuleStats] = useState({ total: 0, enabled: 0 }); const [templateStats, setTemplateStats] = useState({ total: 0, active: 0 }); useEffect(() => { loadDashboardData(); }, []); const loadDashboardData = async () => { try { setLoading(true); const results = await Promise.allSettled([ api.getProjectStats(), api.getProjects(), api.getAuditTasks() ]); if (results[0].status === 'fulfilled') { setStats(results[0].value); } else { setStats({ total_projects: 0, active_projects: 0, total_tasks: 0, completed_tasks: 0, total_issues: 0, resolved_issues: 0, avg_quality_score: 0 }); } if (results[1].status === 'fulfilled') { setRecentProjects(Array.isArray(results[1].value) ? results[1].value.slice(0, 6) : []); } else { setRecentProjects([]); } let tasks: AuditTask[] = []; if (results[2].status === 'fulfilled') { tasks = Array.isArray(results[2].value) ? results[2].value : []; setRecentTasks(tasks.slice(0, 10)); } else { setRecentTasks([]); } if (tasks.length > 0) { const tasksByDate = tasks .filter(t => t.completed_at && t.quality_score > 0) .sort((a, b) => new Date(a.completed_at!).getTime() - new Date(b.completed_at!).getTime()) .slice(-6); const trendData = tasksByDate.map((task) => ({ date: new Date(task.completed_at!).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }), score: task.quality_score })); setQualityTrendData(trendData.length > 0 ? trendData : []); } else { setQualityTrendData([]); } try { const allIssues = await Promise.all( tasks.map(task => api.getAuditIssues(task.id).catch(() => [])) ); const flatIssues = allIssues.flat(); if (flatIssues.length > 0) { const typeCount: Record = {}; flatIssues.forEach(issue => { typeCount[issue.issue_type] = (typeCount[issue.issue_type] || 0) + 1; }); const typeMap: Record = { security: { name: '安全问题', color: '#f43f5e' }, bug: { name: '潜在Bug', color: '#f97316' }, performance: { name: '性能问题', color: '#eab308' }, style: { name: '代码风格', color: '#3b82f6' }, maintainability: { name: '可维护性', color: '#8b5cf6' } }; const issueData = Object.entries(typeCount).map(([type, count]) => ({ name: typeMap[type]?.name || type, value: count, color: typeMap[type]?.color || '#6b7280' })); setIssueTypeData(issueData); } else { setIssueTypeData([]); } } catch (error) { setIssueTypeData([]); } try { const [rulesRes, promptsRes] = await Promise.all([ getRuleSets(), getPromptTemplates(), ]); const totalRules = rulesRes.items.reduce((acc, rs) => acc + rs.rules_count, 0); const enabledRules = rulesRes.items.reduce((acc, rs) => acc + rs.enabled_rules_count, 0); setRuleStats({ total: totalRules, enabled: enabledRules }); setTemplateStats({ total: promptsRes.items.length, active: promptsRes.items.filter(t => t.is_active).length }); } catch (error) { console.error('获取规则和模板统计失败:', error); } } catch (error) { console.error('仪表盘数据加载失败:', error); toast.error("数据加载失败"); } finally { setLoading(false); } }; const getStatusBadge = (status: string) => { switch (status) { case 'completed': return 完成; case 'running': return 运行中; case 'failed': return 失败; default: return 待处理; } }; if (loading) { return (

加载数据中...

); } return (
{/* Grid background */}
{/* Demo Mode Warning */} {isDemoMode && (
当前使用演示模式,显示的是模拟数据。 前往配置 →
)} {/* Stats Cards */}
{/* Total Projects */}

总项目数

{stats?.total_projects || 0}

活跃: {stats?.active_projects || 0}

{/* Audit Tasks */}

审计任务

{stats?.total_tasks || 0}

已完成: {stats?.completed_tasks || 0}

{/* Issues Found */}

发现问题

{stats?.total_issues || 0}

已解决: {stats?.resolved_issues || 0}

{/* Quality Score */}

平均质量分

{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}

{stats?.avg_quality_score ? (

持续改进

) : (

暂无数据

)}
{/* Main Content */}
{/* Left Content */}
{/* Charts */}
{/* Quality Trend */}

代码质量趋势

{qualityTrendData.length > 0 ? ( ) : (

暂无质量趋势数据

)}
{/* Issue Distribution */}

问题类型分布

{issueTypeData.length > 0 ? ( `${name} ${(percent * 100).toFixed(0)}%`} outerRadius={70} dataKey="value" stroke="var(--cyber-bg)" strokeWidth={2} > {issueTypeData.map((entry) => ( ))} ) : (

暂无问题分布数据

)}
{/* Projects Overview */}

项目概览

{recentProjects.length > 0 ? ( recentProjects.map((project) => ( { e.currentTarget.style.background = 'var(--cyber-hover-bg)'; e.currentTarget.style.borderColor = 'var(--cyber-border-accent)'; }} onMouseOut={(e) => { e.currentTarget.style.background = 'var(--cyber-bg-elevated)'; e.currentTarget.style.borderColor = 'var(--cyber-border)'; }} >

{project.name}

{project.is_active ? '活跃' : '暂停'}

{project.description || '暂无描述'}

{new Date(project.created_at).toLocaleDateString('zh-CN')}
)) ) : (

暂无项目

创建您的第一个项目开始审计

)}
{/* Recent Tasks */}

最近任务

{recentTasks.length > 0 ? ( recentTasks.slice(0, 6).map((task) => ( { e.currentTarget.style.background = 'var(--cyber-hover-bg)'; }} onMouseOut={(e) => { e.currentTarget.style.background = 'var(--cyber-bg-elevated)'; }} >
{task.status === 'completed' ? : task.status === 'running' ? : }

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

质量分: {task.quality_score?.toFixed(1) || '0.0'}

{getStatusBadge(task.status)} )) ) : (

暂无任务

)}
{/* Right Sidebar */}
{/* Quick Actions */}

快速操作

{/* System Status */}

系统状态

数据库模式 {dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
活跃项目 {stats?.active_projects || 0}
运行中任务 {recentTasks.filter(t => t.status === 'running').length}
待解决问题 {stats ? stats.total_issues - stats.resolved_issues : 0}
审计规则 {ruleStats.enabled}/{ruleStats.total}
提示词模板 {templateStats.active}/{templateStats.total}
{/* Recent Activity */}

最新活动

{recentTasks.length > 0 ? ( recentTasks.slice(0, 3).map((task) => { const timeAgo = (() => { const now = new Date(); const taskDate = new Date(task.created_at); const diffMs = now.getTime() - taskDate.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 60) return `${diffMins}分钟前`; if (diffHours < 24) return `${diffHours}小时前`; return `${diffDays}天前`; })(); const statusText = task.status === 'completed' ? '任务完成' : task.status === 'running' ? '任务运行中' : task.status === 'failed' ? '任务失败' : '任务待处理'; return (

{statusText}

项目 "{task.project?.name || '未知项目'}" {task.status === 'completed' && task.issues_count > 0 && ` - 发现 ${task.issues_count} 个问题` }

{timeAgo}

); }) ) : (

暂无活动记录

)}
); }