import React, { useState, useEffect, useMemo, useRef } from 'react';
import {
Card,
Table,
Row,
Col,
Statistic,
Progress,
Space,
Button,
Tag,
Select,
App,
Modal,
Descriptions,
Badge,
Tooltip,
Typography,
Divider,
} from 'antd';
import {
UsergroupAddOutlined,
VideoCameraAddOutlined,
DatabaseOutlined,
SyncOutlined,
UserDeleteOutlined,
SearchOutlined,
DeploymentUnitOutlined,
HddOutlined,
ApartmentOutlined,
FileTextOutlined,
ReloadOutlined,
PauseCircleOutlined,
PlayCircleOutlined,
ClockCircleOutlined,
CloseOutlined,
} from '@ant-design/icons';
import apiClient from '../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
import './AdminDashboard.css';
const { Text } = Typography;
const AUTO_REFRESH_INTERVAL = 30;
const TASK_TYPE_MAP = {
transcription: { text: '转录', color: 'blue' },
summary: { text: '总结', color: 'green' },
knowledge_base: { text: '知识库', color: 'purple' },
};
const STATUS_MAP = {
pending: { text: '待处理', color: 'default' },
processing: { text: '处理中', color: 'processing' },
completed: { text: '已完成', color: 'success' },
failed: { text: '失败', color: 'error' },
};
const formatResourcePercent = (value) => `${Number(value || 0).toFixed(1)}%`;
const AdminDashboard = () => {
const { message, modal } = App.useApp();
const inFlightRef = useRef(false);
const mountedRef = useRef(true);
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 [taskLoading, setTaskLoading] = useState(false);
const [lastUpdatedAt, setLastUpdatedAt] = useState(null);
const [taskType, setTaskType] = useState('all');
const [taskStatus, setTaskStatus] = useState('all');
const [autoRefresh, setAutoRefresh] = useState(true);
const [countdown, setCountdown] = useState(AUTO_REFRESH_INTERVAL);
const [showMeetingModal, setShowMeetingModal] = useState(false);
const [meetingDetails, setMeetingDetails] = useState(null);
const [meetingLoading, setMeetingLoading] = useState(false);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
useEffect(() => {
fetchAllData();
}, []);
useEffect(() => {
if (!autoRefresh || showMeetingModal) return;
const timer = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
fetchAllData({ silent: true });
return AUTO_REFRESH_INTERVAL;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [autoRefresh, showMeetingModal]);
useEffect(() => {
fetchTasks();
}, [taskType, taskStatus]);
const fetchAllData = async ({ silent = false } = {}) => {
if (inFlightRef.current) return;
inFlightRef.current = true;
try {
if (!silent && mountedRef.current) {
setLoading(true);
}
await Promise.all([fetchStats(), fetchOnlineUsers(), fetchUsersList(), fetchTasks(), fetchResources()]);
if (mountedRef.current) {
setLastUpdatedAt(new Date());
setCountdown(AUTO_REFRESH_INTERVAL);
}
} catch (err) {
console.error('获取数据失败:', err);
if (mountedRef.current && !silent) {
message.error('加载数据失败,请稍后重试');
}
} finally {
inFlightRef.current = false;
if (mountedRef.current && !silent) {
setLoading(false);
}
}
};
const fetchStats = async () => {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.DASHBOARD_STATS));
if (response.code === '200') setStats(response.data);
};
const fetchOnlineUsers = async () => {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.ONLINE_USERS));
if (response.code === '200') setOnlineUsers(response.data.users || []);
};
const fetchUsersList = async () => {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.USER_STATS));
if (response.code === '200') setUsersList(response.data.users || []);
};
const fetchTasks = async () => {
try {
setTaskLoading(true);
const params = new URLSearchParams();
if (taskType !== 'all') params.append('task_type', taskType);
if (taskStatus !== 'all') params.append('status', taskStatus);
params.append('limit', '20');
const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.ADMIN.TASKS_MONITOR}?${params.toString()}`));
if (response.code === '200') setTasks(response.data.tasks || []);
} finally {
setTaskLoading(false);
}
};
const fetchResources = async () => {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.SYSTEM_RESOURCES));
if (response.code === '200') setResources(response.data);
};
const handleKickUser = (u) => {
modal.confirm({
title: '踢出用户',
content: `确定要踢出用户"${u.caption}"吗?该用户将被强制下线。`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.ADMIN.KICK_USER(u.user_id)));
if (response.code === '200') {
message.success('用户已被踢出');
fetchOnlineUsers();
}
} catch {
message.error('踢出用户失败');
}
},
});
};
const handleViewMeeting = async (meetingId) => {
setMeetingLoading(true);
setShowMeetingModal(true);
try {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meetingId)));
if (response.code === '200') setMeetingDetails(response.data);
} catch {
message.error('获取会议详情失败');
} finally {
setMeetingLoading(false);
}
};
const handleDownloadTranscript = async (meetingId) => {
try {
const response = await apiClient.get(buildApiUrl(`/api/meetings/${meetingId}/transcript`));
if (response.code === '200') {
const dataStr = JSON.stringify(response.data, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `transcript_${meetingId}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
} catch {
message.error('下载失败');
}
};
const taskCompletionRate = useMemo(() => {
const all = tasks.length || 1;
const completed = tasks.filter((item) => item.status === 'completed').length;
return Math.round((completed / all) * 100);
}, [tasks]);
const userColumns = [
{ title: 'ID', dataIndex: 'user_id', key: 'user_id', width: 80 },
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '姓名', dataIndex: 'caption', key: 'caption' },
{
title: '注册时间',
dataIndex: 'created_at',
key: 'created_at',
render: (text) => (text ? new Date(text).toLocaleString() : '-'),
},
{
title: '最新登录',
dataIndex: 'last_login_time',
key: 'last_login_time',
render: (text) => (text ? new Date(text).toLocaleString() : '-'),
},
{ title: '会议数', dataIndex: 'meeting_count', key: 'meeting_count', align: 'right' },
{ title: '总时长', dataIndex: 'total_duration_formatted', key: 'total_duration_formatted' },
];
const onlineUserColumns = [
{ title: '姓名', dataIndex: 'caption', key: 'caption' },
{ title: '会话数', dataIndex: 'token_count', key: 'token_count', align: 'center' },
{ title: '剩余(h)', dataIndex: 'ttl_hours', key: 'ttl_hours', align: 'center' },
{
title: '操作',
key: 'action',
align: 'center',
render: (_, record) => (
} onClick={() => handleKickUser(record)} />
),
},
];
const taskColumns = [
{
title: '类型',
dataIndex: 'task_type',
key: 'task_type',
width: 100,
render: (type) =>
实时监控平台用户、任务与资源,快速处理异常会话与任务状态。