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}
}
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.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 ? (
暂无用户数据
) : (
| ID |
用户名 |
姓名 |
注册时间 |
最新登录时间 |
会议数量 |
会议时长 |
{usersList.map(u => (
| {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 ? (
暂无在线用户
) : (
| ID |
用户名 |
姓名 |
会话数 |
剩余时间 |
操作 |
{onlineUsers.map(u => (
| {u.user_id} |
{u.username} |
{u.caption} |
{u.token_count} |
{u.ttl_hours}h |
|
))}
)}
{/* 任务监控 */}
任务监控
{tasks.length === 0 ? (
暂无任务
) : (
| 任务ID |
类型 |
关联对象 |
创建者 |
状态 |
进度 |
创建时间 |
{tasks.map(task => (
| {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;