jam411 3 лет назад
Родитель
Сommit
625d03d197

+ 4 - 4
packages/app/_obsolete/src/client/admin.jsx

@@ -40,12 +40,12 @@ import FullTextSearchManagement from '../components/Admin/FullTextSearchManageme
 import LegacySlackIntegration from '../components/Admin/LegacySlackIntegration/LegacySlackIntegration';
 import ManageExternalAccount from '../components/Admin/ManageExternalAccount';
 // import MarkdownSetting from '../components/Admin/MarkdownSetting/MarkDownSetting';
-import ManageGlobalNotification from '../components/Admin/Notification/ManageGlobalNotification';
+// import ManageGlobalNotification from '../components/Admin/Notification/ManageGlobalNotification';
 import NotificationSetting from '../components/Admin/Notification/NotificationSetting';
 import SecurityManagement from '../components/Admin/Security/SecurityManagement';
 import SlackIntegration from '../components/Admin/SlackIntegration/SlackIntegration';
 import UserGroupPage from '../components/Admin/UserGroup/UserGroupPage';
-import UserGroupDetailPage from '../components/Admin/UserGroupDetail/UserGroupDetailPage';
+// import UserGroupDetailPage from '../components/Admin/UserGroupDetail/UserGroupDetailPage';
 import UserManagement from '../components/Admin/UserManagement';
 import ErrorBoundary from '../components/ErrorBoudary';
 
