refactor: team permission manager (#3535)

* perf: classify org, group and member

* refactor: team per manager

* fix: missing functions
This commit is contained in:
a.e.
2025-01-07 09:19:23 +08:00
committed by GitHub
parent 2066094047
commit 07cc849877
10 changed files with 892 additions and 571 deletions

View File

@@ -33,3 +33,27 @@ export type UpdatePermissionBody = {
groupId: string; groupId: string;
orgId: string; orgId: string;
}>; }>;
export type CreatePermissionBody = {
tmbId: string[];
groupId: string[];
orgId: string[];
};
export type DeletePermissionQuery = RequireOnlyOne<{
tmbId?: string;
groupId?: string;
orgId?: string;
}>;
export type TeamClbsListType = {
permission: number;
name: string;
avatar: string;
};
export type ListPermissionResponse = {
tmb: (TeamClbsListType & { tmbId: string })[];
group: (TeamClbsListType & { groupId: string })[];
org: (TeamClbsListType & { orgId: string })[];
};

View File

@@ -133,6 +133,9 @@ export async function getResourceAllClbs({
resourceId, resourceId,
groupId: { groupId: {
$exists: false $exists: false
},
orgId: {
$exists: false
} }
}, },
null, null,

View File

@@ -106,6 +106,7 @@
"team.group.set_as_admin": "Set as administrator", "team.group.set_as_admin": "Set as administrator",
"team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first", "team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first",
"team.group.transfer_owner": "transfer owner", "team.group.transfer_owner": "transfer owner",
"team.org.org": "Organization",
"team.manage_collaborators": "Manage Collaborators", "team.manage_collaborators": "Manage Collaborators",
"team.no_collaborators": "No Collaborators", "team.no_collaborators": "No Collaborators",
"team.write_role_member": "", "team.write_role_member": "",

View File

@@ -106,6 +106,7 @@
"team.group.set_as_admin": "设为管理员", "team.group.set_as_admin": "设为管理员",
"team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让",
"team.group.transfer_owner": "转让所有者", "team.group.transfer_owner": "转让所有者",
"team.org.org": "组织",
"team.manage_collaborators": "管理协作者", "team.manage_collaborators": "管理协作者",
"team.no_collaborators": "暂无协作者", "team.no_collaborators": "暂无协作者",
"team.write_role_member": "可写权限", "team.write_role_member": "可写权限",

View File

@@ -106,6 +106,7 @@
"team.group.set_as_admin": "設為管理員", "team.group.set_as_admin": "設為管理員",
"team.group.toast.can_not_delete_owner": "無法刪除擁有者,請先轉移擁有權", "team.group.toast.can_not_delete_owner": "無法刪除擁有者,請先轉移擁有權",
"team.group.transfer_owner": "轉移擁有者", "team.group.transfer_owner": "轉移擁有者",
"team.org.org": "組織",
"team.manage_collaborators": "管理協作者", "team.manage_collaborators": "管理協作者",
"team.no_collaborators": "目前沒有協作者", "team.no_collaborators": "目前沒有協作者",
"team.write_role_member": "可寫入權限", "team.write_role_member": "可寫入權限",

View File

