mirror of
https://github.com/owncast/owncast.git
synced 2025-11-03 04:27:18 +08:00
Updates to the admin to reflect new stream keys and admin password split
This commit is contained in:
@ -173,6 +173,10 @@ export const MainLayout: FC<MainLayoutProps> = ({ children }) => {
|
|||||||
label: <Link href="/admin/config-server-details">Server Setup</Link>,
|
label: <Link href="/admin/config-server-details">Server Setup</Link>,
|
||||||
key: 'config-server-details',
|
key: 'config-server-details',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: <Link href="/admin/config/streamkeys/">Server Setup</Link>,
|
||||||
|
key: 'config-streamkeys',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: <Link href="/admin/config-video">Video</Link>,
|
label: <Link href="/admin/config-video">Video</Link>,
|
||||||
key: 'config-video',
|
key: 'config-video',
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
|||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
|
||||||
const { serverConfig } = serverStatusData || {};
|
const { serverConfig } = serverStatusData || {};
|
||||||
const { streamKey, rtmpServerPort } = serverConfig;
|
const { rtmpServerPort } = serverConfig;
|
||||||
const instanceUrl = global.window?.location.hostname || '';
|
const instanceUrl = global.window?.location.hostname || '';
|
||||||
|
|
||||||
let rtmpURL;
|
let rtmpURL;
|
||||||
@ -58,11 +58,11 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
)}
|
)}
|
||||||
<Text strong className="stream-info-label">
|
<Text strong className="stream-info-label">
|
||||||
Stream Key:
|
Streaming Keys:
|
||||||
|
</Text>
|
||||||
|
<Text strong className="stream-info-box">
|
||||||
|
<Link href="/admin/config/streamkeys"> View </Link>
|
||||||
</Text>
|
</Text>
|
||||||
<Paragraph className="stream-info-box" copyable={{ text: streamKey }}>
|
|
||||||
*********************
|
|
||||||
</Paragraph>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
TEXTFIELD_PROPS_FFMPEG,
|
TEXTFIELD_PROPS_FFMPEG,
|
||||||
TEXTFIELD_PROPS_RTMP_PORT,
|
TEXTFIELD_PROPS_RTMP_PORT,
|
||||||
TEXTFIELD_PROPS_SOCKET_HOST_OVERRIDE,
|
TEXTFIELD_PROPS_SOCKET_HOST_OVERRIDE,
|
||||||
TEXTFIELD_PROPS_STREAM_KEY,
|
TEXTFIELD_PROPS_ADMIN_PASSWORD,
|
||||||
TEXTFIELD_PROPS_WEB_PORT,
|
TEXTFIELD_PROPS_WEB_PORT,
|
||||||
} from '../../utils/config-constants';
|
} from '../../utils/config-constants';
|
||||||
import { UpdateArgs } from '../../types/config-section';
|
import { UpdateArgs } from '../../types/config-section';
|
||||||
@ -24,7 +24,7 @@ export const EditInstanceDetails = () => {
|
|||||||
|
|
||||||
const { serverConfig } = serverStatusData || {};
|
const { serverConfig } = serverStatusData || {};
|
||||||
|
|
||||||
const { streamKey, ffmpegPath, rtmpServerPort, webServerPort, yp, socketHostOverride } =
|
const { adminPassword, ffmpegPath, rtmpServerPort, webServerPort, yp, socketHostOverride } =
|
||||||
serverConfig;
|
serverConfig;
|
||||||
|
|
||||||
const [copyIsVisible, setCopyVisible] = useState(false);
|
const [copyIsVisible, setCopyVisible] = useState(false);
|
||||||
@ -33,7 +33,7 @@ export const EditInstanceDetails = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFormDataValues({
|
setFormDataValues({
|
||||||
streamKey,
|
adminPassword,
|
||||||
ffmpegPath,
|
ffmpegPath,
|
||||||
rtmpServerPort,
|
rtmpServerPort,
|
||||||
webServerPort,
|
webServerPort,
|
||||||
@ -89,10 +89,10 @@ export const EditInstanceDetails = () => {
|
|||||||
<div className="field-container field-streamkey-container">
|
<div className="field-container field-streamkey-container">
|
||||||
<div className="left-side">
|
<div className="left-side">
|
||||||
<TextFieldWithSubmit
|
<TextFieldWithSubmit
|
||||||
fieldName="streamKey"
|
fieldName="adminPassword"
|
||||||
{...TEXTFIELD_PROPS_STREAM_KEY}
|
{...TEXTFIELD_PROPS_ADMIN_PASSWORD}
|
||||||
value={formDataValues.streamKey}
|
value={formDataValues.adminPassword}
|
||||||
initialValue={streamKey}
|
initialValue={adminPassword}
|
||||||
type={TEXTFIELD_TYPE_PASSWORD}
|
type={TEXTFIELD_TYPE_PASSWORD}
|
||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
onSubmit={showStreamKeyChangeMessage}
|
onSubmit={showStreamKeyChangeMessage}
|
||||||
|
|||||||
174
web/pages/admin/config/streamkeys/index.tsx
Normal file
174
web/pages/admin/config/streamkeys/index.tsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { Table, Space, Button, Typography, Alert, Input, Form } from 'antd';
|
||||||
|
import { DeleteOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
||||||
|
|
||||||
|
import { fetchData, UPDATE_STREAM_KEYS } from '../../../../utils/apis';
|
||||||
|
|
||||||
|
const { Title, Paragraph } = Typography;
|
||||||
|
const { Item } = Form;
|
||||||
|
|
||||||
|
const saveKeys = async (keys, setError) => {
|
||||||
|
try {
|
||||||
|
await fetchData(UPDATE_STREAM_KEYS, {
|
||||||
|
method: 'POST',
|
||||||
|
auth: true,
|
||||||
|
data: { value: keys },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setError }) => {
|
||||||
|
const handleAddKey = (newkey: any) => {
|
||||||
|
const updatedKeys = [...streamKeys, newkey];
|
||||||
|
|
||||||
|
console.log(updatedKeys);
|
||||||
|
setFieldInConfigState({
|
||||||
|
fieldName: 'streamKeys',
|
||||||
|
value: updatedKeys,
|
||||||
|
});
|
||||||
|
|
||||||
|
saveKeys(updatedKeys, setError);
|
||||||
|
|
||||||
|
setShowAddKeyForm(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form layout="inline" autoComplete="off" onFinish={handleAddKey}>
|
||||||
|
<Item label="Key" name="key" tooltip="The key you provide your broadcasting software">
|
||||||
|
<Input placeholder="def456" />
|
||||||
|
</Item>
|
||||||
|
<Item label="Comment" name="comment" tooltip="For remembering why you added this key">
|
||||||
|
<Input placeholder="My OBS Key" />
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddKeyButton = ({ setShowAddKeyForm }) => (
|
||||||
|
<Button type="default" onClick={() => setShowAddKeyForm(true)}>
|
||||||
|
<PlusOutlined />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const StreamKeys = () => {
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
|
const { streamKeys } = serverConfig;
|
||||||
|
const [showAddKeyForm, setShowAddKeyForm] = useState(false);
|
||||||
|
const [showKeyMap, setShowKeyMap] = useState({});
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
const handleDeleteKey = keyToRemove => {
|
||||||
|
const newKeys = streamKeys.filter(k => k !== keyToRemove);
|
||||||
|
setFieldInConfigState({
|
||||||
|
fieldName: 'streamKeys',
|
||||||
|
value: newKeys,
|
||||||
|
});
|
||||||
|
saveKeys(newKeys, setError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleShowKey = key => {
|
||||||
|
setShowKeyMap({
|
||||||
|
...showKeyMap,
|
||||||
|
[key]: !showKeyMap[key],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Key',
|
||||||
|
dataIndex: 'key',
|
||||||
|
key: 'key',
|
||||||
|
render: text => (
|
||||||
|
<Space direction="horizontal">
|
||||||
|
<Paragraph copyable>{showKeyMap[text] ? text : '**********'}</Paragraph>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
style={{ top: '-7px' }}
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => handleToggleShowKey(text)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Comment',
|
||||||
|
dataIndex: 'comment',
|
||||||
|
key: 'comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
key: 'delete',
|
||||||
|
render: text => <Button onClick={() => handleDeleteKey(text)} icon={<DeleteOutlined />} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title>Streaming Keys</Title>
|
||||||
|
<Paragraph>
|
||||||
|
A streaming key is used with your broadcasting software to authenticate itself to Owncast.
|
||||||
|
Most people will only need one. However, if you share a server with others or you want
|
||||||
|
different keys for different broadcasting sources you can add more here.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
These keys are unrelated to the admin password and will not grant you access to make changes
|
||||||
|
to Owncast's configuration.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
Read more about broadcasting at{' '}
|
||||||
|
<a
|
||||||
|
href="https://owncast.online/docs/broadcasting/?source=admin"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
the documentation
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Space direction="vertical" style={{ width: '70%' }}>
|
||||||
|
{error && <Alert type="error" message="Saving Keys Error" description={error} />}
|
||||||
|
|
||||||
|
{streamKeys.length === 0 && (
|
||||||
|
<Alert
|
||||||
|
message="No stream keys!"
|
||||||
|
description="You will not be able to stream until you create at least one stream key and add it to your broadcasting software."
|
||||||
|
type="error"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
rowKey="key"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={streamKeys}
|
||||||
|
pagination={false}
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
footer={() =>
|
||||||
|
showAddKeyForm ? (
|
||||||
|
<AddKeyForm
|
||||||
|
setShowAddKeyForm={setShowAddKeyForm}
|
||||||
|
streamKeys={streamKeys}
|
||||||
|
setFieldInConfigState={setFieldInConfigState}
|
||||||
|
setError={setError}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AddKeyButton setShowAddKeyForm={setShowAddKeyForm} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default StreamKeys;
|
||||||
@ -138,13 +138,19 @@ export interface Health {
|
|||||||
representation: number;
|
representation: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StreamKey {
|
||||||
|
key: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigDetails {
|
export interface ConfigDetails {
|
||||||
externalActions: ExternalAction[];
|
externalActions: ExternalAction[];
|
||||||
ffmpegPath: string;
|
ffmpegPath: string;
|
||||||
instanceDetails: ConfigInstanceDetailsFields;
|
instanceDetails: ConfigInstanceDetailsFields;
|
||||||
rtmpServerPort: string;
|
rtmpServerPort: string;
|
||||||
s3: S3Field;
|
s3: S3Field;
|
||||||
streamKey: string;
|
streamKeys: StreamKey[];
|
||||||
|
adminPassword: string;
|
||||||
videoSettings: VideoSettingsFields;
|
videoSettings: VideoSettingsFields;
|
||||||
webServerPort: string;
|
webServerPort: string;
|
||||||
socketHostOverride: string;
|
socketHostOverride: string;
|
||||||
|
|||||||
@ -108,6 +108,9 @@ export const FEDERATION_ACTIONS = `${API_LOCATION}federation/actions`;
|
|||||||
|
|
||||||
export const API_STREAM_HEALTH_METRICS = `${API_LOCATION}metrics/video`;
|
export const API_STREAM_HEALTH_METRICS = `${API_LOCATION}metrics/video`;
|
||||||
|
|
||||||
|
// Save an array of stream keys
|
||||||
|
export const UPDATE_STREAM_KEYS = `${API_LOCATION}config/streamkeys`;
|
||||||
|
|
||||||
export const API_YP_RESET = `${API_LOCATION}yp/reset`;
|
export const API_YP_RESET = `${API_LOCATION}yp/reset`;
|
||||||
|
|
||||||
export const TEMP_UPDATER_API = LOGS_ALL;
|
export const TEMP_UPDATER_API = LOGS_ALL;
|
||||||
|
|||||||
@ -113,13 +113,13 @@ export const TEXTFIELD_PROPS_LOGO = {
|
|||||||
label: 'Logo',
|
label: 'Logo',
|
||||||
tip: 'Upload your logo if you have one. We recommend that you use a square image that is at least 256x256. SVGs are discouraged as they cannot be displayed on all social media platforms.',
|
tip: 'Upload your logo if you have one. We recommend that you use a square image that is at least 256x256. SVGs are discouraged as they cannot be displayed on all social media platforms.',
|
||||||
};
|
};
|
||||||
export const TEXTFIELD_PROPS_STREAM_KEY = {
|
export const TEXTFIELD_PROPS_ADMIN_PASSWORD = {
|
||||||
apiPath: API_STREAM_KEY,
|
apiPath: API_STREAM_KEY,
|
||||||
configPath: '',
|
configPath: '',
|
||||||
maxLength: TEXT_MAXLENGTH,
|
maxLength: TEXT_MAXLENGTH,
|
||||||
placeholder: 'abc123',
|
placeholder: 'abc123',
|
||||||
label: 'Stream Key',
|
label: 'Admin Password',
|
||||||
tip: 'Save this key somewhere safe, you will need it to stream or login to the admin dashboard!',
|
tip: 'Save this password somewhere safe, you will need it to login to the admin dashboard!',
|
||||||
required: true,
|
required: true,
|
||||||
};
|
};
|
||||||
export const TEXTFIELD_PROPS_FFMPEG = {
|
export const TEXTFIELD_PROPS_FFMPEG = {
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import { ConfigDetails, UpdateArgs } from '../types/config-section';
|
|||||||
import { DEFAULT_VARIANT_STATE } from './config-constants';
|
import { DEFAULT_VARIANT_STATE } from './config-constants';
|
||||||
|
|
||||||
export const initialServerConfigState: ConfigDetails = {
|
export const initialServerConfigState: ConfigDetails = {
|
||||||
streamKey: '',
|
streamKeys: [],
|
||||||
|
adminPassword: '',
|
||||||
instanceDetails: {
|
instanceDetails: {
|
||||||
customStyles: '',
|
customStyles: '',
|
||||||
extraPageContent: '',
|
extraPageContent: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user