import React, { useState, useEffect } from 'react'; import { LogOut, User, Users, Activity, Server, HardDrive, Cpu, MemoryStick, RefreshCw, UserX, ChevronDown, KeyRound, Shield, BookText, Waves, UserCog, Search } from 'lucide-react'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import Dropdown from '../components/Dropdown'; import menuService from '../services/menuService'; import ConfirmDialog from '../components/ConfirmDialog'; import Toast from '../components/Toast'; import PageLoading from '../components/PageLoading'; import FormModal from '../components/FormModal'; import './AdminDashboard.css'; // 常量定义 const AUTO_REFRESH_INTERVAL = 30; // 自动刷新间隔(秒) const STATUS_BADGE_MAP = { 'pending': 'status-badge status-pending', 'processing': 'status-badge status-processing', 'completed': 'status-badge status-completed', 'failed': 'status-badge status-failed' }; const STATUS_TEXT_MAP = { 'pending': '待处理', 'processing': '处理中', 'completed': '已完成', 'failed': '失败' }; const TASK_TYPE_TEXT_MAP = { 'transcription': '转录', 'summary': '总结', 'knowledge_base': '知识库' }; // 辅助函数 const getStatusBadgeClass = (status) => STATUS_BADGE_MAP[status] || 'status-badge'; const getStatusText = (status) => STATUS_TEXT_MAP[status] || status; const getTaskTypeText = (type) => TASK_TYPE_TEXT_MAP[type] || type; // 默认管理员菜单 const getDefaultMenus = () => [ { menu_code: 'account_settings', menu_name: '账户设置', menu_type: 'link', menu_url: '/account-settings' }, { menu_code: 'prompt_management', menu_name: '提示词仓库', menu_type: 'link', menu_url: '/prompts' }, { menu_code: 'platform_admin', menu_name: '平台管理', menu_type: 'link', menu_url: '/admin' }, { menu_code: 'logout', menu_name: '退出登录', menu_type: 'action' } ]; const AdminDashboard = ({ user, onLogout }) => { // 统计数据 const [stats, setStats] = useState(null); const [onlineUsers, setOnlineUsers] = useState([]); const [usersList, setUsersList] = useState([]); const [tasks, setTasks] = useState([]); const [resources, setResources] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); // 菜单权限相关状态 const [userMenus, setUserMenus] = useState([]); // 任务筛选 const [taskType, setTaskType] = useState('all'); const [taskStatus, setTaskStatus] = useState('all'); // 自动刷新定时器 const [autoRefresh, setAutoRefresh] = useState(true); const [countdown, setCountdown] = useState(AUTO_REFRESH_INTERVAL); // Toast和确认对话框 const [toasts, setToasts] = useState([]); const [kickConfirmInfo, setKickConfirmInfo] = useState(null); // 会议详情模态框 const [showMeetingModal, setShowMeetingModal] = useState(false); const [meetingDetails, setMeetingDetails] = useState(null); const [meetingLoading, setMeetingLoading] = useState(false); // Toast辅助函数 const showToast = (message, type = 'info') => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); }; const removeToast = (id) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; // 获取菜单配置 const getMenuItemConfig = (menu) => { const iconMap = { 'change_password': , 'account_settings': , 'prompt_management': , 'platform_admin': , 'logout': }; const actionMap = { 'logout': onLogout }; return { icon: iconMap[menu.menu_code], label: menu.menu_name, onClick: menu.menu_type === 'link' && menu.menu_url ? () => window.location.href = menu.menu_url : actionMap[menu.menu_code] }; }; // 获取用户菜单权限 const fetchUserMenus = async () => { try { const response = await menuService.getUserMenus(); if (response.code === '200') { setUserMenus(response.data.menus || []); } else { setUserMenus(getDefaultMenus()); } } catch (error) { console.error('Error fetching user menus:', error); setUserMenus(getDefaultMenus()); } }; // 初始化和自动刷新 useEffect(() => { fetchAllData(); fetchUserMenus(); }, []); useEffect(() => { if (autoRefresh && !showMeetingModal) { const timer = setInterval(() => { setCountdown(prev => { if (prev <= 1) { fetchAllData(); return AUTO_REFRESH_INTERVAL; } return prev - 1; }); }, 1000); return () => clearInterval(timer); } }, [autoRefresh, showMeetingModal]); useEffect(() => { fetchTasks(); }, [taskType, taskStatus]); const fetchAllData = async () => { try { setLoading(true); await Promise.all([ fetchStats(), fetchOnlineUsers(), fetchUsersList(), fetchTasks(), fetchResources() ]); setError(''); } catch (err) { console.error('获取数据失败:', err); setError('加载数据失败,请稍后重试'); } finally { setLoading(false); } }; const fetchStats = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.DASHBOARD_STATS)); if (response.code === '200') { setStats(response.data); } } catch (err) { console.error('获取统计数据失败:', err); } }; const fetchOnlineUsers = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.ONLINE_USERS)); if (response.code === '200') { setOnlineUsers(response.data.users || []); } } catch (err) { console.error('获取在线用户失败:', err); } }; const fetchUsersList = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.USER_STATS)); if (response.code === '200') { setUsersList(response.data.users || []); } } catch (err) { console.error('获取用户列表失败:', err); } }; const fetchTasks = async () => { try { const params = new URLSearchParams(); if (taskType !== 'all') params.append('task_type', taskType); if (taskStatus !== 'all') params.append('status', taskStatus); params.append('limit', '20'); const url = `${API_ENDPOINTS.ADMIN.TASKS_MONITOR}?${params.toString()}`; const response = await apiClient.get(buildApiUrl(url)); if (response.code === '200') { setTasks(response.data.tasks || []); } } catch (err) { console.error('获取任务列表失败:', err); } }; const fetchResources = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.SYSTEM_RESOURCES)); if (response.code === '200') { setResources(response.data); } } catch (err) { console.error('获取系统资源失败:', err); } }; const handleKickUser = async () => { try { const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.ADMIN.KICK_USER(kickConfirmInfo.user_id))); if (response.code === '200') { showToast('用户已被踢出', 'success'); fetchOnlineUsers(); } else { showToast(`踢出失败: ${response.message}`, 'error'); } } catch (err) { console.error('踢出用户失败:', err); showToast('踢出用户失败', 'error'); } finally { setKickConfirmInfo(null); } }; const handleViewMeeting = async (meetingId) => { if (!meetingId) return; setMeetingLoading(true); setShowMeetingModal(true); setMeetingDetails(null); // Clear previous try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meetingId))); if (response.code === '200') { setMeetingDetails(response.data); } else { showToast('获取会议详情失败', 'error'); } } catch (err) { console.error('Fetch meeting details error:', err); showToast('获取会议详情失败', 'error'); } finally { setMeetingLoading(false); } }; if (loading && !stats) { return ; } return (
{/* 顶部导航栏 - 使用与普通Dashboard一致的样式 */}
管理面板
{user.avatar_url ? ( {user.caption} ) : ( )} {user.caption}
} items={userMenus.map(menu => getMenuItemConfig(menu))} />
{/* Dashboard Content */}
{error &&
{error}
} {/* 统计卡片 */} {stats && (
{/* 用户统计 */}

用户统计

{stats.users.total}
今日新增: {stats.users.today_new} 在线: {stats.users.online}
{/* 会议统计 */}

会议统计

{stats.meetings.total}
今日新增: {stats.meetings.today_new}
{/* 存储统计 */}

存储统计

{stats.storage.audio_total_size_gb} GB
音频文件: {stats.storage.audio_files_count} 个
{/* 服务器资源 */} {resources && (

服务器资源

{resources.cpu.percent}%
{resources.memory.percent}%
{resources.disk.percent}%
)}
)} {/* 任务统计 */} {stats && (

任务概览

转录任务

进行中 {stats.tasks.transcription.running}
已完成 {stats.tasks.transcription.completed}
失败 {stats.tasks.transcription.failed}

总结任务

进行中 {stats.tasks.summary.running}
已完成 {stats.tasks.summary.completed}
失败 {stats.tasks.summary.failed}

知识库任务

进行中 {stats.tasks.knowledge_base.running}
已完成 {stats.tasks.knowledge_base.completed}
失败 {stats.tasks.knowledge_base.failed}
)} {/* 用户列表 */}

