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

Merge branch 'feat/ldap-group-sync' into feat/129126-132372-multiple-group-assign-to-pages

Futa Arai 2 лет назад
Родитель
Сommit
55d784d166

+ 4 - 0
apps/app/public/static/locales/en_US/admin.json

@@ -1068,6 +1068,10 @@
     "sync_being_executed": "There is a running external group sync process started by you or another user. The next sync cannot be executed until this finishes.",
     "sync_succeeded": "External group sync succeeded",
     "sync_failed": "External group sync failed",
+    "provider": "Provider",
+    "confirmation_before_sync": "Confirmation before sync",
+    "execution_time_warning": "If the number of groups or users is large, it might take a while until sync finishes",
+    "parallel_sync_forbidden": "While sync is executing, you cannot execute a different external group sync",
     "ldap": {
       "group_sync_settings": "LDAP Group Sync Settings",
       "group_search_base_DN": "Group Search Base DN",

+ 5 - 0
apps/app/public/static/locales/ja_JP/admin.json

@@ -10,6 +10,7 @@
   "Created": "作成日",
   "Edit": "編集",
   "Description": "説明",
+  "Execute": "実行",
   "last_login": "最終ログイン",
   "wiki_management_homepage": "Wiki管理トップ",
   "public": "公開",
@@ -1077,6 +1078,10 @@
     "sync_being_executed": "自身または他のユーザが実行した外部グループ同期が終了するまで次の実行ができません",
     "sync_succeeded": "外部グループ同期に成功しました",
     "sync_failed": "外部グループ同期に失敗しました",
+    "provider": "プロバイダ",
+    "confirmation_before_sync": "同期実行前の確認",
+    "execution_time_warning": "同期するグループやユーザが多い場合、同期が完了するまでに時間を要します",
+    "parallel_sync_forbidden": "同期実行中は、他の外部グループ同期は実行できません",
     "ldap": {
       "group_sync_settings": "LDAP グループ同期設定",
       "group_search_base_DN": "グループ検索ベース DN",

+ 5 - 0
apps/app/public/static/locales/zh_CN/admin.json

@@ -10,6 +10,7 @@
   "Page": "页面",
   "Edit": "编辑",
   "Description": "描述",
+  "Execute": "执行",
   "last_login": "上次登录",
   "wiki_management_homepage": "Wiki管理首页",
   "public": "公共",
@@ -1076,6 +1077,10 @@
     "sync_being_executed": "There is a running external group sync process started by you or another user. The next sync cannot be executed until this finishes.",
     "sync_succeeded": "External group sync succeeded",
     "sync_failed": "External group sync failed",
+    "provider": "Provider",
+    "confirmation_before_sync": "Confirmation before sync",
+    "execution_time_warning": "If the number of groups or users is large, it might take a while until sync finishes",
+    "parallel_sync_forbidden": "While sync is executing, you cannot execute a different external group sync",
     "ldap": {
       "group_sync_settings": "LDAP Group Sync Settings",
       "group_search_base_DN": "Group Search Base DN",

+ 4 - 0
apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx

@@ -7,6 +7,8 @@ import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 
+import { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
+
 
 type Props = {
   headerLabel?: string,
@@ -143,6 +145,7 @@ export const UserGroupTable: FC<Props> = ({
       <table className="table table-bordered table-user-list">
         <thead>
           <tr>
+            {isExternalGroup && <th>{t('external_user_group.provider')}</th>}
             <th>{t('Name')}</th>
             <th>{t('Description')}</th>
             <th>{t('User')}</th>
@@ -157,6 +160,7 @@ export const UserGroupTable: FC<Props> = ({
 
             return (
               <tr key={group._id}>
+                {isExternalGroup && <td>{(group as IExternalUserGroupHasId).provider}</td>}
                 {isAclEnabled
                   ? (
                     <td><Link href={`/admin/user-group-detail/${group._id}?isExternalGroup=${isExternalGroup}`}>{group.name}</Link></td>

+ 1 - 1
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -197,7 +197,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
               onClick={() => groupListItemClickHandler(group)}
             >
               <h5 className="d-inline-block">{group.item.name}</h5>
-              {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">external</span>}
+              {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">{group.item.provider}</span>}
               {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
             </button>
           );

+ 1 - 1
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/LdapGroupManagement.tsx

@@ -31,7 +31,7 @@ export const LdapGroupManagement: FC = () => {
 
   const requestSyncAPI = useCallback(async(e) => {
     if (isUserBind) {
-      const password = e.target.password.value;
+      const password = e.target.password?.value;
       await apiv3Put('/external-user-groups/ldap/sync', { password });
     }
     else {

+ 34 - 4
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/SyncExecution.tsx

@@ -3,6 +3,7 @@ import {
 } from 'react';
 
 import { useTranslation } from 'react-i18next';
+import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import LabeledProgressBar from '~/components/Admin/Common/LabeledProgressBar';
@@ -14,7 +15,7 @@ import { useSWRxExternalUserGroupList } from '../../stores/external-user-group';
 
 type SyncExecutionProps = {
   provider: ExternalGroupProviderType
-  requestSyncAPI: (e) => Promise<void>
+  requestSyncAPI: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>
   AdditionalForm?: FC
 }
 
@@ -38,6 +39,9 @@ export const SyncExecution = ({
     total: 0,
     current: 0,
   });
+  const [isAlertModalOpen, setIsAlertModalOpen] = useState(false);
+  // value to propagate the submit event of form to submit confirm modal
+  const [currentSubmitEvent, setCurrentSubmitEvent] = useState<React.FormEvent<HTMLFormElement>>();
 
   useEffect(() => {
     if (socket == null) return;
@@ -71,19 +75,25 @@ export const SyncExecution = ({
     };
   }, [socket, mutateExternalUserGroups, t, provider]);
 
-  const onSyncBtnClick = useCallback(async(e) => {
+  const onSyncBtnClick = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
+    setCurrentSubmitEvent(e);
+    setIsAlertModalOpen(true);
+  };
+
+  const onSyncExecConfirmBtnClick = useCallback(async() => {
+    setIsAlertModalOpen(false);
     try {
       // set sync status before requesting to API, so that setting to syncFailed does not get overwritten
       setSyncStatus(SyncStatus.syncExecuting);
       setProgress({ total: 0, current: 0 });
-      await requestSyncAPI(e);
+      await requestSyncAPI(currentSubmitEvent);
     }
     catch (errs) {
       setSyncStatus(SyncStatus.syncFailed);
       toastError(t(errs[0]?.code));
     }
-  }, [t, requestSyncAPI]);
+  }, [t, requestSyncAPI, currentSubmitEvent]);
 
   const renderProgressBar = () => {
     if (syncStatus === SyncStatus.beforeSync) return null;
@@ -124,6 +134,26 @@ export const SyncExecution = ({
           <div className="col-md-6"><button className="btn btn-primary" type="submit">{t('external_user_group.sync')}</button></div>
         </div>
       </form>
+
+      <Modal
+        className="select-grant-group"
+        isOpen={isAlertModalOpen}
+        toggle={() => setIsAlertModalOpen(false)}
+      >
+        <ModalHeader tag="h4" toggle={() => setIsAlertModalOpen(false)} className="bg-purple text-light">
+          <i className="icon-fw icon-exclamation align-middle"></i>
+          <span className="align-middle">{t('external_user_group.confirmation_before_sync')}</span>
+        </ModalHeader>
+        <ModalBody>
+          <ul>
+            <li>{t('external_user_group.execution_time_warning')}</li>
+            <li>{t('external_user_group.parallel_sync_forbidden')}</li>
+          </ul>
+          <div className="text-center">
+            <button className="btn btn-primary" type="button" onClick={onSyncExecConfirmBtnClick}>{t('Execute')}</button>
+          </div>
+        </ModalBody>
+      </Modal>
     </>
   );
 };

+ 3 - 1
apps/app/src/features/external-user-group/server/models/external-user-group.ts

@@ -16,7 +16,7 @@ export interface ExternalUserGroupModel extends Model<ExternalUserGroupDocument>
 }
 
 const schema = new Schema<ExternalUserGroupDocument, ExternalUserGroupModel>({
-  name: { type: String, required: true, unique: true },
+  name: { type: String, required: true },
   parent: { type: Schema.Types.ObjectId, ref: 'ExternalUserGroup', index: true },
   description: { type: String, default: '' },
   externalId: { type: String, required: true, unique: true },
@@ -25,6 +25,8 @@ const schema = new Schema<ExternalUserGroupDocument, ExternalUserGroupModel>({
   timestamps: true,
 });
 schema.plugin(mongoosePaginate);
+// group name should be unique for each provider
+schema.index({ name: 1, provider: 1 }, { unique: true });
 
 /**
  * Find group that has specified externalId and update, or create one if it doesn't exist.

+ 5 - 1
apps/app/src/features/external-user-group/server/service/external-user-group-sync.ts

@@ -120,7 +120,11 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
       });
 
       if (!preserveDeletedLdapGroups) {
-        await ExternalUserGroup.deleteMany({ _id: { $nin: existingExternalUserGroupIds }, groupProviderType: this.groupProviderType });
+        await ExternalUserGroup.deleteMany({
+          _id: { $nin: existingExternalUserGroupIds },
+          groupProviderType: this.groupProviderType,
+          provider: this.groupProviderType,
+        });
         await ExternalUserGroupRelation.removeAllInvalidRelations();
       }
       socket?.emit(SocketEventName.externalUserGroup[this.groupProviderType].GroupSyncCompleted);