0

Replace broadcaster API call with generic server status call. Add upgrade check bool

This commit is contained in:
Gabe Kangas 2020-11-05 18:30:14 -08:00
parent 940cc1da71
commit c4351a53bf
9 changed files with 113 additions and 112 deletions

View File

@ -2,17 +2,17 @@ import 'antd/dist/antd.compact.css';
import "../styles/globals.scss"; import "../styles/globals.scss";
import { AppProps } from 'next/app'; import { AppProps } from 'next/app';
import BroadcastStatusProvider from '../utils/broadcast-status-context'; import ServerStatusProvider from '../utils/server-status-context';
import MainLayout from './components/main-layout'; import MainLayout from './components/main-layout';
function App({ Component, pageProps }: AppProps) { function App({ Component, pageProps }: AppProps) {
return ( return (
<BroadcastStatusProvider> <ServerStatusProvider>
<MainLayout> <MainLayout>
<Component {...pageProps} /> <Component {...pageProps} />
</MainLayout> </MainLayout>
</BroadcastStatusProvider> </ServerStatusProvider>
) )
} }

View File

@ -1,9 +1,9 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { BroadcastStatusContext } from '../utils/broadcast-status-context'; import { ServerStatusContext } from '../utils/server-status-context';
export default function BroadcastInfo() { export default function BroadcastInfo() {
const context = useContext(BroadcastStatusContext); const context = useContext(ServerStatusContext);
const { broadcaster } = context || {}; const { broadcaster } = context || {};
const { remoteAddr, time, streamDetails } = broadcaster || {}; const { remoteAddr, time, streamDetails } = broadcaster || {};

View File

@ -18,15 +18,17 @@ import { upgradeVersionAvailable } from "../../utils/apis";
import { parseSecondsToDurationString } from '../../utils/format' import { parseSecondsToDurationString } from '../../utils/format'
import OwncastLogo from './logo'; import OwncastLogo from './logo';
import { BroadcastStatusContext } from '../../utils/broadcast-status-context'; import { ServerStatusContext } from '../../utils/server-status-context';
import adminStyles from '../../styles/styles.module.css'; import adminStyles from '../../styles/styles.module.css';
let performedUpgradeCheck = false;
export default function MainLayout(props) { export default function MainLayout(props) {
const { children } = props; const { children } = props;
const context = useContext(BroadcastStatusContext); const context = useContext(ServerStatusContext);
const { broadcastActive, broadcaster } = context || {}; const { online, broadcaster, versionNumber } = context || {};
const router = useRouter(); const router = useRouter();
const { route } = router || {}; const { route } = router || {};
@ -34,19 +36,15 @@ export default function MainLayout(props) {
const { Header, Footer, Content, Sider } = Layout; const { Header, Footer, Content, Sider } = Layout;
const { SubMenu } = Menu; const { SubMenu } = Menu;
const streamDurationString = broadcastActive ? const streamDurationString = online ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : "";
parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : ""
const statusIcon = broadcastActive ? const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />;
<PlayCircleFilled /> : <MinusSquareFilled />; const statusMessage = online ? `Online ${streamDurationString}` : "Offline";
const statusMessage = broadcastActive
? `Online ${ streamDurationString}`
: "Offline";
const [upgradeVersion, setUpgradeVersion] = useState(null); const [upgradeVersion, setUpgradeVersion] = useState(null);
const checkForUpgrade = async () => { const checkForUpgrade = async () => {
try { try {
const result = await upgradeVersionAvailable(); const result = await upgradeVersionAvailable(versionNumber);
setUpgradeVersion(result); setUpgradeVersion(result);
} catch (error) { } catch (error) {
console.log("==== error", error); console.log("==== error", error);
@ -54,13 +52,17 @@ export default function MainLayout(props) {
}; };
useEffect(() => { useEffect(() => {
checkForUpgrade(); if (!performedUpgradeCheck && !context.disableUpgradeChecks) {
}, []); checkForUpgrade();
console.log('checking')
performedUpgradeCheck = true
}
});
const appClass = classNames({ const appClass = classNames({
'owncast-layout': true, "owncast-layout": true,
[adminStyles.online]: broadcastActive, [adminStyles.online]: online,
}) });
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none'; const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
const upgradeVersionString = upgradeVersion || ''; const upgradeVersionString = upgradeVersion || '';
@ -98,7 +100,7 @@ export default function MainLayout(props) {
<Menu.Item key="viewer-info"> <Menu.Item key="viewer-info">
<Link href="/viewer-info">Viewers</Link> <Link href="/viewer-info">Viewers</Link>
</Menu.Item> </Menu.Item>
{broadcastActive ? ( {online ? (
<Menu.Item key="disconnect-stream" icon={<CloseCircleOutlined />}> <Menu.Item key="disconnect-stream" icon={<CloseCircleOutlined />}>
<Link href="/disconnect-stream">Disconnect Stream...</Link> <Link href="/disconnect-stream">Disconnect Stream...</Link>
</Menu.Item> </Menu.Item>
@ -151,7 +153,7 @@ export default function MainLayout(props) {
<Content className={adminStyles.contentMain}>{children}</Content> <Content className={adminStyles.contentMain}>{children}</Content>
<Footer style={{ textAlign: "center" }}> <Footer style={{ textAlign: "center" }}>
<a href="https://owncast.online/">About Owncast</a> <a href="https://owncast.online/">About Owncast v{versionNumber}</a>
</Footer> </Footer>
</Layout> </Layout>
</Layout> </Layout>

View File

@ -11,12 +11,12 @@ import React, { useState, useEffect, useContext } from "react";
import { Row, Skeleton, Result, List, Typography, Card } from "antd"; import { Row, Skeleton, Result, List, Typography, Card } from "antd";
import { UserOutlined, ClockCircleOutlined } from "@ant-design/icons"; import { UserOutlined, ClockCircleOutlined } from "@ant-design/icons";
import { formatDistanceToNow, formatRelative } from "date-fns"; import { formatDistanceToNow, formatRelative } from "date-fns";
import { BroadcastStatusContext } from "../utils/broadcast-status-context"; import { ServerStatusContext } from "../utils/server-status-context";
import StatisticItem from "./components/statistic" import StatisticItem from "./components/statistic"
import LogTable from "./components/log-table"; import LogTable from "./components/log-table";
import { import {
STREAM_STATUS, STATUS,
SERVER_CONFIG, SERVER_CONFIG,
LOGS_WARN, LOGS_WARN,
fetchData, fetchData,
@ -82,7 +82,7 @@ function Offline() {
} }
export default function Stats() { export default function Stats() {
const context = useContext(BroadcastStatusContext); const context = useContext(ServerStatusContext);
const { broadcaster } = context || {}; const { broadcaster } = context || {};
const { remoteAddr, streamDetails } = broadcaster || {}; const { remoteAddr, streamDetails } = broadcaster || {};
@ -90,7 +90,7 @@ export default function Stats() {
const [stats, setStats] = useState(null); const [stats, setStats] = useState(null);
const getStats = async () => { const getStats = async () => {
try { try {
const result = await fetchData(STREAM_STATUS); const result = await fetchData(STATUS);
setStats(result); setStats(result);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -182,7 +182,7 @@ export default function Stats() {
}); });
const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null
const { viewerCount, sessionMaxViewerCount, lastConnectTime } = stats; const { viewerCount, sessionMaxViewerCount } = stats;
const streamVideoDetailString = `${streamDetails.videoCodec} ${streamDetails.videoBitrate} kbps ${streamDetails.width}x${streamDetails.height}`; const streamVideoDetailString = `${streamDetails.videoCodec} ${streamDetails.videoBitrate} kbps ${streamDetails.width}x${streamDetails.height}`;
const streamAudioDetailString = `${streamDetails.audioCodec} ${streamDetails.audioBitrate} kpbs`; const streamAudioDetailString = `${streamDetails.audioCodec} ${streamDetails.audioBitrate} kpbs`;
@ -192,10 +192,10 @@ export default function Stats() {
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<StatisticItem <StatisticItem
title={`Stream started ${formatRelative( title={`Stream started ${formatRelative(
new Date(lastConnectTime), new Date(broadcaster.time),
new Date() new Date()
)}`} )}`}
value={formatDistanceToNow(new Date(lastConnectTime))} value={formatDistanceToNow(new Date(broadcaster.time))}
prefix={<ClockCircleOutlined />} prefix={<ClockCircleOutlined />}
/> />
<StatisticItem <StatisticItem

View File

@ -1,8 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { Table, Tag } from "antd"; import { Table, Typography } from "antd";
import { getGithubRelease } from "../utils/apis"; import { getGithubRelease } from "../utils/apis";
const { Title } = Typography;
export default function Logs() { export default function Logs() {
const [release, setRelease] = useState({ const [release, setRelease] = useState({
html_url: "", html_url: "",
@ -32,13 +34,11 @@ export default function Logs() {
return ( return (
<div> <div>
<h2> <Title level={2}>
<a href={release.html_url}>{release.name}</a> <a href={release.html_url}>{release.name}</a>
</h2> </Title>
<h1>{release.created_at}</h1> <Title level={5}>{new Date(release.created_at).toDateString()}</Title>
<ReactMarkdown>{release.body}</ReactMarkdown>; <ReactMarkdown>{release.body}</ReactMarkdown>;<h3>Downloads</h3>
<h3>Downloads</h3>
<AssetTable {...release.assets} /> <AssetTable {...release.assets} />
</div> </div>
); );

View File

@ -7,23 +7,27 @@ import { SortOrder } from "antd/lib/table/interface";
import Chart from "./components/chart"; import Chart from "./components/chart";
import StatisticItem from "./components/statistic"; import StatisticItem from "./components/statistic";
import { BroadcastStatusContext } from '../utils/broadcast-status-context'; import { ServerStatusContext } from '../utils/server-status-context';
import { import {
CONNECTED_CLIENTS, CONNECTED_CLIENTS,
STREAM_STATUS, VIEWERS_OVER_TIME, VIEWERS_OVER_TIME,
fetchData, fetchData,
} from "../utils/apis"; } from "../utils/apis";
const FETCH_INTERVAL = 5 * 60 * 1000; // 5 mins const FETCH_INTERVAL = 5 * 60 * 1000; // 5 mins
export default function ViewersOverTime() { export default function ViewersOverTime() {
const context = useContext(BroadcastStatusContext); const context = useContext(ServerStatusContext);
const { broadcastActive } = context || {}; const {
broadcastActive,
viewerCount,
overallPeakViewerCount,
sessionPeakViewerCount,
} = context || {};
const [viewerInfo, setViewerInfo] = useState([]); const [viewerInfo, setViewerInfo] = useState([]);
const [clients, setClients] = useState([]); const [clients, setClients] = useState([]);
const [stats, setStats] = useState(null);
const getInfo = async () => { const getInfo = async () => {
try { try {
@ -39,14 +43,6 @@ export default function ViewersOverTime() {
} catch (error) { } catch (error) {
console.log("==== error", error); console.log("==== error", error);
} }
try {
const result = await fetchData(STREAM_STATUS);
setStats(result);
} catch (error) {
console.log(error);
}
}; };
useEffect(() => { useEffect(() => {
@ -111,12 +107,17 @@ export default function ViewersOverTime() {
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<StatisticItem <StatisticItem
title="Current viewers" title="Current viewers"
value={stats?.viewerCount ?? ""} value={viewerCount.toString()}
prefix={<UserOutlined />} prefix={<UserOutlined />}
/> />
<StatisticItem <StatisticItem
title="Peak viewers this session" title="Peak viewers this session"
value={stats?.sessionMaxViewerCount ?? ""} value={sessionPeakViewerCount.toString()}
prefix={<UserOutlined />}
/>
<StatisticItem
title="Peak viewers overall"
value={overallPeakViewerCount.toString()}
prefix={<UserOutlined />} prefix={<UserOutlined />}
/> />
</Row> </Row>

View File

@ -8,7 +8,7 @@ const API_LOCATION = `${NEXT_PUBLIC_API_HOST}api/admin/`;
export const FETCH_INTERVAL = 15000; export const FETCH_INTERVAL = 15000;
// Current inbound broadcaster info // Current inbound broadcaster info
export const BROADCASTER = `${API_LOCATION}broadcaster`; export const STATUS = `${API_LOCATION}status`;
// Disconnect inbound stream // Disconnect inbound stream
export const DISCONNECT = `${API_LOCATION}disconnect`; export const DISCONNECT = `${API_LOCATION}disconnect`;
@ -36,11 +36,6 @@ export const LOGS_WARN = `${API_LOCATION}logs/warnings`;
const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest";
// Current Stream status.
// This is literally the same as /api/status except it supports
// auth.
export const STREAM_STATUS = `${API_LOCATION}status`;
export async function fetchData(url) { export async function fetchData(url) {
const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`);
@ -81,8 +76,7 @@ export async function getGithubRelease() {
// Make a request to the server status API and the Github releases API // Make a request to the server status API and the Github releases API
// and return a release if it's newer than the server version. // and return a release if it's newer than the server version.
export async function upgradeVersionAvailable() { export async function upgradeVersionAvailable(currentVersion) {
const serverVersion = await fetchData(STREAM_STATUS)
const recentRelease = await getGithubRelease(); const recentRelease = await getGithubRelease();
let recentReleaseVersion = recentRelease.tag_name; let recentReleaseVersion = recentRelease.tag_name;
@ -90,7 +84,7 @@ export async function upgradeVersionAvailable() {
recentReleaseVersion = recentReleaseVersion.substr(1) recentReleaseVersion = recentReleaseVersion.substr(1)
} }
if (!upToDate(serverVersion.versionNumber, recentReleaseVersion)) { if (!upToDate(currentVersion, recentReleaseVersion)) {
return recentReleaseVersion return recentReleaseVersion
} }

View File

@ -1,51 +0,0 @@
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 || result.success;
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 (
<BroadcastStatusContext.Provider value={broadcasterStatus}>
{children}
</BroadcastStatusContext.Provider>
);
}
BroadcastStatusProvider.propTypes = {
children: PropTypes.element.isRequired,
};
export default BroadcastStatusProvider;

View File

@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { STATUS, fetchData, FETCH_INTERVAL } from './apis';
const initialState = {
broadcastActive: false,
broadcaster: null,
online: false,
viewerCount: 0,
sessionPeakViewerCount: 0,
overallPeakViewerCount: 0,
disableUpgradeChecks: true,
versionNumber: '0.0.0',
};
export const ServerStatusContext = React.createContext(initialState);
const ServerStatusProvider = ({ children }) => {
const [status, setStatus] = useState(initialState);
const getStatus = async () => {
try {
const result = await fetchData(STATUS);
setStatus({ ...result });
} catch (error) {
// setBroadcasterStatus({ ...broadcasterStatus, message: error.message });
}
};
useEffect(() => {
let getStatusIntervalId = null;
getStatus();
getStatusIntervalId = setInterval(getStatus, FETCH_INTERVAL);
// returned function will be called on component unmount
return () => {
clearInterval(getStatusIntervalId);
}
}, [])
return (
<ServerStatusContext.Provider value={status}>
{children}
</ServerStatusContext.Provider>
);
}
ServerStatusProvider.propTypes = {
children: PropTypes.element.isRequired,
};
export default ServerStatusProvider;