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

Merge branch 'master' into support/share-link-for-outside-for-merge

itizawa 5 лет назад
Родитель
Сommit
487110f416

+ 4 - 1
CHANGES.md

@@ -2,9 +2,12 @@
 
 ## v4.0.7-RC
 
-* Inprovement: Apply styles faster on booting client
+* Feature: Set request timeout for Elasticsearch with env var `ELASTICSEARCH_REQUEST_TIMEOUT`
+* Improvement: Apply styles faster on booting client
 * Fix: Styles are not applyed on installer
 * Fix: Remove last-resort `next()`
+* Fix: Enable/disable Notification settings couldn't change when either of the params is undefined
+* Fix: Text overflow
 
 ## v4.0.6
 

+ 24 - 0
resource/locales/en-US/translation.json

@@ -694,5 +694,29 @@
     "Sign in error": "Login error",
     "Registration successful": "Registration successful",
     "Setup": "Setup"
+  },
+  "message": {
+    "successfully_connected": "Successfully Connected!",
+    "fail_to_save_access_token": "Failed to save access_token. Please try again.",
+    "fail_to_fetch_access_token": "Failed to fetch access_token. Please do connect again.",
+    "successfully_disconnected": "Successfully Disconnected!",
+    "strategy_has_not_been_set_up": "{{strategy}} has not been set up",
+    "maximum_number_of_users": "Can not register more than the maximum number of users.",
+    "database_error": "Database Server Error occured",
+    "sign_in_failure": "Sign in failure.",
+    "aws_sttings_required": "AWS settings required to use this function. Please ask the administrator.",
+    "application_already_installed": "Application already installed.",
+    "email_address_could_not_be_used": "This email address could not be used. (Make sure the allowed email address)",
+    "user_id_is_not_available.":"This User ID is not available.",
+    "email_address_is_already_registered":"This email address is already registered.",
+    "can_not_register_maximum_number_of_users":"Can not register more than the maximum number of users.",
+    "failed_to_register":"Failed to register.",
+    "successfully_created":"The user {{username}} is successfully created.",
+    "can_not_activate_maximum_number_of_users":"Can not activate more than the maximum number of users.",
+    "failed_to_activate":"Failed to activate.",
+    "unable_to_use_this_user":"Unable to use this user.",
+    "complete_to_install1":"Complete to Install GROWI ! Please login as admin account.",
+    "complete_to_install2":"Complete to Install GROWI ! Please check each settings on this page first.",
+    "failed_to_create_admin_user":"Failed to create admin user. {{errMessage}}"
   }
 }

+ 24 - 0
resource/locales/ja/translation.json

@@ -686,5 +686,29 @@
     "Sign in error": "ログインエラー",
     "Registration successful": "登録完了",
     "Setup": "セットアップ"
+  },
+  "message": {
+    "successfully_connected": "接続に成功しました!",
+    "fail_to_save_access_token": "アクセストークンの保存に失敗しました、再度お試しください。",
+    "fail_to_fetch_access_token": "アクセストークンの取得に失敗しました、再度お試しください。",
+    "successfully_disconnected": "切断に成功しました!",
+    "strategy_has_not_been_set_up": "{{strategy}} はセットアップされていません。",
+    "maximum_number_of_users": "ユーザー数が上限を超えたためアクティベートできません。",
+    "database_error":"データベースサーバーに問題があります。",
+    "sign_in_failure": "ログインに失敗しました。",
+    "aws_sttings_required": "この機能にはAWS設定が必要です。管理者に訪ねて下さい。",
+    "application_already_installed": "アプリケーションのインストールが完了しました。",
+    "email_address_could_not_be_used":"このメールアドレスは使用できません。(許可されたメールアドレスを確認してください。)",
+    "user_id_is_not_available":"このユーザーIDは使用できません。",
+    "email_address_is_already_registered":"このメールアドレスは既に登録されています。",
+    "can_not_register_maximum_number_of_users":"ユーザー数が上限を超えたため登録できません。",
+    "failed_to_register":"登録に失敗しました。",
+    "successfully_created":"{{username}} が作成されました。",
+    "can_not_activate_maximum_number_of_users":"ユーザーが上限に達したためアクティベートできません。",
+    "failed_to_activate":"アクティベートに失敗しました。",
+    "unable_to_use_this_user":"利用できないユーザーIDです。",
+    "complete_to_install1":"GROWI のインストールが完了しました!管理者アカウントでログインしてください。",
+    "complete_to_install2":"GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。",
+    "failed_to_create_admin_user":"管理ユーザーの作成に失敗しました。{{errMessage}}"
   }
 }

