diff --git a/web/pages/components/chart.tsx b/web/pages/components/chart.tsx index 3412331c9..9ccfd6880 100644 --- a/web/pages/components/chart.tsx +++ b/web/pages/components/chart.tsx @@ -1,27 +1,43 @@ import { LineChart, XAxis, YAxis, Line, Tooltip, Legend } from "recharts"; import { timeFormat } from "d3-time-format"; -export default function Chart({ data, color, unit }) { - const CustomizedTooltip = (props) => { - const { active, payload } = props; - if (active && payload && payload[0]) { - const time = payload[0].payload - ? timeFormat("%I:%M")(new Date(payload[0].payload.time), { - nearestTo: 1, - }) - : ""; - return ( -
-

- {time} {payload[0].payload.value} % -

-
- ); - } - return null; - }; +interface ToolTipProps { + active?: boolean, + payload?: object, +}; +const defaultProps = { + active: false, + payload: {}, +}; - const timeFormatter = (tick) => { +interface ChartProps { + data: number, + color: string, + unit: string, +} + +function CustomizedTooltip(props: ToolTipProps) { + const { active, payload } = props; + if (active && payload && payload[0]) { + const time = payload[0].payload + ? timeFormat("%I:%M")(new Date(payload[0].payload.time), { + nearestTo: 1, + }) + : ""; + return ( +
+

+ {time} {payload[0].payload.value} % +

+
+ ); + } + return null; +} +CustomizedTooltip.defaultProps = defaultProps; + +export default function Chart({ data, color, unit }: ChartProps) { + const timeFormatter = (tick: string) => { return timeFormat("%I:%M")(new Date(tick), { nearestTo: 1, }); diff --git a/web/pages/hardware-info.tsx b/web/pages/hardware-info.tsx index 76ac091e4..8a5cf2ccd 100644 --- a/web/pages/hardware-info.tsx +++ b/web/pages/hardware-info.tsx @@ -1,10 +1,14 @@ import React, { useState, useEffect } from 'react'; -import { timeFormat } from "d3-time-format"; import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from './utils/apis'; -import Chart from './components/chart.tsx' +import Chart from './components/chart'; export default function HardwareInfo() { - const [hardwareStatus, setHardwareStatus] = useState({}); + const [hardwareStatus, setHardwareStatus] = useState({ + cpu: 0, + memory: 0, + disk: 0, + message: '', + }); const getHardwareStatus = async () => { try { @@ -29,7 +33,6 @@ export default function HardwareInfo() { }, []); - if (!hardwareStatus.cpu) { return null; } @@ -61,6 +64,4 @@ export default function HardwareInfo() { ); - - } \ No newline at end of file diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 6e5e18476..4705ec1c6 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -18,11 +18,18 @@ import { fetchData, FETCH_INTERVAL, } from "./utils/apis"; -import { formatIPAddress } from "./utils/format"; +import { formatIPAddress, isEmptyObject } from "./utils/format"; -const { Title} = Typography; +const { Title } = Typography; -function Item(title: string, value: string, prefix: Jsx.Element) { +interface ItemProps { + title: string, + value: string, + prefix: JSX.Element, +}; + +function Item(props: ItemProps) { + const { title, value, prefix } = props; const valueStyle = { color: "#334", fontSize: "1.8rem" }; return ( @@ -39,6 +46,19 @@ function Item(title: string, value: string, prefix: Jsx.Element) { ); } +function Offline() { + return ( +
+ There is no stream currently active. Start one. + } + /> +
+ ); +} + export default function Stats() { const context = useContext(BroadcastStatusContext); const { broadcaster } = context || {}; @@ -61,7 +81,8 @@ export default function Stats() { const getConfig = async () => { try { const result = await fetchData(SERVER_CONFIG); - setVideoSettings(result.videoSettings.videoQualityVariants); + const variants = result && result.videoSettings && result.videoSettings.videoQualityVariants; + setVideoSettings(variants); } catch (error) { console.log(error); } @@ -73,7 +94,7 @@ export default function Stats() { getConfig(); }, []); - if (!stats) { + if (!stats || isEmptyObject(stats)) { return (
@@ -84,10 +105,10 @@ export default function Stats() { } if (!broadcaster) { - return Offline(); + return ; } - const videoQualitySettings = videoSettings.map(function (setting, index) { + const videoQualitySettings = videoSettings.map((setting, index) => { const audioSetting = setting.audioPassthrough || setting.audioBitrate === 0 ? `${streamDetails.audioBitrate} kpbs (passthrough)` @@ -95,13 +116,21 @@ export default function Stats() { return ( - {Item("Output", `Video variant ${index}`, "")} - {Item( - "Outbound Video Stream", - `${setting.videoBitrate} kbps ${setting.framerate} fps`, - "" - )} - {Item("Outbound Audio Stream", audioSetting, "")} + + + ); }); @@ -114,39 +143,45 @@ export default function Stats() {
Server Overview - {Item( - `Stream started ${formatRelative( + - )} - - {Item("Viewers", viewerCount, )} - {Item("Peak viewer count", sessionMaxViewerCount, )} + )}`} + value={formatDistanceToNow(new Date(lastConnectTime))} + prefix={} + /> + } + /> + } + /> - {Item("Input", formatIPAddress(remoteAddr), "")} - {Item("Inbound Video Stream", streamVideoDetailString, "")} - {Item("Inbound Audio Stream", streamAudioDetailString, "")} + + + {videoQualitySettings}
); } - -function Offline() { - return ( -
- There is no stream currently active. Start one. - } - /> -
- ); -} diff --git a/web/pages/update-server-config.tsx b/web/pages/update-server-config.tsx index 201656507..782703047 100644 --- a/web/pages/update-server-config.tsx +++ b/web/pages/update-server-config.tsx @@ -1,10 +1,249 @@ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect } from 'react'; import { Table, Typography, Input } from 'antd'; import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from './utils/apis'; +import { isEmptyObject } from './utils/format'; const { Title } = Typography; const { TextArea } = Input; +function KeyValueTable({ title, data }) { + const columns = [ + { + title: "Name", + dataIndex: "name", + key: "name", + }, + { + title: "Value", + dataIndex: "value", + key: "value", + }, + ]; + + return ( +
+ {title} + + + ); +} + +function SocialHandles({ config }) { + if (!config) { + return null; + } + + const columns = [ + { + title: "Platform", + dataIndex: "platform", + key: "platform", + }, + { + title: "URL", + dataIndex: "url", + key: "url", + render: (url) => `${url}` + }, + ]; + + return ( +
+ Social Handles +
+ + ); +} + +function InstanceDetails({ config }) { + console.log(config) + if (!config || isEmptyObject(config)) { + return null; + } + + const { instanceDetails = {}, yp, streamKey, ffmpegPath, webServerPort } = config; + + const data = [ + { + name: "Server name", + value: instanceDetails.name, + }, + { + name: "Title", + value: instanceDetails.title, + }, + { + name: "Summary", + value: instanceDetails.summary, + }, + { + name: "Logo", + value: instanceDetails.logo.large, + }, + { + name: "Tags", + value: instanceDetails.tags.join(", "), + }, + { + name: "NSFW", + value: instanceDetails.nsfw.toString(), + }, + { + name: "Shows in Owncast directory", + value: yp.enabled.toString(), + }, + ]; + + const configData = [ + { + name: "Stream key", + value: streamKey, + }, + { + name: "ffmpeg path", + value: ffmpegPath, + }, + { + name: "Web server port", + value: webServerPort, + }, + ]; + + return ( + <> + + + + ); +} + +function VideoVariants({ config }) { + if (!config) { + return null; + } + + const videoQualityColumns = [ + { + title: "Video bitrate", + dataIndex: "videoBitrate", + key: "videoBitrate", + render: (bitrate) => + bitrate === 0 || !bitrate ? "Passthrough" : `${bitrate} kbps`, + }, + { + title: "Framerate", + dataIndex: "framerate", + key: "framerate", + }, + { + title: "Encoder preset", + dataIndex: "encoderPreset", + key: "framerate", + }, + { + title: "Audio bitrate", + dataIndex: "audioBitrate", + key: "audioBitrate", + render: (bitrate) => + bitrate === 0 || !bitrate ? "Passthrough" : `${bitrate} kbps`, + }, + ]; + + const miscVideoSettingsColumns = [ + { + title: "Name", + dataIndex: "name", + key: "name", + }, + { + title: "Value", + dataIndex: "value", + key: "value", + }, + ]; + + const miscVideoSettings = [ + { + name: "Segment length", + value: config.videoSettings.segmentLengthSeconds, + }, + { + name: "Number of segments", + value: config.videoSettings.numberOfPlaylistItems, + }, + ]; + + return ( +
+ Video configuration +
+ +
+ + ); +} + +function Storage({ config }) { + if (!config) { + return null; + } + + const data = [ + { + name: "Enabled", + value: config.s3.enabled.toString(), + }, + { + name: "Endpoint", + value: config.s3.endpoint, + }, + { + name: "Access Key", + value: config.s3.accessKey, + }, + { + name: "Secret", + value: config.s3.secret, + }, + { + name: "Bucket", + value: config.s3.bucket, + }, + { + name: "Region", + value: config.s3.region, + }, + ]; + return +} + +function PageContent({ config }) { + if (!config) { + return null; + } + return ( +
+ Page content +