Skip to content

Commit

Permalink
Move app owner (#2280)
Browse files Browse the repository at this point in the history
* feat: app owner change (#2271)

* feat(app): changeOwner api

* feat: changeOwner api

* feat: ChangeOwnerModal

* feat: update change owner api

* chore: move change owner api into pro version
feat(fe): change owner modal

* feat: add change owner button and modal to InfoModal

* change owner ux

* feat: doc

* perf: remove info change owner btn

---------

Co-authored-by: Finley Ge <[email protected]>
  • Loading branch information
c121914yu and FinleyGe authored Aug 6, 2024
1 parent 91bc573 commit a109c59
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 24 deletions.
21 changes: 11 additions & 10 deletions docSite/content/zh-cn/docs/development/upgrading/489.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ curl --location --request POST 'https://{{host}}/api/admin/init/489' \
4. 商业版新增 - 团队通知账号绑定,用于接收重要信息。
5. 商业版新增 - 知识库集合标签功能,可以对知识库进行标签管理。
6. 商业版新增 - 知识库搜索节点支持标签过滤和创建时间过滤。
7. 新增 - 删除所有对话引导内容。
8. 优化 - 对话框信息懒加载,减少网络传输。
9. 优化 - 清除选文件缓存,支持重复选择同一个文件。
10. 修复 - 知识库上传文件,网络不稳定或文件较多情况下,进度无法到 100%。
11. 修复 - 删除应用后回到聊天选择最后一次对话的应用为删除的应用时提示无该应用问题。
12. 修复 - 插件动态变量配置默认值时,无法正常显示默认值。
13. 修复 - 工具调用温度和最大回复值未生效。
14. 修复 - 函数调用模式,assistant role 中,GPT 模型必须传入 content 参数。(不影响大部分模型,目前基本都改用用 ToolChoice 模式,FC 模式已弃用)。
15. 修复 - 知识库文件上传进度更新可能异常。
16. 修复 - 知识库 rebuilding 时候,页面总是刷新到第一页。
7. 商业版新增 - 转移 App owner 权限。
8. 新增 - 删除所有对话引导内容。
9. 优化 - 对话框信息懒加载,减少网络传输。
10. 优化 - 清除选文件缓存,支持重复选择同一个文件。
11. 修复 - 知识库上传文件,网络不稳定或文件较多情况下,进度无法到 100%。
12. 修复 - 删除应用后回到聊天选择最后一次对话的应用为删除的应用时提示无该应用问题。
13. 修复 - 插件动态变量配置默认值时,无法正常显示默认值。
14. 修复 - 工具调用温度和最大回复值未生效。
15. 修复 - 函数调用模式,assistant role 中,GPT 模型必须传入 content 参数。(不影响大部分模型,目前基本都改用用 ToolChoice 模式,FC 模式已弃用)。
16. 修复 - 知识库文件上传进度更新可能异常。
17. 修复 - 知识库 rebuilding 时候,页面总是刷新到第一页。
12 changes: 11 additions & 1 deletion packages/global/common/error/code/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { i18nT } from '../../../../web/i18n/utils';
/* dataset: 502000 */
export enum AppErrEnum {
unExist = 'appUnExist',
unAuthApp = 'unAuthApp'
unAuthApp = 'unAuthApp',
invalidOwner = 'invalidOwner',
invalidAppType = 'invalidAppType'
}
const appErrList = [
{
Expand All @@ -13,6 +15,14 @@ const appErrList = [
{
statusText: AppErrEnum.unAuthApp,
message: i18nT('common:code_error.app_error.un_auth_app')
},
{
statusText: AppErrEnum.invalidOwner,
message: i18nT('common:code_error.app_error.invalid_owner')
},
{
statusText: AppErrEnum.invalidAppType,
message: i18nT('common:code_error.app_error.invalid_app_type')
}
];
export default appErrList.reduce((acc, cur, index) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/web/components/common/Icon/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const iconPaths = {
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
'common/lineChange': () => import('./icons/common/lineChange.svg'),
'common/linkBlue': () => import('./icons/common/linkBlue.svg'),
'common/loading': () => import('./icons/common/loading.svg'),
'common/logLight': () => import('./icons/common/logLight.svg'),
Expand Down Expand Up @@ -241,6 +242,7 @@ export const iconPaths = {
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
'modal/AddClb': () => import('./icons/modal/AddClb.svg'),
'modal/changePer': () => import('./icons/modal/changePer.svg'),
'modal/concat': () => import('./icons/modal/concat.svg'),
'modal/confirmPay': () => import('./icons/modal/confirmPay.svg'),
'modal/edit': () => import('./icons/modal/edit.svg'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/web/components/common/Icon/icons/modal/changePer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions packages/web/i18n/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"code_error": {
"app_error": {
"not_exist": "应用不存在",
"un_auth_app": "无权操作该应用"
"un_auth_app": "无权操作该应用",
"invalid_owner": "非法的应用所有者",
"invalid_app_type": "错误的应用类型"
},
"chat_error": {
"un_auth": "没有权限操作此对话记录"
Expand Down Expand Up @@ -1095,7 +1097,13 @@
"Remove InheritPermission Confirm": "此操作会导致权限继承失效,是否进行?",
"Resume InheritPermission Confirm": "是否恢复为继承父级文件夹的权限?",
"Resume InheritPermission Failed": "恢复失败",
"Resume InheritPermission Success": "恢复成功"
"Resume InheritPermission Success": "恢复成功",
"change_owner": "转移所有权",
"change_owner_to": "转移给",
"change_owner_placeholder": "输入用户名查找账号",
"change_owner_tip": "转移后将保留您的管理员权限",
"change_owner_success": "成功转移所有权",
"change_owner_failed": "转移所有权失败"
},
"plugin": {
"App": "选择应用",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { getTeamMembers } from '@/web/support/user/team/api';
import {
Box,
Flex,
HStack,
Input,
ModalBody,
ModalFooter,
Button,
useDisclosure
} from '@chakra-ui/react';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import Avatar from '@fastgpt/web/components/common/Avatar';
import Icon from '@fastgpt/web/components/common/Icon';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTag from '@fastgpt/web/components/common/Tag';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import React, { useState } from 'react';

export type ChangeOwnerModalProps = {
avatar?: string;
name: string;
onChangeOwner: (tmbId: string) => Promise<unknown>;
};

export function ChangeOwnerModal({
onClose,
avatar,
name,
onChangeOwner
}: ChangeOwnerModalProps & { onClose: () => void }) {
const { t } = useTranslation();
const [inputValue, setInputValue] = React.useState('');
const { data: teamMembers = [] } = useRequest2(getTeamMembers, {
manual: false
});

const memberList = teamMembers.filter((item) => {
return item.memberName.includes(inputValue);
});

const {
isOpen: isOpenMemberListMenu,
onClose: onCloseMemberListMenu,
onOpen: onOpenMemberListMenu
} = useDisclosure();
const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null);

const { runAsync, loading } = useRequest2(onChangeOwner, {
onSuccess: onClose,
successToast: t('common:permission.change_owner_success'),
errorToast: t('common:permission.change_owner_failed')
});

const onConfirm = async () => {
if (!selectedMember) {
return;
}
await runAsync(selectedMember.tmbId);
};

return (
<MyModal
isOpen
iconSrc="modal/changePer"
onClose={onClose}
title={t('common:permission.change_owner')}
isLoading={loading}
>
<ModalBody>
<HStack>
<Avatar src={avatar} w={'1.75rem'} borderRadius={'md'} />
<Box>{name}</Box>
</HStack>
<Flex mt={4} justify="start" flexDirection="column">
<Box fontSize="14px" fontWeight="500" color="myGray.900">
{t('common:permission.change_owner_to')}
</Box>
<Flex mt="4" alignItems="center" position={'relative'}>
{selectedMember && (
<Avatar
src={selectedMember.avatar}
w={'20px'}
borderRadius={'md'}
position="absolute"
left={3}
/>
)}
<Input
placeholder={t('common:permission.change_owner_placeholder')}
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
setSelectedMember(null);
}}
onFocus={() => {
onOpenMemberListMenu();
setSelectedMember(null);
}}
// onBlur={() => {
// setTimeout(() => {
// onCloseMemberListMenu();
// }, 10);
// }}
{...(selectedMember && { pl: '10' })}
/>
</Flex>
{isOpenMemberListMenu && memberList.length > 0 && (
<Flex
mt={2}
w={'100%'}
flexDirection={'column'}
gap={2}
p={1}
boxShadow="lg"
bg="white"
borderRadius="md"
zIndex={10}
maxH={'300px'}
overflow={'auto'}
>
{memberList.map((item) => (
<Box
key={item.tmbId}
p="2"
_hover={{ bg: 'myGray.100' }}
mx="1"
borderRadius="md"
cursor={'pointer'}
onClickCapture={() => {
setInputValue(item.memberName);
setSelectedMember(item);
onCloseMemberListMenu();
}}
>
<Flex align="center">
<Avatar src={item.avatar} w="1.25rem" />
<Box ml="2">{item.memberName}</Box>
</Flex>
</Box>
))}
</Flex>
)}

<MyTag mt="4" colorSchema="blue">
<Icon name="common/info" w="1rem" />
<Box ml="2">{t('common:permission.change_owner_tip')}</Box>
</MyTag>
</Flex>
</ModalBody>
<ModalFooter>
<HStack>
<Button onClick={onClose} variant={'whiteBase'}>
{t('common:common.Cancel')}
</Button>
<Button onClick={onConfirm}>{t('common:common.Confirm')}</Button>
</HStack>
</ModalFooter>
</MyModal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import CollaboratorContextProvider, { MemberManagerInputPropsType } from '../MemberManager/context';
import { Box, Button, Flex, HStack, ModalBody } from '@chakra-ui/react';
import { Box, Button, Flex, HStack, ModalBody, useDisclosure } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import DefaultPermissionList from '../DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useI18n } from '@/web/context/I18n';
import ResumeInherit from '../ResumeInheritText';
import { ChangeOwnerModal } from '../ChangeOwnerModal';

export type ConfigPerModalProps = {
avatar?: string;
Expand All @@ -25,6 +24,7 @@ export type ConfigPerModalProps = {
resumeInheritPermission?: () => void;
hasParent?: boolean;
refetchResource?: () => void;
onChangeOwner?: (tmbId: string) => Promise<unknown>;
};

const ConfigPerModal = ({
Expand All @@ -36,11 +36,17 @@ const ConfigPerModal = ({
resumeInheritPermission,
hasParent,
onClose,
refetchResource
refetchResource,
onChangeOwner
}: ConfigPerModalProps & {
onClose: () => void;
}) => {
const { t } = useTranslation();
const {
isOpen: isChangeOwnerModalOpen,
onOpen: onOpenChangeOwnerModal,
onClose: onCloseChangeOwnerModal
} = useDisclosure();

return (
<>
Expand Down Expand Up @@ -113,8 +119,30 @@ const ConfigPerModal = ({
}}
</CollaboratorContextProvider>
</Box>
{onChangeOwner && (
<Box mt={4}>
<Button
size="md"
variant="whitePrimary"
onClick={onOpenChangeOwnerModal}
w="full"
borderRadius="md"
leftIcon={<MyIcon w="4" name="common/lineChange" />}
>
{t('common:permission.change_owner')}
</Button>
</Box>
)}
</ModalBody>
</MyModal>
{isChangeOwnerModalOpen && onChangeOwner && (
<ChangeOwnerModal
onClose={onCloseChangeOwnerModal}
avatar={avatar}
name={name}
onChangeOwner={onChangeOwner}
/>
)}
</>
);
};
Expand Down
5 changes: 5 additions & 0 deletions projects/app/src/global/core/app/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ export type PostRevertAppProps = {
editEdges: AppSchema['edges'];
editChatConfig: AppSchema['chatConfig'];
};

export type AppChangeOwnerBody = {
appId: string;
ownerId: string;
};
8 changes: 5 additions & 3 deletions projects/app/src/pages/app/detail/components/InfoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import {
Input,
Textarea,
ModalFooter,
ModalBody
ModalBody,
useDisclosure
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
Expand All @@ -35,7 +36,6 @@ import {
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { resumeInheritPer } from '@/web/core/app/api';
import { useI18n } from '@/web/context/I18n';
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
Expand All @@ -61,6 +61,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
defaultValues: appDetail
});
const avatar = getValues('avatar');
const name = getValues('name');

// submit config
const { runAsync: saveSubmitSuccess, loading: btnLoading } = useRequest2(
Expand Down Expand Up @@ -135,6 +136,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
appId: appDetail._id
});
};

const onDelCollaborator = async (tmbId: string) => {
await deleteAppCollaborators({
appId: appDetail._id,
Expand Down
Loading

0 comments on commit a109c59

Please sign in to comment.