+ 31 - 19
src/client/js/components/Navbar/PersonalDropdown.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
@@ -9,6 +9,15 @@ import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
 import NavigationContainer from '../../services/NavigationContainer';
 
+import {
+  isUserPreferenceExists,
+  isDarkMode as isDarkModeByUtil,
+  applyColorScheme,
+  removeUserPreference,
+  updateUserPreference,
+  updateUserPreferenceWithOsSettings,
+} from '../../util/color-scheme';
+
 import UserPicture from '../User/UserPicture';
 
 const PersonalDropdown = (props) => {
@@ -16,6 +25,9 @@ const PersonalDropdown = (props) => {
   const { t, appContainer, navigationContainer } = props;
   const user = appContainer.currentUser || {};
 
+  const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
+  const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
+
   const logoutHandler = () => {
     const { interceptorManager } = appContainer;
 
@@ -37,18 +49,25 @@ const PersonalDropdown = (props) => {
   };
 
   const followOsCheckboxModifiedHandler = (bool) => {
-    // reset user preference
     if (bool) {
-      appContainer.setColorSchemePreference(null);
+      removeUserPreference();
     }
-    // set preferDarkModeByMediaQuery as users preference
     else {
-      appContainer.setColorSchemePreference(appContainer.state.preferDarkModeByMediaQuery);
+      updateUserPreferenceWithOsSettings();
     }
+    applyColorScheme();
+
+    // update states
+    setOsSettings(bool);
+    setIsDarkMode(isDarkModeByUtil());
   };
 
   const userPreferenceSwitchModifiedHandler = (bool) => {
-    appContainer.setColorSchemePreference(bool);
+    updateUserPreference(bool);
+    applyColorScheme();
+
+    // update state
+    setIsDarkMode(isDarkModeByUtil());
   };
 
 
@@ -56,15 +75,8 @@ const PersonalDropdown = (props) => {
    * render
    */
   const {
-    preferDarkModeByMediaQuery, preferDarkModeByUser, preferDrawerModeByUser, preferDrawerModeOnEditByUser,
+    preferDrawerModeByUser, preferDrawerModeOnEditByUser,
   } = navigationContainer.state;
-  const isUserPreferenceExists = preferDarkModeByUser != null;
-  const isDarkMode = () => {
-    if (isUserPreferenceExists) {
-      return preferDarkModeByUser;
-    }
-    return preferDarkModeByMediaQuery;
-  };
 
   /* eslint-disable react/prop-types */
   const DrawerIcon = props => (
@@ -167,7 +179,7 @@ const PersonalDropdown = (props) => {
                   id="cbFollowOs"
                   className="custom-control-input"
                   type="checkbox"
-                  checked={!isUserPreferenceExists}
+                  checked={useOsSettings}
                   onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
                 />
                 <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
@@ -176,19 +188,19 @@ const PersonalDropdown = (props) => {
           </div>
           <div className="form-row justify-content-center">
             <div className="form-group col-auto mb-0 d-flex align-items-center">
-              <span className={isUserPreferenceExists ? '' : 'text-muted'}>Light</span>
+              <span className={useOsSettings ? '' : 'text-muted'}>Light</span>
               <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
                 <input
                   id="swUserPreference"
                   className="custom-control-input"
                   type="checkbox"
-                  checked={isDarkMode()}
-                  disabled={!isUserPreferenceExists}
+                  checked={isDarkMode}
+                  disabled={useOsSettings}
                   onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
                 />
                 <label className="custom-control-label" htmlFor="swUserPreference"></label>
               </div>
-              <span className={isUserPreferenceExists ? '' : 'text-muted'}>Dark</span>
+              <span className={useOsSettings ? '' : 'text-muted'}>Dark</span>
             </div>
           </div>
         </form>

+ 2 - 11
src/client/js/services/AppContainer.js

@@ -11,7 +11,6 @@ import GrowiRenderer from '../util/GrowiRenderer';
 import {
   mediaQueryListForDarkMode,
   applyColorScheme,
-  savePreferenceByUser,
 } from '../util/color-scheme';
 import Apiv1ErrorHandler from '../util/apiv1ErrorHandler';
 
@@ -37,6 +36,8 @@ export default class AppContainer extends Container {
     super();
 
     this.state = {
+      preferDarkModeByMediaQuery: false,
+
       // stetes for contents
       recentlyUpdatedPages: [],
     };
@@ -312,16 +313,6 @@ export default class AppContainer extends Container {
     targetComponent.launchDrawioModal(beginLineNumber, endLineNumber);
   }
 
-  /**
-   * Set color scheme preference by user
-   * @param {boolean} isDarkMode
-   */
-  async setColorSchemePreference(isDarkMode) {
-    this.setState({ preferDarkModeByUser: isDarkMode });
-    savePreferenceByUser(isDarkMode);
-    applyColorScheme();
-  }
-
   async apiGet(path, params) {
     return this.apiRequest('get', path, { params });
   }

+ 42 - 14
src/client/js/util/color-scheme.js

@@ -1,14 +1,27 @@
 const mediaQueryListForDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
 
+function isUserPreferenceExists() {
+  return localStorage.preferDarkModeByUser != null;
+}
+
+function isPreferedDarkModeByUser() {
+  return localStorage.preferDarkModeByUser === 'true';
+}
+
+function isDarkMode() {
+  if (isUserPreferenceExists()) {
+    return isPreferedDarkModeByUser();
+  }
+  return mediaQueryListForDarkMode.matches;
+}
+
 /**
  * Apply color scheme as 'dark' attribute of <html></html>
  */
 function applyColorScheme() {
-  const { preferDarkModeByUser } = localStorage;
-
   let isDarkMode = mediaQueryListForDarkMode.matches;
-  if (preferDarkModeByUser != null) {
-    isDarkMode = preferDarkModeByUser === 'true';
+  if (isUserPreferenceExists()) {
+    isDarkMode = isPreferedDarkModeByUser();
   }
 
   // switch to dark mode
@@ -24,22 +37,37 @@ function applyColorScheme() {
 }
 
 /**
- * Set color scheme preference by user
- * @param {boolean} isDarkMode
+ * Remove color scheme preference
  */
-function savePreferenceByUser(isDarkMode) {
-  // store settings to localStorage
-  const { localStorage } = window;
-  if (isDarkMode == null) {
+function removeUserPreference() {
+  if (isUserPreferenceExists()) {
     delete localStorage.removeItem('preferDarkModeByUser');
   }
-  else {
-    localStorage.preferDarkModeByUser = isDarkMode;
-  }
+}
+
+/**
+ * Set color scheme preference
+ * @param {boolean} isDarkMode
+ */
+function updateUserPreference(isDarkMode) {
+  // store settings to localStorage
+  localStorage.preferDarkModeByUser = isDarkMode;
+}
+
+/**
+ * Set color scheme preference with OS settings
+ */
+function updateUserPreferenceWithOsSettings() {
+  localStorage.preferDarkModeByUser = mediaQueryListForDarkMode.matches;
 }
 
 export {
   mediaQueryListForDarkMode,
+  isUserPreferenceExists,
+  isPreferedDarkModeByUser,
+  isDarkMode,
   applyColorScheme,
-  savePreferenceByUser,
+  removeUserPreference,
+  updateUserPreference,
+  updateUserPreferenceWithOsSettings,
 };

+ 2 - 0
src/client/styles/scss/_wiki.scss

@@ -8,6 +8,8 @@ div.body {
 }
 
 .wiki {
+  @extend .text-break;
+
   font-size: 15px;
 
   // override line-height except hljs and child of it.

+ 0 - 24
src/server/models/user.js

@@ -552,30 +552,6 @@ module.exports = function(crowi) {
     });
   };
 
-  userSchema.statics.removeCompletelyById = function(id, callback) {
-    const User = this;
-    User.findById(id, (err, userData) => {
-      if (!userData) {
-        return callback(err, null);
-      }
-
-      debug('Removing user:', userData);
-      // 物理削除可能なのは、承認待ちユーザー、招待中ユーザーのみ
-      // 利用を一度開始したユーザーは論理削除のみ可能
-      if (userData.status !== STATUS_REGISTERED && userData.status !== STATUS_INVITED) {
-        return callback(new Error('Cannot remove completely the user whoes status is not INVITED'), null);
-      }
-
-      userData.remove((err) => {
-        if (err) {
-          return callback(err, null);
-        }
-
-        return callback(null, 1);
-      });
-    });
-  };
-
   userSchema.statics.resetPasswordByRandomString = async function(id) {
     const user = await this.findById(id);
 

+ 5 - 61
src/server/routes/admin.js

@@ -4,8 +4,6 @@ module.exports = function(crowi, app) {
   const logger = require('@alias/logger')('growi:routes:admin');
 
   const models = crowi.models;
-  const User = models.User;
-  const ExternalAccount = models.ExternalAccount;
   const UserGroup = models.UserGroup;
   const UserGroupRelation = models.UserGroupRelation;
   const GlobalNotificationSetting = models.GlobalNotificationSetting;
@@ -170,6 +168,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/notification/slackAuth'     , admin.notification.slackauth);
   actions.notification.slackAuth = function(req, res) {
     const code = req.query.code;
+    const { t } = req;
 
     if (!code || !slackNotificationService.hasSlackConfig()) {
       return res.redirect('/admin/notification');
@@ -182,17 +181,17 @@ module.exports = function(crowi, app) {
 
         try {
           await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': data.access_token });
-          req.flash('successMessage', ['Successfully Connected!']);
+          req.flash('successMessage', [t('message.successfully_connected')]);
         }
         catch (err) {
-          req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
+          req.flash('errorMessage', [t('message.fail_to_save_access_token')]);
         }
 
         return res.redirect('/admin/notification');
       })
       .catch((err) => {
         debug('oauth response ERROR', err);
-        req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
+        req.flash('errorMessage', [t('message.fail_to_fetch_access_token')]);
         return res.redirect('/admin/notification');
       });
   };
@@ -200,7 +199,7 @@ module.exports = function(crowi, app) {
   // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
   actions.notification.disconnectFromSlack = async function(req, res) {
     await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': '' });
-    req.flash('successMessage', ['Successfully Disconnected!']);
+    req.flash('successMessage', [req.t('successfully_disconnected')]);
 
     return res.redirect('/admin/notification');
   };
@@ -232,66 +231,11 @@ module.exports = function(crowi, app) {
     return res.render('admin/users');
   };
 
-  // これやったときの relation の挙動未確認
-  actions.user.removeCompletely = function(req, res) {
-    // ユーザーの物理削除
-    const id = req.params.id;
-
-    User.removeCompletelyById(id, (err, removed) => {
-      if (err) {
-        debug('Error while removing user.', err, id);
-        req.flash('errorMessage', '完全な削除に失敗しました。');
-      }
-      else {
-        req.flash('successMessage', '削除しました');
-      }
-      return res.redirect('/admin/users');
-    });
-  };
-
-  // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
-  actions.user.resetPassword = async function(req, res) {
-    const id = req.body.user_id;
-    const User = crowi.model('User');
-
-    try {
-      const newPassword = await User.resetPasswordByRandomString(id);
-
-      const user = await User.findById(id);
-
-      const result = { user: user.toObject(), newPassword };
-      return res.json(ApiResponse.success(result));
-    }
-    catch (err) {
-      debug('Error on reseting password', err);
-      return res.json(ApiResponse.error(err));
-    }
-  };
-
   actions.externalAccount = {};
   actions.externalAccount.index = function(req, res) {
     return res.render('admin/external-accounts');
   };
 
-  actions.externalAccount.remove = async function(req, res) {
-    const id = req.params.id;
-
-    let account = null;
-
-    try {
-      account = await ExternalAccount.findByIdAndRemove(id);
-      if (account == null) {
-        throw new Error('削除に失敗しました。');
-      }
-    }
-    catch (err) {
-      req.flash('errorMessage', err.message);
-      return res.redirect('/admin/users/external-accounts');
-    }
-
-    req.flash('successMessage', `外部アカウント '${account.providerType}/${account.accountId}' を削除しました`);
-    return res.redirect('/admin/users/external-accounts');
-  };
 
   actions.userGroup = {};
   actions.userGroup.index = function(req, res) {

+ 2 - 2
src/server/routes/apiv3/notification-setting.js

@@ -34,8 +34,8 @@ const validator = {
     }),
   ],
   notifyForPageGrant: [
-    body('isNotificationForOwnerPageEnabled').isBoolean(),
-    body('isNotificationForGroupPageEnabled').isBoolean(),
+    body('isNotificationForOwnerPageEnabled').if(value => value != null).isBoolean(),
+    body('isNotificationForGroupPageEnabled').if(value => value != null).isBoolean(),
   ],
 };
 

+ 0 - 4
src/server/routes/index.js

@@ -93,12 +93,8 @@ module.exports = function(crowi, app) {
   app.get('/admin/global-notification/:id'   , loginRequiredStrictly , adminRequired , admin.globalNotification.detail);
 
   app.get('/admin/users'                , loginRequiredStrictly , adminRequired , admin.user.index);
-  app.post('/admin/user/:id/removeCompletely' , loginRequiredStrictly , adminRequired , csrf, admin.user.removeCompletely);
-  // new route patterns from here:
-  app.post('/_api/admin/users.resetPassword'  , loginRequiredStrictly , adminRequired , csrf, admin.user.resetPassword);
 
   app.get('/admin/users/external-accounts'               , loginRequiredStrictly , adminRequired , admin.externalAccount.index);
-  app.post('/admin/users/external-accounts/:id/remove'   , loginRequiredStrictly , adminRequired , admin.externalAccount.remove);
 
   // user-groups admin
   app.get('/admin/user-groups'             , loginRequiredStrictly, adminRequired, admin.userGroup.index);

+ 3 - 3
src/server/routes/installer.js

@@ -79,7 +79,7 @@ module.exports = function(crowi, app) {
       await adminUser.asyncMakeAdmin();
     }
     catch (err) {
-      req.form.errors.push(`管理ユーザーの作成に失敗しました。${err.message}`);
+      req.form.errors.push(req.t('message.failed_to_create_admin_user', { errMessage: err.message }));
       return res.render('installer');
     }
     // create initial pages
@@ -92,12 +92,12 @@ module.exports = function(crowi, app) {
     // login with passport
     req.logIn(adminUser, (err) => {
       if (err) {
-        req.flash('successMessage', 'GROWI のインストールが完了しました!<br>管理者アカウントでログインしてください。');
+        req.flash('successMessage', req.t('message.complete_to_install1'));
         req.session.redirectTo = '/admin/app';
         return res.redirect('/login');
       }
 
-      req.flash('successMessage', 'GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。');
+      req.flash('successMessage', req.t('message.complete_to_install2'));
       return res.redirect('/admin/app');
     });
   };

+ 12 - 12
src/server/routes/login-passport.js

@@ -34,7 +34,7 @@ module.exports = function(crowi, app) {
    * @param {*} res
    */
   const loginFailureHandler = (req, res, message) => {
-    req.flash('errorMessage', message || 'Sign in failure.');
+    req.flash('errorMessage', message || req.t('message.sign_in_failure'));
     return res.redirect('/login');
   };
 
@@ -44,7 +44,7 @@ module.exports = function(crowi, app) {
    * @param {*} res
    */
   const loginFailure = (req, res) => {
-    return loginFailureHandler(req, res, 'Sign in failure.');
+    return loginFailureHandler(req, res, req.t('message.sign_in_failure'));
   };
 
   /**
@@ -142,7 +142,7 @@ module.exports = function(crowi, app) {
       debug('LdapStrategy has not been set up');
       return res.json(ApiResponse.success({
         status: 'warning',
-        message: 'LdapStrategy has not been set up',
+        message: req.t('message.strategy_has_not_been_set_up', { strategy: 'LdapStrategy' }),
       }));
     }
 
@@ -196,7 +196,7 @@ module.exports = function(crowi, app) {
   const loginWithLocal = (req, res, next) => {
     if (!passportService.isLocalStrategySetup) {
       debug('LocalStrategy has not been set up');
-      req.flash('warningMessage', 'LocalStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'LocalStrategy' }));
       return next();
     }
 
@@ -212,7 +212,7 @@ module.exports = function(crowi, app) {
 
       if (err) { // DB Error
         logger.error('Database Server Error: ', err);
-        req.flash('warningMessage', 'Database Server Error occured.');
+        req.flash('warningMessage', req.t('message.database_error'));
         return next(); // pass and the flash message is displayed when all of authentications are failed.
       }
       if (!user) { return next() }
@@ -227,7 +227,7 @@ module.exports = function(crowi, app) {
   const loginWithGoogle = function(req, res, next) {
     if (!passportService.isGoogleStrategySetup) {
       debug('GoogleStrategy has not been set up');
-      req.flash('warningMessage', 'GoogleStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'GoogleStrategy' }));
       return next();
     }
 
@@ -296,7 +296,7 @@ module.exports = function(crowi, app) {
   const loginWithGitHub = function(req, res, next) {
     if (!passportService.isGitHubStrategySetup) {
       debug('GitHubStrategy has not been set up');
-      req.flash('warningMessage', 'GitHubStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'GitHubStrategy' }));
       return next();
     }
 
@@ -338,7 +338,7 @@ module.exports = function(crowi, app) {
   const loginWithTwitter = function(req, res, next) {
     if (!passportService.isTwitterStrategySetup) {
       debug('TwitterStrategy has not been set up');
-      req.flash('warningMessage', 'TwitterStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'TwitterStrategy' }));
       return next();
     }
 
@@ -380,7 +380,7 @@ module.exports = function(crowi, app) {
   const loginWithOidc = function(req, res, next) {
     if (!passportService.isOidcStrategySetup) {
       debug('OidcStrategy has not been set up');
-      req.flash('warningMessage', 'OidcStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'OidcStrategy' }));
       return next();
     }
 
@@ -428,7 +428,7 @@ module.exports = function(crowi, app) {
   const loginWithSaml = function(req, res, next) {
     if (!passportService.isSamlStrategySetup) {
       debug('SamlStrategy has not been set up');
-      req.flash('warningMessage', 'SamlStrategy has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'SamlStrategy' }));
       return next();
     }
 
@@ -496,7 +496,7 @@ module.exports = function(crowi, app) {
   const loginWithBasic = async(req, res, next) => {
     if (!passportService.isBasicStrategySetup) {
       debug('BasicStrategy has not been set up');
-      req.flash('warningMessage', 'Basic has not been set up');
+      req.flash('warningMessage', req.t('message.strategy_has_not_been_set_up', { strategy: 'Basic' }));
       return next();
     }
 
@@ -587,7 +587,7 @@ module.exports = function(crowi, app) {
         return;
       }
       else if (err.name === 'UserUpperLimitException') {
-        req.flash('warningMessage', 'Can not register more than the maximum number of users.');
+        req.flash('warningMessage', req.t('message.maximum_number_of_users'));
         return;
       }
       /* eslint-enable no-else-return */

+ 9 - 9
src/server/routes/login.js

@@ -102,16 +102,16 @@ module.exports = function(crowi, app) {
         let isError = false;
         if (!User.isEmailValid(email)) {
           isError = true;
-          req.flash('registerWarningMessage', 'This email address could not be used. (Make sure the allowed email address)');
+          req.flash('registerWarningMessage', req.t('message.email_address_could_not_be_used'));
         }
         if (!isRegisterable) {
           if (!errOn.username) {
             isError = true;
-            req.flash('registerWarningMessage', 'This User ID is not available.');
+            req.flash('registerWarningMessage', req.t('message.user_id_is_not_available'));
           }
           if (!errOn.email) {
             isError = true;
-            req.flash('registerWarningMessage', 'This email address is already registered.');
+            req.flash('registerWarningMessage', req.t('message.email_address_is_already_registered'));
           }
         }
         if (isError) {
@@ -122,10 +122,10 @@ module.exports = function(crowi, app) {
         User.createUserByEmailAndPassword(name, username, email, password, undefined, async(err, userData) => {
           if (err) {
             if (err.name === 'UserUpperLimitException') {
-              req.flash('registerWarningMessage', 'Can not register more than the maximum number of users.');
+              req.flash('registerWarningMessage', req.t('message.can_not_register_maximum_number_of_users'));
             }
             else {
-              req.flash('registerWarningMessage', 'Failed to register.');
+              req.flash('registerWarningMessage', req.t('message.failed_to_register'));
             }
             return res.redirect('/register');
           }
@@ -138,7 +138,7 @@ module.exports = function(crowi, app) {
           // add a flash message to inform the user that processing was successful -- 2017.09.23 Yuki Takei
           // cz. loginSuccess method doesn't work on it's own when using passport
           //      because `req.login()` prepared by passport is not called.
-          req.flash('successMessage', `The user '${userData.username}' is successfully created.`);
+          req.flash('successMessage', req.t('message.successfully_created',{ username: userData.username }));
 
           return loginSuccess(req, res, userData);
         });
@@ -192,7 +192,7 @@ module.exports = function(crowi, app) {
       // check user upper limit
       const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
       if (isUserCountExceedsUpperLimit) {
-        req.flash('warningMessage', 'ユーザーが上限に達したためアクティベートできません。');
+        req.flash('warningMessage', req.t('message.can_not_activate_maximum_number_of_users'));
         return res.redirect('/invited');
       }
 
@@ -203,12 +203,12 @@ module.exports = function(crowi, app) {
           return res.redirect('/');
         }
         catch (err) {
-          req.flash('warningMessage', 'アクティベートに失敗しました。');
+          req.flash('warningMessage', req.t('message.failed_to_activate'));
           return res.render('invited');
         }
       }
       else {
-        req.flash('warningMessage', '利用できないユーザーIDです。');
+        req.flash('warningMessage', req.t('message.unable_to_use_this_user'));
         debug('username', username);
         return res.render('invited');
       }

+ 6 - 0
src/server/service/config-loader.js

@@ -143,6 +143,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.STRING,
     default: null,
   },
+  ELASTICSEARCH_REQUEST_TIMEOUT: {
+    ns:      'crowi',
+    key:     'app:elasticsearchRequestTimeout',
+    type:    TYPES.NUMBER,
+    default: 8000, // msec
+  },
   SEARCHBOX_SSL_URL: {
     ns:      'crowi',
     key:     'app:searchboxSslUrl',

+ 1 - 1
src/server/service/search-delegator/elasticsearch.js

@@ -59,7 +59,7 @@ class ElasticsearchDelegator {
     this.client = new elasticsearch.Client({
       host,
       httpAuth,
-      requestTimeout: 5000,
+      requestTimeout: this.configManager.getConfig('crowi', 'app:elasticsearchRequestTimeout'),
       // log: 'debug',
     });
     this.indexName = indexName;

+ 2 - 2
src/server/util/middlewares.js

@@ -163,7 +163,7 @@ module.exports = (crowi) => {
     const isInstalled = await appService.isDBInitialized();
 
     if (isInstalled) {
-      req.flash('errorMessage', 'Application already installed.');
+      req.flash('errorMessage', req.t('message.application_already_installed'));
       return res.redirect('admin'); // admin以外はadminRequiredで'/'にリダイレクトされる
     }
 
@@ -186,7 +186,7 @@ module.exports = (crowi) => {
           && configManager.getConfig('crowi', 'aws:bucket') !== ''
           && configManager.getConfig('crowi', 'aws:accessKeyId') !== ''
           && configManager.getConfig('crowi', 'aws:secretAccessKey') !== '') {
-        req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
+        req.flash('globalError', req.t('message.aws_sttings_required'));
         return res.redirect('/');
       }
 

+ 2 - 2
src/server/views/installer.html

@@ -56,10 +56,10 @@
             <div class="logo">{% include 'widget/logo.html' %}</div>
             <h1 class="my-3">GROWI</h1>
 
-            <div class="login-form-errors">
+            <div class="login-form-errors px-3">
               {% if req.form.errors.length > 0 %}
               <div class="alert alert-danger">
-                <ul>
+                <ul class="mb-0">
                 {% for error in req.form.errors %}
                   <li>{{ error }}</li>
                 {% endfor %}