Przeglądaj źródła

Merge pull request #2445 from weseek/feat/remove-all-share-links-in-admin-security

Feat/remove all share links in admin security
itizawa 5 lat temu
rodzic
commit
a26b2f4f90

+ 2 - 0
resource/locales/en_US/translation.json

@@ -59,6 +59,8 @@
   "Last_Login": "Last login",
   "Last_Login": "Last login",
   "Share": "Share",
   "Share": "Share",
   "Share Link": "Share Link",
   "Share Link": "Share Link",
+  "share_link_notice":"remove {{count}} share links",
+  "delete_all_share_links":"Delete all share links",
   "Markdown Link": "Markdown Link",
   "Markdown Link": "Markdown Link",
   "Create/Edit Template": "Create/Edit template page",
   "Create/Edit Template": "Create/Edit template page",
   "Go to this version": "View this version",
   "Go to this version": "View this version",

+ 2 - 0
resource/locales/ja_JP/translation.json

@@ -59,6 +59,8 @@
   "Last_Login": "最終ログイン",
   "Last_Login": "最終ログイン",
   "Share": "共有",
   "Share": "共有",
   "Share Link": "共有用リンク",
   "Share Link": "共有用リンク",
+  "share_link_notice":"{{count}} 件の共有リンクを削除します",
+  "delete_all_share_links":"全ての共有リンクを削除します",
   "Markdown Link": "Markdown形式のリンク",
   "Markdown Link": "Markdown形式のリンク",
   "Create/Edit Template": "テンプレートページの作成/編集",
   "Create/Edit Template": "テンプレートページの作成/編集",
   "Go to this version": "このバージョンを見る",
   "Go to this version": "このバージョンを見る",

+ 4 - 2
resource/locales/zh_CN/translation.json

@@ -53,7 +53,9 @@
 	"Last updated": "上次更新",
 	"Last updated": "上次更新",
 	"Last_Login": "上次登录",
 	"Last_Login": "上次登录",
 	"Share": "分享",
 	"Share": "分享",
-	"Share Link": "分享链接",
+  "Share Link": "分享链接",
+  "share_link_notice":"remove {{count}} share links",
+  "delete_all_share_links":"Delete all share links",
 	"Markdown Link": "Markdown链接",
 	"Markdown Link": "Markdown链接",
 	"Create/Edit Template": "创建/编辑 模板页面",
 	"Create/Edit Template": "创建/编辑 模板页面",
 	"Unportalize": "未启动",
 	"Unportalize": "未启动",
@@ -697,4 +699,4 @@
 		"Registration successful": "注册成功",
 		"Registration successful": "注册成功",
 		"Setup": "安装程序"
 		"Setup": "安装程序"
 	}
 	}
-}
+}

+ 67 - 0
src/client/js/components/Admin/Security/DeleteAllShareLinksModal.jsx

@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+const DeleteAllShareLinksModal = React.memo((props) => {
+  const { t } = props;
+
+  function closeModal() {
+    if (props.onClose == null) {
+      return;
+    }
+
+    props.onClose();
+  }
+
+  function deleteAllLinkHandler() {
+    if (props.onClickDeleteButton == null) {
+      return;
+    }
+
+    props.onClickDeleteButton();
+
+    closeModal();
+  }
+
+  function closeButtonHandler() {
+    closeModal();
+  }
+
+  return (
+    <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
+      <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
+        <span>
+          <i className="icon-fw icon-fire"></i>
+          {t('delete_all_share_links')}
+        </span>
+      </ModalHeader>
+      <ModalBody>
+        { t('share_link_notice', { count: props.count })}
+      </ModalBody>
+      <ModalFooter>
+        <Button onClick={closeButtonHandler}>Cancel</Button>
+        <Button color="danger" onClick={deleteAllLinkHandler}>
+          <i className="icon icon-fire"></i>
+            Delete
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+
+});
+
+DeleteAllShareLinksModal.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func,
+  count: PropTypes.number.isRequired,
+  onClickDeleteButton: PropTypes.func,
+};
+
+export default withTranslation()(DeleteAllShareLinksModal);

+ 68 - 24
src/client/js/components/Admin/Security/ShareLinkSetting.jsx

