Permission (#1687)
Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
@@ -1212,6 +1212,8 @@
|
||||
"Tools": "Tools"
|
||||
},
|
||||
"permission": {
|
||||
"Default permission": "Default permission",
|
||||
"Manage": "Manage",
|
||||
"Private": "Private",
|
||||
"Private Tip": "Only available to oneself",
|
||||
"Public": "Team",
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"team": {
|
||||
"Add manager": "Add manager"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +585,8 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {}
|
||||
"training": {
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -1218,6 +1219,8 @@
|
||||
"Tools": "工具"
|
||||
},
|
||||
"permission": {
|
||||
"Default permission": "默认权限",
|
||||
"Manage": "管理",
|
||||
"Private": "私有",
|
||||
"Private Tip": "仅自己可用",
|
||||
"Public": "团队",
|
||||
@@ -1574,7 +1577,7 @@
|
||||
"Processing invitations": "处理邀请",
|
||||
"Processing invitations Tips": "你有{{amount}}个需要处理的团队邀请",
|
||||
"Reinvite": "重新邀请",
|
||||
"Remove Member Confirm Tip": "确认将 {{username}} 移出团队?其所有资源将转让到团队创建者的账户内。",
|
||||
"Remove Member Confirm Tip": "确认将 {{username}} 移出团队?",
|
||||
"Remove Member Failed": "移除团队成员异常",
|
||||
"Remove Member Success": "移除团队成员成功",
|
||||
"Remove Member Tip": "移出团队",
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"team": {
|
||||
"Add manager": "添加管理员"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700634007483" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19559" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M65.361 856H967v160H65.361zM65.531 805.062l2.295-188.972L676.402 7.515 863.078 194.19 254.503 802.766 65.53 805.062z m50.726-169.52l-1.46 120.254 120.254-1.46L116.257 635.54z m507.147-507.147L742.198 247.19l52.163-52.163L675.567 76.232l-52.163 52.163z" fill="#1AA5FF" p-id="19560"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700634007483" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19559" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M65.361 856H967v160H65.361zM65.531 805.062l2.295-188.972L676.402 7.515 863.078 194.19 254.503 802.766 65.53 805.062z m50.726-169.52l-1.46 120.254 120.254-1.46L116.257 635.54z m507.147-507.147L742.198 247.19l52.163-52.163L675.567 76.232l-52.163 52.163z" p-id="19560"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 614 B |
@@ -0,0 +1,49 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
export enum defaultPermissionEnum {
|
||||
private = 'private',
|
||||
read = 'read',
|
||||
edit = 'edit'
|
||||
}
|
||||
|
||||
type Props = Omit<BoxProps, 'onChange'> & {
|
||||
per: PermissionValueType;
|
||||
defaultPer: PermissionValueType;
|
||||
readPer: PermissionValueType;
|
||||
writePer: PermissionValueType;
|
||||
onChange: (v: PermissionValueType) => void;
|
||||
};
|
||||
|
||||
const DefaultPermissionList = ({
|
||||
per,
|
||||
defaultPer,
|
||||
readPer,
|
||||
writePer,
|
||||
onChange,
|
||||
...styles
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultPermissionSelectList = [
|
||||
{ label: '仅协作者访问', value: defaultPer },
|
||||
{ label: '团队可访问', value: readPer },
|
||||
{ label: '团队可编辑', value: writePer }
|
||||
];
|
||||
|
||||
return (
|
||||
<Box {...styles}>
|
||||
<MySelect
|
||||
list={defaultPermissionSelectList}
|
||||
value={per}
|
||||
onchange={(v) => {
|
||||
onChange(v);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultPermissionList;
|
||||
@@ -1,19 +1,34 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
|
||||
import { Box, Flex, FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
const PermissionIconText = ({
|
||||
permission,
|
||||
defaultPermission,
|
||||
...props
|
||||
}: { permission: `${PermissionTypeEnum}` } & FlexProps) => {
|
||||
}: {
|
||||
permission?: `${PermissionTypeEnum}`;
|
||||
defaultPermission?: PermissionValueType;
|
||||
} & FlexProps) => {
|
||||
const { t } = useTranslation();
|
||||
return PermissionTypeMap[permission] ? (
|
||||
|
||||
const per = useMemo(() => {
|
||||
if (permission) return permission;
|
||||
if (defaultPermission) {
|
||||
return new Permission({ per: defaultPermission }).hasReadPer ? 'public' : 'private';
|
||||
}
|
||||
return 'private';
|
||||
}, [defaultPermission, permission]);
|
||||
|
||||
return PermissionTypeMap[per] ? (
|
||||
<Flex alignItems={'center'} {...props}>
|
||||
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
|
||||
<MyIcon name={PermissionTypeMap[per]?.iconLight as any} w={'14px'} />
|
||||
<Box ml={'2px'} lineHeight={1}>
|
||||
{t(PermissionTypeMap[permission]?.label)}
|
||||
{t(PermissionTypeMap[per]?.label)}
|
||||
</Box>
|
||||
</Flex>
|
||||
) : null;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Grid,
|
||||
ModalBody,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Input,
|
||||
Checkbox,
|
||||
ModalFooter,
|
||||
Button,
|
||||
useToast
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyAvatar from '@/components/Avatar';
|
||||
import { useMemo, useState } from 'react';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
export type AddModalPropsType = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
const toast = useToast();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPreLabelList } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const {
|
||||
data: members = [],
|
||||
refetch: refetchMembers,
|
||||
isLoading: loadingMembers
|
||||
} = useQuery(['getMembers', userInfo?.team?.teamId], async () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
const members = await getTeamMembers();
|
||||
return members;
|
||||
});
|
||||
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 [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
||||
const perLabel = useMemo(() => {
|
||||
return getPreLabelList(selectedPermission).join('、');
|
||||
}, [getPreLabelList, selectedPermission]);
|
||||
|
||||
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
|
||||
mutationFn: () => {
|
||||
return onUpdateCollaborators(selectedMemberIdList, selectedPermission);
|
||||
},
|
||||
successToast: '添加成功',
|
||||
errorToast: 'Error',
|
||||
onSuccess() {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} iconSrc="modal/AddClb" title="添加协作者" minW="800px">
|
||||
<ModalBody>
|
||||
<MyBox
|
||||
isLoading={loadingMembers}
|
||||
display={'grid'}
|
||||
minH="400px"
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="55% 45%"
|
||||
>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
borderRight="1px solid"
|
||||
borderColor="myGray.200"
|
||||
p="4"
|
||||
minH="200px"
|
||||
>
|
||||
<InputGroup alignItems="center" h="32px" my="2" py="1">
|
||||
<InputLeftElement>
|
||||
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder="搜索用户名"
|
||||
fontSize="lg"
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
{filterMembers.map((member) => {
|
||||
const onChange = () => {
|
||||
if (selectedMemberIdList.includes(member.tmbId)) {
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== member.tmbId));
|
||||
} else {
|
||||
setSelectedMembers([...selectedMemberIdList, member.tmbId]);
|
||||
}
|
||||
};
|
||||
const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<Flex
|
||||
key={member.tmbId}
|
||||
mt="1"
|
||||
py="1"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size="lg"
|
||||
mr="3"
|
||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
onClick={onChange}
|
||||
w="full"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Flex flexDirection="row" alignItems="center">
|
||||
<MyAvatar src={member.avatar} w="32px" />
|
||||
<Box ml="2">{member.memberName}</Box>
|
||||
</Flex>
|
||||
{!!collaborator && <PermissionTags permission={collaborator.permission} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex p="4" flexDirection="column">
|
||||
<Box>已选: {selectedMemberIdList.length}</Box>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
{selectedMemberIdList.map((tmbId) => {
|
||||
const member = filterMembers.find((v) => v.tmbId === tmbId);
|
||||
return member ? (
|
||||
<Flex
|
||||
key={tmbId}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
>
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<Box w="full" fontSize="lg">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="16px"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={() =>
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
) : null;
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<PermissionSelect
|
||||
value={selectedPermission}
|
||||
Button={
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
bg={'myGray.50'}
|
||||
border="base"
|
||||
fontSize={'sm'}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
h={'32px'}
|
||||
>
|
||||
{perLabel}
|
||||
<ChevronDownIcon fontSize={'lg'} />
|
||||
</Flex>
|
||||
}
|
||||
onChange={(v) => setSelectedPermission(v)}
|
||||
/>
|
||||
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
|
||||
确认
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
ModalBody,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Td,
|
||||
Box,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { CollaboratorContext } from './context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
|
||||
export type ManageModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function ManageModal({ onClose }: ManageModalProps) {
|
||||
const { userInfo } = useUserStore();
|
||||
const { collaboratorList, onUpdateCollaborators, onDelOneCollaborator } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const { mutate: onDelete, isLoading: isDeleting } = useRequest({
|
||||
mutationFn: (tmbId: string) => onDelOneCollaborator(tmbId)
|
||||
});
|
||||
|
||||
const { mutate: onUpdate, isLoading: isUpdating } = useRequest({
|
||||
mutationFn: ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => {
|
||||
return onUpdateCollaborators([tmbId], per);
|
||||
},
|
||||
successToast: '更新成功',
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
const loading = isDeleting || isUpdating;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isLoading={loading}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
minW="600px"
|
||||
title="管理协作者"
|
||||
iconSrc="common/settingLight"
|
||||
>
|
||||
<ModalBody>
|
||||
<TableContainer borderRadius="md" minH="400px">
|
||||
<Table>
|
||||
<Thead bg="myGray.100">
|
||||
<Tr>
|
||||
<Th border="none">名称</Th>
|
||||
<Th border="none">权限</Th>
|
||||
<Th border="none">操作</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{collaboratorList?.map((item) => {
|
||||
return (
|
||||
<Tr
|
||||
key={item.tmbId}
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
>
|
||||
<Td border="none">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={item.avatar} w="24px" mr={2} />
|
||||
{item.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td border="none">
|
||||
<PermissionTags permission={item.permission} />
|
||||
</Td>
|
||||
<Td border="none">
|
||||
{item.tmbId !== userInfo?.team?.tmbId && (
|
||||
<PermissionSelect
|
||||
Button={
|
||||
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
||||
}
|
||||
value={item.permission}
|
||||
onChange={(per) => {
|
||||
onUpdate({
|
||||
tmbId: item.tmbId,
|
||||
per
|
||||
});
|
||||
}}
|
||||
onDelete={() => {
|
||||
onDelete(item.tmbId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{collaboratorList?.length === 0 && <EmptyTip text={'暂无协作者'} />}
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManageModal;
|
||||
@@ -0,0 +1,243 @@
|
||||
import {
|
||||
ButtonProps,
|
||||
Flex,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
Box,
|
||||
Radio,
|
||||
useOutsideClick
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
|
||||
export type PermissionSelectProps = {
|
||||
value?: PermissionValueType;
|
||||
onChange: (value: PermissionValueType) => void;
|
||||
trigger?: 'hover' | 'click';
|
||||
offset?: [number, number];
|
||||
Button: React.ReactNode;
|
||||
|
||||
onDelete?: () => void;
|
||||
} & Omit<ButtonProps, 'onChange' | 'value'>;
|
||||
|
||||
const MenuStyle = {
|
||||
py: 2,
|
||||
px: 3,
|
||||
_hover: {
|
||||
bg: 'myGray.50'
|
||||
},
|
||||
borderRadius: 'md',
|
||||
cursor: 'pointer',
|
||||
fontSize: 'sm'
|
||||
};
|
||||
|
||||
function PermissionSelect({
|
||||
value,
|
||||
onChange,
|
||||
trigger = 'click',
|
||||
offset = [0, 5],
|
||||
Button,
|
||||
width = 'auto',
|
||||
onDelete,
|
||||
...props
|
||||
}: PermissionSelectProps) {
|
||||
const { t } = useTranslation();
|
||||
const { permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const closeTimer = useRef<any>();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const permissionSelectList = useMemo(() => {
|
||||
const list = Object.entries(permissionList).map(([key, value]) => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.value,
|
||||
description: value.description,
|
||||
checkBoxType: value.checkBoxType
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
singleCheckBoxList: list.filter((item) => item.checkBoxType === 'single'),
|
||||
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
|
||||
};
|
||||
}, [permissionList]);
|
||||
const selectedSingleValue = useMemo(() => {
|
||||
const per = new Permission({ per: value });
|
||||
|
||||
if (per.hasManagePer) return permissionList['manage'].value;
|
||||
if (per.hasWritePer) return permissionList['write'].value;
|
||||
|
||||
return permissionList['read'].value;
|
||||
}, [permissionList, value]);
|
||||
const selectedMultipleValues = useMemo(() => {
|
||||
const per = new Permission({ per: value });
|
||||
|
||||
return permissionSelectList.multipleCheckBoxList
|
||||
.filter((item) => {
|
||||
return per.checkPer(item.value);
|
||||
})
|
||||
.map((item) => item.value);
|
||||
}, [permissionSelectList.multipleCheckBoxList, value]);
|
||||
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: () => {
|
||||
setIsOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'}>
|
||||
<Box
|
||||
ref={ref}
|
||||
onMouseEnter={() => {
|
||||
if (trigger === 'hover') {
|
||||
setIsOpen(true);
|
||||
}
|
||||
clearTimeout(closeTimer.current);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (trigger === 'hover') {
|
||||
closeTimer.current = setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position={'relative'}
|
||||
onClickCapture={() => {
|
||||
if (trigger === 'click') {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuButton
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
/>
|
||||
<Box position={'relative'} cursor={'pointer'} userSelect={'none'}>
|
||||
{Button}
|
||||
</Box>
|
||||
</Box>
|
||||
<MenuList
|
||||
minW={isOpen ? `${width}px !important` : 0}
|
||||
p="3"
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
|
||||
}
|
||||
zIndex={99}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{/* The list of single select permissions */}
|
||||
{permissionSelectList.singleCheckBoxList.map((item) => {
|
||||
const change = () => {
|
||||
const per = new Permission({ per: value });
|
||||
per.removePer(selectedSingleValue);
|
||||
per.addPer(item.value);
|
||||
onChange(per.value);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={item.value}
|
||||
{...(selectedSingleValue === item.value
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {})}
|
||||
{...MenuStyle}
|
||||
onClick={change}
|
||||
maxW={['70vw', '300px']}
|
||||
>
|
||||
<Radio size="lg" isChecked={selectedSingleValue === item.value} />
|
||||
<Box ml={4}>
|
||||
<Box>{item.name}</Box>
|
||||
<Box color={'myGray.500'}>{item.description}</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* <MyDivider my={3} />
|
||||
|
||||
{multipleValues.length > 0 && <Box m="4">其他权限(多选)</Box>} */}
|
||||
|
||||
{/* The list of multiple select permissions */}
|
||||
{/* {list
|
||||
.filter((item) => item.type === 'multiple')
|
||||
.map((item) => {
|
||||
const change = () => {
|
||||
if (checkPermission(valueState, item.value)) {
|
||||
setValueState(new Permission(valueState).remove(item.value).value);
|
||||
} else {
|
||||
setValueState(new Permission(valueState).add(item.value).value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
key={item.value}
|
||||
{...(checkPermission(valueState, item.value)
|
||||
? {
|
||||
color: 'primary.500',
|
||||
bg: 'myWhite.300'
|
||||
}
|
||||
: {})}
|
||||
whiteSpace="pre-wrap"
|
||||
flexDirection="row"
|
||||
justifyContent="start"
|
||||
p="2"
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size="lg"
|
||||
isChecked={checkPermission(valueState, item.value)}
|
||||
onChange={change}
|
||||
/>
|
||||
<Flex px="4" flexDirection="column" onClick={change}>
|
||||
<Box fontWeight="500">{item.name}</Box>
|
||||
<Box fontWeight="400">{item.description}</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}*/}
|
||||
{onDelete && (
|
||||
<>
|
||||
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />
|
||||
<Flex
|
||||
{...MenuStyle}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<MyIcon name="delete" w="20px" color="red.600" />
|
||||
<Box color="red.600">{t('common.Delete')}</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</MenuList>
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(PermissionSelect);
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorContext } from './context';
|
||||
|
||||
export type PermissionTagsProp = {
|
||||
permission: PermissionValueType;
|
||||
};
|
||||
|
||||
function PermissionTags({ permission }: PermissionTagsProp) {
|
||||
const { getPreLabelList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||
|
||||
const perTagList = getPreLabelList(permission);
|
||||
|
||||
return (
|
||||
<Flex gap="2" alignItems="center">
|
||||
{perTagList.map((item) => (
|
||||
<Tag
|
||||
mixBlendMode={'multiply'}
|
||||
key={item}
|
||||
colorSchema="blue"
|
||||
border="none"
|
||||
py={2}
|
||||
px={3}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{item}
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default PermissionTags;
|
||||
@@ -0,0 +1,107 @@
|
||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { PermissionList } from '@fastgpt/global/support/permission/constant';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { PermissionListType, PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
|
||||
export type MemberManagerInputPropsType = {
|
||||
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
||||
permissionList: PermissionListType;
|
||||
onUpdateCollaborators: (tmbIds: string[], permission: PermissionValueType) => any;
|
||||
onDelOneCollaborator: (tmbId: string) => any;
|
||||
};
|
||||
export type MemberManagerPropsType = MemberManagerInputPropsType & {
|
||||
collaboratorList: CollaboratorItemType[];
|
||||
refetchCollaboratorList: () => void;
|
||||
isFetchingCollaborator: boolean;
|
||||
getPreLabelList: (per: PermissionValueType) => string[];
|
||||
};
|
||||
|
||||
type CollaboratorContextType = MemberManagerPropsType & {};
|
||||
|
||||
export const CollaboratorContext = createContext<CollaboratorContextType>({
|
||||
collaboratorList: [],
|
||||
permissionList: PermissionList,
|
||||
onUpdateCollaborators: function () {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onDelOneCollaborator: function () {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
getPreLabelList: function (): string[] {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
refetchCollaboratorList: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isFetchingCollaborator: false
|
||||
});
|
||||
|
||||
export const CollaboratorContextProvider = ({
|
||||
onGetCollaboratorList,
|
||||
permissionList,
|
||||
onUpdateCollaborators,
|
||||
onDelOneCollaborator,
|
||||
children
|
||||
}: MemberManagerInputPropsType & {
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
data: collaboratorList = [],
|
||||
refetch: refetchCollaboratorList,
|
||||
isLoading: isFetchingCollaborator
|
||||
} = useQuery(['collaboratorList'], onGetCollaboratorList);
|
||||
const onUpdateCollaboratorsThen = async (tmbIds: string[], permission: PermissionValueType) => {
|
||||
await onUpdateCollaborators(tmbIds, permission);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThem = async (tmbId: string) => {
|
||||
await onDelOneCollaborator(tmbId);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
|
||||
const getPreLabelList = useCallback(
|
||||
(per: PermissionValueType) => {
|
||||
const Per = new Permission({ per });
|
||||
const labels: string[] = [];
|
||||
|
||||
if (Per.hasManagePer) {
|
||||
labels.push(permissionList['manage'].name);
|
||||
} else if (Per.hasWritePer) {
|
||||
labels.push(permissionList['write'].name);
|
||||
} else {
|
||||
labels.push(permissionList['read'].name);
|
||||
}
|
||||
|
||||
Object.values(permissionList).forEach((item) => {
|
||||
if (item.checkBoxType === 'multiple') {
|
||||
if (Per.checkPer(item.value)) {
|
||||
labels.push(item.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return labels;
|
||||
},
|
||||
[permissionList]
|
||||
);
|
||||
|
||||
const contextValue = {
|
||||
onGetCollaboratorList,
|
||||
collaboratorList,
|
||||
refetchCollaboratorList,
|
||||
isFetchingCollaborator,
|
||||
permissionList,
|
||||
onUpdateCollaborators: onUpdateCollaboratorsThen,
|
||||
onDelOneCollaborator: onDelOneCollaboratorThem,
|
||||
getPreLabelList
|
||||
};
|
||||
return (
|
||||
<CollaboratorContext.Provider value={contextValue}>{children}</CollaboratorContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Flex, Box, Button, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { AddMemberModal } from './AddMemberModal';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import ManageModal from './ManageModal';
|
||||
import {
|
||||
CollaboratorContext,
|
||||
CollaboratorContextProvider,
|
||||
MemberManagerInputPropsType
|
||||
} from './context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
function MemberManger() {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isOpen: isOpenAddMember,
|
||||
onOpen: onOpenAddMember,
|
||||
onClose: onCloseAddMember
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenManageModal,
|
||||
onOpen: onOpenManageModal,
|
||||
onClose: onCloseManageModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full">
|
||||
<Box>协作者</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common.Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* member list */}
|
||||
<MyBox
|
||||
isLoading={isFetchingCollaborator}
|
||||
mt={2}
|
||||
bg="myGray.100"
|
||||
borderRadius="md"
|
||||
size={'md'}
|
||||
>
|
||||
{collaboratorList?.length === 0 ? (
|
||||
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
|
||||
暂无协作者
|
||||
</Box>
|
||||
) : (
|
||||
<Flex gap="2" p={1.5}>
|
||||
{collaboratorList?.map((member) => {
|
||||
return (
|
||||
<Tag px="4" py="1.5" bgColor="white" key={member.tmbId} width="fit-content">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<TagLabel mx="2">{member.name}</TagLabel>
|
||||
</Flex>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
|
||||
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Render(props: MemberManagerInputPropsType) {
|
||||
return (
|
||||
<CollaboratorContextProvider {...props}>
|
||||
<MemberManger />
|
||||
</CollaboratorContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Render);
|
||||
@@ -1,103 +0,0 @@
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, MenuButton, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberRoleMap,
|
||||
TeamMemberStatusMap
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
|
||||
function MemberTable() {
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const openRemoveMember = useContextSelector(TeamContext, (v) => v.openRemoveMember);
|
||||
const onRemoveMember = useContextSelector(TeamContext, (v) => v.onRemoveMember);
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<TableContainer overflow={'unset'}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead bg={'myWhite.400'}>
|
||||
<Tr>
|
||||
<Th>{t('common.Username')}</Th>
|
||||
<Th>{t('user.team.Role')}</Th>
|
||||
<Th>{t('common.Status')}</Th>
|
||||
<Th>{t('common.Action')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||
{item.memberName}
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||
</Td>
|
||||
<Td>
|
||||
{hasManage(
|
||||
members.find((item) => item.tmbId === userInfo?.team.tmbId)?.permission!
|
||||
) &&
|
||||
item.role !== TeamMemberRoleEnum.owner &&
|
||||
item.tmbId !== userInfo?.team.tmbId && (
|
||||
<MyMenu
|
||||
width={20}
|
||||
trigger="hover"
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
bg: 'myWhite.600'
|
||||
}}
|
||||
borderRadius={'md'}
|
||||
px={2}
|
||||
py={1}
|
||||
lineHeight={1}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'primary.500' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
label: t('user.team.Remove Member Tip'),
|
||||
onClick: () =>
|
||||
openRemoveMember(
|
||||
() =>
|
||||
onRemoveMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.Remove Member Confirm Tip', {
|
||||
username: item.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberTable;
|
||||
@@ -1,72 +1,94 @@
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { TeamContext } from '.';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import RowTabs from '../../../../common/Rowtabs';
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import MemberTable from './MemberTable';
|
||||
import PermissionManage from './PermissionManage';
|
||||
import MemberTable from './components/MemberTable';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
type TabListType = Pick<React.ComponentProps<typeof RowTabs>, 'list'>['list'];
|
||||
import { TeamModalContext } from './context';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { delLeaveTeam } from '@/web/support/user/team/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
enum TabListEnum {
|
||||
member = 'member',
|
||||
permission = 'permission'
|
||||
}
|
||||
|
||||
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
|
||||
const InviteModal = dynamic(() => import('./components/InviteModal'));
|
||||
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
|
||||
|
||||
function TeamCard() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { userT } = useI18n();
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const onOpenInvite = useContextSelector(TeamContext, (v) => v.onOpenInvite);
|
||||
const onOpenTeamTagsAsync = useContextSelector(TeamContext, (v) => v.onOpenTeamTagsAsync);
|
||||
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||
const onLeaveTeam = useContextSelector(TeamContext, (v) => v.onLeaveTeam);
|
||||
const { myTeams, refetchTeams, members, refetchMembers, setEditTeamData, onSwitchTeam } =
|
||||
useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
const { userInfo, teamPlanStatus } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
content: t('user.team.member.Confirm Leave')
|
||||
});
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const Tablist: TabListType = [
|
||||
{
|
||||
icon: 'support/team/memberLight',
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: TabListEnum.member
|
||||
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
||||
mutationFn: async (teamId?: string) => {
|
||||
if (!teamId) return;
|
||||
const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0];
|
||||
// change to personal team
|
||||
// get members
|
||||
onSwitchTeam(defaultTeam.teamId);
|
||||
return delLeaveTeam(teamId);
|
||||
},
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
label: t('common.Role'),
|
||||
value: TabListEnum.permission
|
||||
}
|
||||
];
|
||||
onSuccess() {
|
||||
refetchTeams();
|
||||
},
|
||||
errorToast: t('user.team.Leave Team Failed')
|
||||
});
|
||||
|
||||
const {
|
||||
isOpen: isOpenTeamTagsAsync,
|
||||
onOpen: onOpenTeamTagsAsync,
|
||||
onClose: onCloseTeamTagsAsync
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||
|
||||
const Tablist = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: 'support/team/memberLight',
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: TabListEnum.member
|
||||
},
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
label: t('common.Role'),
|
||||
value: TabListEnum.permission
|
||||
}
|
||||
],
|
||||
[members.length, t]
|
||||
);
|
||||
const [tab, setTab] = useState(Tablist[0].value);
|
||||
|
||||
const [tab, setTab] = useState<string>(Tablist[0].value);
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
flex={'1'}
|
||||
h={['auto', '100%']}
|
||||
bg={'white'}
|
||||
minH={['50vh', 'auto']}
|
||||
h={'100%'}
|
||||
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
||||
>
|
||||
<Flex
|
||||
@@ -107,41 +129,38 @@ function TeamCard() {
|
||||
list={Tablist}
|
||||
value={tab}
|
||||
onChange={(v) => {
|
||||
setTab(v as string);
|
||||
setTab(v as TabListEnum);
|
||||
}}
|
||||
></RowTabs>
|
||||
{/* ctrl buttons */}
|
||||
<Flex alignItems={'center'}>
|
||||
{hasManage(
|
||||
members.find((item) => item.tmbId.toString() === userInfo?.team.tmbId.toString())
|
||||
?.permission!
|
||||
) &&
|
||||
tab === 'member' && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="common/inviteLight" w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
||||
{tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="common/inviteLight" w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
@@ -155,13 +174,14 @@ function TeamCard() {
|
||||
{t('user.team.Team Tags Async')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.role !== TeamMemberRoleEnum.owner && (
|
||||
{!userInfo?.team.permission.isOwner && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
|
||||
isLoading={isLoadingLeaveTeam}
|
||||
onClick={() => {
|
||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||
}}
|
||||
@@ -173,8 +193,18 @@ function TeamCard() {
|
||||
</Flex>
|
||||
|
||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
||||
{tab === 'member' ? <MemberTable /> : <PermissionManage />}
|
||||
{tab === TabListEnum.member && <MemberTable />}
|
||||
{tab === TabListEnum.permission && <PermissionManage />}
|
||||
</Box>
|
||||
|
||||
{isOpenInvite && userInfo?.team?.teamId && (
|
||||
<InviteModal
|
||||
teamId={userInfo.team.teamId}
|
||||
onClose={onCloseInvite}
|
||||
onSuccess={refetchMembers}
|
||||
/>
|
||||
)}
|
||||
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
|
||||
<ConfirmLeaveTeamModal />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -3,20 +3,15 @@ import Avatar from '@/components/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import EditModal, { defaultForm } from './EditModal';
|
||||
import { defaultForm } from './components/EditInfoModal';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
import { TeamModalContext } from './context';
|
||||
|
||||
function TeamList() {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const editTeamData = useContextSelector(TeamContext, (v) => v.editTeamData);
|
||||
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||
const myTeams = useContextSelector(TeamContext, (v) => v.myTeams);
|
||||
const refetchTeam = useContextSelector(TeamContext, (v) => v.refetchTeam);
|
||||
const onSwitchTeam = useContextSelector(TeamContext, (v) => v.onSwitchTeam);
|
||||
// get the list of teams
|
||||
const { userInfo } = useUserStore();
|
||||
const { myTeams, onSwitchTeam, setEditTeamData } = useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -100,16 +95,6 @@ function TeamList() {
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
{!!editTeamData && (
|
||||
<EditModal
|
||||
defaultData={editTeamData}
|
||||
onClose={() => setEditTeamData(undefined)}
|
||||
onSuccess={() => {
|
||||
refetchTeam();
|
||||
initUserInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
@@ -14,7 +14,7 @@ import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api';
|
||||
import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
|
||||
export type FormDataType = CreateTeamProps & {
|
||||
export type EditTeamFormDataType = CreateTeamProps & {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
@@ -28,17 +28,17 @@ function EditModal({
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultData?: FormDataType;
|
||||
defaultData?: EditTeamFormDataType;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<CreateTeamProps>({
|
||||
const { register, setValue, handleSubmit, watch } = useForm<CreateTeamProps>({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
const avatar = watch('avatar');
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png,.svg',
|
||||
@@ -57,7 +57,6 @@ function EditModal({
|
||||
maxH: 300
|
||||
});
|
||||
setValue('avatar', src);
|
||||
setRefresh((state) => !state);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: getErrText(err, t('common.Select File Failed')),
|
||||
@@ -80,7 +79,7 @@ function EditModal({
|
||||
errorToast: t('common.Create Failed')
|
||||
});
|
||||
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||
mutationFn: async (data: FormDataType) => {
|
||||
mutationFn: async (data: EditTeamFormDataType) => {
|
||||
if (!data.id) return Promise.resolve('');
|
||||
return putUpdateTeam({
|
||||
name: data.name,
|
||||
@@ -110,7 +109,7 @@ function EditModal({
|
||||
<MyTooltip label={t('common.Set Avatar')}>
|
||||
<Avatar
|
||||
flexShrink={0}
|
||||
src={getValues('avatar')}
|
||||
src={avatar}
|
||||
w={['28px', '32px']}
|
||||
h={['28px', '32px']}
|
||||
cursor={'pointer'}
|
||||
@@ -0,0 +1,116 @@
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, MenuButton, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberRoleMap,
|
||||
TeamMemberStatusMap
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { TeamModalContext } from '../context';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
function MemberTable() {
|
||||
const { userInfo } = useUserStore();
|
||||
const { t } = useTranslation();
|
||||
const { members, refetchMembers } = useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({});
|
||||
|
||||
const { mutate: onRemoveMember, isLoading: isRemovingMember } = useRequest({
|
||||
mutationFn: delRemoveMember,
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
},
|
||||
successToast: t('user.team.Remove Member Success'),
|
||||
errorToast: t('user.team.Remove Member Failed')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyBox isLoading={isRemovingMember}>
|
||||
<TableContainer overflow={'unset'}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead bg={'myWhite.400'}>
|
||||
<Tr>
|
||||
<Th>{t('common.Username')}</Th>
|
||||
<Th>{t('user.team.Role')}</Th>
|
||||
<Th>{t('common.Status')}</Th>
|
||||
<Th>{t('common.Action')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||
{item.memberName}
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||
</Td>
|
||||
<Td>
|
||||
{userInfo?.team.permission.hasManagePer &&
|
||||
item.role !== TeamMemberRoleEnum.owner &&
|
||||
item.tmbId !== userInfo?.team.tmbId && (
|
||||
<MyMenu
|
||||
width={20}
|
||||
trigger="hover"
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
bg: 'myWhite.600'
|
||||
}}
|
||||
borderRadius={'md'}
|
||||
px={2}
|
||||
py={1}
|
||||
lineHeight={1}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'primary.500' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
label: t('user.team.Remove Member Tip'),
|
||||
onClick: () =>
|
||||
openRemoveMember(
|
||||
() =>
|
||||
onRemoveMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.Remove Member Confirm Tip', {
|
||||
username: item.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
||||
<ConfirmRemoveMemberModal />
|
||||
</TableContainer>
|
||||
</MyBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberTable;
|
||||
@@ -14,43 +14,30 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '.';
|
||||
import {
|
||||
hasManage,
|
||||
constructPermission,
|
||||
PermissionList
|
||||
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { TeamModalContext } from '../../context';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const { userT } = useI18n();
|
||||
const { userInfo } = useUserStore();
|
||||
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||
const members = useContextSelector(TeamContext, (v) =>
|
||||
v.members.filter((member) => {
|
||||
return member.tmbId != userInfo!.team.tmbId && !hasManage(member.permission);
|
||||
})
|
||||
);
|
||||
const { members, refetchMembers } = useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
const [selected, setSelected] = useState<typeof members>([]);
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [searched, setSearched] = useState<typeof members>(members);
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
const { mutate: submit, isLoading } = useRequest({
|
||||
mutationFn: async () => {
|
||||
console.log(selected);
|
||||
return updateMemberPermission({
|
||||
teamId: userInfo!.team.teamId,
|
||||
permission: constructPermission([
|
||||
PermissionList['Read'],
|
||||
PermissionList['Write'],
|
||||
PermissionList['Manage']
|
||||
]).value,
|
||||
permission: ManagePermissionVal,
|
||||
memberIds: selected.map((item) => {
|
||||
return item.tmbId;
|
||||
})
|
||||
@@ -63,18 +50,23 @@ function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSucces
|
||||
successToast: '成功',
|
||||
errorToast: '失败'
|
||||
});
|
||||
|
||||
const filterMembers = useMemo(() => {
|
||||
return members.filter((member) => {
|
||||
if (member.permission.isOwner) return false;
|
||||
if (!searchKey) return true;
|
||||
return !!member.memberName.includes(searchKey);
|
||||
});
|
||||
}, [members, searchKey]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc={'support/permission/collaborator'}
|
||||
iconSrc={'modal/AddClb'}
|
||||
maxW={['90vw']}
|
||||
minW={['900px']}
|
||||
overflow={'unset'}
|
||||
title={
|
||||
<Box>
|
||||
<Box>添加管理员</Box>
|
||||
</Box>
|
||||
}
|
||||
title={userT('team.Add manager')}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody py={6} px={10}>
|
||||
@@ -95,15 +87,12 @@ function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSucces
|
||||
fontSize="lg"
|
||||
bg={'myGray.50'}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value);
|
||||
setSearched(
|
||||
members.filter((member) => member.memberName.includes(e.target.value))
|
||||
);
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Flex flexDirection="column" mt={3}>
|
||||
{searched.map((member) => {
|
||||
{filterMembers.map((member) => {
|
||||
return (
|
||||
<Flex
|
||||
py="2"
|
||||
@@ -164,7 +153,7 @@ function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSucces
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<Button h={'30px'} isLoading={isLoading} onClick={submit}>
|
||||
确定
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
@@ -1,26 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TeamContext } from '.';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import AddManagerModal from './AddManager';
|
||||
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||
import { delMemberPermission } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
constructPermission,
|
||||
hasManage,
|
||||
PermissionList
|
||||
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
import { TeamModalContext } from '../../context';
|
||||
import { TeamPermissionList } from '@fastgpt/global/support/permission/user/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const AddManagerModal = dynamic(() => import('./AddManager'));
|
||||
|
||||
function PermissionManage() {
|
||||
const { t } = useTranslation();
|
||||
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||
const { userInfo } = useUserStore();
|
||||
const { members, refetchMembers } = useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAddManager,
|
||||
@@ -28,30 +26,19 @@ function PermissionManage() {
|
||||
onClose: onCloseAddManager
|
||||
} = useDisclosure();
|
||||
|
||||
const { mutate: removeManager } = useRequest({
|
||||
const { mutate: removeManager, isLoading: isRemovingManager } = useRequest({
|
||||
mutationFn: async (memberId: string) => {
|
||||
return updateMemberPermission({
|
||||
teamId: userInfo!.team.teamId,
|
||||
permission: constructPermission([PermissionList['Read'], PermissionList['Write']]).value,
|
||||
memberIds: [memberId]
|
||||
});
|
||||
return delMemberPermission(memberId);
|
||||
},
|
||||
successToast: 'Success',
|
||||
errorToast: 'Error',
|
||||
successToast: '删除管理员成功',
|
||||
errorToast: '删除管理员异常',
|
||||
onSuccess: () => {
|
||||
refetchMembers();
|
||||
},
|
||||
onError: () => {
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} flex={'1'} h={['auto', '100%']} bg={'white'}>
|
||||
{isOpenAddManager && (
|
||||
<AddManagerModal onClose={onCloseAddManager} onSuccess={onCloseAddManager} />
|
||||
)}
|
||||
|
||||
<MyBox h={'100%'} isLoading={isRemovingManager} bg={'white'}>
|
||||
<Flex
|
||||
mx={'5'}
|
||||
flexDirection={'row'}
|
||||
@@ -73,7 +60,7 @@ function PermissionManage() {
|
||||
px={'3'}
|
||||
borderRadius={'sm'}
|
||||
>
|
||||
可邀请, 删除成员
|
||||
{TeamPermissionList['manage'].description}
|
||||
</Box>
|
||||
</Flex>
|
||||
{userInfo?.team.role === 'owner' && (
|
||||
@@ -93,7 +80,7 @@ function PermissionManage() {
|
||||
</Flex>
|
||||
<Flex mt="4" mx="4">
|
||||
{members.map((member) => {
|
||||
if (hasManage(member.permission) && member.role !== TeamMemberRoleEnum.owner) {
|
||||
if (member.permission.hasManagePer && !member.permission.isOwner) {
|
||||
return (
|
||||
<Tag key={member.memberName} mx={'2'} px="4" py="2" bg="myGray.100">
|
||||
<Avatar src={member.avatar} w="20px" />
|
||||
@@ -106,6 +93,7 @@ function PermissionManage() {
|
||||
w="16px"
|
||||
color="myGray.500"
|
||||
cursor="pointer"
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
removeManager(member.tmbId);
|
||||
}}
|
||||
@@ -116,7 +104,11 @@ function PermissionManage() {
|
||||
}
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{isOpenAddManager && (
|
||||
<AddManagerModal onClose={onCloseAddManager} onSuccess={onCloseAddManager} />
|
||||
)}
|
||||
</MyBox>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import type { EditTeamFormDataType } from './components/EditInfoModal';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getTeamList, getTeamMembers, putSwitchTeam } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const EditInfoModal = dynamic(() => import('./components/EditInfoModal'));
|
||||
|
||||
type TeamModalContextType = {
|
||||
myTeams: TeamTmbItemType[];
|
||||
refetchTeams: () => void;
|
||||
isLoading: boolean;
|
||||
onSwitchTeam: (teamId: string) => void;
|
||||
|
||||
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
||||
members: TeamMemberItemType[];
|
||||
refetchMembers: () => void;
|
||||
};
|
||||
|
||||
export const TeamModalContext = createContext<TeamModalContextType>({
|
||||
myTeams: [],
|
||||
isLoading: false,
|
||||
onSwitchTeam: function (teamId: string): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
setEditTeamData: function (value: React.SetStateAction<EditTeamFormDataType | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
members: [],
|
||||
refetchTeams: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
refetchMembers: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isFetching: isLoadingTeams,
|
||||
refetch: refetchTeams
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
|
||||
// member action
|
||||
const {
|
||||
data: members = [],
|
||||
refetch: refetchMembers,
|
||||
isLoading: loadingMembers
|
||||
} = useQuery(['getMembers', userInfo?.team?.teamId], () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers();
|
||||
});
|
||||
|
||||
const { mutate: onSwitchTeam, isLoading: isSwitchingTeam } = useRequest({
|
||||
mutationFn: async (teamId: string) => {
|
||||
await putSwitchTeam(teamId);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
});
|
||||
|
||||
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers;
|
||||
|
||||
const contextValue = {
|
||||
myTeams,
|
||||
refetchTeams,
|
||||
isLoading,
|
||||
onSwitchTeam,
|
||||
|
||||
// create | update team
|
||||
setEditTeamData,
|
||||
members,
|
||||
refetchMembers
|
||||
};
|
||||
|
||||
return (
|
||||
<TeamModalContext.Provider value={contextValue}>
|
||||
{children}
|
||||
{!!editTeamData && (
|
||||
<EditInfoModal
|
||||
defaultData={editTeamData}
|
||||
onClose={() => setEditTeamData(undefined)}
|
||||
onSuccess={() => {
|
||||
refetchTeams();
|
||||
initUserInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</TeamModalContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,130 +1,24 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
getTeamMembers,
|
||||
delRemoveMember,
|
||||
getTeamList,
|
||||
delLeaveTeam,
|
||||
putSwitchTeam
|
||||
} from '@/web/support/user/team/api';
|
||||
import { Box, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import TeamList from './TeamList';
|
||||
import TeamCard from './TeamCard';
|
||||
import { FormDataType } from './EditModal';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
import { TeamModalContext, TeamModalContextProvider } from './context';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
|
||||
export const TeamContext = createContext<{}>({} as any);
|
||||
|
||||
export const TeamContext = createContext<{
|
||||
editTeamData?: FormDataType;
|
||||
setEditTeamData: React.Dispatch<React.SetStateAction<any>>;
|
||||
members: Awaited<ReturnType<typeof getTeamMembers>>;
|
||||
myTeams: Awaited<ReturnType<typeof getTeamList>>;
|
||||
refetchTeam: ReturnType<typeof useQuery>['refetch'];
|
||||
onSwitchTeam: ReturnType<typeof useRequest>['mutate'];
|
||||
refetchMembers: ReturnType<typeof useQuery>['refetch'];
|
||||
openRemoveMember: ReturnType<typeof useConfirm>['openConfirm'];
|
||||
onOpenInvite: ReturnType<typeof useDisclosure>['onOpen'];
|
||||
onOpenTeamTagsAsync: ReturnType<typeof useDisclosure>['onOpen'];
|
||||
onRemoveMember: ReturnType<typeof useRequest>['mutate'];
|
||||
onLeaveTeam: ReturnType<typeof useRequest>['mutate'];
|
||||
}>({} as any);
|
||||
type Props = { onClose: () => void };
|
||||
|
||||
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const TeamManageModal = ({ onClose }: Props) => {
|
||||
const { Loading } = useLoading();
|
||||
const { isLoading } = useContextSelector(TeamModalContext, (v) => v);
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isFetching: isLoadingTeams,
|
||||
refetch: refetchTeam
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
|
||||
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenTeamTagsAsync,
|
||||
onOpen: onOpenTeamTagsAsync,
|
||||
onClose: onCloseTeamTagsAsync
|
||||
} = useDisclosure();
|
||||
|
||||
// member action
|
||||
const { data: members = [], refetch: refetchMembers } = useQuery(
|
||||
['getMembers', userInfo?.team?.teamId],
|
||||
() => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers();
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
|
||||
mutationFn: delRemoveMember,
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
},
|
||||
successToast: t('user.team.Remove Member Success'),
|
||||
errorToast: t('user.team.Remove Member Failed')
|
||||
});
|
||||
|
||||
const defaultTeam = useMemo(
|
||||
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
||||
[myTeams]
|
||||
);
|
||||
|
||||
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
||||
mutationFn: async (teamId: string) => {
|
||||
const token = await putSwitchTeam(teamId);
|
||||
token && setToken(token);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
});
|
||||
|
||||
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
||||
mutationFn: async (teamId?: string) => {
|
||||
if (!teamId) return;
|
||||
// change to personal team
|
||||
// get members
|
||||
onSwitchTeam(defaultTeam.teamId);
|
||||
return delLeaveTeam(teamId);
|
||||
},
|
||||
onSuccess() {
|
||||
refetchTeam();
|
||||
},
|
||||
errorToast: t('user.team.Leave Team Failed')
|
||||
});
|
||||
|
||||
return !!userInfo?.team ? (
|
||||
<TeamContext.Provider
|
||||
value={{
|
||||
myTeams: myTeams,
|
||||
refetchTeam: refetchTeam,
|
||||
onSwitchTeam: onSwitchTeam,
|
||||
members: members,
|
||||
refetchMembers: refetchMembers,
|
||||
openRemoveMember: openRemoveMember,
|
||||
onOpenInvite: onOpenInvite,
|
||||
onOpenTeamTagsAsync: onOpenTeamTagsAsync,
|
||||
onRemoveMember: onRemoveMember,
|
||||
editTeamData: editTeamData,
|
||||
setEditTeamData: setEditTeamData,
|
||||
onLeaveTeam: onLeaveTeam
|
||||
}}
|
||||
>
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
@@ -137,24 +31,23 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
>
|
||||
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
|
||||
<TeamList />
|
||||
<TeamCard />
|
||||
<Loading
|
||||
loading={isLoadingRemoveMember || isLoadingTeams || isLoadingLeaveTeam || isSwitchTeam}
|
||||
fixed={false}
|
||||
/>
|
||||
<Box h={'100%'} flex={'1 0 0'}>
|
||||
<TeamCard />
|
||||
</Box>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</Box>
|
||||
</MyModal>
|
||||
{isOpenInvite && userInfo?.team?.teamId && (
|
||||
<InviteModal
|
||||
teamId={userInfo.team.teamId}
|
||||
onClose={onCloseInvite}
|
||||
onSuccess={refetchMembers}
|
||||
/>
|
||||
)}
|
||||
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
|
||||
<ConfirmRemoveMemberModal />
|
||||
</TeamContext.Provider>
|
||||
) : null;
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TeamManageModal);
|
||||
const Render = (props: Props) => {
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
return !!userInfo?.team ? (
|
||||
<TeamModalContextProvider>
|
||||
<TeamManageModal {...props} />
|
||||
</TeamModalContextProvider>
|
||||
) : null;
|
||||
};
|
||||
export default React.memo(Render);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Image, useDisclosure } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
@@ -10,7 +10,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
|
||||
|
||||
const TeamMenu = () => {
|
||||
const theme = useTheme();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
@@ -38,7 +37,7 @@ const TeamMenu = () => {
|
||||
} else {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common.Business edition features')
|
||||
title: t('common.system.Commercial version function')
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
||||
1
projects/app/src/global/core/app/api.d.ts
vendored
1
projects/app/src/global/core/app/api.d.ts
vendored
@@ -19,6 +19,7 @@ export type AppUpdateParams = {
|
||||
chatConfig?: AppSchema['chatConfig'];
|
||||
permission?: AppSchema['permission'];
|
||||
teamTags?: AppSchema['teamTags'];
|
||||
defaultPermission?: AppSchema['defaultPermission'];
|
||||
};
|
||||
|
||||
export type PostPublishAppProps = {
|
||||
|
||||
@@ -265,7 +265,7 @@ const MyInfo = () => {
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
{feConfigs?.show_pay && userInfo?.team?.permission.hasWritePer && (
|
||||
<Button variant={'whitePrimary'} size={'sm'} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
|
||||
@@ -105,7 +105,7 @@ const UsageTable = () => {
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.canWrite && (
|
||||
{tmbList.length > 1 && userInfo?.team?.permission.hasWritePer && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('support.user.team.member')}
|
||||
|
||||
@@ -51,7 +51,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay && userInfo?.team.canWrite
|
||||
...(feConfigs?.show_pay && userInfo?.team?.permission.hasWritePer
|
||||
? [
|
||||
{
|
||||
icon: 'support/bill/payRecordLight',
|
||||
@@ -70,7 +70,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(userInfo?.team.canWrite
|
||||
...(userInfo?.team?.permission.hasWritePer
|
||||
? [
|
||||
{
|
||||
icon: 'support/outlink/apikeyLight',
|
||||
|
||||
@@ -3,11 +3,12 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { CreateAppParams } from '@/global/core/app/api.d';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const {
|
||||
@@ -23,7 +24,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
|
||||
@@ -2,12 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { appId } = req.query as { appId: string };
|
||||
@@ -17,7 +18,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
await authApp({ req, authToken: true, appId, per: 'owner' });
|
||||
await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||
|
||||
// 删除对应的聊天
|
||||
await mongoSessionRun(async (session) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
@@ -9,9 +10,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
if (!appId) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { app } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
const { app } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
|
||||
return app;
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import { AppLogsListItemType } from '@/types/app';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import { addDays } from 'date-fns';
|
||||
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -26,7 +27,7 @@ async function handler(
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
const { teamId } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
|
||||
const where = {
|
||||
teamId: new Types.ObjectId(teamId),
|
||||
|
||||
@@ -1,29 +1,63 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import {
|
||||
PerResourceTypeEnum,
|
||||
ReadPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
|
||||
// 凭证校验
|
||||
const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
|
||||
|
||||
// 根据 userId 获取模型信息
|
||||
const myApps = await MongoApp.find(
|
||||
{ ...mongoRPermission({ teamId, tmbId, role }) },
|
||||
'_id avatar name intro tmbId permission'
|
||||
).sort({
|
||||
updateTime: -1
|
||||
const {
|
||||
teamId,
|
||||
tmbId,
|
||||
permission: tmbPer
|
||||
} = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
return myApps.map((app) => ({
|
||||
/* temp: get all apps and per */
|
||||
const [myApps, rpList] = await Promise.all([
|
||||
MongoApp.find({ teamId }, '_id avatar name intro tmbId defaultPermission')
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
.lean(),
|
||||
MongoResourcePermission.find({
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
teamId,
|
||||
tmbId
|
||||
}).lean()
|
||||
]);
|
||||
|
||||
const filterApps = myApps
|
||||
.map((app) => {
|
||||
const perVal = rpList.find((item) => String(item.resourceId) === String(app._id))?.permission;
|
||||
const Per = new AppPermission({
|
||||
per: perVal ?? app.defaultPermission,
|
||||
isOwner: String(app.tmbId) === tmbId || tmbPer.isOwner
|
||||
});
|
||||
|
||||
return {
|
||||
...app,
|
||||
permission: Per
|
||||
};
|
||||
})
|
||||
.filter((app) => app.permission.hasReadPer);
|
||||
|
||||
return filterApps.map((app) => ({
|
||||
_id: app._id,
|
||||
avatar: app.avatar,
|
||||
name: app.name,
|
||||
intro: app.intro,
|
||||
isOwner: teamOwner || String(app.tmbId) === tmbId,
|
||||
permission: app.permission
|
||||
permission: app.permission,
|
||||
defaultPermission: app.defaultPermission
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import type { AppUpdateParams } from '@/global/core/app/api';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
WritePermissionVal,
|
||||
OwnerPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { name, avatar, type, intro, nodes, edges, chatConfig, permission, teamTags } =
|
||||
req.body as AppUpdateParams;
|
||||
const {
|
||||
name,
|
||||
avatar,
|
||||
type,
|
||||
intro,
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig,
|
||||
permission,
|
||||
teamTags,
|
||||
defaultPermission
|
||||
} = req.body as AppUpdateParams;
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
@@ -16,7 +31,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
|
||||
if (permission) {
|
||||
await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||
} else if (defaultPermission) {
|
||||
await authApp({ req, authToken: true, appId, per: ManagePermissionVal });
|
||||
} else {
|
||||
await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
}
|
||||
|
||||
// format nodes data
|
||||
// 1. dataset search limit, less than model quoteMaxToken
|
||||
@@ -33,6 +54,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
avatar,
|
||||
intro,
|
||||
permission,
|
||||
defaultPermission,
|
||||
...(teamTags && teamTags),
|
||||
...(formatNodes && {
|
||||
modules: formatNodes
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
type Response = {};
|
||||
|
||||
@@ -14,7 +15,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
const { appId } = req.query as { appId: string };
|
||||
const { nodes = [], edges = [], chatConfig, type } = req.body as PostPublishAppProps;
|
||||
|
||||
await authApp({ appId, req, per: 'w', authToken: true });
|
||||
await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { PostRevertAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
type Response = {};
|
||||
|
||||
@@ -14,7 +15,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
const { appId } = req.query as { appId: string };
|
||||
const { editNodes = [], editEdges = [], versionId } = req.body as PostRevertAppProps;
|
||||
|
||||
await authApp({ appId, req, per: 'w', authToken: true });
|
||||
await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
const version = await MongoAppVersion.findOne({
|
||||
_id: versionId,
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
ChatItemValueItemType,
|
||||
UserChatItemValueItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
@@ -18,6 +18,7 @@ import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
@@ -61,7 +62,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
/* user auth */
|
||||
const [_, { teamId, tmbId }] = await Promise.all([
|
||||
authApp({ req, authToken: true, appId, per: 'r' }),
|
||||
authApp({ req, authToken: true, appId, per: ReadPermissionVal }),
|
||||
authCert({
|
||||
req,
|
||||
authToken: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
||||
@@ -10,6 +10,7 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -30,13 +31,13 @@ async function handler(
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: 'r'
|
||||
per: ReadPermissionVal
|
||||
}),
|
||||
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
|
||||
]);
|
||||
|
||||
// auth chat permission
|
||||
if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
|
||||
if (chat && !app.permission.hasManagePer && String(tmbId) !== String(chat?.tmbId)) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type createChatInputGuideQuery = {};
|
||||
|
||||
@@ -19,7 +20,7 @@ async function handler(
|
||||
res: ApiResponseType<any>
|
||||
): Promise<createInputGuideResponse> {
|
||||
const { appId, textList } = req.body;
|
||||
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||
await authApp({ req, appId, authToken: true, per: WritePermissionVal });
|
||||
|
||||
try {
|
||||
const result = await MongoChatInputGuide.insertMany(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type deleteChatInputGuideQuery = {};
|
||||
|
||||
@@ -14,7 +15,7 @@ async function handler(
|
||||
res: ApiResponseType<any>
|
||||
): Promise<deleteInputGuideResponse> {
|
||||
const { appId, dataIdList } = req.body;
|
||||
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||
await authApp({ req, appId, authToken: true, per: ReadPermissionVal });
|
||||
console.log(dataIdList);
|
||||
await MongoChatInputGuide.deleteMany({
|
||||
_id: { $in: dataIdList },
|
||||
|
||||
@@ -4,7 +4,8 @@ import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/t
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type ChatInputGuideProps = PaginationProps<{
|
||||
appId: string;
|
||||
@@ -18,7 +19,7 @@ async function handler(
|
||||
): Promise<ChatInputGuideResponse> {
|
||||
const { appId, pageSize, current, searchKey } = req.query;
|
||||
|
||||
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||
await authApp({ req, appId, authToken: true, per: ReadPermissionVal });
|
||||
|
||||
const params = {
|
||||
appId,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type updateChatInputGuideQuery = {};
|
||||
|
||||
@@ -18,7 +19,7 @@ async function handler(
|
||||
res: ApiResponseType<any>
|
||||
): Promise<updateInputGuideResponse> {
|
||||
const { appId, dataId, text } = req.body;
|
||||
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||
await authApp({ req, appId, authToken: true, per: ReadPermissionVal });
|
||||
|
||||
await MongoChatInputGuide.findOneAndUpdate(
|
||||
{
|
||||
|
||||
@@ -3,9 +3,10 @@ import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import type { DatasetSimpleItemType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* get all dataset by teamId or tmbId */
|
||||
async function handler(
|
||||
@@ -13,10 +14,14 @@ async function handler(
|
||||
res: NextApiResponse<any>
|
||||
): Promise<DatasetSimpleItemType[]> {
|
||||
// 凭证校验
|
||||
const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
|
||||
const { teamId, tmbId, permission } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const datasets = await MongoDataset.find({
|
||||
...mongoRPermission({ teamId, tmbId, role }),
|
||||
...mongoRPermission({ teamId, tmbId, permission }),
|
||||
type: { $ne: DatasetTypeEnum.folder }
|
||||
}).lean();
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
|
||||
import { createDefaultCollection } from '@fastgpt/service/core/dataset/collection/controller';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getLLMModel, getVectorModel, getDatasetModel } from '@fastgpt/service/core/ai/model';
|
||||
import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -22,7 +23,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
} = req.body as CreateDatasetParams;
|
||||
|
||||
// auth
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true, authApiKey: true });
|
||||
const { teamId, tmbId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
// check model valid
|
||||
const vectorModelStore = getVectorModel(vectorModel);
|
||||
|
||||
@@ -4,21 +4,23 @@ import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { parentId, type } = req.query as { parentId?: string; type?: DatasetTypeEnum };
|
||||
// 凭证校验
|
||||
const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({
|
||||
const { teamId, tmbId, permission } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
authApiKey: true,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const datasets = await MongoDataset.find({
|
||||
...mongoRPermission({ teamId, tmbId, role }),
|
||||
...mongoRPermission({ teamId, tmbId, permission }),
|
||||
...(parentId !== undefined && { parentId: parentId || null }),
|
||||
...(type && { type })
|
||||
})
|
||||
@@ -34,8 +36,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
intro: item.intro,
|
||||
type: item.type,
|
||||
permission: item.permission,
|
||||
canWrite,
|
||||
isOwner: teamOwner || String(item.tmbId) === tmbId,
|
||||
canWrite: permission.hasWritePer,
|
||||
isOwner: permission.isOwner || String(item.tmbId) === tmbId,
|
||||
vectorModel: getVectorModel(item.vectorModel)
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -2,15 +2,16 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
const body = req.body as CreateOnePluginParams;
|
||||
|
||||
// await checkTeamPluginLimit(teamId);
|
||||
|
||||
@@ -3,13 +3,14 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
|
||||
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { teamId } = await authUserNotVisitor({ req, authToken: true });
|
||||
const { teamId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
const { pluginId } = req.query as { pluginId: string };
|
||||
|
||||
if (!pluginId) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api';
|
||||
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -37,7 +38,7 @@ async function handler(
|
||||
req,
|
||||
authToken: true
|
||||
}),
|
||||
appId && authApp({ req, authToken: true, appId, per: 'r' }),
|
||||
appId && authApp({ req, authToken: true, appId, per: ReadPermissionVal }),
|
||||
pluginId && authPluginCrud({ req, authToken: true, pluginId, per: 'r' })
|
||||
]);
|
||||
|
||||
|
||||
@@ -4,13 +4,15 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import type { EditApiKeyProps } from '@/global/support/openapi/api';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, name, limit } = req.body as EditApiKeyProps;
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
|
||||
const count = await MongoOpenApi.find({ tmbId, appId }).countDocuments();
|
||||
|
||||
@@ -18,11 +20,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('最多 10 组 API 秘钥');
|
||||
}
|
||||
|
||||
const nanoid = customAlphabet(
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890',
|
||||
Math.floor(Math.random() * 14) + 52
|
||||
);
|
||||
const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid()}`;
|
||||
const nanoid = getNanoid(Math.floor(Math.random() * 14) + 52);
|
||||
const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid}`;
|
||||
|
||||
await MongoOpenApi.create({
|
||||
teamId,
|
||||
|
||||
@@ -3,8 +3,9 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
|
||||
import type { GetApiKeyProps } from '@/global/support/openapi/api';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -12,11 +13,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const { appId } = req.query as GetApiKeyProps;
|
||||
|
||||
if (appId) {
|
||||
const { tmbId, teamOwner } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
|
||||
const findResponse = await MongoOpenApi.find({
|
||||
appId,
|
||||
...(!teamOwner && { tmbId })
|
||||
appId
|
||||
}).sort({ _id: -1 });
|
||||
|
||||
return jsonRes(res, {
|
||||
@@ -24,16 +29,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
teamId,
|
||||
tmbId,
|
||||
isOwner: teamOwner
|
||||
} = await authUserNotVisitor({ req, authToken: true });
|
||||
const { teamId, tmbId, permission } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
|
||||
const findResponse = await MongoOpenApi.find({
|
||||
appId,
|
||||
teamId,
|
||||
...(!teamOwner && { tmbId })
|
||||
...(!permission.isOwner && { tmbId })
|
||||
}).sort({ _id: -1 });
|
||||
|
||||
return jsonRes(res, {
|
||||
|
||||
@@ -2,10 +2,11 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
|
||||
/* create a shareChat */
|
||||
@@ -18,7 +19,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
type: PublishChannelEnum;
|
||||
};
|
||||
|
||||
const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
const { teamId, tmbId } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
const shareId = nanoid();
|
||||
await MongoOutLink.create({
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/auth/outLink';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* delete a shareChat by shareChatId */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -13,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
id: string;
|
||||
};
|
||||
|
||||
await authOutLinkCrud({ req, outLinkId: id, authToken: true, per: 'owner' });
|
||||
await authOutLinkCrud({ req, outLinkId: id, authToken: true, per: ManagePermissionVal });
|
||||
|
||||
await MongoOutLink.findByIdAndRemove(id);
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* get shareChat list by appId */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -14,11 +15,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
type: string;
|
||||
};
|
||||
|
||||
const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
|
||||
const data = await MongoOutLink.find({
|
||||
appId,
|
||||
...(isOwner ? { teamId } : { tmbId }),
|
||||
type: type
|
||||
}).sort({
|
||||
_id: -1
|
||||
|
||||
@@ -3,7 +3,8 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/auth/outLink';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -15,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('_id is required');
|
||||
}
|
||||
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: 'owner' });
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: ManagePermissionVal });
|
||||
|
||||
await MongoOutLink.findByIdAndUpdate(_id, {
|
||||
name,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
|
||||
import { authTeamOwner } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { updateTeam } from '@fastgpt/service/support/user/team/controller';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type updateQuery = {};
|
||||
|
||||
@@ -13,7 +14,7 @@ export type updateResponse = {};
|
||||
async function handler(req: ApiRequestProps<updateBody, updateQuery>, res: ApiResponseType<any>) {
|
||||
const body = req.body as UpdateTeamProps;
|
||||
|
||||
const { teamId } = await authTeamOwner({ req, authToken: true });
|
||||
const { teamId } = await authUserPer({ req, authToken: true, per: ManagePermissionVal });
|
||||
|
||||
await updateTeam({ teamId, ...body });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
@@ -47,6 +47,7 @@ import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1';
|
||||
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
type FastGptWebChatProps = {
|
||||
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
|
||||
@@ -482,17 +483,16 @@ const authHeaderRequest = async ({
|
||||
if (!appId) {
|
||||
return Promise.reject('appId is empty');
|
||||
}
|
||||
const { app, canWrite } = await authApp({
|
||||
const { app, permission } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: 'r'
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
return {
|
||||
app,
|
||||
|
||||
canWrite: canWrite
|
||||
canWrite: permission.hasReadPer
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
Input,
|
||||
Textarea,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Image
|
||||
ModalBody
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
@@ -19,39 +18,46 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import PermissionRadio from '@/components/support/permission/Radio';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { AppContext } from '@/web/core/app/context/appContext';
|
||||
import MemberManager from '@/components/support/permission/MemberManager';
|
||||
import {
|
||||
postUpdateAppCollaborators,
|
||||
deleteAppCollaborators,
|
||||
getCollaboratorList
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/web/core/app/context/appContext';
|
||||
import {
|
||||
AppDefaultPermission,
|
||||
AppPermissionList
|
||||
} from '@fastgpt/global/support/permission/app/constant';
|
||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
||||
|
||||
const InfoModal = ({
|
||||
defaultApp,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultApp: AppSchema;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}) => {
|
||||
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { updateAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { updateAppDetail, appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
handleSubmit
|
||||
handleSubmit,
|
||||
watch
|
||||
} = useForm({
|
||||
defaultValues: defaultApp
|
||||
defaultValues: appDetail
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const defaultPermission = watch('defaultPermission');
|
||||
const avatar = getValues('avatar');
|
||||
|
||||
// submit config
|
||||
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
|
||||
@@ -60,11 +66,10 @@ const InfoModal = ({
|
||||
name: data.name,
|
||||
avatar: data.avatar,
|
||||
intro: data.intro,
|
||||
permission: data.permission
|
||||
defaultPermission: data.defaultPermission
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
onSuccess && onSuccess();
|
||||
onClose();
|
||||
toast({
|
||||
title: t('common.Update Success'),
|
||||
@@ -108,7 +113,6 @@ const InfoModal = ({
|
||||
maxH: 300
|
||||
});
|
||||
setValue('avatar', src);
|
||||
setRefresh((state) => !state);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: getErrText(err, t('common.error.Select avatar failed')),
|
||||
@@ -119,6 +123,20 @@ const InfoModal = ({
|
||||
[setValue, t, toast]
|
||||
);
|
||||
|
||||
const onUpdateCollaborators = async (tmbIds: string[], permission: PermissionValueType) => {
|
||||
await postUpdateAppCollaborators({
|
||||
tmbIds,
|
||||
permission,
|
||||
appId: appDetail._id
|
||||
});
|
||||
};
|
||||
const onDelCollaborator = async (tmbId: string) => {
|
||||
await deleteAppCollaborators({
|
||||
appId: appDetail._id,
|
||||
tmbId
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -130,7 +148,7 @@ const InfoModal = ({
|
||||
<Box>{t('core.app.Name and avatar')}</Box>
|
||||
<Flex mt={2} alignItems={'center'}>
|
||||
<Avatar
|
||||
src={getValues('avatar')}
|
||||
src={avatar}
|
||||
w={['26px', '34px']}
|
||||
h={['26px', '34px']}
|
||||
cursor={'pointer'}
|
||||
@@ -152,9 +170,6 @@ const InfoModal = ({
|
||||
<Box mt={4} mb={1}>
|
||||
{t('core.app.App intro')}
|
||||
</Box>
|
||||
{/* <Box color={'myGray.500'} mb={2} fontSize={'sm'}>
|
||||
该介绍主要用于记忆和在应用市场展示
|
||||
</Box> */}
|
||||
<Textarea
|
||||
rows={4}
|
||||
maxLength={500}
|
||||
@@ -162,16 +177,32 @@ const InfoModal = ({
|
||||
bg={'myWhite.600'}
|
||||
{...register('intro')}
|
||||
/>
|
||||
<Box mt={4}>
|
||||
<Box mb={1}>{t('user.Permission')}</Box>
|
||||
<PermissionRadio
|
||||
value={getValues('permission')}
|
||||
onChange={(e) => {
|
||||
setValue('permission', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* role */}
|
||||
{appDetail.permission.hasManagePer && (
|
||||
<>
|
||||
{' '}
|
||||
<Box mt="4">
|
||||
<Box>{t('permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="2"
|
||||
per={defaultPermission}
|
||||
defaultPer={AppDefaultPermission}
|
||||
readPer={ReadPermissionVal}
|
||||
writePer={WritePermissionVal}
|
||||
onChange={(v) => setValue('defaultPermission', v)}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={6}>
|
||||
<MemberManager
|
||||
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
|
||||
permissionList={AppPermissionList}
|
||||
onUpdateCollaborators={onUpdateCollaborators}
|
||||
onDelOneCollaborator={onDelCollaborator}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
||||
@@ -182,16 +182,17 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
||||
label: t('common.Delete'),
|
||||
icon: 'delete',
|
||||
type: 'danger',
|
||||
onClick: openConfirm(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
onClick: () =>
|
||||
openConfirm(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Flex, Button, IconButton } from '@chakra-ui/react';
|
||||
import { Box, Flex, Button, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
@@ -19,17 +19,23 @@ import { AppContext } from '@/web/core/app/context/appContext';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
|
||||
const AppCard = ({ appId }: { appId: string }) => {
|
||||
const AppCard = () => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { toast } = useToast();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const appId = appDetail._id;
|
||||
const { feConfigs } = useSystemStore();
|
||||
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
|
||||
|
||||
const {
|
||||
isOpen: isOpenInfoEdit,
|
||||
onOpen: onOpenInfoEdit,
|
||||
onClose: onCloseInfoEdit
|
||||
} = useDisclosure();
|
||||
|
||||
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
|
||||
content: appT('Confirm Del App Tip'),
|
||||
type: 'delete'
|
||||
@@ -58,7 +64,7 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
<Box px={4}>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'}>
|
||||
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
|
||||
<PermissionIconText permission={appDetail.permission} />
|
||||
<PermissionIconText defaultPermission={appDetail.defaultPermission} />
|
||||
</Box>
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
AppId:{' '}
|
||||
@@ -83,7 +89,7 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
|
||||
{appDetail.name}
|
||||
</Box>
|
||||
{appDetail.isOwner && (
|
||||
{appDetail.permission.isOwner && (
|
||||
<IconButton
|
||||
className="delete"
|
||||
position={'absolute'}
|
||||
@@ -133,7 +139,7 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
>
|
||||
{t('core.app.navbar.Publish')}
|
||||
</Button>
|
||||
{appDetail.canWrite && feConfigs?.show_team_chat && (
|
||||
{appDetail.permission.hasWritePer && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
mr={3}
|
||||
size={['sm', 'md']}
|
||||
@@ -144,12 +150,12 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
{t('common.Team Tags Set')}
|
||||
</Button>
|
||||
)}
|
||||
{appDetail.isOwner && (
|
||||
{appDetail.permission.hasManagePer && (
|
||||
<Button
|
||||
size={['sm', 'md']}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<MyIcon name={'common/settingLight'} w={'16px'} />}
|
||||
onClick={() => setSettingAppInfo(appDetail)}
|
||||
onClick={onOpenInfoEdit}
|
||||
>
|
||||
{t('common.Setting')}
|
||||
</Button>
|
||||
@@ -158,9 +164,7 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
<ConfirmDelModal />
|
||||
{settingAppInfo && (
|
||||
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
|
||||
)}
|
||||
{isOpenInfoEdit && <InfoModal onClose={onCloseInfoEdit} />}
|
||||
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ const SimpleEdit = ({ appId }: { appId: string }) => {
|
||||
pb={10}
|
||||
overflow={'overlay'}
|
||||
>
|
||||
<AppCard appId={appId} />
|
||||
<AppCard />
|
||||
|
||||
<Box mt={2}>
|
||||
<EditForm editForm={editForm} divRef={divRef} isSticky={isSticky} />
|
||||
|
||||
@@ -59,24 +59,25 @@ const AppDetail = ({ appId, currentTab }: { appId: string; currentTab: TabEnum }
|
||||
id: TabEnum.simpleEdit,
|
||||
icon: 'common/overviewLight'
|
||||
},
|
||||
...(feConfigs?.hide_app_flow
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: t('core.app.navbar.Flow mode'),
|
||||
id: TabEnum.adEdit,
|
||||
icon: 'core/modules/flowLight'
|
||||
}
|
||||
]),
|
||||
|
||||
{
|
||||
label: t('core.app.navbar.Publish app'),
|
||||
id: TabEnum.publish,
|
||||
icon: 'support/outlink/shareLight'
|
||||
label: t('core.app.navbar.Flow mode'),
|
||||
id: TabEnum.adEdit,
|
||||
icon: 'core/modules/flowLight'
|
||||
},
|
||||
{ label: appT('Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' },
|
||||
...(appDetail.permission.hasManagePer
|
||||
? [
|
||||
{
|
||||
label: t('core.app.navbar.Publish app'),
|
||||
id: TabEnum.publish,
|
||||
icon: 'support/outlink/shareLight'
|
||||
},
|
||||
{ label: appT('Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' }
|
||||
]
|
||||
: []),
|
||||
{ label: t('core.Start chat'), id: TabEnum.startChat, icon: 'core/chat/chatLight' }
|
||||
],
|
||||
[appT, feConfigs?.hide_app_flow, t]
|
||||
[appDetail.permission.hasManagePer, appT, t]
|
||||
);
|
||||
|
||||
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@@ -8,12 +8,8 @@ import {
|
||||
Input,
|
||||
Grid,
|
||||
useTheme,
|
||||
Card,
|
||||
Text,
|
||||
HStack,
|
||||
Tag
|
||||
Card
|
||||
} from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
@@ -136,52 +132,48 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
{!feConfigs?.hide_app_flow && (
|
||||
<>
|
||||
<Box mt={[4, 7]} mb={[0, 3]} color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('core.app.Select app from template')}
|
||||
</Box>
|
||||
<Grid
|
||||
userSelect={'none'}
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
|
||||
gridGap={[2, 4]}
|
||||
<Box mt={[4, 7]} mb={[0, 3]} color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('core.app.Select app from template')}
|
||||
</Box>
|
||||
<Grid
|
||||
userSelect={'none'}
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{appTemplates.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
border={theme.borders.base}
|
||||
p={3}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
boxShadow={'sm'}
|
||||
{...(templateId === item.id
|
||||
? {
|
||||
bg: 'primary.50',
|
||||
borderColor: 'primary.500'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
boxShadow: 'md'
|
||||
}
|
||||
})}
|
||||
onClick={() => {
|
||||
setValue('templateId', item.id);
|
||||
}}
|
||||
>
|
||||
{appTemplates.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
border={theme.borders.base}
|
||||
p={3}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
boxShadow={'sm'}
|
||||
{...(templateId === item.id
|
||||
? {
|
||||
bg: 'primary.50',
|
||||
borderColor: 'primary.500'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
boxShadow: 'md'
|
||||
}
|
||||
})}
|
||||
onClick={() => {
|
||||
setValue('templateId', item.id);
|
||||
}}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} borderRadius={'md'} w={'20px'} />
|
||||
<Box ml={3} fontWeight={'bold'}>
|
||||
{t(item.name)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box fontSize={'sm'} mt={4}>
|
||||
{t(item.intro)}
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} borderRadius={'md'} w={'20px'} />
|
||||
<Box ml={3} fontWeight={'bold'}>
|
||||
{t(item.name)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box fontSize={'sm'} mt={4}>
|
||||
{t(item.intro)}
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
||||
@@ -27,7 +27,7 @@ const MyApps = () => {
|
||||
const { userInfo } = useUserStore();
|
||||
const { myApps, loadMyApps } = useAppStore();
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
title: '删除提示',
|
||||
type: 'delete',
|
||||
content: '确认删除该应用所有信息?'
|
||||
});
|
||||
const {
|
||||
@@ -67,9 +67,11 @@ const MyApps = () => {
|
||||
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
|
||||
{appT('My Apps')}
|
||||
</Box>
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{commonT('New Create')}
|
||||
</Button>
|
||||
{userInfo?.team.permission.hasWritePer && (
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{commonT('New Create')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Grid
|
||||
py={[4, 6]}
|
||||
@@ -79,7 +81,7 @@ const MyApps = () => {
|
||||
{myApps.map((app) => (
|
||||
<MyTooltip
|
||||
key={app._id}
|
||||
label={userInfo?.team.canWrite ? appT('To Settings') : appT('To Chat')}
|
||||
label={app.permission.hasWritePer ? appT('To Settings') : appT('To Chat')}
|
||||
>
|
||||
<Box
|
||||
lineHeight={1.5}
|
||||
@@ -106,7 +108,7 @@ const MyApps = () => {
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (userInfo?.team.canWrite) {
|
||||
if (app.permission.hasWritePer) {
|
||||
router.push(`/app/detail?appId=${app._id}`);
|
||||
} else {
|
||||
router.push(`/chat?appId=${app._id}`);
|
||||
@@ -116,7 +118,7 @@ const MyApps = () => {
|
||||
<Flex alignItems={'center'} h={'38px'}>
|
||||
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
|
||||
<Box ml={3}>{app.name}</Box>
|
||||
{app.isOwner && userInfo?.team.canWrite && (
|
||||
{app.permission.isOwner && (
|
||||
<IconButton
|
||||
className="delete"
|
||||
position={'absolute'}
|
||||
@@ -146,9 +148,12 @@ const MyApps = () => {
|
||||
</Box>
|
||||
<Flex h={'34px'} alignItems={'flex-end'}>
|
||||
<Box flex={1}>
|
||||
<PermissionIconText permission={app.permission} color={'myGray.600'} />
|
||||
<PermissionIconText
|
||||
defaultPermission={app.defaultPermission}
|
||||
color={'myGray.600'}
|
||||
/>
|
||||
</Box>
|
||||
{userInfo?.team.canWrite && (
|
||||
{app.permission.hasWritePer && (
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'xsSquare'}
|
||||
|
||||
@@ -41,7 +41,7 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
icon: 'common/overviewLight'
|
||||
},
|
||||
{ label: t('core.dataset.test.Search Test'), id: TabEnum.test, icon: 'kbTest' },
|
||||
...(userInfo?.team.canWrite && datasetDetail.isOwner
|
||||
...(userInfo?.team.permission.hasWritePer && datasetDetail.isOwner
|
||||
? [{ label: t('common.Config'), id: TabEnum.info, icon: 'common/settingLight' }]
|
||||
: [])
|
||||
];
|
||||
|
||||
@@ -168,7 +168,7 @@ const Dataset = () => {
|
||||
}}
|
||||
/>
|
||||
{/* create icon */}
|
||||
{userInfo?.team?.canWrite && (
|
||||
{userInfo?.team?.permission.hasWritePer && (
|
||||
<MyMenu
|
||||
offset={[-30, 5]}
|
||||
width={120}
|
||||
@@ -276,7 +276,7 @@ const Dataset = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{userInfo?.team.canWrite && dataset.isOwner && (
|
||||
{userInfo?.team?.permission.hasWritePer && dataset.isOwner && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={3}
|
||||
|
||||
@@ -75,7 +75,7 @@ const TeamPlugins = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{userInfo?.team?.canWrite && (
|
||||
{userInfo?.team?.permission.hasWritePer && (
|
||||
<MyMenu
|
||||
offset={[-30, 5]}
|
||||
width={120}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { AuthModeType } from '@fastgpt/service/support/permission/type';
|
||||
import { authOutLink, authOutLinkInit } from './outLink';
|
||||
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { authTeamSpaceToken } from './team';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { AuthUserTypeEnum, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
/*
|
||||
@@ -64,15 +64,18 @@ export async function autChatCrud({
|
||||
if (!chat) return { id: outLinkUid };
|
||||
|
||||
// auth req
|
||||
const { teamId, tmbId, role } = await authUserRole(props);
|
||||
const { teamId, tmbId, permission } = await authUserPer({
|
||||
...props,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
if (String(teamId) !== String(chat.teamId)) return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
|
||||
if (role === TeamMemberRoleEnum.owner) return { uid: outLinkUid };
|
||||
if (permission.isOwner) return { uid: outLinkUid };
|
||||
if (String(tmbId) === String(chat.tmbId)) return { uid: outLinkUid };
|
||||
|
||||
// admin
|
||||
if (per === 'r' && role === TeamMemberRoleEnum.admin) return { uid: outLinkUid };
|
||||
if (per === 'r' && permission.hasManagePer) return { uid: outLinkUid };
|
||||
|
||||
return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
})();
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
AuthOutLinkInitProps,
|
||||
AuthOutLinkResponse
|
||||
} from '@fastgpt/global/support/outLink/api.d';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
|
||||
15
projects/app/src/web/core/app/api/collaborator.ts
Normal file
15
projects/app/src/web/core/app/api/collaborator.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
UpdateAppCollaboratorBody,
|
||||
AppCollaboratorDeleteParams
|
||||
} from '@fastgpt/global/core/app/collaborator';
|
||||
import { DELETE, GET, POST } from '@/web/common/api/request';
|
||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
|
||||
export const getCollaboratorList = (appId: string) =>
|
||||
GET<CollaboratorItemType[]>('/proApi/core/app/collaborator/list', { appId });
|
||||
|
||||
export const postUpdateAppCollaborators = (body: UpdateAppCollaboratorBody) =>
|
||||
POST('/proApi/core/app/collaborator/update', body);
|
||||
|
||||
export const deleteAppCollaborators = ({ ...params }: AppCollaboratorDeleteParams) =>
|
||||
DELETE('/proApi/core/app/collaborator/delete', { ...params });
|
||||
@@ -1,5 +1,7 @@
|
||||
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { FeishuType, 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';
|
||||
|
||||
export const defaultApp: AppDetailType = {
|
||||
_id: '',
|
||||
@@ -12,12 +14,11 @@ export const defaultApp: AppDetailType = {
|
||||
chatConfig: {},
|
||||
teamId: '',
|
||||
tmbId: '',
|
||||
permission: 'private',
|
||||
isOwner: false,
|
||||
canWrite: false,
|
||||
teamTags: [],
|
||||
edges: [],
|
||||
version: 'v2'
|
||||
version: 'v2',
|
||||
defaultPermission: NullPermission,
|
||||
permission: new AppPermission()
|
||||
};
|
||||
|
||||
export const defaultOutLinkForm: OutLinkEditType = {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@fastgpt/global/support/user/team/controller.d';
|
||||
import type { TeamTagItemType, TeamTagSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import {
|
||||
TeamItemType,
|
||||
TeamTmbItemType,
|
||||
TeamMemberItemType,
|
||||
TeamMemberSchema
|
||||
} from '@fastgpt/global/support/user/team/type.d';
|
||||
@@ -19,7 +19,7 @@ import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wal
|
||||
|
||||
/* --------------- team ---------------- */
|
||||
export const getTeamList = (status: `${TeamMemberSchema['status']}`) =>
|
||||
GET<TeamItemType[]>(`/proApi/support/user/team/list`, { status });
|
||||
GET<TeamTmbItemType[]>(`/proApi/support/user/team/list`, { status });
|
||||
export const postCreateTeam = (data: CreateTeamProps) =>
|
||||
POST<string>(`/proApi/support/user/team/create`, data);
|
||||
export const putUpdateTeam = (data: UpdateTeamProps) => PUT(`/support/user/team/update`, data);
|
||||
@@ -39,8 +39,12 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
|
||||
PUT('/proApi/support/user/team/member/updateInvite', data);
|
||||
export const delLeaveTeam = (teamId: string) =>
|
||||
DELETE('/proApi/support/user/team/member/leave', { teamId });
|
||||
|
||||
/* -------------- team collaborator -------------------- */
|
||||
export const updateMemberPermission = (data: UpdateTeamMemberPermissionProps) =>
|
||||
PUT('/proApi/support/user/team/member/updatePermission', data);
|
||||
PUT('/proApi/support/user/team/collaborator/update', data);
|
||||
export const delMemberPermission = (tmbId: string) =>
|
||||
DELETE('/proApi/support/user/team/collaborator/delete', { tmbId });
|
||||
|
||||
/* --------------- team tags ---------------- */
|
||||
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
|
||||
|
||||
Reference in New Issue
Block a user