feat: team permission refine (#4494) (#4498)

* feat: team permission refine (#4402)

* chore: team permission extend

* feat: manage team permission

* chore: api auth

* fix: i18n

* feat: add initv493

* fix: test, org auth manager

* test: app test for refined permission

* update init sh

* fix: add/remove manage permission (#4427)

* fix: add/remove manage permission

* fix: github action fastgpt-test

* fix: mock create model

* fix: team write permission

* fix: ts

* account permission

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2025-04-10 11:11:54 +08:00
committed by GitHub
parent 80f41dd2a9
commit 199f454b6b
51 changed files with 1116 additions and 460 deletions

View File

@@ -1,20 +1,24 @@
import React from 'react';
import { useTranslation } from 'next-i18next';
import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import PermissionTags from './PermissionTags';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import OrgTags from '../../user/team/OrgTags';
import Tag from '@fastgpt/web/components/common/Tag';
function MemberItemCard({
avatar,
key,
onChange,
onChange: _onChange,
isChecked,
onDelete,
name,
permission,
orgs
orgs,
addOnly,
rightSlot
}: {
avatar: string;
key: string;
@@ -23,44 +27,66 @@ function MemberItemCard({
onDelete?: () => void;
name: string;
permission?: PermissionValueType;
addOnly?: boolean;
orgs?: string[];
rightSlot?: React.ReactNode;
}) {
const isAdded = addOnly && !!permission;
const onChange = () => {
if (!isAdded) _onChange();
};
const { t } = useTranslation();
return (
<>
<HStack
justifyContent="space-between"
alignItems="center"
key={key}
px="3"
py="2"
borderRadius="sm"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
{isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />}
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
<HStack
justifyContent="space-between"
alignItems="center"
key={key}
px="3"
py="2"
borderRadius="sm"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
{isChecked !== undefined && (
<Checkbox isChecked={isChecked} pointerEvents="none" disabled={isAdded} />
)}
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full">
<Box fontSize={'sm'}>{name}</Box>
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
<Box w="full">
<Box fontSize={'sm'} className="textEllipsis" maxW="300px">
{name}
</Box>
{permission && <PermissionTags permission={permission} />}
{onDelete !== undefined && (
<MyIcon
name="common/closeLight"
w="1rem"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
onClick={onDelete}
/>
)}
</HStack>
</>
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
</Box>
{!isAdded && permission && <PermissionTags permission={permission} />}
{isAdded && (
<Tag
mixBlendMode={'multiply'}
colorSchema="blue"
border="none"
py={2}
px={3}
fontSize={'xs'}
>
{t('user:team.collaborator.added')}
</Tag>
)}
{onDelete !== undefined && (
<MyIcon
name="common/closeLight"
w="1rem"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
onClick={onDelete}
/>
)}
{rightSlot}
</HStack>
);
}

View File

@@ -1,17 +1,6 @@
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 { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter, 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';
@@ -19,27 +8,26 @@ 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 { useEffect, useMemo, useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags';
import {
DEFAULT_ORG_AVATAR,
DEFAULT_TEAM_AVATAR,
DEFAULT_USER_AVATAR
} from '@fastgpt/global/common/system/constants';
import Path from '@/components/common/folder/Path';
import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
import { useContextSelector } from 'use-context-selector';
import { CollaboratorContext } from './context';
import { getTeamMembers } from '@/web/support/user/team/api';
import { getGroupList } from '@/web/support/user/team/group/api';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import MemberItemCard from './MemberItemCard';
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
import _ from 'lodash';
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
import { ValueOf } from 'next/dist/shared/lib/constants';
const HoverBoxStyle = {
bgColor: 'myGray.50',
@@ -131,8 +119,8 @@ function MemberModal({
members: selectedMemberList.map((item) => item.tmbId),
groups: selectedGroupList.map((item) => item._id),
orgs: selectedOrgList.map((item) => item._id),
permission: selectedPermission!
}),
permission: addOnly ? undefined : selectedPermission!
} as UpdateClbPermissionProps<ValueOf<typeof addOnly>>),
{
successToast: t('common:common.Add Success'),
onSuccess() {
@@ -285,6 +273,7 @@ function MemberModal({
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
return (
<MemberItemCard
addOnly={addOnly}
avatar={member.avatar}
key={member.tmbId}
name={member.memberName}
@@ -321,49 +310,33 @@ function MemberModal({
};
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
return (
<HStack
justifyContent="space-between"
<MemberItemCard
avatar={org.avatar}
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={!!selectedOrgList.find((v) => v._id === org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack w="full">
<Text>{org.name}</Text>
{org.total && (
<>
<Tag size="sm" my="auto">
{org.total}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.total && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
onClickOrg(org);
// setPath(getOrgChildrenPath(org));
e.stopPropagation();
}}
/>
)}
</HStack>
name={org.name}
onChange={onChange}
addOnly={addOnly}
permission={collaborator?.permission.value}
isChecked={!!selectedOrgList.find((v) => String(v._id) === String(org._id))}
rightSlot={
org.total && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
onClickOrg(org);
// setPath(getOrgChildrenPath(org));
e.stopPropagation();
}}
/>
)
}
/>
);
});
return searchKey ? (
@@ -372,6 +345,9 @@ function MemberModal({
<OrgMemberScrollData>
{Orgs}
{orgMembers.map((member) => {
const isChecked = !!selectedMemberList.find(
(v) => v.tmbId === member.tmbId
);
return (
<MemberItemCard
avatar={member.avatar}
@@ -385,7 +361,9 @@ function MemberModal({
return [...state, member];
});
}}
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
isChecked={isChecked}
permission={member.permission.value}
addOnly={addOnly && !!member.permission.value}
orgs={member.orgs}
/>
);
@@ -414,6 +392,7 @@ function MemberModal({
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
addOnly={addOnly}
/>
);
})}

View File

@@ -110,7 +110,15 @@ const CollaboratorContextProvider = ({
} = useRequest2(
async () => {
if (feConfigs.isPlus) {
return onGetCollaboratorList();
const data = await onGetCollaboratorList();
return data.map((item) => {
return {
...item,
permission: new Permission({
per: item.permission.value
})
};
});
}
return [];
},

View File

@@ -10,7 +10,14 @@ function OrgTags({ orgs, type = 'simple' }: { orgs?: string[]; type?: 'simple' |
label={
<VStack gap="1" alignItems={'start'}>
{orgs.map((org, index) => (
<Box key={index} fontSize="sm" fontWeight={400} color="myGray.500">
<Box
key={index}
fontSize="sm"
fontWeight={400}
color="myGray.500"
maxW={'300px'}
className="textEllipsis"
>
{org.slice(1)}
</Box>
))}