@@ -1,399 +1,11 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import { ChevronDownIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Checkbox,
Flex,
Grid,
HStack,
ModalBody,
ModalFooter
} from '@chakra-ui/react';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import MyAvatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags';
import { CollaboratorContext } from './context'; import { CollaboratorContext } from './context';
import { AddModalPropsType } from './MemberModal';
export type AddModalPropsType = { import MemberModal from './MemberModal';
onClose: () => void;
mode?: 'member' | 'all';
};
function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
const { t } = useTranslation(); const context = useContextSelector(CollaboratorContext, (v) => v);
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs, myOrgs } = return <MemberModal onClose={onClose} mode={mode} collaboratorContext={context} />;
useUserStore();
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } =
useContextSelector(CollaboratorContext, (v) => v);
const [searchText, setSearchText] = useState<string>('');
const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } =
useRequest2(
async () => {
if (!userInfo?.team?.teamId) return [[], []];
return Promise.all([
loadAndGetTeamMembers(true),
loadAndGetGroups(true),
loadAndGetOrgs(true)
]);
},
{
manual: false,
refreshDeps: [userInfo?.team?.teamId]
}
);
const filterMembers = useMemo(() => {
return members.filter((item) => {
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 filterOrgs = useMemo(() => {
if (mode !== 'all') return [];
return orgs.filter((item) => {
if (item.path === '') return false; // exclude root org
if (!permission.isOwner && !myOrgs.find((i) => String(i._id) === String(item._id)))
return false;
if (!searchText) return true;
return item.name.includes(searchText);
});
}, [orgs, searchText, myOrgs, mode, permission]);
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
const perLabel = useMemo(() => {
return getPerLabelList(selectedPermission).join('、');
}, [getPerLabelList, selectedPermission]);
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
() =>
onUpdateCollaborators({
members: selectedMemberIdList,
groups: selectedGroupIdList,
orgs: selectedOrgIdList,
permission: selectedPermission
}),
{
successToast: t('common:common.Add Success'),
errorToast: 'Error',
onSuccess() {
onClose();
}
}
);
return (
<MyModal
isOpen
onClose={onClose}
iconSrc="modal/AddClb"
title={t('user:team.add_collaborator')}
minW="800px"
h={'100%'}
isCentered
isLoading={loadingMembersAndGroups}
>
<ModalBody flex={'1'}>
<Grid
border="1px solid"
borderColor="myGray.200"
borderRadius="0.5rem"
gridTemplateColumns="1fr 1fr"
h={'100%'}
>
<Flex flexDirection="column" borderRight="1px solid" borderColor="myGray.200" p="4">
<SearchInput
placeholder={t('user:search_user')}
bgColor="myGray.50"
onChange={(e) => setSearchText(e.target.value)}
/>
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{filterOrgs.map((org) => {
const onChange = () => {
setSelectedOrgIdList((state) => {
if (state.includes(org._id)) {
return state.filter((v) => v !== org._id);
}
return [...state, org._id];
});
};
const collaborator = collaboratorList.find((v) => v.orgId === org._id);
return (
<HStack
justifyContent="space-between"
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedOrgIdList.includes(org._id)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<Checkbox isChecked={selectedOrgIdList.includes(org._id)} />
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{org.name}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
{filterGroups.map((group) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(group._id)) {
return state.filter((v) => v !== group._id);
}
return [...state, group._id];
});
};
const collaborator = collaboratorList.find((v) => v.groupId === group._id);
return (
<HStack
justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedGroupIdList.includes(group._id)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<Checkbox isChecked={selectedGroupIdList.includes(group._id)} />
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
{filterMembers.map((member) => {
const onChange = () => {
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
}
return [...state, member.tmbId];
});
};
const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId);
return (
<HStack
justifyContent="space-between"
key={member.tmbId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedMemberIdList.includes(member.tmbId)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<Checkbox
isChecked={selectedMemberIdList.includes(member.tmbId)}
icon={<MyIcon name={'common/check'} w={'12px'} />}
/>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{member.memberName}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
</Flex>
</Flex>
<Flex p="4" flexDirection="column">
<Box>
{`${t('user:has_chosen')}: `}
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
</Box>
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{selectedOrgIdList.map((orgId) => {
const org = orgs.find((v) => String(v._id) === orgId);
return (
<HStack
justifyContent="space-between"
key={orgId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedOrgIdList.includes(orgId) ? { svg: { color: 'myGray.50' } } : {})
}}
onClick={() =>
setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId))
}
>
<MyAvatar src={org?.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{org?.name}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
);
})}
{selectedGroupIdList.map((groupId) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(groupId)) {
return state.filter((v) => v !== groupId);
}
return [...state, groupId];
});
};
const group = groups.find((v) => String(v._id) === groupId);
return (
<HStack
justifyContent="space-between"
key={groupId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedGroupIdList.includes(groupId)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<MyAvatar src={group?.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
);
})}
{selectedMemberIdList.map((tmbId) => {
const member = filterMembers.find((v) => v.tmbId === tmbId);
return member ? (
<HStack
justifyContent="space-between"
key={tmbId}
alignItems="center"
py="2"
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.50' }}
onClick={() =>
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
}
>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius="50%" />
<Box w="full" ml={2}>
{member.memberName}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
) : null;
})}
</Flex>
</Flex>
</Grid>
</ModalBody>
<ModalFooter>
<PermissionSelect
value={selectedPermission}
Button={
<Flex
alignItems={'center'}
bg={'myGray.50'}
border="base"
fontSize={'sm'}
px={3}
borderRadius={'md'}
h={'32px'}
>
{t(perLabel as any)}
<ChevronDownIcon fontSize={'md'} />
</Flex>
}
onChange={(v) => setSelectedPermission(v)}
/>
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
} }
export default AddMemberModal; export default AddMemberModal;

View File

