Browse Source

Merge branch 'master' of github.com:owncast/owncast-admin

pull/1886/head
gingervitis 5 years ago
parent
commit
3f1f96a768
  1. 2
      web/.env.production
  2. 1
      web/next.config.js
  3. 43
      web/pages/components/chart.tsx
  4. 1
      web/pages/components/log-table.tsx
  5. 9
      web/pages/components/main-layout.tsx
  6. 48
      web/pages/components/statistic.tsx
  7. 51
      web/pages/hardware-info.tsx
  8. 86
      web/pages/index.tsx
  9. 20
      web/pages/offline-notice.tsx
  10. 15
      web/pages/storage.tsx
  11. 15
      web/pages/update-server-config.tsx
  12. 4
      web/pages/upgrade.tsx
  13. 13
      web/pages/video-config.tsx
  14. 7
      web/pages/viewer-info.tsx
  15. 19
      web/utils/apis.ts

2
web/.env.production

@ -1,3 +1 @@
NEXT_PUBLIC_ADMIN_USERNAME=admin
NEXT_PUBLIC_ADMIN_STREAMKEY=abc123
NEXT_PUBLIC_API_HOST=/ NEXT_PUBLIC_API_HOST=/

1
web/next.config.js

@ -1,3 +1,4 @@
module.exports = { module.exports = {
basePath: "/admin", basePath: "/admin",
trailingSlash: true,
}; };

43
web/pages/components/chart.tsx

@ -5,7 +5,7 @@ import styles from '../../styles/styles.module.css';
interface ToolTipProps { interface ToolTipProps {
active?: boolean, active?: boolean,
payload?: object, payload?: {name: string, payload: {value: string, time: Date}}[],
unit?: string unit?: string
} }
@ -22,6 +22,7 @@ interface TimedValue {
interface ChartProps { interface ChartProps {
data?: TimedValue[], data?: TimedValue[],
title?: string,
color: string, color: string,
unit: string, unit: string,
dataCollections?: any[], dataCollections?: any[],
@ -31,19 +32,24 @@ function CustomizedTooltip(props: ToolTipProps) {
const { active, payload, unit } = props; const { active, payload, unit } = props;
if (active && payload && payload[0]) { if (active && payload && payload[0]) {
const time = payload[0].payload ? timeFormat("%I:%M")(new Date(payload[0].payload.time)) : ""; const time = payload[0].payload ? timeFormat("%I:%M")(new Date(payload[0].payload.time)) : "";
const tooltipDetails = payload.map(data => {
return <div className="label" key={data.name}>
{data.payload.value}{unit} {data.name}
</div>
});
return ( return (
<div className="custom-tooltip"> <span className="custom-tooltip">
<p className="label"> <strong>{time}</strong>
<strong>{time}</strong> {payload[0].payload.value} {unit} {tooltipDetails}
</p> </span>
</div>
); );
} }
return null; return null;
} }
CustomizedTooltip.defaultProps = defaultProps; CustomizedTooltip.defaultProps = defaultProps;
export default function Chart({ data, color, unit, dataCollections }: ChartProps) { export default function Chart({ data, title, color, unit, dataCollections }: ChartProps) {
if (!data && !dataCollections) { if (!data && !dataCollections) {
return null; return null;
} }
@ -67,6 +73,18 @@ export default function Chart({ data, color, unit, dataCollections }: ChartProps
}); });
} }
const line = data ? (
<Line
type="natural"
dataKey="value"
stroke={color}
dot={null}
strokeWidth={3}
legendType="square"
name={title}
/>
) : null;
return ( return (
<div className={styles.lineChartContainer}> <div className={styles.lineChartContainer}>
<LineChart width={chartWidth} height={chartHeight} data={data}> <LineChart width={chartWidth} height={chartHeight} data={data}>
@ -87,23 +105,18 @@ export default function Chart({ data, color, unit, dataCollections }: ChartProps
/> />
<Tooltip content={<CustomizedTooltip unit={unit} />} /> <Tooltip content={<CustomizedTooltip unit={unit} />} />
<Legend /> <Legend />
<Line {line}
type="monotone"
dataKey="value"
stroke={color}
dot={null}
strokeWidth={3}
/>
{dataCollections?.map((s) => ( {dataCollections?.map((s) => (
<Line <Line
dataKey="value" dataKey="value"
data={s.data} data={s.data}
name={s.name} name={s.name}
key={s.name} key={s.name}
type="monotone" type="natural"
stroke={s.color} stroke={s.color}
dot={null} dot={null}
strokeWidth={3} strokeWidth={3}
legendType="square"
/> />
))} ))}
</LineChart> </LineChart>

1
web/pages/components/log-table.tsx

@ -77,7 +77,6 @@ export default function LogTable({ logs, pageSize }: Props) {
rowKey={(row) => row.time} rowKey={(row) => row.time}
pagination={{ pageSize: pageSize || 20 }} pagination={{ pageSize: pageSize || 20 }}
/> />
;
</div> </div>
); );
} }

