29 changed files with 323 additions and 281 deletions
@ -0,0 +1,11 @@ |
|||||||
|
import { AppProps } from 'next/app'; |
||||||
|
|
||||||
|
function SimpleLayout({ Component, pageProps }: AppProps) { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<Component {...pageProps} /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default SimpleLayout; |
||||||
@ -1,6 +1,5 @@ |
|||||||
const withLess = require('next-with-less'); |
const withLess = require('next-with-less'); |
||||||
|
|
||||||
module.exports = withLess({ |
module.exports = withLess({ |
||||||
basePath: '/admin', |
|
||||||
trailingSlash: true, |
trailingSlash: true, |
||||||
}); |
}); |
||||||
|
|||||||
@ -1,15 +1,15 @@ |
|||||||
import { DeleteOutlined } from '@ant-design/icons'; |
import { DeleteOutlined } from '@ant-design/icons'; |
||||||
import { Button, Checkbox, Input, Modal, Space, Table, Typography } from 'antd'; |
import { Button, Checkbox, Input, Modal, Space, Table, Typography } from 'antd'; |
||||||
import React, { useContext, useEffect, useState } from 'react'; |
import React, { useContext, useEffect, useState } from 'react'; |
||||||
import FormStatusIndicator from '../components/config/form-status-indicator'; |
import FormStatusIndicator from '../../components/config/form-status-indicator'; |
||||||
import { |
import { |
||||||
API_EXTERNAL_ACTIONS, |
API_EXTERNAL_ACTIONS, |
||||||
postConfigUpdateToAPI, |
postConfigUpdateToAPI, |
||||||
RESET_TIMEOUT, |
RESET_TIMEOUT, |
||||||
} from '../utils/config-constants'; |
} from '../../utils/config-constants'; |
||||||
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../utils/input-statuses'; |
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses'; |
||||||
import { ServerStatusContext } from '../utils/server-status-context'; |
import { ServerStatusContext } from '../../utils/server-status-context'; |
||||||
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../utils/urls'; |
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls'; |
||||||
|
|
||||||
const { Title, Paragraph } = Typography; |
const { Title, Paragraph } = Typography; |
||||||
let resetTimer = null; |
let resetTimer = null; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
import { AppProps } from 'next/app'; |
||||||
|
import ServerStatusProvider from '../../utils/server-status-context'; |
||||||
|
import AlertMessageProvider from '../../utils/alert-message-context'; |
||||||
|
import MainLayout from '../../components/main-layout'; |
||||||
|
|
||||||
|
function AdminLayout({ Component, pageProps }: AppProps) { |
||||||
|
return ( |
||||||
|
<ServerStatusProvider> |
||||||
|
<AlertMessageProvider> |
||||||
|
<MainLayout> |
||||||
|
<Component {...pageProps} /> |
||||||
|
</MainLayout> |
||||||
|
</AlertMessageProvider> |
||||||
|
</ServerStatusProvider> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default AdminLayout; |
||||||
@ -1,16 +1,16 @@ |
|||||||
import React, { useState, useEffect, useContext } from 'react'; |
import React, { useState, useEffect, useContext } from 'react'; |
||||||
import { Tabs } from 'antd'; |
import { Tabs } from 'antd'; |
||||||
import { ServerStatusContext } from '../../utils/server-status-context'; |
import { ServerStatusContext } from '../../../utils/server-status-context'; |
||||||
import { |
import { |
||||||
CONNECTED_CLIENTS, |
CONNECTED_CLIENTS, |
||||||
fetchData, |
fetchData, |
||||||
DISABLED_USERS, |
DISABLED_USERS, |
||||||
MODERATORS, |
MODERATORS, |
||||||
BANNED_IPS, |
BANNED_IPS, |
||||||
} from '../../utils/apis'; |
} from '../../../utils/apis'; |
||||||
import UserTable from '../../components/user-table'; |
import UserTable from '../../../components/user-table'; |
||||||
import ClientTable from '../../components/client-table'; |
import ClientTable from '../../../components/client-table'; |
||||||
import BannedIPsTable from '../../components/banned-ips-table'; |
import BannedIPsTable from '../../../components/banned-ips-table'; |
||||||
|
|
||||||
const { TabPane } = Tabs; |
const { TabPane } = Tabs; |
||||||
|
|
||||||
@ -1,11 +1,11 @@ |
|||||||
import React from 'react'; |
import React from 'react'; |
||||||
import { Typography } from 'antd'; |
import { Typography } from 'antd'; |
||||||
|
|
||||||
import EditInstanceDetails from '../components/config/edit-instance-details'; |
import EditInstanceDetails from '../../components/config/edit-instance-details'; |
||||||
import EditInstanceTags from '../components/config/edit-tags'; |
import EditInstanceTags from '../../components/config/edit-tags'; |
||||||
import EditSocialLinks from '../components/config/edit-social-links'; |
import EditSocialLinks from '../../components/config/edit-social-links'; |
||||||
import EditPageContent from '../components/config/edit-page-content'; |
import EditPageContent from '../../components/config/edit-page-content'; |
||||||
import EditCustomStyles from '../components/config/edit-custom-css'; |
import EditCustomStyles from '../../components/config/edit-custom-css'; |
||||||
|
|
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
|
|
||||||
@ -1,6 +1,6 @@ |
|||||||
import React from 'react'; |
import React from 'react'; |
||||||
import { Typography } from 'antd'; |
import { Typography } from 'antd'; |
||||||
import EditServerDetails from '../components/config/edit-server-details'; |
import EditServerDetails from '../../components/config/edit-server-details'; |
||||||
|
|
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
|
|
||||||
@ -1,6 +1,6 @@ |
|||||||
import React from 'react'; |
import React from 'react'; |
||||||
import { Typography } from 'antd'; |
import { Typography } from 'antd'; |
||||||
import EditSocialLinks from '../components/config/edit-social-links'; |
import EditSocialLinks from '../../components/config/edit-social-links'; |
||||||
|
|
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
|
|
||||||
@ -1,6 +1,6 @@ |
|||||||
import { Typography } from 'antd'; |
import { Typography } from 'antd'; |
||||||
import React from 'react'; |
import React from 'react'; |
||||||
import EditStorage from '../components/config/edit-storage'; |
import EditStorage from '../../components/config/edit-storage'; |
||||||
|
|
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
|
|
||||||
@ -1,8 +1,8 @@ |
|||||||
import { Col, Collapse, Row, Typography } from 'antd'; |
import { Col, Collapse, Row, Typography } from 'antd'; |
||||||
import React from 'react'; |
import React from 'react'; |
||||||
import VideoCodecSelector from '../components/config/video-codec-selector'; |
import VideoCodecSelector from '../../components/config/video-codec-selector'; |
||||||
import VideoLatency from '../components/config/video-latency'; |
import VideoLatency from '../../components/config/video-latency'; |
||||||
import VideoVariantsTable from '../components/config/video-variants-table'; |
import VideoVariantsTable from '../../components/config/video-variants-table'; |
||||||
|
|
||||||
const { Panel } = Collapse; |
const { Panel } = Collapse; |
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
@ -1,9 +1,9 @@ |
|||||||
import { BulbOutlined, LaptopOutlined, SaveOutlined } from '@ant-design/icons'; |
import { BulbOutlined, LaptopOutlined, SaveOutlined } from '@ant-design/icons'; |
||||||
import { Row, Col, Typography } from 'antd'; |
import { Row, Col, Typography } from 'antd'; |
||||||
import React, { useEffect, useState } from 'react'; |
import React, { useEffect, useState } from 'react'; |
||||||
import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../utils/apis'; |
import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../../utils/apis'; |
||||||
import Chart from '../components/chart'; |
import Chart from '../../components/chart'; |
||||||
import StatisticItem from '../components/statistic'; |
import StatisticItem from '../../components/statistic'; |
||||||
|
|
||||||
// TODO: FIX TS WARNING FROM THIS.
|
// TODO: FIX TS WARNING FROM THIS.
|
||||||
// interface TimedValue {
|
// interface TimedValue {
|
||||||
@ -0,0 +1,180 @@ |
|||||||
|
import React, { useState, useEffect, useContext } from 'react'; |
||||||
|
import { Skeleton, Card, Statistic, Row, Col } from 'antd'; |
||||||
|
import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons'; |
||||||
|
import { formatDistanceToNow, formatRelative } from 'date-fns'; |
||||||
|
import { ServerStatusContext } from '../../utils/server-status-context'; |
||||||
|
import LogTable from '../../components/log-table'; |
||||||
|
import Offline from '../../components/offline-notice'; |
||||||
|
import StreamHealthOverview from '../../components/stream-health-overview'; |
||||||
|
|
||||||
|
import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis'; |
||||||
|
import { formatIPAddress, isEmptyObject } from '../../utils/format'; |
||||||
|
import NewsFeed from '../../components/news-feed'; |
||||||
|
|
||||||
|
function streamDetailsFormatter(streamDetails) { |
||||||
|
return ( |
||||||
|
<ul className="statistics-list"> |
||||||
|
<li> |
||||||
|
{streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps |
||||||
|
</li> |
||||||
|
<li>{streamDetails.framerate || 'Unknown'} fps</li> |
||||||
|
<li> |
||||||
|
{streamDetails.width} x {streamDetails.height} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default function Home() { |
||||||
|
const serverStatusData = useContext(ServerStatusContext); |
||||||
|
const { broadcaster, serverConfig: configData } = serverStatusData || {}; |
||||||
|
const { remoteAddr, streamDetails } = broadcaster || {}; |
||||||
|
|
||||||
|
const encoder = streamDetails?.encoder || 'Unknown encoder'; |
||||||
|
|
||||||
|
const [logsData, setLogs] = useState([]); |
||||||
|
const getLogs = async () => { |
||||||
|
try { |
||||||
|
const result = await fetchData(LOGS_WARN); |
||||||
|
setLogs(result); |
||||||
|
} catch (error) { |
||||||
|
console.log('==== error', error); |
||||||
|
} |
||||||
|
}; |
||||||
|
const getMoreStats = () => { |
||||||
|
getLogs(); |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getMoreStats(); |
||||||
|
|
||||||
|
let intervalId = null; |
||||||
|
intervalId = setInterval(getMoreStats, FETCH_INTERVAL); |
||||||
|
|
||||||
|
return () => { |
||||||
|
clearInterval(intervalId); |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<Skeleton active /> |
||||||
|
<Skeleton active /> |
||||||
|
<Skeleton active /> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (!broadcaster) { |
||||||
|
return <Offline logs={logsData} config={configData} />; |
||||||
|
} |
||||||
|
|
||||||
|
// map out settings
|
||||||
|
const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map(setting => { |
||||||
|
const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting; |
||||||
|
|
||||||
|
const audioSetting = audioPassthrough |
||||||
|
? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps` |
||||||
|
: `${audioBitrate || 'Unknown'} kbps`; |
||||||
|
|
||||||
|
const videoSetting = videoPassthrough |
||||||
|
? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${ |
||||||
|
streamDetails.width |
||||||
|
} x ${streamDetails.height}` |
||||||
|
: `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="stream-details-item-container"> |
||||||
|
<Statistic |
||||||
|
className="stream-details-item" |
||||||
|
title="Outbound Video Stream" |
||||||
|
value={videoSetting} |
||||||
|
/> |
||||||
|
<Statistic |
||||||
|
className="stream-details-item" |
||||||
|
title="Outbound Audio Stream" |
||||||
|
value={audioSetting} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
// inbound
|
||||||
|
const { viewerCount, sessionPeakViewerCount } = serverStatusData; |
||||||
|
|
||||||
|
const streamAudioDetailString = `${streamDetails.audioCodec}, ${ |
||||||
|
streamDetails.audioBitrate || 'Unknown' |
||||||
|
} kbps`;
|
||||||
|
|
||||||
|
const broadcastDate = new Date(broadcaster.time); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="home-container"> |
||||||
|
<div className="sections-container"> |
||||||
|
<div className="online-status-section"> |
||||||
|
<Card size="small" type="inner" className="online-details-card"> |
||||||
|
<Row gutter={[16, 16]} align="middle"> |
||||||
|
<Col span={8} sm={24} md={8}> |
||||||
|
<Statistic |
||||||
|
title={`Stream started ${formatRelative(broadcastDate, Date.now())}`} |
||||||
|
value={formatDistanceToNow(broadcastDate)} |
||||||
|
prefix={<ClockCircleOutlined />} |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
<Col span={8} sm={24} md={8}> |
||||||
|
<Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} /> |
||||||
|
</Col> |
||||||
|
<Col span={8} sm={24} md={8}> |
||||||
|
<Statistic |
||||||
|
title="Peak viewer count" |
||||||
|
value={sessionPeakViewerCount} |
||||||
|
prefix={<UserOutlined />} |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
<StreamHealthOverview /> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Row gutter={[16, 16]} className="section stream-details-section"> |
||||||
|
<Col className="stream-details" span={12} sm={24} md={24} lg={12}> |
||||||
|
<Card |
||||||
|
size="small" |
||||||
|
title="Outbound Stream Details" |
||||||
|
type="inner" |
||||||
|
className="outbound-details" |
||||||
|
> |
||||||
|
{videoQualitySettings} |
||||||
|
</Card> |
||||||
|
|
||||||
|
<Card size="small" title="Inbound Stream Details" type="inner"> |
||||||
|
<Statistic |
||||||
|
className="stream-details-item" |
||||||
|
title="Input" |
||||||
|
value={`${encoder} ${formatIPAddress(remoteAddr)}`} |
||||||
|
/> |
||||||
|
<Statistic |
||||||
|
className="stream-details-item" |
||||||
|
title="Inbound Video Stream" |
||||||
|
value={streamDetails} |
||||||
|
formatter={streamDetailsFormatter} |
||||||
|
/> |
||||||
|
<Statistic |
||||||
|
className="stream-details-item" |
||||||
|
title="Inbound Audio Stream" |
||||||
|
value={streamAudioDetailString} |
||||||
|
/> |
||||||
|
</Card> |
||||||
|
</Col> |
||||||
|
|
||||||
|
<Col span={12} xs={24} sm={24} md={24} lg={12}> |
||||||
|
<NewsFeed /> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
<br /> |
||||||
|
<LogTable logs={logsData} pageSize={5} /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
@ -1,7 +1,7 @@ |
|||||||
import React, { useState, useEffect } from 'react'; |
import React, { useState, useEffect } from 'react'; |
||||||
import LogTable from '../components/log-table'; |
import LogTable from '../../components/log-table'; |
||||||
|
|
||||||
import { LOGS_ALL, fetchData } from '../utils/apis'; |
import { LOGS_ALL, fetchData } from '../../utils/apis'; |
||||||
|
|
||||||
const FETCH_INTERVAL = 5 * 1000; // 5 sec
|
const FETCH_INTERVAL = 5 * 1000; // 5 sec
|
||||||
|
|
||||||
@ -1,7 +1,7 @@ |
|||||||
import React, { useState, useEffect } from 'react'; |
import React, { useState, useEffect } from 'react'; |
||||||
import ReactMarkdown from 'react-markdown'; |
import ReactMarkdown from 'react-markdown'; |
||||||
import { Table, Typography } from 'antd'; |
import { Table, Typography } from 'antd'; |
||||||
import { getGithubRelease } from '../utils/apis'; |
import { getGithubRelease } from '../../utils/apis'; |
||||||
|
|
||||||
const { Title } = Typography; |
const { Title } = Typography; |
||||||
|
|
||||||
@ -1,180 +1,8 @@ |
|||||||
import React, { useState, useEffect, useContext } from 'react'; |
|
||||||
import { Skeleton, Card, Statistic, Row, Col } from 'antd'; |
|
||||||
import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons'; |
|
||||||
import { formatDistanceToNow, formatRelative } from 'date-fns'; |
|
||||||
import { ServerStatusContext } from '../utils/server-status-context'; |
|
||||||
import LogTable from '../components/log-table'; |
|
||||||
import Offline from '../components/offline-notice'; |
|
||||||
import StreamHealthOverview from '../components/stream-health-overview'; |
|
||||||
|
|
||||||
import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../utils/apis'; |
|
||||||
import { formatIPAddress, isEmptyObject } from '../utils/format'; |
|
||||||
import NewsFeed from '../components/news-feed'; |
|
||||||
|
|
||||||
function streamDetailsFormatter(streamDetails) { |
|
||||||
return ( |
|
||||||
<ul className="statistics-list"> |
|
||||||
<li> |
|
||||||
{streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps |
|
||||||
</li> |
|
||||||
<li>{streamDetails.framerate || 'Unknown'} fps</li> |
|
||||||
<li> |
|
||||||
{streamDetails.width} x {streamDetails.height} |
|
||||||
</li> |
|
||||||
</ul> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
export default function Home() { |
export default function Home() { |
||||||
const serverStatusData = useContext(ServerStatusContext); |
|
||||||
const { broadcaster, serverConfig: configData } = serverStatusData || {}; |
|
||||||
const { remoteAddr, streamDetails } = broadcaster || {}; |
|
||||||
|
|
||||||
const encoder = streamDetails?.encoder || 'Unknown encoder'; |
|
||||||
|
|
||||||
const [logsData, setLogs] = useState([]); |
|
||||||
const getLogs = async () => { |
|
||||||
try { |
|
||||||
const result = await fetchData(LOGS_WARN); |
|
||||||
setLogs(result); |
|
||||||
} catch (error) { |
|
||||||
console.log('==== error', error); |
|
||||||
} |
|
||||||
}; |
|
||||||
const getMoreStats = () => { |
|
||||||
getLogs(); |
|
||||||
}; |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
getMoreStats(); |
|
||||||
|
|
||||||
let intervalId = null; |
|
||||||
intervalId = setInterval(getMoreStats, FETCH_INTERVAL); |
|
||||||
|
|
||||||
return () => { |
|
||||||
clearInterval(intervalId); |
|
||||||
}; |
|
||||||
}, []); |
|
||||||
|
|
||||||
if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) { |
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Skeleton active /> |
|
||||||
<Skeleton active /> |
|
||||||
<Skeleton active /> |
|
||||||
</> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if (!broadcaster) { |
|
||||||
return <Offline logs={logsData} config={configData} />; |
|
||||||
} |
|
||||||
|
|
||||||
// map out settings
|
|
||||||
const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map(setting => { |
|
||||||
const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting; |
|
||||||
|
|
||||||
const audioSetting = audioPassthrough |
|
||||||
? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps` |
|
||||||
: `${audioBitrate || 'Unknown'} kbps`; |
|
||||||
|
|
||||||
const videoSetting = videoPassthrough |
|
||||||
? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${ |
|
||||||
streamDetails.width |
|
||||||
} x ${streamDetails.height}` |
|
||||||
: `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`; |
|
||||||
|
|
||||||
return ( |
return ( |
||||||
<div className="stream-details-item-container"> |
<div> |
||||||
<Statistic |
This is where v2 of the Owncast web UI will be built. Begin with the layout component |
||||||
className="stream-details-item" |
https://ant.design/components/layout/ and edit pages/index.tsx.
|
||||||
title="Outbound Video Stream" |
|
||||||
value={videoSetting} |
|
||||||
/> |
|
||||||
<Statistic |
|
||||||
className="stream-details-item" |
|
||||||
title="Outbound Audio Stream" |
|
||||||
value={audioSetting} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}); |
|
||||||
|
|
||||||
// inbound
|
|
||||||
const { viewerCount, sessionPeakViewerCount } = serverStatusData; |
|
||||||
|
|
||||||
const streamAudioDetailString = `${streamDetails.audioCodec}, ${ |
|
||||||
streamDetails.audioBitrate || 'Unknown' |
|
||||||
} kbps`;
|
|
||||||
|
|
||||||
const broadcastDate = new Date(broadcaster.time); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="home-container"> |
|
||||||
<div className="sections-container"> |
|
||||||
<div className="online-status-section"> |
|
||||||
<Card size="small" type="inner" className="online-details-card"> |
|
||||||
<Row gutter={[16, 16]} align="middle"> |
|
||||||
<Col span={8} sm={24} md={8}> |
|
||||||
<Statistic |
|
||||||
title={`Stream started ${formatRelative(broadcastDate, Date.now())}`} |
|
||||||
value={formatDistanceToNow(broadcastDate)} |
|
||||||
prefix={<ClockCircleOutlined />} |
|
||||||
/> |
|
||||||
</Col> |
|
||||||
<Col span={8} sm={24} md={8}> |
|
||||||
<Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} /> |
|
||||||
</Col> |
|
||||||
<Col span={8} sm={24} md={8}> |
|
||||||
<Statistic |
|
||||||
title="Peak viewer count" |
|
||||||
value={sessionPeakViewerCount} |
|
||||||
prefix={<UserOutlined />} |
|
||||||
/> |
|
||||||
</Col> |
|
||||||
</Row> |
|
||||||
<StreamHealthOverview /> |
|
||||||
</Card> |
|
||||||
</div> |
|
||||||
|
|
||||||
<Row gutter={[16, 16]} className="section stream-details-section"> |
|
||||||
<Col className="stream-details" span={12} sm={24} md={24} lg={12}> |
|
||||||
<Card |
|
||||||
size="small" |
|
||||||
title="Outbound Stream Details" |
|
||||||
type="inner" |
|
||||||
className="outbound-details" |
|
||||||
> |
|
||||||
{videoQualitySettings} |
|
||||||
</Card> |
|
||||||
|
|
||||||
<Card size="small" title="Inbound Stream Details" type="inner"> |
|
||||||
<Statistic |
|
||||||
className="stream-details-item" |
|
||||||
title="Input" |
|
||||||
value={`${encoder} ${formatIPAddress(remoteAddr)}`} |
|
||||||
/> |
|
||||||
<Statistic |
|
||||||
className="stream-details-item" |
|
||||||
title="Inbound Video Stream" |
|
||||||
value={streamDetails} |
|
||||||
formatter={streamDetailsFormatter} |
|
||||||
/> |
|
||||||
<Statistic |
|
||||||
className="stream-details-item" |
|
||||||
title="Inbound Audio Stream" |
|
||||||
value={streamAudioDetailString} |
|
||||||
/> |
|
||||||
</Card> |
|
||||||
</Col> |
|
||||||
|
|
||||||
<Col span={12} xs={24} sm={24} md={24} lg={12}> |
|
||||||
<NewsFeed /> |
|
||||||
</Col> |
|
||||||
</Row> |
|
||||||
</div> |
|
||||||
<br /> |
|
||||||
<LogTable logs={logsData} pageSize={5} /> |
|
||||||
</div> |
</div> |
||||||
); |
); |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue