mizozobu пре 6 година
родитељ
комит
f98d3664c6

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

@@ -711,7 +711,6 @@
     "created_group": "Group was created",
     "add_user": "Add a user to the created group",
     "deny_create_group": "You can't create a new group with the current settings",
-    "is_loading_data": "Loading data...",
     "choose_action": "Choose an action for private pages",
     "delete_group": "Delete Group",
     "group_name": "Group Name",

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

@@ -711,7 +711,6 @@
     "created_group": "グループを作成しました",
     "add_user": "グループへのユーザー追加",
     "deny_create_group": "現在の設定では新規グループの作成はできません。",
-    "is_loading_data": "データを取得中です...",
     "choose_action": "削除するグループの限定公開ページの処理を選択してください",
     "delete_group": "グループの削除",
     "group_name": "グループ名",

+ 6 - 13
src/client/js/app.js

@@ -44,7 +44,6 @@ import CustomCssEditor from './components/Admin/CustomCssEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
-import GroupDeleteModal from './components/GroupDeleteModal/GroupDeleteModal';
 
 import UserGroupPage from './components/Admin/UserGroup/UserGroupPage';
 
@@ -592,26 +591,20 @@ if (adminRebuildSearchElem != null) {
     adminRebuildSearchElem,
   );
 }
-const adminGrantSelectorElem = document.getElementById('admin-delete-user-group-modal');
-if (adminGrantSelectorElem != null) {
-  ReactDOM.render(
-    <I18nextProvider i18n={i18n}>
-      <GroupDeleteModal
-        crowi={crowi}
-      />
-    </I18nextProvider>,
-    adminGrantSelectorElem,
-  );
-}
 
 const adminUserGroupPageElem = document.getElementById('admin-user-group-page');
 if (adminUserGroupPageElem != null) {
-  // get props
+  const userGroups = JSON.parse(adminUserGroupPageElem.getAttribute('data-user-groups'));
+  const userGroupRelations = JSON.parse(adminUserGroupPageElem.getAttribute('data-user-group-relations'));
+  const isAclEnabled = adminUserGroupPageElem.getAttribute('data-isAclEnabled') === 'true';
 
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
       <UserGroupPage
         crowi={crowi}
+        userGroups={userGroups}
+        userGroupRelations={userGroupRelations}
+        isAclEnabled={isAclEnabled}
       />
     </I18nextProvider>,
     adminUserGroupPageElem,

+ 100 - 0
src/client/js/components/Admin/UserGroup/UserGroupCreateForm.jsx

@@ -0,0 +1,100 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+class UserGroupCreateForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      name: '',
+    };
+
+    this.handleInputChange = this.handleInputChange.bind(this);
+    this.validateForm = this.validateForm.bind(this);
+  }
+
+  handleInputChange(event) {
+    const target = event.target;
+    const value = target.type === 'checkbox' ? target.checked : target.value;
+    const name = target.name;
+
+    this.setState({
+      [name]: value,
+    });
+  }
+
+  async handleSubmit(e) {
+    // e.preventDefault();
+
+    // try {
+    //   const res = await this.props.crowi.apiGet('/bookmarks.get', { page_id: this.props.pageId });
+
+    //   if (res.ok) {
+    //     groups = res.userGroups;
+
+    //     this.props.addGroup();
+    //   }
+    //   else {
+    //     throw new Error('Unable to create a group');
+    //   }
+    // }
+    // catch (err) {
+    //   this.handleError(err);
+    // }
+  }
+
+  validateForm() {
+    return this.state.name !== '';
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <div>
+        <p>
+          {this.props.isAclEnabled
+            ? (
+              <button type="button" data-toggle="collapse" className="btn btn-default" href="#createGroupForm">
+                { t('user_group_management.create_group') }
+              </button>
+            )
+            : (
+              t('user_group_management.deny_create_group')
+            )
+          }
+        </p>
+        <form role="form" action="/admin/user-group/create" method="post" onSubmit={this.handleSubmit}>
+          <div id="createGroupForm" className="collapse">
+            <div className="form-group">
+              <label htmlFor="name">{ t('user_group_management.group_name') }</label>
+              <textarea
+                id="name"
+                name="name"
+                className="form-control"
+                placeholder={t('user_group_management.group_example')}
+                value={this.state.name}
+                onChange={this.handleInputChange}
+              >
+              </textarea>
+            </div>
+            <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>{ t('Create') }</button>
+          </div>
+          <input type="hidden" name="_csrf" defaultValue={this.props.crowi.csrfToken} />
+        </form>
+      </div>
+    );
+  }
+
+}
+
+UserGroupCreateForm.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  crowi: PropTypes.object.isRequired,
+  isAclEnabled: PropTypes.bool,
+  addGroup: PropTypes.func.isRequired,
+};
+
+export default withTranslation()(UserGroupCreateForm);