@@ -3,35 +3,77 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 
-import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
+import AppContainer from '../../../services/AppContainer';
+
+import ShareLinkList from '../../ShareLinkList';
+import DeleteAllShareLinksModal from './DeleteAllShareLinksModal';
 
 
 class ShareLinkSetting extends React.Component {
 class ShareLinkSetting extends React.Component {
 
 
+  constructor() {
+    super();
+
+    this.state = {
+      shareLinks: [],
+      isDeleteConfirmModalShown: false,
+    };
+
+    this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this);
+    this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
+    this.deleteAllLinksButtonHandler = this.deleteAllLinksButtonHandler.bind(this);
+  }
+
+  showDeleteConfirmModal() {
+    this.setState({ isDeleteConfirmModalShown: true });
+  }
+
+  closeDeleteConfirmModal() {
+    this.setState({ isDeleteConfirmModalShown: false });
+  }
+
+  async deleteAllLinksButtonHandler() {
+    const { t, appContainer } = this.props;
+
+    try {
+      const res = await appContainer.apiv3Delete('/share-links/all');
+      const { deletedCount } = res.data;
+      toastSuccess(t('toaster.remove_share_link', { count: deletedCount }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+
+  }
+
+
   render() {
   render() {
     return (
     return (
       <>
       <>
-        <div className="mb-3">
-          <h2 className="alert-anchor border-bottom">Shared Link List</h2>
-        </div>
-        <button className="pull-right btn btn-danger" type="button">Delete all links</button>
-
-        <div className="table-responsive">
-          <table className="table table-bordered">
-            <thead>
-              <tr>
-                <th>Link</th>
-                <th>PagePath</th>
-                <th>Expiration</th>
-                <th>Description</th>
-                <th>Order</th>
-              </tr>
-            </thead>
-            <tbody>
-              {/* ShareLinkListを参考に */}
-            </tbody>
-          </table>
-        </div>
+        <h2 className="border-bottom mb-3">
+          Shared Link List
+          <button
+            type="button"
+            className="btn btn-danger pull-right"
+            disabled={this.state.shareLinks.length === 0}
+            onClick={this.showDeleteConfirmModal}
+          >
+            Delete all links
+          </button>
+        </h2>
+
+        <ShareLinkList
+          shareLinks={this.state.shareLinks}
+          onClickDeleteButton={this.deleteLinkById}
+        />
+
+        <DeleteAllShareLinksModal
+          isOpen={this.state.isDeleteConfirmModalShown}
+          onClose={this.closeDeleteConfirmModal}
+          count={this.state.shareLinks.length}
+          onClickDeleteButton={this.deleteAllLinksButtonHandler}
+        />
 
 
       </>
       </>
     );
     );
@@ -39,10 +81,12 @@ class ShareLinkSetting extends React.Component {
 
 
 }
 }
 
 
-const ShareLinkSettingWrapper = withUnstatedContainers(ShareLinkSetting, [AdminGeneralSecurityContainer]);
+const ShareLinkSettingWrapper = withUnstatedContainers(ShareLinkSetting, [AppContainer]);
 
 
 ShareLinkSetting.propTypes = {
 ShareLinkSetting.propTypes = {
-  adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
+  t: PropTypes.func.isRequired, // i18next
+
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 };
 };
 
 
 export default withTranslation()(ShareLinkSettingWrapper);
 export default withTranslation()(ShareLinkSettingWrapper);

+ 26 - 1
src/server/routes/apiv3/share-links.js

@@ -24,6 +24,7 @@ const today = new Date();
 
 
 module.exports = (crowi) => {
 module.exports = (crowi) => {
   const loginRequired = require('../../middlewares/login-required')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi);
+  const adminRequired = require('../../middlewares/admin-required')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const ShareLink = crowi.model('ShareLink');
   const ShareLink = crowi.model('ShareLink');
@@ -136,7 +137,6 @@ module.exports = (crowi) => {
   */
   */
   router.delete('/', loginRequired, csrf, async(req, res) => {
   router.delete('/', loginRequired, csrf, async(req, res) => {
     const { relatedPage } = req.query;
     const { relatedPage } = req.query;
-    const ShareLink = crowi.model('ShareLink');
 
 
     try {
     try {
       const deletedShareLink = await ShareLink.remove({ relatedPage });
       const deletedShareLink = await ShareLink.remove({ relatedPage });
@@ -149,6 +149,31 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+  * @swagger
+  *
+  *    /share-links/all:
+  *      delete:
+  *        tags: [ShareLinks]
+  *        description: delete all share links
+  *        responses:
+  *          200:
+  *            description: Succeeded to remove all share links
+  */
+  router.delete('/all', loginRequired, adminRequired, csrf, async(req, res) => {
+
+    try {
+      const deletedShareLink = await ShareLink.deleteMany({});
+      const { deletedCount } = deletedShareLink;
+      return res.apiv3({ deletedCount });
+    }
+    catch (err) {
+      const msg = 'Error occurred in delete all share link';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'delete-all-shareLink-failed'));
+    }
+  });
+
   /**
   /**
   * @swagger
   * @swagger
   *
   *