@@ -0,0 +1,569 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import { ChevronDownIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Checkbox,
Flex,
Grid,
HStack,
ModalBody,
ModalFooter,
Tag,
Text
} from '@chakra-ui/react';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import MyAvatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags';
import { MemberManagerPropsType } from './context';
import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
import Path from '@/components/common/folder/Path';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
import { createMemberPermission } from '@/web/support/user/team/api';
export type AddModalPropsType = {
onClose: () => void;
mode?: 'member' | 'all';
};
function MemberModal({
onClose,
mode = 'member',
collaboratorContext: context
}: AddModalPropsType & { collaboratorContext?: MemberManagerPropsType }) {
const { t } = useTranslation();
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs } =
useUserStore();
const [searchText, setSearchText] = useState<string>('');
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const [parentPath, setParentPath] = useState('');
const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } =
useRequest2(
async () => {
if (!userInfo?.team?.teamId) return [[], []];
return Promise.all([
loadAndGetTeamMembers(true),
loadAndGetGroups(true),
loadAndGetOrgs(true)
]);
},
{
manual: false,
refreshDeps: [userInfo?.team?.teamId]
}
);
const currentOrg = useMemo(() => {
const splitPath = parentPath.split('/');
const currentOrgId = splitPath[splitPath.length - 1];
if (!currentOrgId) return;
return orgs.find((org) => org.pathId === currentOrgId);
}, [orgs, parentPath]);
const paths = useMemo(() => {
const splitPath = parentPath.split('/').filter(Boolean);
return splitPath
.map((id) => {
const org = orgs.find((org) => org.pathId === id)!;
if (org.path === '') return;
return {
parentId: getOrgChildrenPath(org),
parentName: org.name
};
})
.filter(Boolean) as ParentTreePathItemType[];
}, [parentPath, orgs]);
const filterMembers = useMemo(() => {
if (!searchText && filterClass !== 'member' && filterClass !== 'org') return [];
if (searchText) return members.filter((item) => item.memberName.includes(searchText));
if (filterClass === 'org') {
if (!currentOrg) return [];
return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId));
}
return members;
}, [members, searchText, filterClass, currentOrg]);
const filterGroups = useMemo(() => {
if (mode !== 'all') return [];
if (!searchText && filterClass !== 'group') return [];
if (searchText) return groups.filter((item) => item.name.includes(searchText));
return groups.filter((item) => {
if (context === undefined || context.permission.isOwner) return true; // owner can see all groups
return !myGroups.find((i) => String(i._id) === String(item._id));
});
}, [groups, searchText, filterClass, myGroups, mode, context]);
const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => {
if (mode !== 'all') return [];
if (!searchText && filterClass !== 'org') return [];
if (searchText) return orgs.filter((item) => item.name.includes(searchText));
if (parentPath === '') {
setParentPath(`/${orgs[0].pathId}`);
return [];
}
return orgs
.filter((org) => org.path === parentPath)
.map((item) => ({
...item,
count:
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
}));
}, [orgs, searchText, filterClass, mode, parentPath]);
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
const [selectedPermission, setSelectedPermission] = useState(
context?.permissionList['read'].value
);
const perLabel = useMemo(() => {
if (context) return context.getPerLabelList(selectedPermission!).join('、');
}, [context, selectedPermission]);
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
() => {
if (context) {
return context.onUpdateCollaborators({
members: selectedMemberIdList,
groups: selectedGroupIdList,
orgs: selectedOrgIdList,
permission: selectedPermission!
});
} else {
return createMemberPermission({
tmbId: selectedMemberIdList,
groupId: selectedGroupIdList,
orgId: selectedOrgIdList
});
}
},
{
successToast: t('common:common.Add Success'),
errorToast: 'Error',
onSuccess() {
onClose();
}
}
);
return (
<MyModal
isOpen
onClose={onClose}
iconSrc="modal/AddClb"
title={t('user:team.add_collaborator')}
minW="800px"
h={'100%'}
maxH={'800px'}
isCentered
isLoading={loadingMembersAndGroups}
>
<ModalBody flex={'1'}>
<Grid
border="1px solid"
borderColor="myGray.200"
borderRadius="0.5rem"
gridTemplateColumns="1fr 1fr"
h={'100%'}
>
<Flex flexDirection="column" borderRight="1px solid" borderColor="myGray.200" p="4">
<SearchInput
placeholder={t('user:search_user')}
bgColor="myGray.50"
onChange={(e) => setSearchText(e.target.value)}
/>
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{!searchText &&
(filterClass === undefined ? (
<>
<HStack
justifyContent="space-between"
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={() => setFilterClass('member')}
>
<MyAvatar src="/imgs/avatar/BlueAvatar.svg" w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{t('user:team.group.members')}
</Box>
<MyIcon name="core/chat/chevronRight" w="16px" />
</HStack>
<HStack
justifyContent="space-between"
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={() => setFilterClass('org')}
>
<MyAvatar src={DEFAULT_ORG_AVATAR} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{t('user:team.org.org')}
</Box>
<MyIcon name="core/chat/chevronRight" w="16px" />
</HStack>
<HStack
justifyContent="space-between"
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={() => setFilterClass('group')}
>
<MyAvatar src={DEFAULT_TEAM_AVATAR} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{t('user:team.group.group')}
</Box>
<MyIcon name="core/chat/chevronRight" w="16px" />
</HStack>
</>
) : (
<Path
paths={[
{
parentId: filterClass,
parentName:
filterClass === 'member'
? t('user:team.group.members')
: filterClass === 'org'
? t('user:team.org.org')
: t('user:team.group.group')
},
...paths
]}
onClick={(parentId) => {
if (parentId === '') {
setFilterClass(undefined);
setParentPath('');
} else if (
parentId === 'member' ||
parentId === 'org' ||
parentId === 'group'
) {
setFilterClass(parentId);
setParentPath('');
} else {
setParentPath(parentId);
}
}}
rootName={t('common:common.Team')}
/>
))}
{filterOrgs.map((org) => {
const onChange = () => {
setSelectedOrgIdList((state) => {
if (state.includes(org._id)) {
return state.filter((v) => v !== org._id);
}
return [...state, org._id];
});
};
const collaborator = context?.collaboratorList?.find((v) => v.orgId === org._id);
return (
<HStack
justifyContent="space-between"
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
<Checkbox
isChecked={selectedOrgIdList.includes(org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack ml="2" w="full" gap="5px">
<Text>{org.name}</Text>
{org.count && (
<>
<Tag size="sm" my="auto">
{org.count}
</Tag>
</>
)}
</HStack>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
{org.count && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={() => {
setParentPath(getOrgChildrenPath(org));
}}
/>
)}
</HStack>
);
})}
{filterGroups.map((group) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(group._id)) {
return state.filter((v) => v !== group._id);
}
return [...state, group._id];
});
};
const collaborator = context?.collaboratorList?.find(
(v) => v.groupId === group._id
);
return (
<HStack
justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
<Checkbox
isChecked={selectedGroupIdList.includes(group._id)}
pointerEvents="none"
/>
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
{filterMembers.map((member) => {
const onChange = () => {
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
}
return [...state, member.tmbId];
});
};
const collaborator = context?.collaboratorList?.find(
(v) => v.tmbId === member.tmbId
);
return (
<HStack
justifyContent="space-between"
key={member.tmbId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={userInfo?.team?.tmbId === member.tmbId ? undefined : onChange}
>
<Checkbox
isChecked={selectedMemberIdList.includes(member.tmbId)}
pointerEvents="none"
isDisabled={userInfo?.team?.tmbId === member.tmbId}
/>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{member.memberName}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
</Flex>
</Flex>
<Flex p="4" flexDirection="column">
<Box>
{`${t('user:has_chosen')}: `}
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
</Box>
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{selectedOrgIdList.map((orgId) => {
const org = orgs.find((v) => String(v._id) === orgId);
return (
<HStack
justifyContent="space-between"
key={orgId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedOrgIdList.includes(orgId) ? { svg: { color: 'myGray.50' } } : {})
}}
onClick={() =>
setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId))
}
>
<MyAvatar src={org?.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{org?.name}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
);
})}
{selectedGroupIdList.map((groupId) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(groupId)) {
return state.filter((v) => v !== groupId);
}
return [...state, groupId];
});
};
const group = groups.find((v) => String(v._id) === groupId);
return (
<HStack
justifyContent="space-between"
key={groupId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedGroupIdList.includes(groupId)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<MyAvatar src={group?.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
);
})}
{selectedMemberIdList.map((tmbId) => {
const member = members.find((v) => v.tmbId === tmbId);
return member ? (
<HStack
justifyContent="space-between"
key={tmbId}
alignItems="center"
py="2"
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.50' }}
onClick={() =>
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
}
>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius="50%" />
<Box w="full" ml={2}>
{member.memberName}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
) : null;
})}
</Flex>
</Flex>
</Grid>
</ModalBody>
<ModalFooter>
{selectedPermission && (
<PermissionSelect
value={selectedPermission}
Button={
<Flex
alignItems={'center'}
bg={'myGray.50'}
border="base"
fontSize={'sm'}
px={3}
borderRadius={'md'}
h={'32px'}
>
{t(perLabel as any)}
<ChevronDownIcon fontSize={'md'} />
</Flex>
}
onChange={(v) => setSelectedPermission(v)}
/>
)}
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
}
export default MemberModal;