@@ -102,10 +102,10 @@ Object.assign(componentMappings, {
   'admin-notification-setting': <NotificationSetting />,
   'admin-slack-integration': <SlackIntegration />,
   'admin-slack-integration-legacy': <LegacySlackIntegration />,
-  'admin-global-notification-setting': <ManageGlobalNotification />,
+  // 'admin-global-notification-setting': <ManageGlobalNotification />,
   'admin-user-page': <UserManagement />,
   'admin-external-account-setting': <ManageExternalAccount />,
-  'admin-user-group-detail': <UserGroupDetailPage />,
+  // 'admin-user-group-detail': <UserGroupDetailPage />,
   'admin-full-text-search-management': <FullTextSearchManagement />,
   'admin-user-group-page': <UserGroupPage />,
   'admin-audit-log': <AuditLogManagement />,

+ 2 - 1
packages/app/public/static/locales/ja_JP/admin.json

@@ -280,7 +280,8 @@
     "delete_notification_pattern": "通知パターンを削除しました。",
     "delete_notification_pattern_desc1": "Path: {{path}} を削除します。",
     "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-    "toggle_notification": "{{path}}の通知設定を変更しました"
+    "toggle_notification": "{{path}}の通知設定を変更しました",
+    "not_found_global_notification_triggerid": "アクセス先の通知設定は見つかりませんでした"
   },
   "full_text_search_management": {
     "full_text_search_management": "全文検索管理",

+ 2 - 1
packages/app/public/static/locales/zh_CN/admin.json

@@ -285,7 +285,8 @@
 		"delete_notification_pattern": "Delete notification pattern",
 		"delete_notification_pattern_desc1": "Delete Path: {{path}}",
 		"delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-		"toggle_notification": "Updated setting of {{path}}"
+		"toggle_notification": "Updated setting of {{path}}",
+    "not_found_global_notification_triggerid": "未找到全局通知 ID"
 	},
   "full_text_search_management": {
     "full_text_search_management": "全文搜索管理",

+ 60 - 19
packages/app/src/components/Admin/Notification/ManageGlobalNotification.jsx

@@ -1,12 +1,16 @@
-import React, { useCallback, useState } from 'react';
+import React, {
+  useCallback, useMemo, useEffect, useState,
+} from 'react';
 
 import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
 import PropTypes from 'prop-types';
 
 import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
 import { toastError } from '~/client/util/apiNotification';
 import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { useIsMailerSetup } from '~/stores/context';
+import { useSWRxGlobalNotification } from '~/stores/global-notification';
 import loggerFactory from '~/utils/logger';
 
 
@@ -18,18 +22,48 @@ import TriggerEventCheckBox from './TriggerEventCheckBox';
 
 const logger = loggerFactory('growi:manageGlobalNotification');
 
-const ManageGlobalNotification = (props) => {
 
-  let globalNotification;
-  // TODO: securely fetch the data of globalNotification variable without using swig. URL https://redmine.weseek.co.jp/issues/103901
-  // globalNotification = JSON.parse(document.getElementById('admin-global-notification-setting').getAttribute('data-global-notification'));
+const ManageGlobalNotification = (props) => {
 
-  const [globalNotificationId, setGlobalNotificationId] = useState(null);
   const [triggerPath, setTriggerPath] = useState('');
-  const [notifyToType, setNotifyToType] = useState('mail');
+  const [notifyType, setNotifyType] = useState('mail');
   const [emailToSend, setEmailToSend] = useState('');
   const [slackChannelToSend, setSlackChannelToSend] = useState('');
-  const [triggerEvents, setTriggerEvents] = useState(new Set(globalNotification?.triggerEvents));
+  const [triggerEvents, setTriggerEvents] = useState(new Set());
+  const { data: globalNotificationData, update: updateGlobalNotification } = useSWRxGlobalNotification(props.globalNotificationId);
+  const globalNotification = useMemo(() => globalNotificationData?.globalNotification, [globalNotificationData?.globalNotification]);
+
+  const router = useRouter();
+
+
+  useEffect(() => {
+    if (globalNotification != null) {
+      const notifyType = globalNotification.__t;
+      setNotifyType(notifyType);
+
+      setTriggerPath(globalNotification.triggerPath);
+      setTriggerEvents(new Set(globalNotification.triggerEvents));
+
+      if (notifyType === 'mail') {
+        setEmailToSend(globalNotification.toEmail);
+      }
+      else {
+        setSlackChannelToSend(globalNotification.slackChannels);
+      }
+    }
+
+
+  }, [globalNotification]);
+
+  const isLoading = globalNotificationData === undefined;
+  const notExistsGlobalNotification = !isLoading && globalNotificationData == null;
+
+  useEffect(() => {
+    if (notExistsGlobalNotification) {
+      router.push('/admin/notification');
+    }
+  }, [notExistsGlobalNotification, router]);
+
 
   const onChangeTriggerEvents = useCallback((triggerEvent) => {
     let newTriggerEvents;
@@ -44,29 +78,35 @@ const ManageGlobalNotification = (props) => {
     }
   }, [triggerEvents]);
 
-  const updateButtonClickedHandler = useCallback(async() => {
 
+  const updateButtonClickedHandler = useCallback(async() => {
     const requestParams = {
       triggerPath,
-      notifyToType,
+      notifyType,
       toEmail: emailToSend,
       slackChannels: slackChannelToSend,
       triggerEvents: [...triggerEvents],
     };
 
+    const { _id: globalNotificationId } = globalNotification;
+
     try {
       if (globalNotificationId != null) {
-        await apiv3Put(`/notification-setting/global-notification/${globalNotificationId}`, requestParams);
+        await updateGlobalNotification(requestParams);
+        router.push('/admin/notification');
+        // await apiv3Put(`/notification-setting/global-notification/${globalNotificationId}`, requestParams);
       }
       else {
         await apiv3Post('/notification-setting/global-notification', requestParams);
+        router.push('/admin/notification');
       }
     }
     catch (err) {
       toastError(err);
       logger.error(err);
     }
-  }, [emailToSend, globalNotificationId, notifyToType, slackChannelToSend, triggerEvents, triggerPath]);
+  }, [emailToSend, globalNotification, notifyType, router, slackChannelToSend, triggerEvents, triggerPath, updateGlobalNotification]);
+
 
   const { data: isMailerSetup } = useIsMailerSetup();
   const { adminNotificationContainer } = props;
@@ -110,10 +150,10 @@ const ManageGlobalNotification = (props) => {
                 className="custom-control-input"
                 type="radio"
                 id="mail"
-                name="notifyToType"
+                name="notifyType"
                 value="mail"
-                checked={notifyToType === 'mail'}
-                onChange={() => { setNotifyToType('mail') }}
+                checked={notifyType === 'mail'}
+                onChange={() => { setNotifyType('mail') }}
               />
               <label className="custom-control-label" htmlFor="mail">
                 <p className="font-weight-bold">Email</p>
@@ -124,10 +164,10 @@ const ManageGlobalNotification = (props) => {
                 className="custom-control-input"
                 type="radio"
                 id="slack"
-                name="notifyToType"
+                name="notifyType"
                 value="slack"
-                checked={notifyToType === 'slack'}
-                onChange={() => { setNotifyToType('slack') }}
+                checked={notifyType === 'slack'}
+                onChange={() => { setNotifyType('slack') }}
               />
               <label className="custom-control-label" htmlFor="slack">
                 <p className="font-weight-bold">Slack</p>
@@ -135,7 +175,7 @@ const ManageGlobalNotification = (props) => {
             </div>
           </div>
 
-          {notifyToType === 'mail'
+          {notifyType === 'mail'
             ? (
               <>
                 <div className="input-group notify-to-option" id="mail-input">
@@ -278,6 +318,7 @@ const ManageGlobalNotification = (props) => {
 
 ManageGlobalNotification.propTypes = {
   adminNotificationContainer: PropTypes.instanceOf(AdminNotificationContainer).isRequired,
+  globalNotificationId: PropTypes.string,
 };
 
 const ManageGlobalNotificationWrapper = withUnstatedContainers(ManageGlobalNotification, [AdminNotificationContainer]);

+ 1 - 1
packages/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -37,7 +37,7 @@ const UpdateParentConfirmModal = dynamic(() => import('./UpdateParentConfirmModa
 
 
 type Props = {
-  userGroupId?: string,
+  userGroupId: string,
 }
 
 const UserGroupDetailPage = (props: Props): JSX.Element => {

+ 73 - 0
packages/app/src/pages/admin/global-notification/[globalNotificationId].page.tsx

@@ -0,0 +1,73 @@
+import { useEffect } from 'react';
+
+import { isClient, objectIdUtils } from '@growi/core';
+import {
+  NextPage, GetServerSideProps, GetServerSidePropsContext,
+} from 'next';
+import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
+import { useRouter } from 'next/router';
+import { Container, Provider } from 'unstated';
+
+import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { CommonProps, useCustomTitle } from '~/pages/utils/commons';
+
+import { retrieveServerSideProps } from '../../../utils/admin-page-util';
+
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
+const ManageGlobalNotification = dynamic(() => import('~/components/Admin/Notification/ManageGlobalNotification'), { ssr: false });
+
+
+const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
+  const { t } = useTranslation('admin');
+  const router = useRouter();
+  const { globalNotificationId } = router.query;
+  const currentGlobalNotificationId = Array.isArray(globalNotificationId) ? globalNotificationId[0] : globalNotificationId;
+
+
+  useEffect(() => {
+    if (globalNotificationId == null) {
+      router.push('/admin/notification');
+    }
+    if ((currentGlobalNotificationId != null && !objectIdUtils.isValidObjectId(currentGlobalNotificationId))) {
+      toastError(t('notification_settings.not_found_global_notification_triggerid'));
+      router.push('/admin/global-notification/new');
+      return;
+    }
+  }, [currentGlobalNotificationId, globalNotificationId, router, t]);
+
+
+  const title = t('external_notification.external_notification');
+  const customTitle = useCustomTitle(props, title);
+
+
+  const injectableContainers: Container<any>[] = [];
+
+  if (isClient()) {
+    const adminNotificationContainer = new AdminNotificationContainer();
+    injectableContainers.push(adminNotificationContainer);
+  }
+
+
+  return (
+    <Provider inject={[...injectableContainers]}>
+      <AdminLayout title={customTitle} componentTitle={title} >
+        {
+          currentGlobalNotificationId != null && router.isReady
+      && <ManageGlobalNotification globalNotificationId={currentGlobalNotificationId} />
+        }
+      </AdminLayout>
+    </Provider>
+  );
+
+};
+
+
+export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+  const props = await retrieveServerSideProps(context);
+  return props;
+};
+
+
+export default AdminGlobalNotificationNewPage;

+ 45 - 0
packages/app/src/pages/admin/user-group-detail/[userGroupId].page.tsx

@@ -0,0 +1,45 @@
+import {
+  NextPage, GetServerSideProps, GetServerSidePropsContext,
+} from 'next';
+import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
+import { useRouter } from 'next/router';
+
+import { CommonProps, useCustomTitle } from '~/pages/utils/commons';
+import { useIsMaintenanceMode } from '~/stores/maintenanceMode';
+
+import { retrieveServerSideProps } from '../../../utils/admin-page-util';
+
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
+const UserGroupDetailPage = dynamic(() => import('~/components/Admin/UserGroupDetail/UserGroupDetailPage'), { ssr: false });
+
+
+const AdminUserGroupDetailPage: NextPage<CommonProps> = (props) => {
+  const { t } = useTranslation('admin');
+  useIsMaintenanceMode(props.isMaintenanceMode);
+  const router = useRouter();
+  const { userGroupId } = router.query;
+
+  const title = t('user_group_management.user_group_management');
+  const customTitle = useCustomTitle(props, title);
+
+
+  const currentUserGroupId = Array.isArray(userGroupId) ? userGroupId[0] : userGroupId;
+
+  return (
+    <AdminLayout title={customTitle} componentTitle={title} >
+      {
+        currentUserGroupId != null && router.isReady
+      && <UserGroupDetailPage userGroupId={currentUserGroupId} />
+      }
+    </AdminLayout>
+  );
+};
+
+export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+  const props = await retrieveServerSideProps(context);
+  return props;
+};
+
+
+export default AdminUserGroupDetailPage;

+ 25 - 25
packages/app/src/server/routes/admin.js

@@ -175,22 +175,22 @@ module.exports = function(crowi, app) {
     return res.redirect('/admin/notification');
   };
 
-  actions.globalNotification = {};
-  actions.globalNotification.detail = async(req, res) => {
-    const notificationSettingId = req.params.id;
-    let globalNotification;
-
-    if (notificationSettingId) {
-      try {
-        globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId });
-      }
-      catch (err) {
-        logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
-      }
-    }
-
-    return res.render('admin/global-notification-detail', { globalNotification });
-  };
+  // actions.globalNotification = {};
+  // actions.globalNotification.detail = async(req, res) => {
+  //   const notificationSettingId = req.params.id;
+  //   let globalNotification;
+
+  //   if (notificationSettingId) {
+  //     try {
+  //       globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId });
+  //     }
+  //     catch (err) {
+  //       logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
+  //     }
+  //   }
+
+  //   return res.render('admin/global-notification-detail', { globalNotification });
+  // };
 
   actions.search = {};
   actions.search.index = function(req, res) {
@@ -263,17 +263,17 @@ module.exports = function(crowi, app) {
   };
 
   // グループ詳細
-  actions.userGroup.detail = async function(req, res) {
-    const userGroupId = req.params.id;
-    const userGroup = await UserGroup.findOne({ _id: userGroupId }).populate('parent');
+  // actions.userGroup.detail = async function(req, res) {
+  //   const userGroupId = req.params.id;
+  //   const userGroup = await UserGroup.findOne({ _id: userGroupId }).populate('parent');
 
-    if (userGroup == null) {
-      logger.error('no userGroup is exists. ', userGroupId);
-      return res.redirect('/admin/user-groups');
-    }
+  //   if (userGroup == null) {
+  //     logger.error('no userGroup is exists. ', userGroupId);
+  //     return res.redirect('/admin/user-groups');
+  //   }
 
-    return res.render('admin/user-group-detail', { userGroup });
-  };
+  //   return res.render('admin/user-group-detail', { userGroup });
+  // };
 
   // AuditLog
   actions.auditLog = {};

+ 1 - 0
packages/app/src/server/routes/apiv3/index.js

@@ -66,6 +66,7 @@ module.exports = (crowi, app, isInstalled) => {
 
   router.use('/personal-setting', require('./personal-setting')(crowi));
 
+  router.use('/user-group-relations', require('./user-group-relation')(crowi));
   router.use('/user-group-relations', require('./user-group-relation')(crowi));
 
   router.use('/statistics', require('./statistics')(crowi));

+ 30 - 12
packages/app/src/server/routes/apiv3/notification-setting.js

@@ -26,12 +26,12 @@ const validator = {
   globalNotification: [
     body('triggerPath').isString().trim().not()
       .isEmpty(),
-    body('notifyToType').isString().trim().isIn(['mail', 'slack']),
+    body('notifyType').isString().trim().isIn(['mail', 'slack']),
     body('toEmail').trim().custom((value, { req }) => {
-      return (req.body.notifyToType === 'mail') ? (!!value && value.match(/.+@.+\..+/)) : true;
+      return (req.body.notifyType === 'mail') ? (!!value && value.match(/.+@.+\..+/)) : true;
     }),
     body('slackChannels').trim().custom((value, { req }) => {
-      return (req.body.notifyToType === 'slack') ? !!value : true;
+      return (req.body.notifyType === 'slack') ? !!value : true;
     }),
   ],
   notifyForPageGrant: [
@@ -72,7 +72,7 @@ const validator = {
  *      GlobalNotificationParams:
  *        type: object
  *        properties:
- *          notifyToType:
+ *          notifyType:
  *            type: string
  *            description: What is type for notify
  *          toEmail:
@@ -232,6 +232,24 @@ module.exports = (crowi) => {
 
   });
 
+
+  router.get('/global-notification/:id', loginRequiredStrictly, adminRequired, validator.globalNotification, async(req, res) => {
+
+    const notificationSettingId = req.params.id;
+    let globalNotification;
+
+    if (notificationSettingId) {
+      try {
+        globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId });
+      }
+      catch (err) {
+        logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
+      }
+    }
+
+    return res.apiv3({ globalNotification });
+  });
+
   /**
    * @swagger
    *
@@ -260,16 +278,16 @@ module.exports = (crowi) => {
   router.post('/global-notification', loginRequiredStrictly, adminRequired, addActivity, validator.globalNotification, apiV3FormValidator, async(req, res) => {
 
     const {
-      notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
+      notifyType, toEmail, slackChannels, triggerPath, triggerEvents,
     } = req.body;
 
     let notification;
 
-    if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
+    if (notifyType === GlobalNotificationSetting.TYPE.MAIL) {
       notification = new GlobalNotificationMailSetting(crowi);
       notification.toEmail = toEmail;
     }
-    if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
+    if (notifyType === GlobalNotificationSetting.TYPE.SLACK) {
       notification = new GlobalNotificationSlackSetting(crowi);
       notification.slackChannels = slackChannels;
     }
@@ -328,7 +346,7 @@ module.exports = (crowi) => {
   router.put('/global-notification/:id', loginRequiredStrictly, adminRequired, addActivity, validator.globalNotification, apiV3FormValidator, async(req, res) => {
     const { id } = req.params;
     const {
-      notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
+      notifyType, toEmail, slackChannels, triggerPath, triggerEvents,
     } = req.body;
 
     const models = {
@@ -342,7 +360,7 @@ module.exports = (crowi) => {
 
       // when switching from one type to another,
       // remove toEmail from slack setting and slackChannels from mail setting
-      if (setting.__t !== notifyToType) {
+      if (setting.__t !== notifyType) {
         setting = models[setting.__t].hydrate(setting);
         setting.toEmail = undefined;
         setting.slackChannels = undefined;
@@ -350,16 +368,16 @@ module.exports = (crowi) => {
         setting = setting.toObject();
       }
 
-      if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
+      if (notifyType === GlobalNotificationSetting.TYPE.MAIL) {
         setting = GlobalNotificationMailSetting.hydrate(setting);
         setting.toEmail = toEmail;
       }
-      if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
+      if (notifyType === GlobalNotificationSetting.TYPE.SLACK) {
         setting = GlobalNotificationSlackSetting.hydrate(setting);
         setting.slackChannels = slackChannels;
       }
 
-      setting.__t = notifyToType;
+      setting.__t = notifyType;
       setting.triggerPath = triggerPath;
       setting.triggerEvents = triggerEvents || [];
 

+ 2 - 2
packages/app/src/server/views/admin/global-notification-detail.html

@@ -1,4 +1,4 @@
-{% extends '../layout/admin.html' %}
+<!-- {% extends '../layout/admin.html' %}
 
 {% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('external_notification.external_notification')) }}{% endblock %}
 
@@ -9,4 +9,4 @@
 {% block content_main %}
 <div id="admin-global-notification-setting"
     data-global-notification="{{ globalNotification|json }}"></div>
-{% endblock content_main %}
+{% endblock content_main %} -->

+ 2 - 2
packages/app/src/server/views/admin/user-group-detail.html

@@ -1,4 +1,4 @@
-{% extends '../layout/admin.html' %}
+<!-- {% extends '../layout/admin.html' %}
 
 {% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('user_group_management.user_group_management') + '/' + userGroup.name) | preventXss }}{% endblock %}
 
@@ -12,4 +12,4 @@
   data-user-group="{{ userGroup|json }}"
 >
 </div>
-{% endblock content_main %}
+{% endblock content_main %} -->

+ 37 - 0
packages/app/src/stores/global-notification.ts

@@ -0,0 +1,37 @@
+import { SWRResponseWithUtils, withUtils } from '@growi/core';
+import useSWRImmutable from 'swr/immutable';
+
+
+import { apiv3Get, apiv3Put } from '../client/util/apiv3-client';
+
+
+type Util = {
+  update(updateData: any): Promise<void>
+};
+
+
+// TODO: typescriptize
+export const useSWRxGlobalNotification = (globalNotificationId: string): SWRResponseWithUtils<Util, any, Error> => {
+  const swrResult = useSWRImmutable(
+    globalNotificationId != null ? `/notification-setting/global-notification/${globalNotificationId}` : null,
+    endpoint => apiv3Get(endpoint).then((response) => {
+      return {
+        globalNotification: response.data.globalNotification,
+      };
+    }),
+  );
+
+
+  const update = async(updateData) => {
+    const { data } = swrResult;
+
+    if (data == null) {
+      return;
+    }
+
+    // invoke API
+    await apiv3Put(`/notification-setting/global-notification/${globalNotificationId}`, updateData);
+  };
+
+  return withUtils<Util, any, Error>(swrResult, { update });
+};