a bit of refactor, use context for overall broacast status; move files around for routing
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
|
||||
export default function BroadcastInfo(props) {
|
||||
const { remoteAddr, streamDetails, time } = props;
|
||||
|
||||
return (
|
||||
<div style={{border: '1px solid green', width: '100%'}}>
|
||||
<h2>Broadcast Info</h2>
|
||||
<p>Remote Address: {remoteAddr}</p>
|
||||
<p>Time: {(new Date(time)).toLocaleTimeString()}</p>
|
||||
<p>Stream Details: {JSON.stringify(streamDetails)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Table } from 'antd';
|
||||
|
||||
import { CONNECTED_CLIENTS, fetchData, FETCH_INTERVAL } from '../utils/apis';
|
||||
|
||||
/*
|
||||
geo data looks like this
|
||||
"geo": {
|
||||
"countryCode": "US",
|
||||
"regionName": "California",
|
||||
"timeZone": "America/Los_Angeles"
|
||||
}
|
||||
*/
|
||||
|
||||
export default function HardwareInfo() {
|
||||
const [clients, setClients] = useState([]);
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
const result = await fetchData(CONNECTED_CLIENTS);
|
||||
setClients(result);
|
||||
|
||||
} catch (error) {
|
||||
console.log("==== error", error)
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let getStatusIntervalId = null;
|
||||
|
||||
getInfo();
|
||||
getStatusIntervalId = setInterval(getInfo, FETCH_INTERVAL);
|
||||
|
||||
// returned function will be called on component unmount
|
||||
return () => {
|
||||
clearInterval(getStatusIntervalId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'User name',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
render: username => username || '-',
|
||||
sorter: (a, b) => a.username - b.username,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
},
|
||||
{
|
||||
title: 'Messages sent',
|
||||
dataIndex: 'messageCount',
|
||||
key: 'messageCount',
|
||||
sorter: (a, b) => a.messageCount - b.messageCount,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
},
|
||||
{
|
||||
title: 'Connected Time',
|
||||
dataIndex: 'connectedAt',
|
||||
key: 'connectedAt',
|
||||
render: time => (Date.now() - (new Date(time).getTime())) / 1000 / 60,
|
||||
},
|
||||
{
|
||||
title: 'User Agent',
|
||||
dataIndex: 'userAgent',
|
||||
key: 'userAgent',
|
||||
},
|
||||
{
|
||||
title: 'Location',
|
||||
dataIndex: 'geo',
|
||||
key: 'geo',
|
||||
render: geo => geo && `${geo.regionName}, ${geo.countryCode}`,
|
||||
},
|
||||
];
|
||||
|
||||
console.log({clients})
|
||||
return (
|
||||
<div>
|
||||
<h2>Connected Clients</h2>
|
||||
<Table dataSource={clients} columns={columns} />;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from '../utils/apis';
|
||||
|
||||
export default function HardwareInfo() {
|
||||
const [hardwareStatus, setHardwareStatus] = useState({});
|
||||
|
||||
const getHardwareStatus = async () => {
|
||||
try {
|
||||
const result = await fetchData(HARDWARE_STATS);
|
||||
console.log("hardare result", result)
|
||||
|
||||
setHardwareStatus({ ...result });
|
||||
|
||||
} catch (error) {
|
||||
setHardwareStatus({ ...hardwareStatus, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let getStatusIntervalId = null;
|
||||
|
||||
getHardwareStatus();
|
||||
getStatusIntervalId = setInterval(getHardwareStatus, FETCH_INTERVAL); //runs every 1 min.
|
||||
|
||||
// returned function will be called on component unmount
|
||||
return () => {
|
||||
clearInterval(getStatusIntervalId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Hardware Info</h2>
|
||||
<p>cpu:[], disk: [], memory: []; value = %age.</p>
|
||||
<p>the times should be the same for each, though milliseconds differ</p>
|
||||
<div style={{border: '1px solid blue', height: '300px', width: '100%', overflow:'auto'}}>
|
||||
{JSON.stringify(hardwareStatus)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import adminStyles from '../../styles/styles.module.css';
|
||||
|
||||
export default function Logo() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95.68623352050781 104.46271514892578" className={adminStyles.logoSVG}>
|
||||
<g transform="matrix(1 0 0 1 -37.08803939819336 -18.940391540527344)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g transform="matrix(1.0445680396949917 0 0 1.0445679172996596 36.34559138380523 18.877718021903796)">
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient120" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stopColor="#1f2022" stopOpacity="1"/>
|
||||
<stop offset="1" stopColor="#635e69" stopOpacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient120)" d="M91.5 75.35Q93.05 71.15 91.65 67.7 90.35 64.5 86.65 62.3 83.2 60.3 78.3 59.4 73.85 58.6 68.6 58.7 63.55 58.85 58.8 59.8 54.25 60.75 50.8 62.2 47.4 63.65 45.5 65.35 43.6 67.15 43.5 69.05 43.35 71.3 45.8 73.9 48.05 76.3 52.1 78.6 56.15 80.9 61.05 82.55 66.3 84.3 71.4 84.8 74.7 85.1 77.55 84.9 80.65 84.6 83.3 83.6 86.15 82.5 88.15 80.55 90.4 78.4 91.5 75.35M70.6 67.5Q72.3 68.4 73.1 69.7 73.9 71.15 73.45 73 73.1 74.3 72.3 75.25 71.55 76.1 70.3 76.6 69.25 77.05 67.75 77.25 66.3 77.4 64.85 77.3 62.3 77.15 59.25 76.3 56.6 75.5 54.15 74.3 51.9 73.2 50.45 72 49.05 70.75 49.1 69.8 49.2 69 50.25 68.25 51.3 67.55 53.15 67 55 66.4 57.25 66.1 59.8 65.8 62.1 65.8 64.65 65.85 66.7 66.2 68.9 66.65 70.6 67.5Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient121" gradientTransform="rotate(-180 .5 .5)">
|
||||
<stop offset="0" stopColor="#2087e2" stopOpacity="1"/>
|
||||
<stop offset="1" stopColor="#b63fff" stopOpacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient121)" d="M66.6 15.05Q66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.75 18.45 32.7 23.4 31.7 28.05 31.35 32.85 31.05 37.2 31.3 41.2 31.6 45.15 32.4 48.35 34 54.9 37.3 56.4 37.6 56.55 37.9 56.65L39.2 56.85Q39.45 56.85 39.95 56.8 42.05 56.6 44.7 55.05 47.25 53.5 50.05 50.8 53.05 47.9 55.85 44.05 58.8 40.05 61.1 35.6 63.8 30.35 65.25 25.3 66.75 19.75 66.6 15.05M47.55 23.15Q48.05 23.25 48.4 23.4 52.45 24.8 52.55 29.85 52.6 34 50 39.4 47.85 43.9 44.85 47.3 42.05 50.5 40.15 50.7L39.9 50.75 39.45 50.7 39.2 50.6Q37.8 49.95 37.25 46.35 36.7 42.7 37.3 38 37.95 32.75 39.75 28.8 41.9 24.1 45.05 23.25 45.6 23.1 45.85 23.1 46.25 23.05 46.65 23.05 47.05 23.05 47.55 23.15Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient122" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stopColor="#100f0f" stopOpacity="1"/>
|
||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient122)" d="M2.7 33.6Q2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7 0 42.6 2.2 47.2 4 51 8 54.35 11.55 57.3 16 59.15 20.5 61 23.85 60.85 24.5 60.85 25.25 60.7 26 60.55 26.5 60.3 27 60.05 27.45 59.65 27.9 59.25 28.15 58.75 29.35 56.45 27.5 51.65 25.6 47 21.75 42.1 17.75 37 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6M10.1 43.55Q10.35 43.1 10.6 42.85 10.85 42.6 11.2 42.4 11.6 42.25 11.9 42.2 13.5 41.9 15.95 43.6 18.15 45.05 20.35 47.7 22.35 50.1 23.55 52.4 24.7 54.75 24.25 55.7 24.15 55.9 24 56 23.85 56.2 23.65 56.25 23.55 56.35 23.25 56.4L22.7 56.5Q21.1 56.6 18.55 55.6 16.05 54.6 13.85 52.95 11.5 51.2 10.35 49.15 9.05 46.8 9.75 44.45 9.9 43.95 10.1 43.55Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient123" gradientTransform="rotate(-180 .5 .5)">
|
||||
<stop offset="0" stopColor="#222020" stopOpacity="1"/>
|
||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient123)" d="M34.95 74.2L34.75 74.2Q33.2 74.15 31.9 75.25 30.7 76.3 29.85 78.25 29.1 80 28.8 82.2 28.5 84.4 28.7 86.65 29.1 91.4 31.5 94.7 34.3 98.5 39.3 99.7L39.4 99.7 39.7 99.8 39.85 99.8Q45.3 100.85 47.15 97.75 48 96.3 48 94.05 47.95 91.9 47.2 89.35 46.45 86.75 45.1 84.15 43.75 81.5 42.05 79.35 40.25 77.1 38.45 75.75 36.55 74.35 34.95 74.2M33.55 80.4Q34.35 78.2 35.6 78.3L35.65 78.3Q36.9 78.45 38.6 80.9 40.3 83.35 41.15 86.05 42.1 89 41.55 90.75 40.9 92.6 38.35 92.25L38.3 92.25 38.25 92.2 38.1 92.2Q35.6 91.7 34.25 89.6 33.1 87.7 32.95 85 32.8 82.35 33.55 80.4Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.9999999999999999 0 0 1 0 5.684341886080802e-14)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient124" gradientTransform="rotate(-180 .5 .5)"> <stop offset="0" stopColor="#1e1c1c" stopOpacity="1"/>
|
||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient124)" d="M22.7 69.65Q22.25 69.3 21.6 69.05 20.95 68.8 20.25 68.7 19.6 68.55 18.85 68.5 16.7 68.45 14.65 69.15 12.65 69.8 11.4 71.1 10.15 72.5 10.2 74.2 10.25 76.05 11.95 78.2 12.4 78.75 13.05 79.4 13.55 79.9 14.2 80.3 14.7 80.6 15.3 80.85 16 81.1 16.4 81.1 18.2 81.35 19.9 80.35 21.55 79.4 22.75 77.65 24 75.85 24.3 73.95 24.6 71.85 23.55 70.5 23.15 70 22.7 69.65M21.7 71.7Q22.15 72.3 21.9 73.3 21.7 74.25 21 75.25 20.3 76.2 19.4 76.75 18.45 77.35 17.55 77.25L17 77.15Q16.7 77.05 16.45 76.85 16.25 76.75 15.9 76.45 15.7 76.25 15.4 75.9 14.5 74.75 14.7 73.8 14.8 72.95 15.75 72.3 16.6 71.7 17.8 71.4 19 71.1 20.1 71.15L20.65 71.2 21.1 71.3Q21.3 71.4 21.45 71.5L21.7 71.7Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient125" gradientTransform="rotate(-360 .5 .5)">
|
||||
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5"/>
|
||||
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient125)" d="M52.6 19.25Q59.6 19.25 66.2 20.95 66.7 17.8 66.6 15.05 66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.85 18.3 32.8 22.85 42.25 19.25 52.6 19.25Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient126" gradientTransform="rotate(-360 .5 .5)">
|
||||
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5"/>
|
||||
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient126)" d="M1.05 37.7Q0 42.6 2.2 47.2 2.95 48.8 4.05 50.25 7.55 41.65 14.4 34.75 14 34.45 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6 2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1.219512230276127 0 0 1.2195122143630526 32.82519274395008 88.56945194723018)">
|
||||
<path fill="#000000" fillOpacity="1" d=""/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Layout, Menu } from 'antd';
|
||||
import {
|
||||
SettingOutlined,
|
||||
HomeOutlined,
|
||||
LineChartOutlined,
|
||||
CloseCircleOutlined,
|
||||
PlayCircleFilled,
|
||||
} from '@ant-design/icons';
|
||||
import classNames from 'classNames';
|
||||
|
||||
|
||||
import OwncastLogo from './logo';
|
||||
import { BroadcastStatusContext } from '../utils/broadcast-status-context';
|
||||
|
||||
import adminStyles from '../../styles/styles.module.css';
|
||||
|
||||
export default function MainLayout(props) {
|
||||
const { children } = props;
|
||||
|
||||
const context = useContext(BroadcastStatusContext);
|
||||
const { broadcastActive } = context || {};
|
||||
|
||||
const router = useRouter();
|
||||
const { route } = router || {};
|
||||
|
||||
const { Header, Footer, Content, Sider } = Layout;
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
const statusMessage = broadcastActive ?
|
||||
'Online' : 'Offline';
|
||||
|
||||
const appClass = classNames({
|
||||
'owncast-layout': true,
|
||||
[adminStyles.online]: broadcastActive,
|
||||
})
|
||||
return (
|
||||
<Layout className={appClass}>
|
||||
<Sider
|
||||
width={240}
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[route.substring(1)]}
|
||||
defaultOpenKeys={['current-stream-menu', 'utilities-menu']}
|
||||
mode="inline"
|
||||
>
|
||||
<h1 className={adminStyles.owncastTitleContainer}>
|
||||
<span className={adminStyles.logoContainer}>
|
||||
<OwncastLogo />
|
||||
</span>
|
||||
<span className={adminStyles.owncastTitle}>Owncast Admin</span>
|
||||
</h1>
|
||||
<Menu.Item key="home" icon={<HomeOutlined />}>
|
||||
<Link href="/index2">Home</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<SubMenu key="current-stream-menu" icon={<LineChartOutlined />} title="Stream Details">
|
||||
<Menu.Item key="hardware-info">
|
||||
<Link href="/hardware-info">Hardware</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="broadcast-info">
|
||||
<Link href="/broadcast-info">Broadcaster Info</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="viewer-info">
|
||||
<Link href="/viewer-info">Viewers</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="connected-clients">
|
||||
<Link href="/connected-clients">Connected Clients</Link>
|
||||
</Menu.Item>
|
||||
{ broadcastActive ? (
|
||||
<Menu.Item key="disconnect-stream" icon={<CloseCircleOutlined />}>
|
||||
<Link href="/disconnect-stream">Disconnect Stream...</Link>
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
</SubMenu>
|
||||
|
||||
<SubMenu key="utilities-menu" icon={<SettingOutlined />} title="Utilities">
|
||||
<Menu.Item key="update-server-config">
|
||||
<Link href="/update-server-config">Update Server Configuration</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="update-stream-key">
|
||||
<Link href="/update-stream-key">Change Stream Key</Link>
|
||||
</Menu.Item>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
</Sider>
|
||||
|
||||
<Layout>
|
||||
<Header className={adminStyles.header}>
|
||||
<div className={adminStyles.statusIndicatorContainer}>
|
||||
<span className={adminStyles.statusIcon}>
|
||||
<PlayCircleFilled />
|
||||
</span>
|
||||
<span className={adminStyles.statusLabel}>
|
||||
{statusMessage}
|
||||
</span>
|
||||
</div>
|
||||
</Header>
|
||||
<Content className={adminStyles.contentMain}>
|
||||
{children}
|
||||
</Content>
|
||||
|
||||
<Footer style={{ textAlign: 'center' }}><a href="https://owncast.online/">About Owncast</a></Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
MainLayout.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {timeFormat} from 'd3-time-format';
|
||||
import { LineChart, XAxis, YAxis, Line, Tooltip } from 'recharts';
|
||||
import { VIEWERS_OVER_TIME, fetchData } from '../utils/apis';
|
||||
|
||||
const FETCH_INTERVAL = 5 * 60 * 1000; // 5 mins
|
||||
|
||||
export default function ViewersOverTime() {
|
||||
const [viewerInfo, setViewerInfo] = useState([]);
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
const result = await fetchData(VIEWERS_OVER_TIME);
|
||||
setViewerInfo(result);
|
||||
} catch (error) {
|
||||
console.log("==== error", error)
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let getStatusIntervalId = null;
|
||||
|
||||
getInfo();
|
||||
getStatusIntervalId = setInterval(getInfo, FETCH_INTERVAL);
|
||||
|
||||
// returned function will be called on component unmount
|
||||
return () => {
|
||||
clearInterval(getStatusIntervalId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const timeFormatter = (tick) => {return timeFormat('%H:%M:%S')(new Date(tick));};
|
||||
|
||||
const CustomizedTooltip = (props) => {
|
||||
const { active, payload, label } = props;
|
||||
if (active) {
|
||||
const numViewers = payload && payload[0] && payload[0].value;
|
||||
const time = timeFormatter(label);
|
||||
const message = `${numViewers} viewer(s) at ${time}`;
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
<p className="label">{message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Current Viewers</h2>
|
||||
<div className="chart-container">
|
||||
<LineChart width={800} height={400} data={viewerInfo}>
|
||||
<XAxis dataKey="time" tickFormatter={timeFormatter}/>
|
||||
<YAxis dataKey="value"/>
|
||||
<Tooltip
|
||||
content={<CustomizedTooltip />}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="value"
|
||||
stroke="#ff84d8"
|
||||
dot={{ stroke: 'red', strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user