import { useEffect, useState } from 'react' import { Button, Card, Col, Empty, Row, Select, Space, Statistic, Table, Tag, Typography, message } from 'antd' import { DashboardOutlined, DatabaseOutlined, FullscreenExitOutlined, FullscreenOutlined, ReloadOutlined, SignalFilled, TeamOutlined, WarningOutlined } from '@ant-design/icons' import api from '../../api/client' import { getSystemMetrics, type SystemMetrics } from '../../api/system' import { createRemoteSession, fetchStreams, fetchViewerSessions, sendRemoteCommand, type StreamInfo, type ViewerSessionInfo, } from '../../api/streams' const { Title, Text } = Typography interface NodeMetric { id: string name: string online: boolean metrics?: { cpu_percent?: number | null memory_percent?: number | null network_in_mbps?: number | null network_out_mbps?: number | null viewer_count?: number | null } | null } const formatBytes = (value = 0) => { const units = ['B', 'KB', 'MB', 'GB', 'TB'] let amount = Math.max(0, value) let index = 0 while (amount >= 1024 && index < units.length - 1) { amount /= 1024; index += 1 } return `${amount.toFixed(index ? 1 : 0)} ${units[index]}` } export default function LiveBoard() { const [streams, setStreams] = useState([]) const [streamId, setStreamId] = useState() const [sessions, setSessions] = useState([]) const [selected, setSelected] = useState([]) const [loading, setLoading] = useState(true) const [systemMetrics, setSystemMetrics] = useState(null) const [nodes, setNodes] = useState([]) const [quality, setQuality] = useState('auto') const [nodeId, setNodeId] = useState('auto') const [wallMode, setWallMode] = useState(false) async function load() { try { const programs = await fetchStreams(true) setStreams(programs) const nextId = streamId || programs.find(program => program.status === 'live')?.id if (nextId) { setStreamId(nextId) setSessions(await fetchViewerSessions(nextId)) } else { setSessions([]) } } catch { message.error('看板数据加载失败') } finally { try { setSystemMetrics(await getSystemMetrics()) } catch { setSystemMetrics(null) } try { const nodeResponse = await api.get('/nodes') setNodes(nodeResponse.data) } catch { setNodes([]) } setLoading(false) } } useEffect(() => { void load() const timer = window.setInterval(load, 5000) return () => window.clearInterval(timer) }, [streamId]) async function controlViewers() { if (!streamId || selected.length === 0) return try { const session = await createRemoteSession(streamId) const targets = selected.map(String) if (nodeId !== 'auto') await sendRemoteCommand(streamId, session.token, 'switch_node', targets, nodeId) if (quality !== 'auto') await sendRemoteCommand(streamId, session.token, 'switch_quality', targets, quality) if (nodeId === 'auto' && quality === 'auto') await sendRemoteCommand(streamId, session.token, 'restore_auto', targets) message.success(`已向 ${selected.length} 个观众下发节点与清晰度策略`) } catch { message.error('控制命令下发失败,请确认节目已启用审计式播放控制') } } const activeSessions = sessions.filter(item => Date.now() - new Date(item.last_heartbeat).getTime() < 30000) const buffering = activeSessions.filter(item => item.playback_state === 'buffering' || Number(item.buffered_seconds || 0) < 1) const totalCardsLoading = !systemMetrics return (
直播看板 实时检查节目、观众会话、线路与播放健康度
node.online).map(node => ({ value: node.id, label: `节点:${node.name}` }))]} />