index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /* eslint-disable @next/next/no-css-tags */
  2. import React, { useState, useEffect, useContext, ReactElement } from 'react';
  3. import { Skeleton, Card, Statistic, Row, Col } from 'antd';
  4. import { formatDistanceToNow, formatRelative } from 'date-fns';
  5. import dynamic from 'next/dynamic';
  6. import { ServerStatusContext } from '../../utils/server-status-context';
  7. import { LogTable } from '../../components/admin/LogTable';
  8. import { Offline } from '../../components/admin/Offline';
  9. import { StreamHealthOverview } from '../../components/admin/StreamHealthOverview';
  10. import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis';
  11. import { formatIPAddress, isEmptyObject } from '../../utils/format';
  12. import { NewsFeed } from '../../components/admin/NewsFeed';
  13. import { AdminLayout } from '../../components/layouts/AdminLayout';
  14. // Lazy loaded components
  15. const UserOutlined = dynamic(() => import('@ant-design/icons/UserOutlined'), {
  16. ssr: false,
  17. });
  18. const ClockCircleOutlined = dynamic(() => import('@ant-design/icons/ClockCircleOutlined'), {
  19. ssr: false,
  20. });
  21. function streamDetailsFormatter(streamDetails) {
  22. return (
  23. <ul className="statistics-list">
  24. <li>
  25. {streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps
  26. </li>
  27. <li>{streamDetails.framerate || 'Unknown'} fps</li>
  28. <li>
  29. {streamDetails.width} x {streamDetails.height}
  30. </li>
  31. </ul>
  32. );
  33. }
  34. export default function Home() {
  35. const serverStatusData = useContext(ServerStatusContext);
  36. const { broadcaster, serverConfig: configData } = serverStatusData || {};
  37. const { remoteAddr, streamDetails } = broadcaster || {};
  38. const encoder = streamDetails?.encoder || 'Unknown encoder';
  39. const [logsData, setLogs] = useState([]);
  40. const getLogs = async () => {
  41. try {
  42. const result = await fetchData(LOGS_WARN);
  43. setLogs(result);
  44. } catch (error) {
  45. console.log('==== error', error);
  46. }
  47. };
  48. const getMoreStats = () => {
  49. getLogs();
  50. };
  51. useEffect(() => {
  52. getMoreStats();
  53. let intervalId = null;
  54. intervalId = setInterval(getMoreStats, FETCH_INTERVAL);
  55. return () => {
  56. clearInterval(intervalId);
  57. };
  58. }, []);
  59. if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) {
  60. return (
  61. <>
  62. <Skeleton active />
  63. <Skeleton active />
  64. <Skeleton active />
  65. </>
  66. );
  67. }
  68. if (!broadcaster) {
  69. return <Offline logs={logsData} config={configData} />;
  70. }
  71. // map out settings
  72. const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map(setting => {
  73. const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting;
  74. const audioSetting = audioPassthrough
  75. ? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps`
  76. : `${audioBitrate || 'Unknown'} kbps`;
  77. const videoSetting = videoPassthrough
  78. ? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${
  79. streamDetails.width
  80. } x ${streamDetails.height}`
  81. : `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`;
  82. return (
  83. <div className="stream-details-item-container">
  84. <Statistic
  85. className="stream-details-item"
  86. title="Outbound Video Stream"
  87. value={videoSetting}
  88. />
  89. <Statistic
  90. className="stream-details-item"
  91. title="Outbound Audio Stream"
  92. value={audioSetting}
  93. />
  94. </div>
  95. );
  96. });
  97. // inbound
  98. const { viewerCount, sessionPeakViewerCount } = serverStatusData;
  99. const streamAudioDetailString = `${streamDetails.audioCodec}, ${
  100. streamDetails.audioBitrate || 'Unknown'
  101. } kbps`;
  102. const broadcastDate = new Date(broadcaster.time);
  103. return (
  104. <div className="home-container">
  105. <div className="sections-container">
  106. <div className="online-status-section">
  107. <Card size="small" type="inner" className="online-details-card">
  108. <Row gutter={[16, 16]} align="middle">
  109. <Col span={8} sm={24} md={8}>
  110. <Statistic
  111. title={`Stream started ${formatRelative(broadcastDate, Date.now())}`}
  112. value={formatDistanceToNow(broadcastDate)}
  113. prefix={<ClockCircleOutlined />}
  114. />
  115. </Col>
  116. <Col span={8} sm={24} md={8}>
  117. <Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} />
  118. </Col>
  119. <Col span={8} sm={24} md={8}>
  120. <Statistic
  121. title="Peak viewer count"
  122. value={sessionPeakViewerCount}
  123. prefix={<UserOutlined />}
  124. />
  125. </Col>
  126. </Row>
  127. <StreamHealthOverview />
  128. </Card>
  129. </div>
  130. <Row gutter={[16, 16]} className="section stream-details-section">
  131. <Col className="stream-details" span={12} sm={24} md={24} lg={12}>
  132. <Card
  133. size="small"
  134. title="Outbound Stream Details"
  135. type="inner"
  136. className="outbound-details"
  137. >
  138. {videoQualitySettings}
  139. </Card>
  140. <Card size="small" title="Inbound Stream Details" type="inner">
  141. <Statistic
  142. className="stream-details-item"
  143. title="Input"
  144. value={`${encoder} ${formatIPAddress(remoteAddr)}`}
  145. />
  146. <Statistic
  147. className="stream-details-item"
  148. title="Inbound Video Stream"
  149. value={streamDetails}
  150. formatter={streamDetailsFormatter}
  151. />
  152. <Statistic
  153. className="stream-details-item"
  154. title="Inbound Audio Stream"
  155. value={streamAudioDetailString}
  156. />
  157. </Card>
  158. </Col>
  159. <Col span={12} xs={24} sm={24} md={24} lg={12}>
  160. <NewsFeed />
  161. </Col>
  162. </Row>
  163. </div>
  164. <br />
  165. <LogTable logs={logsData} pageSize={5} />
  166. </div>
  167. );
  168. }
  169. Home.getLayout = function getLayout(page: ReactElement) {
  170. return <AdminLayout page={page} />;
  171. };