Initial commit: LiveStream Platform

Backend: FastAPI + SQLAlchemy + SQLite
Frontend: React + Vite + Ant Design (serif Anthropic theme)
Media server: MediaMTX with edge node support
Load balancing: round-robin / weighted / least-conn
Multicast detection with automatic fallback
Transcoding support via FFmpeg (configurable quality)
hls.js player with line & quality switching
Admin panel with password auth
Windows compatible scripts
This commit is contained in:
admin committed 2026-08-07 15:02:07 +08:00
commit 2e4594fa77
43 files changed
+2196

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
import { useState, useEffect } from 'react'
import { Row, Col, Card, Statistic, Typography, Table, Tag, Space } from 'antd'
import { VideoCameraOutlined, EyeOutlined, CloudServerOutlined, SignalFilled } from '@ant-design/icons'
import { fetchStreams, type StreamInfo } from '../../api/streams'
const { Title } = Typography
export default function Dashboard() {
const [streams, setStreams] = useState<StreamInfo[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
loadData()
const timer = setInterval(loadData, 10000)
return () => clearInterval(timer)
}, [])
async function loadData() {
try {
const data = await fetchStreams()
setStreams(data)
} catch { /* ignore */ }
setLoading(false)
}
const liveCount = streams.filter((s) => s.status === 'live').length
const pendingCount = streams.filter((s) => s.status === 'pending').length
const endedCount = streams.filter((s) => s.status === 'ended').length
const statusMap: Record<string, { color: string; label: string }> = {
pending: { color: 'blue', label: 'Pending' },
live: { color: 'green', label: 'Live' },
ended: { color: 'default', label: 'Ended' },
cancelled: { color: 'red', label: 'Cancelled' },
}
const recentColumns = [
{ title: 'Title', dataIndex: 'title', key: 'title', ellipsis: true },
{
title: 'Status', dataIndex: 'status', key: 'status',
render: (status: string) => {
const s = statusMap[status] || { color: 'default', label: status }
return <Tag color={s.color}>{s.label}</Tag>
},
},
{ title: 'Created', dataIndex: 'created_at', key: 'created_at', render: (v: string) => v?.slice(0, 16) },
]
return (
<div>
<Title level={3} style={{ marginBottom: 24 }}>Dashboard</Title>
<Row gutter={[24, 24]}>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="Live Streams"
value={liveCount}
prefix={<SignalFilled style={{ color: '#52c41a' }} />}
loading={loading}
/>
</Card>
</Col>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="Pending"
value={pendingCount}
prefix={<VideoCameraOutlined style={{ color: '#1677ff' }} />}
loading={loading}
/>
</Card>
</Col>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="Total Streams"
value={streams.length}
prefix={<EyeOutlined />}
loading={loading}
/>
</Card>
</Col>
</Row>
<Card title="Recent Streams" style={{ marginTop: 24 }}>
<Table
dataSource={streams.slice(0, 10)}
columns={recentColumns}
rowKey="id"
loading={loading}
pagination={false}
size="middle"
/>
</Card>
</div>
)
}