0

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

This commit is contained in:
gingervitis 2020-11-12 22:39:18 -08:00
commit 3f1f96a768
15 changed files with 220 additions and 114 deletions

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import styles from '../../styles/styles.module.css';
interface ToolTipProps {
active?: boolean,
payload?: object,
payload?: {name: string, payload: {value: string, time: Date}}[],
unit?: string
}
@ -22,6 +22,7 @@ interface TimedValue {
interface ChartProps {
data?: TimedValue[],
title?: string,
color: string,
unit: string,
dataCollections?: any[],
@ -31,19 +32,24 @@ function CustomizedTooltip(props: ToolTipProps) {
const { active, payload, unit } = props;
if (active && payload && payload[0]) {
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 (
<div className="custom-tooltip">
<p className="label">
<strong>{time}</strong> {payload[0].payload.value} {unit}
</p>
</div>
<span className="custom-tooltip">
<strong>{time}</strong>
{tooltipDetails}
</span>
);
}
return null;
}
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) {
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 (
<div className={styles.lineChartContainer}>
<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} />} />
<Legend />
<Line
type="monotone"
dataKey="value"
stroke={color}
dot={null}
strokeWidth={3}
/>
{line}
{dataCollections?.map((s) => (
<Line
dataKey="value"
data={s.data}
name={s.name}
key={s.name}
type="monotone"
type="natural"
stroke={s.color}
dot={null}
strokeWidth={3}
legendType="square"
/>
))}
</LineChart>

View File

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

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import Link from 'next/link';
import { differenceInSeconds } from "date-fns";
import { useRouter } from 'next/router';
import { Layout, Menu } from 'antd';
import { Layout, Menu, Popover } from 'antd';
import {
SettingOutlined,
@ -38,6 +38,11 @@ export default function MainLayout(props) {
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 statusMessage = online ? `Online ${streamDurationString}` : "Offline";
@ -145,10 +150,12 @@ export default function MainLayout(props) {
<Layout>
<Header className={adminStyles.header}>
<Popover content={content} title="Thumbnail" trigger="hover">
<div className={adminStyles.statusIndicatorContainer}>
<span className={adminStyles.statusLabel}>{statusMessage}</span>
<span className={adminStyles.statusIcon}>{statusIcon}</span>
</div>
</Popover>
</Header>
<Content className={adminStyles.contentMain}>{children}</Content>

View File

@ -1,25 +1,57 @@
import { Statistic, Card, Col} from "antd";
import { Typography, Statistic, Card, Col, Progress} from "antd";
const { Text } = Typography;
interface ItemProps {
title: string,
value: string,
prefix: JSX.Element,
color: string,
progress?: boolean,
centered: boolean,
};
export default function StatisticItem(props: ItemProps) {
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 (
<Col span={8}>
<Card>
<Statistic
title={title}
value={value}
valueStyle={valueStyle}
prefix={prefix}
/>
<div style={style}>
<View {...props} />
</div>
</Card>
</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}
/>
)
}

View File

@ -1,8 +1,8 @@
/* eslint-disable no-array-constructor */
import React, { useState, useEffect } from 'react';
import { BulbOutlined, LaptopOutlined, SaveOutlined } from "@ant-design/icons";
import { Row } from "antd";
import {LaptopOutlined, BulbOutlined, SaveOutlined} from "@ant-design/icons"
import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from '../utils/apis';
import React, { useEffect, useState } from 'react';
import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../utils/apis';
import Chart from './components/chart';
import StatisticItem from "./components/statistic";
@ -55,17 +55,17 @@ export default function HardwareInfo() {
const series = [
{
name: "CPU",
color: "#FF7700",
color: "#B63FFF",
data: hardwareStatus.cpu,
},
{
name: "Memory",
color: "#004777",
color: "#2087E2",
data: hardwareStatus.memory,
},
{
name: "Disk",
color: "#A9E190",
color: "#FF7700",
data: hardwareStatus.disk,
},
];
@ -76,19 +76,28 @@ const series = [
<h2>Hardware Info</h2>
<Row gutter={[16, 16]}>
<StatisticItem
title="CPU used"
value={`${currentCPUUsage} %`}
prefix={<LaptopOutlined />}
title={series[0].name}
value={`${currentCPUUsage}`}
prefix={<LaptopOutlined style={{color: series[0].color }}/>}
color={series[0].color}
progress
centered
/>
<StatisticItem
title="Memory used"
value={`${currentRamUsage} %`}
prefix={<BulbOutlined />}
title={series[1].name}
value={`${currentRamUsage}`}
prefix={<BulbOutlined style={{color: series[1].color }} />}
color={series[1].color}
progress
centered
/>
<StatisticItem
title="Disk used"
value={`${currentDiskUsage} %`}
prefix={<SaveOutlined />}
title={series[2].name}
value={`${currentDiskUsage}`}
prefix={<SaveOutlined style={{color: series[2].color }} />}
color={series[2].color}
progress
centered
/>
</Row>
@ -96,18 +105,6 @@ const series = [
<Chart dataCollections={series} color="#FF7700" unit="%" />
</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>
);
}

View File

@ -8,7 +8,11 @@ TODO: Link each overview value to the sub-page that focuses on it.
*/
import React, { useState, useEffect, useContext } from "react";
<<<<<<< HEAD
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 { formatDistanceToNow, formatRelative } from "date-fns";
import { ServerStatusContext } from "../utils/server-status-context";
@ -27,6 +31,10 @@ import { formatIPAddress, isEmptyObject } from "../utils/format";
const { Title } = Typography;
<<<<<<< HEAD
=======
>>>>>>> 4cdf5b73baa0584a0e6b2f586c27ca53923c65c7
<<<<<<< HEAD
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) {
return <Offline />;
}
@ -122,17 +133,18 @@ export default function Stats() {
title="Outbound Video Stream"
value={`${setting.videoBitrate} kbps ${setting.framerate} fps`}
prefix={null}
color="#334"
/>
<StatisticItem
title="Outbound Audio Stream"
value={audioSetting}
prefix={null}
color="#334"
/>
</Row>
);
});
const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null
const { viewerCount, sessionMaxViewerCount } = stats;
const streamVideoDetailString = `${streamDetails.videoCodec} ${streamDetails.videoBitrate} kbps ${streamDetails.width}x${streamDetails.height}`;
const streamAudioDetailString = `${streamDetails.audioCodec} ${streamDetails.audioBitrate} kpbs`;
@ -148,16 +160,19 @@ export default function Stats() {
)}`}
value={formatDistanceToNow(new Date(broadcaster.time))}
prefix={<ClockCircleOutlined />}
color="#334"
/>
<StatisticItem
title="Viewers"
value={viewerCount}
prefix={<UserOutlined />}
color="#334"
/>
<StatisticItem
title="Peak viewer count"
value={sessionMaxViewerCount}
prefix={<UserOutlined />}
color="#334"
/>
</Row>
@ -166,16 +181,19 @@ export default function Stats() {
title="Input"
value={formatIPAddress(remoteAddr)}
prefix={null}
color="#334"
/>
<StatisticItem
title="Inbound Video Stream"
value={streamVideoDetailString}
prefix={null}
color="#334"
/>
<StatisticItem
title="Inbound Audio Stream"
value={streamAudioDetailString}
prefix={null}
color="#334"
/>
</Row>
@ -186,15 +204,81 @@ export default function Stats() {
title="Stream key"
value={config.streamKey}
prefix={null}
color="#334"
/>
<StatisticItem
title="Directory registration enabled"
value={config.yp.enabled.toString()}
prefix={null}
color="#334"
/>
</Row>
{logTable}
</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>
);
}
}

View File

@ -7,7 +7,7 @@ export default function Offline() {
title: "Send some test content",
content: (
<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>
<em>./test/ocTestStream.sh yourVideo.mp4</em>
</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 (
<div>
@ -39,9 +48,9 @@ export default function Offline() {
gutter: 16,
xs: 1,
sm: 2,
md: 4,
lg: 4,
xl: 6,
md: 2,
lg: 6,
xl: 3,
xxl: 3,
}}
dataSource={data}
@ -51,6 +60,7 @@ export default function Offline() {
</List.Item>
)}
/>
{logTable}
</div>
);
}

View File

@ -12,7 +12,7 @@ function Storage({ config }) {
return (
<h3>
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>
);
}
@ -74,20 +74,7 @@ export default function ServerConfig() {
return (
<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} />
</div>
</div>
);
}

View File

@ -149,24 +149,9 @@ export default function ServerConfig() {
return (
<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} />
<SocialHandles config={config} />
<PageContent config={config} />
{JSON.stringify(config)}
</div>
</div>
);
}

View File

@ -38,7 +38,7 @@ export default function Logs() {
<a href={release.html_url}>{release.name}</a>
</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} />
</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} />
}

View File

@ -109,20 +109,7 @@ export default function VideoConfig() {
return (
<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} />
</div>
</div>
);
}

View File

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

View File

@ -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";
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 {
const response = await fetch(url, {
headers: {
'Authorization': `Basic ${encoded}`,
},
mode: 'cors',
credentials: 'include',
});
const response = await fetch(url, options);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);