+ 195 - 0
src/client/js/components/Admin/UserGroup/UserGroupDeleteModal.jsx

@@ -0,0 +1,195 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import Modal from 'react-bootstrap/es/Modal';
+
+/**
+ * Delete User Group Select component
+ *
+ * @export
+ * @class GrantSelector
+ * @extends {React.Component}
+ */
+class UserGroupDeleteModal extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    const { t } = this.props;
+
+    // actionName master constants
+    this.actionForPages = {
+      public: 'public',
+      delete: 'delete',
+      transfer: 'transfer',
+    };
+
+    this.availableOptions = [
+      {
+        id: 1, actionForPages: this.actionForPages.public, iconClass: 'icon-people', styleClass: '', label: t('user_group_management.publish_pages'),
+      },
+      {
+        id: 2, actionForPages: this.actionForPages.delete, iconClass: 'icon-trash', styleClass: 'text-danger', label: t('user_group_management.delete_pages'),
+      },
+      {
+        id: 3, actionForPages: this.actionForPages.transfer, iconClass: 'icon-options', styleClass: '', label: t('user_group_management.transfer_pages'),
+      },
+    ];
+
+    this.initialState = {
+      actionName: '',
+      transferToUserGroupId: '',
+    };
+
+    this.state = this.initialState;
+
+    // logger
+    this.logger = require('@alias/logger')('growi:GroupDeleteModal:GroupDeleteModal');
+
+    // retrieve xss library from window
+    this.xss = window.xss;
+
+    this.onHide = this.onHide.bind(this);
+    this.getGroupName = this.getGroupName.bind(this);
+    this.changeActionHandler = this.changeActionHandler.bind(this);
+    this.changeGroupHandler = this.changeGroupHandler.bind(this);
+    this.renderPageActionSelector = this.renderPageActionSelector.bind(this);
+    this.renderGroupSelector = this.renderGroupSelector.bind(this);
+    this.validateForm = this.validateForm.bind(this);
+  }
+
+  onHide() {
+    this.setState(this.initialState);
+    this.props.onHide();
+  }
+
+  getGroupName(group) {
+    return this.xss.process(group.name);
+  }
+
+  changeActionHandler(e) {
+    const actionName = e.target.value;
+    this.setState({ actionName });
+  }
+
+  changeGroupHandler(e) {
+    const transferToUserGroupId = e.target.value;
+    this.setState({ transferToUserGroupId });
+  }
+
+  renderPageActionSelector() {
+    const { t } = this.props;
+
+    const optoins = this.availableOptions.map((opt) => {
+      const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="action-name ${opt.styleClass}">${t(opt.label)}</span>`;
+      return <option key={opt.id} value={opt.actionForPages} data-content={dataContent}>{t(opt.label)}</option>;
+    });
+
+    return (
+      <select
+        name="actionName"
+        className="form-control"
+        placeholder="select"
+        value={this.state.actionName}
+        onChange={this.changeActionHandler}
+      >
+        <option value="" disabled>{t('user_group_management.choose_action')}</option>
+        {optoins}
+      </select>
+    );
+  }
+
+  renderGroupSelector() {
+    const { t } = this.props;
+
+    const groups = this.props.userGroups.filter((group) => {
+      return group._id !== this.props.deleteUserGroup._id;
+    });
+
+    const options = groups.map((group) => {
+      const dataContent = `<i class="icon icon-fw icon-organization"></i> ${this.getGroupName(group)}`;
+      return <option key={group._id} value={group._id} data-content={dataContent}>{this.getGroupName(group)}</option>;
+    });
+
+    const defaultOptionText = groups.length === 0 ? t('user_group_management.no_groups') : t('user_group_management.select_group');
+
+    return (
+      <select
+        name="transferToUserGroupId"
+        className={`form-control ${this.state.actionName === this.actionForPages.transfer ? '' : 'd-none'}`}
+        value={this.state.transferToUserGroupId}
+        onChange={this.changeGroupHandler}
+      >
+        <option value="" disabled>{defaultOptionText}</option>
+        {options}
+      </select>
+    );
+  }
+
+  validateForm() {
+    let isValid = true;
+
+    if (this.state.actionName === '') {
+      isValid = false;
+    }
+    else if (this.state.actionName === this.actionForPages.transfer) {
+      isValid = this.state.transferToUserGroupId !== '';
+    }
+
+    return isValid;
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <Modal show={this.props.isShow} onHide={this.onHide}>
+        <Modal.Header className="modal-header bg-danger" closeButton>
+          <Modal.Title>
+            <i className="icon icon-fire"></i> {t('user_group_management.delete_group')}
+          </Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <div>
+            <span className="font-weight-bold">{t('user_group_management.group_name')}</span> : &quot;{this.props.deleteUserGroup.name}&quot;
+          </div>
+          <div className="text-danger mt-5">
+            {t('user_group_management.group_and_pages_not_retrievable')}
+          </div>
+        </Modal.Body>
+        <Modal.Footer>
+          <form action="/admin/user-group.remove" method="post" id="admin-user-groups-delete" className="d-flex justify-content-between">
+            <div className="d-flex">
+              {this.renderPageActionSelector()}
+              {this.renderGroupSelector()}
+            </div>
+            {/* keep these two hidden inputs controlled */}
+            <input type="hidden" id="deleteGroupId" name="deleteGroupId" value={this.props.deleteUserGroup._id || ''} onChange={() => {}} />
+            <input type="hidden" name="_csrf" value={this.props.crowi.csrfToken} onChange={() => {}} />
+            <button type="submit" value="" className="btn btn-sm btn-danger" disabled={!this.validateForm()}>
+              <i className="icon icon-fire"></i> {t('Delete')}
+            </button>
+          </form>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+
+}
+
+UserGroupDeleteModal.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  crowi: PropTypes.object.isRequired,
+  userGroups: PropTypes.arrayOf(PropTypes.object).isRequired,
+  deleteUserGroup: PropTypes.object,
+  isShow: PropTypes.bool.isRequired,
+  onShow: PropTypes.func.isRequired,
+  onHide: PropTypes.func.isRequired,
+};
+
+UserGroupDeleteModal.defaultProps = {
+  deleteUserGroup: {},
+};
+
+export default withTranslation()(UserGroupDeleteModal);

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

@@ -1,18 +1,108 @@
-import React from 'react';
+import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 
+import * as toastr from 'toastr';
+
+import UserGroupTable from './UserGroupTable';
+import UserGroupCreateForm from './UserGroupCreateForm';
+import UserGroupDeleteModal from './UserGroupDeleteModal';
+
 class UserGroupPage extends React.Component {
 
   constructor(props) {
     super(props);
 
     this.state = {
+      userGroups: props.userGroups,
+      selectedUserGroup: undefined,
+      isDeleteModalShow: false,
     };
+
+    this.showDeleteModal = this.showDeleteModal.bind(this);
+    this.hideDeleteModal = this.hideDeleteModal.bind(this);
+    this.addGroup = this.addGroup.bind(this);
+  }
+
+  async showDeleteModal(group) {
+    await this.syncUserGroupState();
+
+    this.setState({
+      selectedUserGroup: group,
+      isDeleteModalShow: true,
+    });
+  }
+
+  hideDeleteModal() {
+    this.setState({
+      selectedUserGroup: undefined,
+      isDeleteModalShow: false,
+    });
+  }
+
+  addGroup(newUserGroup) {
+    this.setState((prevState) => {
+      return {
+        userGroups: [...prevState.userGroups, newUserGroup],
+        isDeleteModalShow: false,
+      };
+    });
+  }
+
+  async syncUserGroupState() {
+    let userGroups = [];
+
+    try {
+      const res = await this.props.crowi.apiGet('/admin/user-groups');
+      if (res.ok) {
+        userGroups = res.userGroups;
+      }
+      else {
+        throw new Error('Unable to fetch groups from server');
+      }
+    }
+    catch (err) {
+      this.handleError(err);
+    }
+
+    this.setState({ userGroups });
+  }
+
+  handleError(err) {
+    this.logger.error(err);
+    toastr.error(err, 'Error occured', {
+      closeButton: true,
+      progressBar: true,
+      newestOnTop: false,
+      showDuration: '100',
+      hideDuration: '100',
+      timeOut: '3000',
+    });
   }
 
   render() {
     return (
-      'asdf'
+      <Fragment>
+        <UserGroupCreateForm
+          crowi={this.props.crowi}
+          isAclEnabled={this.props.isAclEnabled}
+          addGroup={this.addGroup}
+        />
+        <UserGroupTable
+          crowi={this.props.crowi}
+          userGroups={this.state.userGroups}
+          userGroupRelations={this.props.userGroupRelations}
+          isAclEnabled={this.props.isAclEnabled}
+          onDelete={this.showDeleteModal}
+        />
+        <UserGroupDeleteModal
+          crowi={this.props.crowi}
+          userGroups={this.state.userGroups}
+          deleteUserGroup={this.state.selectedUserGroup}
+          isShow={this.state.isDeleteModalShow}
+          onShow={this.showDeleteModal}
+          onHide={this.hideDeleteModal}
+        />
+      </Fragment>
     );
   }
 
@@ -20,6 +110,9 @@ class UserGroupPage extends React.Component {
 
 UserGroupPage.propTypes = {
   crowi: PropTypes.object.isRequired,
+  userGroups: PropTypes.arrayOf(PropTypes.object).isRequired,
+  userGroupRelations: PropTypes.object.isRequired,
+  isAclEnabled: PropTypes.bool,
 };
 
 export default UserGroupPage;

+ 112 - 0
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -0,0 +1,112 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+class UserGroupTable extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onDelete = this.onDelete.bind(this);
+  }
+
+  onDelete(e) {
+    const { target } = e;
+    const groupId = target.getAttribute('data-user-group-id');
+    const group = this.props.userGroups.find((group) => {
+      return group._id === groupId;
+    });
+
+    this.props.onDelete(group);
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <Fragment>
+        <h2>{t('user_group_management.group_list')}</h2>
+
+        <table className="table table-bordered table-user-list">
+          <thead>
+            <tr>
+              <th>{ t('Name') }</th>
+              <th>{ t('User') }</th>
+              <th width="100px">{ t('Created') }</th>
+              <th width="70px"></th>
+            </tr>
+          </thead>
+          <tbody>
+            {this.props.userGroups.map((group) => {
+              return (
+                <tr key={group._id}>
+                  {this.props.isAclEnabled
+                    ? (
+                      <td><a href={`/admin/user-group-detail/${group._id}`}>{group.name}</a></td>/* preventXSS */
+                    )
+                    : (
+                      <td>{group.name}</td>/* preventXSS */
+                    )
+                  }
+                  <td>
+                    <ul className="list-inline">
+                      {this.props.userGroupRelations[group._id].map((user) => {
+                        return <li key={user._id} className="list-inline-item badge badge-primary">{user.username}</li>;/* preventXSS ?? */
+                      })}
+                    </ul>
+                  </td>
+                  <td>{group.createdAt}</td>{/* formatdate */}
+                  {this.props.isAclEnabled
+                    ? (
+                      <td>
+                        <div className="btn-group admin-group-menu">
+                          <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
+                            <i className="icon-settings"></i> <span className="caret"></span>
+                          </button>
+                          <ul className="dropdown-menu" role="menu">
+                            <li>
+                              <a href={`/admin/user-group-detail/${group._id}`}>
+                                <i className="icon-fw icon-note"></i> { t('Edit') }
+                              </a>
+                            </li>
+
+                            <li>
+                              <a
+                                href="#"
+                                data-user-group-id={group._id}
+                                data-user-group-name={group.name}/* encodeHTML */
+                                onClick={this.onDelete}
+                              >
+                                <i className="icon-fw icon-fire text-danger"></i> { t('Delete') }
+                              </a>
+                            </li>
+
+                          </ul>
+                        </div>
+                      </td>
+                    )
+                    : (
+                      <td></td>
+                    )
+                  }
+                </tr>
+              );
+            })}
+          </tbody>
+        </table>
+      </Fragment>
+    );
+  }
+
+}
+
+UserGroupTable.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  crowi: PropTypes.object.isRequired,
+  userGroups: PropTypes.arrayOf(PropTypes.object).isRequired,
+  userGroupRelations: PropTypes.object.isRequired,
+  isAclEnabled: PropTypes.bool,
+  onDelete: PropTypes.func.isRequired,
+};
+
+export default withTranslation()(UserGroupTable);

+ 0 - 260
src/client/js/components/GroupDeleteModal/GroupDeleteModal.jsx

@@ -1,260 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-
-import * as toastr from 'toastr';
-
-/**
- * Delete User Group Select component
- *
- * @export
- * @class GrantSelector
- * @extends {React.Component}
- */
-class GroupDeleteModal extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    const { t } = this.props;
-
-    // actionName master constants
-    this.actionForPages = {
-      public: 'public',
-      delete: 'delete',
-      transfer: 'transfer',
-    };
-
-    this.availableOptions = [
-      {
-        id: 1, actionForPages: this.actionForPages.public, iconClass: 'icon-people', styleClass: '', label: t('user_group_management.publish_pages'),
-      },
-      {
-        id: 2, actionForPages: this.actionForPages.delete, iconClass: 'icon-trash', styleClass: 'text-danger', label: t('user_group_management.delete_pages'),
-      },
-      {
-        id: 3, actionForPages: this.actionForPages.transfer, iconClass: 'icon-options', styleClass: '', label: t('user_group_management.transfer_pages'),
-      },
-    ];
-
-    this.initialState = {
-      deleteGroupId: '',
-      deleteGroupName: '',
-      groups: [],
-      actionName: '',
-      selectedGroupId: '',
-      isFetching: false,
-    };
-
-    this.state = this.initialState;
-
-    // logger
-    this.logger = require('@alias/logger')('growi:GroupDeleteModal:GroupDeleteModal');
-
-    // retrieve xss library from window
-    this.xss = window.xss;
-
-    this.getGroupName = this.getGroupName.bind(this);
-    this.changeActionHandler = this.changeActionHandler.bind(this);
-    this.changeGroupHandler = this.changeGroupHandler.bind(this);
-    this.renderPageActionSelector = this.renderPageActionSelector.bind(this);
-    this.renderGroupSelector = this.renderGroupSelector.bind(this);
-    this.validateForm = this.validateForm.bind(this);
-  }
-
-  componentDidMount() {
-    // bootstrap and this jQuery opens/hides the modal.
-    // let React handle it in the future.
-    $('#admin-delete-user-group-modal').on('show.bs.modal', async(button) => {
-      this.setState({ isFetching: true });
-
-      const groups = await this.fetchAllGroups();
-
-      const data = $(button.relatedTarget);
-      const deleteGroupId = data.data('user-group-id');
-      const deleteGroupName = data.data('user-group-name');
-
-      this.setState({
-        groups,
-        deleteGroupId,
-        deleteGroupName,
-        isFetching: false,
-      });
-    });
-
-    $('#admin-delete-user-group-modal').on('hide.bs.modal', (button) => {
-      this.setState(this.initialState);
-    });
-  }
-
-  getGroupName(group) {
-    return this.xss.process(group.name);
-  }
-
-  async fetchAllGroups() {
-    let groups = [];
-
-    try {
-      const res = await this.props.crowi.apiGet('/admin/user-groups');
-      if (res.ok) {
-        groups = res.userGroups;
-      }
-      else {
-        throw new Error('Unable to fetch groups from server');
-      }
-    }
-    catch (err) {
-      this.handleError(err);
-    }
-
-    return groups;
-  }
-
-  handleError(err) {
-    this.logger.error(err);
-    toastr.error(err, 'Error occured', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '3000',
-    });
-  }
-
-  changeActionHandler(e) {
-    const actionName = e.target.value;
-    this.setState({ actionName });
-  }
-
-  changeGroupHandler(e) {
-    const selectedGroupId = e.target.value;
-    this.setState({ selectedGroupId });
-  }
-
-  renderPageActionSelector() {
-    const { t } = this.props;
-
-    const optoins = this.availableOptions.map((opt) => {
-      const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="action-name ${opt.styleClass}">${t(opt.label)}</span>`;
-      return <option key={opt.id} value={opt.actionForPages} data-content={dataContent}>{t(opt.label)}</option>;
-    });
-
-    return (
-      <select
-        name="actionName"
-        className="form-control"
-        placeholder="select"
-        value={this.state.actionName}
-        onChange={this.changeActionHandler}
-      >
-        <option value="" disabled>{t('user_group_management.choose_action')}</option>
-        {optoins}
-      </select>
-    );
-  }
-
-  renderGroupSelector() {
-    const { t } = this.props;
-
-    const groups = this.state.groups.filter((group) => {
-      return group._id !== this.state.deleteGroupId;
-    });
-
-    const options = groups.map((group) => {
-      const dataContent = `<i class="icon icon-fw icon-organization"></i> ${this.getGroupName(group)}`;
-      return <option key={group._id} value={group._id} data-content={dataContent}>{this.getGroupName(group)}</option>;
-    });
-
-    const defaultOptionText = groups.length === 0 ? t('user_group_management.no_groups') : t('user_group_management.select_group');
-
-    return (
-      <select
-        name="selectedGroupId"
-        className={`form-control ${this.state.actionName === this.actionForPages.transfer ? '' : 'd-none'}`}
-        value={this.state.selectedGroupId}
-        onChange={this.changeGroupHandler}
-      >
-        <option value="" disabled>{defaultOptionText}</option>
-        {options}
-      </select>
-    );
-  }
-
-  validateForm() {
-    let isValid = true;
-
-    if (this.state.actionName === '') {
-      isValid = false;
-    }
-    else if (this.state.actionName === this.actionForPages.transfer) {
-      isValid = this.state.selectedGroupId !== '';
-    }
-
-    return isValid;
-  }
-
-  render() {
-    const { t } = this.props;
-
-    return (
-      <div className="modal-dialog">
-        <div className="modal-content">
-          <div className="modal-header bg-danger">
-            <button type="button" className="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-            <div className="modal-title">
-              <i className="icon icon-fire"></i> {t('user_group_management.delete_group')}
-            </div>
-          </div>
-
-          <div className="modal-body">
-            <div>
-              <span className="font-weight-bold">{t('user_group_management.group_name')}</span> : &quot;{this.state.deleteGroupName}&quot;
-            </div>
-            {this.state.isFetching
-              ? (
-                <div className="mt-5">
-                  {t('user_group_management.is_loading_data')}
-                </div>
-              )
-              : (
-                <div className="text-danger mt-5">
-                  {t('user_group_management.group_and_pages_not_retrievable')}
-                </div>
-              )
-            }
-          </div>
-
-          {this.state.isFetching
-            ? (
-              null
-            )
-            : (
-              <div className="modal-footer">
-                <form action="/admin/user-group.remove" method="post" id="admin-user-groups-delete" className="d-flex justify-content-between">
-                  <div className="d-flex">
-                    {this.renderPageActionSelector()}
-                    {this.renderGroupSelector()}
-                  </div>
-                  <input type="hidden" id="deleteGroupId" name="deleteGroupId" value={this.state.deleteGroupId} onChange={() => {}} />
-                  <input type="hidden" name="_csrf" defaultValue={this.props.crowi.csrfToken} />
-                  <button type="submit" value="" className="btn btn-sm btn-danger" disabled={!this.validateForm()}>
-                    <i className="icon icon-fire"></i> {t('Delete')}
-                  </button>
-                </form>
-              </div>
-            )
-          }
-        </div>
-      </div>
-    );
-  }
-
-}
-
-GroupDeleteModal.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  crowi: PropTypes.object.isRequired,
-};
-
-export default withTranslation()(GroupDeleteModal);

