diff --git a/web/pages/components/config/constants.tsx b/web/pages/components/config/constants.tsx
index 1de2f3fe2..cf6ec3606 100644
--- a/web/pages/components/config/constants.tsx
+++ b/web/pages/components/config/constants.tsx
@@ -184,3 +184,69 @@ export const DEFAULT_SOCIAL_HANDLE: SocialHandle = {
};
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: '',
+ },
+};
diff --git a/web/pages/components/config/edit-server-details.tsx b/web/pages/components/config/edit-server-details.tsx
index 20c40f663..2b048c7e5 100644
--- a/web/pages/components/config/edit-server-details.tsx
+++ b/web/pages/components/config/edit-server-details.tsx
@@ -63,50 +63,48 @@ export default function EditInstanceDetails() {
}
return (
-
-
-
-
-
- Save this key somewhere safe, you will need it to stream or login to the admin
- dashboard!
-
-
- } size="small" onClick={copyStreamKey} />
-
- } size="small" onClick={generateStreamKey} />
-
-
-
-
+
+
+
+
+ Save this key somewhere safe, you will need it to stream or login to the admin
+ dashboard!
+
+
+ } size="small" onClick={copyStreamKey} />
+
+ } size="small" onClick={generateStreamKey} />
+
+
+
);
}
diff --git a/web/pages/components/config/edit-tags.tsx b/web/pages/components/config/edit-tags.tsx
index 9be437ab6..f04c2a2c0 100644
--- a/web/pages/components/config/edit-tags.tsx
+++ b/web/pages/components/config/edit-tags.tsx
@@ -19,7 +19,7 @@ const { Title } = Typography;
export default function EditInstanceTags() {
const [newTagInput, setNewTagInput] = useState
('');
- const [fieldStatus, setFieldStatus] = useState(null);
+ const [submitStatus, setSubmitStatus] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
@@ -38,34 +38,34 @@ export default function EditInstanceTags() {
}, []);
const resetStates = () => {
- setFieldStatus(null);
+ setSubmitStatus(null);
resetTimer = null;
clearTimeout(resetTimer);
};
// posts all the tags at once as an array obj
const postUpdateToAPI = async (postValue: any) => {
- setFieldStatus(createInputStatus(STATUS_PROCESSING));
+ setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({
apiPath,
data: { value: postValue },
onSuccess: () => {
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
- setFieldStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
+ setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
setNewTagInput('');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
onError: (message: string) => {
- setFieldStatus(createInputStatus(STATUS_ERROR, message));
+ setSubmitStatus(createInputStatus(STATUS_ERROR, message));
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
});
};
const handleInputChange = ({ value }: UpdateArgs) => {
- if (!fieldStatus) {
- setFieldStatus(null);
+ if (!submitStatus) {
+ setSubmitStatus(null);
}
setNewTagInput(value);
};
@@ -75,11 +75,11 @@ export default function EditInstanceTags() {
resetStates();
const newTag = newTagInput.trim();
if (newTag === '') {
- setFieldStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
+ setSubmitStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
return;
}
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;
}
@@ -121,7 +121,7 @@ export default function EditInstanceTags() {
onPressEnter={handleSubmitNewTag}
maxLength={maxLength}
placeholder={placeholder}
- status={fieldStatus}
+ status={submitStatus}
/>
diff --git a/web/pages/config-public-details.tsx b/web/pages/config-public-details.tsx
index b18adefd0..3b3009d7a 100644
--- a/web/pages/config-public-details.tsx
+++ b/web/pages/config-public-details.tsx
@@ -11,14 +11,12 @@ export default function PublicFacingDetails() {
<>
Edit your public facing instance details
-
-
>
);
diff --git a/web/pages/config-storage.tsx b/web/pages/config-storage.tsx
index e46713350..9266e035b 100644
--- a/web/pages/config-storage.tsx
+++ b/web/pages/config-storage.tsx
@@ -1,111 +1,230 @@
+import { Typography, 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 { Typography, Switch, Input, Button } from 'antd';
import {
postConfigUpdateToAPI,
API_S3_INFO,
+ RESET_TIMEOUT,
+ S3_TEXT_FIELDS_INFO,
} from './components/config/constants';
-const { Title } = Typography;
-
-function Storage({ config }) {
- 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)
- }
+import {
+ createInputStatus,
+ StatusState,
+ STATUS_ERROR,
+ STATUS_PROCESSING,
+ STATUS_SUCCESS,
+} from '../utils/input-statuses';
+import TextField from './components/config/form-textfield';
+import InputStatusInfo from './components/config/input-status-info';
- function servingEndpointChanged(e) {
- setServingEndpoint(e.target.value)
+const { Title } = Typography;
+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.bucket ||
+ region !== currentValues.region ||
+ servingEndpoint !== currentValues.servingEndpoint ||
+ acl !== currentValues.acl
+ ) {
+ return true;
+ }
+ }
+ } else if (enabled !== currentValues.enabled) {
+ return true;
}
+ return false;
+}
- async function save() {
- const payload = {
- value: {
- enabled: enabled,
- endpoint: endpoint,
- accessKey: accessKey,
- secret: secret,
- bucket: bucket,
- region: region,
- acl: acl,
- servingEndpoint: servingEndpoint,
- }
- };
+function EditStorage() {
+ const [formDataValues, setFormDataValues] = useState(null);
+ const [submitStatus, setSubmitStatus] = useState
(null);
- try {
- await postConfigUpdateToAPI({apiPath: API_S3_INFO, data: payload});
- } catch(e) {
- console.error(e);
- }
+ 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;
}
- const table = enabled ? (
- <>
-
- endpoint
- access key
- secret
- bucket
- region
- advanced
- acl
- serving endpoint
- Save
- >
- ): 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 (
- <>
- Storage
- Enabled:
-
- { table }
- >
+
+
+ {' '}
+ Enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Advanced
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
);
}
-export default function ServerConfig() {
- const serverStatusData = useContext(ServerStatusContext);
- const { serverConfig: config } = serverStatusData || {};
-
+export default function ConfigStorageInfo() {
return (
-
-
-
+ <>
+ Storage
+
+ >
);
}
diff --git a/web/styles/config-formfields.scss b/web/styles/config-formfields.scss
index e49ce5c89..53b8fd03a 100644
--- a/web/styles/config-formfields.scss
+++ b/web/styles/config-formfields.scss
@@ -169,3 +169,30 @@
}
}
}
+
+/* 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;
+ }
+ }
+}
diff --git a/web/styles/config.scss b/web/styles/config.scss
index 9af5cf087..66f568234 100644
--- a/web/styles/config.scss
+++ b/web/styles/config.scss
@@ -98,30 +98,30 @@
// form-toggleswitch
// form-toggleswitch
-.toggleswitch-container {
- .status-message {
- 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;
- }
-}
+// .toggleswitch-container {
+// .status-message {
+// 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;
+// }
+// }
// TAGS STUFF
// TAGS STUFF
@@ -321,3 +321,23 @@
// }
// }
// }
+
+
+
+
+// EDIT STORAGE
+.edit-storage-container {
+ .form-fields {
+ display: none;
+ margin-bottom: 1em;
+ }
+ &.enabled {
+ .form-fields {
+ display: block;
+ }
+ }
+
+ .button-container {
+ margin: 1em 0;
+ }
+}
diff --git a/web/types/config-section.ts b/web/types/config-section.ts
index 25854a0a1..e85036397 100644
--- a/web/types/config-section.ts
+++ b/web/types/config-section.ts
@@ -63,11 +63,22 @@ export interface VideoSettingsFields {
cpuUsageLevel: CpuUsageLevel;
}
+export interface S3Field {
+ acl?: string;
+ accessKey: string;
+ bucket: string;
+ enabled: boolean;
+ endpoint: string;
+ region: string;
+ secret: string;
+ servingEndpoint?: string;
+}
+
export interface ConfigDetails {
ffmpegPath: string;
instanceDetails: ConfigInstanceDetailsFields;
rtmpServerPort: string;
- s3: any; // tbd
+ s3: S3Field;
streamKey: string;
webServerPort: string;
yp: ConfigDirectoryFields;
diff --git a/web/utils/server-status-context.tsx b/web/utils/server-status-context.tsx
index 16f82255c..3c2b2517c 100644
--- a/web/utils/server-status-context.tsx
+++ b/web/utils/server-status-context.tsx
@@ -23,7 +23,16 @@ export const initialServerConfigState: ConfigDetails = {
ffmpegPath: '',
rtmpServerPort: '',
webServerPort: '',
- s3: {},
+ s3: {
+ accessKey: '',
+ acl: '',
+ bucket: '',
+ enabled: false,
+ endpoint: '',
+ region: '',
+ secret: '',
+ servingEndpoint: '',
+ },
yp: {
enabled: false,
instanceUrl: '',
@@ -32,7 +41,7 @@ export const initialServerConfigState: ConfigDetails = {
latencyLevel: 4,
cpuUsageLevel: 3,
videoQualityVariants: [DEFAULT_VARIANT_STATE],
- }
+ },
};
const initialServerStatusState = {
@@ -51,7 +60,9 @@ export const ServerStatusContext = React.createContext({
...initialServerStatusState,
serverConfig: initialServerConfigState,
- setFieldInConfigState: (args: UpdateArgs) => { return args },
+ setFieldInConfigState: (args: UpdateArgs) => {
+ return args;
+ },
});
const ServerStatusProvider = ({ children }) => {
@@ -62,7 +73,6 @@ const ServerStatusProvider = ({ children }) => {
try {
const result = await fetchData(STATUS);
setStatus({ ...result });
-
} catch (error) {
// todo
}
@@ -77,22 +87,21 @@ const ServerStatusProvider = ({ children }) => {
};
const setFieldInConfigState = ({ fieldName, value, path }: UpdateArgs) => {
- const updatedConfig = path ?
- {
- ...config,
- [path]: {
- ...config[path],
- [fieldName]: value,
- },
- } :
- {
- ...config,
- [fieldName]: value,
- };
+ const updatedConfig = path
+ ? {
+ ...config,
+ [path]: {
+ ...config[path],
+ [fieldName]: value,
+ },
+ }
+ : {
+ ...config,
+ [fieldName]: value,
+ };
setConfig(updatedConfig);
};
-
useEffect(() => {
let getStatusIntervalId = null;
@@ -101,27 +110,25 @@ const ServerStatusProvider = ({ children }) => {
getConfig();
- // returned function will be called on component unmount
+ // returned function will be called on component unmount
return () => {
clearInterval(getStatusIntervalId);
- }
+ };
}, []);
const providerValue = {
- ...status,
- serverConfig: config,
+ ...status,
+ serverConfig: config,
- setFieldInConfigState,
+ setFieldInConfigState,
};
return (
-
- {children}
-
+ {children}
);
-}
+};
ServerStatusProvider.propTypes = {
children: PropTypes.element.isRequired,
};
-export default ServerStatusProvider;
\ No newline at end of file
+export default ServerStatusProvider;