Merge branch 'master' of github.com:owncast/owncast-admin
This commit is contained in:
@@ -1,3 +1 @@
|
|||||||
NEXT_PUBLIC_ADMIN_USERNAME=admin
|
|
||||||
NEXT_PUBLIC_ADMIN_STREAMKEY=abc123
|
|
||||||
NEXT_PUBLIC_API_HOST=/
|
NEXT_PUBLIC_API_HOST=/
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
basePath: "/admin",
|
basePath: "/admin",
|
||||||
|
trailingSlash: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)) : "";
|
||||||
return (
|
|
||||||
<div className="custom-tooltip">
|
const tooltipDetails = payload.map(data => {
|
||||||
<p className="label">
|
return <div className="label" key={data.name}>
|
||||||
<strong>{time}</strong> {payload[0].payload.value} {unit}
|
{data.payload.value}{unit} {data.name}
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span className="custom-tooltip">
|
||||||
|
<strong>{time}</strong>
|
||||||
|
{tooltipDetails}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
<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
|
<Statistic
|
||||||
title={title}
|
title={title}
|
||||||
value={value}
|
value={value}
|
||||||
valueStyle={valueStyle}
|
valueStyle={valueStyle}
|
||||||
prefix={prefix}
|
prefix={prefix}
|
||||||
/>
|
/>
|
||||||
</Card>
|
)
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,21 +109,8 @@ 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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
let options: RequestInit = {};
|
||||||
|
|
||||||
|
if (ADMIN_USERNAME && ADMIN_STREAMKEY) {
|
||||||
const encoded = btoa(`${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);
|
||||||
|
|||||||
Reference in New Issue
Block a user