+ 9 - 2
src/server/routes/admin.js

@@ -667,7 +667,12 @@ module.exports = function(crowi, app) {
           return new Promise((resolve, reject) => {
             UserGroupRelation.findAllRelationForUserGroup(userGroup)
               .then((relations) => {
-                return resolve([userGroup, relations]);
+                return resolve({
+                  id: userGroup._id,
+                  relatedUsers: relations.map((relation) => {
+                    return relation.relatedUser;
+                  }),
+                });
               });
           });
         });
@@ -676,7 +681,9 @@ module.exports = function(crowi, app) {
         return Promise.all(allRelationsPromise);
       })
       .then((relations) => {
-        renderVar.userGroupRelations = new Map(relations);
+        for (const relation of relations) {
+          renderVar.userGroupRelations[relation.id] = relation.relatedUsers;
+        }
         debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations);
         return res.render('admin/user-groups', renderVar);
       })

+ 10 - 148
src/server/views/admin/user-groups.html

@@ -12,158 +12,20 @@
 
 {% block content_main %}
 <div class="content-main">
-
-  <!-- user toastr start -->
-
-  {% 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 %}
-
-  <!-- user toastr end -->
-
   <div class="row">
     <div class="col-md-3">
       {% include './widget/menu.html' with {current: 'user-group'} %}
     </div>
