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

Merge branch 'feat/growi-bot' into feat/create-input-slack-ws-for-test

zahmis 5 лет назад
Родитель
Сommit
100ab65327

+ 5 - 3
.github/workflows/ci.yml

@@ -7,6 +7,8 @@ on:
       - tmp/**
     paths:
       - .github/workflows/ci.yml
+      - packages/app/**
+      - packages/app-for-hoisting/**
       - .eslint*
       - .prettier*
       - .stylelint*
@@ -196,7 +198,7 @@ jobs:
       if: steps.cache-dependencies.outputs.cache-hit != 'true'
       run: |
         npx lerna bootstrap
-        yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs react-images@1.0.0 react-motion --scope @growi/core
+        yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs react-images@1.0.0 react-motion --scope @growi/app --scope @growi/app-for-hoisting
     - name: Print dependencies
       run: |
         echo -n "node " && node -v
@@ -269,8 +271,8 @@ jobs:
     - name: Install dependencies
       run: |
         npx lerna bootstrap
-        yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs --scope @growi/core
-        yarn lerna add -D react-images@1.0.0 react-motion --scope @growi/core
+        yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs --scope @growi/app --scope @growi/app-for-hoisting
+        yarn lerna add -D react-images@1.0.0 react-motion --scope @growi/app --scope @growi/app-for-hoisting
     - name: Print dependencies
       run: |
         echo -n "node " && node -v

+ 1 - 3
packages/core/package.json → packages/app-for-hoisting/package.json

@@ -1,9 +1,7 @@
 {
-  "name": "@growi/core",
+  "name": "@growi/app-for-hoisting",
   "version": "0.9.0-RC",
   "license": "MIT",
-  "main": "dist/index.js",
-  "typings": "dist/index.d.ts",
   "scripts": {
   },
   "// comments for dependencies": {

+ 209 - 0
packages/app/package.json

@@ -0,0 +1,209 @@
+{
+  "name": "@growi/app",
+  "version": "0.9.0-RC",
+  "license": "MIT",
+  "scripts": {
+  },
+  "// comments for dependencies": {
+    "openid-client": "Node.js 12 or higher is required for openid-client@3 and above."
+  },
+  "dependencies": {
+    "@google-cloud/storage": "^3.3.0",
+    "@kobalab/socket.io-session": "^1.0.3",
+    "@promster/express": "^5.0.1",
+    "@promster/server": "^6.0.0",
+    "@slack/events-api": "^3.0.0",
+    "@slack/web-api": "^6.1.0",
+    "JSONStream": "^1.3.5",
+    "archiver": "^3.1.1",
+    "array.prototype.flatmap": "^1.2.2",
+    "async-canvas-to-blob": "^1.0.3",
+    "aws-sdk": "^2.88.0",
+    "axios": "^0.21.1",
+    "body-parser": "^1.18.2",
+    "bunyan": "^1.8.12",
+    "bunyan-format": "^0.2.1",
+    "check-node-version": "^4.1.0",
+    "connect-flash": "~0.1.1",
+    "connect-mongo": "^3.2.0",
+    "connect-redis": "^4.0.4",
+    "cookie-parser": "^1.4.5",
+    "cross-env": "^7.0.0",
+    "csrf": "^3.1.0",
+    "date-fns": "^2.0.0",
+    "detect-indent": "^6.0.0",
+    "diff": "^4.0.1",
+    "elasticsearch": "^16.0.0",
+    "entities": "^2.0.0",
+    "env-cmd": "^10.0.1",
+    "esa-nodejs": "^0.0.7",
+    "escape-string-regexp": "^2.0.0",
+    "express": "^4.16.1",
+    "express-bunyan-logger": "^1.3.3",
+    "express-form": "~0.12.0",
+    "express-session": "^1.16.1",
+    "express-validator": "^6.1.1",
+    "express-webpack-assets": "^0.1.0",
+    "graceful-fs": "^4.1.11",
+    "growi-commons": "^5.0.3",
+    "helmet": "^3.13.0",
+    "i18next": "^19.0.0",
+    "i18next-express-middleware": "^1.4.1",
+    "i18next-node-fs-backend": "^2.1.0",
+    "i18next-sprintf-postprocessor": "^0.2.2",
+    "is-iso-date": "^0.0.1",
+    "lucene-query-parser": "^1.2.0",
+    "md5": "^2.2.1",
+    "method-override": "^3.0.0",
+    "migrate-mongo": "^8.1.4",
+    "mkdirp": "^1.0.3",
+    "module-alias": "^2.0.6",
+    "mongoose": "5.10.11",
+    "mongoose-gridfs": "^1.2.42",
+    "mongoose-paginate-v2": "^1.3.9",
+    "mongoose-unique-validator": "^2.0.3",
+    "multer": "~1.4.0",
+    "multer-autoreap": "^1.0.3",
+    "nodemailer": "^6.0.0",
+    "nodemailer-ses-transport": "~1.5.0",
+    "npm-run-all": "^4.1.2",
+    "openid-client": "=2.5.0",
+    "package-installed-version-sync": "^2.1.0",
+    "passport": "^0.4.0",
+    "passport-github": "^1.1.0",
+    "passport-google-oauth20": "^2.0.0",
+    "passport-http": "^0.3.0",
+    "passport-ldapauth": "^2.0.0",
+    "passport-local": "^1.0.0",
+    "passport-saml": "^1.0.0",
+    "passport-twitter": "^1.0.4",
+    "prom-client": "^13.0.0",
+    "react-card-flip": "^1.0.10",
+    "react-image-crop": "^8.3.0",
+    "reconnecting-websocket": "^4.4.0",
+    "redis": "^3.0.2",
+    "rimraf": "^3.0.0",
+    "slack-node": "^0.1.8",
+    "socket.io": "^2.3.0",
+    "stream-to-promise": "^2.2.0",
+    "string-width": "^4.1.0",
+    "swig-templates": "^2.0.2",
+    "uglifycss": "^0.0.29",
+    "unzipper": "^0.10.5",
+    "url-join": "^4.0.0",
+    "validator": "^12.0.0",
+    "ws": "^7.3.1",
+    "xss": "^1.0.6"
+  },
+  "// comments for defDependencies": {
+    "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
+    "handsontable": "v7.0.0 or above is no loger MIT lisence."
+  },
+  "devDependencies": {
+    "@alienfast/i18next-loader": "^1.0.16",
+    "@atlaskit/drawer": "^5.3.7",
+    "@atlaskit/navigation-next": "^8.0.5",
+    "@babel/core": "^7.4.5",
+    "@babel/plugin-proposal-class-properties": "^7.8.3",
+    "@babel/plugin-proposal-optional-chaining": "^7.9.0",
+    "@babel/polyfill": "^7.4.4",
+    "@babel/preset-env": "^7.4.5",
+    "@babel/preset-react": "^7.0.0",
+    "@handsontable/react": "=2.1.0",
+    "@types/compression": "^1.7.0",
+    "@types/express": "^4.17.11",
+    "@types/multer": "^1.4.5",
+    "@types/node": "^14.14.35",
+    "autoprefixer": "^9.0.0",
+    "babel-eslint": "^10.0.1",
+    "babel-loader": "^8.0.6",
+    "babel-plugin-lodash": "^3.3.4",
+    "babel-plugin-transform-imports": "^2.0.0",
+    "bootstrap": "^4.5.0",
+    "browser-bunyan": "^1.3.0",
+    "browser-sync": "^2.26.3",
+    "bunyan-debug": "^2.0.0",
+    "cli": "~1.0.1",
+    "codemirror": "^5.48.4",
+    "colors": "^1.2.5",
+    "connect-browser-sync": "^2.1.0",
+    "core-js": "=2.6.9",
+    "css-loader": "^3.0.0",
+    "csv-to-markdown-table": "^1.0.1",
+    "diff2html": "^3.1.2",
+    "eazy-logger": "^3.0.2",
+    "eslint": "^6.0.1",
+    "eslint-config-weseek": "^1.0.8",
+    "eslint-plugin-import": "^2.18.0",
+    "eslint-plugin-jest": "^23.0.3",
+    "eslint-plugin-react": "^7.14.2",
+    "eslint-plugin-react-hooks": "^4.0.4",
+    "file-loader": "^5.0.2",
+    "handsontable": "=6.2.2",
+    "hard-source-webpack-plugin": "^0.13.1",
+    "i18next-browser-languagedetector": "^4.0.1",
+    "imports-loader": "^0.8.0",
+    "jest": "^25.1.0",
+    "jquery-slimscroll": "^1.3.8",
+    "jquery-ui": "^1.12.1",
+    "jquery.cookie": "~1.4.1",
+    "load-css-file": "^1.0.0",
+    "lodash-webpack-plugin": "^0.11.5",
+    "markdown-it": "^10.0.0",
+    "markdown-it-blockdiag": "^1.1.1",
+    "markdown-it-drawio-viewer": "^1.3.1",
+    "markdown-it-emoji": "^1.4.0",
+    "markdown-it-footnote": "^3.0.1",
+    "markdown-it-mathjax": "^2.0.0",
+    "markdown-it-named-headers": "^0.0.4",
+    "markdown-it-plantuml": "^1.3.0",
+    "markdown-it-task-checkbox": "^1.0.6",
+    "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
+    "markdown-table": "^1.1.1",
+    "mini-css-extract-plugin": "^0.9.0",
+    "morgan": "^1.9.0",
+    "node-dev": "^4.0.0",
+    "node-sass": "^4.14.1",
+    "normalize-path": "^3.0.0",
+    "null-loader": "^3.0.0",
+    "on-headers": "^1.0.1",
+    "optimize-css-assets-webpack-plugin": "^5.0.3",
+    "penpal": "^4.0.0",
+    "plantuml-encoder": "^1.2.5",
+    "postcss-loader": "^3.0.0",
+    "prettier": "^1.19.1",
+    "react": "^16.8.3",
+    "react-bootstrap-typeahead": "^3.4.7",
+    "react-codemirror2": "^6.0.0",
+    "react-copy-to-clipboard": "^5.0.1",
+    "react-dom": "^16.8.3",
+    "react-dropzone": "^11.2.4",
+    "react-frame-component": "^4.0.0",
+    "react-hotkeys": "^2.0.0",
+    "react-i18next": "^11.1.0",
+    "react-waypoint": "^9.0.0",
+    "reactstrap": "^8.0.1",
+    "replacestream": "^4.0.3",
+    "reveal.js": "^3.5.0",
+    "rs-i18n": "^0.0.9",
+    "sass-loader": "^8.0.0",
+    "simple-load-script": "^1.0.2",
+    "socket.io-client": "^2.3.0",
+    "sticky-events": "^3.1.3",
+    "style-loader": "^1.0.0",
+    "styled-components": "^5.0.1",
+    "stylelint": "^13.2.0",
+    "stylelint-config-recess-order": "^2.0.1",
+    "swagger-jsdoc": "^3.4.0",
+    "swagger2openapi": "^5.3.1",
+    "terser-webpack-plugin": "^4.1.0",
+    "throttle-debounce": "^2.0.0",
+    "toastr": "^2.1.2",
+    "unstated": "^2.1.1",
+    "webpack": "^4.39.3",
+    "webpack-assets-manifest": "^3.1.1",
+    "webpack-bundle-analyzer": "^3.0.2",
+    "webpack-cli": "^3.3.7",
+    "webpack-merge": "^4.2.2"
+  }
+}

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

@@ -299,7 +299,8 @@
     "integration_sentence": {
       "integration_is_not_complete": "Integration is not complete.",
       "proceed_with_the_following_integration_procedure": "Proceed with the following integration procedure."
-    }
+    },
+    "custom_bot_with_proxy_integration": "Custom bot with proxy integration"
   },
   "user_management": {
     "invite_users": "Temporarily issue a new user",

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

@@ -297,7 +297,8 @@
     "integration_sentence": {
       "integration_is_not_complete": "連携は完了していません。",
       "proceed_with_the_following_integration_procedure": "下記の連携手順を進めてください。"
-    }
+    },
+    "custom_bot_with_proxy_integration": "Custom bot with proxy 連携"
   },
   "user_management": {
     "invite_users": "新規ユーザーの仮発行",

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

@@ -307,7 +307,8 @@
     "integration_sentence": {
       "integration_is_not_complete": "一体化未完成。",
       "proceed_with_the_following_integration_procedure": "进行以下一体化程序。"
-    }
+    },
+    "custom_bot_with_proxy_integration": "Custom bot with proxy 一体化"
   },
 	"user_management": {
 		"invite_users": "临时发布新用户",

+ 35 - 0
src/client/js/components/Admin/Common/Accordion.jsx

@@ -0,0 +1,35 @@
+import React, { useState } from 'react';
+import { Collapse } from 'reactstrap';
+import PropTypes from 'prop-types';
+
+const Accordion = (props) => {
+  const [isOpen, setIsOpen] = useState(props.isOpenDefault);
+  return (
+    <div className="card border-0 rounded-lg mb-0">
+      <div
+        className="card-header font-weight-normal py-3 d-flex justify-content-between"
+        role="button"
+        onClick={() => setIsOpen(prevState => !prevState)}
+      >
+        <p className="mb-0">{props.title}</p>
+        {isOpen
+          ? <i className="fa fa-chevron-up" />
+          : <i className="fa fa-chevron-down" />
+        }
+      </div>
+      <Collapse isOpen={isOpen}>
+        <div className="card-body">
+          {props.children}
+        </div>
+      </Collapse>
+    </div>
+  );
+};
+
+Accordion.propTypes = {
+  title: PropTypes.node.isRequired,
+  children: PropTypes.node.isRequired,
+  isOpenDefault: PropTypes.bool.isRequired,
+};
+
+export default Accordion;

+ 58 - 5
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -1,12 +1,65 @@
 import React from 'react';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+import AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
 
-const CustomBotWithProxySettings = () => {
+const CustomBotWithProxySettings = (props) => {
+  // eslint-disable-next-line no-unused-vars
+  const { appContainer, adminAppContainer } = props;
+  const { t } = useTranslation();
 
   return (
-    <div className="row my-5">
-      <h1>With Proxy Component</h1>
-    </div>
+    <>
+
+      <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
+
+      <div className="d-flex justify-content-center my-5 bot-integration">
+
+        <div className="card rounded shadow border-0 w-50 admin-bot-card">
+          <h5 className="card-title font-weight-bold mt-3 ml-4">Slack</h5>
+          <div className="card-body p-4"></div>
+        </div>
+
+        <div className="text-center w-25 mb-5">
+          <p className="text-secondary m-0"><small>{t('admin:slack_integration.integration_sentence.integration_is_not_complete')}</small></p>
+          <p className="text-secondary"><small>{t('admin:slack_integration.integration_sentence.proceed_with_the_following_integration_procedure')}</small></p>
+
+          <div className="row m-0">
+            <hr className="border-danger align-self-center admin-border col"></hr>
+            <div className="circle text-center bg-primary border-light">
+              <p className="text-light font-weight-bold m-0 pt-3">Proxy</p>
+              <p className="text-light font-weight-bold">Server</p>
+            </div>
+            <hr className="border-danger align-self-center admin-border col"></hr>
+          </div>
+        </div>
+
+        <div className="card rounded-lg shadow border-0 w-50 admin-bot-card">
+          <div className="row m-0">
+            <h5 className="card-title font-weight-bold mt-3 ml-4 col">GROWI App</h5>
+            <div className="pull-right mt-3">
+              <a className="icon-fw fa fa-repeat fa-2x"></a>
+            </div>
+          </div>
+          <div className="card-body p-4 text-center">
+            <a className="btn btn-primary mt-3">WESEEK Inner Wiki</a>
+          </div>
+        </div>
+
+      </div>
+
+    </>
   );
 };
 
-export default CustomBotWithProxySettings;
+
+const CustomBotWithProxySettingsWrapper = withUnstatedContainers(CustomBotWithProxySettings, [AppContainer, AdminAppContainer]);
+
+CustomBotWithProxySettings.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+};
+
+export default CustomBotWithProxySettingsWrapper;

+ 107 - 172
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -1,10 +1,10 @@
 import React, { useState, useEffect, useCallback } from 'react';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
-import { Collapse } from 'reactstrap';
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import Accordion from '../Common/Accordion';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithoutProxySecretTokenSection from './CustomBotWithoutProxySecretTokenSection';
 
@@ -17,7 +17,9 @@ export const botInstallationStep = {
 
 const CustomBotWithoutProxySettingsAccordion = ({ appContainer, adminAppContainer, activeStep }) => {
   const { t } = useTranslation('admin');
-  const [openAccordionIndexes, setOpenAccordionIndexes] = useState(new Set([activeStep]));
+  // TODO: GW-5644 Store default open accordion
+  // eslint-disable-next-line no-unused-vars
+  const [defaultOpenAccordionKeys, setDefaultOpenAccordionKeys] = useState(new Set([activeStep]));
   const [connectionErrorCode, setConnectionErrorCode] = useState(null);
   const [connectionErrorMessage, setConnectionErrorMessage] = useState(null);
   const [connectionSuccessMessage, setConnectionSuccessMessage] = useState(null);
@@ -49,18 +51,6 @@ const CustomBotWithoutProxySettingsAccordion = ({ appContainer, adminAppContaine
     fetchData();
   }, [fetchData]);
 
-
-  const onToggleAccordionHandler = (installationStep) => {
-    const accordionIndexes = new Set(openAccordionIndexes);
-    if (accordionIndexes.has(installationStep)) {
-      accordionIndexes.delete(installationStep);
-    }
-    else {
-      accordionIndexes.add(installationStep);
-    }
-    setOpenAccordionIndexes(accordionIndexes);
-  };
-
   const updateSecretTokenHandler = async() => {
     try {
       await appContainer.apiv3.put('/slack-integration/custom-bot-without-proxy', {
@@ -69,7 +59,7 @@ const CustomBotWithoutProxySettingsAccordion = ({ appContainer, adminAppContaine
         currentBotType,
       });
       fetchData();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_without_proxy_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('slack_integration.custom_bot_without_proxy_settings') }));
     }
     catch (err) {
       toastError(err);
@@ -78,7 +68,6 @@ const CustomBotWithoutProxySettingsAccordion = ({ appContainer, adminAppContaine
 
   const onChangeSigningSecretHandler = (signingSecretInput) => {
     setSlackSigningSecret(signingSecretInput);
-
   };
 
   const onChangeBotTokenHandler = (botTokenInput) => {
@@ -107,169 +96,115 @@ const CustomBotWithoutProxySettingsAccordion = ({ appContainer, adminAppContaine
 
   return (
     <div className="card border-0 rounded-lg shadow overflow-hidden">
-      <div className="card border-0 rounded-lg mb-0">
-        <div
-          className="card-header font-weight-normal py-3 d-flex justify-content-between"
-          role="button"
-          onClick={() => onToggleAccordionHandler(botInstallationStep.CREATE_BOT)}
-        >
-          <p className="mb-0 text-primary"><span className="mr-2">①</span>{t('slack_integration.without_proxy.create_bot')}</p>
-          {openAccordionIndexes.has(botInstallationStep.CREATE_BOT)
-            ? <i className="fa fa-chevron-up" />
-            : <i className="fa fa-chevron-down" />
-          }
-        </div>
-        <Collapse isOpen={openAccordionIndexes.has(botInstallationStep.CREATE_BOT)}>
-          <div className="card-body">
-
-            <div className="row my-5">
-              <div className="mx-auto">
-                <div>
-                  <button type="button" className="btn btn-primary text-nowrap mx-1" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
-                    {t('admin:slack_integration.without_proxy.create_bot')}
-                    <i className="fa fa-external-link ml-2" aria-hidden="true" />
-                  </button>
-                </div>
-                {/* TODO: Insert DOCS link */}
-                <a href="#">
-                  <p className="text-center mt-1">
-                    <small>
-                      {t('admin:slack_integration.without_proxy.how_to_create_a_bot')}
-                      <i className="fa fa-external-link ml-2" aria-hidden="true" />
-                    </small>
-                  </p>
-                </a>
-              </div>
+      <Accordion
+        defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CREATE_BOT)}
+        title={<><span className="mr-2">①</span>{t('slack_integration.without_proxy.create_bot')}</>}
+      >
+        <div className="row my-5">
+          <div className="mx-auto">
+            <div>
+              <button type="button" className="btn btn-primary text-nowrap mx-1" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
+                {t('slack_integration.without_proxy.create_bot')}
+                <i className="fa fa-external-link ml-2" aria-hidden="true" />
+              </button>
             </div>