View File

@@ -9,15 +9,17 @@ import {
Td, Td,
Th, Th,
Thead, Thead,
Text,
Tr Tr
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getTeamClbs, updateMemberPermission } from '@/web/support/user/team/api'; import {
deleteMemberPermission,
getTeamClbs,
updateMemberPermission
} from '@/web/support/user/team/api';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { TeamContext } from '../context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import Avatar from '@fastgpt/web/components/common/Avatar'; import Avatar from '@fastgpt/web/components/common/Avatar';
import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; import MemberTag from '../../../../../components/support/user/team/Info/MemberTag';
@@ -27,65 +29,62 @@ import {
TeamWritePermissionVal TeamWritePermissionVal
} from '@fastgpt/global/support/permission/user/constant'; } from '@fastgpt/global/support/permission/user/constant';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { useCreation } from 'ahooks'; import { useCreation, useToggle } from 'ahooks';
import { getOrgList } from '@/web/support/user/team/org/api'; import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import MemberModal from '@/components/support/permission/MemberManager/MemberModal';
function PermissionManage() { function PermissionManage({
isOpenAddPermission,
onCloseAddPermission
}: {
isOpenAddPermission: boolean;
onCloseAddPermission: () => void;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { groups, refetchMembers, refetchGroups, members, searchKey } = useContextSelector(
TeamContext,
(v) => v
);
const { const { runAsync: refetchClbs, data: clbs = { tmb: [], group: [], org: [] } } = useRequest2(
data: orgs = [], getTeamClbs,
loading: isLoadingOrgs, {
refresh: refetchOrgs
} = useRequest2(getOrgList, {
manual: false, manual: false,
refreshDeps: [userInfo?.team?.teamId] refreshDeps: [userInfo?.team?.teamId]
}); }
const filteredOrgs = useCreation(
() =>
orgs.filter(
(org) => org.path !== '' && org.name.toLowerCase().includes(searchKey.toLowerCase())
),
[orgs, searchKey]
); );
const { runAsync: refetchClbs, data: clbs = [] } = useRequest2(getTeamClbs, { const [isExpandMember, setExpandMember] = useToggle(true);
manual: false, const [isExpandGroup, setExpandGroup] = useToggle(true);
refreshDeps: [userInfo?.team?.teamId] const [isExpandOrg, setExpandOrg] = useToggle(true);
});
const filteredGroups = useCreation( const members = useCreation(
() => groups?.filter((group) => group.name.toLowerCase().includes(searchKey.toLowerCase())),
[groups, searchKey]
);
const filteredMembers = useCreation(
() => () =>
members clbs.tmb.map((item) => ({
?.filter((member) => member.memberName.toLowerCase().includes(searchKey.toLowerCase())) ...item,
.map((member) => { permission: new TeamPermission({ per: item.permission })
const clb = clbs?.find((clb) => String(clb.tmbId) === String(member.tmbId)); })),
const permission = [clbs]
member.role === 'owner' );
? new TeamPermission({ isOwner: true })
: new TeamPermission({ per: clb?.permission });
return { ...member, permission }; const groups = useCreation(
}), () =>
[clbs, members, searchKey] clbs.group.map((item) => ({
...item,
permission: new TeamPermission({ per: item.permission })
})),
[clbs]
);
const orgs = useCreation(
() =>
clbs.org.map((item) => ({
...item,
permission: new TeamPermission({ per: item.permission })
})),
[clbs]
); );
const { runAsync: onUpdateMemberPermission } = useRequest2(updateMemberPermission, { const { runAsync: onUpdateMemberPermission } = useRequest2(updateMemberPermission, {
onSuccess: () => { onSuccess: () => {
refetchGroups();
refetchMembers();
refetchClbs(); refetchClbs();
refetchOrgs();
} }
}); });
@@ -102,60 +101,60 @@ function PermissionManage() {
per: 'write' | 'manage'; per: 'write' | 'manage';
}) => { }) => {
if (groupId) { if (groupId) {
const group = groups?.find((group) => group._id === groupId); const group = groups?.find((group) => group.groupId === groupId);
if (group) { if (group) {
const permission = new TeamPermission({ per: group.permission.value }); const permission = new TeamPermission({ per: group.permission.value });
switch (per) { switch (per) {
case 'write': case 'write':
permission.addPer(TeamWritePermissionVal); permission.addPer(TeamWritePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
groupId: group._id, groupId: group.groupId,
permission: permission.value permission: permission.value
}); });
case 'manage': case 'manage':
permission.addPer(TeamManagePermissionVal); permission.addPer(TeamManagePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
groupId: group._id, groupId: group.groupId,
permission: permission.value permission: permission.value
}); });
} }
} }
} }
if (orgId) { if (orgId) {
const org = orgs.find((org) => String(org._id) === orgId); const org = orgs.find((org) => String(org.orgId) === orgId);
if (org) { if (org) {
const permission = new TeamPermission({ per: org.permission.value }); const permission = new TeamPermission({ per: org.permission.value });
switch (per) { switch (per) {
case 'write': case 'write':
permission.addPer(TeamWritePermissionVal); permission.addPer(TeamWritePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
orgId: org._id, orgId: org.orgId,
permission: permission.value permission: permission.value
}); });
case 'manage': case 'manage':
permission.addPer(TeamManagePermissionVal); permission.addPer(TeamManagePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
orgId: org._id, orgId: org.orgId,
permission: permission.value permission: permission.value
}); });
} }
} }
} }
if (memberId) { if (memberId) {
const member = filteredMembers?.find((member) => String(member.tmbId) === memberId); const member = members?.find((member) => member.tmbId === memberId);
if (member) { if (member) {
const permission = new TeamPermission({ per: member.permission.value }); const permission = new TeamPermission({ per: member.permission.value });
switch (per) { switch (per) {
case 'write': case 'write':
permission.addPer(TeamWritePermissionVal); permission.addPer(TeamWritePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
memberId: String(member.tmbId), memberId: member.tmbId,
permission: permission.value permission: permission.value
}); });
case 'manage': case 'manage':
permission.addPer(TeamManagePermissionVal); permission.addPer(TeamManagePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
memberId: String(member.tmbId), memberId: member.tmbId,
permission: permission.value permission: permission.value
}); });
} }
@@ -177,40 +176,40 @@ function PermissionManage() {
per: 'write' | 'manage'; per: 'write' | 'manage';
}) => { }) => {
if (groupId) { if (groupId) {
const group = groups?.find((group) => group._id === groupId); const group = groups?.find((group) => group.groupId === groupId);
if (group) { if (group) {
const permission = new TeamPermission({ per: group.permission.value }); const permission = new TeamPermission({ per: group.permission.value });
switch (per) { switch (per) {
case 'write': case 'write':
permission.removePer(TeamWritePermissionVal); permission.removePer(TeamWritePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
groupId: group._id, groupId: group.groupId,
permission: permission.value permission: permission.value
}); });
case 'manage': case 'manage':
permission.removePer(TeamManagePermissionVal); permission.removePer(TeamManagePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
groupId: group._id, groupId: group.groupId,
permission: permission.value permission: permission.value
}); });
} }
} }
} }
if (orgId) { if (orgId) {
const org = orgs.find((org) => String(org._id) === orgId); const org = orgs.find((org) => String(org.orgId) === orgId);
if (org) { if (org) {
const permission = new TeamPermission({ per: org.permission.value }); const permission = new TeamPermission({ per: org.permission.value });
switch (per) { switch (per) {
case 'write': case 'write':
permission.removePer(TeamWritePermissionVal); permission.removePer(TeamWritePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
orgId: org._id, orgId: org.orgId,
permission: permission.value permission: permission.value
}); });
case 'manage': case 'manage':
permission.removePer(TeamManagePermissionVal); permission.removePer(TeamManagePermissionVal);
return onUpdateMemberPermission({ return onUpdateMemberPermission({
orgId: org._id, orgId: org.orgId,
permission: permission.value permission: permission.value
}); });
} }
@@ -239,6 +238,12 @@ function PermissionManage() {
} }
); );
const { runAsync: onDeleteMemberPermission } = useRequest2(deleteMemberPermission, {
onSuccess: () => {
refetchClbs();
}
});
const userManage = userInfo?.permission.hasManagePer; const userManage = userInfo?.permission.hasManagePer;
return ( return (
@@ -247,7 +252,7 @@ function PermissionManage() {
<Thead> <Thead>
<Tr bg={'white !important'}> <Tr bg={'white !important'}>
<Th bg="myGray.100" borderLeftRadius="md" maxW={'150px'}> <Th bg="myGray.100" borderLeftRadius="md" maxW={'150px'}>
{t('user:team.group.group')} / {t('user:team.group.members')} {`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`}
<QuestionTip ml="1" label={t('user:team.group.permission_tip')} /> <QuestionTip ml="1" label={t('user:team.group.permission_tip')} />
</Th> </Th>
<Th bg="myGray.100"> <Th bg="myGray.100">
@@ -255,101 +260,36 @@ function PermissionManage() {
{t('user:team.group.permission.write')} {t('user:team.group.permission.write')}
</Box> </Box>
</Th> </Th>
<Th bg="myGray.100" borderRightRadius="md"> <Th bg="myGray.100">
<Box mx="auto" w="fit-content"> <Box mx="auto" w="fit-content">
{t('user:team.group.permission.manage')} {t('user:team.group.permission.manage')}
<QuestionTip ml="1" label={t('user:team.group.manage_tip')} /> <QuestionTip ml="1" label={t('user:team.group.manage_tip')} />
</Box> </Box>
</Th> </Th>
<Th bg="myGray.100" borderRightRadius="md">
<Box mx="auto" w="fit-content">
{t('common:common.Action')}
</Box>
</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{filteredGroups?.map((group) => ( <Tr overflow={'unset'} border="none">
<Tr key={group._id} overflow={'unset'} border="none"> <HStack paddingX={'8px'} paddingY={'4px'}>
<Td border="none"> <MyIconButton
<MemberTag icon={isExpandMember ? 'common/downArrowFill' : 'common/rightArrowFill'}
name={ onClick={setExpandMember.toggle}
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
avatar={group.avatar}
/> />
</Td> <Text>{t('user:team.group.members')}</Text>
<Td border="none"> </HStack>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={group.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group._id, per: 'write' })
: onRemovePermission({ groupId: group._id, per: 'write' })
}
/>
</Box>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={group.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group._id, per: 'manage' })
: onRemovePermission({ groupId: group._id, per: 'manage' })
}
/>
</Box>
</Td>
</Tr> </Tr>
))} {isExpandMember &&
{filteredGroups?.length > 0 && filteredOrgs?.length > 0 && ( members.map((member) => (
<Tr borderBottom={'1px solid'} borderColor={'myGray.300'} />
)}
{filteredOrgs?.map((org) => (
<Tr key={org._id} overflow={'unset'} border="none">
<Td border="none">
<MemberTag name={org.name} avatar={org.avatar} />
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={org.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ orgId: org._id, per: 'write' })
: onRemovePermission({ orgId: org._id, per: 'write' })
}
/>
</Box>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={org.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ orgId: org._id, per: 'manage' })
: onRemovePermission({ orgId: org._id, per: 'manage' })
}
/>
</Box>
</Td>
</Tr>
))}
{filteredOrgs?.length > 0 && filteredMembers?.length > 0 && (
<Tr borderBottom={'1px solid'} borderColor={'myGray.300'} />
)}
{filteredMembers?.map((member) => (
<Tr key={member.tmbId} overflow={'unset'} border="none"> <Tr key={member.tmbId} overflow={'unset'} border="none">
<Td border="none"> <Td border="none">
<HStack> <HStack>
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} /> <Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box>{member.memberName}</Box> <Box>{member.name}</Box>
</HStack> </HStack>
</Td> </Td>
<Td border="none"> <Td border="none">
@@ -378,10 +318,148 @@ function PermissionManage() {
/> />
</Box> </Box>
</Td> </Td>
{userManage &&
!member.permission.isOwner &&
userInfo?.team.tmbId !== member.tmbId && (
<Td border="none">
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() => onDeleteMemberPermission({ tmbId: String(member.tmbId) })}
/>
</Box>
</Td>
)}
</Tr>
))}
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr overflow={'unset'} border="none">
<HStack paddingX={'8px'} paddingY={'4px'}>
<MyIconButton
icon={isExpandOrg ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandOrg.toggle}
/>
<Text>{t('user:team.org.org')}</Text>
</HStack>
</Tr>
{isExpandOrg &&
orgs.map((org) => (
<Tr key={org.orgId} overflow={'unset'} border="none">
<Td border="none">
<MemberTag name={org.name} avatar={org.avatar} />
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={org.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ orgId: org.orgId, per: 'write' })
: onRemovePermission({ orgId: org.orgId, per: 'write' })
}
/>
</Box>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={org.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ orgId: org.orgId, per: 'manage' })
: onRemovePermission({ orgId: org.orgId, per: 'manage' })
}
/>
</Box>
</Td>
{userInfo?.permission.isOwner && (
<Td border="none">
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() => onDeleteMemberPermission({ orgId: org.orgId })}
/>
</Box>
</Td>
)}
</Tr>
))}
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr overflow={'unset'} border="none">
<HStack paddingX={'8px'} paddingY={'4px'}>
<MyIconButton
icon={isExpandGroup ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandGroup.toggle}
/>
<Text>{t('user:team.group.group')}</Text>
</HStack>
</Tr>
{isExpandGroup &&
groups.map((group) => (
<Tr key={group.groupId} overflow={'unset'} border="none">
<Td border="none">
<MemberTag
name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
avatar={group.avatar}
/>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={group.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group.groupId, per: 'write' })
: onRemovePermission({ groupId: group.groupId, per: 'write' })
}
/>
</Box>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={group.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group.groupId, per: 'manage' })
: onRemovePermission({ groupId: group.groupId, per: 'manage' })
}
/>
</Box>
</Td>
{userInfo?.permission.isOwner && (
<Td border="none">
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() => onDeleteMemberPermission({ groupId: group.groupId })}
/>
</Box>
</Td>
)}
</Tr> </Tr>
))} ))}
</Tbody> </Tbody>
</Table> </Table>
{isOpenAddPermission && (
<MemberModal
onClose={() => {
refetchClbs();
onCloseAddPermission();
}}
mode="all"
/>
)}
</TableContainer> </TableContainer>
); );
} }

