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

Merge pull request #1740 from weseek/support/security-page

Support/security page
Yuki Takei 6 лет назад
Родитель
Сommit
dc839ca14e

+ 0 - 1
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -116,7 +116,6 @@ class CustomizeBehaviorSetting extends React.Component {
                   <DropdownToggle className="text-right col-6" caret>
                     <span className="float-left">{adminCustomizeContainer.state.currentRecentCreatedLimit}</span>
                   </DropdownToggle>
-                  {/* TODO adjust dropdown after BS4 */}
                   <DropdownMenu className="dropdown-menu" role="menu">
                     <DropdownItem key={10} role="presentation" onClick={() => { adminCustomizeContainer.switchRecentCreatedLimit(10) }}>
                       <a role="menuitem">10</a>

+ 1 - 1
src/client/js/components/Admin/Notification/SlackAppConfiguration.jsx

@@ -50,7 +50,7 @@ class SlackAppConfiguration extends React.Component {
                 aria-haspopup="true"
                 aria-expanded="true"
               >
-                {`Slack ${adminNotificationContainer.state.selectSlackOption}`} <span className="caret"></span>
+                {`Slack ${adminNotificationContainer.state.selectSlackOption}`}
               </button>
               <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
                 <a className="dropdown-item" onClick={() => adminNotificationContainer.switchSlackOption('Incoming Webhooks')}>Slack Incoming Webhooks</a>

+ 10 - 10
src/client/js/components/Admin/Security/BasicSecuritySetting.jsx

@@ -68,18 +68,16 @@ class BasicSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.Basic.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isBasicEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isBasicEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsBasicEnabled() }}
               />
-              <label htmlFor="isBasicEnabled">
+              <label className="custom-control-label" htmlFor="isBasicEnabled">
                 { t('security_setting.Basic.enable_basic') }
               </label>
             </div>
@@ -90,22 +88,24 @@ class BasicSecurityManagement extends React.Component {
               </small>
             </p>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('basic') && isBasicEnabled)