+            {/* TODO: Insert DOCS link */}
+            <a href="#">
+              <p className="text-center mt-1">
+                <small>
+                  {t('slack_integration.without_proxy.how_to_create_a_bot')}
+                  <i className="fa fa-external-link ml-2" aria-hidden="true" />
+                </small>
+              </p>
+            </a>
           </div>
-        </Collapse>
-      </div>
-
-      <div className="card border-0 rounded-lg mb-0">
-        <div
-          className="card-header font-weight-normal py-3 d-flex justify-content-between"
-          role="button"
-          onClick={() => onToggleAccordionHandler(botInstallationStep.INSTALL_BOT)}
-        >
-          <p className="mb-0 text-primary"><span className="mr-2">②</span>{t('slack_integration.without_proxy.install_bot_to_slack')}</p>
-          {openAccordionIndexes.has(botInstallationStep.INSTALL_BOT)
-            ? <i className="fa fa-chevron-up" />
-            : <i className="fa fa-chevron-down" />
-          }
         </div>
-        <Collapse isOpen={openAccordionIndexes.has(botInstallationStep.INSTALL_BOT)}>
-          <div className="card-body py-5">
-            <div className="container w-75">
-              <p>1. Install your app をクリックします。</p>
-              <img src="/images/slack-integration/slack-bot-install-your-app-introduction.png" className="border border-light img-fluid mb-5" />
-              <p>2. Install to Workspace をクリックします。</p>
-              <img src="/images/slack-integration/slack-bot-install-to-workspace.png" className="border border-light img-fluid mb-5" />
-              <p>3. 遷移先の画面にて、Allowをクリックします。</p>
-              <img src="/images/slack-integration/slack-bot-install-your-app-transition-destination.png" className="border border-light img-fluid mb-5" />
-              <p>4. Install your app の右側に 緑色のチェックがつけばワークスペースへのインストール完了です。</p>
-              <img src="/images/slack-integration/slack-bot-install-your-app-complete.png" className="border border-light img-fluid mb-5" />
-              <p>5. GROWI bot を使いたいチャンネルに @example を使用して招待します。</p>
-              <img src="/images/slack-integration/slack-bot-install-to-workspace-joined-bot.png" className="border border-light img-fluid mb-1" />
-              <img src="/images/slack-integration/slack-bot-install-your-app-introduction-to-channel.png" className="border border-light img-fluid" />
-            </div>
-          </div>
-        </Collapse>
-      </div>
-
-      <div className="card border-0 rounded-lg mb-0">
-        <div
-          className="card-header font-weight-normal py-3 d-flex justify-content-between"
-          role="button"
-          onClick={() => onToggleAccordionHandler(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}
-        >
-          <p className="mb-0 text-primary"><span className="mr-2">③</span>{t('slack_integration.without_proxy.register_secret_and_token')}</p>
-          {openAccordionIndexes.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)
-            ? <i className="fa fa-chevron-up" />
-            : <i className="fa fa-chevron-down" />
-          }
+      </Accordion>
+      <Accordion
+        defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.INSTALL_BOT)}
+        title={<><span className="mr-2">②</span>{t('slack_integration.without_proxy.install_bot_to_slack')}</>}
+      >
+        <div className="container w-75 py-5">
+          <p>1. Install your app をクリックします。</p>
+          <img src="/images/slack-integration/slack-bot-install-your-app-introduction.png" className="border border-light img-fluid mb-5" />
+          <p>2. Install to Workspace をクリックします。</p>
+          <img src="/images/slack-integration/slack-bot-install-to-workspace.png" className="border border-light img-fluid mb-5" />
+          <p>3. 遷移先の画面にて、Allowをクリックします。</p>
+          <img src="/images/slack-integration/slack-bot-install-your-app-transition-destination.png" className="border border-light img-fluid mb-5" />
+          <p>4. Install your app の右側に 緑色のチェックがつけばワークスペースへのインストール完了です。</p>
+          <img src="/images/slack-integration/slack-bot-install-your-app-complete.png" className="border border-light img-fluid mb-5" />
+          <p>5. GROWI bot を使いたいチャンネルに @example を使用して招待します。</p>
+          <img src="/images/slack-integration/slack-bot-install-to-workspace-joined-bot.png" className="border border-light img-fluid mb-1" />
+          <img src="/images/slack-integration/slack-bot-install-your-app-introduction-to-channel.png" className="border border-light img-fluid" />
         </div>
