2023-02-07 19:22:52 +03:00
|
|
|
import React, { useContext, useEffect, useState } from 'react';
|
2023-04-04 06:49:48 +03:00
|
|
|
import { Table, Space, Button, Typography, Alert, Input, Form, message } from 'antd';
|
2023-01-16 09:31:36 +03:00
|
|
|
import dynamic from 'next/dynamic';
|
2023-01-10 07:57:29 +03:00
|
|
|
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
2022-11-29 07:22:26 +03:00
|
|
|
|
2023-01-10 07:57:29 +03:00
|
|
|
import { fetchData, UPDATE_STREAM_KEYS } from '../../../../utils/apis';
|
2023-02-07 19:22:52 +03:00
|
|
|
import { PASSWORD_COMPLEXITY_RULES, REGEX_PASSWORD } from '../../../../utils/config-constants';
|
2022-11-29 07:22:26 +03:00
|
|
|
|
2022-12-28 05:48:21 +03:00
|
|
|
const { Paragraph } = Typography;
|
2023-01-24 16:33:56 +03:00
|
|
|
|
2023-01-16 09:31:36 +03:00
|
|
|
// Lazy loaded components
|
|
|
|
|
|
|
|
const DeleteOutlined = dynamic(() => import('@ant-design/icons/DeleteOutlined'), {
|
|
|
|
ssr: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const EyeOutlined = dynamic(() => import('@ant-design/icons/EyeOutlined'), {
|
|
|
|
ssr: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const PlusOutlined = dynamic(() => import('@ant-design/icons/PlusOutlined'), {
|
|
|
|
ssr: false,
|
|
|
|
});
|
|
|
|
|
2022-11-29 07:22:26 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-03-01 15:58:07 +03:00
|
|
|
const generateRndKey = () => {
|
|
|
|
let defaultKey = '';
|
|
|
|
let isValidStreamKey = false;
|
|
|
|
const streamKeyRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#$%^&*]).{8,192}$/;
|
|
|
|
const s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
|
|
|
|
|
|
|
|
while (!isValidStreamKey) {
|
|
|
|
const temp = Array.apply(20, Array(30))
|
|
|
|
.map(() => s.charAt(Math.floor(Math.random() * s.length)))
|
|
|
|
.join('');
|
|
|
|
if (streamKeyRegex.test(temp)) {
|
|
|
|
isValidStreamKey = true;
|
|
|
|
defaultKey = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return defaultKey;
|
|
|
|
};
|
|
|
|
|
2022-11-29 07:22:26 +03:00
|
|
|
const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setError }) => {
|
2023-03-01 15:58:07 +03:00
|
|
|
const [hasChanged, setHasChanged] = useState(true);
|
2023-01-24 16:33:56 +03:00
|
|
|
const [form] = Form.useForm();
|
|
|
|
const { Item } = Form;
|
2023-03-28 07:34:39 +03:00
|
|
|
|
2023-02-07 19:22:52 +03:00
|
|
|
// Password Complexity rules
|
|
|
|
const passwordComplexityRules = [];
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
PASSWORD_COMPLEXITY_RULES.forEach(element => {
|
|
|
|
passwordComplexityRules.push(element);
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
2022-11-29 07:22:26 +03:00
|
|
|
const handleAddKey = (newkey: any) => {
|
|
|
|
const updatedKeys = [...streamKeys, newkey];
|
|
|
|
|
|
|
|
setFieldInConfigState({
|
|
|
|
fieldName: 'streamKeys',
|
|
|
|
value: updatedKeys,
|
|
|
|
});
|
|
|
|
|
|
|
|
saveKeys(updatedKeys, setError);
|
|
|
|
|
|
|
|
setShowAddKeyForm(false);
|
|
|
|
};
|
|
|
|
|
2023-01-24 16:33:56 +03:00
|
|
|
const handleInputChange = (event: any) => {
|
|
|
|
const val = event.target.value;
|
2023-02-07 19:22:52 +03:00
|
|
|
if (REGEX_PASSWORD.test(val)) {
|
2023-01-24 16:33:56 +03:00
|
|
|
setHasChanged(true);
|
|
|
|
} else {
|
|
|
|
setHasChanged(false);
|
|
|
|
}
|
|
|
|
};
|
2023-03-01 16:12:39 +03:00
|
|
|
|
2023-03-01 15:58:07 +03:00
|
|
|
// Default auto-generated key
|
|
|
|
const defaultKey = generateRndKey();
|
|
|
|
|
2022-11-29 07:22:26 +03:00
|
|
|
return (
|
2023-01-17 14:29:16 +03:00
|
|
|
<Form
|
|
|
|
layout="horizontal"
|
|
|
|
autoComplete="off"
|
|
|
|
onFinish={handleAddKey}
|
2023-01-24 16:33:56 +03:00
|
|
|
form={form}
|
2023-01-17 14:29:16 +03:00
|
|
|
style={{ display: 'flex', flexDirection: 'row' }}
|
2023-03-28 07:34:39 +03:00
|
|
|
initialValues={{ key: defaultKey, comment: 'My new key' }}
|
2023-01-17 14:29:16 +03:00
|
|
|
>
|
|
|
|
<Item
|
|
|
|
style={{ width: '60%', marginRight: '5px' }}
|
|
|
|
label="Key"
|
|
|
|
name="key"
|
|
|
|
tooltip={
|
|
|
|
<p>
|
|
|
|
The key you provide your broadcasting software. Please note that the key must be a
|
|
|
|
minimum of eight characters and must include at least one uppercase letter, at least one
|
|
|
|
lowercase letter, at least one special character, and at least one number.
|
|
|
|
</p>
|
|
|
|
}
|
2023-02-07 19:22:52 +03:00
|
|
|
rules={PASSWORD_COMPLEXITY_RULES}
|
2023-01-17 14:29:16 +03:00
|
|
|
>
|
2023-03-28 07:34:39 +03:00
|
|
|
<Input placeholder="your key" onChange={handleInputChange} />
|
2022-11-29 07:22:26 +03:00
|
|
|
</Item>
|
2023-01-17 14:29:16 +03:00
|
|
|
<Item
|
|
|
|
style={{ width: '60%', marginRight: '5px' }}
|
|
|
|
label="Comment"
|
|
|
|
name="comment"
|
|
|
|
tooltip="For remembering why you added this key"
|
|
|
|
>
|
2022-11-29 07:22:26 +03:00
|
|
|
<Input placeholder="My OBS Key" />
|
|
|
|
</Item>
|
2023-01-24 16:33:56 +03:00
|
|
|
<Button type="primary" htmlType="submit" disabled={!hasChanged}>
|
2022-11-29 07:22:26 +03:00
|
|
|
Add
|
|
|
|
</Button>
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const AddKeyButton = ({ setShowAddKeyForm }) => (
|
|
|
|
<Button type="default" onClick={() => setShowAddKeyForm(true)}>
|
|
|
|
<PlusOutlined />
|
|
|
|
</Button>
|
|
|
|
);
|
2023-04-04 06:49:48 +03:00
|
|
|
const copyText = (text: string) => {
|
|
|
|
navigator.clipboard
|
|
|
|
.writeText(text)
|
|
|
|
.then(() => message.success('Copied to clipboard'))
|
|
|
|
.catch(() => message.error('Failed to copy to clipboard'));
|
|
|
|
};
|
2022-11-29 07:22:26 +03:00
|
|
|
|
|
|
|
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">
|
2023-04-04 06:49:48 +03:00
|
|
|
<Paragraph
|
|
|
|
copyable={{
|
|
|
|
text: showKeyMap[text] ? text : '**********',
|
|
|
|
onCopy: () => copyText(text),
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{showKeyMap[text] ? text : '**********'}
|
|
|
|
</Paragraph>
|
2022-11-29 07:22:26 +03:00
|
|
|
|
|
|
|
<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>
|
|
|
|
<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;
|