EditStorage.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { Button, Collapse } from 'antd';
  2. import classNames from 'classnames';
  3. import React, { useContext, useState, useEffect } from 'react';
  4. import { UpdateArgs } from '../../../../types/config-section';
  5. import { ServerStatusContext } from '../../../../utils/server-status-context';
  6. import { AlertMessageContext } from '../../../../utils/alert-message-context';
  7. import {
  8. postConfigUpdateToAPI,
  9. API_S3_INFO,
  10. RESET_TIMEOUT,
  11. S3_TEXT_FIELDS_INFO,
  12. } from '../../../../utils/config-constants';
  13. import {
  14. createInputStatus,
  15. StatusState,
  16. STATUS_ERROR,
  17. STATUS_PROCESSING,
  18. STATUS_SUCCESS,
  19. } from '../../../../utils/input-statuses';
  20. import { TextField } from '../../TextField';
  21. import { FormStatusIndicator } from '../../FormStatusIndicator';
  22. import { isValidUrl } from '../../../../utils/validators';
  23. import { ToggleSwitch } from '../../ToggleSwitch';
  24. const { Panel } = Collapse;
  25. // we could probably add more detailed checks here
  26. // `currentValues` is what's currently in the global store and in the db
  27. function checkSaveable(formValues: any, currentValues: any) {
  28. const { endpoint, accessKey, secret, bucket, region, enabled, acl, forcePathStyle, pathPrefix } =
  29. formValues;
  30. // if fields are filled out and different from what's in store, then return true
  31. if (enabled) {
  32. if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
  33. if (
  34. enabled !== currentValues.enabled ||
  35. endpoint !== currentValues.endpoint ||
  36. accessKey !== currentValues.accessKey ||
  37. secret !== currentValues.secret ||
  38. bucket !== currentValues.bucket ||
  39. region !== currentValues.region ||
  40. pathPrefix !== currentValues.pathPrefix ||
  41. (!currentValues.acl && acl !== '') ||
  42. (!!currentValues.acl && acl !== currentValues.acl) ||
  43. forcePathStyle !== currentValues.forcePathStyle
  44. ) {
  45. return true;
  46. }
  47. }
  48. } else if (enabled !== currentValues.enabled) {
  49. return true;
  50. }
  51. return false;
  52. }
  53. // eslint-disable-next-line react/function-component-definition
  54. export default function EditStorage() {
  55. const [formDataValues, setFormDataValues] = useState(null);
  56. const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
  57. const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
  58. const serverStatusData = useContext(ServerStatusContext);
  59. const { serverConfig, setFieldInConfigState } = serverStatusData || {};
  60. const { setMessage: setAlertMessage } = useContext(AlertMessageContext);
  61. const { s3 } = serverConfig;
  62. const {
  63. accessKey = '',
  64. acl = '',
  65. bucket = '',
  66. enabled = false,
  67. endpoint = '',
  68. region = '',
  69. secret = '',
  70. pathPrefix = '',
  71. forcePathStyle = false,
  72. } = s3;
  73. useEffect(() => {
  74. setFormDataValues({
  75. accessKey,
  76. acl,
  77. bucket,
  78. enabled,
  79. endpoint,
  80. region,
  81. secret,
  82. pathPrefix,
  83. forcePathStyle,
  84. });
  85. setShouldDisplayForm(enabled);
  86. }, [s3]);
  87. if (!formDataValues) {
  88. return null;
  89. }
  90. let resetTimer = null;
  91. const resetStates = () => {
  92. setSubmitStatus(null);
  93. resetTimer = null;
  94. clearTimeout(resetTimer);
  95. };
  96. // update individual values in state
  97. const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
  98. setFormDataValues({
  99. ...formDataValues,
  100. [fieldName]: value,
  101. });
  102. };
  103. // posts the whole state
  104. const handleSave = async () => {
  105. setSubmitStatus(createInputStatus(STATUS_PROCESSING));
  106. const postValue = formDataValues;
  107. if (postValue?.servingEndpoint) {
  108. postValue.servingEndpoint = postValue?.servingEndpoint?.replace(/\/+$/g, '');
  109. }
  110. await postConfigUpdateToAPI({
  111. apiPath: API_S3_INFO,
  112. data: { value: postValue },
  113. onSuccess: () => {
  114. setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
  115. setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
  116. resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
  117. setAlertMessage(
  118. 'Changing your storage configuration will take place the next time you start a new stream.',
  119. );
  120. },
  121. onError: (message: string) => {
  122. setSubmitStatus(createInputStatus(STATUS_ERROR, message));
  123. resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
  124. },
  125. });
  126. };
  127. // toggle switch.
  128. const handleSwitchChange = (storageEnabled: boolean) => {
  129. setShouldDisplayForm(storageEnabled);
  130. handleFieldChange({ fieldName: 'enabled', value: storageEnabled });
  131. };
  132. const handleForcePathStyleSwitchChange = (forcePathStyleEnabled: boolean) => {
  133. handleFieldChange({ fieldName: 'forcePathStyle', value: forcePathStyleEnabled });
  134. };
  135. const containerClass = classNames({
  136. 'edit-storage-container': true,
  137. 'form-module': true,
  138. enabled: shouldDisplayForm,
  139. });
  140. const isSaveable = checkSaveable(formDataValues, s3);
  141. return (
  142. <div className={containerClass}>
  143. <div className="enable-switch">
  144. <ToggleSwitch
  145. apiPath=""
  146. fieldName="enabled"
  147. label="Use S3 Storage Provider"
  148. checked={formDataValues.enabled}
  149. onChange={handleSwitchChange}
  150. />
  151. {/* <Switch
  152. checked={formDataValues.enabled}
  153. defaultChecked={formDataValues.enabled}
  154. onChange={handleSwitchChange}
  155. checkedChildren="ON"
  156. unCheckedChildren="OFF"
  157. />{' '}
  158. Enabled */}
  159. </div>
  160. <div className="form-fields">
  161. <div className="field-container">
  162. <TextField
  163. {...S3_TEXT_FIELDS_INFO.endpoint}
  164. value={formDataValues.endpoint}
  165. onChange={handleFieldChange}
  166. />
  167. </div>
  168. <div className="field-container">
  169. <TextField
  170. {...S3_TEXT_FIELDS_INFO.accessKey}
  171. value={formDataValues.accessKey}
  172. onChange={handleFieldChange}
  173. />
  174. </div>
  175. <div className="field-container">
  176. <TextField
  177. {...S3_TEXT_FIELDS_INFO.secret}
  178. value={formDataValues.secret}
  179. onChange={handleFieldChange}
  180. />
  181. </div>
  182. <div className="field-container">
  183. <TextField
  184. {...S3_TEXT_FIELDS_INFO.bucket}
  185. value={formDataValues.bucket}
  186. onChange={handleFieldChange}
  187. />
  188. </div>
  189. <div className="field-container">
  190. <TextField
  191. {...S3_TEXT_FIELDS_INFO.region}
  192. value={formDataValues.region}
  193. onChange={handleFieldChange}
  194. />
  195. </div>
  196. <Collapse className="advanced-section">
  197. <Panel header="Optional Settings" key="1">
  198. <div className="field-container">
  199. <TextField
  200. {...S3_TEXT_FIELDS_INFO.acl}
  201. value={formDataValues.acl}
  202. onChange={handleFieldChange}
  203. />
  204. </div>
  205. <div className="field-container">
  206. <TextField
  207. {...S3_TEXT_FIELDS_INFO.pathPrefix}
  208. value={formDataValues.pathPrefix}
  209. onChange={handleFieldChange}
  210. />
  211. </div>
  212. <div className="enable-switch">
  213. <ToggleSwitch
  214. {...S3_TEXT_FIELDS_INFO.forcePathStyle}
  215. fieldName="forcePathStyle"
  216. checked={formDataValues.forcePathStyle}
  217. onChange={handleForcePathStyleSwitchChange}
  218. />
  219. </div>
  220. </Panel>
  221. </Collapse>
  222. </div>
  223. <div className="button-container">
  224. <Button type="primary" onClick={handleSave} disabled={!isSaveable}>
  225. Save
  226. </Button>
  227. <FormStatusIndicator status={submitStatus} />
  228. </div>
  229. </div>
  230. );
  231. }