-        <Collapse isOpen={openAccordionIndexes.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}>
-          <CustomBotWithoutProxySecretTokenSection
-            updateSecretTokenHandler={updateSecretTokenHandler}
-            onChangeSigningSecretHandler={onChangeSigningSecretHandler}
-            onChangeBotTokenHandler={onChangeBotTokenHandler}
-            slackSigningSecret={slackSigningSecret}
-            slackSigningSecretEnv={slackSigningSecretEnv}
-            slackBotToken={slackBotToken}
-            slackBotTokenEnv={slackBotTokenEnv}
-          />
-        </Collapse>
-      </div>
-
-      <div className="card border-0 rounded-lg mb-0">
-        <div
-          className="card-header font-weight-normal py-3 d-flex justify-content-between"
-          role="button"
-          onClick={() => onToggleAccordionHandler(botInstallationStep.CONNECTION_TEST)}
-        >
-          <p className="mb-0 text-primary"><span className="mr-2">④</span>{t('slack_integration.without_proxy.test_connection')}</p>
-          {openAccordionIndexes.has(botInstallationStep.CONNECTION_TEST)
-            ? <i className="fa fa-chevron-up" />
-            : <i className="fa fa-chevron-down" />
-          }
+      </Accordion>
+      <Accordion
+        defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}
+        title={<><span className="mr-2">③</span>{t('slack_integration.without_proxy.register_secret_and_token')}</>}
+      >
+        <CustomBotWithoutProxySecretTokenSection
+          updateSecretTokenHandler={updateSecretTokenHandler}
+          onChangeSigningSecretHandler={onChangeSigningSecretHandler}
+          onChangeBotTokenHandler={onChangeBotTokenHandler}
+          slackSigningSecret={slackSigningSecret}
+          slackSigningSecretEnv={slackSigningSecretEnv}
+          slackBotToken={slackBotToken}
+          slackBotTokenEnv={slackBotTokenEnv}
+        />
+      </Accordion>
+      <Accordion
+        defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
+        title={<><span className="mr-2">④</span>{t('slack_integration.without_proxy.test_connection')}</>}
+      >
+        <p className="text-center m-4">以下のテストボタンを押して、Slack連携が完了しているかの確認をしましょう</p>
+        <div className="d-flex justify-content-center">
+          <form className="form-row align-items-center w-25">
+            <div className="col-8">
+              <input
+                className="form-control"
+                type="text"
+                value={testChannel}
+                placeholder="eg. general"
+                onChange={e => inputTestChannelHandler(e.target.value)}
+              />
+            </div>
+            <div className="col-4">
+              <button
+                type="button"
+                className="btn btn-info m-3 font-weight-bold"
+                onClick={onTestConnectionHandler}
+              >Test
+              </button>
+            </div>
+          </form>
         </div>
