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. 42
      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. 17
      web/utils/apis.ts

2
web/.env.production

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

1
web/next.config.js

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

43
web/pages/components/chart.tsx

@ -5,7 +5,7 @@ import styles from '../../styles/styles.module.css'; @@ -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 { @@ -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) { @@ -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)) : "";
return (
<div className="custom-tooltip">
<p className="label">
<strong>{time}</strong> {payload[0].payload.value} {unit}
</p>
const tooltipDetails = payload.map(data => {
return <div className="label" key={data.name}>
{data.payload.value}{unit} {data.name}
</div>
});
return (
<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 @@ -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 @@ -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>

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

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

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

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; @@ -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) { @@ -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) { @@ -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>

42
web/pages/components/statistic.tsx

@ -1,25 +1,57 @@ @@ -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>
<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}
/>
</Card>
</Col>
);
)
}

51
web/pages/hardware-info.tsx

@ -1,8 +1,8 @@ @@ -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() { @@ -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 = [ @@ -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 = [ @@ -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>
);
}

86
web/pages/index.tsx

@ -8,7 +8,11 @@ TODO: Link each overview value to the sub-page that focuses on it. @@ -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"; @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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>
);
}
}

20
web/pages/offline-notice.tsx

@ -7,7 +7,7 @@ export default function Offline() { @@ -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() { @@ -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() { @@ -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() { @@ -51,6 +60,7 @@ export default function Offline() {
</List.Item>
)}
/>
{logTable}
</div>
);
}

15
web/pages/storage.tsx

@ -12,7 +12,7 @@ function Storage({ config }) { @@ -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() { @@ -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>
);
}

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

@ -149,24 +149,9 @@ export default function ServerConfig() { @@ -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>
);
}

4
web/pages/upgrade.tsx

@ -38,7 +38,7 @@ export default function Logs() { @@ -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) { @@ -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,21 +109,8 @@ export default function VideoConfig() { @@ -109,21 +109,8 @@ 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>
);
}

7
web/pages/viewer-info.tsx

@ -109,22 +109,25 @@ export default function ViewersOverTime() { @@ -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>
);
}

17
web/utils/apis.ts

@ -37,16 +37,19 @@ export const LOGS_WARN = `${API_LOCATION}logs/warnings`; @@ -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) {
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);

Loading…
Cancel
Save