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

Merge branch 'feat/GW-3658-can-use-GCP-from-db-vars' into feat/GW-3661-show-env-var-in-GCP-settings

# Conflicts:
#	src/client/js/services/AdminAppContainer.js
#	src/server/routes/apiv3/app-settings.js
yusuketk 5 лет назад
Родитель
Сommit
5efd3fd9e3

+ 8 - 2
resource/locales/en_US/admin/admin.json

@@ -38,10 +38,16 @@
     "host": "Host",
     "port": "Port",
     "user": "User",
+    "initialize_mail_settings": "initialize e-mail settings",
+    "initialize_mail_modal_header": "Initialize e-mail settings",
+    "confirm_to_initialize_mail_settings": "You can't restore to the current settings. Are you sure you want to initialize e-mail settings?",
+    "file_upload_settings":"File Upload Settings",
+    "file_upload_method":"File Upload Method",
+    "gcp_label": "GCP",
+    "aws_label": "AWS",
+    "file_upload": "This is for uploading file settings. If you complete file upload settings, file upload function, profile picture function etc will be enabled.",
     "ses_settings":"SES settings",
     "test_connection": "Test connection to mail",
-    "aws_settings": "AWS settings",
-    "aws_access": "This is for AWS settings. If you complete AWS settings, file upload function, profile picture function etc will be enabled.",
     "change_setting": "Caution:if you change this setting not completed, you will not be able to access files you have uploaded so far.",
     "region": "Region",
     "bucket_name": "Bucket name",

+ 8 - 2
resource/locales/ja_JP/admin/admin.json

@@ -38,10 +38,16 @@
     "host": "ホスト",
     "port": "ポート",
     "user": "ユーザー",
+    "initialize_mail_settings": "設定を初期化",
+    "initialize_mail_modal_header": "メール設定の初期化",
+    "confirm_to_initialize_mail_settings": "一度初期化した設定は戻せません。本当に初期化しますか?",
+    "file_upload_settings":"ファイルアップロード設定",
+    "file_upload_method":"ファイルアップロード方法",
+    "gcp_label": "GCP",
+    "aws_label": "AWS",
+    "file_upload": "ファイルをアップロードするための設定を行います。ファイルアップロードの設定を完了させると、ファイルアップロード機能、プロフィール写真機能などが有効になります。",
     "ses_settings":"SES設定",
     "test_connection": "接続テスト",
-    "aws_settings": "AWS設定",
-    "aws_access": "AWS にアクセスするための設定を行います。AWS の設定を完了させると、ファイルアップロード機能、プロフィール写真機能などが有効になります。",
     "change_setting": "この設定を途中で変更すると、これまでにアップロードしたファイル等へのアクセスができなくなりますのでご注意下さい。",
     "region": "リージョン",
     "bucket_name": "バケット名",

+ 8 - 2
resource/locales/zh_CN/admin/admin.json

@@ -38,10 +38,16 @@
 		"host": "服务器",
 		"port": "端口号",
 		"user": "用户名",
+    "initialize_mail_settings": "重置邮件设置",
+    "initialize_mail_modal_header": "重置邮件设置",
+    "confirm_to_initialize_mail_settings": "当前设置将被清空且不可恢复。确认重置?",
+    "file_upload_settings":"File Upload Settings",
+    "file_upload_method":"File Upload Method",
+    "gcp_label": "GCP",
+    "aws_label": "AWS",
+    "file_upload": "This is for uploading file settings. If you complete file upload settings, file upload function, profile picture function etc will be enabled.",
     "ses_settings":"SES设置",
     "test_connection": "测试邮件服务器连接",
-		"aws_settings": "AWS设置",
-		"aws_access": "这是用于AWS设置的。如果您完成了AWS设置,文件上传功能,个人资料图片功能等将被启用。",
 		"": "如果您没有SMTP设置,电子邮件将通过SES发送。您需要从电子邮件地址和生产设置进行验证。",
 		"change_setting": "注意:如果你更改此设置未完成,您将无法访问迄今为止上传的文件。",
 		"region": "Region",

+ 1 - 1
src/client/js/components/Admin/App/AppSetting.jsx