-        <Collapse isOpen={openAccordionIndexes.has(botInstallationStep.CONNECTION_TEST)}>
-          <div className="card-body">
-            <p className="text-center m-4">以下のテストボタンを押して、Slack連携が完了しているかの確認をしましょう</p>
-            <div className="d-flex justify-content-center">
-              <form className="form-row align-items-center w-25">
-                <div className="col-8">
-                  <input
-                    className="form-control"
-                    type="text"
-                    value={testChannel}
-                    placeholder="eg. general"
-                    onChange={e => inputTestChannelHandler(e.target.value)}
-                  />
-                </div>
-                <div className="col-4">
-                  <button
-                    type="button"
-                    className="btn btn-info m-3 font-weight-bold"
-                    onClick={onTestConnectionHandler}
-                  >Test
-                  </button>
-                </div>
-              </form>
+        {connectionErrorMessage != null && <p className="text-danger text-center m-4">エラーが発生しました。下記のログを確認してください。</p>}
+        {connectionSuccessMessage != null && <p className="text-info text-center m-4">Slack ワークスペースに送信しました。</p>}
+        <form>
+          <div className="row m-3 justify-content-center">
+            <div className="form-group slack-connection-log w-25">
+              <label><p className="border-info slack-connection-log-title mb-1 pl-2">Logs</p></label>
+              {connectionErrorMessage == null && connectionSuccessMessage == null && (
+                <textarea className="form-control card border-info slack-connection-log-body rounded-lg pl-3" />
+              )}
+              {connectionErrorMessage != null && (
+                <textarea
+                  className="form-control card border-info slack-connection-log-body rounded-lg pl-3"
+                  multiple
+                  value={[connectionErrorCode, connectionErrorMessage]}
+                />
+              )}
+              {connectionSuccessMessage != null && (
+                <textarea
+                  className="form-control card border-info slack-connection-log-body rounded-lg pl-2"
+                  multiple
+                  value={connectionSuccessMessage}
+                />
+              )}
             </div>