-            && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+            && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         {isBasicEnabled && (
         <React.Fragment>
           <div className="row mb-5">
-            <div className="col-xs-offset-3 col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
+            <div className="offset-3 col-6">
+              <div className="custom-control custom-switch checkbox-success">
                 <input
                   id="bindByEmail-basic"
+                  className="custom-control-input"
                   type="checkbox"
                   checked={adminBasicSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                   onChange={() => { adminBasicSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                 />
                 <label
+                  className="custom-control-label"
                   htmlFor="bindByEmail-basic"
                   dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical', 'username') }}
                 />
@@ -117,7 +117,7 @@ class BasicSecurityManagement extends React.Component {
           </div>
 
           <div className="row my-3">
-            <div className="col-xs-offset-4 col-xs-5">
+            <div className="offset-4 col-5">
               <button type="button" className="btn btn-primary" disabled={adminBasicSecurityContainer.state.retrieveError != null} onClick={this.onClickSubmit}>
                 {t('Update')}
               </button>

+ 16 - 16
src/client/js/components/Admin/Security/GitHubSecuritySetting.jsx

@@ -69,29 +69,27 @@ class GitHubSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.GitHub.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6 text-left">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isGitHubEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isGitHubEnabled || false}
                 onChange={() => { adminGeneralSecurityContainer.switchIsGitHubOAuthEnabled() }}
               />
-              <label htmlFor="isGitHubEnabled">
+              <label className="custom-control-label" htmlFor="isGitHubEnabled">
                 {t('security_setting.OAuth.GitHub.enable_github')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('github') && isGitHubEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badg badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class GitHubSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="githubClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="githubClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="githubClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="githubClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByUserNameGitHub"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminGitHubSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminGitHubSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameGitHub"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class GitHubSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <div className="btn btn-primary" disabled={adminGitHubSecurityContainer.state.retrieveError != null} onClick={this.onClickSubmit}>
                   {t('Update')}
                 </div>

+ 16 - 16
src/client/js/components/Admin/Security/GoogleSecuritySetting.jsx

@@ -69,29 +69,27 @@ class GoogleSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.Google.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isGoogleEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isGoogleEnabled || false}
                 onChange={() => { adminGeneralSecurityContainer.switchIsGoogleOAuthEnabled() }}
               />
-              <label htmlFor="isGoogleEnabled">
+              <label className="custom-control-label" htmlFor="isGoogleEnabled">
                 {t('security_setting.OAuth.Google.enable_google')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('google') && isGoogleEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class GoogleSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="googleClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="googleClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="googleClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="googleClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByUserNameGoogle"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminGoogleSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminGoogleSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameGoogle"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class GoogleSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 6 - 6
src/client/js/components/Admin/Security/LdapAuthTestModal.jsx

@@ -118,8 +118,8 @@ class LdapAuthTestModal extends React.Component {
           {this.state.successMessage != null && <div className="alert alert-success">{this.state.successMessage}</div>}
           {this.state.errorMessage != null && <div className="alert alert-warning">{this.state.errorMessage}</div>}
           <div className="row p-3">
-            <label htmlFor="username" className="col-xs-3 text-right">{t('username')}</label>
-            <div className="col-xs-6">
+            <label htmlFor="username" className="col-3 text-right">{t('username')}</label>
+            <div className="col-6">
               <input
                 className="form-control"
                 name="username"
@@ -129,8 +129,8 @@ class LdapAuthTestModal extends React.Component {
             </div>
           </div>
           <div className="row p-3">
-            <label htmlFor="password" className="col-xs-3 text-right">{t('Password')}</label>
-            <div className="col-xs-6">
+            <label htmlFor="password" className="col-3 text-right">{t('Password')}</label>
+            <div className="col-6">
               <input
                 className="form-control"
                 type="password"
@@ -142,11 +142,11 @@ class LdapAuthTestModal extends React.Component {
           </div>
           <div>
             <h5>Logs</h5>
-            <textarea id="taLogs" className="col-xs-12" rows="4" value={this.state.logs} readOnly />
+            <textarea id="taLogs" className="col-12" rows="4" value={this.state.logs} readOnly />
           </div>
         </ModalBody>
         <ModalFooter>
-          <button type="button" className="btn btn-default mt-3 col-xs-offset-5 col-xs-2" onClick={this.testLdapCredentials}>Test</button>
+          <button type="button" className="btn btn-light mt-3 offset-5" onClick={this.testLdapCredentials}>Test</button>
         </ModalFooter>
       </Modal>
     );

+ 76 - 53
src/client/js/components/Admin/Security/LdapSecuritySetting.jsx

@@ -74,23 +74,21 @@ class LdapSecuritySetting extends React.Component {
         </h2>
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>Use LDAP</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isLdapEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={isLdapEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsLdapEnabled() }}
               />
-              <label htmlFor="isLdapEnabled">
+              <label className="custom-control-label" htmlFor="isLdapEnabled">
                 {t('security_setting.ldap.enable_ldap')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isLdapEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -101,8 +99,10 @@ class LdapSecuritySetting extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="serverUrl" className="col-xs-3 control-label text-right">Server URL</label>
-              <div className="col-xs-6">
+              <label htmlFor="serverUrl" className="col-3 control-label text-right py-2">
+                Server URL
+              </label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -122,35 +122,40 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('security_setting.ldap.bind_mode')}</strong>
-              <div className="col-xs-6 text-left">
-                <div className="my-0 btn-group">
-                  <div className="dropdown">
-                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      {adminLdapSecurityContainer.state.isUserBind
+              <div className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.bind_mode')}</strong>
+              </div>
+              <div className="col-6">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-light dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="true"
+                  >
+                    {adminLdapSecurityContainer.state.isUserBind
                         ? <span className="pull-left">{t('security_setting.ldap.bind_user')}</span>
                         : <span className="pull-left">{t('security_setting.ldap.bind_manager')}</span>}
-                      <span className="bs-caret pull-right">
-                        <span className="caret" />
-                      </span>
-                    </button>
-                    {/* TODO adjust dropdown after BS4 */}
-                    <ul className="dropdown-menu" role="menu">
-                      <li key="user" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
-                        <a role="menuitem">{t('security_setting.ldap.bind_user')}</a>
-                      </li>
-                      <li key="manager" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
-                        <a role="menuitem">{t('security_setting.ldap.bind_manager')}</a>
-                      </li>
-                    </ul>
+                  </button>
+                  <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
+                      {t('security_setting.ldap.bind_user')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
+                      {t('security_setting.ldap.bind_manager')}
+                    </a>
                   </div>
                 </div>
               </div>
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">Bind DN</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong>Bind DN</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -182,10 +187,12 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="bindDNPassword" className="col-xs-3 text-right">{t('security_setting.ldap.bind_DN_password')}</label>
-              <div className="col-xs-6">
+              <div htmlFor="bindDNPassword" className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.bind_DN_password')}</strong>
+              </div>
+              <div className="col-6">
                 {(adminLdapSecurityContainer.state.isUserBind) ? (
-                  <p className="help-block passport-ldap-userbind">
+                  <p className="well card passport-ldap-userbind">
                     <small>
                       {t('security_setting.ldap.bind_DN_password_user_detail')}
                     </small>
@@ -193,7 +200,7 @@ class LdapSecuritySetting extends React.Component {
                 )
                   : (
                     <>
-                      <p className="help-block passport-ldap-managerbind">
+                      <p className="well card passport-ldap-managerbind">
                         <small>
                           {t('security_setting.ldap.bind_DN_password_manager_detail')}
                         </small>
@@ -211,8 +218,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('security_setting.ldap.search_filter')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong>{t('security_setting.ldap.search_filter')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -245,8 +254,10 @@ class LdapSecuritySetting extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapUsername" className="col-xs-3 text-right">{t('username')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapUsername">{t('username')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -263,15 +274,17 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
-                    id="isSameUsernameTreatedAsIdenticalUser"
                     type="checkbox"
+                    className="custom-control-input"
+                    id="isSameUsernameTreatedAsIdenticalUser"
                     checked={adminLdapSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
                     onChange={() => { adminLdapSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="isSameUsernameTreatedAsIdenticalUser"
                     // eslint-disable-next-line react/no-danger
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
@@ -285,8 +298,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapMail" className="col-xs-3 text-right">{t('Email')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapMail">{t('Email')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -304,8 +319,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="attrMapName" className="col-xs-3 text-right">{t('Name')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="attrMapName">{t('Name')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -327,8 +344,10 @@ class LdapSecuritySetting extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <strong htmlFor="groupSearchBase" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_base_DN')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupSearchBase">{t('security_setting.ldap.group_search_base_DN')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -347,8 +366,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <strong htmlFor="groupSearchFilter" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_filter')}</strong>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupSearchFilter">{t('security_setting.ldap.group_search_filter')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -376,8 +397,10 @@ class LdapSecuritySetting extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="groupDnProperty" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_user_DN_property')}</label>
-              <div className="col-xs-6">
+              <div className="col-3 text-right py-2">
+                <strong htmlFor="groupDnProperty">{t('security_setting.ldap.group_search_user_DN_property')}</strong>
+              </div>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -393,7 +416,7 @@ class LdapSecuritySetting extends React.Component {
               </div>
             </div>
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"
@@ -402,7 +425,7 @@ class LdapSecuritySetting extends React.Component {
                 >
                   {t('Update')}
                 </button>
-                <button type="button" className="btn btn-default ml-2" onClick={this.openLdapAuthTestModal}>{t('security_setting.ldap.test_config')}</button>
+                <button type="button" className="btn btn-light ml-2" onClick={this.openLdapAuthTestModal}>{t('security_setting.ldap.test_config')}</button>
               </div>
             </div>
 

+ 57 - 70
src/client/js/components/Admin/Security/LocalSecuritySetting.jsx

@@ -75,24 +75,22 @@ class LocalSecuritySetting extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.Local.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch checkbox-success">
               <input
-                id="isLocalEnabled"
                 type="checkbox"
-                checked={adminGeneralSecurityContainer.state.isLocalEnabled}
-                onChange={() => { adminGeneralSecurityContainer.switchIsLocalEnabled() }}
+                className="custom-control-input"
+                id="isLocalEnabled"
+                checked={isLocalEnabled}
+                onChange={() => adminGeneralSecurityContainer.switchIsLocalEnabled()}
                 disabled={adminLocalSecurityContainer.state.useOnlyEnvVars}
               />
-              <label htmlFor="isLocalEnabled">
+              <label className="custom-control-label" htmlFor="isLocalEnabled">
                 {t('security_setting.Local.enable_local')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('local') && isLocalEnabled)
-            && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -101,74 +99,63 @@ class LocalSecuritySetting extends React.Component {
 
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{t('Register limitation')}</strong>
-              <div className="col-xs-9 text-left">
-                <div className="my-0 btn-group">
-                  <div className="dropdown">
-                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      {registrationMode === 'Open' && <span className="pull-left">{t('security_setting.registration_mode.open')}</span>}
-                      {registrationMode === 'Restricted' && <span className="pull-left">{t('security_setting.registration_mode.restricted')}</span>}
-                      {registrationMode === 'Closed' && <span className="pull-left">{t('security_setting.registration_mode.closed')}</span>}
-                      <span className="bs-caret pull-right">
-                        <span className="caret" />
-                      </span>
-                    </button>
-                    {/* TODO adjust dropdown after BS4 */}
-                    <ul className="dropdown-menu" role="menu">
-                      <li
-                        key="Open"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Open') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.open')}</a>
-                      </li>
-                      <li
-                        key="Restricted"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Restricted') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.restricted')}</a>
-                      </li>
-                      <li
-                        key="Closed"
-                        role="presentation"
-                        type="button"
-                        onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Closed') }}
-                      >
-                        <a role="menuitem">{t('security_setting.registration_mode.closed')}</a>
-                      </li>
-                    </ul>
+            <div className="row">
+              <div className="col-3 text-right py-2">
+                <strong>{t('Register limitation')}</strong>
+              </div>
+              <div className="col-6">
+                <div className="dropdown">
+                  <button
+                    className="btn btn-light dropdown-toggle"
+                    type="button"
+                    id="dropdownMenuButton"
+                    data-toggle="dropdown"
+                    aria-haspopup="true"
+                    aria-expanded="true"
+                  >
+                    {registrationMode === 'Open' && t('security_setting.registration_mode.open')}
+                    {registrationMode === 'Restricted' && t('security_setting.registration_mode.restricted')}
+                    {registrationMode === 'Closed' && t('security_setting.registration_mode.closed')}
+                  </button>
+                  <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Open') }}>
+                      {t('security_setting.registration_mode.open')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Restricted') }}>
+                      {t('security_setting.registration_mode.restricted')}
+                    </a>
+                    <a className="dropdown-item" onClick={() => { adminLocalSecurityContainer.changeRegistrationMode('Closed') }}>
+                      {t('security_setting.registration_mode.closed')}
+                    </a>
                   </div>
-                  <p className="help-block">
-                    {t('security_setting.Register limitation desc')}
-                  </p>
                 </div>
+
+                <p className="help-block small">
+                  {t('security_setting.Register limitation desc')}
+                </p>
               </div>
             </div>
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('The whitelist of registration permission E-mail address') }} />
-              <div className="col-xs-6">
-                <div>
-                  <textarea
-                    className="form-control"
-                    type="textarea"
-                    name="registrationWhiteList"
-                    defaultValue={adminLocalSecurityContainer.state.registrationWhiteList.join('\n')}
-                    onChange={e => adminLocalSecurityContainer.changeRegistrationWhiteList(e.target.value)}
-                  />
-                  <p className="help-block small">{t('security_setting.restrict_emails')}<br />{t('security_setting.for_instance')}
-                    <code>@growi.org</code>{t('security_setting.only_those')}<br />
-                    {t('security_setting.insert_single')}
-                  </p>
-                </div>
+            <div className="row">
+              <div className="col-3 text-right">
+                <strong dangerouslySetInnerHTML={{ __html: t('The whitelist of registration permission E-mail address') }} />
+              </div>
+              <div className="col-6">
+                <textarea
+                  className="form-control"
+                  type="textarea"
+                  name="registrationWhiteList"
+                  defaultValue={adminLocalSecurityContainer.state.registrationWhiteList.join('\n')}
+                  onChange={e => adminLocalSecurityContainer.changeRegistrationWhiteList(e.target.value)}
+                />
+                <p className="help-block small">{t('security_setting.restrict_emails')}<br />{t('security_setting.for_instance')}
+                  <code>@growi.org</code>{t('security_setting.only_those')}<br />
+                  {t('security_setting.insert_single')}
+                </p>
               </div>
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-6">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 34 - 32
src/client/js/components/Admin/Security/OidcSecuritySetting.jsx

@@ -64,29 +64,27 @@ class OidcSecurityManagement extends React.Component {
         </h2>
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.OIDC.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isOidcEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isOidcEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsOidcEnabled() }}
               />
-              <label htmlFor="isOidcEnabled">
+              <label className="custom-control-label" htmlFor="isOidcEnabled">
                 {t('security_setting.OAuth.enable_oidc')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('oidc') && isOidcEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -112,8 +110,8 @@ class OidcSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="oidcProviderName" className="col-xs-3 text-right">{t('security_setting.providerName')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcProviderName" className="col-3 text-right py-2">{t('security_setting.providerName')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -125,8 +123,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcIssuerHost" className="col-xs-3 text-right">{t('security_setting.issuerHost')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcIssuerHost" className="col-3 text-right py-2">{t('security_setting.issuerHost')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -141,8 +139,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcClientId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -157,8 +155,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcClientSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -177,8 +175,8 @@ class OidcSecurityManagement extends React.Component {
             </h3>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapId" className="col-xs-3 text-right">Identifier</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapId" className="col-3 text-right py-2">Identifier</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -193,8 +191,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapUserName" className="col-xs-3 text-right">{t('username')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapUserName" className="col-3 text-right py-2">{t('username')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -209,8 +207,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapName" className="col-xs-3 text-right">{t('Name')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapName" className="col-3 text-right py-2">{t('Name')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -225,8 +223,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="oidcAttrMapEmail" className="col-xs-3 text-right">{t('Email')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="oidcAttrMapEmail" className="col-3 text-right py-2">{t('Email')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -241,8 +239,8 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-              <div className="col-xs-6">
+              <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -263,15 +261,17 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-3">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByUserName-oidc"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminOidcSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
                     onChange={() => { adminOidcSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserName-oidc"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
                   />
@@ -283,15 +283,17 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByEmail-oidc"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
                     onChange={() => { adminOidcSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByEmail-oidc"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -303,7 +305,7 @@ class OidcSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 18 - 16
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -90,30 +90,28 @@ class SamlSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.SAML.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isSamlEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isSamlEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsSamlEnabled() }}
                 disabled={adminSamlSecurityContainer.state.useOnlyEnvVars}
               />
-              <label htmlFor="isSamlEnabled">
+              <label className="custom-control-label" htmlFor="isSamlEnabled">
                 {t('security_setting.SAML.enable_saml')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isSamlEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -223,7 +221,7 @@ class SamlSecurityManagement extends React.Component {
                       defaultValue={adminSamlSecurityContainer.state.samlCert}
                       onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                     />
-                    <p className="help-block">
+                    <p>
                       <small>
                         {t('security_setting.SAML.cert_detail')}
                       </small>
@@ -231,7 +229,7 @@ class SamlSecurityManagement extends React.Component {
                     <div>
                       <small>
                         e.g.
-                        <pre>{`-----BEGIN CERTIFICATE-----
+                        <pre className="well card">{`-----BEGIN CERTIFICATE-----
 MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
 UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
 ...
@@ -423,15 +421,17 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </h3>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByUserName-SAML"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminSamlSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminSamlSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserName-SAML"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
                   />
@@ -443,15 +443,17 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6 text-left">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByEmail-SAML"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminSamlSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
                     onChange={() => { adminSamlSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByEmail-SAML"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -515,7 +517,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
             </table>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 121 - 62
src/client/js/components/Admin/Security/SecurityManagement.jsx

@@ -1,6 +1,9 @@
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import {
+  TabContent, TabPane, Nav, NavItem, NavLink,
+} from 'reactstrap';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 
@@ -18,13 +21,27 @@ import FacebookSecuritySetting from './FacebookSecuritySetting';
 
 class SecurityManagement extends React.Component {
 
-  constructor(props) {
+  constructor() {
     super();
 
+    this.state = {
+      activeTab: 'passport-local',
+      // Prevent unnecessary rendering
+      activeComponents: new Set(['passport-local']),
+    };
+
+    this.toggleActiveTab = this.toggleActiveTab.bind(this);
+  }
+
+  toggleActiveTab(activeTab) {
+    this.setState({
+      activeTab, activeComponents: this.state.activeComponents.add(activeTab),
+    });
   }
 
   render() {
     const { t } = this.props;
+    const { activeTab, activeComponents } = this.state;
     return (
       <Fragment>
         <div>
@@ -41,69 +58,111 @@ class SecurityManagement extends React.Component {
           </div>
         </div>
 
-        {/* TODO GW-226 adapt BS4 */}
         <div className="auth-mechanism-configurations m-t-10">
           <h2 className="border-bottom">{t('security_setting.Authentication mechanism settings')}</h2>
-          <div className="passport-settings">
-            <ul className="nav nav-tabs" role="tablist">
-              <li className="active">
-                <a href="#passport-local" data-toggle="tab" role="tab"><i className="fa fa-users"></i> ID/Pass</a>
-              </li>
-              <li>
-                <a href="#passport-ldap" data-toggle="tab" role="tab"><i className="fa fa-sitemap"></i> LDAP</a>
-              </li>
-              <li>
-                <a href="#passport-saml" data-toggle="tab" role="tab"><i className="fa fa-key"></i> SAML</a>
-              </li>
-              <li>
-                <a href="#passport-oidc" data-toggle="tab" role="tab"><i className="fa fa-openid"></i> OIDC</a>
-              </li>
-              <li>
-                <a href="#passport-basic" data-toggle="tab" role="tab"><i className="fa fa-lock"></i> Basic</a>
-              </li>
-              <li>
-                <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i className="fa fa-google"></i> Google</a>
-              </li>
-              <li>
-                <a href="#passport-github" data-toggle="tab" role="tab"><i className="fa fa-github"></i> GitHub</a>
-              </li>
-              <li>
-                <a href="#passport-twitter" data-toggle="tab" role="tab"><i className="fa fa-twitter"></i> Twitter</a>
-              </li>
-              <li className="tbd">
-                <a href="#passport-facebook" data-toggle="tab" role="tab"><i className="fa fa-facebook"></i> (TBD) Facebook</a>
-              </li>
-            </ul>
-            <div className="tab-content p-t-10">
-              <div id="passport-local" className="tab-pane active" role="tabpanel">
-                <LocalSecuritySetting />
-              </div>
-              <div id="passport-ldap" className="tab-pane" role="tabpanel">
-                <LdapSecuritySetting />
-              </div>
-              <div id="passport-saml" className="tab-pane" role="tabpanel">
-                <SamlSecuritySetting />
-              </div>
-              <div id="passport-oidc" className="tab-pane" role="tabpanel">
-                <OidcSecuritySetting />
-              </div>
-              <div id="passport-basic" className="tab-pane" role="tabpanel">
-                <BasicSecuritySetting />
-              </div>
-              <div id="passport-google-oauth" className="tab-pane" role="tabpanel">
-                <GoogleSecuritySetting />
-              </div>
-              <div id="passport-github" className="tab-pane" role="tabpanel">
-                <GitHubSecuritySetting />
-              </div>
-              <div id="passport-twitter" className="tab-pane" role="tabpanel">
-                <TwitterSecuritySetting />
-              </div>
-              <div id="passport-facebook" className="tab-pane" role="tabpanel">
-                <FacebookSecuritySetting />
-              </div>
-            </div>
-          </div>
+          <Nav tabs>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-local' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-local') }}
+              >
+                <i className="fa fa-users" /> ID/Pass
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-ldap' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-ldap') }}
+              >
+                <i className="fa fa-sitemap" /> LDAP
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-saml' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-saml') }}
+              >
+                <i className="fa fa-key" /> SAML
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-oidc' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-oidc') }}
+              >
+                <i className="fa fa-openid" /> OIDC
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-basic' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-basic') }}
+              >
+                <i className="fa fa-lock" /> BASIC
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-google' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-google') }}
+              >
+                <i className="fa fa-google" /> Google
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-github' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-github') }}
+              >
+                <i className="fa fa-github" /> GitHub
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-twitter' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-twitter') }}
+              >
+                <i className="fa fa-twitter" /> Twitter
+              </NavLink>
+            </NavItem>
+            <NavItem>
+              <NavLink
+                className={`${activeTab === 'passport-facebook' && 'active'} `}
+                onClick={() => { this.toggleActiveTab('passport-facebook') }}
+              >
+                <i className="fa fa-facebook" /> (TBD) Facebook
+              </NavLink>
+            </NavItem>
+          </Nav>
+          <TabContent activeTab={activeTab} className="mt-2">
+            <TabPane tabId="passport-local">
+              {activeComponents.has('passport-local') && <LocalSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-ldap">
+              {activeComponents.has('passport-ldap') && <LdapSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-saml">
+              {activeComponents.has('passport-saml') && <SamlSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-oidc">
+              {activeComponents.has('passport-oidc') && <OidcSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-basic">
+              {activeComponents.has('passport-basic') && <BasicSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-google">
+              {activeComponents.has('passport-google') && <GoogleSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-github">
+              {activeComponents.has('passport-github') && <GitHubSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-twitter">
+              {activeComponents.has('passport-twitter') && <TwitterSecuritySetting />}
+            </TabPane>
+            <TabPane tabId="passport-facebook">
+              {activeComponents.has('passport-facebook') && <FacebookSecuritySetting />}
+            </TabPane>
+          </TabContent>
         </div>
       </Fragment>
     );

+ 114 - 143
src/client/js/components/Admin/Security/SecuritySetting.jsx

@@ -49,166 +49,137 @@ class SecuritySetting extends React.Component {
 
     return (
       <React.Fragment>
-        <fieldset>
-          <h2 className="alert-anchor border-bottom">
-            {t('security_settings')}
-          </h2>
-          {this.state.retrieveError != null && (
-            <div className="alert alert-danger">
-              <p>{t('Error occurred')} : {this.state.retrieveError}</p>
-            </div>
+        <h2 className="alert-anchor border-bottom">
+          {t('security_settings')}
+        </h2>
+        {this.state.retrieveError != null && (
+        <div className="alert alert-danger">
+          <p>{t('Error occurred')} : {this.state.retrieveError}</p>
+        </div>
           )}
-          <div className="row">
-            <strong className="col-xs-3 text-right"> {t('security_setting.Guest Users Access')} </strong>
-            <div className="col-xs-9 text-left">
-              <div className="my-0 btn-group">
-                <div className="dropdown">
-                  <button
-                    className={`btn btn-default dropdown-toggle w-100 ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
-                    type="button"
-                    data-toggle="dropdown"
-                    aria-haspopup="true"
-                    aria-expanded="false"
-                  >
-                    <span className="pull-left">
-                      {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
-                      {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
-                    </span>
-                    <span className="bs-caret pull-right">
-                      <span className="caret" />
-                    </span>
-                  </button>
-                  {/* TODO adjust dropdown after BS4 */}
-                  <ul className="dropdown-menu" role="menu">
-                    <li
-                      key="Deny"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Deny') }}
-                    >
-                      <a role="menuitem">{t('security_setting.guest_mode.deny')}</a>
-                    </li>
-                    <li
-                      key="Readonly"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Readonly') }}
-                    >
-                      <a role="menuitem">{t('security_setting.guest_mode.readonly')}</a>
-                    </li>
-                  </ul>
-                </div>
+        <div className="row mb-5">
+          <div className="col-3 text-right py-2">
+            <strong>{t('security_setting.Guest Users Access')}</strong>
+          </div>
+          <div className="col-6">
+            <div className="dropdown">
+              <button
+                className={`btn btn-light dropdown-toggle ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
+                {currentRestrictGuestMode === 'Readonly' && t('security_setting.guest_mode.readonly')}
+              </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Deny') }}>
+                  {t('security_setting.guest_mode.deny')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changeRestrictGuestMode('Readonly') }}>
+                  {t('security_setting.guest_mode.readonly')}
+                </a>
               </div>
             </div>
           </div>
-          {adminGeneralSecurityContainer.isWikiModeForced && (
-            <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <p className="alert alert-warning mt-2 text-left">
-                  <i className="icon-exclamation icon-fw">
-                  </i><b>FIXED</b><br />
-                  <b
-                    dangerouslySetInnerHTML={{
+        </div>
+        {adminGeneralSecurityContainer.isWikiModeForced && (
+        <div className="row mb-5">
+          <div className="col-xs-offset-3 col-xs-6 text-left">
+            <p className="alert alert-warning mt-2 text-left">
+              <i className="icon-exclamation icon-fw">
+              </i><b>FIXED</b><br />
+              <b
+                dangerouslySetInnerHTML={{
                     __html: t('security_setting.Fixed by env var',
                     { forcewikimode: 'FORCE_WIKI_MODE', wikimode: adminGeneralSecurityContainer.state.wikiMode }),
                     }}
-                  />
-                </p>
-              </div>
-            </div>
+              />
+            </p>
+          </div>
+        </div>
           )}
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
-            <div className="col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
-                <input
-                  id="isShowRestrictedByOwner"
-                  type="checkbox"
-                  checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
-                  onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
-                />
-                <label htmlFor="isShowRestrictedByOwner">
-                  {t('security_setting.page_listing_1_desc')}
-                </label>
-              </div>
+        <div className="row mb-5">
+          <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
+          <div className="col-6">
+            <div className="custom-control custom-switch checkbox-success">
+              <input
+                type="checkbox"
+                className="custom-control-input"
+                id="isShowRestrictedByOwner"
+                checked={adminGeneralSecurityContainer.state.isShowRestrictedByOwner}
+                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByOwner() }}
+              />
+              <label className="custom-control-label" htmlFor="isShowRestrictedByOwner">
+                {t('security_setting.page_listing_1_desc')}
+              </label>
             </div>
           </div>
-
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
-            <div className="col-xs-6 text-left">
-              <div className="checkbox checkbox-success">
-                <input
-                  id="isShowRestrictedByGroup"
-                  type="checkbox"
-                  checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
-                  onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
-                />
-                <label htmlFor="isShowRestrictedByGroup">
-                  {t('security_setting.page_listing_2_desc')}
-                </label>
-              </div>
+        </div>
+
+        <div className="row mb-5">
+          <strong className="col-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
+          <div className="col-6">
+            <div className="custom-control custom-switch checkbox-success">
+              <input
+                type="checkbox"
+                className="custom-control-input"
+                id="isShowRestrictedByGroup"
+                checked={adminGeneralSecurityContainer.state.isShowRestrictedByGroup}
+                onChange={() => { adminGeneralSecurityContainer.switchIsShowRestrictedByGroup() }}
+              />
+              <label className="custom-control-label" htmlFor="isShowRestrictedByGroup">
+                {t('security_setting.page_listing_2_desc')}
+              </label>
             </div>
           </div>
+        </div>
 
-          <div className="row mb-5">
-            <strong className="col-xs-3 text-right"> {t('security_setting.complete_deletion')} </strong>
-            <div className="col-xs-9 text-left">
-              <div className="my-0 btn-group">
-                <div className="dropdown">
-                  <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span className="pull-left">
-                      {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
-                      {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
-                      {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
-                        && t('security_setting.admin_and_author')}
-                    </span>
-                    <span className="bs-caret pull-right">
-                      <span className="caret" />
-                    </span>
-                  </button>
-                  {/* TODO adjust dropdown after BS4 */}
-                  <ul className="dropdown-menu" role="menu">
-                    <li
-                      key="anyone"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}
-                    >
-                      <a role="menuitem">{t('security_setting.anyone')}</a>
-                    </li>
-                    <li
-                      key="admin_only"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminOnly') }}
-                    >
-                      <a role="menuitem">{t('security_setting.admin_only')}</a>
-                    </li>
-                    <li
-                      key="admin_and_author"
-                      role="presentation"
-                      type="button"
-                      onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminAndAuthor') }}
-                    >
-                      <a role="menuitem">{t('security_setting.admin_and_author')}</a>
-                    </li>
-                  </ul>
-                </div>
-                <p className="help-block small">
-                  {t('security_setting.complete_deletion_explain')}
-                </p>
-              </div>
-            </div>
+        <div className="row mb-5">
+          <div className="col-3 text-right">
+            <strong>{t('security_setting.complete_deletion')}</strong>
           </div>
-          <div className="row my-3">
-            <div className="col-xs-offset-3 col-xs-5">
-              <button type="submit" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.putSecuritySetting}>
-                {t('Update')}
+          <div className="col-9">
+            <div className="dropdown">
+              <button
+                className="btn btn-light dropdown-toggle"
+                type="button"
+                id="dropdownMenuButton"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="true"
+              >
+                {currentPageCompleteDeletionAuthority === 'anyOne' && t('security_setting.anyone')}
+                {currentPageCompleteDeletionAuthority === 'adminOnly' && t('security_setting.admin_only')}
+                {(currentPageCompleteDeletionAuthority === 'adminAndAuthor' || currentPageCompleteDeletionAuthority == null)
+                    && t('security_setting.admin_and_author')}
               </button>
+              <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('anyOne') }}>
+                  {t('security_setting.anyone')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminOnly') }}>
+                  {t('security_setting.admin_only')}
+                </a>
+                <a className="dropdown-item" onClick={() => { adminGeneralSecurityContainer.changePageCompleteDeletionAuthority('adminAndAuthor') }}>
+                  {t('security_setting.admin_and_author')}
+                </a>
+              </div>
+              <p className="help-block small">
+                {t('security_setting.complete_deletion_explain')}
+              </p>
             </div>
           </div>
-        </fieldset>
+        </div>
+        <div className="row my-3">
+          <div className="offset-3 col-5">
+            <button type="button" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.putSecuritySetting}>
+              {t('Update')}
+            </button>
+          </div>
+        </div>
       </React.Fragment>
     );
   }

+ 16 - 16
src/client/js/components/Admin/Security/TwitterSecuritySetting.jsx

@@ -69,29 +69,27 @@ class TwitterSecurityManagement extends React.Component {
         )}
 
         <div className="row mb-5">
-          <div className="col-xs-3 my-3 text-right">
-            <strong>{t('security_setting.OAuth.Twitter.name')}</strong>
-          </div>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
+          <div className="offset-3 col-6">
+            <div className="custom-control custom-switch checkbox-success">
               <input
                 id="isTwitterEnabled"
+                className="custom-control-input"
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isTwitterEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsTwitterOAuthEnabled() }}
               />
-              <label htmlFor="isTwitterEnabled">
+              <label className="custom-control-label" htmlFor="isTwitterEnabled">
                 {t('security_setting.OAuth.Twitter.enable_twitter')}
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('twitter') && isTwitterEnabled)
-              && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
+              && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
-          <div className="col-xs-6">
+          <label className="col-3 text-right py-2">{t('security_setting.callback_URL')}</label>
+          <div className="col-6">
             <input
               className="form-control"
               type="text"
@@ -118,8 +116,8 @@ class TwitterSecurityManagement extends React.Component {
             <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
 
             <div className="row mb-5">
-              <label htmlFor="TwitterConsumerId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="TwitterConsumerId" className="col-3 text-right py-2">{t('security_setting.clientID')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -134,8 +132,8 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <label htmlFor="TwitterConsumerSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
-              <div className="col-xs-6">
+              <label htmlFor="TwitterConsumerSecret" className="col-3 text-right py-2">{t('security_setting.client_secret')}</label>
+              <div className="col-6">
                 <input
                   className="form-control"
                   type="text"
@@ -150,15 +148,17 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row mb-5">
-              <div className="col-xs-offset-3 col-xs-6 text-left">
-                <div className="checkbox checkbox-success">
+              <div className="offset-3 col-6">
+                <div className="custom-control custom-switch checkbox-success">
                   <input
                     id="bindByUserNameTwitter"
+                    className="custom-control-input"
                     type="checkbox"
                     checked={adminTwitterSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
                     onChange={() => { adminTwitterSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
                   />
                   <label
+                    className="custom-control-label"
                     htmlFor="bindByUserNameTwitter"
                     dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
                   />
@@ -170,7 +170,7 @@ class TwitterSecurityManagement extends React.Component {
             </div>
 
             <div className="row my-3">
-              <div className="col-xs-offset-3 col-xs-5">
+              <div className="offset-3 col-5">
                 <button
                   type="button"
                   className="btn btn-primary"

+ 1 - 1
src/client/js/components/Admin/Users/ExternalAccountTable.jsx

@@ -85,7 +85,7 @@ class ExternalAccountTable extends React.Component {
                         </span>
                       )
                       : (
-                        <span className="label label-warning">
+                        <span className="badge badge-warning">
                           {t('admin:user_management.unset')}
                         </span>
                       )