mirror of
				https://github.com/owncast/owncast.git
				synced 2025-10-31 18:18:06 +08:00 
			
		
		
		
	 cb387d88be
			
		
	
	cb387d88be
	
	
	
		
			
			* First pass at configuring localization * Add CI job for translations * Update CI job * Update default value * Update parser config * Update defaults again * try to fix the multiple parsing of a file * Update crowdlin config * Update configs * New Crowdin translations by GitHub Action (#3448) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Point to updated translated files * Tooltip i18n * Run translation job when web components are updated * Commit updated translations * Translations update (#3453) * Update source file strings.json Updated translations * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (English, United States) Updated translations * Commit updated translations * New Crowdin translations by GitHub Action (#3452) Co-authored-by: Owncast <owncast@owncast.online> * chore(deps): update to next config to address build errors * New Crowdin translations by GitHub Action (#3455) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Translations update (#3456) * New translations strings.json (Arabic) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (Greek) Updated translations * New translations strings.json (Irish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Korean) Updated translations * New translations strings.json (Dutch) Updated translations * New translations strings.json (Norwegian) Updated translations * New translations strings.json (Punjabi) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Swedish) Updated translations * New translations strings.json (Chinese Traditional) Updated translations * New translations strings.json (Vietnamese) Updated translations * New translations strings.json (Bengali) Updated translations * New translations strings.json (Thai) Updated translations * New translations strings.json (Croatian) Updated translations * New translations strings.json (Hindi) Updated translations * New translations strings.json (Malay) Updated translations * New Crowdin translations by GitHub Action (#3457) * New translations strings.json (Arabic) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (Greek) Updated translations * New translations strings.json (Irish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Korean) Updated translations * New translations strings.json (Dutch) Updated translations * New translations strings.json (Norwegian) Updated translations * New translations strings.json (Punjabi) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Swedish) Updated translations * New translations strings.json (Chinese Traditional) Updated translations * New translations strings.json (Vietnamese) Updated translations * New translations strings.json (Bengali) Updated translations * New translations strings.json (Thai) Updated translations * New translations strings.json (Croatian) Updated translations * New translations strings.json (Hindi) Updated translations * New translations strings.json (Malay) Updated translations * New Crowdin translations by GitHub Action --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Commit updated API documentation * Update translations job * New Crowdin translations by GitHub Action (#3698) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Update Crowdin configuration file * Translations update (#3700) * New translations strings.json (French) Updated translations * New translations strings.json (Italian) Updated translations * Translations update (#3699) * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Polish) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Portuguese, Brazilian) Updated translations * Commit updated API documentation --------- Co-authored-by: Owncast <owncast@owncast.online> * New Crowdin translations by GitHub Action (#3701) * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Polish) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Portuguese, Brazilian) Updated translations * New Crowdin translations by GitHub Action --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Draft: Mark strings for translation. (#3458) * Mark strings for translation. * Mark up strings for translation * fix(web): fix linter warnings --------- Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org> Co-authored-by: Gabe Kangas <gabek@real-ity.com> * do not pull from cowdin via workflow * Commit updated translations * feat: add translations support to admin pages and components (#3977) * feat: add translations support to admin pages and components Added translations support admin main page and its components, help page, handware-info page. Added translations support for LogTable, NewsFeed and StreamHealthOverview components. * update package.json * fix rendering issue * Commit updated API documentation --------- Co-authored-by: Owncast <owncast@owncast.online> Co-authored-by: Gabe Kangas <gabek@real-ity.com> * Offline banner i18n formatting (#3997) * Fix "Last live ago" string formatting with i18n interpolation * Change some base translation jsons to use i18n interpolation * Linting fix * chore(js): ignore i18n pkgs in knip * fix(test): fix browser ui test * fix(js): remove unused var --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: Owncast <owncast@owncast.online> Co-authored-by: taintedcypher <119351153+taintedcypher@users.noreply.github.com> Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org> Co-authored-by: Sufyaan Khateeb <81009832+SufyaanKhateeb@users.noreply.github.com> Co-authored-by: mahmed2000 <mahmad2000@protonmail.com>
		
			
				
	
	
		
			201 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /* eslint-disable @next/next/no-css-tags */
 | |
| import React, { useState, useEffect, useContext, ReactElement } from 'react';
 | |
| import { Skeleton, Card, Statistic, Row, Col } from 'antd';
 | |
| import { formatDistanceToNow, formatRelative } from 'date-fns';
 | |
| import dynamic from 'next/dynamic';
 | |
| import { useTranslation } from 'next-export-i18n';
 | |
| import { ServerStatusContext } from '../../utils/server-status-context';
 | |
| import { LogTable } from '../../components/admin/LogTable';
 | |
| import { Offline } from '../../components/admin/Offline';
 | |
| import { StreamHealthOverview } from '../../components/admin/StreamHealthOverview';
 | |
| 
 | |
| import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis';
 | |
| import { formatIPAddress, isEmptyObject } from '../../utils/format';
 | |
| import { NewsFeed } from '../../components/admin/NewsFeed';
 | |
| 
 | |
| import { AdminLayout } from '../../components/layouts/AdminLayout';
 | |
| 
 | |
| // Lazy loaded components
 | |
| 
 | |
| const UserOutlined = dynamic(() => import('@ant-design/icons/UserOutlined'), {
 | |
|   ssr: false,
 | |
| });
 | |
| 
 | |
| const ClockCircleOutlined = dynamic(() => import('@ant-design/icons/ClockCircleOutlined'), {
 | |
|   ssr: false,
 | |
| });
 | |
| 
 | |
| 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 { t } = useTranslation();
 | |
| 
 | |
|   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={t('Outbound Video Stream')}
 | |
|           value={videoSetting}
 | |
|         />
 | |
|         <Statistic
 | |
|           className="stream-details-item"
 | |
|           title={t('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={`${t('Stream started')} ${formatRelative(broadcastDate, Date.now())}`}
 | |
|                   value={formatDistanceToNow(broadcastDate)}
 | |
|                   prefix={<ClockCircleOutlined />}
 | |
|                 />
 | |
|               </Col>
 | |
|               <Col span={8} sm={24} md={8}>
 | |
|                 <Statistic title={t('Viewers')} value={viewerCount} prefix={<UserOutlined />} />
 | |
|               </Col>
 | |
|               <Col span={8} sm={24} md={8}>
 | |
|                 <Statistic
 | |
|                   title={t('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={t('Outbound Stream Details')}
 | |
|               type="inner"
 | |
|               className="outbound-details"
 | |
|             >
 | |
|               {videoQualitySettings}
 | |
|             </Card>
 | |
| 
 | |
|             <Card size="small" title={t('Inbound Stream Details')} type="inner">
 | |
|               <Statistic
 | |
|                 className="stream-details-item"
 | |
|                 title={t('Input')}
 | |
|                 value={`${encoder} ${formatIPAddress(remoteAddr)}`}
 | |
|               />
 | |
|               <Statistic
 | |
|                 className="stream-details-item"
 | |
|                 title={t('Inbound Video Stream')}
 | |
|                 value={streamDetails}
 | |
|                 formatter={streamDetailsFormatter}
 | |
|               />
 | |
|               <Statistic
 | |
|                 className="stream-details-item"
 | |
|                 title={t('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} initialPageSize={5} />
 | |
|     </div>
 | |
|   );
 | |
| }
 | |
| 
 | |
| Home.getLayout = function getLayout(page: ReactElement) {
 | |
|   return <AdminLayout page={page} />;
 | |
| };
 |