diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 000000000..7a859e7ee Binary files /dev/null and b/web/favicon.ico differ 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