Explorar o código

Merge branch 'master' into imprv/refacter-recursively

itizawa %!s(int64=5) %!d(string=hai) anos
pai
achega
ea48d9b65d

+ 10 - 2
CHANGES.md

@@ -1,6 +1,11 @@
 # CHANGES
 
-## v4.2.6-RC
+
+## v.4.2.7
+
+* 
+
+## v4.2.6
 
 * Fix: Failed to save temporaryUrlCached with using gcs
     * Introduced by v4.2.3
@@ -10,14 +15,17 @@
 * Improvement: Layout of Edit Link Modal
 * Improvement: Focus to the first input when modal is opened
 * Improvement: Preview layout in edit mode
+* Improvement: Install process under redundant environment
+* Improvement: Add contributors
 * Fix: Upgrading to v4.x failed when the user uses Kibela Layout
     * Introduced by v4.2.0
+* Fix: diagrams.net (draw.io) errors
 * Fix: Navbar is not rendered on old iOS
 * Support: Expose metrics with Promster
 * Support: Upgrade libs
     * axios
 
-## v4.2.5-RC
+## v4.2.5
 
 * Improvement: Invoke garbage collection when reindex all pages by elasticsearch
 * Fix: MathJax rendering does not work

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "4.2.6-RC",
+  "version": "4.2.7-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 17 - 1
src/client/js/components/InstallerForm.jsx

