ManageCommandsProcessWithoutProxy.jsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import React, { useCallback, useEffect, useState } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { useTranslation } from 'react-i18next';
  4. import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
  5. import loggerFactory from '~/utils/logger';
  6. import { toastSuccess, toastError } from '../../../client/util/apiNotification';
  7. const logger = loggerFactory('growi:SlackIntegration:ManageCommandsProcess');
  8. const PermissionTypes = {
  9. ALLOW_ALL: 'allowAll',
  10. DENY_ALL: 'denyAll',
  11. ALLOW_SPECIFIED: 'allowSpecified',
  12. };
  13. const defaultCommandsName = [...defaultSupportedCommandsNameForBroadcastUse, ...defaultSupportedCommandsNameForSingleUse];
  14. // A utility function that returns the new state but identical to the previous state
  15. const getUpdatedChannelsList = (commandPermissionObj, commandName, value) => {
  16. // string to array
  17. const allowedChannelsArray = value.split(',');
  18. // trim whitespace from all elements
  19. const trimedAllowedChannelsArray = allowedChannelsArray.map(channelName => channelName.trim());
  20. commandPermissionObj[commandName] = trimedAllowedChannelsArray;
  21. return commandPermissionObj;
  22. };
  23. // A utility function that returns the new state
  24. const getUpdatedPermissionSettings = (commandPermissionObj, commandName, value) => {
  25. const editedCommandPermissionObj = { ...commandPermissionObj };
  26. switch (value) {
  27. case PermissionTypes.ALLOW_ALL:
  28. editedCommandPermissionObj[commandName] = true;
  29. break;
  30. case PermissionTypes.DENY_ALL:
  31. editedCommandPermissionObj[commandName] = false;
  32. break;
  33. case PermissionTypes.ALLOW_SPECIFIED:
  34. editedCommandPermissionObj[commandName] = [];
  35. break;
  36. default:
  37. logger.error('Not implemented');
  38. break;
  39. }
  40. return editedCommandPermissionObj;
  41. };
  42. const PermissionSettingForEachCommandComponent = ({
  43. commandName, editingCommandPermission, onPermissionTypeClicked, onPermissionListChanged,
  44. }) => {
  45. const { t } = useTranslation();
  46. if (editingCommandPermission == null) {
  47. return null;
  48. }
  49. function permissionTypeClickHandler(e) {
  50. if (onPermissionTypeClicked == null) {
  51. return;
  52. }
  53. onPermissionTypeClicked(e);
  54. }
  55. function onPermissionListChangeHandler(e) {
  56. if (onPermissionListChanged == null) {
  57. return;
  58. }
  59. onPermissionListChanged(e);
  60. }
  61. const permission = editingCommandPermission[commandName];
  62. const hiddenClass = Array.isArray(permission) ? '' : 'd-none';
  63. const textareaDefaultValue = Array.isArray(permission) ? permission.join(',') : '';
  64. return (
  65. <div className="my-1 mb-2">
  66. <div className="row align-items-center mb-3">
  67. <p className="col my-auto text-capitalize align-middle">{commandName}</p>
  68. <div className="col dropdown">
  69. <button
  70. className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
  71. type="button"
  72. id="dropdownMenuButton"
  73. data-toggle="dropdown"
  74. aria-haspopup="true"
  75. aria-expanded="true"
  76. >
  77. <span className="float-left">
  78. {permission === true && t('admin:slack_integration.accordion.allow_all')}
  79. {permission === false && t('admin:slack_integration.accordion.deny_all')}
  80. {Array.isArray(permission) && t('admin:slack_integration.accordion.allow_specified')}
  81. </span>
  82. </button>
  83. <div className="dropdown-menu">
  84. <button
  85. className="dropdown-item"
  86. type="button"
  87. name={commandName}
  88. value={PermissionTypes.ALLOW_ALL}
  89. onClick={e => permissionTypeClickHandler(e)}
  90. >
  91. {t('admin:slack_integration.accordion.allow_all_long')}
  92. </button>
  93. <button
  94. className="dropdown-item"
  95. type="button"
  96. name={commandName}
  97. value={PermissionTypes.DENY_ALL}
  98. onClick={e => permissionTypeClickHandler(e)}
  99. >
  100. {t('admin:slack_integration.accordion.deny_all_long')}
  101. </button>
  102. <button
  103. className="dropdown-item"
  104. type="button"
  105. name={commandName}
  106. value={PermissionTypes.ALLOW_SPECIFIED}
  107. onClick={e => permissionTypeClickHandler(e)}
  108. >
  109. {t('admin:slack_integration.accordion.allow_specified_long')}
  110. </button>
  111. </div>
  112. </div>
  113. </div>
  114. <div className={`row-12 row-md-6 ${hiddenClass}`}>
  115. <textarea
  116. className="form-control"
  117. type="textarea"
  118. name={commandName}
  119. value={textareaDefaultValue}
  120. onChange={e => onPermissionListChangeHandler(e)}
  121. />
  122. <p className="form-text text-muted small">
  123. {t('admin:slack_integration.accordion.allowed_channels_description', { commandName })}
  124. <br />
  125. </p>
  126. </div>
  127. </div>
  128. );
  129. };
  130. PermissionSettingForEachCommandComponent.propTypes = {
  131. commandName: PropTypes.string,
  132. editingCommandPermission: PropTypes.object,
  133. onPermissionTypeClicked: PropTypes.func,
  134. onPermissionListChanged: PropTypes.func,
  135. };
  136. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  137. const ManageCommandsProcessWithoutProxy = ({ apiv3Put, commandPermission }) => {
  138. const { t } = useTranslation();
  139. const [editingCommandPermission, setEditingCommandPermission] = useState({});
  140. const updatePermissionsCommandsState = useCallback((e) => {
  141. const { target } = e;
  142. const { name: commandName, value } = target;
  143. // update state
  144. setEditingCommandPermission(commandPermissionObj => getUpdatedPermissionSettings(commandPermissionObj, commandName, value));
  145. }, []);
  146. useEffect(() => {
  147. if (commandPermission == null) {
  148. return;
  149. }
  150. const updatedState = { ...commandPermission };
  151. setEditingCommandPermission(updatedState);
  152. }, [commandPermission]);
  153. const updateChannelsListState = useCallback((e) => {
  154. const { target } = e;
  155. const { name: commandName, value } = target;
  156. // update state
  157. setEditingCommandPermission((commandPermissionObj) => {
  158. return {
  159. ...getUpdatedChannelsList(commandPermissionObj, commandName, value),
  160. };
  161. });
  162. }, []);
  163. const updateCommandsHandler = async(e) => {
  164. try {
  165. await apiv3Put('/slack-integration-settings/without-proxy/update-permissions', {
  166. commandPermission: editingCommandPermission,
  167. });
  168. toastSuccess(t('toaster.update_successed', { target: 'Token' }));
  169. }
  170. catch (err) {
  171. toastError(err);
  172. logger.error(err);
  173. }
  174. };
  175. return (
  176. <div className="py-4 px-5">
  177. <p className="mb-4 font-weight-bold">{t('admin:slack_integration.accordion.manage_commands')}</p>
  178. <div className="row d-flex flex-column align-items-center">
  179. <div className="col-8">
  180. <div className="custom-control custom-checkbox">
  181. <div className="row mb-5 d-block">
  182. { defaultCommandsName.map((commandName) => {
  183. // eslint-disable-next-line max-len
  184. return (
  185. <PermissionSettingForEachCommandComponent
  186. key={`${commandName}-component`}
  187. commandName={commandName}
  188. editingCommandPermission={editingCommandPermission}
  189. onPermissionTypeClicked={updatePermissionsCommandsState}
  190. onPermissionListChanged={updateChannelsListState}
  191. />
  192. );
  193. })}
  194. </div>
  195. </div>
  196. </div>
  197. </div>
  198. <div className="row">
  199. <button
  200. type="submit"
  201. className="btn btn-primary mx-auto"
  202. onClick={updateCommandsHandler}
  203. >
  204. { t('Update') }
  205. </button>
  206. </div>
  207. </div>
  208. );
  209. };
  210. ManageCommandsProcessWithoutProxy.propTypes = {
  211. apiv3Put: PropTypes.func,
  212. commandPermission: PropTypes.object,
  213. };
  214. export default ManageCommandsProcessWithoutProxy;