-
-    <div id ="admin-user-group-page" class="col-md-9">
-
-      <!-- to react start -->
-
-      <!-- to UserGruopCreateForm start -->
-
-      <p>
-        {% if isAclEnabled %}
-          <button  data-toggle="collapse" class="btn btn-default" href="#createGroupForm">{{ t('user_group_management.create_group') }}</button>
-        {% else %}
-          {{ t('user_group_management.deny_create_group')}}
-        {% endif %}
-      </p>
-      <form role="form" action="/admin/user-group/create" method="post">
-        <div id="createGroupForm" class="collapse">
-          <div class="form-group">
-            <label for="createGroupForm[userGroupName]">{{ t('user_group_management.group_name') }}</label>
-            <textarea class="form-control" name="createGroupForm[userGroupName]" placeholder="{{t('user_group_management.group_example')}}"></textarea>
-          </div>
-          <button type="submit" class="btn btn-primary">{{ t('Create') }}</button>
-        </div>
-        <input type="hidden" name="_csrf" value="{{ csrf() }}">
-      </form>
-
-      <!-- to UserGruopCreateForm end -->
-
-      {% set createdUserGroup = req.flash('createdUserGroup') %}
-      {% if createdUserGroup.length %}
-      <div class="modal fade in" id="createdGroupModal">
-        <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.created_group') }}</h4>
-            </div>
-
-            <div class="modal-body">
-              <p>
-                {{ t('user_group_management.add_user') }}
-              </p>
-
-              <pre>{{ createdUserGroup.name }}</pre>
-            </div>
-
-          </div><!-- /.modal-content -->
-        </div><!-- /.modal-dialog -->
-      </div><!-- /.modal -->
-      {% endif %}
-
-      <!-- import in react start -->
-
-      <div class="modal fade" id="admin-delete-user-group-modal"></div>
-
-      <!-- import in react end -->
-
-      <!-- to UserGroupTable start -->
-
-      <h2>{{ t('user_group_management.group_list') }}</h2>
-
-      <table class="table table-bordered table-user-list">
-        <thead>
-          <tr>
-            <th>{{ t('Name') }}</th>
-            <th>{{ t('User') }}</th>
-            <th width="100px">{{ t('Created') }}</th>
-            <th width="70px"></th>
-          </tr>
-        </thead>
-        <tbody>
-          {% for sGroup in userGroups %}
-          {% set sGroupDetailPageUrl = '/admin/user-group-detail/' + sGroup._id.toString() %}
-          <tr>
-            {% if isAclEnabled %}
-              <td><a href="{{ sGroupDetailPageUrl }}">{{ sGroup.name | preventXss }}</a></td>
-            {% else %}
-              <td>{{ sGroup.name | preventXss }}</td>
-            {% endif %}
-            <td><ul class="list-inline">
-              {% for relation in userGroupRelations.get(sGroup) %}
-              <li class="list-inline-item badge badge-primary">{{relation.relatedUser.username}}</li>
-              {% endfor %}
-            </ul></td>
-            <td>{{ sGroup.createdAt|date('Y-m-d', sGroup.createdAt.getTimezoneOffset()) }}</td>
-            {% if isAclEnabled %}
-            <td>
-              <div class="btn-group admin-group-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">
-                  <li>
-                    <a href="{{ sGroupDetailPageUrl }}">
-                      <i class="icon-fw icon-note"></i> {{ t('Edit') }}
-                    </a>
-                  </li>
-
-                  <li>
-                    <a href="#"
-                        data-user-group-id="{{ sGroup._id.toString() }}"
-                        data-user-group-name="{{ sGroup.name.toString() | encodeHTML }}"
-                        data-target="#admin-delete-user-group-modal"
-                        data-toggle="modal">
-                      <i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}
-                    </a>
-                  </li>
-
-                </ul>
-              </div>
-            </td>
-            {% else %}
-              <td></td>
-            {% endif %}
-          </tr>
-          {% endfor %}
-        </tbody>
-      </table>
-
-      <!-- to UserGroupTable end -->
-
-      <!-- to Paginator start -->
-
-      {% include '../widget/pager.html' with {path: "/admin/user-groups", pager: pager} %}
-
-      <!-- to Paginator end -->
-
-      <!-- to react end -->
-
+    <div
+      id ="admin-user-group-page"
+      class="col-md-9"
+      data-user-groups="{{ userGroups|json }}"
+      data-user-group-relations="{{ userGroupRelations|json }}"
+      data-isAclEnabled="{{ isAclEnabled }}"
+    >
+      <!-- Reactify Paginator start -->
+      <!-- {% include '../widget/pager.html' with {path: "/admin/user-groups", pager: pager} %} -->
+      <!-- Reactify Paginator end -->
     </div>
   </div>
 </div>