Merge branch 'css-overhaul' into gw/20201226-admin-formfields
This commit is contained in:
@@ -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,50 +63,52 @@ 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">
|
||||||
<TextFieldWithSubmit
|
<div className="left-side">
|
||||||
fieldName="streamKey"
|
<TextFieldWithSubmit
|
||||||
{...TEXTFIELD_PROPS_STREAM_KEY}
|
fieldName="streamKey"
|
||||||
value={formDataValues.streamKey}
|
{...TEXTFIELD_PROPS_STREAM_KEY}
|
||||||
initialValue={streamKey}
|
value={formDataValues.streamKey}
|
||||||
type={TEXTFIELD_TYPE_PASSWORD}
|
initialValue={streamKey}
|
||||||
onChange={handleFieldChange}
|
type={TEXTFIELD_TYPE_PASSWORD}
|
||||||
/>
|
onChange={handleFieldChange}
|
||||||
<div>
|
/>
|
||||||
<span style={{ fontSize: '0.75em', color: '#ff7777', marginRight: '0.5em' }}>
|
<div className="streamkey-actions">
|
||||||
Save this key somewhere safe, you will need it to stream or login to the admin
|
<Tooltip className="copy-tooltip" title="Copied!" trigger="" visible={copyIsVisible}>
|
||||||
dashboard!
|
<Button icon={<CopyOutlined />} size="small" onClick={copyStreamKey} />
|
||||||
</span>
|
</Tooltip>
|
||||||
<Tooltip className="copy-tooltip" title="Copied!" trigger="" visible={copyIsVisible}>
|
<Button icon={<RedoOutlined />} size="small" onClick={generateStreamKey} />
|
||||||
<Button type="primary" icon={<CopyOutlined />} size="small" onClick={copyStreamKey} />
|
</div>
|
||||||
</Tooltip>
|
</div>
|
||||||
<Button type="primary" icon={<RedoOutlined />} size="small" onClick={generateStreamKey} />
|
<div className="streamkey-notice">
|
||||||
|
Save this key somewhere safe, you will need it to stream or login to the admin
|
||||||
|
dashboard!
|
||||||
</div>
|
</div>
|
||||||
<TextFieldWithSubmit
|
|
||||||
fieldName="ffmpegPath"
|
|
||||||
{...TEXTFIELD_PROPS_FFMPEG}
|
|
||||||
value={formDataValues.ffmpegPath}
|
|
||||||
initialValue={ffmpegPath}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
<TextFieldWithSubmit
|
|
||||||
fieldName="webServerPort"
|
|
||||||
{...TEXTFIELD_PROPS_WEB_PORT}
|
|
||||||
value={formDataValues.webServerPort}
|
|
||||||
initialValue={webServerPort}
|
|
||||||
type={TEXTFIELD_TYPE_NUMBER}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
<TextFieldWithSubmit
|
|
||||||
fieldName="rtmpServerPort"
|
|
||||||
{...TEXTFIELD_PROPS_RTMP_PORT}
|
|
||||||
value={formDataValues.rtmpServerPort}
|
|
||||||
initialValue={rtmpServerPort}
|
|
||||||
type={TEXTFIELD_TYPE_NUMBER}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<TextFieldWithSubmit
|
||||||
|
fieldName="ffmpegPath"
|
||||||
|
{...TEXTFIELD_PROPS_FFMPEG}
|
||||||
|
value={formDataValues.ffmpegPath}
|
||||||
|
initialValue={ffmpegPath}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<TextFieldWithSubmit
|
||||||
|
fieldName="webServerPort"
|
||||||
|
{...TEXTFIELD_PROPS_WEB_PORT}
|
||||||
|
value={formDataValues.webServerPort}
|
||||||
|
initialValue={webServerPort}
|
||||||
|
type={TEXTFIELD_TYPE_NUMBER}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<TextFieldWithSubmit
|
||||||
|
fieldName="rtmpServerPort"
|
||||||
|
{...TEXTFIELD_PROPS_RTMP_PORT}
|
||||||
|
value={formDataValues.rtmpServerPort}
|
||||||
|
initialValue={rtmpServerPort}
|
||||||
|
type={TEXTFIELD_TYPE_NUMBER}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
</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
|
||||||
|
|||||||
218
web/pages/components/config/edit-storage.tsx
Normal file
218
web/pages/components/config/edit-storage.tsx
Normal file
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
22
web/pages/components/config/form-status-indicator.tsx
Normal file
22
web/pages/components/config/form-status-indicator.tsx
Normal file
@@ -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}>
|
||||||
<TextField
|
<div className="textfield-component">
|
||||||
{...textFieldProps}
|
<TextField
|
||||||
status={status || fieldStatus}
|
{...textFieldProps}
|
||||||
onSubmit={null}
|
onSubmit={null}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{hasChanged ? (
|
<div className="textfield-container lower-container">
|
||||||
<div className="update-button-container">
|
<p className="label-spacer" />
|
||||||
<Button type="primary" size="small" className="submit-button" onClick={handleSubmit}>
|
<div className="lower-content">
|
||||||
Update
|
<div className="field-tip">{tip}</div>
|
||||||
</Button>
|
<FormStatusIndicator status={status || submitStatus} />
|
||||||
|
<div className="update-button-container">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
className="submit-button"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!hasChanged}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,14 +11,12 @@ 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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
|
||||||
.required-label {
|
|
||||||
color: var(--ant-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ':';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.required {
|
||||||
|
.textfield-label {
|
||||||
|
&::before {
|
||||||
|
content: '*';
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: .25em;
|
||||||
|
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;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
.update-button-container {
|
align-items: flex-start;
|
||||||
display: inline-block;
|
margin-bottom: 1em;
|
||||||
margin: .25em;
|
|
||||||
|
.textfield-component {
|
||||||
|
width: 100%;
|
||||||
|
.textfield-container {
|
||||||
|
.field-tip,
|
||||||
|
.status-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lack of a better name
|
||||||
|
.lower-container {
|
||||||
|
display: flex;
|
||||||
|
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 {
|
||||||
|
visibility: hidden;
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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: '',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,21 +113,19 @@ 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 = {
|
||||||
...status,
|
...status,
|
||||||
serverConfig: config,
|
serverConfig: config,
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
Reference in New Issue
Block a user