@@ -81,7 +81,7 @@ class AppSetting extends React.Component {
           >
             {t('admin:app_setting.default_language')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-6 py-2">
             {
               localeMetadatas.map(meta => (
                 <div key={meta.id} className="custom-control custom-radio custom-control-inline">

+ 3 - 3
src/client/js/components/Admin/App/AppSettingsPageContents.jsx

@@ -5,8 +5,8 @@ import PropTypes from 'prop-types';
 import AppSetting from './AppSetting';
 import SiteUrlSetting from './SiteUrlSetting';
 import MailSetting from './MailSetting';
-import AwsSetting from './AwsSetting';
 import PluginSetting from './PluginSetting';
+import FileUploadSetting from './FileUploadSetting';
 
 class AppSettingsPageContents extends React.Component {
 
@@ -38,8 +38,8 @@ class AppSettingsPageContents extends React.Component {
 
         <div className="row mt-5">
           <div className="col-lg-12">
-            <h2 className="admin-setting-header">{t('admin:app_setting.aws_settings')}</h2>
-            <AwsSetting />
+            <h2 className="admin-setting-header">{t('admin:app_setting.file_upload_settings')}</h2>
+            <FileUploadSetting />
           </div>
         </div>
 

+ 0 - 9
src/client/js/components/Admin/App/AwsSetting.jsx

@@ -38,15 +38,6 @@ class AwsSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <p className="card well">
-          {t('admin:app_setting.aws_access')}
-          <br />
-          <span className="text-danger">
-            <i className="ti-unlink"></i>
-            {t('admin:app_setting.change_setting')}
-          </span>
-        </p>
-
         <div className="row form-group">
           <label className="text-left text-md-right col-md-3 col-form-label">
             {t('admin:app_setting.region')}

+ 76 - 0
src/client/js/components/Admin/App/FileUploadSetting.jsx

@@ -0,0 +1,76 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
+import AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+
+import AwsSetting from './AwsSetting';
+import GcpSettings from './GcpSettings';
+
+function FileUploadSetting(props) {
+
+  const { t, adminAppContainer } = props;
+  const { fileUploadType } = adminAppContainer.state;
+  const fileUploadTypes = ['aws', 'gcp'];
+
+  return (
+    <React.Fragment>
+      <p className="card well my-3">
+        {t('admin:app_setting.file_upload')}
+        <br />
+        <br />
+        <span className="text-danger">
+          <i className="ti-unlink"></i>
+          {t('admin:app_setting.change_setting')}
+        </span>
+      </p>
+
+      <div className="row form-group mb-5">
+        <label className="text-left text-md-right col-md-3 col-form-label">
+          {t('admin:app_setting.file_upload_method')}
+        </label>
+
+        <div className="col-md-6 py-2">
+          {fileUploadTypes.map((type) => {
+              return (
+                <div key={type} className="custom-control custom-radio custom-control-inline">
+                  <input
+                    type="radio"
+                    className="custom-control-input"
+                    name="file-upload-type"
+                    id={`file-upload-type-radio-${type}`}
+                    checked={adminAppContainer.state.fileUploadType === type}
+                    onChange={(e) => {
+                    adminAppContainer.changeFileUploadType(type);
+                  }}
+                  />
+                  <label className="custom-control-label" htmlFor={`file-upload-type-radio-${type}`}>{t(`admin:app_setting.${type}_label`)}</label>
+                </div>
+              );
+            })}
+        </div>
+      </div>
+
+      {fileUploadType === 'aws' && <AwsSetting />}
+      {fileUploadType === 'gcp' && <GcpSettings />}
+
+    </React.Fragment>
+  );
+}
+
+
+/**
+ * Wrapper component for using unstated
+ */
+const FileUploadSettingWrapper = withUnstatedContainers(FileUploadSetting, [AppContainer, AdminAppContainer]);
+
+FileUploadSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+};
+
+export default withTranslation()(FileUploadSettingWrapper);

+ 149 - 0
src/client/js/components/Admin/App/GcpSettings.jsx

@@ -0,0 +1,149 @@
+
+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 AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+const logger = loggerFactory('growi:GcpSetting');
+
+class GcpSetting extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.submitHandler = this.submitHandler.bind(this);
+  }
+
+  async submitHandler() {
+    const { t, adminAppContainer } = this.props;
+
+    try {
+      await adminAppContainer.updateGcpSettingHandler();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.gcp_settings') }));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  render() {
+    const { t, adminAppContainer } = this.props;
+
+    return (
+      <React.Fragment>
+
+        <table className="table settings-table">
+          <colgroup>
+            <col className="item-name" />
+            <col className="from-db" />
+            <col className="from-env-vars" />
+          </colgroup>
+          <thead>
+            <tr><th></th><th>Database</th><th>Environment variables</th></tr>
+          </thead>
+          <tbody>
+            <tr>
+              <th>Api Key Json Path</th>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  name="gcsApiKeyJsonPath"
+                  defaultValue={adminAppContainer.state.gcsApiKeyJsonPath}
+                  onChange={e => adminAppContainer.changeGcsApiKeyJsonPath(e.target.value)}
+                />
+              </td>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  value={adminAppContainer.state.envGcsApiKeyJsonPath || ''}
+                  readOnly
+                  tabIndex="-1"
+                />
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'GCS_API_KEY_JSON_PATH' }) }} />
+                </p>
+              </td>
+            </tr>
+            <tr>
+              <th>{t('admin:app_setting.bucket_name')}</th>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  name="gcsBucket"
+                  defaultValue={adminAppContainer.state.gcsBucket}
+                  onChange={e => adminAppContainer.changeGcsBucket(e.target.value)}
+                />
+              </td>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  value={adminAppContainer.state.envGcsBucket || ''}
+                  readOnly
+                  tabIndex="-1"
+                />
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'GCS_BUCKET' }) }} />
+                </p>
+              </td>
+            </tr>
+            <tr>
+              <th>Name Space</th>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  name="gcsUploadNamespace"
+                  defaultValue={adminAppContainer.state.gcsUploadNamespace}
+                  onChange={e => adminAppContainer.changeGcsUploadNamespace(e.target.value)}
+                />
+              </td>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  value={adminAppContainer.state.envGcsUploadNamespace || ''}
+                  readOnly
+                  tabIndex="-1"
+                />
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'GCS_UPLOAD_NAMESPACE' }) }} />
+                </p>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
+      </React.Fragment>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const GcpSettingWrapper = withUnstatedContainers(GcpSetting, [AppContainer, AdminAppContainer]);
+
+GcpSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+};
+
+export default withTranslation()(GcpSettingWrapper);

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

