Browse Source

Merge pull request #1190 from weseek/imprv/reactify-admin-user-groups-detail-3

Imprv/reactify admin user groups detail 3
Sou Mizobuchi 6 years ago
parent
commit
1881c7f1b6

+ 4 - 1
resource/locales/en-US/translation.json

@@ -20,6 +20,7 @@
   "New": "New",
   "Shortcuts": "Shortcuts",
   "eg": "e.g.",
+  "add": "Add",
   "Undo": "Undo",
   "Article": "Article",
   "Page": "Page",
@@ -29,7 +30,6 @@
   "status":"Status",
   "account_id": "Account Id",
 
-
   "Update": "Update",
   "Update Page": "Update Page",
   "Warning": "Warning",
@@ -49,6 +49,7 @@
   "History": "History",
   "Presentation Mode": "Presentation",
 
+  "username": "Username",
   "Created": "Created",
   "Last updated": "Updated",
   "Last_Login": "Last Login",
@@ -704,6 +705,8 @@
   "user_group_management": {
     "group_list": "Group List",
     "back_to_list": "Go Back to Group List",
+    "basic_info": "Basic Info",
+    "user_list": "User List",
     "create_group": "Create New Group",
     "group_example": "e.g. : Group1",
     "created_group": "Group was created",

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

@@ -20,6 +20,7 @@
   "New": "作成",
   "Shortcuts": "ショートカット",
   "eg": "例:",
+  "add": "追加",
   "Undo": "元に戻す",
   "Article": "記事",
   "Page": "ページ",
@@ -48,6 +49,7 @@
   "History": "更新履歴",
   "Presentation Mode": "プレゼンテーション",
 
+  "username": "ユーザー名",
   "Created": "作成日",
   "Last updated": "最終更新",
   "Last_Login": "最終ログイン",
@@ -687,6 +689,8 @@
   "user_group_management": {
     "group_list": "グループ一覧",
     "back_to_list": "グループ一覧に戻る",
+    "basic_info": "基本情報",
+    "user_list": "ユーザー一覧",
     "create_group": "新規グループの作成",
     "group_example": "例: Group1",
     "created_group": "グループを作成しました",

+ 1 - 2
src/client/js/components/Admin/UserGroup/UserGroupPage.jsx

@@ -155,8 +155,7 @@ class UserGroupPage extends React.Component {
           changePage={this.handlePage}
           totalItemsCount={this.state.totalUserGroups}
           pagingLimit={this.state.pagingLimit}
-        >
-        </PaginationWrapper>
+        />
         <UserGroupDeleteModal
           userGroups={this.state.userGroups}
           deleteUserGroup={this.state.selectedUserGroup}

+ 2 - 2
src/client/js/components/Admin/UserGroupDetail/UserGroupDetailPage.jsx

@@ -18,12 +18,12 @@ class UserGroupDetailPage extends React.Component {
       <div>
         <a href="/admin/user-groups" className="btn btn-default">
           <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
-        グループ一覧に戻る
+          {t('user_group_management.back_to_list')}
         </a>
         <div className="m-t-20 form-box">
           <UserGroupEditForm />
         </div>
-        <legend className="m-t-20">{ t('User List') }</legend>
+        <legend className="m-t-20">{ t('user_group_management.user_list') }</legend>
         <UserGroupUserTable />
         <UserGroupUserModal />
         <legend className="m-t-20">{ t('Page') }</legend>

+ 1 - 1
src/client/js/components/Admin/UserGroupDetail/UserGroupEditForm.jsx

@@ -60,7 +60,7 @@ class UserGroupEditForm extends React.Component {
     return (
       <form className="form-horizontal" onSubmit={this.handleSubmit}>
         <fieldset>
-          <legend>基本情報</legend>
+          <legend>{ t('user_group_management.basic_info') }</legend>
           <div className="form-group">
             <label htmlFor="name" className="col-sm-2 control-label">{ t('Name') }</label>
             <div className="col-sm-4">

+ 48 - 1
src/client/js/components/Admin/UserGroupDetail/UserGroupPageList.jsx

@@ -3,21 +3,68 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
 import Page from '../../PageList/Page';
+import PaginationWrapper from '../../PaginationWrapper';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import UserGroupDetailContainer from '../../../services/UserGroupDetailContainer';
+import { toastError } from '../../../util/apiNotification';
 
 class UserGroupPageList extends React.Component {
 
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      currentPages: [],
+      activePage: 1,
+      total: 0,
+      pagingLimit: 10,
+    };
+
+    this.handlePageChange = this.handlePageChange.bind(this);
+  }
+
+  async componentDidMount() {
+    await this.handlePageChange(this.state.activePage);
+  }
+
+  async handlePageChange(pageNum) {
+    const limit = this.state.pagingLimit;
+    const offset = (pageNum - 1) * limit;
+
+    try {
+      const res = await this.props.appContainer.apiv3.get(`/user-groups/${this.props.userGroupDetailContainer.state.userGroup._id}/pages`, {
+        limit,
+        offset,
+      });
+      const { total, pages } = res.data;
+
+      this.setState({
+        total,
+        activePage: pageNum,
+        currentPages: pages,
+      });
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
   render() {
     const { t, userGroupDetailContainer } = this.props;
 
     return (
       <Fragment>
         <ul className="page-list-ul page-list-ul-flat">
-          {userGroupDetailContainer.state.relatedPages.map((page) => { return <Page key={page._id} page={page} /> })}
+          {this.state.currentPages.map((page) => { return <Page key={page._id} page={page} /> })}
         </ul>
         {userGroupDetailContainer.state.relatedPages.length === 0 ? <p>{ t('user_group_management.no_pages') }</p> : null}
+        <PaginationWrapper
+          activePage={this.state.activePage}
+          changePage={this.handlePageChange}
+          totalItemsCount={this.state.total}
+          pagingLimit={this.state.pagingLimit}
+        />
       </Fragment>
     );
   }

+ 2 - 2
src/client/js/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -55,12 +55,12 @@ class UserGroupUserFormByInput extends React.Component {
             type="text"
             name="username"
             className="form-control input-sm"
-            placeholder={t('User Name')}
+            placeholder={t('username')}
             value={this.state.username}
             onChange={this.changeUsername}
           />
         </div>
-        <button type="submit" className="btn btn-sm btn-success" disabled={!this.validateForm()}>{ t('Add') }</button>
+        <button type="submit" className="btn btn-sm btn-success" disabled={!this.validateForm()}>{ t('add') }</button>
       </form>
     );
   }

+ 2 - 2
src/client/js/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -39,11 +39,11 @@ class UserGroupUserTable extends React.Component {
           <tr>
             <th width="100px">#</th>
             <th>
-              { t('User') }
+              { t('username') }
             </th>
             <th>{ t('Name') }</th>
             <th width="100px">{ t('Created') }</th>
-            <th width="160px">{ t('Last Login')}</th>
+            <th width="160px">{ t('Last_Login')}</th>
             <th width="70px"></th>
           </tr>
         </thead>

+ 1 - 1
src/client/js/components/PaginationWrapper.jsx

@@ -42,7 +42,7 @@ class PaginationWrapper extends React.Component {
 
     let paginationStart = activePage - 2;
     let maxViewPageNum = activePage + 2;
-    // pagiNation Number area size = 5 , pageNuber calculate in here
+    // if pagiNation Number area size = 5 , pageNumber is calculated here
     // activePage Position calculate ex. 4 5 [6] 7 8 (Page8 over is Max), 3 4 5 [6] 7 (Page7 is Max)
     if (paginationStart < 1) {
       const diff = 1 - paginationStart;

+ 2 - 0
src/server/models/page.js

@@ -6,6 +6,7 @@
 const debug = require('debug')('growi:models:page');
 const nodePath = require('path');
 const mongoose = require('mongoose');
+const mongoosePaginate = require('mongoose-paginate');
 const uniqueValidator = require('mongoose-unique-validator');
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
@@ -64,6 +65,7 @@ const pageSchema = new mongoose.Schema({
   toObject: { getters: true },
 });
 // apply plugins
+pageSchema.plugin(mongoosePaginate);
 pageSchema.plugin(uniqueValidator);
 
 

+ 27 - 8
src/server/routes/apiv3/user-group.js

@@ -7,11 +7,14 @@ const express = require('express');
 const router = express.Router();
 
 const { body, param, query } = require('express-validator/check');
+const { sanitizeQuery } = require('express-validator/filter');
 
 const validator = {};
 
 const { ObjectId } = require('mongoose').Types;
 
+const { toPagingLimit, toPagingOffset } = require('../../util/express-validator/sanitizer');
+
 /**
  * @swagger
  *  tags:
@@ -519,7 +522,13 @@ module.exports = (crowi) => {
     }
   });
 
-  validator.userGroupRelations = {};
+  validator.pages = {};
+
+  validator.pages.get = [
+    param('id').trim().exists({ checkFalsy: true }),
+    sanitizeQuery('limit').customSanitizer(toPagingLimit),
+    sanitizeQuery('offset').customSanitizer(toPagingOffset),
+  ];
 
   /**
    * @swagger
@@ -550,17 +559,27 @@ module.exports = (crowi) => {
    *                        type: object
    *                      description: page objects
    */
-  router.get('/:id/pages', loginRequired(), adminRequired, async(req, res) => {
+  router.get('/:id/pages', loginRequired(), adminRequired, validator.pages.get, ApiV3FormValidator, async(req, res) => {
     const { id } = req.params;
+    const { limit, offset } = req.query;
 
     try {
-      const userGroup = await UserGroup.findById(id);
-      const pages = await Page
-        .find({ grant: Page.GRANT_USER_GROUP, grantedGroup: { $in: [userGroup] } })
-        .populate('lastUpdateUser', User.USER_PUBLIC_FIELDS)
-        .exec();
+      const { docs, total } = await Page.paginate({
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: { $in: [id] },
+      }, {
+        offset,
+        limit,
+        populate: {
+          path: 'lastUpdateUser',
+          select: User.USER_PUBLIC_FIELDS,
+        },
+      });
+
+      const current = offset / limit + 1;
 
-      return res.apiv3({ pages });
+      // TODO: create a common moudule for paginated response
+      return res.apiv3({ total, current, pages: docs });
     }
     catch (err) {
       const msg = `Error occurred in fetching pages for group: ${id}`;

+ 18 - 0
src/server/util/express-validator/sanitizer.js

@@ -0,0 +1,18 @@
+// custom sanitizers not covered by express-validator
+// https://github.com/validatorjs/validator.js#sanitizers
+
+const sanitizers = {};
+
+sanitizers.toPagingLimit = (_value) => {
+  const value = parseInt(_value);
+  // eslint-disable-next-line no-restricted-globals
+  return !isNaN(value) && isFinite(value) ? value : 20;
+};
+
+sanitizers.toPagingOffset = (_value) => {
+  const value = parseInt(_value);
+  // eslint-disable-next-line no-restricted-globals
+  return !isNaN(value) && isFinite(value) ? value : 0;
+};
+
+module.exports = sanitizers;

+ 2 - 0
src/server/util/express-validator/validator.js

@@ -0,0 +1,2 @@
+// custom validators not covered by express-validator
+// https://github.com/validatorjs/validator.js#validators

+ 0 - 185
src/server/views/admin/user-group-detail.html

@@ -12,198 +12,15 @@
 
 {% block content_main %}
 <div class="content-main">
-  {% set smessage = req.flash('successMessage') %}
-  {% if smessage.length %}
-  <div class="alert alert-success">
-    {{ smessage }}
-  </div>
-  {% endif %}
-
-  {% set emessage = req.flash('errorMessage') %}
-  {% if emessage.length %}
-  <div class="alert alert-danger">
-    {{ emessage }}
-  </div>
-  {% endif %}
-
   <div class="row">
     <div class="col-md-3">
       {% include './widget/menu.html' with {current: 'user-group'} %}
     </div>
-
     <div
       id="admin-user-group-detail"
       class="col-md-9"
       data-user-group="{{ userGroup|json }}"
     >
-      <!-- そのまま start -->
-      <a href="/admin/user-groups" class="btn btn-default">
-        <i class="icon-fw ti-arrow-left" aria-hidden="true"></i>
-        {{ t('user_group_management.back_to_list') }}
-      </a>
-      <!-- そのまま end -->
-
-      <!-- UserGroupUserModal start -->
-      <div class="modal fade" id="admin-add-user-group-relation-modal">
-        <div class="modal-dialog">
-          <div class="modal-content">
-            <div class="modal-header">
-              <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-              <h4 class="modal-title">
-                {{ t('user_group_management.add_user') }}
-              </h4>
-            </div>
-
-            <div class="modal-body">
-              <p>
-                <strong>{{ t('Method') }}1.</strong> {{ t('user_group_management.how_to_add1') }}
-              </p>
-              <form class="form-inline" role="form" action="/admin/user-group-relation/create" method="post">
-                <div class="form-group">
-                  <input type="text" name="user_name" class="form-control input-sm" id="inputRelatedUserName" placeholder="{{ t('User Name')}}">
-                </div>
-                <input type="hidden" name="user_group_id" value="{{userGroup.id}}">
-                <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" class="btn btn-sm btn-success">{{ t('Add') }}</button>
-              </form>
-
-              {% if 0 < notRelatedusers.length %}
-              <hr>
-              <p>
-                <strong>{{ t('Method') }}2.</strong> {{ t('user_group_management.how_to_add2') }}
-              </p>
-
-              <ul class="list-inline">
-                {% for sUser in notRelatedusers %}
-                <li>
-                  <form role="form" action="/admin/user-group-relation/create" method="post">
-                    <!-- <input type="hidden" name="user_name" value="{{sUser.username}}"> -->
-                    <input type="hidden" name="user_group_id" value="{{userGroup.id}}">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" name="user_name" value="{{sUser.username}}" class="btn btn-xs btn-primary">{{sUser.username}}</button>
-                  </form>
-                </li>
-                {% endfor %}
-              </ul>
-              {% endif %}
-
-            </div>
-
-          </div>
-          <!-- /.modal-content -->
-        </div>
-        <!-- /.modal-dialog -->
-      </div>
-      <!-- UserGroupUserModal end -->
-
-      <!-- UserGroupEditForm start -->
-      <div class="m-t-20 form-box">
-        <form action="/admin/user-group/{{userGroup.id}}/update" method="post" class="form-horizontal" role="form">
-          <fieldset>
-            <legend>{{ t('Basic Settings') }}</legend>
-            <div class="form-group">
-              <label for="name" class="col-sm-2 control-label">{{ t('Name') }}</label>
-              <div class="col-sm-4">
-                <input class="form-control" type="text" name="name" value="{{ userGroup.name }}" required>
-              </div>
-            </div>
-            <div class="form-group">
-              <label class="col-sm-2 control-label">{{ t('Created') }}</label>
-              <div class="col-sm-4">
-                <input class="form-control" type="text" disabled value="{{userGroup.createdAt|datetz('Y-m-d') }}">
-              </div>
-            </div>
-            <div class="form-group">
-              <div class="col-sm-offset-2 col-sm-10">
-                <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
-              </div>
-            </div>
-          </fieldset>
-        </form>
-      </div>
-      <!-- UserGroupEditForm end -->
-
-      <!-- UserGroupUserTable start -->
-      <legend class="m-t-20">{{ t('User List') }}</legend>
-
-      <table class="table table-bordered table-user-list">
-        <thead>
-          <tr>
-            <th width="100px">#</th>
-            <th>
-              {{ t('User') }}
-            </th>
-            <th>{{ t('Name') }}</th>
-            <th width="100px">{{ t('Created') }}</th>
-            <th width="160px">{{ t('Last Login')}}</th>
-            <th width="70px"></th>
-          </tr>
-        </thead>
-        <tbody>
-          {% for sRelation in userGroupRelations %}
-          {% set sUser = sRelation.relatedUser%}
-          <tr>
-            <td>
-              <img src="{{ sRelation.relatedUser|picture }}" class="picture img-circle" />
-            </td>
-            <td>
-              <strong>{{ sRelation.relatedUser.username }}</strong>
-            </td>
-            <td>{{ sRelation.relatedUser.name }}</td>
-            <td>{{ sRelation.relatedUser.createdAt|datetz('Y-m-d') }}</td>
-            <td>
-              {% if sRelation.relatedUser.lastLoginAt %} {{ sRelation.relatedUser.lastLoginAt|datetz('Y-m-d H:i:s') }} {% endif %}
-            </td>
-            <td>
-              <div class="btn-group admin-user-menu">
-                <button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
-                  <i class="icon-settings"></i> <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu" role="menu">
-                  <form id="form_removeFromGroup_{{ loop.index }}" action="/admin/user-group-relation/{{userGroup._id.toString()}}/remove-relation/{{ sRelation._id.toString() }}" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    <a href="javascript:form_removeFromGroup_{{ loop.index }}.submit()">
-                      <i class="icon-fw icon-user-unfollow"></i> {{ t('user_group_management.remove_from_group')}}
-                    </a>
-                  </li>
-                </ul>
-              </div>
-            </td>
-          </tr>
-          {% endfor %}
-
-          {% if 0 < notRelatedusers.length %}
-          <tr>
-            <td></td>
-            <td class="text-center">
-              <button class="btn btn-default" data-target="#admin-add-user-group-relation-modal" data-toggle="modal">
-                <i class="ti-plus"></i>
-              </button>
-            </td>
-            <td></td>
-            <td></td>
-            <td></td>
-            <td></td>
-          </tr>
-          {% endif %}
-        </tbody>
-      </table>
-      <!-- UserGroupUserTable end -->
-
-      <!-- {% include '../widget/pager.html' with {path: "/admin/user-group-detail", pager: pager} %} -->
-
-      <legend class="m-t-20">{{ t('Page') }}</legend>
-
-      <!-- UserGroupPageList start -->
-      <div class="page-list">
-        {% if relatedPages.length == 0 %}<p>{{ t('user_group_management.no_pages') }}</p>{% endif %}
-        {% include '../widget/page_list.html' with { pages: relatedPages } %}
-      </div>
-      <!-- UserGroupPageList end -->
-
     </div>
   </div>
 </div>
@@ -211,5 +28,3 @@
 
 {% block content_footer %}
 {% endblock content_footer %}
-
-

+ 1 - 1
src/server/views/me/external-accounts.html

@@ -143,7 +143,7 @@
                 <div class="clearfix">
                   <button type="button" class="btn btn-info pull-right" onclick="associateLdap()">
                     <i class="fa fa-plus-circle" aria-hidden="true"></i>
-                    {{ t('Add') }}
+                    {{ t('add') }}
                   </button>
                 </div>
               </div>

+ 1 - 1
src/server/views/widget/passport/ldap-association-tester.html

@@ -2,7 +2,7 @@
   <div class="alert-container"></div>
   <fieldset>
     <div class="form-group">
-      <label for="username" class="col-xs-3 control-label">{{ t('Username') }}</label>
+      <label for="username" class="col-xs-3 control-label">{{ t('username') }}</label>
       <div class="col-xs-6">
         <input class="form-control" name="loginForm[username]">
       </div>