Replace broadcaster API call with generic server status call. Add upgrade check bool
This commit is contained in:
parent
940cc1da71
commit
c4351a53bf
@ -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>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 || {};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
55
web/utils/server-status-context.tsx
Normal file
55
web/utils/server-status-context.tsx
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user