@@ -62,7 +62,7 @@ function MailSetting(props) {
         <label className="text-left text-md-right col-md-3 col-form-label">
           {t('admin:app_setting.transmission_method')}
         </label>
-        <div className="col-md-6">
+        <div className="col-md-6 py-2">
           {transmissionMethods.map((method) => {
               return (
                 <div key={method} className="custom-control custom-radio custom-control-inline">
@@ -70,13 +70,13 @@ function MailSetting(props) {
                     type="radio"
                     className="custom-control-input"
                     name="transmission-method"
-                    id={`transmission-nethod-radio-${method}`}
+                    id={`transmission-method-radio-${method}`}
                     checked={adminAppContainer.state.transmissionMethod === method}
                     onChange={(e) => {
                     adminAppContainer.changeTransmissionMethod(method);
                   }}
                   />
-                  <label className="custom-control-label" htmlFor={`transmission-nethod-radio-${method}`}>{t(`admin:app_setting.${method}_label`)}</label>
+                  <label className="custom-control-label" htmlFor={`transmission-method-radio-${method}`}>{t(`admin:app_setting.${method}_label`)}</label>
                 </div>
               );
             })}

+ 51 - 3
src/client/js/services/AdminAppContainer.js

@@ -20,26 +20,36 @@ export default class AdminAppContainer extends Container {
       confidential: '',
       globalLang: '',
       fileUpload: '',
+
       siteUrl: '',
       envSiteUrl: '',
       isSetSiteUrl: true,
       isMailerSetup: false,
       fromAddress: '',
       transmissionMethod: '',
+
       smtpHost: '',
       smtpPort: '',
       smtpUser: '',
       smtpPassword: '',
       sesAccessKeyId: '',
       sesSecretAccessKey: '',
+
+      fileUploadType: 'aws',
+
+      gcsApiKeyJsonPath: '',
+      envGcsApiKeyJsonPath: '',
+      gcsBucket: '',
+      envGcsBucket: '',
+      gcsUploadNamespace: '',
+      envGcsUploadNamespace: '',
+
       region: '',
       customEndpoint: '',
       bucket: '',
       accessKeyId: '',
       secretAccessKey: '',
-      envGcsApiKeyJsonPath: '',
-      envGcsBucket: '',
-      envGcsUploadNamespace: '',
+
       isEnabledPlugins: true,
     };
 
@@ -180,6 +190,13 @@ export default class AdminAppContainer extends Container {
     this.setState({ sesSecretAccessKey });
   }
 
+  /**
+   * Change fileUploadType
+   */
+  changeFileUploadType(fileUploadType) {
+    this.setState({ fileUploadType });
+  }
+
   /**
    * Change region
    */
@@ -215,6 +232,27 @@ export default class AdminAppContainer extends Container {
     this.setState({ secretAccessKey });
   }
 
+  /**
+   * Change gcsApiKeyJsonPath
+   */
+  changeGcsApiKeyJsonPath(gcsApiKeyJsonPath) {
+    this.setState({ gcsApiKeyJsonPath });
+  }
+
+  /**
+   * Change gcsBucket
+   */
+  changeGcsBucket(gcsBucket) {
+    this.setState({ gcsBucket });
+  }
+
+  /**
+   * Change gcsUploadNamespace
+   */
+  changeGcsUploadNamespace(gcsUploadNamespace) {
+    this.setState({ gcsUploadNamespace });
+  }
+
   /**
    * Change secret key
    */
@@ -325,6 +363,16 @@ export default class AdminAppContainer extends Container {
     return awsSettingParams;
   }
 
+  /**
+   * Update GCP setting
+   * @memberOf AdminAppContainer
+   * @return {Array} Appearance
+   */
+  async updateGcpSettingHandler() {
+    // TODO GW-3660 cteate api
+    return;
+  }
+
   /**
    * Update plugin setting
    * @memberOf AdminAppContainer

+ 68 - 5
src/server/routes/apiv3/app-settings.js

@@ -105,10 +105,19 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
  *          secretAccessKey:
  *            type: string
  *            description: secret key for authentification of AWS
- *      FileUploadSettingParams:
- *        description: FileUploadSettingParams
+ *      GcpSettingParams:
+ *        description: GcpSettingParams
  *        type: object
  *        properties:
+ *          gcsApiKeyJsonPath:
+ *            type: string
+ *            description: apiKeyJsonPath of gcp
+ *          gcsBucket:
+ *            type: string
+ *            description: bucket name of gcs
+ *          gcsUploadNamespace:
+ *            type: string
+ *            description: name space of gcs
  *          envGcsApiKeyJsonPath:
  *            type: string
  *            description: Path of the JSON file that contains service account key to authenticate to GCP API
@@ -166,6 +175,11 @@ module.exports = (crowi) => {
       body('accessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
       body('secretAccessKey').trim(),
     ],
+    gcpSetting: [
+      body('gcsApiKeyJsonPath').trim(),
+      body('gcsBucket').trim(),
+      body('gcsUploadNamespace').trim(),
+    ],
     pluginSetting: [
       body('isEnabledPlugins').isBoolean(),
     ],
@@ -556,10 +570,9 @@ module.exports = (crowi) => {
     };
 
     try {
-      const { configManager } = crowi;
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams);
 
-      // update config without publishing S2sMessage
-      await configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams, true);
+      // TODO GW-3797 re-setup file uploader
 
       const awsSettingParams = {
         region: crowi.configManager.getConfig('crowi', 'aws:region'),
@@ -578,6 +591,56 @@ module.exports = (crowi) => {
 
   });
 
+  /**
+   * @swagger
+   *
+   *    /app-settings/gcp-setting:
+   *      put:
+   *        tags: [AppSettings]
+   *        operationId: updateAppSettingGcpSetting
+   *        summary: /app-settings/gcp-setting
+   *        description: Update gcp setting
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/GcpSettingParams'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update gcp setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/GcpSettingParams'
+   */
+  router.put('/gcp-setting', loginRequiredStrictly, adminRequired, csrf, validator.gcpSetting, apiV3FormValidator, async(req, res) => {
+    const requestGcpSettingParams = {
+      'gcs:apiKeyJsonPath': req.body.gcsApiKeyJsonPath,
+      'gcs:bucket': req.body.gcsBucket,
+      'gcs:uploadNamespace': req.body.gcsUploadNamespace,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestGcpSettingParams);
+
+      // TODO GW-3797 re-setup file uploader
+
+      const gcpSettingParams = {
+        gcsApiKeyJsonPath: crowi.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath'),
+        gcsBucket: crowi.configManager.getConfig('crowi', 'gcs:bucket'),
+        gcsUploadNamespace: crowi.configManager.getConfig('crowi', 'gcs:uploadNamespace'),
+      };
+      return res.apiv3({ gcpSettingParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating aws setting';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-awsSetting-failed'));
+    }
+
+  });
+
   /**
    * @swagger
    *