Просмотр исходного кода

Merge pull request #2706 from weseek/imprv/create-ses-form

Imprv/create ses form
Yuki Takei 5 лет назад
Родитель
Сommit
c86cb68fd1

+ 0 - 3
resource/locales/en_US/admin/admin.json

@@ -27,9 +27,6 @@
     "attach_enable": "You can attach files other than image files if you enable this option.",
     "update": "Update",
     "mail_settings": "E-mail Settings",
-    "smtp_used": "If you have SMTP settings, it will be used.",
-    "smtp_but_aws": "If you do not have SMTP settings but AWS settings,  e-mails will be sent by SES.",
-    "neihter_of": "If neither is selected, then no email will be sent.",
     "from_e-mail_address": "From e-mail address",
     "smtp_settings": "SMTP settings",
     "host": "Host",

+ 2 - 1
resource/locales/en_US/translation.json

@@ -747,6 +747,7 @@
   },
   "validation":{
     "aws_region": "For the region, enter the AWS region name. ex):us-east-1",
-    "aws_custom_endpoint":"For the custom endpoint, specify the URL that starts with http(s)://. Also, the trailing slash is not required."
+    "aws_custom_endpoint":"For the custom endpoint, specify the URL that starts with http(s)://. Also, the trailing slash is not required.",
+    "failed_to_send_a_test_email":"Failed to send a test email using SMTP. Please check your settings."
   }
 }

+ 0 - 3
resource/locales/ja_JP/admin/admin.json

@@ -27,9 +27,6 @@
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
     "update": "更新",
     "mail_settings": "メールの設定",
-    "smtp_used": "SMTPの設定がされている場合、それが利用されます。",
-    "smtp_but_aws": "SMTP設定がなく、AWSの設定がある場合、SESでの送信を試みます。",
-    "neihter_of": "どちらの設定もない場合、メールは送信されません。",
     "from_e-mail_address": "Fromアドレス",
     "smtp_settings": "SMTP設定",
     "host": "ホスト",

+ 2 - 1
resource/locales/ja_JP/translation.json

@@ -740,6 +740,7 @@
   },
   "validation":{
     "aws_region": "リージョンには、AWSリージョン名を入力してください。例: ap-northeast-1",
-    "aws_custom_endpoint": "カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。"
+    "aws_custom_endpoint": "カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。",
+    "failed_to_send_a_test_email":"SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。"
   }
 }

+ 0 - 3
resource/locales/zh_CN/admin/admin.json

@@ -27,9 +27,6 @@
 		"attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
 		"update": "更新",
 		"mail_settings": "邮件设置",
-		"smtp_used": "如果您有SMTP设置,将使用它。",
-		"smtp_but_aws": "如果您没有SMTP设置,但有AWS设置,则电子邮件将由SES发送。",
-		"neihter_of": "如果两者都未选中,则不会发送电子邮件。",
 		"from_e-mail_address": "From e-mail address",
 		"smtp_settings": "SMTP 设置",
 		"host": "服务器",

+ 2 - 1
resource/locales/zh_CN/translation.json

@@ -741,6 +741,7 @@
   },
   "validation":{
     "aws_region": "关于地区,请输入AWS地区名,例如:ap-east-1",
-    "aws_custom_endpoint": "关于自定义端点,请指定以http(s)://开头的URL,链接末尾不需要添加“/”"
+    "aws_custom_endpoint": "关于自定义端点,请指定以http(s)://开头的URL,链接末尾不需要添加“/”",
+    "failed_to_send_a_test_email":"SMTP方式测试邮件发送失败,请检查相关设定。"
   }
 }

+ 42 - 130
src/client/js/components/Admin/App/MailSetting.jsx

@@ -3,14 +3,18 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import loggerFactory from '@alias/logger';
 
-import { Modal, ModalHeader, ModalBody } from 'reactstrap';
+import {
+  TabContent, TabPane, Nav, NavItem, NavLink,
+} from 'reactstrap';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
+import SmtpSetting from './SmtpSetting';
+import SesSetting from './SesSetting';
 
