From a0628567267d200fca579a5511a33663e9c4a297 Mon Sep 17 00:00:00 2001 From: Ginger Wong Date: Thu, 22 Oct 2020 16:18:18 -0700 Subject: [PATCH] a bit of refactor, use context for overall broacast status; move files around for routing --- web/favicon.ico | Bin 0 -> 3991 bytes web/package.json | 1 + web/pages/_app.tsx | 20 +-- web/pages/broadcast-info.tsx | 18 +++ web/pages/components/broadcast-info.tsx | 15 --- web/pages/components/logo.tsx | 85 +++++++++++++ web/pages/components/main-layout.tsx | 119 ++++++++++++++++++ .../{components => }/connected-clients.tsx | 3 +- web/pages/{components => }/hardware-info.tsx | 2 +- web/pages/home.tsx | 12 -- web/pages/index2.tsx | 45 +------ web/pages/update-server-config.tsx | 40 ++++++ web/pages/utils/broadcast-status-context.tsx | 51 ++++++++ web/pages/{components => }/viewer-info.tsx | 5 +- web/styles/styles.module.css | 69 ++++++++++ 15 files changed, 406 insertions(+), 79 deletions(-) create mode 100644 web/favicon.ico create mode 100644 web/pages/broadcast-info.tsx delete mode 100644 web/pages/components/broadcast-info.tsx create mode 100644 web/pages/components/logo.tsx create mode 100644 web/pages/components/main-layout.tsx rename web/pages/{components => }/connected-clients.tsx (94%) rename web/pages/{components => }/hardware-info.tsx (93%) delete mode 100644 web/pages/home.tsx create mode 100644 web/pages/update-server-config.tsx create mode 100644 web/pages/utils/broadcast-status-context.tsx rename web/pages/{components => }/viewer-info.tsx (96%) create mode 100644 web/styles/styles.module.css diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a859e7ee041c7be65e53c2aa45b1cac0b530aa8 GIT binary patch literal 3991 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 + return ( + + + + + + + ) } -export default App \ No newline at end of file +export default App; \ No newline at end of file diff --git a/web/pages/broadcast-info.tsx b/web/pages/broadcast-info.tsx new file mode 100644 index 000000000..465b48de5 --- /dev/null +++ b/web/pages/broadcast-info.tsx @@ -0,0 +1,18 @@ +import React, { useContext } from 'react'; +import { BroadcastStatusContext } from './utils/broadcast-status-context'; + + +export default function BroadcastInfo() { + const context = useContext(BroadcastStatusContext); + const { broadcaster } = context || {}; + const { remoteAddr, time, streamDetails } = broadcaster || {}; + + return ( +
+

Broadcast Info

+

Remote Address: {remoteAddr}

+

Time: {(new Date(time)).toLocaleTimeString()}

+

Stream Details: {JSON.stringify(streamDetails)}

+
+ ); +} diff --git a/web/pages/components/broadcast-info.tsx b/web/pages/components/broadcast-info.tsx deleted file mode 100644 index bf68c888d..000000000 --- a/web/pages/components/broadcast-info.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { useState, useEffect } from 'react'; - - -export default function BroadcastInfo(props) { -const { remoteAddr, streamDetails, time } = props; - - return ( -
-

Broadcast Info

-

Remote Address: {remoteAddr}

-

Time: {(new Date(time)).toLocaleTimeString()}

-

Stream Details: {JSON.stringify(streamDetails)}

-
- ); -} diff --git a/web/pages/components/logo.tsx b/web/pages/components/logo.tsx new file mode 100644 index 000000000..a6a814185 --- /dev/null +++ b/web/pages/components/logo.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import adminStyles from '../../styles/styles.module.css'; + +export default function Logo() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx new file mode 100644 index 000000000..72bdd59de --- /dev/null +++ b/web/pages/components/main-layout.tsx @@ -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 ( + + + +

+ + + + Owncast Admin +

+ }> + Home + + + } title="Stream Details"> + + Hardware + + + Broadcaster Info + + + Viewers + + + Connected Clients + + { broadcastActive ? ( + }> + Disconnect Stream... + + ) : null} + + + } title="Utilities"> + + Update Server Configuration + + + Change Stream Key + + +
+
+ + +
+
+ + + + + {statusMessage} + +
+
+ + {children} + + + +
+
+ ); +} + +MainLayout.propTypes = { + children: PropTypes.element.isRequired, +}; \ No newline at end of file diff --git a/web/pages/components/connected-clients.tsx b/web/pages/connected-clients.tsx similarity index 94% rename from web/pages/components/connected-clients.tsx rename to web/pages/connected-clients.tsx index 5a3e8d29b..312cbc9cc 100644 --- a/web/pages/components/connected-clients.tsx +++ b/web/pages/connected-clients.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Table } from 'antd'; -import { CONNECTED_CLIENTS, fetchData, FETCH_INTERVAL } from '../utils/apis'; +import { CONNECTED_CLIENTS, fetchData, FETCH_INTERVAL } from './utils/apis'; /* geo data looks like this @@ -71,7 +71,6 @@ export default function HardwareInfo() { }, ]; - console.log({clients}) return (

Connected Clients

diff --git a/web/pages/components/hardware-info.tsx b/web/pages/hardware-info.tsx similarity index 93% rename from web/pages/components/hardware-info.tsx rename to web/pages/hardware-info.tsx index 919489c2e..d92876499 100644 --- a/web/pages/components/hardware-info.tsx +++ b/web/pages/hardware-info.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from '../utils/apis'; +import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from './utils/apis'; export default function HardwareInfo() { const [hardwareStatus, setHardwareStatus] = useState({}); diff --git a/web/pages/home.tsx b/web/pages/home.tsx deleted file mode 100644 index 1609d3893..000000000 --- a/web/pages/home.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import adminStyles from './components/styles.module.css'; - - -export default function HomeView(props) { - return ( -
- < pick something -
- ); -} diff --git a/web/pages/index2.tsx b/web/pages/index2.tsx index fdb9060ae..58dc825d7 100644 --- a/web/pages/index2.tsx +++ b/web/pages/index2.tsx @@ -1,43 +1,10 @@ -import React, { useState, useEffect } from 'react'; -import { BROADCASTER, fetchData, FETCH_INTERVAL } from './utils/apis'; -import MainLayout from './components/main-layout'; -import Home from './home'; +import React from 'react'; -export default function Admin() { - const [broadcasterStatus, setBroadcasterStatus] = useState({}); - const [count, setCount] = useState(0); - - const getBroadcastStatus = async () => { - try { - const result = await fetchData(BROADCASTER); - const broadcastActive = !!result.broadcaster; - - console.log("====",{count, result}) - - setBroadcasterStatus({ ...result, broadcastActive }); - setCount(c => c + 1); - - } catch (error) { - setBroadcasterStatus({ ...broadcasterStatus, message: error.message }); - } - }; - - useEffect(() => { - let getStatusIntervalId = null; - - getBroadcastStatus(); - getStatusIntervalId = setInterval(getBroadcastStatus, FETCH_INTERVAL); - - // returned function will be called on component unmount - return () => { - clearInterval(getStatusIntervalId); - } - }, []) - - +export default function AdminHome() { return ( - - - +
+ < pick something
+ Home view. pretty pictures. Rainbows. Kittens. +
); } diff --git a/web/pages/update-server-config.tsx b/web/pages/update-server-config.tsx new file mode 100644 index 000000000..7aa101dd0 --- /dev/null +++ b/web/pages/update-server-config.tsx @@ -0,0 +1,40 @@ +import React, { useState, useEffect } from 'react'; +import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from './utils/apis'; + +export default function ServerConfig() { + const [clients, setClients] = useState({}); + + const getInfo = async () => { + try { + const result = await fetchData(SERVER_CONFIG); + console.log("viewers result", result) + + setClients({ ...result }); + + } catch (error) { + setClients({ ...clients, message: error.message }); + } + }; + + useEffect(() => { + let getStatusIntervalId = null; + + getInfo(); + getStatusIntervalId = setInterval(getInfo, FETCH_INTERVAL); + + // returned function will be called on component unmount + return () => { + clearInterval(getStatusIntervalId); + } + }, []); + + return ( +
+

Server Config

+

Display this data all pretty, most things will be editable in the future, not now.

+
+ {JSON.stringify(clients)} +
+
+ ); +} diff --git a/web/pages/utils/broadcast-status-context.tsx b/web/pages/utils/broadcast-status-context.tsx new file mode 100644 index 000000000..86e595b24 --- /dev/null +++ b/web/pages/utils/broadcast-status-context.tsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +import { BROADCASTER, fetchData, FETCH_INTERVAL } from './apis'; + +const initialState = { + broadcastActive: false, + message: '', + broadcaster: null, +}; + +export const BroadcastStatusContext = React.createContext(initialState); + +const BroadcastStatusProvider = ({ children }) => { + const [broadcasterStatus, setBroadcasterStatus] = useState(initialState); + + const getBroadcastStatus = async () => { + try { + const result = await fetchData(BROADCASTER); + const broadcastActive = !!result.broadcaster; + setBroadcasterStatus({ ...result, broadcastActive }); + + } catch (error) { + setBroadcasterStatus({ ...broadcasterStatus, message: error.message }); + } + }; + + useEffect(() => { + let getStatusIntervalId = null; + + getBroadcastStatus(); + getStatusIntervalId = setInterval(getBroadcastStatus, FETCH_INTERVAL); + + // returned function will be called on component unmount + return () => { + clearInterval(getStatusIntervalId); + } + }, []) + + return ( + + {children} + + ); +} + +BroadcastStatusProvider.propTypes = { + children: PropTypes.element.isRequired, +}; + +export default BroadcastStatusProvider; \ No newline at end of file diff --git a/web/pages/components/viewer-info.tsx b/web/pages/viewer-info.tsx similarity index 96% rename from web/pages/components/viewer-info.tsx rename to web/pages/viewer-info.tsx index 1446585fd..e63806a03 100644 --- a/web/pages/components/viewer-info.tsx +++ b/web/pages/viewer-info.tsx @@ -1,7 +1,8 @@ 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'; + +import { VIEWERS_OVER_TIME, fetchData } from './utils/apis'; const FETCH_INTERVAL = 5 * 60 * 1000; // 5 mins @@ -65,8 +66,6 @@ export default function ViewersOverTime() { />
- - ); } diff --git a/web/styles/styles.module.css b/web/styles/styles.module.css new file mode 100644 index 000000000..0b59be3fb --- /dev/null +++ b/web/styles/styles.module.css @@ -0,0 +1,69 @@ + +.logoSVG { + height: 2rem; + width: 2rem; +} + +.owncastTitleContainer { + padding: 1rem; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + +} +.logoContainer { + background-color: #fff; + padding: .35rem; + border-radius: 9999px; +} +.owncastTitle { + display: inline-block; + margin-left: 1rem; + color: rgba(203,213,224, 1); + font-size: 1.15rem; + font-weight: 200; + text-transform: uppercase; + line-height: normal; + letter-spacing: .05em; +} + +.contentMain { + padding: 3em; +} + +.header { + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +.statusIndicatorContainer { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + +} +.statusIcon { + font-size: 1.5rem; +} +.statusIcon svg { + fill: #ccc; +} +.statusLabel { + color: #fff; + text-transform: uppercase; + font-size: .75rem; + display: inline-block; + margin-left: .5rem; + color: #ccc; +} +.online .statusIcon svg { + fill: #52c41a; +} +.online .statusLabel { + color: #52c41a; +} + +/* //844-227-3943 */ \ No newline at end of file