用户列表 ({usersList.length})

{usersList.length === 0 ? (
暂无用户数据
) : ( {usersList.map(u => ( ))}
ID 用户名 姓名 注册时间 最新登录时间 会议数量 会议时长
{u.user_id} {u.username} {u.caption} {u.created_at ? new Date(u.created_at).toLocaleString('zh-CN') : '-'} {u.last_login_time ? new Date(u.last_login_time).toLocaleString('zh-CN') : '-'} {u.meeting_count || 0} {u.total_duration_formatted || '-'}
)}
{/* 在线用户列表 */}

在线用户 ({onlineUsers.length})

{onlineUsers.length === 0 ? (
暂无在线用户
) : ( {onlineUsers.map(u => ( ))}
ID 用户名 姓名 会话数 剩余时间 操作
{u.user_id} {u.username} {u.caption} {u.token_count} {u.ttl_hours}h
)}
{/* 任务监控 */}

任务监控

{tasks.length === 0 ? (
暂无任务
) : ( {tasks.map(task => ( ))}
任务ID 类型 关联对象 创建者 状态 进度 创建时间
{task.task_id} {getTaskTypeText(task.task_type)}
{task.meeting_id && task.task_type === 'transcription' && ( )} {task.meeting_title || '-'}
{task.creator_name || '-'} {getStatusText(task.status)} {task.progress !== null ? `${task.progress}%` : '-'} {new Date(task.created_at).toLocaleString()}
)}
{/* 踢出用户确认对话框 */} setKickConfirmInfo(null)} onConfirm={handleKickUser} title="踢出用户" message={`确定要踢出用户"${kickConfirmInfo?.caption}"吗?该用户将被强制下线。`} confirmText="确定踢出" cancelText="取消" type="warning" /> {/* 会议数据模态框 (使用标准 FormModal) */} setShowMeetingModal(false)} title="会议数据" size="medium" actions={ } > {meetingLoading ? (

正在获取会议数据...

) : meetingDetails ? (
会议名称: {meetingDetails.title}
开始时间: {meetingDetails.meeting_time ? new Date(meetingDetails.meeting_time).toLocaleString() : '-'}
使用模版: {meetingDetails.prompt_name || '默认模版'}
音频信息:
{meetingDetails.audio_file_path && meetingDetails.audio_file_path.length > 5 ? ( <> {meetingDetails.audio_duration ? `${Math.floor(meetingDetails.audio_duration / 60)}分${Math.floor(meetingDetails.audio_duration % 60)}秒` : '未知时长'} 下载 ) : ( 无音频 )}
) : (
无法加载数据
)}
{/* Toast notifications */} {toasts.map(toast => ( removeToast(toast.id)} /> ))} ); }; export default AdminDashboard;