It builds
This commit is contained in:
@@ -3,7 +3,7 @@ import 'antd/dist/antd.compact.css';
|
||||
import "../styles/globals.scss";
|
||||
|
||||
import { AppProps } from 'next/app';
|
||||
import BroadcastStatusProvider from './utils/broadcast-status-context';
|
||||
import BroadcastStatusProvider from '../utils/broadcast-status-context';
|
||||
import MainLayout from './components/main-layout';
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { BroadcastStatusContext } from './utils/broadcast-status-context';
|
||||
import { BroadcastStatusContext } from '../utils/broadcast-status-context';
|
||||
|
||||
|
||||
export default function BroadcastInfo() {
|
||||
|
||||
@@ -9,20 +9,23 @@ interface ToolTipProps {
|
||||
|
||||
const defaultProps = {
|
||||
active: false,
|
||||
payload: {},
|
||||
unit: ""
|
||||
payload: Object,
|
||||
unit: "",
|
||||
};
|
||||
|
||||
interface ChartProps {
|
||||
data: [{
|
||||
time: string,
|
||||
}],
|
||||
color: string,
|
||||
unit: string,
|
||||
dataCollections?: any,
|
||||
interface TimedValue {
|
||||
time: Date;
|
||||
value: Number;
|
||||
}
|
||||
|
||||
|
||||
interface ChartProps {
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
data?: TimedValue[],
|
||||
color: string,
|
||||
unit: string,
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
dataCollections?: any[],
|
||||
}
|
||||
|
||||
function CustomizedTooltip(props: ToolTipProps) {
|
||||
const { active, payload, unit } = props;
|
||||
@@ -41,13 +44,23 @@ function CustomizedTooltip(props: ToolTipProps) {
|
||||
CustomizedTooltip.defaultProps = defaultProps;
|
||||
|
||||
export default function Chart({ data, color, unit, dataCollections }: ChartProps) {
|
||||
if (!data && !dataCollections) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeFormatter = (tick: string) => {
|
||||
return timeFormat("%I:%M")(new Date(tick));
|
||||
};
|
||||
|
||||
let ticks = data.map((item) => item?.time);
|
||||
if (dataCollections) {
|
||||
ticks = dataCollections[0].data?.map((collection) => collection?.time);
|
||||
let ticks
|
||||
if (dataCollections.length > 0) {
|
||||
ticks = dataCollections[0].data?.map(function (collection) {
|
||||
return collection?.time;
|
||||
})
|
||||
} else if (data?.length > 0){
|
||||
ticks = data?.map(function (item) {
|
||||
return item?.time;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,13 +19,18 @@ function renderColumnLevel(text, entry) {
|
||||
return <div style={style}>{text}</div>;
|
||||
}
|
||||
|
||||
function renderMessage(text, entry) {
|
||||
function renderMessage(text) {
|
||||
return (
|
||||
<Linkify>{text}</Linkify>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LogTable({ logs, pageSize }) {
|
||||
interface Props {
|
||||
logs: object[],
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export default function LogTable({ logs, pageSize }: Props) {
|
||||
const columns = [
|
||||
{
|
||||
title: "Level",
|
||||
|
||||
@@ -9,14 +9,13 @@ import {
|
||||
LineChartOutlined,
|
||||
CloseCircleOutlined,
|
||||
PlayCircleFilled,
|
||||
StopFilled,
|
||||
MinusSquareFilled,
|
||||
} from '@ant-design/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
||||
import OwncastLogo from './logo';
|
||||
import { BroadcastStatusContext } from '../utils/broadcast-status-context';
|
||||
import { BroadcastStatusContext } from '../../utils/broadcast-status-context';
|
||||
|
||||
import adminStyles from '../../styles/styles.module.css';
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
/* eslint-disable no-array-constructor */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Row, Skeleton, Empty, Typography } from "antd";
|
||||
import { Row } from "antd";
|
||||
import {LaptopOutlined, BulbOutlined, SaveOutlined} from "@ant-design/icons"
|
||||
import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from './utils/apis';
|
||||
import { HARDWARE_STATS, fetchData, FETCH_INTERVAL } from '../utils/apis';
|
||||
import Chart from './components/chart';
|
||||
import StatisticItem from "./components/statistic";
|
||||
|
||||
interface TimedValue {
|
||||
time: Date,
|
||||
value: Number
|
||||
}
|
||||
|
||||
export default function HardwareInfo() {
|
||||
const [hardwareStatus, setHardwareStatus] = useState({
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
disk: 0,
|
||||
message: '',
|
||||
cpu: Array<TimedValue>(),
|
||||
memory: Array<TimedValue>(),
|
||||
disk: Array<TimedValue>(),
|
||||
message: "",
|
||||
});
|
||||
|
||||
const getHardwareStatus = async () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import React, { useState, useEffect, useContext } from "react";
|
||||
import { Row, Skeleton, Empty, Typography } from "antd";
|
||||
import { UserOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { formatDistanceToNow, formatRelative } from "date-fns";
|
||||
import { BroadcastStatusContext } from "./utils/broadcast-status-context";
|
||||
import { BroadcastStatusContext } from "../utils/broadcast-status-context";
|
||||
import StatisticItem from "./components/statistic"
|
||||
import LogTable from "./components/log-table";
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
LOGS_WARN,
|
||||
fetchData,
|
||||
FETCH_INTERVAL,
|
||||
} from "./utils/apis";
|
||||
import { formatIPAddress, isEmptyObject } from "./utils/format";
|
||||
} from "../utils/apis";
|
||||
import { formatIPAddress, isEmptyObject } from "../utils/format";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@@ -60,7 +60,22 @@ export default function Stats() {
|
||||
|
||||
|
||||
// Pull in the server config so we can show config overview.
|
||||
const [config, setConfig] = useState([]);
|
||||
const [config, setConfig] = useState({
|
||||
streamKey: "",
|
||||
yp: {
|
||||
enabled: false,
|
||||
},
|
||||
videoSettings: {
|
||||
videoQualityVariants: [
|
||||
{
|
||||
audioPassthrough: false,
|
||||
videoBitrate: 0,
|
||||
audioBitrate: 0,
|
||||
framerate: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const [logs, setLogs] = useState([]);
|
||||
|
||||
const getConfig = async () => {
|
||||
@@ -108,6 +123,7 @@ export default function Stats() {
|
||||
: `${setting.audioBitrate} kbps`;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Row gutter={[16, 16]} key={index}>
|
||||
<StatisticItem
|
||||
title="Outbound Video Stream"
|
||||
|
||||
@@ -4,7 +4,7 @@ import LogTable from "./components/log-table"
|
||||
import {
|
||||
LOGS_ALL,
|
||||
fetchData,
|
||||
} from "./utils/apis";
|
||||
} from "../utils/apis";
|
||||
|
||||
const FETCH_INTERVAL = 5 * 1000; // 5 sec
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from "./utils/apis";
|
||||
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from "../utils/apis";
|
||||
import KeyValueTable from "./components/key-value-table";
|
||||
|
||||
function Storage({ config }) {
|
||||
if (!config) {
|
||||
if (!config || !config.s3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ function Storage({ config }) {
|
||||
}
|
||||
|
||||
export default function ServerConfig() {
|
||||
const [config, setConfig] = useState();
|
||||
const [config, setConfig] = useState({});
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
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';
|
||||
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from '../utils/apis';
|
||||
import { isEmptyObject } from '../utils/format';
|
||||
import KeyValueTable from "./components/key-value-table";
|
||||
|
||||
const { Title } = Typography;
|
||||
@@ -27,20 +28,23 @@ function SocialHandles({ config }) {
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>Social Handles</Title>
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={config.instanceDetails.socialHandles}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (!config.instanceDetails?.socialHandles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>Social Handles</Title>
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={config.instanceDetails.socialHandles}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InstanceDetails({ config }) {
|
||||
console.log(config)
|
||||
if (!config || isEmptyObject(config)) {
|
||||
return null;
|
||||
}
|
||||
@@ -102,7 +106,7 @@ function InstanceDetails({ config }) {
|
||||
}
|
||||
|
||||
function PageContent({ config }) {
|
||||
if (!config) {
|
||||
if (!config?.instanceDetails?.extraPageContent) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
@@ -117,7 +121,7 @@ function PageContent({ config }) {
|
||||
}
|
||||
|
||||
export default function ServerConfig() {
|
||||
const [config, setConfig] = useState();
|
||||
const [config, setConfig] = useState({});
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
const ADMIN_USERNAME = process.env.NEXT_PUBLIC_ADMIN_USERNAME;
|
||||
const ADMIN_STREAMKEY = process.env.NEXT_PUBLIC_ADMIN_STREAMKEY;
|
||||
const NEXT_PUBLIC_API_HOST = process.env.NEXT_PUBLIC_API_HOST;
|
||||
|
||||
const API_LOCATION = `${NEXT_PUBLIC_API_HOST}api/admin/`;
|
||||
|
||||
export const FETCH_INTERVAL = 15000;
|
||||
|
||||
// Current inbound broadcaster info
|
||||
export const BROADCASTER = `${API_LOCATION}broadcaster`;
|
||||
|
||||
// Disconnect inbound stream
|
||||
export const DISCONNECT = `${API_LOCATION}disconnect`;
|
||||
|
||||
// Change the current streaming key in memory
|
||||
export const STREAMKEY_CHANGE = `${API_LOCATION}changekey`;
|
||||
|
||||
// Current server config
|
||||
export const SERVER_CONFIG = `${API_LOCATION}serverconfig`;
|
||||
|
||||
// Get viewer count over time
|
||||
export const VIEWERS_OVER_TIME = `${API_LOCATION}viewersOverTime`;
|
||||
|
||||
// Get currently connected clients
|
||||
export const CONNECTED_CLIENTS = `${API_LOCATION}clients`;
|
||||
|
||||
// Get hardware stats
|
||||
export const HARDWARE_STATS = `${API_LOCATION}hardwarestats`;
|
||||
|
||||
// Get all logs
|
||||
export const LOGS_ALL = `${API_LOCATION}logs`;
|
||||
|
||||
// Get warnings + errors
|
||||
export const LOGS_WARN = `${API_LOCATION}logs/warnings`;
|
||||
|
||||
// Current Stream status.
|
||||
// This is literally the same as /api/status except it supports
|
||||
// auth.
|
||||
export const STREAM_STATUS = `${API_LOCATION}status`;
|
||||
|
||||
export async function fetchData(url) {
|
||||
const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Basic ${encoded}`,
|
||||
},
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const message = `An error has occured: ${response.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
const json = await response.json();
|
||||
return json;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { BROADCASTER, fetchData, FETCH_INTERVAL } from './apis';
|
||||
|
||||
const initialState = {
|
||||
broadcastActive: false,
|
||||
message: '',
|
||||
broadcaster: null,
|
||||
};
|
||||
|
||||
export const BroadcastStatusContext = React.createContext(initialState);
|
||||
|
||||
const BroadcastStatusProvider = ({ children }) => {
|
||||
const [broadcasterStatus, setBroadcasterStatus] = useState(initialState);
|
||||
|
||||
const getBroadcastStatus = async () => {
|
||||
try {
|
||||
const result = await fetchData(BROADCASTER);
|
||||
const broadcastActive = !!result.broadcaster || result.success;
|
||||
setBroadcasterStatus({ ...result, broadcastActive });
|
||||
|
||||
} catch (error) {
|
||||
setBroadcasterStatus({ ...broadcasterStatus, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let getStatusIntervalId = null;
|
||||
|
||||
getBroadcastStatus();
|
||||
getStatusIntervalId = setInterval(getBroadcastStatus, FETCH_INTERVAL);
|
||||
|
||||
// returned function will be called on component unmount
|
||||
return () => {
|
||||
clearInterval(getStatusIntervalId);
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<BroadcastStatusContext.Provider value={broadcasterStatus}>
|
||||
{children}
|
||||
</BroadcastStatusContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
BroadcastStatusProvider.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
export default BroadcastStatusProvider;
|
||||
@@ -1,19 +0,0 @@
|
||||
export function formatIPAddress(ipAddress: string): string {
|
||||
const ipAddressComponents = ipAddress.split(':')
|
||||
|
||||
// Wipe out the port component
|
||||
ipAddressComponents[ipAddressComponents.length - 1] = '';
|
||||
|
||||
let ip = ipAddressComponents.join(':')
|
||||
ip = ip.slice(0, ip.length - 1)
|
||||
if (ip === '[::1]' || ip === '127.0.0.1') {
|
||||
return "Localhost"
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
// check if obj is {}
|
||||
export function isEmptyObject(obj) {
|
||||
return !obj || Object.keys(obj).length === 0;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Table, Typography } from 'antd';
|
||||
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from './utils/apis';
|
||||
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL } from '../utils/apis';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
|
||||
function VideoVariants({ config }) {
|
||||
if (!config) {
|
||||
if (!config || !config.videoSettings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ function VideoVariants({ config }) {
|
||||
}
|
||||
|
||||
export default function VideoConfig() {
|
||||
const [config, setConfig] = useState();
|
||||
const [config, setConfig] = useState({});
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { timeFormat } from "d3-time-format";
|
||||
import { Table, Row } from "antd";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { UserOutlined} from "@ant-design/icons";
|
||||
import { SortOrder } from "antd/lib/table/interface";
|
||||
import Chart from "./components/chart";
|
||||
import StatisticItem from "./components/statistic";
|
||||
|
||||
import { BroadcastStatusContext } from './utils/broadcast-status-context';
|
||||
import { BroadcastStatusContext } from '../utils/broadcast-status-context';
|
||||
|
||||
import {
|
||||
CONNECTED_CLIENTS,
|
||||
STREAM_STATUS, VIEWERS_OVER_TIME,
|
||||
fetchData,
|
||||
} from "./utils/apis";
|
||||
} from "../utils/apis";
|
||||
|
||||
const FETCH_INTERVAL = 5 * 60 * 1000; // 5 mins
|
||||
|
||||
@@ -78,14 +80,14 @@ export default function ViewersOverTime() {
|
||||
key: "username",
|
||||
render: (username) => username || "-",
|
||||
sorter: (a, b) => a.username - b.username,
|
||||
sortDirections: ["descend", "ascend"],
|
||||
sortDirections: ["descend", "ascend"] as SortOrder[],
|
||||
},
|
||||
{
|
||||
title: "Messages sent",
|
||||
dataIndex: "messageCount",
|
||||
key: "messageCount",
|
||||
sorter: (a, b) => a.messageCount - b.messageCount,
|
||||
sortDirections: ["descend", "ascend"],
|
||||
sortDirections: ["descend", "ascend"] as SortOrder[],
|
||||
},
|
||||
{
|
||||
title: "Connected Time",
|
||||
@@ -106,34 +108,6 @@ export default function ViewersOverTime() {
|
||||
},
|
||||
];
|
||||
|
||||
const timeFormatter = (tick) => {
|
||||
return timeFormat("%H:%M")(new Date(tick));
|
||||
};
|
||||
|
||||
const CustomizedTooltip = (props) => {
|
||||
const { active, payload, label } = props;
|
||||
if (active) {
|
||||
const numViewers = payload && payload[0] && payload[0].value;
|
||||
const time = timeFormatter(label);
|
||||
const message = `${numViewers} viewer(s) at ${time}`;
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
<p className="label">{message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/*
|
||||
geo data looks like this
|
||||
"geo": {
|
||||
"countryCode": "US",
|
||||
"regionName": "California",
|
||||
"timeZone": "America/Los_Angeles"
|
||||
}
|
||||
*/
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Current Viewers</h2>
|
||||
|
||||
Reference in New Issue
Block a user