-const logger = loggerFactory('growi:appSettings');
+const logger = loggerFactory('growi:mailSettings');
 
 class MailSetting extends React.Component {
 
@@ -18,7 +22,9 @@ class MailSetting extends React.Component {
     super(props);
 
     this.state = {
-      isInitializeValueModalOpen: false,
+      activeTab: 'smtp-setting',
+      // Prevent unnecessary rendering
+      activeComponents: new Set(['smtp-setting']),
     };
 
     this.emailInput = React.createRef();
@@ -27,32 +33,13 @@ class MailSetting extends React.Component {
     this.userInput = React.createRef();
     this.passwordInput = React.createRef();
 
-    this.openInitializeValueModal = this.openInitializeValueModal.bind(this);
-    this.closeInitializeValueModal = this.closeInitializeValueModal.bind(this);
     this.submitFromAdressHandler = this.submitFromAdressHandler.bind(this);
-    this.submitHandler = this.submitHandler.bind(this);
-    this.initialize = this.initialize.bind(this);
   }
 
-  openInitializeValueModal() {
-    this.setState({ isInitializeValueModalOpen: true });
-  }
-
-  closeInitializeValueModal() {
-    this.setState({ isInitializeValueModalOpen: false });
-  }
-
-  async submitHandler() {
-    const { t, adminAppContainer } = this.props;
-
-    try {
-      await adminAppContainer.updateMailSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.mail_settings') }));
-    }
-    catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
+  toggleActiveTab(activeTab) {
+    this.setState({
+      activeTab, activeComponents: this.state.activeComponents.add(activeTab),
+    });
   }
 
   async submitFromAdressHandler() {
@@ -68,31 +55,12 @@ class MailSetting extends React.Component {
     }
   }
 
-  async initialize() {
-    const { t, adminAppContainer } = this.props;
-
-    try {
-      const mailSettingParams = await adminAppContainer.initializeMailSettingHandler();
-      toastSuccess(t('toaster.initialize_successed', { target: t('admin:app_setting.smtp_settings') }));
-      // convert values to '' if value is null for overwriting values of inputs with refs
-      this.hostInput.current.value = mailSettingParams.smtpHost || '';
-      this.portInput.current.value = mailSettingParams.smtpPort || '';
-      this.userInput.current.value = mailSettingParams.smtpUser || '';
-      this.passwordInput.current.value = mailSettingParams.smtpPassword || '';
-      this.closeInitializeValueModal();
-    }
-    catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
-  }
-
   render() {
     const { t, adminAppContainer } = this.props;
+    const { activeTab, activeComponents } = this.state;
 
     return (
       <React.Fragment>
-        <p className="card well">{t('admin:app_setting.smtp_used')} {t('admin:app_setting.smtp_but_aws')}<br />{t('admin:app_setting.neihter_of')}</p>
         <div className="row form-group mb-5">
           <label className="col-md-3 col-form-label text-left">{t('admin:app_setting.from_e-mail_address')}</label>
           <div className="col-md-6">
@@ -111,91 +79,35 @@ class MailSetting extends React.Component {
             <button type="button" className="btn btn-primary" onClick={this.submitFromAdressHandler}>{ t('Update') }</button>
           </div>
         </div>
-        <div id="mail-smtp" className="tab-pane active mt-5">
-          <div className="row form-group mb-5">
-            <label className="col-md-3 col-form-label text-left">{t('admin:app_setting.smtp_settings')}</label>
-            <div className="col-md-4">
-              <label>{t('admin:app_setting.host')}</label>
-              <input
-                className="form-control"
-                type="text"
-                ref={this.hostInput}
-                defaultValue={adminAppContainer.state.smtpHost || ''}
-                onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
-              />
-            </div>
-            <div className="col-md-2">
-              <label>{t('admin:app_setting.port')}</label>
-              <input
-                className="form-control"
-                ref={this.portInput}
-                defaultValue={adminAppContainer.state.smtpPort || ''}
-                onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
-              />
-            </div>
-          </div>
-
-          <div className="row form-group mb-5">
-            <div className="col-md-3 offset-md-3">
-              <label>{t('admin:app_setting.user')}</label>
-              <input
-                className="form-control"
-                type="text"
-                ref={this.userInput}
-                defaultValue={adminAppContainer.state.smtpUser || ''}
-                onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
-              />
-            </div>
-            <div className="col-md-3">
-              <label>{t('Password')}</label>
-              <input
-                className="form-control"
-                type="password"
-                ref={this.passwordInput}
-                defaultValue={adminAppContainer.state.smtpPassword || ''}
-                onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
-              />
-            </div>
-          </div>
-
-          <div className="row my-3">
-            <div className="offset-5">
-              <button type="button" className="btn btn-primary" onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null}>
-                { t('Update') }
-              </button>
-            </div>
-            <div className="offset-1">
-              <button
-                type="button"
-                className="btn btn-secondary"
-                onClick={this.openInitializeValueModal}
-                disabled={adminAppContainer.state.retrieveError != null}
-              >
-                {t('admin:app_setting.initialize_mail_settings')}
-              </button>
-            </div>
-          </div>
-        </div>
-
 
-        <Modal isOpen={this.state.isInitializeValueModalOpen} toggle={this.closeInitializeValueModal} className="initialize-mail-settings">
-          <ModalHeader tag="h4" toggle={this.closeInitializeValueModal} className="bg-danger text-light">
-            {t('admin:app_setting.initialize_mail_modal_header')}
-          </ModalHeader>
-          <ModalBody>
-            <div className="text-center mb-4">
-              {t('admin:app_setting.confirm_to_initialize_mail_settings')}
-            </div>
-            <div className="text-center my-2">
-              <button type="button" className="btn btn-outline-secondary mr-4" onClick={this.closeInitializeValueModal}>
-                {t('Cancel')}
-              </button>
-              <button type="button" className="btn btn-danger" onClick={this.initialize}>
-                {t('Reset')}
-              </button>
-            </div>
-          </ModalBody>
-        </Modal>
+        <Nav tabs>
+          <NavItem>
+            <NavLink
+              className={`${activeTab === 'smtp-setting' && 'active'} `}
+              onClick={() => { this.toggleActiveTab('smtp-setting') }}
+              href="#smtp-setting"
+            >
+              SMTP
+            </NavLink>
+          </NavItem>
+          <NavItem>
+            <NavLink
+              className={`${activeTab === 'ses-setting' && 'active'} `}
+              onClick={() => { this.toggleActiveTab('ses-setting') }}
+              href="#ses-setting"
+            >
+              SES(AWS)
+            </NavLink>
+          </NavItem>
+        </Nav>
+        <TabContent activeTab={activeTab}>
+          <TabPane tabId="smtp-setting">
+            {activeComponents.has('smtp-setting') && <SmtpSetting />}
+          </TabPane>
+          <TabPane tabId="ses-setting">
+            {activeComponents.has('ses-setting') && <SesSetting />}
+          </TabPane>
+        </TabContent>
       </React.Fragment>
     );
   }

+ 95 - 0
src/client/js/components/Admin/App/SesSetting.jsx

@@ -0,0 +1,95 @@
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+import { withLoadingSppiner } from '../../SuspenseUtils';
+
+
+import AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+
+const logger = loggerFactory('growi:smtpSettings');
+
+
+function SmtpSetting(props) {
+  const { adminAppContainer, t } = props;
+
+  async function submitHandler() {
+    const { t } = props;
+
+    try {
+      // TODO GW-3627 update ses setting
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.mail_settings') }));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  return (
+    <React.Fragment>
+      <div id="mail-smtp" className="tab-pane active mt-5">
+        <label className="col-md-3 col-form-label text-left mb-3">SES {t('settings')}</label>
+
+        <div className="row form-group">
+          <label className="text-left text-md-right col-md-3 col-form-label">
+            Access key ID
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.sesAccessKeyId || ''}
+              onChange={(e) => {
+                adminAppContainer.changeSesAccessKeyId(e.target.value);
+              }}
+            />
+          </div>
+        </div>
+
+        <div className="row form-group">
+          <label className="text-left text-md-right col-md-3 col-form-label">
+            Secret access key
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.sesSecretAccessKey || ''}
+              onChange={(e) => {
+                adminAppContainer.changeSesSecretAccessKey(e.target.value);
+              }}
+            />
+          </div>
+        </div>
+
+        <div className="row my-3">
+          <div className="mx-auto">
+            <button type="button" className="btn btn-primary" onClick={submitHandler} disabled={adminAppContainer.state.retrieveError != null}>
+              { t('Update') }
+            </button>
+          </div>
+        </div>
+      </div>
+
+    </React.Fragment>
+  );
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const SmtpSettingWrapper = withUnstatedContainers(withLoadingSppiner(SmtpSetting), [AppContainer, AdminAppContainer]);
+
+SmtpSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+};
+
+export default withTranslation()(SmtpSettingWrapper);

+ 176 - 0
src/client/js/components/Admin/App/SmtpSetting.jsx

@@ -0,0 +1,176 @@
+
+import React, { useState, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+
+import {
+  Modal, ModalHeader, ModalBody,
+} from 'reactstrap';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+import { withLoadingSppiner } from '../../SuspenseUtils';
+
+
+import AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+
+const logger = loggerFactory('growi:smtpSettings');
+
+
+function SmtpSetting(props) {
+  const { adminAppContainer, t } = props;
+
+  const hostInput = useRef();
+  const portInput = useRef();
+  const userInput = useRef();
+  const passwordInput = useRef();
+
+  const [isInitializeValueModalOpen, setIsInitializeValueModalOpen] = useState(false);
+
+  function openInitializeValueModal() {
+    setIsInitializeValueModalOpen(true);
+
+  }
+
+  function closeInitializeValueModal() {
+    setIsInitializeValueModalOpen(false);
+  }
+
+  async function submitHandler() {
+    const { t, adminAppContainer } = props;
+
+    try {
+      await adminAppContainer.updateMailSettingHandler();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.mail_settings') }));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  async function initialize() {
+    const { t, adminAppContainer } = props;
+
+    try {
+      const mailSettingParams = await adminAppContainer.initializeMailSettingHandler();
+      toastSuccess(t('toaster.initialize_successed', { target: t('admin:app_setting.smtp_settings') }));
+      // convert values to '' if value is null for overwriting values of inputs with refs
+      hostInput.current.value = mailSettingParams.smtpHost || '';
+      portInput.current.value = mailSettingParams.smtpPort || '';
+      userInput.current.value = mailSettingParams.smtpUser || '';
+      passwordInput.current.value = mailSettingParams.smtpPassword || '';
+      closeInitializeValueModal();
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+
+  return (
+    <React.Fragment>
+      <div id="mail-smtp" className="tab-pane active mt-5">
+        <div className="row form-group mb-5">
+          <label className="col-md-3 col-form-label text-left">{t('admin:app_setting.smtp_settings')}</label>
+          <div className="col-md-4">
+            <label>{t('admin:app_setting.host')}</label>
+            <input
+              className="form-control"
+              type="text"
+              ref={hostInput}
+              defaultValue={adminAppContainer.state.smtpHost || ''}
+              onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
+            />
+          </div>
+          <div className="col-md-2">
+            <label>{t('admin:app_setting.port')}</label>
+            <input
+              className="form-control"
+              ref={portInput}
+              defaultValue={adminAppContainer.state.smtpPort || ''}
+              onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
+            />
+          </div>
+        </div>
+
+        <div className="row form-group mb-5">
+          <div className="col-md-3 offset-md-3">
+            <label>{t('admin:app_setting.user')}</label>
+            <input
+              className="form-control"
+              type="text"
+              ref={userInput}
+              defaultValue={adminAppContainer.state.smtpUser || ''}
+              onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
+            />
+          </div>
+          <div className="col-md-3">
+            <label>{t('Password')}</label>
+            <input
+              className="form-control"
+              type="password"
+              ref={passwordInput}
+              defaultValue={adminAppContainer.state.smtpPassword || ''}
+              onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
+            />
+          </div>
+        </div>
+
+        <div className="row my-3">
+          <div className="offset-5">
+            <button type="button" className="btn btn-primary" onClick={submitHandler} disabled={adminAppContainer.state.retrieveError != null}>
+              { t('Update') }
+            </button>
+          </div>
+          <div className="offset-1">
+            <button
+              type="button"
+              className="btn btn-secondary"
+              onClick={openInitializeValueModal}
+              disabled={adminAppContainer.state.retrieveError != null}
+            >
+              {t('admin:app_setting.initialize_mail_settings')}
+            </button>
+          </div>
+        </div>
+      </div>
+
+
+      <Modal isOpen={isInitializeValueModalOpen} toggle={closeInitializeValueModal} className="initialize-mail-settings">
+        <ModalHeader tag="h4" toggle={closeInitializeValueModal} className="bg-danger text-light">
+          {t('admin:app_setting.initialize_mail_modal_header')}
+        </ModalHeader>
+        <ModalBody>
+          <div className="text-center mb-4">
+            {t('admin:app_setting.confirm_to_initialize_mail_settings')}
+          </div>
+          <div className="text-center my-2">
+            <button type="button" className="btn btn-outline-secondary mr-4" onClick={closeInitializeValueModal}>
+              {t('Cancel')}
+            </button>
+            <button type="button" className="btn btn-danger" onClick={initialize}>
+              {t('Reset')}
+            </button>
+          </div>
+        </ModalBody>
+      </Modal>
+
+    </React.Fragment>
+  );
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const SmtpSettingWrapper = withUnstatedContainers(withLoadingSppiner(SmtpSetting), [AppContainer, AdminAppContainer]);
+
+SmtpSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+};
+
+export default withTranslation()(SmtpSettingWrapper);

+ 18 - 16
src/client/js/services/AdminAppContainer.js

@@ -28,6 +28,8 @@ export default class AdminAppContainer extends Container {
       smtpPort: '',
       smtpUser: '',
       smtpPassword: '',
+      sesAccessKeyId: '',
+      sesSecretAccessKey: '',
       region: '',
       customEndpoint: '',
       bucket: '',
@@ -36,22 +38,6 @@ export default class AdminAppContainer extends Container {
       isEnabledPlugins: true,
     };
 
-    this.changeTitle = this.changeTitle.bind(this);
-    this.changeConfidential = this.changeConfidential.bind(this);
-    this.changeGlobalLang = this.changeGlobalLang.bind(this);
-    this.changeFileUpload = this.changeFileUpload.bind(this);
-    this.changeSiteUrl = this.changeSiteUrl.bind(this);
-    this.changeFromAddress = this.changeFromAddress.bind(this);
-    this.changeSmtpHost = this.changeSmtpHost.bind(this);
-    this.changeSmtpPort = this.changeSmtpPort.bind(this);
-    this.changeSmtpUser = this.changeSmtpUser.bind(this);
-    this.changeSmtpPassword = this.changeSmtpPassword.bind(this);
-    this.changeRegion = this.changeRegion.bind(this);
-    this.changeCustomEndpoint = this.changeCustomEndpoint.bind(this);
-    this.changeBucket = this.changeBucket.bind(this);
-    this.changeAccessKeyId = this.changeAccessKeyId.bind(this);
-    this.changeSecretAccessKey = this.changeSecretAccessKey.bind(this);
-    this.changeIsEnabledPlugins = this.changeIsEnabledPlugins.bind(this);
     this.updateAppSettingHandler = this.updateAppSettingHandler.bind(this);
     this.updateSiteUrlSettingHandler = this.updateSiteUrlSettingHandler.bind(this);
     this.updateMailSettingHandler = this.updateMailSettingHandler.bind(this);
@@ -86,6 +72,8 @@ export default class AdminAppContainer extends Container {
       smtpPort: appSettingsParams.smtpPort,
       smtpUser: appSettingsParams.smtpUser,
       smtpPassword: appSettingsParams.smtpPassword,
+      sesAccessKeyId: appSettingsParams.sesAccessKeyId,
+      sesSecretAccessKey: appSettingsParams.sesSecretAccessKey,
       region: appSettingsParams.region,
       customEndpoint: appSettingsParams.customEndpoint,
       bucket: appSettingsParams.bucket,
@@ -166,6 +154,20 @@ export default class AdminAppContainer extends Container {
     this.setState({ smtpPassword });
   }
 
+  /**
+   * Change sesAccessKeyId
+   */
+  changeSesAccessKeyId(sesAccessKeyId) {
+    this.setState({ sesAccessKeyId });
+  }
+
+  /**
+   * Change sesSecretAccessKey
+   */
+  changeSesSecretAccessKey(sesSecretAccessKey) {
+    this.setState({ sesSecretAccessKey });
+  }
+
   /**
    * Change region
    */

+ 1 - 1
src/server/routes/apiv3/app-settings.js

@@ -417,7 +417,7 @@ module.exports = (crowi) => {
       await validateMailSetting(req);
     }
     catch (err) {
-      const msg = 'SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。';
+      const msg = req.t('validation.failed_to_send_a_test_email');
       logger.error('Error', err);
       debug('Error validate mail setting: ', err);
       return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));