View File

@@ -75,6 +75,11 @@ const Team = () => {
onOpen: onOpenManageGroupMember, onOpen: onOpenManageGroupMember,
onClose: onCloseManageGroupMember onClose: onCloseManageGroupMember
} = useDisclosure(); } = useDisclosure();
const {
isOpen: isOpenAddPermission,
onOpen: onOpenAddPermission,
onClose: onCloseAddPermission
} = useDisclosure();
const { runAsync: onLeaveTeam } = useRequest2( const { runAsync: onLeaveTeam } = useRequest2(
async () => { async () => {
@@ -268,6 +273,18 @@ const Team = () => {
/> />
</Box> </Box>
)} )}
{teamTab === TeamTabEnum.permission && userInfo?.team.permission.hasManagePer && (
<Button
variant={'primary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="common/add2" w={'14px'} />}
onClick={onOpenAddPermission}
>
{t('common:common.Add')}
</Button>
)}
</Flex> </Flex>
</Flex> </Flex>
<Box flex={'1 0 0'} overflow={'auto'}> <Box flex={'1 0 0'} overflow={'auto'}>
@@ -276,7 +293,12 @@ const Team = () => {
<GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} /> <GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} />
)} )}
{teamTab === TeamTabEnum.org && <OrgManage />} {teamTab === TeamTabEnum.org && <OrgManage />}
{teamTab === TeamTabEnum.permission && <PermissionManage />} {teamTab === TeamTabEnum.permission && (
<PermissionManage
isOpenAddPermission={isOpenAddPermission}
onCloseAddPermission={onCloseAddPermission}
/>
)}
</Box> </Box>
</Box> </Box>
{isOpenInvite && userInfo?.team?.teamId && ( {isOpenInvite && userInfo?.team?.teamId && (

View File

@@ -1,5 +1,10 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import { UpdatePermissionBody } from '@fastgpt/global/support/permission/collaborator'; import {
CreatePermissionBody,
DeletePermissionQuery,
ListPermissionResponse,
UpdatePermissionBody
} from '@fastgpt/global/support/permission/collaborator';
import { import {
CreateTeamProps, CreateTeamProps,
InviteMemberProps, InviteMemberProps,
@@ -15,7 +20,6 @@ import {
} from '@fastgpt/global/support/user/team/type.d'; } from '@fastgpt/global/support/user/team/type.d';
import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type'; import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type';
import { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
/* --------------- team ---------------- */ /* --------------- team ---------------- */
export const getTeamList = (status: `${TeamMemberSchema['status']}`) => export const getTeamList = (status: `${TeamMemberSchema['status']}`) =>
@@ -40,12 +44,18 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave'); export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave');
export const getTeamClbs = () => export const getTeamClbs = () =>
GET<ResourcePermissionType[]>(`/proApi/support/user/team/collaborator/list`); GET<ListPermissionResponse>(`/proApi/support/user/team/collaborator/list`);
/* -------------- team collaborator -------------------- */ /* -------------- team collaborator -------------------- */
export const updateMemberPermission = (data: UpdatePermissionBody) => export const updateMemberPermission = (data: UpdatePermissionBody) =>
PUT('/proApi/support/user/team/collaborator/updatePermission', data); PUT('/proApi/support/user/team/collaborator/updatePermission', data);
export const createMemberPermission = (data: CreatePermissionBody) =>
POST('/proApi/support/user/team/collaborator/create', data);
export const deleteMemberPermission = (id: DeletePermissionQuery) =>
DELETE('/proApi/support/user/team/collaborator/delete', id);
/* --------------- team tags ---------------- */ /* --------------- team tags ---------------- */
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`); export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
export const loadTeamTagsByDomain = (domain: string) => export const loadTeamTagsByDomain = (domain: string) =>