9
web/pages/components/main-layout.tsx

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import Link from 'next/link'; import Link from 'next/link';
import { differenceInSeconds } from "date-fns"; import { differenceInSeconds } from "date-fns";
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Layout, Menu } from 'antd'; import { Layout, Menu, Popover } from 'antd';
import { import {
SettingOutlined, SettingOutlined,
@ -38,6 +38,11 @@ export default function MainLayout(props) {
const streamDurationString = online ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : ""; const streamDurationString = online ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : "";
const content = (
<div>
<img src="/thumbnail.jpg" width="200px" />
</div>
);
const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />; const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />;
const statusMessage = online ? `Online ${streamDurationString}` : "Offline"; const statusMessage = online ? `Online ${streamDurationString}` : "Offline";
@ -145,10 +150,12 @@ export default function MainLayout(props) {
<Layout> <Layout>
<Header className={adminStyles.header}> <Header className={adminStyles.header}>
<Popover content={content} title="Thumbnail" trigger="hover">
<div className={adminStyles.statusIndicatorContainer}> <div className={adminStyles.statusIndicatorContainer}>
<span className={adminStyles.statusLabel}>{statusMessage}</span> <span className={adminStyles.statusLabel}>{statusMessage}</span>
<span className={adminStyles.statusIcon}>{statusIcon}</span> <span className={adminStyles.statusIcon}>{statusIcon}</span>
</div> </div>
</Popover>
</Header> </Header>
<Content className={adminStyles.contentMain}>{children}</Content> <Content className={adminStyles.contentMain}>{children}</Content>

48
web/pages/components/statistic.tsx

@ -1,25 +1,57 @@
import { Statistic, Card, Col} from "antd"; import { Typography, Statistic, Card, Col, Progress} from "antd";
const { Text } = Typography;
interface ItemProps { interface ItemProps {
title: string, title: string,
value: string, value: string,
prefix: JSX.Element, prefix: JSX.Element,
color: string,
progress?: boolean,
centered: boolean,
}; };
export default function StatisticItem(props: ItemProps) { export default function StatisticItem(props: ItemProps) {
const { title, value, prefix } = props; const { title, value, prefix } = props;
const valueStyle = { color: "#334", fontSize: "1.8rem" }; const View = props.progress ? ProgressView : StatisticView;
const style = props.centered ? {display: 'flex', alignItems: 'center', justifyContent: 'center'} : {};
return ( return (
<Col span={8}> <Col span={8}>
<Card> <Card>
<Statistic <div style={style}>
title={title} <View {...props} />
value={value} </div>
valueStyle={valueStyle}
prefix={prefix}
/>
</Card> </Card>
</Col> </Col>
); );
} }
function ProgressView({title, value, prefix, color}) {
const endColor = value > 90 ? 'red' : color;
const content = (
<div>
{prefix}
<div><Text type="secondary">{title}</Text></div>
<div><Text type="secondary">{value}%</Text></div>
</div>
)
return (
<Progress type="dashboard" percent={value} width={120} strokeColor={{
'0%': color,
'90%': endColor,
}} format={percent => content} />
)
}
function StatisticView({title, value, prefix, color}) {
const valueStyle = { fontSize: "1.8rem" };
return (
<Statistic
title={title}
value={value}
valueStyle={valueStyle}
prefix={prefix}
/>
)
}

51
web/pages/hardware-info.tsx

@ -1,8 +1,8 @@
/* eslint-disable no-array-constructor */ /* eslint-disable no-array-constructor */
import React, { useState, useEffect } from 'react'; import { BulbOutlined, LaptopOutlined, SaveOutlined } from "@ant-design/icons";
import { Row } from "antd"; import { Row } from "antd";
import {LaptopOutlined, BulbOutlined, SaveOutlined} from "@ant-design/icons" import React, { useEffect, useState } from 'react';
import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from '../utils/apis'; import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../utils/apis';
import Chart from './components/chart'; import Chart from './components/chart';
import StatisticItem from "./components/statistic"; import StatisticItem from "./components/statistic";
@ -55,17 +55,17 @@ export default function HardwareInfo() {
const series = [ const series = [
{ {
name: "CPU", name: "CPU",
color: "#FF7700", color: "#B63FFF",
data: hardwareStatus.cpu, data: hardwareStatus.cpu,
}, },
{ {
name: "Memory", name: "Memory",
color: "#004777", color: "#2087E2",
data: hardwareStatus.memory, data: hardwareStatus.memory,
}, },
{ {
name: "Disk", name: "Disk",
color: "#A9E190", color: "#FF7700",
data: hardwareStatus.disk, data: hardwareStatus.disk,
}, },
]; ];
@ -76,19 +76,28 @@ const series = [
<h2>Hardware Info</h2> <h2>Hardware Info</h2>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<StatisticItem <StatisticItem
title="CPU used" title={series[0].name}
value={`${currentCPUUsage} %`} value={`${currentCPUUsage}`}
prefix={<LaptopOutlined />} prefix={<LaptopOutlined style={{color: series[0].color }}/>}
color={series[0].color}
progress
centered
/> />
<StatisticItem <StatisticItem
title="Memory used" title={series[1].name}
value={`${currentRamUsage} %`} value={`${currentRamUsage}`}
prefix={<BulbOutlined />} prefix={<BulbOutlined style={{color: series[1].color }} />}
color={series[1].color}
progress
centered
/> />
<StatisticItem <StatisticItem
title="Disk used" title={series[2].name}
value={`${currentDiskUsage} %`} value={`${currentDiskUsage}`}
prefix={<SaveOutlined />} prefix={<SaveOutlined style={{color: series[2].color }} />}
color={series[2].color}
progress
centered
/> />
</Row> </Row>
@ -96,18 +105,6 @@ const series = [
<Chart dataCollections={series} color="#FF7700" unit="%" /> <Chart dataCollections={series} color="#FF7700" unit="%" />
</div> </div>
</div> </div>
<p>cpu:[], disk: [], memory: []; value = %age.</p>
<p>the times should be the same for each, though milliseconds differ</p>
<div
style={{
border: "1px solid blue",
height: "300px",
width: "100%",
overflow: "auto",
}}
>
{JSON.stringify(hardwareStatus)}
</div>
</div> </div>
); );
} }

86
web/pages/index.tsx

@ -8,7 +8,11 @@ TODO: Link each overview value to the sub-page that focuses on it.
*/ */
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
<<<<<<< HEAD
import { Row, Skeleton, Typography } from "antd"; import { Row, Skeleton, Typography } from "antd";
=======
import { Row, Col, Skeleton, Result, List, Typography, Card } from "antd";
>>>>>>> 4cdf5b73baa0584a0e6b2f586c27ca53923c65c7
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 { ServerStatusContext } from "../utils/server-status-context"; import { ServerStatusContext } from "../utils/server-status-context";
@ -27,6 +31,10 @@ import { formatIPAddress, isEmptyObject } from "../utils/format";
const { Title } = Typography; const { Title } = Typography;
<<<<<<< HEAD
=======
>>>>>>> 4cdf5b73baa0584a0e6b2f586c27ca53923c65c7
<<<<<<< HEAD <<<<<<< HEAD
export default function Home() { export default function Home() {
@ -105,6 +113,9 @@ export default function Stats() {
); );
} }
const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null
console.log(logs)
if (!broadcaster) { if (!broadcaster) {
return <Offline />; return <Offline />;
} }
@ -122,17 +133,18 @@ export default function Stats() {
title="Outbound Video Stream" title="Outbound Video Stream"
value={`${setting.videoBitrate} kbps ${setting.framerate} fps`} value={`${setting.videoBitrate} kbps ${setting.framerate} fps`}
prefix={null} prefix={null}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Outbound Audio Stream" title="Outbound Audio Stream"
value={audioSetting} value={audioSetting}
prefix={null} prefix={null}
color="#334"
/> />
</Row> </Row>
); );
}); });
const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null
const { viewerCount, sessionMaxViewerCount } = 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`;
@ -148,16 +160,19 @@ export default function Stats() {
)}`} )}`}
value={formatDistanceToNow(new Date(broadcaster.time))} value={formatDistanceToNow(new Date(broadcaster.time))}
prefix={<ClockCircleOutlined />} prefix={<ClockCircleOutlined />}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Viewers" title="Viewers"
value={viewerCount} value={viewerCount}
prefix={<UserOutlined />} prefix={<UserOutlined />}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Peak viewer count" title="Peak viewer count"
value={sessionMaxViewerCount} value={sessionMaxViewerCount}
prefix={<UserOutlined />} prefix={<UserOutlined />}
color="#334"
/> />
</Row> </Row>
@ -166,16 +181,19 @@ export default function Stats() {
title="Input" title="Input"
value={formatIPAddress(remoteAddr)} value={formatIPAddress(remoteAddr)}
prefix={null} prefix={null}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Inbound Video Stream" title="Inbound Video Stream"
value={streamVideoDetailString} value={streamVideoDetailString}
prefix={null} prefix={null}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Inbound Audio Stream" title="Inbound Audio Stream"
value={streamAudioDetailString} value={streamAudioDetailString}
prefix={null} prefix={null}
color="#334"
/> />
</Row> </Row>
@ -186,15 +204,81 @@ export default function Stats() {
title="Stream key" title="Stream key"
value={config.streamKey} value={config.streamKey}
prefix={null} prefix={null}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Directory registration enabled" title="Directory registration enabled"
value={config.yp.enabled.toString()} value={config.yp.enabled.toString()}
prefix={null} prefix={null}
color="#334"
/> />
</Row> </Row>
{logTable} {logTable}
</div> </div>
); );
function Offline() {
const data = [
{
title: "Send some test content",
content: (
<div>
Test your server with any video you have around. Pass it to the test script and start streaming it.
<blockquote>
<em>./test/ocTestStream.sh yourVideo.mp4</em>
</blockquote>
</div>
),
},
{
title: "Use your broadcasting software",
content: (
<div>
<a href="https://owncast.online/docs/broadcasting/">Learn how to point your existing software to your new server and start streaming your content.</a>
</div>
)
},
{
title: "Chat is disabled",
content: "Chat will continue to be disabled until you begin a live stream."
},
{
title: "Embed your video onto other sites",
content: (
<div>
<a href="https://owncast.online/docs/embed">Learn how you can add your Owncast stream to other sites you control.</a>
</div>
)
}
];
return (
<div>
<Result
icon={<OwncastLogo />}
title="No stream is active."
subTitle="You should start one."
/>
<List
grid={{
gutter: 16,
xs: 1,
sm: 2,
md: 2,
lg: 6,
xl: 3,
xxl: 3,
}}
dataSource={data}
renderItem={(item) => (
<List.Item>
<Card title={item.title}>{item.content}</Card>
</List.Item>
)}
/>
{logTable}
</div>
);
}
} }

20
web/pages/offline-notice.tsx

@ -7,7 +7,7 @@ export default function Offline() {
title: "Send some test content", title: "Send some test content",
content: ( content: (
<div> <div>
With any video you have around you can pass it to the test script and start streaming it. Test your server with any video you have around. Pass it to the test script and start streaming it.
<blockquote> <blockquote>
<em>./test/ocTestStream.sh yourVideo.mp4</em> <em>./test/ocTestStream.sh yourVideo.mp4</em>
</blockquote> </blockquote>
@ -23,8 +23,17 @@ export default function Offline() {
) )
}, },
{ {
title: "Something else", title: "Chat is disabled",
content: "Chat will continue to be disabled until you begin a live stream."
}, },
{
title: "Embed your video onto other sites",
content: (
<div>
<a href="https://owncast.online/docs/embed">Learn how you can add your Owncast stream to other sites you control.</a>
</div>
)
}
]; ];
return ( return (
<div> <div>
@ -39,9 +48,9 @@ export default function Offline() {
gutter: 16, gutter: 16,
xs: 1, xs: 1,
sm: 2, sm: 2,
md: 4, md: 2,
lg: 4, lg: 6,
xl: 6, xl: 3,
xxl: 3, xxl: 3,
}} }}
dataSource={data} dataSource={data}
@ -51,6 +60,7 @@ export default function Offline() {
</List.Item> </List.Item>
)} )}
/> />
{logTable}
</div> </div>
); );
} }

15
web/pages/storage.tsx

@ -12,7 +12,7 @@ function Storage({ config }) {
return ( return (
<h3> <h3>
Local storage is being used. Enable external S3 storage if you want Local storage is being used. Enable external S3 storage if you want
to use it. to use it. TODO: Make this message somewhat more informative. Point to documentation or something.
</h3> </h3>
); );
} }
@ -74,20 +74,7 @@ export default function ServerConfig() {
return ( return (
<div> <div>
<h2>Server Config</h2>
<p>
Display this data all pretty, most things will be editable in the
future, not now.
</p>
<div
style={{
border: "1px solid pink",
width: "100%",
overflow: "auto",
}}
>
<Storage config={config} /> <Storage config={config} />
</div>
</div> </div>
); );
} }

15
web/pages/update-server-config.tsx

@ -149,24 +149,9 @@ export default function ServerConfig() {
return ( return (
<div> <div>
<h2>Server Config</h2>
<p>
Display this data all pretty, most things will be editable in the
future, not now.
</p>
<div
style={{
border: "1px solid pink",
width: "100%",
overflow: "auto",
}}
>
<InstanceDetails config={config} /> <InstanceDetails config={config} />
<SocialHandles config={config} /> <SocialHandles config={config} />
<PageContent config={config} /> <PageContent config={config} />
{JSON.stringify(config)}
</div>
</div> </div>
); );
} }

4
web/pages/upgrade.tsx

@ -38,7 +38,7 @@ export default function Logs() {
<a href={release.html_url}>{release.name}</a> <a href={release.html_url}>{release.name}</a>
</Title> </Title>
<Title level={5}>{new Date(release.created_at).toDateString()}</Title> <Title level={5}>{new Date(release.created_at).toDateString()}</Title>
<ReactMarkdown>{release.body}</ReactMarkdown>;<h3>Downloads</h3> <ReactMarkdown>{release.body}</ReactMarkdown><h3>Downloads</h3>
<AssetTable {...release.assets} /> <AssetTable {...release.assets} />
</div> </div>
); );
@ -68,6 +68,6 @@ function AssetTable(assets) {
}, },
]; ];
return <Table dataSource={data} columns={columns} rowKey="id" size="large" />; return <Table dataSource={data} columns={columns} rowKey="id" size="large" pagination={false} />
} }

13
web/pages/video-config.tsx

@ -109,20 +109,7 @@ export default function VideoConfig() {
return ( return (
<div> <div>
<h2>Server Config</h2>
<p>
Display this data all pretty, most things will be editable in the
future, not now.
</p>
<div
style={{
border: "1px solid pink",
width: "100%",
overflow: "auto",
}}
>
<VideoVariants config={config} /> <VideoVariants config={config} />
</div>
</div> </div>
); );
} }

7
web/pages/viewer-info.tsx

@ -109,22 +109,25 @@ export default function ViewersOverTime() {
title="Current viewers" title="Current viewers"
value={viewerCount.toString()} value={viewerCount.toString()}
prefix={<UserOutlined />} prefix={<UserOutlined />}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Peak viewers this session" title="Peak viewers this session"
value={sessionPeakViewerCount.toString()} value={sessionPeakViewerCount.toString()}
prefix={<UserOutlined />} prefix={<UserOutlined />}
color="#334"
/> />
<StatisticItem <StatisticItem
title="Peak viewers overall" title="Peak viewers overall"
value={overallPeakViewerCount.toString()} value={overallPeakViewerCount.toString()}
prefix={<UserOutlined />} prefix={<UserOutlined />}
color="#334"
/> />
</Row> </Row>
<div className="chart-container"> <div className="chart-container">
<Chart data={viewerInfo} color="#ff84d8" unit="" /> <Chart title="Viewers" data={viewerInfo} color="#2087E2" unit="" />
</div> </div>
<Table dataSource={clients} columns={columns} />; <Table dataSource={clients} columns={columns} />
</div> </div>
); );
} }

19
web/utils/apis.ts

@ -37,16 +37,19 @@ 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";
export async function fetchData(url) { export async function fetchData(url) {
const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); let options: RequestInit = {};
if (ADMIN_USERNAME && ADMIN_STREAMKEY) {
const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`);
options.headers = {
'Authorization': `Basic ${encoded}`
}
options.mode = 'cors';
options.credentials = 'include'
}
try { try {
const response = await fetch(url, { const response = await fetch(url, options);
headers: {
'Authorization': `Basic ${encoded}`,
},
mode: 'cors',
credentials: 'include',
});
if (!response.ok) { if (!response.ok) {
const message = `An error has occured: ${response.status}`; const message = `An error has occured: ${response.status}`;
throw new Error(message); throw new Error(message);

Loading…
Cancel
Save