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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 N%AT%%F6*2Ung9`H6kY%T literal 0 HcmV?d00001 diff --git a/web/package.json b/web/package.json index bf39b247f..834ef4456 100644 --- a/web/package.json +++ b/web/package.json @@ -10,6 +10,7 @@ "dependencies": { "@ant-design/icons": "^4.2.2", "antd": "^4.6.6", + "classnames": "^2.2.6", "d3-scale": "^3.2.3", "d3-time-format": "^3.0.0", "next": "9.5.3", diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index f1a4e0eb9..958b72559 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -1,15 +1,21 @@ -// import 'antd/dist/antd.css'; -// import '../styles/globals.scss' - import 'antd/dist/antd.dark.css'; import 'antd/dist/antd.compact.css'; - import "../styles/globals.scss"; -import { AppProps } from 'next/app' +import { AppProps } from 'next/app'; +import BroadcastStatusProvider from './utils/broadcast-status-context'; +import MainLayout from './components/main-layout'; + function App({ Component, pageProps }: AppProps) { - return + 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