@@ -13,9 +13,12 @@ class InstallerForm extends React.Component {
 
     this.state = {
       isValidUserName: true,
+      isSubmitButtonDisabled: false,
       selectedLang: {},
     };
     // this.checkUserName = this.checkUserName.bind(this);
+
+    this.clickHandler = this.clickHandler.bind(this);
   }
 
   componentWillMount() {
@@ -39,6 +42,13 @@ class InstallerForm extends React.Component {
     this.setState({ selectedLang: meta });
   }
 
+  clickHandler() {
+    this.setState({ isSubmitButtonDisabled: true });
+    setTimeout(() => {
+      this.setState({ isSubmitButtonDisabled: false });
+    }, 3000);
+  }
+
   render() {
     const hasErrorClass = this.state.isValidUserName ? '' : ' has-error';
     const unavailableUserId = this.state.isValidUserName
@@ -149,7 +159,13 @@ class InstallerForm extends React.Component {
             <input type="hidden" name="_csrf" value={this.props.csrf} />
 
             <div className="input-group mt-4 mb-3 d-flex justify-content-center">
-              <button type="submit" className="btn-fill btn btn-register" id="register">
+              <button
+                type="submit"
+                className="btn-fill btn btn-register"
+                id="register"
+                disabled={this.state.isSubmitButtonDisabled}
+                onClick={this.clickHandler}
+              >
                 <div className="eff"></div>
                 <span className="btn-label"><i className="icon-user-follow" /></span>
                 <span className="btn-label-text">{ this.props.t('Create') }</span>

+ 10 - 11
src/client/js/components/PageEditor/MarkdownDrawioUtil.js

@@ -10,7 +10,7 @@ class MarkdownDrawioUtil {
 
   /**
    * return the postion of the BOD(beginning of drawio)
-   * (If the cursor is not in a drawio block, return its position)
+   * (If the BOD is not found after the cursor or the EOD is found before the BOD, return null)
    */
   getBod(editor) {
     const curPos = editor.getCursor();
@@ -36,7 +36,7 @@ class MarkdownDrawioUtil {
     }
 
     if (!isFound) {
-      return { line: curPos.line, ch: curPos.ch };
+      return null;
     }
 
     const bodLine = Math.max(firstLine, line);
@@ -45,7 +45,7 @@ class MarkdownDrawioUtil {
 
   /**
    * return the postion of the EOD(end of drawio)
-   * (If the cursor is not in a drawio block, return its position)
+   * (If the EOD is not found after the cursor or the BOD is found before the EOD, return null)
    */
   getEod(editor) {
     const curPos = editor.getCursor();
@@ -71,7 +71,7 @@ class MarkdownDrawioUtil {
     }
 
     if (!isFound) {
-      return { line: curPos.line, ch: curPos.ch };
+      return null;
     }
 
     const eodLine = Math.min(line, lastLine);
@@ -85,17 +85,17 @@ class MarkdownDrawioUtil {
   isInDrawioBlock(editor) {
     const bod = this.getBod(editor);
     const eod = this.getEod(editor);
-
-    return (JSON.stringify(bod) !== JSON.stringify(eod));
+    if (bod === null || eod === null) {
+      return false;
+    }
+    return JSON.stringify(bod) !== JSON.stringify(eod);
   }
 
   /**
    * return drawioData instance where the cursor is
-   * (If the cursor is not in a drawio block, return current line)
+   * (If the cursor is not in a drawio block, return null)
    */
   getMarkdownDrawioMxfile(editor) {
-    const curPos = editor.getCursor();
-
     if (this.isInDrawioBlock(editor)) {
       const bod = this.getBod(editor);
       const eod = this.getEod(editor);
@@ -108,8 +108,7 @@ class MarkdownDrawioUtil {
 
       return editor.getDoc().getRange(bod, eod);
     }
-
-    return editor.getDoc().getLine(curPos.line);
+    return null;
   }
 
   replaceFocusedDrawioWithEditor(editor, drawioData) {

+ 10 - 1
src/client/js/components/StaffCredit/Contributor.js

@@ -10,6 +10,7 @@ const contributors = [
           { position: 'Soncho 1st', name: 'mizozobu' },
           { position: 'Soncho 2nd', name: 'yusuketk' },
           { position: 'Paladin', name: 'itizawa' },
+          { position: 'Valkyrie', name: 'kaoritokashiki' },
         ],
       },
       {
@@ -35,7 +36,12 @@ const contributors = [
           { name: 'ryuichi-e' },
           { name: 'N1koge' },
           { name: 'Ertai87' },
-          { name: 'kaoritokashiki' },
+          { name: 'zahmis' },
+          { name: 'takeru0001' },
+          { name: 'Shu Katabe' },
+          { name: 'oshikishintaro' },
+          { name: 'makotoshiraishi' },
+          { name: 'yamagai' },
         ],
       },
     ],
@@ -53,6 +59,7 @@ const contributors = [
           { name: 'hitochan777' },
           { name: 'ttaka66' },
           { name: 'watagashi' },
+          { name: 'paichi81' },
           { name: 'nt-7' },
           { name: 'hideo54' },
           { name: 'wadahiro' },
@@ -79,6 +86,7 @@ const contributors = [
           { name: 'aximov' },
           { name: 'tats-u' },
           { name: 'yamatomo717' },
+          { name: 'tohutohu' },
         ],
       },
     ],
@@ -94,6 +102,7 @@ const contributors = [
           { name: 'Kanta Nishitani' },
           { position: 'The University of Tokyo', name: 'Takashi Yoneuchi' },
           { position: 'DeCurret', name: 'Yusuke Tanomogi' },
+          { position: 'Flatt Security', name: 'stypr' },
         ],
       },
     ],

+ 0 - 5
src/server/crowi/index.js

@@ -469,11 +469,6 @@ Crowi.prototype.setupRoutesAtLast = function() {
   require('../routes')(this, this.express);
 };
 
-Crowi.prototype.setupAfterInstall = function() {
-  this.pluginService.autoDetectAndLoadPlugins();
-  this.setupRoutesAtLast();
-};
-
 /**
  * require API for plugins
  *

+ 12 - 4
src/server/middlewares/application-installed.js

@@ -2,12 +2,20 @@ module.exports = (crowi) => {
   const { appService } = crowi;
 
   return async(req, res, next) => {
-    const isInstalled = await appService.isDBInitialized();
+    const isDBInitialized = await appService.isDBInitialized();
 
-    if (!isInstalled) {
-      return res.redirect('/installer');
+    // when already installed
+    if (isDBInitialized) {
+      return next();
     }
 
-    return next();
+    // when other server have initialized DB
+    const isDBInitializedAfterForceReload = await appService.isDBInitialized(true);
+    if (isDBInitializedAfterForceReload) {
+      await appService.setupAfterInstall();
+      return res.safeRedirect(req.originalUrl);
+    }
+
+    return res.redirect('/installer');
   };
 };

+ 2 - 2
src/server/middlewares/application-not-installed.js

@@ -2,9 +2,9 @@ module.exports = (crowi) => {
   const { appService } = crowi;
 
   return async(req, res, next) => {
-    const isInstalled = await appService.isDBInitialized();
+    const isDBInitialized = await appService.isDBInitialized(true);
 
-    if (isInstalled) {
+    if (isDBInitialized) {
       req.flash('errorMessage', req.t('message.application_already_installed'));
       return res.redirect('admin');
     }

+ 34 - 33
src/server/routes/index.js

@@ -33,37 +33,33 @@ module.exports = function(crowi, app) {
 
   /* eslint-disable max-len, comma-spacing, no-multi-spaces */
 
-  app.get('/'                        , applicationInstalled, loginRequired , autoReconnectToSearch, page.showTopPage);
-
   // API v3
   app.use('/api-docs', require('./apiv3/docs')(crowi));
   app.use('/_api/v3', require('./apiv3')(crowi));
 
+  app.get('/'                         , applicationInstalled, loginRequired , autoReconnectToSearch, page.showTopPage);
+
+  app.get('/login/error/:reason'      , applicationInstalled, login.error);
+  app.get('/login'                    , applicationInstalled, login.preLogin, login.login);
+  app.get('/login/invited'            , applicationInstalled, login.invited);
+  app.post('/login/activateInvited'   , applicationInstalled, form.invited                         , csrf, login.invited);
+  app.post('/login'                   , applicationInstalled, form.login                           , csrf, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
+
+  app.post('/register'                , applicationInstalled, form.register                        , csrf, login.register);
+  app.get('/register'                 , applicationInstalled, login.preLogin, login.register);
+  app.get('/logout'                   , applicationInstalled, logout.logout);
+
+  app.get('/admin'                    , applicationInstalled, loginRequiredStrictly , adminRequired , admin.index);
+  app.get('/admin/app'                , applicationInstalled, loginRequiredStrictly , adminRequired , admin.app.index);
+
   // installer
   if (!isInstalled) {
     const installer = require('./installer')(crowi);
-    app.get('/installer'               , applicationNotInstalled , installer.index);
-    app.post('/installer'              , applicationNotInstalled , form.register , csrf, installer.install);
+    app.get('/installer'              , applicationNotInstalled , installer.index);
+    app.post('/installer'             , applicationNotInstalled , form.register , csrf, installer.install);
     return;
   }
 
-  app.get('/login/error/:reason'     , login.error);
-  app.get('/login'                   , applicationInstalled     , login.preLogin, login.login);
-  app.get('/login/invited'           , login.invited);
-  app.post('/login/activateInvited'  , form.invited                         , csrf, login.invited);
-  app.post('/login'                  , form.login                           , csrf, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
-  app.post('/_api/login/testLdap'    , loginRequiredStrictly , form.login , loginPassport.testLdapCredentials);
-
-  app.post('/register'               , form.register                        , csrf, login.register);
-  app.get('/register'                , applicationInstalled     , login.preLogin, login.register);
-  app.get('/logout'                  , logout.logout);
-
-  app.get('/admin'                          , loginRequiredStrictly , adminRequired , admin.index);
-  app.get('/admin/app'                      , loginRequiredStrictly , adminRequired , admin.app.index);
-
-  // security admin
-  app.get('/admin/security'                     , loginRequiredStrictly , adminRequired , admin.security.index);
-
   // OAuth
   app.get('/passport/google'                      , loginPassport.loginWithGoogle, loginPassport.loginFailure);
   app.get('/passport/github'                      , loginPassport.loginWithGitHub, loginPassport.loginFailure);
@@ -77,29 +73,34 @@ module.exports = function(crowi, app) {
   app.get('/passport/oidc/callback'               , loginPassport.loginPassportOidcCallback     , loginPassport.loginFailure);
   app.post('/passport/saml/callback'              , loginPassport.loginPassportSamlCallback     , loginPassport.loginFailure);
 
+  app.post('/_api/login/testLdap'    , loginRequiredStrictly , form.login , loginPassport.testLdapCredentials);
+
+  // security admin
+  app.get('/admin/security'          , loginRequiredStrictly , adminRequired , admin.security.index);
+
   // markdown admin
-  app.get('/admin/markdown'                   , loginRequiredStrictly , adminRequired , admin.markdown.index);
+  app.get('/admin/markdown'          , loginRequiredStrictly , adminRequired , admin.markdown.index);
 
   // customize admin
-  app.get('/admin/customize'                , loginRequiredStrictly , adminRequired , admin.customize.index);
+  app.get('/admin/customize'         , loginRequiredStrictly , adminRequired , admin.customize.index);
 
   // search admin
-  app.get('/admin/search'              , loginRequiredStrictly , adminRequired , admin.search.index);
+  app.get('/admin/search'            , loginRequiredStrictly , adminRequired , admin.search.index);
 
   // notification admin
-  app.get('/admin/notification'              , loginRequiredStrictly , adminRequired , admin.notification.index);
-  app.get('/admin/notification/slackAuth'    , loginRequiredStrictly , adminRequired , admin.notification.slackAuth);
-  app.get('/admin/notification/slackSetting/disconnect', loginRequiredStrictly , adminRequired , admin.notification.disconnectFromSlack);
-  app.get('/admin/global-notification/new'   , loginRequiredStrictly , adminRequired , admin.globalNotification.detail);
-  app.get('/admin/global-notification/:id'   , loginRequiredStrictly , adminRequired , admin.globalNotification.detail);
+  app.get('/admin/notification'                         , loginRequiredStrictly , adminRequired , admin.notification.index);
+  app.get('/admin/notification/slackAuth'               , loginRequiredStrictly , adminRequired , admin.notification.slackAuth);
+  app.get('/admin/notification/slackSetting/disconnect' , loginRequiredStrictly , adminRequired , admin.notification.disconnectFromSlack);
+  app.get('/admin/global-notification/new'              , loginRequiredStrictly , adminRequired , admin.globalNotification.detail);
+  app.get('/admin/global-notification/:id'              , loginRequiredStrictly , adminRequired , admin.globalNotification.detail);
 
-  app.get('/admin/users'                , loginRequiredStrictly , adminRequired , admin.user.index);
+  app.get('/admin/users'                                , loginRequiredStrictly , adminRequired , admin.user.index);
 
-  app.get('/admin/users/external-accounts'               , loginRequiredStrictly , adminRequired , admin.externalAccount.index);
+  app.get('/admin/users/external-accounts'              , loginRequiredStrictly , adminRequired , admin.externalAccount.index);
 
   // user-groups admin
-  app.get('/admin/user-groups'             , loginRequiredStrictly, adminRequired, admin.userGroup.index);
-  app.get('/admin/user-group-detail/:id'   , loginRequiredStrictly, adminRequired, admin.userGroup.detail);
+  app.get('/admin/user-groups'                          , loginRequiredStrictly, adminRequired, admin.userGroup.index);
+  app.get('/admin/user-group-detail/:id'                , loginRequiredStrictly, adminRequired, admin.userGroup.detail);
 
   // importer management for admin
   app.get('/admin/importer'                     , loginRequiredStrictly , adminRequired , admin.importer.index);

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

@@ -85,7 +85,7 @@ module.exports = function(crowi) {
     // create initial pages
     await createInitialPages(adminUser, language);
 
-    crowi.setupAfterInstall();
+    appService.setupAfterInstall();
     appService.publishPostInstallationMessage();
 
     // login with passport

+ 20 - 17
src/server/service/app.js

@@ -38,17 +38,9 @@ class AppService extends S2sMessageHandlable {
   async handleS2sMessage(s2sMessage) {
     logger.info('Invoke post installation process by pubsub notification');
 
-    const { crowi, configManager, s2sMessagingService } = this;
-
-    // load config and setup
-    await configManager.loadConfigs();
-
-    const isInstalled = this.crowi.configManager.getConfig('crowi', 'app:installed');
-    if (isInstalled) {
-      crowi.setupAfterInstall();
-
-      // remove message handler
-      s2sMessagingService.removeMessageHandler(this);
+    const isDBInitialized = await this.isDBInitialized(true);
+    if (isDBInitialized) {
+      this.setupAfterInstall();
     }
   }
 
@@ -64,9 +56,6 @@ class AppService extends S2sMessageHandlable {
       catch (e) {
         logger.error('Failed to publish post installation message with S2sMessagingService: ', e.message);
       }
-
-      // remove message handler
-      s2sMessagingService.removeMessageHandler(this);
     }
 
   }
@@ -113,9 +102,23 @@ class AppService extends S2sMessageHandlable {
     await this.configManager.updateConfigsInTheSameNamespace('crowi', initialConfig, true);
   }
 
-  async isDBInitialized() {
-    const appInstalled = await this.configManager.getConfigFromDB('crowi', 'app:installed');
-    return appInstalled;
+  async isDBInitialized(forceReload) {
+    if (forceReload) {
+      // load configs
+      await this.configManager.loadConfigs();
+    }
+    return this.configManager.getConfigFromDB('crowi', 'app:installed');
+  }
+
+  async setupAfterInstall() {
+    this.crowi.pluginService.autoDetectAndLoadPlugins();
+    this.crowi.setupRoutesAtLast();
+
+    // remove message handler
+    const { s2sMessagingService } = this;
+    if (s2sMessagingService != null) {
+      this.s2sMessagingService.removeMessageHandler(this);
+    }
   }
 
 }