-            {connectionErrorMessage != null
-              && <p className="text-danger text-center m-4">エラーが発生しました。下記のログを確認してください。</p>
-            }
-            {connectionSuccessMessage != null
-              && <p className="text-info text-center m-4">Slack ワークスペースに送信しました。</p>
-            }
-            <form>
-              <div className="row m-3 justify-content-center">
-                <div className="form-group slack-connection-log w-25">
-                  <label><p className="border-info slack-connection-log-title mb-1 pl-2">Logs</p></label>
-                  {connectionErrorMessage == null && connectionSuccessMessage == null && (
-                    <textarea className="form-control card border-info slack-connection-log-body rounded-lg pl-3" />
-                  )}
-                  {connectionErrorMessage != null && (
-                    <textarea
-                      className="form-control card border-info slack-connection-log-body rounded-lg pl-3"
-                      multiple
-                      value={[connectionErrorCode, connectionErrorMessage]}
-                    />
-                  )}
-                  {connectionSuccessMessage != null && (
-                    <textarea
-                      className="form-control card border-info slack-connection-log-body rounded-lg pl-2"
-                      multiple
-                      value={connectionSuccessMessage}
-                    />
-                  )}
-                </div>
-              </div>
-            </form>
           </div>
-        </Collapse>
-      </div>
+        </form>
+      </Accordion>
     </div>
   );
 };

+ 7 - 0
src/client/styles/scss/_admin.scss

@@ -123,6 +123,13 @@
       border-style : dashed;
       border-width : 2px;
     }
+    .circle{
+      width: 100px;
+      height: 100px;
+      // background: #092c58;
+      border: 13px solid;
+      border-radius: 50%;
+    }
   }
 
   //// TODO: migrate to Bootstrap 4