From 673c57d895e0fb86d8979c17a26230ff0e676654 Mon Sep 17 00:00:00 2001 From: Finley Ge Date: Thu, 10 Oct 2024 17:55:57 +0800 Subject: [PATCH] feat: app collaborator with group, remove default permission --- packages/global/core/app/collaborator.d.ts | 10 +- packages/global/core/app/type.d.ts | 8 +- .../global/core/dataset/collaborator.d.ts | 5 +- .../support/permission/collaborator.d.ts | 6 +- .../global/support/permission/constant.ts | 1 - packages/global/support/permission/type.d.ts | 5 + packages/service/core/app/schema.ts | 8 +- .../service/support/permission/app/auth.ts | 13 +- .../service/support/permission/controller.ts | 71 +++++- .../support/permission/dataset/auth.ts | 6 +- .../support/permission/inheritPermission.ts | 37 +-- .../permission/memberGroup/controllers.ts | 2 +- packages/service/support/permission/type.d.ts | 1 + .../service/support/permission/user/auth.ts | 2 +- packages/web/i18n/zh/common.json | 2 +- .../components/common/folder/SlideCard.tsx | 6 +- .../permission/ConfigPerModal/index.tsx | 29 ++- .../MemberManager/AddMemberModal.tsx | 224 ++++++++++++------ .../permission/MemberManager/ManageModal.tsx | 36 ++- .../MemberManager/MemberListCard.tsx | 12 +- .../permission/MemberManager/context.tsx | 14 +- .../components/SelectMember.tsx | 33 +-- projects/app/src/global/core/app/api.d.ts | 1 - .../src/pages/api/core/app/folder/create.ts | 41 ++-- projects/app/src/pages/api/core/app/list.ts | 82 +++++-- projects/app/src/pages/api/core/app/update.ts | 122 ++-------- .../app/src/pages/api/core/dataset/update.ts | 2 - .../pages/app/detail/components/InfoModal.tsx | 58 +++-- .../detail/components/SimpleApp/AppCard.tsx | 24 +- .../pages/app/detail/components/context.tsx | 2 +- .../src/pages/app/list/components/List.tsx | 44 ++-- projects/app/src/pages/app/list/index.tsx | 47 ++-- .../pages/dataset/detail/components/Info.tsx | 20 +- .../src/pages/dataset/list/component/List.tsx | 24 +- projects/app/src/pages/dataset/list/index.tsx | 29 ++- projects/app/src/web/core/app/constants.ts | 3 - .../src/web/core/dataset/api/collaborator.ts | 4 +- .../app/src/web/support/user/useUserStore.ts | 5 + 38 files changed, 574 insertions(+), 465 deletions(-) diff --git a/packages/global/core/app/collaborator.d.ts b/packages/global/core/app/collaborator.d.ts index f9773ec9442..ca0fec7217f 100644 --- a/packages/global/core/app/collaborator.d.ts +++ b/packages/global/core/app/collaborator.d.ts @@ -1,4 +1,8 @@ -import { UpdateClbPermissionProps } from '../../support/permission/collaborator'; +import { RequireOnlyOne } from '../../common/type/utils'; +import { + UpdateClbPermissionProps, + UpdatePermissionBody +} from '../../support/permission/collaborator'; import { PermissionValueType } from '../../support/permission/type'; export type UpdateAppCollaboratorBody = UpdateClbPermissionProps & { @@ -7,5 +11,7 @@ export type UpdateAppCollaboratorBody = UpdateClbPermissionProps & { export type AppCollaboratorDeleteParams = { appId: string; +} & RequireOnlyOne<{ tmbId: string; -}; + groupId: string; +}>; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 761b60f85f1..804837cdc47 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -10,7 +10,6 @@ import { SelectedDatasetType } from '../workflow/api'; import { DatasetSearchModeEnum } from '../dataset/constants'; import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d'; import { StoreEdgeItemType } from '../workflow/type/edge'; -import { PermissionSchemaType, PermissionValueType } from '../../support/permission/type'; import { AppPermission } from '../../support/permission/app/controller'; import { ParentIdType } from '../../common/parentFolder/type'; import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant'; @@ -45,7 +44,8 @@ export type AppSchema = { inited?: boolean; teamTags: string[]; -} & PermissionSchemaType; + inheritPermission?: boolean; +}; export type AppListItemType = { _id: string; @@ -57,7 +57,9 @@ export type AppListItemType = { updateTime: Date; pluginData?: AppSchema['pluginData']; permission: AppPermission; -} & PermissionSchemaType; + inheritPermission?: boolean; + private?: boolean; +}; export type AppDetailType = AppSchema & { permission: AppPermission; diff --git a/packages/global/core/dataset/collaborator.d.ts b/packages/global/core/dataset/collaborator.d.ts index 543d95f2927..7f33f4d516b 100644 --- a/packages/global/core/dataset/collaborator.d.ts +++ b/packages/global/core/dataset/collaborator.d.ts @@ -1,5 +1,6 @@ import { UpdateClbPermissionProps } from '../../support/permission/collaborator'; import { PermissionValueType } from '../../support/permission/type'; +import { RequireOnlyOne } from '../../common/type/utils'; export type UpdateDatasetCollaboratorBody = UpdateClbPermissionProps & { datasetId: string; @@ -7,5 +8,7 @@ export type UpdateDatasetCollaboratorBody = UpdateClbPermissionProps & { export type DatasetCollaboratorDeleteParams = { datasetId: string; +} & RequireOnlyOne<{ tmbId: string; -}; + groupId: string; +}>; diff --git a/packages/global/support/permission/collaborator.d.ts b/packages/global/support/permission/collaborator.d.ts index ea780144629..60a84a9d58e 100644 --- a/packages/global/support/permission/collaborator.d.ts +++ b/packages/global/support/permission/collaborator.d.ts @@ -4,11 +4,13 @@ import { PermissionValueType } from './type'; export type CollaboratorItemType = { teamId: string; - tmbId: string; permission: Permission; name: string; avatar: string; -}; +} & RequireOnlyOne<{ + tmbId: string; + groupId: string; +}>; export type UpdateClbPermissionProps = { members?: string[]; diff --git a/packages/global/support/permission/constant.ts b/packages/global/support/permission/constant.ts index 6e0e2c1b0ac..7824dd72d39 100644 --- a/packages/global/support/permission/constant.ts +++ b/packages/global/support/permission/constant.ts @@ -1,4 +1,3 @@ -import { Permission } from './controller'; import { PermissionListType } from './type'; import { i18nT } from '../../../web/i18n/utils'; export enum AuthUserTypeEnum { diff --git a/packages/global/support/permission/type.d.ts b/packages/global/support/permission/type.d.ts index a97b43c144c..f6f29c52a86 100644 --- a/packages/global/support/permission/type.d.ts +++ b/packages/global/support/permission/type.d.ts @@ -1,6 +1,7 @@ import { RequireOnlyOne } from '../../common/type/utils'; import { TeamMemberWithUserSchema } from '../user/team/type'; import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant'; +import { MemberGroupSchemaType } from './memberGroup/type'; // PermissionValueType, the type of permission's value is a number, which is a bit field actually. // It is spired by the permission system in Linux. @@ -33,6 +34,10 @@ export type ResourcePerWithTmbWithUser = Omit & tmbId: TeamMemberWithUserSchema; }; +export type ResourcePerWithGroup = Omit & { + groupId: MemberGroupSchemaType; +}; + export type PermissionSchemaType = { defaultPermission: PermissionValueType; inheritPermission: boolean; diff --git a/packages/service/core/app/schema.ts b/packages/service/core/app/schema.ts index 0e2f117b008..c66ec4810b9 100644 --- a/packages/service/core/app/schema.ts +++ b/packages/service/core/app/schema.ts @@ -5,8 +5,6 @@ import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant'; -import { getPermissionSchema } from '@fastgpt/global/support/permission/utils'; export const AppCollectionName = 'apps'; @@ -111,8 +109,10 @@ const AppSchema = new Schema({ inited: { type: Boolean }, - - ...getPermissionSchema(AppDefaultPermissionVal) + inheritPermission: { + type: Boolean, + default: true + } }); AppSchema.index({ teamId: 1, updateTime: -1 }); diff --git a/packages/service/support/permission/app/auth.ts b/packages/service/support/permission/app/auth.ts index 9a545b05ffc..f5fc5e33b1c 100644 --- a/packages/service/support/permission/app/auth.ts +++ b/packages/service/support/permission/app/auth.ts @@ -13,6 +13,7 @@ import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { splitCombinePluginId } from '../../../core/app/plugin/controller'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { AuthModeType, AuthResponseType } from '../type'; +import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant'; export const authPluginByTmbId = async ({ tmbId, @@ -60,7 +61,6 @@ export const authAppByTmbId = async ({ if (isRoot) { return { ...app, - defaultPermission: app.defaultPermission, permission: new AppPermission({ isOwner: true }) }; } @@ -71,7 +71,7 @@ export const authAppByTmbId = async ({ const isOwner = tmbPer.isOwner || String(app.tmbId) === String(tmbId); - const { Per, defaultPermission } = await (async () => { + const { Per } = await (async () => { if ( AppFolderTypeList.includes(app.type) || app.inheritPermission === false || @@ -86,10 +86,9 @@ export const authAppByTmbId = async ({ resourceId: appId, resourceType: PerResourceTypeEnum.app }); - const Per = new AppPermission({ per: rp ?? app.defaultPermission, isOwner }); + const Per = new AppPermission({ per: rp ?? AppDefaultPermissionVal, isOwner }); return { - Per, - defaultPermission: app.defaultPermission + Per }; } else { // is not folder and inheritPermission is true and is not root folder. @@ -104,8 +103,7 @@ export const authAppByTmbId = async ({ isOwner }); return { - Per, - defaultPermission: parent.defaultPermission + Per }; } })(); @@ -116,7 +114,6 @@ export const authAppByTmbId = async ({ return { ...app, - defaultPermission, permission: Per }; })(); diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 3924f909ac9..807b35e27bc 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -10,12 +10,16 @@ import { MongoResourcePermission } from './schema'; import { ClientSession } from 'mongoose'; import { PermissionValueType, - ResourcePermissionType + ResourcePermissionType, + ResourcePerWithGroup, + ResourcePerWithTmbWithUser } from '@fastgpt/global/support/permission/type'; import { bucketNameMap } from '@fastgpt/global/common/file/constants'; import { addMinutes } from 'date-fns'; import { getGroupsByTmbId } from './memberGroup/controllers'; import { Permission } from '@fastgpt/global/support/permission/controller'; +import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; +import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; /** get resource permission for a team member * If there is no permission for the team member, it will return undefined @@ -123,6 +127,66 @@ export async function getResourceAllClbs({ ).lean(); } +export async function getResourceClbsAndGroups({ + resourceId, + resourceType, + teamId, + session +}: { + resourceId: ParentIdType; + resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; + teamId: string; + session: ClientSession; +}) { + return MongoResourcePermission.find( + { + resourceId, + resourceType, + teamId + }, + undefined, + { session } + ).lean(); +} + +export const getClbsAndGroupsWithInfo = async ({ + resourceId, + resourceType, + teamId +}: { + resourceId: ParentIdType; + resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; + teamId: string; +}) => + Promise.all([ + (await MongoResourcePermission.find({ + teamId, + resourceId, + resourceType, + tmbId: { + $exists: true + } + }).populate({ + path: 'tmbId', + select: 'name userId', + populate: { + path: 'userId', + select: 'avatar' + } + })) as ResourcePerWithTmbWithUser[], + (await MongoResourcePermission.find({ + teamId, + resourceId, + resourceType, + groupId: { + $exists: true + } + }).populate({ + path: 'groupId', + select: 'name avatar' + })) as ResourcePerWithGroup[] + ]); + export const delResourcePermissionById = (id: string) => { return MongoResourcePermission.findByIdAndRemove(id); }; @@ -135,7 +199,10 @@ export const delResourcePermission = ({ resourceId: string; tmbId: string; session?: ClientSession; -}) => { +} & RequireOnlyOne<{ + tmbId: string; + groupId: string; +}>) => { return MongoResourcePermission.deleteOne(props, { session }); }; diff --git a/packages/service/support/permission/dataset/auth.ts b/packages/service/support/permission/dataset/auth.ts index 2592b02c875..97259f7aaf2 100644 --- a/packages/service/support/permission/dataset/auth.ts +++ b/packages/service/support/permission/dataset/auth.ts @@ -186,7 +186,8 @@ export async function authDatasetCollection({ teamId, tmbId, collection, - permission: dataset.permission + permission: dataset.permission, + isRoot: isRootFromHeader || isRoot }; } @@ -231,7 +232,8 @@ export async function authDatasetFile({ teamId, tmbId, file, - permission + permission, + isRoot }; } catch (error) { return Promise.reject(DatasetErrEnum.unAuthDatasetFile); diff --git a/packages/service/support/permission/inheritPermission.ts b/packages/service/support/permission/inheritPermission.ts index a30dfb8b91d..c3ac42eb8a3 100644 --- a/packages/service/support/permission/inheritPermission.ts +++ b/packages/service/support/permission/inheritPermission.ts @@ -1,9 +1,9 @@ import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { MongoResourcePermission } from './schema'; import { ClientSession, Model } from 'mongoose'; -import { NullPermission, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PermissionValueType } from '@fastgpt/global/support/permission/type'; -import { getResourceAllClbs } from './controller'; +import { getResourceClbsAndGroups } from './controller'; import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; @@ -28,7 +28,6 @@ export async function syncChildrenPermission({ resourceModel, session, - defaultPermission, collaborators }: { resource: SyncChildrenPermissionResourceType; @@ -42,7 +41,6 @@ export async function syncChildrenPermission({ // should be provided when inheritPermission is true session: ClientSession; - defaultPermission?: PermissionValueType; collaborators?: UpdateCollaboratorItem[]; }) { // only folder has permission @@ -76,19 +74,6 @@ export async function syncChildrenPermission({ } if (!children.length) return; - // Sync default permission - if (defaultPermission !== undefined) { - await resourceModel.updateMany( - { - _id: { $in: children } - }, - { - defaultPermission - }, - { session } - ); - } - // sync the resource permission if (collaborators) { // Update the collaborators of all children @@ -124,28 +109,20 @@ export async function resumeInheritPermission({ const isFolder = folderTypeList.includes(resource.type); const fn = async (session: ClientSession) => { - const parentResource = await resourceModel - .findById(resource.parentId, 'defaultPermission') - .lean() - .session(session); - - const parentDefaultPermissionVal = parentResource?.defaultPermission ?? NullPermission; - // update the resource permission await resourceModel.updateOne( { _id: resource._id }, { - inheritPermission: true, - defaultPermission: parentDefaultPermissionVal + inheritPermission: true }, { session } ); // Folder resource, need to sync children if (isFolder) { - const parentClbs = await getResourceAllClbs({ + const parentClbsAndGroups = await getResourceClbsAndGroups({ resourceId: resource.parentId, teamId: resource.teamId, resourceType, @@ -155,7 +132,7 @@ export async function resumeInheritPermission({ // sync self await syncCollaborators({ resourceType, - collaborators: parentClbs, + collaborators: parentClbsAndGroups, teamId: resource.teamId, resourceId: resource._id, session @@ -169,8 +146,7 @@ export async function resumeInheritPermission({ folderTypeList, resourceType, session, - defaultPermission: parentDefaultPermissionVal, - collaborators: parentClbs + collaborators: parentClbsAndGroups }); } else { // Not folder, delete all clb @@ -215,6 +191,7 @@ export async function syncCollaborators({ resourceId, resourceType: resourceType, tmbId: item.tmbId, + groupId: item.groupId, permission: item.permission })), { diff --git a/packages/service/support/permission/memberGroup/controllers.ts b/packages/service/support/permission/memberGroup/controllers.ts index 7203f4b2de9..c6027ffb999 100644 --- a/packages/service/support/permission/memberGroup/controllers.ts +++ b/packages/service/support/permission/memberGroup/controllers.ts @@ -64,7 +64,7 @@ export const getGroupsByTmbId = async ({ groupId: { $exists: true }, - role: role ? { $in: role } : undefined + ...(role ? { role: { $in: role } } : {}) }) .populate('groupId') .lean() diff --git a/packages/service/support/permission/type.d.ts b/packages/service/support/permission/type.d.ts index 176903aa137..e6ce6c7973e 100644 --- a/packages/service/support/permission/type.d.ts +++ b/packages/service/support/permission/type.d.ts @@ -28,5 +28,6 @@ export type AuthResponseType = { authType?: `${AuthUserTypeEnum}`; appId?: string; apikey?: string; + isRoot: boolean; permission: T; }; diff --git a/packages/service/support/permission/user/auth.ts b/packages/service/support/permission/user/auth.ts index 78855b44359..4b9ae40a2f1 100644 --- a/packages/service/support/permission/user/auth.ts +++ b/packages/service/support/permission/user/auth.ts @@ -8,7 +8,7 @@ import { TeamPermission } from '@fastgpt/global/support/permission/user/controll /* auth user role */ export async function authUserPer(props: AuthModeType): Promise< - AuthResponseType & { + AuthResponseType & { tmb: TeamTmbItemType; } > { diff --git a/packages/web/i18n/zh/common.json b/packages/web/i18n/zh/common.json index 2e994b2e508..708515ab472 100644 --- a/packages/web/i18n/zh/common.json +++ b/packages/web/i18n/zh/common.json @@ -915,7 +915,7 @@ "permission.Permission config": "权限配置", "permission.Private": "私有", "permission.Private Tip": "仅自己可用", - "permission.Public": "团队", + "permission.Public": "协作", "permission.Public Tip": "团队所有成员可使用", "permission.Remove InheritPermission Confirm": "此操作会导致权限继承失效,是否进行?", "permission.Resume InheritPermission Confirm": "是否恢复为继承父级文件夹的权限?", diff --git a/projects/app/src/components/common/folder/SlideCard.tsx b/projects/app/src/components/common/folder/SlideCard.tsx index 1639e98fce3..16590c990bb 100644 --- a/projects/app/src/components/common/folder/SlideCard.tsx +++ b/projects/app/src/components/common/folder/SlideCard.tsx @@ -1,5 +1,4 @@ import { Box, Button, Flex, HStack } from '@chakra-ui/react'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import React from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { FolderIcon } from '@fastgpt/global/common/file/image/constants'; @@ -40,7 +39,7 @@ const FolderSlideCard = ({ deleteTip: string; onDelete: () => void; - defaultPer: { + defaultPer?: { value: PermissionValueType; defaultValue: PermissionValueType; onChange: (v: PermissionValueType) => Promise; @@ -54,7 +53,6 @@ const FolderSlideCard = ({ }) => { const { t } = useTranslation(); const { feConfigs } = useSystemStore(); - const { toast } = useToast(); const { ConfirmModal, openConfirm } = useConfirm({ type: 'delete', @@ -136,7 +134,7 @@ const FolderSlideCard = ({ )} - {managePer.permission.hasManagePer && ( + {managePer.permission.hasManagePer && !!defaultPer && ( {t('common:permission.Default permission')} diff --git a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx index 73cad5e19bb..6dc826d6b26 100644 --- a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx +++ b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx @@ -14,7 +14,7 @@ export type ConfigPerModalProps = { avatar?: string; name: string; - defaultPer: { + defaultPer?: { value: PermissionValueType; defaultValue: PermissionValueType; onChange: (v: PermissionValueType) => Promise; @@ -66,17 +66,22 @@ const ConfigPerModal = ({ )} - - {t('common:permission.Default permission')} - defaultPer.onChange(v)} - hasParent={hasParent} - /> - + { + // deprecated + !!defaultPer && ( + + {t('common:permission.Default permission')} + defaultPer.onChange(v)} + hasParent={hasParent} + /> + + ) + } void; + mode?: 'member' | 'all'; }; -function AddMemberModal({ onClose }: AddModalPropsType) { +function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { const { t } = useTranslation(); - const { userInfo, loadAndGetTeamMembers } = useUserStore(); + const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups } = useUserStore(); - const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList } = + const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } = useContextSelector(CollaboratorContext, (v) => v); const [searchText, setSearchText] = useState(''); - const { data: members = [], loading: loadingMembers } = useRequest2( + const { data: [members = [], groups = []] = [], loading: loadingMembersAndGroups } = useRequest2( async () => { - if (!userInfo?.team?.teamId) return []; - const members = await loadAndGetTeamMembers(true); - return members; + if (!userInfo?.team?.teamId) return [[], []]; + return await Promise.all([loadAndGetTeamMembers(true), loadAndGetGroups(true)]); }, { manual: false, refreshDeps: [userInfo?.team?.teamId] } ); + const filterMembers = useMemo(() => { return members.filter((item) => { - // if (item.permission.isOwner) return false; if (item.tmbId === userInfo?.team?.tmbId) return false; if (!searchText) return true; return item.memberName.includes(searchText); }); }, [members, searchText, userInfo?.team?.tmbId]); + const filterGroups = useMemo(() => { + if (mode !== 'all') return []; + return groups.filter((item) => { + if (permission.isOwner) return true; // owner can see all groups + if (myGroups.find((i) => String(i._id) === String(item._id))) return false; + if (!searchText) return true; + return item.name.includes(searchText); + }); + }, [groups, searchText, myGroups, mode, permission]); + const [selectedMemberIdList, setSelectedMembers] = useState([]); + const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value); const perLabel = useMemo(() => { return getPerLabelList(selectedPermission).join('、'); }, [getPerLabelList, selectedPermission]); - const { mutate: onConfirm, isLoading: isUpdating } = useRequest({ - mutationFn: () => { - return onUpdateCollaborators({ + const { runAsync: onConfirm, loading: isUpdating } = useRequest2( + () => + onUpdateCollaborators({ members: selectedMemberIdList, + groups: selectedGroupIdList, permission: selectedPermission - }); - }, - successToast: t('common:common.Add Success'), - errorToast: 'Error', - onSuccess() { - onClose(); + }), + { + successToast: t('common:common.Add Success'), + errorToast: 'Error', + onSuccess() { + onClose(); + } } - }); + ); return ( - - - - - - setSearchText(e.target.value)} - /> - - + setSearchText(e.target.value)} + /> + + + {filterGroups.map((group) => { + const onChange = () => { + if (selectedGroupIdList.includes(group._id)) { + setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== group._id)); + } else { + setSelectedGroupIdList([...selectedGroupIdList, group._id]); + } + }; + const collaborator = collaboratorList.find((v) => v.groupId === group._id); + return ( + + } + /> + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + {!!collaborator && ( + + )} + + ); + })} {filterMembers.map((member) => { const onChange = () => { if (selectedMemberIdList.includes(member.tmbId)) { @@ -123,10 +168,10 @@ function AddMemberModal({ onClose }: AddModalPropsType) { }; const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId); return ( - } - onChange={onChange} /> - - - - {member.memberName} - - {!!collaborator && ( - - )} - - + + + {member.memberName} + + {!!collaborator && ( + + )} + ); })} - {t('user:has_chosen') + ': '}+ {selectedMemberIdList.length} + {t('user:has_chosen') + ': '}{' '} + {selectedMemberIdList.length + selectedGroupIdList.length} - + + {selectedGroupIdList.map((groupId) => { + const onChange = () => { + if (selectedGroupIdList.includes(groupId)) { + setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== groupId)); + } else { + setSelectedGroupIdList([...selectedGroupIdList, groupId]); + } + }; + const group = groups.find((v) => String(v._id) === groupId); + return ( + + + + {group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name} + + + + ); + })} {selectedMemberIdList.map((tmbId) => { const member = filterMembers.find((v) => v.tmbId === tmbId); return member ? ( - + setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) + } > - + {member.memberName} @@ -192,16 +273,13 @@ function AddMemberModal({ onClose }: AddModalPropsType) { _hover={{ color: 'red.600' }} - onClick={() => - setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) - } /> - + ) : null; })} - + void; }; @@ -23,21 +23,12 @@ function ManageModal({ onClose }: ManageModalProps) { const { permission, collaboratorList, onUpdateCollaborators, onDelOneCollaborator } = useContextSelector(CollaboratorContext, (v) => v); - const { runAsync: onDelete, loading: isDeleting } = useRequest2((tmbId: string) => - onDelOneCollaborator(tmbId) - ); + const { runAsync: onDelete, loading: isDeleting } = useRequest2(onDelOneCollaborator); - const { runAsync: onUpdate, loading: isUpdating } = useRequest2( - ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => - onUpdateCollaborators({ - members: [tmbId], - permission: per - }), - { - successToast: t('common.Update Success'), - errorToast: 'Error' - } - ); + const { runAsync: onUpdate, loading: isUpdating } = useRequest2(onUpdateCollaborators, { + successToast: t('common.Update Success'), + errorToast: 'Error' + }); const loading = isDeleting || isUpdating; @@ -74,7 +65,7 @@ function ManageModal({ onClose }: ManageModalProps) { - {item.name} + {item.name === DefaultGroupName ? userInfo?.team.teamName : item.name} @@ -89,14 +80,19 @@ function ManageModal({ onClose }: ManageModalProps) { } value={item.permission.value} - onChange={(per) => { + onChange={(permission) => { onUpdate({ - tmbId: item.tmbId, - per + members: item.tmbId ? [item.tmbId] : undefined, + groups: item.groupId ? [item.groupId] : undefined, + permission }); }} onDelete={() => { - onDelete(item.tmbId); + console.log('onDelete', item); + onDelete({ + tmbId: item.tmbId, + groupId: item.groupId + }); }} /> )} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx index c9de309dfb2..477c777f7f2 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx @@ -6,11 +6,14 @@ import { CollaboratorContext } from './context'; import Tag, { TagProps } from '@fastgpt/web/components/common/Tag'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { useTranslation } from 'next-i18next'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import { useUserStore } from '@/web/support/user/useUserStore'; export type MemberListCardProps = BoxProps & { tagStyle?: Omit }; const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => { const { t } = useTranslation(); + const { userInfo } = useUserStore(); const { collaboratorList, isFetchingCollaborator } = useContextSelector( CollaboratorContext, @@ -27,10 +30,15 @@ const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => { {collaboratorList?.map((member) => { return ( - + - {member.name} + {member.name === DefaultGroupName ? userInfo?.team.teamName : member.name} ); diff --git a/projects/app/src/components/support/permission/MemberManager/context.tsx b/projects/app/src/components/support/permission/MemberManager/context.tsx index be399703bb1..44daa84b81a 100644 --- a/projects/app/src/components/support/permission/MemberManager/context.tsx +++ b/projects/app/src/components/support/permission/MemberManager/context.tsx @@ -22,10 +22,12 @@ export type MemberManagerInputPropsType = { permission: Permission; onGetCollaboratorList: () => Promise; permissionList: PermissionListType; - onUpdateCollaborators: (props: any) => any; // TODO: type. should be UpdatePermissionBody after app and dataset permission refactored - onDelOneCollaborator: (tmbId: string) => any; + onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise; + onDelOneCollaborator: (props: { tmbId?: string; groupId?: string }) => Promise; refreshDeps?: any[]; + mode?: 'member' | 'all'; }; + export type MemberManagerPropsType = MemberManagerInputPropsType & { collaboratorList: CollaboratorItemType[]; refetchCollaboratorList: () => void; @@ -72,7 +74,8 @@ const CollaboratorContextProvider = ({ refetchResource, refreshDeps = [], isInheritPermission, - hasParent + hasParent, + mode = 'member' }: MemberManagerInputPropsType & { children: (props: ChildrenProps) => ReactNode; refetchResource?: () => void; @@ -83,8 +86,8 @@ const CollaboratorContextProvider = ({ await onUpdateCollaborators(props); refetchCollaboratorList(); }; - const onDelOneCollaboratorThen = async (tmbId: string) => { - await onDelOneCollaborator(tmbId); + const onDelOneCollaboratorThen = async (props: { tmbId?: string; groupId?: string }) => { + await onDelOneCollaborator(props); refetchCollaboratorList(); }; @@ -197,6 +200,7 @@ const CollaboratorContextProvider = ({ onCloseAddMember(); refetchResource?.(); }} + mode={mode} /> )} {isOpenManageModal && ( diff --git a/projects/app/src/components/support/user/team/TeamManageModal/components/SelectMember.tsx b/projects/app/src/components/support/user/team/TeamManageModal/components/SelectMember.tsx index f2badaeaa46..57f4e2fa4ab 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/components/SelectMember.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/components/SelectMember.tsx @@ -1,14 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { - Box, - Checkbox, - Flex, - Grid, - HStack, - Input, - InputGroup, - InputLeftElement -} from '@chakra-ui/react'; +import { Box, Checkbox, Flex, Grid, HStack } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { useTranslation } from 'next-i18next'; @@ -16,6 +7,7 @@ import { Control, Controller } from 'react-hook-form'; import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils'; import { useUserStore } from '@/web/support/user/useUserStore'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; type memberType = { type: 'member'; @@ -120,19 +112,14 @@ function SelectMember({ h={'100%'} > - - - - - { - setSearchKey(e.target.value); - }} - /> - + { + setSearchKey(e.target.value); + }} + /> {filtered.map((member) => { return ( diff --git a/projects/app/src/global/core/app/api.d.ts b/projects/app/src/global/core/app/api.d.ts index 9da2b7368f2..fe6923ff1ec 100644 --- a/projects/app/src/global/core/app/api.d.ts +++ b/projects/app/src/global/core/app/api.d.ts @@ -12,7 +12,6 @@ export type AppUpdateParams = { edges?: AppSchema['edges']; chatConfig?: AppSchema['chatConfig']; teamTags?: AppSchema['teamTags']; - defaultPermission?: AppSchema['defaultPermission']; }; export type PostPublishAppProps = { diff --git a/projects/app/src/pages/api/core/app/folder/create.ts b/projects/app/src/pages/api/core/app/folder/create.ts index cc7092f2a2b..75fc161fa21 100644 --- a/projects/app/src/pages/api/core/app/folder/create.ts +++ b/projects/app/src/pages/api/core/app/folder/create.ts @@ -11,11 +11,11 @@ import { NextAPI } from '@/service/middleware/entry'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; -import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission'; -import { getResourceAllClbs } from '@fastgpt/service/support/permission/controller'; +import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller'; +import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; export type CreateAppFolderBody = { parentId?: ParentIdType; @@ -31,20 +31,21 @@ async function handler(req: ApiRequestProps) { } // 凭证校验 - const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); - const parentApp = await (async () => { - if (parentId) { - // if it is not a root folder - return ( - await authApp({ - req, - appId: parentId, - per: WritePermissionVal, - authToken: true - }) - ).app; // check the parent folder permission - } - })(); + const { teamId, tmbId } = await authUserPer({ + req, + authToken: true, + per: TeamWritePermissionVal + }); + + if (parentId) { + // if it is not a root folder + await authApp({ + req, + appId: parentId, + per: WritePermissionVal, + authToken: true + }); + } // Create app await mongoSessionRun(async (session) => { @@ -55,13 +56,11 @@ async function handler(req: ApiRequestProps) { intro, teamId, tmbId, - type: AppTypeEnum.folder, - // inheritPermission: !!parentApp ? true : false, - defaultPermission: !!parentApp ? parentApp.defaultPermission : AppDefaultPermissionVal + type: AppTypeEnum.folder }); if (parentId) { - const parentClbs = await getResourceAllClbs({ + const parentClbsAndGroups = await getResourceClbsAndGroups({ teamId, resourceId: parentId, resourceType: PerResourceTypeEnum.app, @@ -72,7 +71,7 @@ async function handler(req: ApiRequestProps) { resourceType: PerResourceTypeEnum.app, teamId, resourceId: app._id, - collaborators: parentClbs, + collaborators: parentClbsAndGroups, session }); } diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index 179b3ffb515..45d9a2beff8 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -15,6 +15,8 @@ import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; +import { getGroupPer } from '@fastgpt/service/support/permission/controller'; +import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; export type ListAppBody = { parentId?: ParentIdType; @@ -31,7 +33,7 @@ async function handler(req: ApiRequestProps): Promise { if (parentId) { return await authApp({ @@ -87,10 +89,17 @@ async function handler(req: ApiRequestProps): Promise String(item._id)); + + const [myApps, perList] = await Promise.all([ MongoApp.find( findAppsQuery, - '_id parentId avatar type name intro tmbId updateTime pluginData defaultPermission inheritPermission' + '_id parentId avatar type name intro tmbId updateTime pluginData inheritPermission' ) .sort({ updateTime: -1 @@ -98,41 +107,64 @@ async function handler(req: ApiRequestProps): Promise { - const Per = (() => { + const { Per, privateApp } = (() => { // Inherit app if (app.inheritPermission && ParentApp && !AppFolderTypeList.includes(app.type)) { - // get its parent's permission as its permission - app.defaultPermission = ParentApp.defaultPermission; - const perVal = rpList.find( - (item) => String(item.resourceId) === String(ParentApp._id) + const tmbPer = perList.find( + (item) => String(item.resourceId) === String(ParentApp._id) && !!item.tmbId )?.permission; + const groupPer = getGroupPer( + perList + .filter( + (item) => + String(item.resourceId) === String(ParentApp._id) && + myGroupIds.includes(String(item.groupId)) + ) + .map((item) => item.permission) + ); - return new AppPermission({ - per: perVal ?? app.defaultPermission, - isOwner: String(app.tmbId) === String(tmbId) || tmbPer.isOwner - }); + return { + Per: new AppPermission({ + per: tmbPer ?? groupPer ?? AppDefaultPermissionVal, + isOwner: String(app.tmbId) === String(tmbId) || myPer.isOwner + }), + privateApp: !tmbPer && !groupPer + }; } else { - const perVal = rpList.find( - (item) => String(item.resourceId) === String(app._id) + const tmbPer = perList.find( + (item) => String(item.resourceId) === String(app._id) && !!item.tmbId )?.permission; - return new AppPermission({ - per: perVal ?? app.defaultPermission, - isOwner: String(app.tmbId) === String(tmbId) || tmbPer.isOwner - }); + const group = perList.filter( + (item) => + String(item.resourceId) === String(app._id) && + myGroupIds.includes(String(item.groupId)) + ); + const groupPer = getGroupPer(group.map((item) => item.permission)); + return { + Per: new AppPermission({ + per: tmbPer ?? groupPer ?? AppDefaultPermissionVal, + isOwner: String(app.tmbId) === String(tmbId) || myPer.isOwner + }), + privateApp: !tmbPer && !groupPer + }; } })(); - return { ...app, - permission: Per + permission: Per, + privateApp }; }) .filter((app) => app.permission.hasReadPer); @@ -148,9 +180,9 @@ async function handler(req: ApiRequestProps): Promise) { - const { - parentId, - name, - avatar, - type, - intro, - nodes, - edges, - chatConfig, - teamTags, - defaultPermission - } = req.body as AppUpdateParams; +// update the app information or move the app to another folder +async function handler(req: ApiRequestProps) { + const { parentId, name, avatar, type, intro, nodes, edges, chatConfig, teamTags } = req.body; - const { appId } = req.query as { appId: string }; + const { appId } = req.query; if (!appId) { Promise.reject(CommonErrEnum.missingParams); } - const { app } = await (async () => { - if (defaultPermission !== undefined) { - // if defaultPermission or inheritPermission is set, then need manage permission - return authApp({ req, authToken: true, appId, per: ManagePermissionVal }); - } else { - return authApp({ req, authToken: true, appId, per: WritePermissionVal }); - } - })(); + const { app } = await authApp({ req, authToken: true, appId, per: WritePermissionVal }); // format nodes data // 1. dataset search limit, less than model quoteMaxToken - const isDefaultPermissionChanged = - defaultPermission !== undefined && defaultPermission !== app.defaultPermission; const isFolder = AppFolderTypeList.includes(app.type); - const onUpdate = async ( - session?: ClientSession, - updatedDefaultPermission?: PermissionValueType - ) => { + const onUpdate = async (session?: ClientSession) => { const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes }); return MongoApp.findByIdAndUpdate( @@ -84,12 +52,6 @@ async function handler(req: ApiRequestProps) ...(type && { type }), ...(avatar && { avatar }), ...(intro !== undefined && { intro }), - // update default permission(Maybe move update) - ...(updatedDefaultPermission !== undefined && { - defaultPermission: updatedDefaultPermission - }), - // Not root, update default permission - ...(app.parentId && isDefaultPermissionChanged && { inheritPermission: false }), ...(teamTags && { teamTags }), ...(formatNodes && { modules: formatNodes @@ -106,25 +68,9 @@ async function handler(req: ApiRequestProps) // Move if (parentId !== undefined) { await mongoSessionRun(async (session) => { - // Auth - const parentDefaultPermission = await (async () => { - if (parentId) { - const { app: parentApp } = await authApp({ - req, - authToken: true, - appId: parentId, - per: WritePermissionVal - }); - - return parentApp.defaultPermission; - } - - return AppDefaultPermissionVal; - })(); - // Inherit folder: Sync children permission and it's clbs if (isFolder && app.inheritPermission) { - const parentClbs = await getResourceAllClbs({ + const parentClbsAndGroups = await getResourceClbsAndGroups({ teamId: app.teamId, resourceId: parentId, resourceType: PerResourceTypeEnum.app, @@ -134,7 +80,7 @@ async function handler(req: ApiRequestProps) await syncCollaborators({ resourceId: app._id, resourceType: PerResourceTypeEnum.app, - collaborators: parentClbs, + collaborators: parentClbsAndGroups, session, teamId: app.teamId }); @@ -144,53 +90,15 @@ async function handler(req: ApiRequestProps) resourceType: PerResourceTypeEnum.app, resourceModel: MongoApp, folderTypeList: AppFolderTypeList, - defaultPermission: parentDefaultPermission, - collaborators: parentClbs, + collaborators: parentClbsAndGroups, session }); - return onUpdate(session, parentDefaultPermission); + return onUpdate(session); } return onUpdate(session); }); - } else if (isDefaultPermissionChanged) { - // Update default permission - await mongoSessionRun(async (session) => { - if (isFolder) { - // Sync children default permission - await syncChildrenPermission({ - resource: { - _id: app._id, - type: app.type, - teamId: app.teamId, - parentId: app.parentId - }, - folderTypeList: AppFolderTypeList, - resourceModel: MongoApp, - resourceType: PerResourceTypeEnum.app, - session, - defaultPermission - }); - } else if (app.inheritPermission && app.parentId) { - // Inherit app - const parentClbs = await getResourceAllClbs({ - teamId: app.teamId, - resourceId: app.parentId, - resourceType: PerResourceTypeEnum.app, - session - }); - await syncCollaborators({ - resourceId: app._id, - resourceType: PerResourceTypeEnum.app, - collaborators: parentClbs, - session, - teamId: app.teamId - }); - } - - return onUpdate(session, defaultPermission); - }); } else { return onUpdate(); } diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index bcc197d0b96..427d340bc6a 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -122,7 +122,6 @@ async function handler( resourceModel: MongoDataset, folderTypeList: [DatasetTypeEnum.folder], collaborators: parentClbs, - defaultPermission: parentDefaultPermission, session }); return onUpdate(session, parentDefaultPermission); @@ -133,7 +132,6 @@ async function handler( await mongoSessionRun(async (session) => { if (isFolder) { await syncChildrenPermission({ - defaultPermission, resource: { _id: dataset._id, type: dataset.type, diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx index 1e18f56e92b..cde3e45fe7e 100644 --- a/projects/app/src/pages/app/detail/components/InfoModal.tsx +++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx @@ -28,11 +28,7 @@ import { } from '@/web/core/app/api/collaborator'; import { useContextSelector } from 'use-context-selector'; import { AppContext } from '@/pages/app/detail/components/context'; -import { - AppDefaultPermissionVal, - AppPermissionList -} from '@fastgpt/global/support/permission/app/constant'; -import DefaultPermissionList from '@/components/support/permission/DefaultPerList'; +import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { resumeInheritPer } from '@/web/core/app/api'; import { useI18n } from '@/web/context/I18n'; @@ -67,8 +63,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => { await updateAppDetail({ name: data.name, avatar: data.avatar, - intro: data.intro, - defaultPermission: data.defaultPermission + intro: data.intro }); }, { @@ -129,23 +124,32 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => { const onUpdateCollaborators = ({ members, + groups, permission }: { - members: string[]; + members?: string[]; + groups?: string[]; permission: PermissionValueType; - }) => { - return postUpdateAppCollaborators({ + }) => + postUpdateAppCollaborators({ members, + groups, permission, appId: appDetail._id }); - }; - const onDelCollaborator = (tmbId: string) => { - return deleteAppCollaborators({ - appId: appDetail._id, - tmbId - }); + const onDelCollaborator = async ({ tmbId, groupId }: { tmbId?: string; groupId?: string }) => { + if (groupId) { + return deleteAppCollaborators({ + appId: appDetail._id, + groupId + }); + } else if (tmbId) { + return deleteAppCollaborators({ + appId: appDetail._id, + tmbId + }); + } }; const { runAsync: resumeInheritPermission } = useRequest2(() => resumeInheritPer(appDetail._id), { @@ -204,31 +208,23 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => { )} - - {t('common:permission.Default permission')} - { - setValue('defaultPermission', v); - return handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)(); - }} - hasParent={!!appDetail.parentId} - /> - getCollaboratorList(appDetail._id)} permissionList={AppPermissionList} - onUpdateCollaborators={(props) => { + onUpdateCollaborators={async (props) => { if (props.members) { return onUpdateCollaborators({ permission: props.permission, members: props.members }); + } else { + return onUpdateCollaborators({ + permission: props.permission, + groups: props.groups + }); } }} onDelOneCollaborator={onDelCollaborator} diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx index 8d854f8ce3f..f7ffd1f6cb2 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx @@ -5,7 +5,6 @@ import { Button, IconButton, HStack, - Modal, ModalBody, Checkbox, ModalFooter @@ -19,26 +18,23 @@ import TagsEditModal from '../TagsEditModal'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { AppContext } from '@/pages/app/detail/components/context'; import { useContextSelector } from 'use-context-selector'; -import PermissionIconText from '@/components/support/permission/IconText'; -import MyTag from '@fastgpt/web/components/common/Tag/index'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { useI18n } from '@/web/context/I18n'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { postTransition2Workflow } from '@/web/core/app/api/app'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; -import { useSystem } from '@fastgpt/web/hooks/useSystem'; const AppCard = () => { const router = useRouter(); const { t } = useTranslation(); const { appT } = useI18n(); - const { isPc } = useSystem(); const { appDetail, setAppDetail, onOpenInfoEdit, onDelApp } = useContextSelector( AppContext, (v) => v ); + const appId = appDetail._id; const { feConfigs } = useSystemStore(); const [TeamTagsSet, setTeamTagsSet] = useState(); @@ -150,15 +146,15 @@ const AppCard = () => { /> )} - {isPc && ( - (appDetail.permission.hasManagePer ? onOpenInfoEdit() : undefined)} - > - - - )} + {/* {isPc && ( */} + {/* (appDetail.permission.hasManagePer ? onOpenInfoEdit() : undefined)} */} + {/* > */} + {/* */} + {/* */} + {/* )} */} {TeamTagsSet && setTeamTagsSet(undefined)} />} diff --git a/projects/app/src/pages/app/detail/components/context.tsx b/projects/app/src/pages/app/detail/components/context.tsx index 157fdba30e7..8f403922d7f 100644 --- a/projects/app/src/pages/app/detail/components/context.tsx +++ b/projects/app/src/pages/app/detail/components/context.tsx @@ -1,4 +1,4 @@ -import { Dispatch, ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react'; import { createContext } from 'use-context-selector'; import { defaultApp } from '@/web/core/app/constants'; import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api'; diff --git a/projects/app/src/pages/app/list/components/List.tsx b/projects/app/src/pages/app/list/components/List.tsx index f407e124f3f..80daea158da 100644 --- a/projects/app/src/pages/app/list/components/List.tsx +++ b/projects/app/src/pages/app/list/components/List.tsx @@ -17,10 +17,7 @@ import { useFolderDrag } from '@/components/common/folder/useFolderDrag'; import dynamic from 'next/dynamic'; import type { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; -import { - AppDefaultPermissionVal, - AppPermissionList -} from '@fastgpt/global/support/permission/app/constant'; +import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant'; import { deleteAppCollaborators, getCollaboratorList, @@ -233,7 +230,7 @@ const ListItem = () => { )} { isInheritPermission={editPerApp.inheritPermission} avatar={editPerApp.avatar} name={editPerApp.name} - defaultPer={{ - value: editPerApp.defaultPermission, - defaultValue: AppDefaultPermissionVal, - onChange: (e) => { - return onUpdateApp(editPerApp._id, { defaultPermission: e }); - } - }} managePer={{ + mode: 'all', permission: editPerApp.permission, onGetCollaboratorList: () => getCollaboratorList(editPerApp._id), permissionList: AppPermissionList, onUpdateCollaborators: ({ - members = [], // TODO: remove the default value after group is ready + members, + groups, permission }: { members?: string[]; + groups?: string[]; permission: number; }) => { return postUpdateAppCollaborators({ members, + groups, permission, appId: editPerApp._id }); }, - onDelOneCollaborator: (tmbId: string) => - deleteAppCollaborators({ - appId: editPerApp._id, - tmbId - }), + onDelOneCollaborator: async ({ + tmbId, + groupId + }: { + tmbId?: string; + groupId?: string; + }) => { + if (tmbId) { + return deleteAppCollaborators({ + appId: editPerApp._id, + tmbId + }); + } else if (groupId) { + return deleteAppCollaborators({ + appId: editPerApp._id, + groupId + }); + } + }, refreshDeps: [editPerApp.inheritPermission] }} onClose={() => setEditPerAppIndex(undefined)} diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index e324c3644c2..21881c94cf9 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Box, Flex, @@ -14,8 +14,6 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { useI18n } from '@/web/context/I18n'; import { useTranslation } from 'next-i18next'; import dynamic from 'next/dynamic'; - -import List from './components/List'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { FolderIcon } from '@fastgpt/global/common/file/image/constants'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; @@ -27,10 +25,7 @@ import FolderPath from '@/components/common/folder/Path'; import { useRouter } from 'next/router'; import FolderSlideCard from '@/components/common/folder/SlideCard'; import { delAppById, resumeInheritPer } from '@/web/core/app/api'; -import { - AppDefaultPermissionVal, - AppPermissionList -} from '@fastgpt/global/support/permission/app/constant'; +import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant'; import { deleteAppCollaborators, getCollaboratorList, @@ -49,6 +44,7 @@ const EditFolderModal = dynamic( () => import('@fastgpt/web/components/common/MyModal/EditFolderModal') ); const HttpEditModal = dynamic(() => import('./components/HttpPluginEditModal')); +const List = dynamic(() => import('./components/List')); const MyApps = () => { const { t } = useTranslation(); @@ -273,36 +269,47 @@ const MyApps = () => { onMove={() => setMoveAppId(folderDetail._id)} deleteTip={appT('confirm_delete_folder_tip')} onDelete={() => onDeleFolder(folderDetail._id)} - defaultPer={{ - value: folderDetail.defaultPermission, - defaultValue: AppDefaultPermissionVal, - onChange: (e) => { - return onUpdateApp(folderDetail._id, { defaultPermission: e }); - } - }} managePer={{ + mode: 'all', permission: folderDetail.permission, onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), permissionList: AppPermissionList, onUpdateCollaborators: ({ - members = [], // TODO: remove the default value after group is ready + members, + groups, permission }: { members?: string[]; + groups?: string[]; permission: number; }) => { return postUpdateAppCollaborators({ members, + groups, permission, appId: folderDetail._id }); }, refreshDeps: [folderDetail._id, folderDetail.inheritPermission], - onDelOneCollaborator: (tmbId: string) => - deleteAppCollaborators({ - appId: folderDetail._id, - tmbId - }) + onDelOneCollaborator: async ({ + tmbId, + groupId + }: { + tmbId?: string; + groupId?: string; + }) => { + if (tmbId) { + return deleteAppCollaborators({ + appId: folderDetail._id, + tmbId + }); + } else if (groupId) { + return deleteAppCollaborators({ + appId: folderDetail._id, + groupId + }); + } + } }} /> diff --git a/projects/app/src/pages/dataset/detail/components/Info.tsx b/projects/app/src/pages/dataset/detail/components/Info.tsx index b5d38794ce0..fca5b693a02 100644 --- a/projects/app/src/pages/dataset/detail/components/Info.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info.tsx @@ -59,7 +59,7 @@ const Info = ({ datasetId }: { datasetId: string }) => { const defaultPermission = watch('defaultPermission'); const { datasetModelList, vectorModelList } = useSystemStore(); - const { openConfirm: onOpenConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({ + const { ConfirmModal: ConfirmDelModal } = useConfirm({ content: t('common:core.dataset.Delete Confirm'), type: 'delete' }); @@ -348,11 +348,19 @@ const Info = ({ datasetId }: { datasetId: string }) => { ...body, datasetId }), - onDelOneCollaborator: (tmbId) => - deleteDatasetCollaborators({ - datasetId, - tmbId - }) + onDelOneCollaborator: async ({ groupId, tmbId }) => { + if (tmbId) { + return deleteDatasetCollaborators({ + datasetId, + tmbId + }); + } else if (groupId) { + return deleteDatasetCollaborators({ + datasetId, + groupId + }); + } + } }} /> diff --git a/projects/app/src/pages/dataset/list/component/List.tsx b/projects/app/src/pages/dataset/list/component/List.tsx index f1570616526..f677b7882a4 100644 --- a/projects/app/src/pages/dataset/list/component/List.tsx +++ b/projects/app/src/pages/dataset/list/component/List.tsx @@ -34,7 +34,6 @@ import MyBox from '@fastgpt/web/components/common/MyBox'; import { useI18n } from '@/web/context/I18n'; import { useTranslation } from 'next-i18next'; import { useUserStore } from '@/web/support/user/useUserStore'; -import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import SideTag from './SideTag'; @@ -441,23 +440,34 @@ function List() { onGetCollaboratorList: () => getCollaboratorList(editPerDataset._id), permissionList: DatasetPermissionList, onUpdateCollaborators: ({ - members = [], // TODO: remove default value after group is ready + members, + groups, permission }: { members?: string[]; + groups?: string[]; permission: number; }) => { return postUpdateDatasetCollaborators({ members, + groups, permission, datasetId: editPerDataset._id }); }, - onDelOneCollaborator: (tmbId: string) => - deleteDatasetCollaborators({ - datasetId: editPerDataset._id, - tmbId - }), + onDelOneCollaborator: async ({ tmbId, groupId }) => { + if (tmbId) { + return deleteDatasetCollaborators({ + datasetId: editPerDataset._id, + tmbId + }); + } else if (groupId) { + return deleteDatasetCollaborators({ + datasetId: editPerDataset._id, + groupId + }); + } + }, refreshDeps: [editPerDataset._id, editPerDataset.inheritPermission] }} onClose={() => setEditPerDatasetIndex(undefined)} diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index a3db766af5e..985b6f86710 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -52,7 +52,6 @@ const Dataset = () => { loadMyDatasets, refetchFolderDetail, folderDetail, - setEditedDataset, setMoveDatasetId, onDelDataset, onUpdateDataset, @@ -243,23 +242,33 @@ const Dataset = () => { onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), permissionList: DatasetPermissionList, onUpdateCollaborators: ({ - members = [], // TODO: remove the default value after group is ready + members, + groups, permission }: { members?: string[]; + groups?: string[]; permission: number; - }) => { - return postUpdateDatasetCollaborators({ + }) => + postUpdateDatasetCollaborators({ members, + groups, permission, datasetId: folderDetail._id - }); - }, - onDelOneCollaborator: (tmbId: string) => - deleteDatasetCollaborators({ - datasetId: folderDetail._id, - tmbId }), + onDelOneCollaborator: async ({ tmbId, groupId }) => { + if (tmbId) { + return deleteDatasetCollaborators({ + datasetId: folderDetail._id, + tmbId + }); + } else if (groupId) { + return deleteDatasetCollaborators({ + datasetId: folderDetail._id, + groupId + }); + } + }, refreshDeps: [folderDetail._id, folderDetail.inheritPermission] }} /> diff --git a/projects/app/src/web/core/app/constants.ts b/projects/app/src/web/core/app/constants.ts index ec69273324a..47b3ab83052 100644 --- a/projects/app/src/web/core/app/constants.ts +++ b/projects/app/src/web/core/app/constants.ts @@ -2,8 +2,6 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppDetailType } from '@fastgpt/global/core/app/type.d'; import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; -import { NullPermission } from '@fastgpt/global/support/permission/constant'; -import { i18nT } from '@fastgpt/web/i18n/utils'; export const defaultApp: AppDetailType = { _id: '', name: 'AI', @@ -18,7 +16,6 @@ export const defaultApp: AppDetailType = { teamTags: [], edges: [], version: 'v2', - defaultPermission: NullPermission, permission: new AppPermission(), inheritPermission: false }; diff --git a/projects/app/src/web/core/dataset/api/collaborator.ts b/projects/app/src/web/core/dataset/api/collaborator.ts index 05e76876f32..9e75607a4f1 100644 --- a/projects/app/src/web/core/dataset/api/collaborator.ts +++ b/projects/app/src/web/core/dataset/api/collaborator.ts @@ -11,5 +11,5 @@ export const getCollaboratorList = (datasetId: string) => export const postUpdateDatasetCollaborators = (body: UpdateDatasetCollaboratorBody) => POST('/proApi/core/dataset/collaborator/update', body); -export const deleteDatasetCollaborators = ({ ...params }: DatasetCollaboratorDeleteParams) => - DELETE('/proApi/core/dataset/collaborator/delete', { ...params }); +export const deleteDatasetCollaborators = (params: DatasetCollaboratorDeleteParams) => + DELETE('/proApi/core/dataset/collaborator/delete', params); diff --git a/projects/app/src/web/support/user/useUserStore.ts b/projects/app/src/web/support/user/useUserStore.ts index 70ae2cba18e..5cf25a22325 100644 --- a/projects/app/src/web/support/user/useUserStore.ts +++ b/projects/app/src/web/support/user/useUserStore.ts @@ -28,6 +28,7 @@ type State = { loadAndGetTeamMembers: (init?: boolean) => Promise; teamMemberGroups: MemberGroupListType; + myGroups: MemberGroupListType; loadAndGetGroups: (init?: boolean) => Promise; }; @@ -106,6 +107,7 @@ export const useUserStore = create()( return res; }, teamMemberGroups: [], + myGroups: [], loadAndGetGroups: async (init = false) => { if (!useSystemStore.getState()?.feConfigs?.isPlus) return []; @@ -116,6 +118,9 @@ export const useUserStore = create()( const res = await getGroupList(); set((state) => { state.teamMemberGroups = res; + state.myGroups = res.filter((item) => + item.members.map((i) => String(i.tmbId)).includes(String(state.userInfo?.team?.tmbId)) + ); }); return res;