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

This commit is contained in:
gingervitis 2021-02-06 19:39:32 -08:00
commit 31728f2538
41 changed files with 290 additions and 291 deletions

View file

@ -1,6 +1,6 @@
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Slider } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { ServerStatusContext } from '../../utils/server-status-context';
const { Title } = Typography;

View file

@ -4,8 +4,8 @@ import { Typography } from 'antd';
import ToggleSwitch from './form-toggleswitch-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from './constants';
import { ServerStatusContext } from '../../utils/server-status-context';
import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from '../../utils/config-constants';
const { Title } = Typography;

View file

@ -4,7 +4,7 @@ import TextFieldWithSubmit, {
TEXTFIELD_TYPE_URL,
} from './form-textfield-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { ServerStatusContext } from '../../utils/server-status-context';
import {
postConfigUpdateToAPI,
TEXTFIELD_PROPS_INSTANCE_URL,
@ -12,9 +12,9 @@ import {
TEXTFIELD_PROPS_SERVER_SUMMARY,
TEXTFIELD_PROPS_LOGO,
API_YP_SWITCH,
} from './constants';
} from '../../utils/config-constants';
import { UpdateArgs } from '../../../types/config-section';
import { UpdateArgs } from '../../types/config-section';
export default function EditInstanceDetails() {
const [formDataValues, setFormDataValues] = useState(null);

View file

@ -5,17 +5,17 @@ import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
import TextFieldWithSubmit from './form-textfield-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { AlertMessageContext } from '../../../utils/alert-message-context';
import { ServerStatusContext } from '../../utils/server-status-context';
import { AlertMessageContext } from '../../utils/alert-message-context';
import {
TEXTFIELD_PROPS_FFMPEG,
TEXTFIELD_PROPS_RTMP_PORT,
TEXTFIELD_PROPS_STREAM_KEY,
TEXTFIELD_PROPS_WEB_PORT,
} from './constants';
} from '../../utils/config-constants';
import { UpdateArgs } from '../../../types/config-section';
import { UpdateArgs } from '../../types/config-section';
export default function EditInstanceDetails() {
const [formDataValues, setFormDataValues] = useState(null);
@ -55,7 +55,9 @@ export default function EditInstanceDetails() {
};
const showStreamKeyChangeMessage = () => {
setMessage('Changing your stream key will log you out of the admin and block you from streaming until you change the key in your broadcasting software.');
setMessage(
'Changing your stream key will log you out of the admin and block you from streaming until you change the key in your broadcasting software.',
);
};
const showFfmpegChangeMessage = () => {

View file

@ -3,19 +3,19 @@ import { Typography, Table, Button, Modal, Input } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { DeleteOutlined } from '@ant-design/icons';
import SocialDropdown from './social-icons-dropdown';
import { fetchData, NEXT_PUBLIC_API_HOST, SOCIAL_PLATFORMS_LIST } from '../../../utils/apis';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { fetchData, NEXT_PUBLIC_API_HOST, SOCIAL_PLATFORMS_LIST } from '../../utils/apis';
import { ServerStatusContext } from '../../utils/server-status-context';
import {
API_SOCIAL_HANDLES,
postConfigUpdateToAPI,
RESET_TIMEOUT,
DEFAULT_SOCIAL_HANDLE,
OTHER_SOCIAL_HANDLE_OPTION,
} from './constants';
import { SocialHandle, UpdateArgs } from '../../../types/config-section';
import { isValidUrl } from '../../../utils/urls';
} from '../../utils/config-constants';
import { SocialHandle, UpdateArgs } from '../../types/config-section';
import { isValidUrl } from '../../utils/urls';
import TextField from './form-textfield';
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../../utils/input-statuses';
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
const { Title } = Typography;
@ -43,9 +43,9 @@ export default function EditSocialLinks() {
let resetTimer = null;
const PLACEHOLDERS = {
'mastodon': 'https://mastodon.social/@username',
'twitter': 'https://twitter.com/username'
}
mastodon: 'https://mastodon.social/@username',
twitter: 'https://twitter.com/username',
};
const getAvailableIcons = async () => {
try {

View file

@ -1,26 +1,26 @@
import { Switch, Button, Collapse } from 'antd';
import classNames from 'classnames';
import React, { useContext, useState, useEffect } from 'react';
import { UpdateArgs } from '../../../types/config-section';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { AlertMessageContext } from '../../../utils/alert-message-context';
import { UpdateArgs } from '../../types/config-section';
import { ServerStatusContext } from '../../utils/server-status-context';
import { AlertMessageContext } from '../../utils/alert-message-context';
import {
postConfigUpdateToAPI,
API_S3_INFO,
RESET_TIMEOUT,
S3_TEXT_FIELDS_INFO,
} from './constants';
} from '../../utils/config-constants';
import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
} from '../../utils/input-statuses';
import TextField from './form-textfield';
import FormStatusIndicator from './form-status-indicator';
import { isValidUrl } from '../../../utils/urls';
import { isValidUrl } from '../../utils/urls';
const { Panel } = Collapse;

View file

@ -2,10 +2,14 @@
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Tag } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { FIELD_PROPS_TAGS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { ServerStatusContext } from '../../utils/server-status-context';
import {
FIELD_PROPS_TAGS,
RESET_TIMEOUT,
postConfigUpdateToAPI,
} from '../../utils/config-constants';
import TextField from './form-textfield';
import { UpdateArgs } from '../../../types/config-section';
import { UpdateArgs } from '../../types/config-section';
import {
createInputStatus,
StatusState,
@ -13,7 +17,7 @@ import {
STATUS_PROCESSING,
STATUS_SUCCESS,
STATUS_WARNING,
} from '../../../utils/input-statuses';
} from '../../utils/input-statuses';
const { Title } = Typography;

View file

@ -1,7 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { StatusState } from '../../../utils/input-statuses';
import { StatusState } from '../../utils/input-statuses';
interface FormStatusIndicatorProps {
status: StatusState;

View file

@ -1,9 +1,9 @@
import React, { useEffect, useState, useContext } from 'react';
import { Button } from 'antd';
import classNames from 'classnames';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { ServerStatusContext } from '../../utils/server-status-context';
import TextField, { TextFieldProps } from './form-textfield';
import {
createInputStatus,
@ -11,8 +11,8 @@ import {
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
import { UpdateArgs } from '../../../types/config-section';
} from '../../utils/input-statuses';
import { UpdateArgs } from '../../types/config-section';
import FormStatusIndicator from './form-status-indicator';
export const TEXTFIELD_TYPE_TEXT = 'default';

View file

@ -1,9 +1,9 @@
import React from 'react';
import classNames from 'classnames';
import { Input, InputNumber } from 'antd';
import { FieldUpdaterFunc } from '../../../types/config-section';
import { FieldUpdaterFunc } from '../../types/config-section';
// import InfoTip from '../info-tip';
import { StatusState } from '../../../utils/input-statuses';
import { StatusState } from '../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
export const TEXTFIELD_TYPE_TEXT = 'default';
@ -96,10 +96,6 @@ export default function TextField(props: TextFieldProps) {
type: 'number',
min: 1,
max: 10 ** maxLength - 1,
onKeyDown: (e: React.KeyboardEvent) => {
if (e.target.value.length > maxLength - 1) e.preventDefault();
return false;
},
};
} else if (type === TEXTFIELD_TYPE_URL) {
fieldProps = {
@ -140,7 +136,7 @@ export default function TextField(props: TextFieldProps) {
onBlur={handleBlur}
onPressEnter={handlePressEnter}
disabled={disabled}
value={value}
value={value as number | (readonly string[] & number)}
/>
</div>
<FormStatusIndicator status={status} />
@ -159,7 +155,7 @@ TextField.defaultProps = {
disabled: false,
// initialValue: '',
label: '',
maxLength: null,
maxLength: 255,
placeholder: '',
required: false,

View file

@ -6,12 +6,12 @@ import {
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
} from '../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { ServerStatusContext } from '../../utils/server-status-context';
import InfoTip from '../info-tip';
interface ToggleSwitchProps {

View file

@ -1,8 +1,8 @@
import React from 'react';
import { Select } from 'antd';
import { SocialHandleDropdownItem } from '../../../types/config-section';
import { NEXT_PUBLIC_API_HOST } from '../../../utils/apis';
import { OTHER_SOCIAL_HANDLE_OPTION } from './constants';
import { SocialHandleDropdownItem } from '../../types/config-section';
import { NEXT_PUBLIC_API_HOST } from '../../utils/apis';
import { OTHER_SOCIAL_HANDLE_OPTION } from '../../utils/config-constants';
interface DropdownProps {
iconList: SocialHandleDropdownItem[];

View file

@ -1,15 +1,19 @@
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Slider } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { AlertMessageContext } from '../../../utils/alert-message-context';
import { API_VIDEO_SEGMENTS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { ServerStatusContext } from '../../utils/server-status-context';
import { AlertMessageContext } from '../../utils/alert-message-context';
import {
API_VIDEO_SEGMENTS,
RESET_TIMEOUT,
postConfigUpdateToAPI,
} from '../../utils/config-constants';
import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
} from '../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
const { Title } = Typography;
@ -88,7 +92,9 @@ export default function VideoLatency() {
// setSubmitStatusMessage('Variants updated.');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
if (serverStatusData.online) {
setMessage('Your latency buffer setting will take effect the next time you begin a live stream.')
setMessage(
'Your latency buffer setting will take effect the next time you begin a live stream.',
);
}
},
onError: (message: string) => {
@ -109,11 +115,13 @@ export default function VideoLatency() {
<div className="config-video-segements-conatiner">
<Title level={3}>Latency Buffer</Title>
<p>
While it's natural to want to keep your latency as low as possible, you may experience reduced error tolerance and stability in some environments the lower you go.
</p>
For interactive live streams you may want to experiment with a lower latency, for non-interactive broadcasts you may want to increase it. <a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
<p>
While it's natural to want to keep your latency as low as possible, you may experience
reduced error tolerance and stability in some environments the lower you go.
</p>
For interactive live streams you may want to experiment with a lower latency, for
non-interactive broadcasts you may want to increase it.{' '}
<a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
<p></p>
<div className="segment-slider-container">
<Slider
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}

View file

@ -1,8 +1,8 @@
// This content populates the video variant modal, which is spawned from the variants table.
import React from 'react';
import { Slider, Switch, Collapse } from 'antd';
import { FieldUpdaterFunc, VideoVariant } from '../../../types/config-section';
import { DEFAULT_VARIANT_STATE } from './constants';
import { FieldUpdaterFunc, VideoVariant } from '../../types/config-section';
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants';
import InfoTip from '../info-tip';
import CPUUsageSelector from './cpu-usage';

View file

@ -4,9 +4,9 @@ import React, { useContext, useState } from 'react';
import { Typography, Table, Modal, Button } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { DeleteOutlined } from '@ant-design/icons';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { AlertMessageContext } from '../../../utils/alert-message-context';
import { UpdateArgs, VideoVariant } from '../../../types/config-section';
import { ServerStatusContext } from '../../utils/server-status-context';
import { AlertMessageContext } from '../../utils/alert-message-context';
import { UpdateArgs, VideoVariant } from '../../types/config-section';
import VideoVariantForm from './video-variant-form';
import {
@ -15,7 +15,7 @@ import {
SUCCESS_STATES,
RESET_TIMEOUT,
postConfigUpdateToAPI,
} from './constants';
} from '../../utils/config-constants';
const { Title } = Typography;

View file

@ -18,17 +18,17 @@ import {
ExperimentOutlined,
} from '@ant-design/icons';
import classNames from 'classnames';
import { upgradeVersionAvailable } from '../../utils/apis';
import { parseSecondsToDurationString } from '../../utils/format';
import { upgradeVersionAvailable } from '../utils/apis';
import { parseSecondsToDurationString } from '../utils/format';
import OwncastLogo from './logo';
import { ServerStatusContext } from '../../utils/server-status-context';
import { AlertMessageContext } from '../../utils/alert-message-context';
import { ServerStatusContext } from '../utils/server-status-context';
import { AlertMessageContext } from '../utils/alert-message-context';
import TextFieldWithSubmit from './config/form-textfield-with-submit';
import { TEXTFIELD_PROPS_STREAM_TITLE } from './config/constants';
import { TEXTFIELD_PROPS_STREAM_TITLE } from '../utils/config-constants';
import { UpdateArgs } from '../../types/config-section';
import { UpdateArgs } from '../types/config-section';
let performedUpgradeCheck = false;

View file

@ -7,10 +7,10 @@ import {
CheckCircleFilled,
ExclamationCircleFilled,
} from '@ant-design/icons';
import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from '../../utils/apis';
import { MessageType } from '../../types/chat';
import { OUTCOME_TIMEOUT } from '../chat';
import { isEmptyObject } from '../../utils/format';
import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from '../utils/apis';
import { MessageType } from '../types/chat';
import { OUTCOME_TIMEOUT } from '../pages/chat';
import { isEmptyObject } from '../utils/format';
interface MessageToggleProps {
isVisible: boolean;

View file

@ -23,7 +23,7 @@ import { AppProps } from 'next/app';
import ServerStatusProvider from '../utils/server-status-context';
import AlertMessageProvider from '../utils/alert-message-context';
import MainLayout from './components/main-layout';
import MainLayout from '../components/main-layout';
function App({ Component, pageProps }: AppProps) {
return (

View file

@ -1,14 +1,14 @@
import React, { useState, useEffect } from "react";
import { Table, Typography, Tooltip, Button } from "antd";
import { CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons";
import React, { useState, useEffect } from 'react';
import { Table, Typography, Tooltip, Button } from 'antd';
import { CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
import classNames from 'classnames';
import { ColumnsType } from 'antd/es/table';
import format from 'date-fns/format'
import format from 'date-fns/format';
import { CHAT_HISTORY, fetchData, FETCH_INTERVAL, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis";
import { CHAT_HISTORY, fetchData, FETCH_INTERVAL, UPDATE_CHAT_MESSGAE_VIZ } from '../utils/apis';
import { MessageType } from '../types/chat';
import { isEmptyObject } from "../utils/format";
import MessageVisiblityToggle from "./components/message-visiblity-toggle";
import { isEmptyObject } from '../utils/format';
import MessageVisiblityToggle from '../components/message-visiblity-toggle';
const { Title } = Typography;
@ -55,7 +55,7 @@ export default function Chat() {
setMessages(result);
}
} catch (error) {
console.log("==== error", error);
console.log('==== error', error);
}
};
@ -73,7 +73,7 @@ export default function Chat() {
}, []);
const nameFilters = createUserNameFilters(messages);
const rowSelection = {
selectedRowKeys,
onChange: (selectedKeys: string[]) => {
@ -81,10 +81,10 @@ export default function Chat() {
},
};
const updateMessage = message => {
const messageIndex = messages.findIndex(m => m.id === message.id);
messages.splice(messageIndex, 1, message)
setMessages([...messages]);
const updateMessage = message => {
const messageIndex = messages.findIndex(m => m.id === message.id);
messages.splice(messageIndex, 1, message);
setMessages([...messages]);
};
const resetBulkOutcome = () => {
@ -93,7 +93,7 @@ export default function Chat() {
setBulkAction('');
}, OUTCOME_TIMEOUT);
};
const handleSubmitBulk = async (bulkVisibility) => {
const handleSubmitBulk = async bulkVisibility => {
setBulkProcessing(true);
const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, {
auth: true,
@ -104,7 +104,7 @@ export default function Chat() {
},
});
if (result.success && result.message === "changed") {
if (result.success && result.message === 'changed') {
setBulkOutcome(<CheckCircleFilled />);
resetBulkOutcome();
@ -112,7 +112,7 @@ export default function Chat() {
const updatedList = [...messages];
selectedRowKeys.map(key => {
const messageIndex = updatedList.findIndex(m => m.id === key);
const newMessage = {...messages[messageIndex], visible: bulkVisibility };
const newMessage = { ...messages[messageIndex], visible: bulkVisibility };
updatedList.splice(messageIndex, 1, newMessage);
return null;
});
@ -123,15 +123,15 @@ export default function Chat() {
resetBulkOutcome();
}
setBulkProcessing(false);
}
};
const handleSubmitBulkShow = () => {
setBulkAction('show');
handleSubmitBulk(true);
}
};
const handleSubmitBulkHide = () => {
setBulkAction('hide');
handleSubmitBulk(false);
}
};
const chatColumns: ColumnsType<MessageType> = [
{
@ -140,7 +140,7 @@ export default function Chat() {
key: 'timestamp',
className: 'timestamp-col',
defaultSortOrder: 'descend',
render: (timestamp) => {
render: timestamp => {
const dateObject = new Date(timestamp);
return format(dateObject, 'PP pp');
},
@ -155,7 +155,7 @@ export default function Chat() {
filters: nameFilters,
onFilter: (value, record) => record.author === value,
sorter: (a, b) => a.author.localeCompare(b.author),
sortDirections: ['ascend', 'descend'],
sortDirections: ['ascend', 'descend'],
ellipsis: true,
render: author => (
<Tooltip placement="topLeft" title={author}>
@ -176,21 +176,20 @@ export default function Chat() {
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: body }}
/>
)
),
},
{
title: '',
dataIndex: 'visible',
key: 'visible',
className: 'toggle-col',
filters: [{ text: 'Visible messages', value: true }, { text: 'Hidden messages', value: false }],
filters: [
{ text: 'Visible messages', value: true },
{ text: 'Hidden messages', value: false },
],
onFilter: (value, record) => record.visible === value,
render: (visible, record) => (
<MessageVisiblityToggle
isVisible={visible}
message={record}
setMessage={updateMessage}
/>
<MessageVisiblityToggle isVisible={visible} message={record} setMessage={updateMessage} />
),
width: 30,
},
@ -200,7 +199,7 @@ export default function Chat() {
'bulk-editor': true,
active: selectedRowKeys.length,
});
return (
<div className="chat-messages">
<Title level={2}>Chat Messages</Title>
@ -236,14 +235,14 @@ export default function Chat() {
<Table
size="small"
className="messages-table"
pagination={{ pageSize: 100 }}
pagination={{ pageSize: 100 }}
scroll={{ y: 540 }}
rowClassName={record => !record.visible ? 'hidden' : ''}
rowClassName={record => (!record.visible ? 'hidden' : '')}
dataSource={messages}
columns={chatColumns}
rowKey={(row) => row.id}
rowKey={row => row.id}
rowSelection={rowSelection}
/>
</div>)
</div>
);
}

View file

@ -8,7 +8,7 @@ import {
postConfigUpdateToAPI,
RESET_TIMEOUT,
API_CUSTOM_CONTENT,
} from './components/config/constants';
} from '../utils/config-constants';
import {
createInputStatus,
StatusState,
@ -17,7 +17,7 @@ import {
STATUS_SUCCESS,
} from '../utils/input-statuses';
import 'react-markdown-editor-lite/lib/index.css';
import FormStatusIndicator from './components/config/form-status-indicator';
import FormStatusIndicator from '../components/config/form-status-indicator';
const { Title } = Typography;
@ -87,7 +87,8 @@ export default function PageContentEditor() {
<Title level={2}>Page Content</Title>
<p>
Edit the content of your page by using simple <a href="https://www.markdownguide.org/basic-syntax/">Markdown syntax</a>.
Edit the content of your page by using simple{' '}
<a href="https://www.markdownguide.org/basic-syntax/">Markdown syntax</a>.
</p>
<MdEditor
@ -100,7 +101,7 @@ export default function PageContentEditor() {
markdownClass: 'markdown-editor-pane',
}}
/>
<div className="page-content-actions">
{hasChanged ? (
<Button type="primary" onClick={handleSave}>
@ -108,7 +109,6 @@ export default function PageContentEditor() {
</Button>
) : null}
<FormStatusIndicator status={submitStatus} />
</div>
</div>
);

View file

@ -2,9 +2,9 @@ import React from 'react';
import { Typography } from 'antd';
import Link from 'next/link';
import EditInstanceDetails from './components/config/edit-instance-details';
import EditDirectoryDetails from './components/config/edit-directory';
import EditInstanceTags from './components/config/edit-tags';
import EditInstanceDetails from '../components/config/edit-instance-details';
import EditDirectoryDetails from '../components/config/edit-directory';
import EditInstanceTags from '../components/config/edit-tags';
const { Title } = Typography;
@ -13,7 +13,8 @@ export default function PublicFacingDetails() {
<>
<Title level={2}>General Settings</Title>
<p>
The following are displayed on your site to describe your stream and its content. <a href="https://owncast.online/docs/website/">Learn more.</a>
The following are displayed on your site to describe your stream and its content.{' '}
<a href="https://owncast.online/docs/website/">Learn more.</a>
</p>
<div className="edit-public-details-container">
<EditInstanceDetails />

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Typography } from 'antd';
import EditServerDetails from './components/config/edit-server-details';
import EditServerDetails from '../components/config/edit-server-details';
const { Title } = Typography;
@ -9,7 +9,8 @@ export default function ConfigServerDetails() {
<div className="config-server-details-form">
<Title level={2}>Server Settings</Title>
<p>
You should change your stream key from the default and keep it safe. For most people it's likely the other settings will not need to be changed.
You should change your stream key from the default and keep it safe. For most people it's
likely the other settings will not need to be changed.
</p>
<div className="config-server-details-container">
<EditServerDetails />

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Typography } from 'antd';
import EditSocialLinks from './components/config/edit-social-links';
import EditSocialLinks from '../components/config/edit-social-links';
const { Title } = Typography;

View file

@ -1,6 +1,6 @@
import { Typography } from 'antd';
import React from 'react';
import EditStorage from './components/config/edit-storage';
import EditStorage from '../components/config/edit-storage';
const { Title } = Typography;
@ -9,10 +9,14 @@ export default function ConfigStorageInfo() {
<>
<Title level={2}>Storage</Title>
<p>
Owncast supports optionally using external storage providers to distribute your video. Learn more about this by visiting our <a href="https://owncast.online/docs/storage/">Storage Documentation</a>.
Owncast supports optionally using external storage providers to distribute your video. Learn
more about this by visiting our{' '}
<a href="https://owncast.online/docs/storage/">Storage Documentation</a>.
</p>
<p>
Configuring this incorrectly will likely cause your video to be unplayable. Double check the documentation for your storage provider on how to configure the bucket you created for Owncast.
Configuring this incorrectly will likely cause your video to be unplayable. Double check the
documentation for your storage provider on how to configure the bucket you created for
Owncast.
</p>
<EditStorage />
</>

View file

@ -1,8 +1,8 @@
import React from 'react';
import { Typography } from 'antd';
import VideoVariantsTable from './components/config/video-variants-table';
import VideoLatency from './components/config/video-latency';
import VideoVariantsTable from '../components/config/video-variants-table';
import VideoLatency from '../components/config/video-latency';
const { Title } = Typography;
@ -11,18 +11,20 @@ export default function ConfigVideoSettings() {
<div className="config-video-variants">
<Title level={2}>Video configuration</Title>
<p>
Before changing your video configuration <a href="https://owncast.online/docs/encoding">visit the video documentation</a>{' '}
to learn how it impacts your stream performance.
Before changing your video configuration{' '}
<a href="https://owncast.online/docs/encoding">visit the video documentation</a> to learn
how it impacts your stream performance.
</p>
<p>
<VideoVariantsTable />
</p>
<br/><hr/><br/>
<p>
<VideoLatency />
</p>
<p>
<VideoVariantsTable />
</p>
<br />
<hr />
<br />
<p>
<VideoLatency />
</p>
</div>
);
}

View file

@ -1,13 +1,13 @@
import { BulbOutlined, LaptopOutlined, SaveOutlined } from "@ant-design/icons";
import { Row } from "antd";
import { BulbOutlined, LaptopOutlined, SaveOutlined } from '@ant-design/icons';
import { Row } from 'antd';
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";
import Chart from '../components/chart';
import StatisticItem from '../components/statistic';
interface TimedValue {
time: Date,
value: Number
time: Date;
value: Number;
}
export default function HardwareInfo() {
@ -15,92 +15,88 @@ export default function HardwareInfo() {
cpu: Array<TimedValue>(),
memory: Array<TimedValue>(),
disk: Array<TimedValue>(),
message: "",
message: '',
});
const getHardwareStatus = async () => {
try {
const result = await fetchData(HARDWARE_STATS);
setHardwareStatus({ ...result });
} catch (error) {
setHardwareStatus({ ...hardwareStatus, message: error.message });
}
};
useEffect(() => {
let getStatusIntervalId = null;
getHardwareStatus();
getStatusIntervalId = setInterval(getHardwareStatus, FETCH_INTERVAL); // runs every 1 min.
// returned function will be called on component unmount
// returned function will be called on component unmount
return () => {
clearInterval(getStatusIntervalId);
}
};
}, []);
if (!hardwareStatus.cpu) {
return null;
}
const currentCPUUsage = hardwareStatus.cpu[hardwareStatus.cpu.length - 1]?.value;
const currentRamUsage =
hardwareStatus.memory[hardwareStatus.memory.length - 1]?.value;
const currentDiskUsage =
hardwareStatus.disk[hardwareStatus.disk.length - 1]?.value;
const series = [
{
name: "CPU",
color: "#B63FFF",
data: hardwareStatus.cpu,
},
{
name: "Memory",
color: "#2087E2",
data: hardwareStatus.memory,
},
{
name: "Disk",
color: "#FF7700",
data: hardwareStatus.disk,
},
];
return (
<div>
<div>
<Row gutter={[16, 16]} justify="space-around">
<StatisticItem
title={series[0].name}
value={`${currentCPUUsage}`}
prefix={<LaptopOutlined style={{color: series[0].color }}/>}
color={series[0].color}
progress
centered
/>
<StatisticItem
title={series[1].name}
value={`${currentRamUsage}`}
prefix={<BulbOutlined style={{color: series[1].color }} />}
color={series[1].color}
progress
centered
/>
<StatisticItem
title={series[2].name}
value={`${currentDiskUsage}`}
prefix={<SaveOutlined style={{color: series[2].color }} />}
color={series[2].color}
progress
centered
/>
</Row>
const currentRamUsage = hardwareStatus.memory[hardwareStatus.memory.length - 1]?.value;
const currentDiskUsage = hardwareStatus.disk[hardwareStatus.disk.length - 1]?.value;
<Chart title="% used" dataCollections={series} color="#FF7700" unit="%" />
</div>
const series = [
{
name: 'CPU',
color: '#B63FFF',
data: hardwareStatus.cpu,
},
{
name: 'Memory',
color: '#2087E2',
data: hardwareStatus.memory,
},
{
name: 'Disk',
color: '#FF7700',
data: hardwareStatus.disk,
},
];
return (
<div>
<div>
<Row gutter={[16, 16]} justify="space-around">
<StatisticItem
title={series[0].name}
value={`${currentCPUUsage}`}
prefix={<LaptopOutlined style={{ color: series[0].color }} />}
color={series[0].color}
progress
centered
/>
<StatisticItem
title={series[1].name}
value={`${currentRamUsage}`}
prefix={<BulbOutlined style={{ color: series[1].color }} />}
color={series[1].color}
progress
centered
/>
<StatisticItem
title={series[2].name}
value={`${currentDiskUsage}`}
prefix={<SaveOutlined style={{ color: series[2].color }} />}
color={series[2].color}
progress
centered
/>
</Row>
<Chart title="% used" dataCollections={series} color="#FF7700" unit="%" />
</div>
);
}
</div>
);
}

View file

@ -7,29 +7,29 @@ Will display an overview with the following datasources:
TODO: Link each overview value to the sub-page that focuses on it.
*/
import React, { useState, useEffect, useContext } from "react";
import { Skeleton, Card, Statistic } from "antd";
import { UserOutlined, ClockCircleOutlined } from "@ant-design/icons";
import { formatDistanceToNow, formatRelative } from "date-fns";
import { ServerStatusContext } from "../utils/server-status-context";
import StatisticItem from "./components/statistic"
import LogTable from "./components/log-table";
import React, { useState, useEffect, useContext } from 'react';
import { Skeleton, Card, Statistic } from 'antd';
import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons';
import { formatDistanceToNow, formatRelative } from 'date-fns';
import { ServerStatusContext } from '../utils/server-status-context';
import StatisticItem from '../components/statistic';
import LogTable from '../components/log-table';
import Offline from './offline-notice';
import {
LOGS_WARN,
fetchData,
FETCH_INTERVAL,
} from "../utils/apis";
import { formatIPAddress, isEmptyObject } from "../utils/format";
import { UpdateArgs } from "../types/config-section";
import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../utils/apis';
import { formatIPAddress, isEmptyObject } from '../utils/format';
import { UpdateArgs } from '../types/config-section';
function streamDetailsFormatter(streamDetails) {
return (
<ul className="statistics-list">
<li>{streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps</li>
<li>
{streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps
</li>
<li>{streamDetails.framerate || 'Unknown'} fps</li>
<li>{streamDetails.width} x {streamDetails.height}</li>
<li>
{streamDetails.width} x {streamDetails.height}
</li>
</ul>
);
}
@ -39,7 +39,7 @@ export default function Home() {
const { broadcaster, serverConfig: configData } = serverStatusData || {};
const { remoteAddr, streamDetails } = broadcaster || {};
const encoder = streamDetails?.encoder || "Unknown encoder";
const encoder = streamDetails?.encoder || 'Unknown encoder';
const [logsData, setLogs] = useState([]);
const getLogs = async () => {
@ -47,22 +47,22 @@ export default function Home() {
const result = await fetchData(LOGS_WARN);
setLogs(result);
} catch (error) {
console.log("==== error", error);
console.log('==== error', error);
}
};
const getMoreStats = () => {
getLogs();
}
};
useEffect(() => {
getMoreStats();
let intervalId = null;
intervalId = setInterval(getMoreStats, FETCH_INTERVAL);
return () => {
clearInterval(intervalId);
}
};
}, []);
if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) {
@ -80,40 +80,38 @@ export default function Home() {
}
// map out settings
const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map((setting, index) => {
const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting;
const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map(
(setting, index) => {
const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting;
const audioSetting = audioPassthrough
? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps`
: `${audioBitrate || 'Unknown'} kbps`;
const audioSetting = audioPassthrough
? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps`
: `${audioBitrate || 'Unknown'} kbps`;
const videoSetting = videoPassthrough
? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${streamDetails.width} x ${streamDetails.height}`
: `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`;
const videoSetting = videoPassthrough
? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${
streamDetails.width
} x ${streamDetails.height}`
: `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`;
let settingTitle = 'Outbound Stream Details';
settingTitle = (videoQualitySettings?.length > 1) ?
`${settingTitle} ${index + 1}` : settingTitle;
return (
<Card title={settingTitle} type="inner" key={`${settingTitle}${index}`}>
<StatisticItem
title="Outbound Video Stream"
value={videoSetting}
prefix={null}
/>
<StatisticItem
title="Outbound Audio Stream"
value={audioSetting}
prefix={null}
/>
</Card>
);
});
let settingTitle = 'Outbound Stream Details';
settingTitle =
videoQualitySettings?.length > 1 ? `${settingTitle} ${index + 1}` : settingTitle;
return (
<Card title={settingTitle} type="inner" key={`${settingTitle}${index}`}>
<StatisticItem title="Outbound Video Stream" value={videoSetting} prefix={null} />
<StatisticItem title="Outbound Audio Stream" value={audioSetting} prefix={null} />
</Card>
);
},
);
// inbound
const { viewerCount, sessionPeakViewerCount } = serverStatusData;
const streamAudioDetailString = `${streamDetails.audioCodec}, ${streamDetails.audioBitrate || 'Unknown'} kbps`;
const streamAudioDetailString = `${streamDetails.audioCodec}, ${
streamDetails.audioBitrate || 'Unknown'
} kbps`;
const broadcastDate = new Date(broadcaster.time);
@ -123,18 +121,11 @@ export default function Home() {
<div className="section online-status-section">
<Card title="Stream is online" type="inner">
<Statistic
title={`Stream started ${formatRelative(
broadcastDate,
Date.now()
)}`}
title={`Stream started ${formatRelative(broadcastDate, Date.now())}`}
value={formatDistanceToNow(broadcastDate)}
prefix={<ClockCircleOutlined />}
/>
<Statistic
title="Viewers"
value={viewerCount}
prefix={<UserOutlined />}
/>
<Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} />
<Statistic
title="Peak viewer count"
value={sessionPeakViewerCount}
@ -144,10 +135,7 @@ export default function Home() {
</div>
<div className="section stream-details-section">
<div className="details outbound-details">
{videoQualitySettings}
</div>
<div className="details outbound-details">{videoQualitySettings}</div>
<div className="details other-details">
<Card title="Inbound Stream Details" type="inner">

View file

@ -1,10 +1,7 @@
import React, { useState, useEffect } from "react";
import LogTable from "./components/log-table";
import React, { useState, useEffect } from 'react';
import LogTable from '../components/log-table';
import {
LOGS_ALL,
fetchData,
} from "../utils/apis";
import { LOGS_ALL, fetchData } from '../utils/apis';
const FETCH_INTERVAL = 5 * 1000; // 5 sec
@ -16,7 +13,7 @@ export default function Logs() {
const result = await fetchData(LOGS_ALL);
setLogs(result);
} catch (error) {
console.log("==== error", error);
console.log('==== error', error);
}
};
@ -33,6 +30,5 @@ export default function Logs() {
};
}, []);
return <LogTable logs={logs} pageSize={20}/>;
return <LogTable logs={logs} pageSize={20} />;
}

View file

@ -6,8 +6,8 @@ import {
BookTwoTone,
PlaySquareTwoTone,
} from '@ant-design/icons';
import OwncastLogo from './components/logo';
import LogTable from './components/log-table';
import OwncastLogo from '../components/logo';
import LogTable from '../components/log-table';
const { Meta } = Card;

View file

@ -3,8 +3,8 @@ 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 Chart from '../components/chart';
import StatisticItem from '../components/statistic';
import { ServerStatusContext } from '../utils/server-status-context';

View file

@ -3,9 +3,8 @@ import PropTypes from 'prop-types';
export const AlertMessageContext = React.createContext({
message: null,
setMessage: (text?: string) => {
return text;
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setMessage: (text?: string) => null,
});
const AlertMessageProvider = ({ children }) => {

View file

@ -1,8 +1,8 @@
// DEFAULT VALUES
import React from 'react';
import { CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis';
import { ApiPostArgs, VideoVariant, SocialHandle } from '../../../types/config-section';
import { fetchData, SERVER_CONFIG_UPDATE_URL } from './apis';
import { ApiPostArgs, VideoVariant, SocialHandle } from '../types/config-section';
export const TEXT_MAXLENGTH = 255;

View file

@ -15,13 +15,16 @@ export const STATUS_WARNING = 'warning';
export type InputStatusTypes = 'error' | 'invalid' | 'proessing' | 'success' | 'warning';
export type StatusState = {
export interface StatusState {
type: InputStatusTypes;
icon: any; // Element type of sorts?
message: string;
};
}
interface InputStates {
[key: string]: StatusState;
}
export const INPUT_STATES = {
export const INPUT_STATES: InputStates = {
[STATUS_SUCCESS]: {
type: STATUS_SUCCESS,
icon: <CheckCircleFilled style={{ color: 'green' }} />,

View file

@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { STATUS, fetchData, FETCH_INTERVAL, SERVER_CONFIG } from './apis';
import { ConfigDetails, UpdateArgs } from '../types/config-section';
import { DEFAULT_VARIANT_STATE } from '../pages/components/config/constants';
import { DEFAULT_VARIANT_STATE } from './config-constants';
export const initialServerConfigState: ConfigDetails = {
streamKey: '',
@ -47,6 +47,7 @@ export const initialServerConfigState: ConfigDetails = {
const initialServerStatusState = {
broadcastActive: false,
broadcaster: null,
currentBroadcast: null,
online: false,
viewerCount: 0,
sessionMaxViewerCount: 0,
@ -60,9 +61,8 @@ export const ServerStatusContext = React.createContext({
...initialServerStatusState,
serverConfig: initialServerConfigState,
setFieldInConfigState: (args: UpdateArgs) => {
return args;
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setFieldInConfigState: (args: UpdateArgs) => null,
});
const ServerStatusProvider = ({ children }) => {