123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- import React, { useContext, useEffect, useState } from 'react';
- import { Table, Space, Button, Typography, Alert, Input, Form, message } from 'antd';
- import dynamic from 'next/dynamic';
- import { ServerStatusContext } from '../../../../utils/server-status-context';
- import { fetchData, UPDATE_STREAM_KEYS } from '../../../../utils/apis';
- import { PASSWORD_COMPLEXITY_RULES, REGEX_PASSWORD } from '../../../../utils/config-constants';
- const { Paragraph } = Typography;
- // 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,
- });
- 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.message);
- }
- };
- export 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;
- };
- const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setError }) => {
- const [hasChanged, setHasChanged] = useState(true);
- const [form] = Form.useForm();
- const { Item } = Form;
- // Password Complexity rules
- const passwordComplexityRules = [];
- useEffect(() => {
- PASSWORD_COMPLEXITY_RULES.forEach(element => {
- passwordComplexityRules.push(element);
- });
- }, []);
- const handleAddKey = (newkey: any) => {
- const updatedKeys = [...streamKeys, newkey];
- setFieldInConfigState({
- fieldName: 'streamKeys',
- value: updatedKeys,
- });
- saveKeys(updatedKeys, setError);
- setShowAddKeyForm(false);
- };
- const handleInputChange = (event: any) => {
- const val = event.target.value;
- if (REGEX_PASSWORD.test(val)) {
- setHasChanged(true);
- } else {
- setHasChanged(false);
- }
- };
- // Default auto-generated key
- const defaultKey = generateRndKey();
- return (
- <Form
- layout="horizontal"
- autoComplete="off"
- onFinish={handleAddKey}
- form={form}
- style={{ display: 'flex', flexDirection: 'row' }}
- initialValues={{ key: defaultKey, comment: 'My new key' }}
- >
- <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>
- }
- rules={PASSWORD_COMPLEXITY_RULES}
- >
- <Input placeholder="your key" onChange={handleInputChange} />
- </Item>
- <Item
- style={{ width: '60%', marginRight: '5px' }}
- label="Comment"
- name="comment"
- tooltip="For remembering why you added this key"
- >
- <Input placeholder="My OBS Key" />
- </Item>
- <Button type="primary" htmlType="submit" disabled={!hasChanged}>
- Add
- </Button>
- </Form>
- );
- };
- const AddKeyButton = ({ setShowAddKeyForm }) => (
- <Button type="default" onClick={() => setShowAddKeyForm(true)}>
- <PlusOutlined />
- </Button>
- );
- const copyText = (text: string) => {
- navigator.clipboard
- .writeText(text)
- .then(() => message.success('Copied to clipboard'))
- .catch(() => message.error('Failed to copy to clipboard'));
- };
- 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={{
- text: showKeyMap[text] ? text : '**********',
- onCopy: () => copyText(text),
- }}
- >
- {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
- disabled={streamKeys.length === 1}
- 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;
|