Reorganize admin components to help bundling
This commit is contained in:
259
web/components/admin/config/server/EditStorage.tsx
Normal file
259
web/components/admin/config/server/EditStorage.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
import { 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 {
|
||||
postConfigUpdateToAPI,
|
||||
API_S3_INFO,
|
||||
RESET_TIMEOUT,
|
||||
S3_TEXT_FIELDS_INFO,
|
||||
} from '../../../../utils/config-constants';
|
||||
import {
|
||||
createInputStatus,
|
||||
StatusState,
|
||||
STATUS_ERROR,
|
||||
STATUS_PROCESSING,
|
||||
STATUS_SUCCESS,
|
||||
} from '../../../../utils/input-statuses';
|
||||
import { TextField } from '../../TextField';
|
||||
import { FormStatusIndicator } from '../../FormStatusIndicator';
|
||||
import { isValidUrl } from '../../../../utils/urls';
|
||||
import { ToggleSwitch } from '../../ToggleSwitch';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
// we could probably add more detailed checks here
|
||||
// `currentValues` is what's currently in the global store and in the db
|
||||
function checkSaveable(formValues: any, currentValues: any) {
|
||||
const {
|
||||
endpoint,
|
||||
accessKey,
|
||||
secret,
|
||||
bucket,
|
||||
region,
|
||||
enabled,
|
||||
servingEndpoint,
|
||||
acl,
|
||||
forcePathStyle,
|
||||
} = formValues;
|
||||
// if fields are filled out and different from what's in store, then return true
|
||||
if (enabled) {
|
||||
if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
|
||||
if (
|
||||
enabled !== currentValues.enabled ||
|
||||
endpoint !== currentValues.endpoint ||
|
||||
accessKey !== currentValues.accessKey ||
|
||||
secret !== currentValues.secret ||
|
||||
bucket !== currentValues.bucket ||
|
||||
region !== currentValues.region ||
|
||||
(!currentValues.servingEndpoint && servingEndpoint !== '') ||
|
||||
(!!currentValues.servingEndpoint && servingEndpoint !== currentValues.servingEndpoint) ||
|
||||
(!currentValues.acl && acl !== '') ||
|
||||
(!!currentValues.acl && acl !== currentValues.acl) ||
|
||||
forcePathStyle !== currentValues.forcePathStyle
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (enabled !== currentValues.enabled) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
export default function EditStorage() {
|
||||
const [formDataValues, setFormDataValues] = useState(null);
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
|
||||
const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||
|
||||
const { setMessage: setAlertMessage } = useContext(AlertMessageContext);
|
||||
|
||||
const { s3 } = serverConfig;
|
||||
const {
|
||||
accessKey = '',
|
||||
acl = '',
|
||||
bucket = '',
|
||||
enabled = false,
|
||||
endpoint = '',
|
||||
region = '',
|
||||
secret = '',
|
||||
servingEndpoint = '',
|
||||
forcePathStyle = false,
|
||||
} = s3;
|
||||
|
||||
useEffect(() => {
|
||||
setFormDataValues({
|
||||
accessKey,
|
||||
acl,
|
||||
bucket,
|
||||
enabled,
|
||||
endpoint,
|
||||
region,
|
||||
secret,
|
||||
servingEndpoint,
|
||||
forcePathStyle,
|
||||
});
|
||||
setShouldDisplayForm(enabled);
|
||||
}, [s3]);
|
||||
|
||||
if (!formDataValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let resetTimer = null;
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
resetTimer = null;
|
||||
clearTimeout(resetTimer);
|
||||
};
|
||||
|
||||
// update individual values in state
|
||||
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
|
||||
setFormDataValues({
|
||||
...formDataValues,
|
||||
[fieldName]: value,
|
||||
});
|
||||
};
|
||||
|
||||
// posts the whole state
|
||||
const handleSave = async () => {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
const postValue = formDataValues;
|
||||
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath: API_S3_INFO,
|
||||
data: { value: postValue },
|
||||
onSuccess: () => {
|
||||
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
setAlertMessage(
|
||||
'Changing your storage configuration will take place the next time you start a new stream.',
|
||||
);
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// toggle switch.
|
||||
const handleSwitchChange = (storageEnabled: boolean) => {
|
||||
setShouldDisplayForm(storageEnabled);
|
||||
handleFieldChange({ fieldName: 'enabled', value: storageEnabled });
|
||||
};
|
||||
|
||||
const handleForcePathStyleSwitchChange = (forcePathStyleEnabled: boolean) => {
|
||||
handleFieldChange({ fieldName: 'forcePathStyle', value: forcePathStyleEnabled });
|
||||
};
|
||||
|
||||
const containerClass = classNames({
|
||||
'edit-storage-container': true,
|
||||
'form-module': true,
|
||||
enabled: shouldDisplayForm,
|
||||
});
|
||||
|
||||
const isSaveable = checkSaveable(formDataValues, s3);
|
||||
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<div className="enable-switch">
|
||||
<ToggleSwitch
|
||||
apiPath=""
|
||||
fieldName="enabled"
|
||||
label="Use S3 Storage Provider"
|
||||
checked={formDataValues.enabled}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
{/* <Switch
|
||||
checked={formDataValues.enabled}
|
||||
defaultChecked={formDataValues.enabled}
|
||||
onChange={handleSwitchChange}
|
||||
checkedChildren="ON"
|
||||
unCheckedChildren="OFF"
|
||||
/>{' '}
|
||||
Enabled */}
|
||||
</div>
|
||||
|
||||
<div className="form-fields">
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.endpoint}
|
||||
value={formDataValues.endpoint}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.accessKey}
|
||||
value={formDataValues.accessKey}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.secret}
|
||||
value={formDataValues.secret}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.bucket}
|
||||
value={formDataValues.bucket}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.region}
|
||||
value={formDataValues.region}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Collapse className="advanced-section">
|
||||
<Panel header="Optional Settings" key="1">
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.acl}
|
||||
value={formDataValues.acl}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field-container">
|
||||
<TextField
|
||||
{...S3_TEXT_FIELDS_INFO.servingEndpoint}
|
||||
value={formDataValues.servingEndpoint}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="enable-switch">
|
||||
<ToggleSwitch
|
||||
{...S3_TEXT_FIELDS_INFO.forcePathStyle}
|
||||
fieldName="forcePathStyle"
|
||||
checked={formDataValues.forcePathStyle}
|
||||
onChange={handleForcePathStyleSwitchChange}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
<div className="button-container">
|
||||
<Button type="primary" onClick={handleSave} disabled={!isSaveable}>
|
||||
Save
|
||||
</Button>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
web/components/admin/config/server/ServerConfig.tsx
Normal file
17
web/components/admin/config/server/ServerConfig.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import EditInstanceDetails from '../../EditInstanceDetails2';
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
export default function ConfigServerDetails() {
|
||||
return (
|
||||
<div className="config-server-details-form">
|
||||
<p className="description">
|
||||
You should change your admin password 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="form-module config-server-details-container">
|
||||
<EditInstanceDetails />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
web/components/admin/config/server/StorageConfig.tsx
Normal file
31
web/components/admin/config/server/StorageConfig.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import EditStorage from './EditStorage';
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
export default function ConfigStorageInfo() {
|
||||
return (
|
||||
<>
|
||||
<p className="description">
|
||||
Owncast supports optionally using external storage providers to stream your video. Learn
|
||||
more about this by visiting our{' '}
|
||||
<a
|
||||
href="https://owncast.online/docs/storage/?source=admin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Storage Documentation
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p className="description">
|
||||
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>
|
||||
<p className="description">
|
||||
Keep in mind this is for live streaming, not for archival, recording or VOD purposes.
|
||||
</p>
|
||||
<EditStorage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
172
web/components/admin/config/server/StreamKeys.tsx
Normal file
172
web/components/admin/config/server/StreamKeys.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
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 { 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];
|
||||
|
||||
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>
|
||||
<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;
|
||||
Reference in New Issue
Block a user