Merge branch 'css-overhaul' into gw/20201226-admin-formfields

This commit is contained in:
gingervitis
2021-02-01 00:36:47 -08:00
19 changed files with 763 additions and 392 deletions
+66
View File
@@ -184,3 +184,69 @@ export const DEFAULT_SOCIAL_HANDLE: SocialHandle = {
}; };
export const OTHER_SOCIAL_HANDLE_OPTION = 'OTHER_SOCIAL_HANDLE_OPTION'; export const OTHER_SOCIAL_HANDLE_OPTION = 'OTHER_SOCIAL_HANDLE_OPTION';
export const TEXTFIELD_PROPS_S3_COMMON = {
maxLength: 255,
}
// export const FIELD_PROPS_CUSTOM_CONTENT = {
// apiPath: API_CUSTOM_CONTENT,
// configPath: 'instanceDetails',
// placeholder: '',
// label: 'Extra page content',
// tip: 'Custom markup about yourself',
// };
export const S3_TEXT_FIELDS_INFO = {
accessKey: {
fieldName: 'accessKey',
label: 'Access Key',
maxLength: 255,
placeholder: 'access key 123',
tip: '',
},
acl: {
fieldName: 'acl',
label: 'ACL',
maxLength: 255,
placeholder: 'acl thing',
tip: '',
},
bucket: {
fieldName: 'bucket',
label: 'Bucket',
maxLength: 255,
placeholder: 'bucket 123',
tip: '',
},
endpoint: {
fieldName: 'endpoint',
label: 'Endpoint',
maxLength: 255,
placeholder: 'endpoint 123',
tip: 'This field has a some info',
},
region: {
fieldName: 'region',
label: 'Region',
maxLength: 255,
placeholder: 'region 123',
tip: '',
},
secret: {
fieldName: 'secret',
label: 'Secret key',
maxLength: 255,
placeholder: 'secret key 123',
tip: '',
},
servingEndpoint: {
fieldName: 'servingEndpoint',
label: 'Serving Endpoint',
maxLength: 255,
placeholder: 'servingEndpoint 123',
tip: '',
},
};
@@ -16,7 +16,6 @@ import {
API_YP_SWITCH, API_YP_SWITCH,
} from './constants'; } from './constants';
import configStyles from '../../../styles/config-pages.module.scss';
import { UpdateArgs } from '../../../types/config-section'; import { UpdateArgs } from '../../../types/config-section';
export default function EditInstanceDetails() { export default function EditInstanceDetails() {
@@ -57,8 +56,8 @@ export default function EditInstanceDetails() {
}; };
return ( return (
<div className={configStyles.publicDetailsContainer}> <div className={`publicDetailsContainer`}>
<div className={configStyles.textFieldsSection}> <div className={`textFieldsSection`}>
<TextFieldWithSubmit <TextFieldWithSubmit
fieldName="instanceUrl" fieldName="instanceUrl"
{...TEXTFIELD_PROPS_INSTANCE_URL} {...TEXTFIELD_PROPS_INSTANCE_URL}
@@ -13,7 +13,6 @@ import {
TEXTFIELD_PROPS_WEB_PORT, TEXTFIELD_PROPS_WEB_PORT,
} from './constants'; } from './constants';
import configStyles from '../../../styles/config-pages.module.scss';
import { UpdateArgs } from '../../../types/config-section'; import { UpdateArgs } from '../../../types/config-section';
export default function EditInstanceDetails() { export default function EditInstanceDetails() {
@@ -64,8 +63,9 @@ export default function EditInstanceDetails() {
} }
return ( return (
<div className={configStyles.publicDetailsContainer}> <div className="edit-public-details-container">
<div className={configStyles.textFieldsSection}> <div className="field-container field-streamkey-container">
<div className="left-side">
<TextFieldWithSubmit <TextFieldWithSubmit
fieldName="streamKey" fieldName="streamKey"
{...TEXTFIELD_PROPS_STREAM_KEY} {...TEXTFIELD_PROPS_STREAM_KEY}
@@ -74,15 +74,17 @@ export default function EditInstanceDetails() {
type={TEXTFIELD_TYPE_PASSWORD} type={TEXTFIELD_TYPE_PASSWORD}
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
<div> <div className="streamkey-actions">
<span style={{ fontSize: '0.75em', color: '#ff7777', marginRight: '0.5em' }}> <Tooltip className="copy-tooltip" title="Copied!" trigger="" visible={copyIsVisible}>
<Button icon={<CopyOutlined />} size="small" onClick={copyStreamKey} />
</Tooltip>
<Button icon={<RedoOutlined />} size="small" onClick={generateStreamKey} />
</div>
</div>
<div className="streamkey-notice">
Save this key somewhere safe, you will need it to stream or login to the admin Save this key somewhere safe, you will need it to stream or login to the admin
dashboard! dashboard!
</span> </div>
<Tooltip className="copy-tooltip" title="Copied!" trigger="" visible={copyIsVisible}>
<Button type="primary" icon={<CopyOutlined />} size="small" onClick={copyStreamKey} />
</Tooltip>
<Button type="primary" icon={<RedoOutlined />} size="small" onClick={generateStreamKey} />
</div> </div>
<TextFieldWithSubmit <TextFieldWithSubmit
fieldName="ffmpegPath" fieldName="ffmpegPath"
@@ -108,6 +110,5 @@ export default function EditInstanceDetails() {
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
</div> </div>
</div>
); );
} }
@@ -9,14 +9,14 @@ import {
API_SOCIAL_HANDLES, API_SOCIAL_HANDLES,
postConfigUpdateToAPI, postConfigUpdateToAPI,
RESET_TIMEOUT, RESET_TIMEOUT,
SUCCESS_STATES,
DEFAULT_SOCIAL_HANDLE, DEFAULT_SOCIAL_HANDLE,
OTHER_SOCIAL_HANDLE_OPTION, OTHER_SOCIAL_HANDLE_OPTION,
} from './constants'; } from './constants';
import { SocialHandle } from '../../../types/config-section'; import { SocialHandle, UpdateArgs } from '../../../types/config-section';
import { isValidUrl } from '../../../utils/urls'; import { isValidUrl } from '../../../utils/urls';
import TextField from './form-textfield';
import configStyles from '../../../styles/config-pages.module.scss'; import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
const { Title } = Typography; const { Title } = Typography;
@@ -33,7 +33,6 @@ export default function EditSocialLinks() {
const [modalDataState, setModalDataState] = useState(DEFAULT_SOCIAL_HANDLE); const [modalDataState, setModalDataState] = useState(DEFAULT_SOCIAL_HANDLE);
const [submitStatus, setSubmitStatus] = useState(null); const [submitStatus, setSubmitStatus] = useState(null);
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { serverConfig, setFieldInConfigState } = serverStatusData || {};
@@ -73,7 +72,6 @@ export default function EditSocialLinks() {
const resetStates = () => { const resetStates = () => {
setSubmitStatus(null); setSubmitStatus(null);
setSubmitStatusMessage('');
resetTimer = null; resetTimer = null;
clearTimeout(resetTimer); clearTimeout(resetTimer);
}; };
@@ -108,8 +106,8 @@ export default function EditSocialLinks() {
const { value } = event.target; const { value } = event.target;
updateModalState('platform', value); updateModalState('platform', value);
}; };
const handleUrlChange = event => {
const { value } = event.target; const handleUrlChange = ({ value }: UpdateArgs) => {
updateModalState('url', value); updateModalState('url', value);
}; };
@@ -129,13 +127,12 @@ export default function EditSocialLinks() {
setModalProcessing(false); setModalProcessing(false);
handleModalCancel(); handleModalCancel();
setSubmitStatus('success'); setSubmitStatus(createInputStatus(STATUS_SUCCESS));
setSubmitStatusMessage('Social Handles updated.');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
onError: (message: string) => { onError: (message: string) => {
setSubmitStatus('error'); setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
setSubmitStatusMessage(message);
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
}); });
@@ -223,24 +220,16 @@ export default function EditSocialLinks() {
}, },
]; ];
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
SUCCESS_STATES[submitStatus] || {};
const statusMessage = (
<div className={`status-message ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
</div>
);
const okButtonProps = { const okButtonProps = {
disabled: !isValidUrl(modalDataState.url), disabled: !isValidUrl(modalDataState.url),
}; };
return ( return (
<div className={configStyles.socialLinksEditor}> <div className="social-links-edit-container">
<Title level={2}>Social Links</Title> <Title level={2}>Social Links</Title>
<p>Add all your social media handles and links to your other profiles here.</p> <p>Add all your social media handles and links to your other profiles here.</p>
{statusMessage} <FormStatusIndicator status={submitStatus} />
<Table <Table
className="dataTable" className="dataTable"
@@ -275,14 +264,14 @@ export default function EditSocialLinks() {
</> </>
) : null} ) : null}
<br /> <br />
URL <TextField
<Input fieldName="social-url"
label="URL"
placeholder="Url to page" placeholder="Url to page"
defaultValue={modalDataState.url}
value={modalDataState.url} value={modalDataState.url}
onChange={handleUrlChange} onChange={handleUrlChange}
/> />
{statusMessage} <FormStatusIndicator status={submitStatus} />
</Modal> </Modal>
<br /> <br />
<Button <Button
@@ -0,0 +1,218 @@
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 {
postConfigUpdateToAPI,
API_S3_INFO,
RESET_TIMEOUT,
S3_TEXT_FIELDS_INFO,
} from './constants';
import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
import TextField from './form-textfield';
import FormStatusIndicator from './form-status-indicator';
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 } = formValues;
// if fields are filled out and different from what's in store, then return true
if (enabled) {
if (!!endpoint && !!accessKey && !!secret && !!bucket && !!region) {
if (
endpoint !== currentValues.endpoint ||
accessKey !== currentValues.accessKey ||
secret !== currentValues.secret ||
region !== currentValues.region ||
(!!currentValues.servingEndpoint && servingEndpoint !== currentValues.servingEndpoint) ||
(!!currentValues.acl && acl !== currentValues.acl)
) {
return true;
}
}
} else if (enabled !== currentValues.enabled) {
return true;
}
return false;
}
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 { s3 } = serverConfig;
const {
accessKey = '',
acl = '',
bucket = '',
enabled = false,
endpoint = '',
region = '',
secret = '',
servingEndpoint = '',
} = s3;
useEffect(() => {
setFormDataValues({
accessKey,
acl,
bucket,
enabled,
endpoint,
region,
secret,
servingEndpoint,
});
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);
},
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 });
// if current data in current store says s3 is enabled,
// we should save this state
// if (!storageEnabled && s3.enabled) {
// handleSave();
// }
};
const containerClass = classNames({
'edit-storage-container': true,
enabled: shouldDisplayForm,
});
const isSaveable = checkSaveable(formDataValues, s3);
return (
<div className={containerClass}>
<div className="enable-switch">
<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="Advanced 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>
</Panel>
</Collapse>
</div>
<div className="button-container">
<Button type="primary" onClick={handleSave} disabled={!isSaveable}>
Save
</Button>
<FormStatusIndicator status={submitStatus} />
</div>
</div>
);
}
+10 -10
View File
@@ -19,7 +19,7 @@ const { Title } = Typography;
export default function EditInstanceTags() { export default function EditInstanceTags() {
const [newTagInput, setNewTagInput] = useState<string | number>(''); const [newTagInput, setNewTagInput] = useState<string | number>('');
const [fieldStatus, setFieldStatus] = useState<StatusState>(null); const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { serverConfig, setFieldInConfigState } = serverStatusData || {};
@@ -38,34 +38,34 @@ export default function EditInstanceTags() {
}, []); }, []);
const resetStates = () => { const resetStates = () => {
setFieldStatus(null); setSubmitStatus(null);
resetTimer = null; resetTimer = null;
clearTimeout(resetTimer); clearTimeout(resetTimer);
}; };
// posts all the tags at once as an array obj // posts all the tags at once as an array obj
const postUpdateToAPI = async (postValue: any) => { const postUpdateToAPI = async (postValue: any) => {
setFieldStatus(createInputStatus(STATUS_PROCESSING)); setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
apiPath, apiPath,
data: { value: postValue }, data: { value: postValue },
onSuccess: () => { onSuccess: () => {
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath }); setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
setFieldStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.')); setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
setNewTagInput(''); setNewTagInput('');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
onError: (message: string) => { onError: (message: string) => {
setFieldStatus(createInputStatus(STATUS_ERROR, message)); setSubmitStatus(createInputStatus(STATUS_ERROR, message));
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
}); });
}; };
const handleInputChange = ({ value }: UpdateArgs) => { const handleInputChange = ({ value }: UpdateArgs) => {
if (!fieldStatus) { if (!submitStatus) {
setFieldStatus(null); setSubmitStatus(null);
} }
setNewTagInput(value); setNewTagInput(value);
}; };
@@ -75,11 +75,11 @@ export default function EditInstanceTags() {
resetStates(); resetStates();
const newTag = newTagInput.trim(); const newTag = newTagInput.trim();
if (newTag === '') { if (newTag === '') {
setFieldStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag')); setSubmitStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
return; return;
} }
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) { if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
setFieldStatus(createInputStatus(STATUS_WARNING, 'This tag is already used!')); setSubmitStatus(createInputStatus(STATUS_WARNING, 'This tag is already used!'));
return; return;
} }
@@ -121,7 +121,7 @@ export default function EditInstanceTags() {
onPressEnter={handleSubmitNewTag} onPressEnter={handleSubmitNewTag}
maxLength={maxLength} maxLength={maxLength}
placeholder={placeholder} placeholder={placeholder}
status={fieldStatus} status={submitStatus}
/> />
</div> </div>
</div> </div>
@@ -0,0 +1,22 @@
import React from 'react';
import classNames from 'classnames';
import { StatusState } from '../../../utils/input-statuses';
interface FormStatusIndicatorProps {
status: StatusState;
}
export default function FormStatusIndicator({ status }: FormStatusIndicatorProps) {
const { type, icon, message } = status || {};
const classes = classNames({
'status-container': true,
[`status-${type}`]: type,
empty: !message,
});
return (
<div className={classes}>
{icon ? <span className="status-icon">{icon}</span> : null}
{message ? <span className="status-message">{message}</span> : null}
</div>
);
}
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useContext } from 'react'; import React, { useEffect, useState, useContext } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import classNames from 'classnames';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants'; import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
@@ -13,6 +13,7 @@ import {
STATUS_SUCCESS, STATUS_SUCCESS,
} from '../../../utils/input-statuses'; } from '../../../utils/input-statuses';
import { UpdateArgs } from '../../../types/config-section'; import { UpdateArgs } from '../../../types/config-section';
import FormStatusIndicator from './form-status-indicator';
export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_TEXT = 'default';
export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
@@ -27,7 +28,7 @@ interface TextFieldWithSubmitProps extends TextFieldProps {
} }
export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
const [fieldStatus, setFieldStatus] = useState<StatusState>(null); const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
@@ -43,11 +44,11 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
...textFieldProps // rest of props ...textFieldProps // rest of props
} = props; } = props;
const { fieldName, required, status, value, onChange, onSubmit } = textFieldProps; const { fieldName, required, tip, status, value, onChange, onSubmit } = textFieldProps;
// Clear out any validation states and messaging // Clear out any validation states and messaging
const resetStates = () => { const resetStates = () => {
setFieldStatus(null); setSubmitStatus(null);
setHasChanged(false); setHasChanged(false);
clearTimeout(resetTimer); clearTimeout(resetTimer);
resetTimer = null; resetTimer = null;
@@ -83,17 +84,17 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
// how to get current value of input // how to get current value of input
const handleSubmit = async () => { const handleSubmit = async () => {
if ((required && value !== '') || value !== initialValue) { if ((required && value !== '') || value !== initialValue) {
setFieldStatus(createInputStatus(STATUS_PROCESSING)); setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
apiPath, apiPath,
data: { value }, data: { value },
onSuccess: () => { onSuccess: () => {
setFieldInConfigState({ fieldName, value, path: configPath }); setFieldInConfigState({ fieldName, value, path: configPath });
setFieldStatus(createInputStatus(STATUS_SUCCESS)); setSubmitStatus(createInputStatus(STATUS_SUCCESS));
}, },
onError: (message: string) => { onError: (message: string) => {
setFieldStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`)); setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
}, },
}); });
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
@@ -105,23 +106,38 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
} }
}; };
const textfieldContainerClass = classNames({
'textfield-with-submit-container': true,
submittable: hasChanged,
});
return ( return (
<div className="textfield-with-submit-container"> <div className={textfieldContainerClass}>
<div className="textfield-component">
<TextField <TextField
{...textFieldProps} {...textFieldProps}
status={status || fieldStatus}
onSubmit={null} onSubmit={null}
onBlur={handleBlur} onBlur={handleBlur}
onChange={handleChange} onChange={handleChange}
/> />
</div>
{hasChanged ? ( <div className="textfield-container lower-container">
<p className="label-spacer" />
<div className="lower-content">
<div className="field-tip">{tip}</div>
<FormStatusIndicator status={status || submitStatus} />
<div className="update-button-container"> <div className="update-button-container">
<Button type="primary" size="small" className="submit-button" onClick={handleSubmit}> <Button
type="primary"
size="small"
className="submit-button"
onClick={handleSubmit}
disabled={!hasChanged}
>
Update Update
</Button> </Button>
</div> </div>
) : null} </div>
</div>
</div> </div>
); );
} }
+16 -13
View File
@@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { Input, InputNumber } from 'antd'; import { Input, InputNumber } from 'antd';
import { FieldUpdaterFunc } from '../../../types/config-section'; import { FieldUpdaterFunc } from '../../../types/config-section';
import InfoTip from '../info-tip'; // 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'; export const TEXTFIELD_TYPE_TEXT = 'default';
export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
@@ -107,15 +109,20 @@ export default function TextField(props: TextFieldProps) {
const fieldId = `field-${fieldName}`; const fieldId = `field-${fieldName}`;
const { icon: statusIcon, message: statusMessage } = status || {}; const { type: statusType } = status || {};
const containerClass = classNames({
'textfield-container': true,
[`type-${type}`]: true,
required,
[`status-${statusType}`]: status,
});
return ( return (
<div className={`textfield-container type-${type}`}> <div className={containerClass}>
{label ? ( {label ? (
<div className="label-side"> <div className="label-side">
<label htmlFor={fieldId} className="textfield-label"> <label htmlFor={fieldId} className="textfield-label">
{required ? <span className="required-label">* </span> : null} {label}
{label}:
</label> </label>
</div> </div>
) : null} ) : null}
@@ -135,17 +142,13 @@ export default function TextField(props: TextFieldProps) {
disabled={disabled} disabled={disabled}
value={value} value={value}
/> />
</div> </div>
<div className="status-container"> <FormStatusIndicator status={status} />
{status ? <span className="status-icon">{statusIcon}</span> : null} <p className="field-tip">
{status ? <span className="status-message">{statusMessage}</span> : null} {tip}
</div> {/* <InfoTip tip={tip} /> */}
<p className="tip">
<InfoTip tip={tip} />
</p> </p>
</div> </div>
</div> </div>
); );
} }
@@ -1,8 +1,15 @@
import React, { useState, useContext } from 'react'; import React, { useState, useContext } from 'react';
import { Switch } from 'antd'; import { Switch } from 'antd';
import { FormItemProps } from 'antd/es/form'; import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../../utils/input-statuses';
import FormStatusIndicator from './form-status-indicator';
import { RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants'; import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
import InfoTip from '../info-tip'; import InfoTip from '../info-tip';
@@ -19,8 +26,7 @@ interface ToggleSwitchProps {
} }
export default function ToggleSwitch(props: ToggleSwitchProps) { export default function ToggleSwitch(props: ToggleSwitchProps) {
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>(''); const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
let resetTimer = null; let resetTimer = null;
@@ -30,37 +36,35 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
const { apiPath, checked, configPath = '', disabled = false, fieldName, label, tip } = props; const { apiPath, checked, configPath = '', disabled = false, fieldName, label, tip } = props;
const resetStates = () => { const resetStates = () => {
setSubmitStatus(''); setSubmitStatus(null);
clearTimeout(resetTimer); clearTimeout(resetTimer);
resetTimer = null; resetTimer = null;
}; };
const handleChange = async (isChecked: boolean) => { const handleChange = async (isChecked: boolean) => {
setSubmitStatus('validating'); setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
apiPath, apiPath,
data: { value: isChecked }, data: { value: isChecked },
onSuccess: () => { onSuccess: () => {
setFieldInConfigState({ fieldName, value: isChecked, path: configPath }); setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
setSubmitStatus('success'); setSubmitStatus(createInputStatus(STATUS_SUCCESS));
}, },
onError: (message: string) => { onError: (message: string) => {
setSubmitStatus('error'); setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
setSubmitStatusMessage(`There was an error: ${message}`);
}, },
}); });
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}; };
const { icon: newStatusIcon = null, message: newStatusMessage = '' } = const loading = submitStatus !== null && submitStatus.type === STATUS_PROCESSING;
SUCCESS_STATES[submitStatus] || {};
return ( return (
<div className="toggleswitch-container"> <div className="toggleswitch-container">
<div className="toggleswitch"> <div className="toggleswitch">
<Switch <Switch
className={`switch field-${fieldName}`} className={`switch field-${fieldName}`}
loading={submitStatus === 'validating'} loading={loading}
onChange={handleChange} onChange={handleChange}
defaultChecked={checked} defaultChecked={checked}
checked={checked} checked={checked}
@@ -71,11 +75,8 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
<span className="label"> <span className="label">
{label} <InfoTip tip={tip} /> {label} <InfoTip tip={tip} />
</span> </span>
{submitStatus}
</div>
<div className={`status-message ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
</div> </div>
<FormStatusIndicator status={submitStatus} />
</div> </div>
); );
} }
+16 -17
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect, useContext } from 'react'; import React, { useState, useEffect, useContext } from 'react';
import { Typography, Button } from 'antd'; import { Typography, Button } from 'antd';
import { FormItemProps } from 'antd/lib/form';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MarkdownIt from 'markdown-it'; import MarkdownIt from 'markdown-it';
@@ -8,11 +7,17 @@ import { ServerStatusContext } from '../utils/server-status-context';
import { import {
postConfigUpdateToAPI, postConfigUpdateToAPI,
RESET_TIMEOUT, RESET_TIMEOUT,
SUCCESS_STATES,
API_CUSTOM_CONTENT, API_CUSTOM_CONTENT,
} from './components/config/constants'; } from './components/config/constants';
import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../utils/input-statuses';
import 'react-markdown-editor-lite/lib/index.css'; import 'react-markdown-editor-lite/lib/index.css';
import FormStatusIndicator from './components/config/form-status-indicator';
const { Title } = Typography; const { Title } = Typography;
@@ -24,8 +29,7 @@ const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
export default function PageContentEditor() { export default function PageContentEditor() {
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>(''); const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
@@ -47,7 +51,7 @@ export default function PageContentEditor() {
// Clear out any validation states and messaging // Clear out any validation states and messaging
const resetStates = () => { const resetStates = () => {
setSubmitStatus(''); setSubmitStatus(null);
setHasChanged(false); setHasChanged(false);
clearTimeout(resetTimer); clearTimeout(resetTimer);
resetTimer = null; resetTimer = null;
@@ -55,21 +59,20 @@ export default function PageContentEditor() {
// posts all the tags at once as an array obj // posts all the tags at once as an array obj
async function handleSave() { async function handleSave() {
setSubmitStatus('validating'); setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
apiPath: API_CUSTOM_CONTENT, apiPath: API_CUSTOM_CONTENT,
data: { value: content }, data: { value: content },
onSuccess: () => { onSuccess: (message: string) => {
setFieldInConfigState({ setFieldInConfigState({
fieldName: 'extraPageContent', fieldName: 'extraPageContent',
value: content, value: content,
path: 'instanceDetails', path: 'instanceDetails',
}); });
setSubmitStatus('success'); setSubmitStatus(createInputStatus(STATUS_SUCCESS, message));
}, },
onError: (message: string) => { onError: (message: string) => {
setSubmitStatus('error'); setSubmitStatus(createInputStatus(STATUS_ERROR, message));
setSubmitStatusMessage(`There was an error: ${message}`);
}, },
}); });
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
@@ -79,9 +82,6 @@ export default function PageContentEditor() {
setContent(initialContent); setContent(initialContent);
}, [instanceDetails]); }, [instanceDetails]);
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
SUCCESS_STATES[submitStatus] || {};
return ( return (
<div className="config-page-content-form"> <div className="config-page-content-form">
<Title level={2}>Edit custom content</Title> <Title level={2}>Edit custom content</Title>
@@ -107,9 +107,8 @@ export default function PageContentEditor() {
Save Save
</Button> </Button>
) : null} ) : null}
<div className={`status-message ${submitStatus || ''}`}> <FormStatusIndicator status={submitStatus} />
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
</div>
</div> </div>
</div> </div>
); );
+1 -4
View File
@@ -2,7 +2,6 @@ import React from 'react';
import { Typography } from 'antd'; import { Typography } from 'antd';
import Link from 'next/link'; import Link from 'next/link';
import configStyles from '../styles/config-pages.module.scss';
import EditInstanceDetails from './components/config/edit-instance-details'; import EditInstanceDetails from './components/config/edit-instance-details';
const { Title } = Typography; const { Title } = Typography;
@@ -12,15 +11,13 @@ export default function PublicFacingDetails() {
<> <>
<Title level={2}>Edit your public facing instance details</Title> <Title level={2}>Edit your public facing instance details</Title>
<div className={configStyles.publicDetailsContainer}> <div className="edit-public-details-container">
<div className={configStyles.textFieldsSection}>
<EditInstanceDetails /> <EditInstanceDetails />
<Link href="/admin/config-page-content"> <Link href="/admin/config-page-content">
<a>Edit your extra page content here.</a> <a>Edit your extra page content here.</a>
</Link> </Link>
</div> </div>
</div>
</> </>
); );
} }
+6 -103
View File
@@ -1,111 +1,14 @@
import React, { useContext, useState, useEffect } from 'react'; import { Typography } from 'antd';
import { ServerStatusContext } from '../utils/server-status-context'; import React from 'react';
import { Typography, Switch, Input, Button } from 'antd'; import EditStorage from './components/config/edit-storage';
import {
postConfigUpdateToAPI,
API_S3_INFO,
} from './components/config/constants';
const { Title } = Typography; const { Title } = Typography;
function Storage({ config }) { export default function ConfigStorageInfo() {
if (!config || !config.s3) {
return null;
}
const [endpoint, setEndpoint] = useState(config.s3.endpoint);
const [accessKey, setAccessKey] = useState(config.s3.accessKey);
const [secret, setSecret] = useState(config.s3.secret);
const [bucket, setBucket] = useState(config.s3.bucket);
const [region, setRegion] = useState(config.s3.region);
const [acl, setAcl] = useState(config.s3.acl);
const [servingEndpoint, setServingEndpoint] = useState(config.s3.servingEndpoint);
const [enabled, setEnabled] = useState(config.s3.enabled);
function storageEnabledChanged(storageEnabled) {
setEnabled(storageEnabled);
}
function endpointChanged(e) {
setEndpoint(e.target.value)
}
function accessKeyChanged(e) {
setAccessKey(e.target.value)
}
function secretChanged(e) {
setSecret(e.target.value)
}
function bucketChanged(e) {
setBucket(e.target.value)
}
function regionChanged(e) {
setRegion(e.target.value)
}
function aclChanged(e) {
setAcl(e.target.value)
}
function servingEndpointChanged(e) {
setServingEndpoint(e.target.value)
}
async function save() {
const payload = {
value: {
enabled: enabled,
endpoint: endpoint,
accessKey: accessKey,
secret: secret,
bucket: bucket,
region: region,
acl: acl,
servingEndpoint: servingEndpoint,
}
};
try {
await postConfigUpdateToAPI({apiPath: API_S3_INFO, data: payload});
} catch(e) {
console.error(e);
}
}
const table = enabled ? (
<>
<br></br>
endpoint <Input defaultValue={endpoint} value={endpoint} onChange={endpointChanged} />
access key<Input label="Access key" defaultValue={accessKey} value={accessKey} onChange={accessKeyChanged} />
secret <Input label="Secret" defaultValue={secret} value={secret} onChange={secretChanged} />
bucket <Input label="Bucket" defaultValue={bucket} value={bucket} onChange={bucketChanged} />
region <Input label="Region" defaultValue={region} value={region} onChange={regionChanged} />
advanced<br></br>
acl <Input label="ACL" defaultValue={acl} value={acl} onChange={aclChanged} />
serving endpoint <Input label="Serving endpoint" defaultValue={servingEndpoint} value={servingEndpoint} onChange={servingEndpointChanged} />
<Button onClick={save}>Save</Button>
</>
): null;
return ( return (
<> <>
<Title level={2}>Storage</Title> <Title level={2}>Storage</Title>
Enabled: <EditStorage />
<Switch checked={enabled} onChange={storageEnabledChanged} />
{ table }
</> </>
); );
} }
export default function ServerConfig() {
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig: config } = serverStatusData || {};
return (
<div>
<Storage config={config} />
</div>
);
}
+149 -36
View File
@@ -1,35 +1,79 @@
// Base styles for form-textfield, form-textfield-with-submit, and helper components.
/* STATUS-CONTAINER BASE */
.status-container {
&.status-success {
color: var(--ant-success);
}
&.status-error {
color: var(--ant-error);
}
&.status-warning {
color: var(--ant-warning);
}
&.empty {
display: none;
}
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: .75rem;
.status-icon {
display: inline-block;
margin-right: .5em;
}
}
/* TIP CONTAINER BASE */
.field-tip {
font-size: .7em;
color: rgba(255,255,255,.5)
}
/* TEXTFIELD-CONTAINER BASE */
.textfield-container { .textfield-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
justify-content: flex-start; justify-content: flex-start;
width: 100%; width: 100%;
max-width: 600px;
.label-side { .label-side {
padding-right: 1em; padding-right: .75em;
text-align: right; text-align: right;
width: 12rem; width: 12em;
margin: .2em 0; margin: .2em 0;
} }
.textfield-label { .textfield-label {
font-weight: 400; font-weight: 400;
font-size: .85rem; font-size: .85em;
color: var(--owncast-purple); color: var(--owncast-purple);
&::after {
content: ':';
} }
.required-label { }
&.required {
.textfield-label {
&::before {
content: '*';
display: inline-block;
margin-right: .25em;
color: var(--ant-error); color: var(--ant-error);
} }
}
}
.input-side { .input-side {
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
.input-group {
.input-group,
.status-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
@@ -37,49 +81,118 @@
} }
.status-container { .status-container {
margin: 0 .25em; margin: .25em;
min-height: 1.5em; width: 100%;
font-size: .75em; display: block;
&.empty {
.status-icon { display: none;
display: inline-block; visibility: visible;
margin-right: .5em;
} }
} }
.tip { .field-tip {
margin: .5em .5em; margin: .5em .5em;
font-size: .75rem;
color: rgba(255,255,255,.75);
} }
@media (max-width: 800px) { @media (max-width: 800px) {
// flex-direction: column;
flex-wrap: wrap; flex-wrap: wrap;
.label-side { .label-side {
width: 100%;
text-align: left; text-align: left;
} }
} }
} }
.status-message { /* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
// margin: 1rem 0;
// min-height: 1.4em;
// font-size: .75rem;
&.success {
color: var(--ant-success);
}
&.error {
color: var(--ant-error);
}
}
.textfield-with-submit-container { .textfield-with-submit-container {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 1em;
.textfield-component {
width: 100%;
.textfield-container {
.field-tip,
.status-container {
display: none;
}
}
}
// for lack of a better name
.lower-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
.label-spacer {
width: 12em;
}
.lower-content {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-end;
width: 100%;
.field-tip {
margin-right: 1em;
width: 100%;
}
.status-container {
margin: .5em;
&.empty {
display: none;
}
}
}
.update-button-container { .update-button-container {
display: inline-block; visibility: hidden;
margin: .25em; margin: .25em 0;
}
}
&.submittable {
.lower-container {
.update-button-container {
visibility: visible;
}
}
}
@media (max-width: 800px) {
.label-spacer {
display: none;
}
}
}
/* TOGGLE SWITCH-WITH-SUBMIT-CONTAINER BASE */
.toggleswitch-container {
.status-container {
margin-top: .25rem;
}
.toggleswitch {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
.label {
font-weight: bold;
color: var(--owncast-purple);
}
.info-tip {
margin-left: .5rem;
svg {
fill: white;
}
}
.ant-form-item {
margin: 0 .75rem 0 0;
}
} }
} }
-33
View File
@@ -1,33 +0,0 @@
.publicDetailsContainer {
display: flex;
flex-direction: row;
align-items: flex-start;
flex-wrap: wrap;
.textFieldsSection {
margin-right: 2rem;
}
}
.socialLinksEditor {
width: 20rem;
margin: 2em 0;
}
.tag-editor-container,
.config-directory-details-form {
border-radius: 1em;
background-color: rgba(128,99,255,.1);
padding: 1.5em;
margin-bottom: 1em;
}
//////////////////////////////
// common?
.dataTable {
}
+83 -24
View File
@@ -98,30 +98,30 @@
// form-toggleswitch // form-toggleswitch
// form-toggleswitch // form-toggleswitch
.toggleswitch-container { // .toggleswitch-container {
.status-message { // .status-message {
margin-top: .25rem; // margin-top: .25rem;
} // }
} // }
.toggleswitch { // .toggleswitch {
display: flex; // display: flex;
flex-direction: row; // flex-direction: row;
align-items: center; // align-items: center;
justify-content: flex-start; // justify-content: flex-start;
.label { // .label {
font-weight: bold; // font-weight: bold;
color: var(--owncast-purple); // color: var(--owncast-purple);
} // }
.info-tip { // .info-tip {
margin-left: .5rem; // margin-left: .5rem;
svg { // svg {
fill: white; // fill: white;
} // }
} // }
.ant-form-item { // .ant-form-item {
margin: 0 .75rem 0 0; // margin: 0 .75rem 0 0;
} // }
} // }
// TAGS STUFF // TAGS STUFF
// TAGS STUFF // TAGS STUFF
@@ -321,3 +321,62 @@
// } // }
// } // }
// } // }
// EDIT STORAGE
.edit-storage-container {
.form-fields {
display: none;
margin-bottom: 1em;
}
&.enabled {
.form-fields {
display: block;
}
}
.button-container {
margin: 1em 0;
}
.advanced-section {
margin: 1em 0;
}
}
.field-container {
padding: .85em 0 .5em;
&:nth-child(even) {
background-color: rgba(0,0,0,.25);
}
}
.field-streamkey-container {
.left-side {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.textfield-with-submit-container {
margin-bottom: 0;
.textfield-container {
max-width: 400px;
}
}
.streamkey-actions {
max-width: 90px;
button {
margin: .25em;
}
}
.streamkey-notice {
font-size: 0.75em;
color: var(--ant-error);
margin-left: 12em;
margin-bottom: 1em;
}
}
+12 -1
View File
@@ -63,11 +63,22 @@ export interface VideoSettingsFields {
cpuUsageLevel: CpuUsageLevel; cpuUsageLevel: CpuUsageLevel;
} }
export interface S3Field {
acl?: string;
accessKey: string;
bucket: string;
enabled: boolean;
endpoint: string;
region: string;
secret: string;
servingEndpoint?: string;
}
export interface ConfigDetails { export interface ConfigDetails {
ffmpegPath: string; ffmpegPath: string;
instanceDetails: ConfigInstanceDetailsFields; instanceDetails: ConfigInstanceDetailsFields;
rtmpServerPort: string; rtmpServerPort: string;
s3: any; // tbd s3: S3Field;
streamKey: string; streamKey: string;
webServerPort: string; webServerPort: string;
yp: ConfigDirectoryFields; yp: ConfigDirectoryFields;
+6 -6
View File
@@ -13,12 +13,7 @@ export const STATUS_PROCESSING = 'proessing';
export const STATUS_SUCCESS = 'success'; export const STATUS_SUCCESS = 'success';
export const STATUS_WARNING = 'warning'; export const STATUS_WARNING = 'warning';
export type InputStatusTypes = export type InputStatusTypes = 'error' | 'invalid' | 'proessing' | 'success' | 'warning';
| typeof STATUS_ERROR
| typeof STATUS_INVALID
| typeof STATUS_PROCESSING
| typeof STATUS_SUCCESS
| typeof STATUS_WARNING;
export type StatusState = { export type StatusState = {
type: InputStatusTypes; type: InputStatusTypes;
@@ -28,22 +23,27 @@ export type StatusState = {
export const INPUT_STATES = { export const INPUT_STATES = {
[STATUS_SUCCESS]: { [STATUS_SUCCESS]: {
type: STATUS_SUCCESS,
icon: <CheckCircleFilled style={{ color: 'green' }} />, icon: <CheckCircleFilled style={{ color: 'green' }} />,
message: 'Success!', message: 'Success!',
}, },
[STATUS_ERROR]: { [STATUS_ERROR]: {
type: STATUS_ERROR,
icon: <ExclamationCircleFilled style={{ color: 'red' }} />, icon: <ExclamationCircleFilled style={{ color: 'red' }} />,
message: 'An error occurred.', message: 'An error occurred.',
}, },
[STATUS_INVALID]: { [STATUS_INVALID]: {
type: STATUS_INVALID,
icon: <ExclamationCircleFilled style={{ color: 'red' }} />, icon: <ExclamationCircleFilled style={{ color: 'red' }} />,
message: 'An error occurred.', message: 'An error occurred.',
}, },
[STATUS_PROCESSING]: { [STATUS_PROCESSING]: {
type: STATUS_PROCESSING,
icon: <LoadingOutlined />, icon: <LoadingOutlined />,
message: '', message: '',
}, },
[STATUS_WARNING]: { [STATUS_WARNING]: {
type: STATUS_WARNING,
icon: <WarningOutlined style={{ color: '#fc0' }} />, icon: <WarningOutlined style={{ color: '#fc0' }} />,
message: '', message: '',
}, },
+21 -14
View File
@@ -23,7 +23,16 @@ export const initialServerConfigState: ConfigDetails = {
ffmpegPath: '', ffmpegPath: '',
rtmpServerPort: '', rtmpServerPort: '',
webServerPort: '', webServerPort: '',
s3: {}, s3: {
accessKey: '',
acl: '',
bucket: '',
enabled: false,
endpoint: '',
region: '',
secret: '',
servingEndpoint: '',
},
yp: { yp: {
enabled: false, enabled: false,
instanceUrl: '', instanceUrl: '',
@@ -32,7 +41,7 @@ export const initialServerConfigState: ConfigDetails = {
latencyLevel: 4, latencyLevel: 4,
cpuUsageLevel: 3, cpuUsageLevel: 3,
videoQualityVariants: [DEFAULT_VARIANT_STATE], videoQualityVariants: [DEFAULT_VARIANT_STATE],
} },
}; };
const initialServerStatusState = { const initialServerStatusState = {
@@ -51,7 +60,9 @@ export const ServerStatusContext = React.createContext({
...initialServerStatusState, ...initialServerStatusState,
serverConfig: initialServerConfigState, serverConfig: initialServerConfigState,
setFieldInConfigState: (args: UpdateArgs) => { return args }, setFieldInConfigState: (args: UpdateArgs) => {
return args;
},
}); });
const ServerStatusProvider = ({ children }) => { const ServerStatusProvider = ({ children }) => {
@@ -62,7 +73,6 @@ const ServerStatusProvider = ({ children }) => {
try { try {
const result = await fetchData(STATUS); const result = await fetchData(STATUS);
setStatus({ ...result }); setStatus({ ...result });
} catch (error) { } catch (error) {
// todo // todo
} }
@@ -77,22 +87,21 @@ const ServerStatusProvider = ({ children }) => {
}; };
const setFieldInConfigState = ({ fieldName, value, path }: UpdateArgs) => { const setFieldInConfigState = ({ fieldName, value, path }: UpdateArgs) => {
const updatedConfig = path ? const updatedConfig = path
{ ? {
...config, ...config,
[path]: { [path]: {
...config[path], ...config[path],
[fieldName]: value, [fieldName]: value,
}, },
} : }
{ : {
...config, ...config,
[fieldName]: value, [fieldName]: value,
}; };
setConfig(updatedConfig); setConfig(updatedConfig);
}; };
useEffect(() => { useEffect(() => {
let getStatusIntervalId = null; let getStatusIntervalId = null;
@@ -104,7 +113,7 @@ const ServerStatusProvider = ({ children }) => {
// returned function will be called on component unmount // returned function will be called on component unmount
return () => { return () => {
clearInterval(getStatusIntervalId); clearInterval(getStatusIntervalId);
} };
}, []); }, []);
const providerValue = { const providerValue = {
@@ -114,11 +123,9 @@ const ServerStatusProvider = ({ children }) => {
setFieldInConfigState, setFieldInConfigState,
}; };
return ( return (
<ServerStatusContext.Provider value={providerValue}> <ServerStatusContext.Provider value={providerValue}>{children}</ServerStatusContext.Provider>
{children}
</ServerStatusContext.Provider>
); );
} };
ServerStatusProvider.propTypes = { ServerStatusProvider.propTypes = {
children: PropTypes.element.isRequired, children: PropTypes.element.isRequired,