From 19c8a06d51dae05ba5ec6eab85cfeee7027def16 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 4 Jun 2024 17:52:00 +0800 Subject: [PATCH] Permission (#1687) Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> --- .../global/common/system/types/index.d.ts | 1 - packages/global/core/app/collaborator.d.ts | 12 + packages/global/core/app/type.d.ts | 11 +- .../global/support/permission/app/constant.ts | 20 ++ .../support/permission/app/controller.ts | 15 ++ .../global/support/permission/app/type.d.ts | 0 .../support/permission/collaborator.d.ts | 9 + .../global/support/permission/constant.ts | 38 ++- .../global/support/permission/controller.ts | 71 +++++ packages/global/support/permission/type.d.ts | 21 +- .../support/permission/user/constant.ts | 16 ++ .../support/permission/user/controller.ts | 15 ++ packages/global/support/permission/utils.ts | 15 +- .../global/support/user/team/controller.d.ts | 4 +- packages/global/support/user/team/type.d.ts | 8 +- packages/global/support/user/type.d.ts | 4 +- packages/service/core/app/schema.ts | 7 + .../service/support/permission/app/auth.ts | 85 ++++++ .../service/support/permission/auth/app.ts | 72 ------ .../support/permission/auth/outLink.ts | 96 ------- .../service/support/permission/auth/user.ts | 66 ----- .../service/support/permission/controller.ts | 68 ++++- .../support/permission/publish/authLink.ts | 69 +++++ .../resourcePermission/controller.ts | 16 -- .../resourcePermission/permisson.ts | 127 --------- .../{resourcePermission => }/schema.ts | 34 ++- .../service/support/permission/teamLimit.ts | 1 - packages/service/support/permission/type.d.ts | 4 +- .../service/support/permission/type/auth.d.ts | 21 ++ .../service/support/permission/user/auth.ts | 26 ++ .../service/support/user/team/controller.ts | 22 +- .../service/support/user/team/teamSchema.ts | 4 +- .../web/components/common/Icon/constants.ts | 1 + .../common/Icon/icons/modal/AddClb.svg | 5 + .../icons/support/permission/collaborator.svg | 5 +- .../web/components/common/MyBox/index.tsx | 7 +- .../web/components/common/MyLoading/index.tsx | 8 +- .../web/components/common/MySelect/index.tsx | 4 +- projects/app/i18n/en/common.json | 2 + projects/app/i18n/en/user.json | 6 +- projects/app/i18n/zh/common.json | 7 +- projects/app/i18n/zh/user.json | 6 +- projects/app/public/imgs/modal/edit.svg | 2 +- .../permission/DefaultPerList/index.tsx | 49 ++++ .../support/permission/IconText/index.tsx | 25 +- .../MemberManager/AddMemberModal.tsx | 216 ++++++++++++++++ .../permission/MemberManager/ManageModal.tsx | 119 +++++++++ .../MemberManager/PermissionSelect.tsx | 243 ++++++++++++++++++ .../MemberManager/PermissionTags.tsx | 36 +++ .../permission/MemberManager/context.tsx | 107 ++++++++ .../permission/MemberManager/index.tsx | 99 +++++++ .../user/team/TeamManageModal/MemberTable.tsx | 103 -------- .../user/team/TeamManageModal/TeamCard.tsx | 174 +++++++------ .../user/team/TeamManageModal/TeamList.tsx | 23 +- .../EditInfoModal.tsx} | 15 +- .../{ => components}/InviteModal.tsx | 0 .../components/MemberTable.tsx | 116 +++++++++ .../PermissionManage}/AddManager.tsx | 57 ++-- .../PermissionManage/index.tsx} | 52 ++-- .../user/team/TeamManageModal/context.tsx | 102 ++++++++ .../user/team/TeamManageModal/index.tsx | 159 ++---------- .../support/user/team/TeamMenu/index.tsx | 5 +- projects/app/src/global/core/app/api.d.ts | 1 + .../app/src/pages/account/components/Info.tsx | 2 +- .../pages/account/components/UsageTable.tsx | 2 +- projects/app/src/pages/account/index.tsx | 4 +- projects/app/src/pages/api/core/app/create.ts | 5 +- projects/app/src/pages/api/core/app/del.ts | 5 +- .../api/core/app/{detail.tsx => detail.ts} | 6 +- .../app/src/pages/api/core/app/getChatLogs.ts | 5 +- projects/app/src/pages/api/core/app/list.ts | 60 ++++- projects/app/src/pages/api/core/app/update.ts | 30 ++- .../src/pages/api/core/app/version/publish.ts | 5 +- .../src/pages/api/core/app/version/revert.ts | 5 +- .../app/src/pages/api/core/chat/chatTest.ts | 5 +- projects/app/src/pages/api/core/chat/init.ts | 7 +- .../pages/api/core/chat/inputGuide/create.ts | 5 +- .../pages/api/core/chat/inputGuide/delete.ts | 5 +- .../pages/api/core/chat/inputGuide/list.ts | 5 +- .../pages/api/core/chat/inputGuide/update.ts | 5 +- .../src/pages/api/core/dataset/allDataset.ts | 11 +- .../app/src/pages/api/core/dataset/create.ts | 10 +- .../app/src/pages/api/core/dataset/list.ts | 14 +- .../app/src/pages/api/core/plugin/create.ts | 5 +- .../app/src/pages/api/core/plugin/delete.ts | 5 +- .../app/src/pages/api/core/workflow/debug.ts | 5 +- .../src/pages/api/support/openapi/create.ts | 13 +- .../app/src/pages/api/support/openapi/list.ts | 27 +- .../src/pages/api/support/outLink/create.ts | 10 +- .../src/pages/api/support/outLink/delete.ts | 5 +- .../app/src/pages/api/support/outLink/list.ts | 11 +- .../src/pages/api/support/outLink/update.ts | 5 +- .../src/pages/api/support/user/team/update.ts | 5 +- .../app/src/pages/api/v1/chat/completions.ts | 10 +- .../pages/app/detail/components/InfoModal.tsx | 99 ++++--- .../detail/components/Publish/Link/index.tsx | 21 +- .../detail/components/SimpleEdit/AppCard.tsx | 26 +- .../detail/components/SimpleEdit/index.tsx | 2 +- projects/app/src/pages/app/detail/index.tsx | 29 ++- .../pages/app/list/component/CreateModal.tsx | 94 ++++--- projects/app/src/pages/app/list/index.tsx | 23 +- .../dataset/detail/components/Slider.tsx | 2 +- projects/app/src/pages/dataset/list/index.tsx | 4 +- projects/app/src/pages/plugin/list/index.tsx | 2 +- .../service/support/permission/auth/chat.ts | 15 +- .../support/permission/auth/outLink.ts | 2 +- .../app/src/web/core/app/api/collaborator.ts | 15 ++ projects/app/src/web/core/app/constants.ts | 9 +- projects/app/src/web/support/user/team/api.ts | 10 +- 109 files changed, 2291 insertions(+), 1091 deletions(-) create mode 100644 packages/global/core/app/collaborator.d.ts create mode 100644 packages/global/support/permission/app/constant.ts create mode 100644 packages/global/support/permission/app/controller.ts create mode 100644 packages/global/support/permission/app/type.d.ts create mode 100644 packages/global/support/permission/collaborator.d.ts create mode 100644 packages/global/support/permission/controller.ts create mode 100644 packages/global/support/permission/user/constant.ts create mode 100644 packages/global/support/permission/user/controller.ts create mode 100644 packages/service/support/permission/app/auth.ts delete mode 100644 packages/service/support/permission/auth/app.ts delete mode 100644 packages/service/support/permission/auth/outLink.ts delete mode 100644 packages/service/support/permission/auth/user.ts create mode 100644 packages/service/support/permission/publish/authLink.ts delete mode 100644 packages/service/support/permission/resourcePermission/controller.ts delete mode 100644 packages/service/support/permission/resourcePermission/permisson.ts rename packages/service/support/permission/{resourcePermission => }/schema.ts (64%) create mode 100644 packages/service/support/permission/type/auth.d.ts create mode 100644 packages/service/support/permission/user/auth.ts create mode 100644 packages/web/components/common/Icon/icons/modal/AddClb.svg create mode 100644 projects/app/src/components/support/permission/DefaultPerList/index.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/ManageModal.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/context.tsx create mode 100644 projects/app/src/components/support/permission/MemberManager/index.tsx delete mode 100644 projects/app/src/components/support/user/team/TeamManageModal/MemberTable.tsx rename projects/app/src/components/support/user/team/TeamManageModal/{EditModal.tsx => components/EditInfoModal.tsx} (91%) rename projects/app/src/components/support/user/team/TeamManageModal/{ => components}/InviteModal.tsx (100%) create mode 100644 projects/app/src/components/support/user/team/TeamManageModal/components/MemberTable.tsx rename projects/app/src/components/support/user/team/TeamManageModal/{ => components/PermissionManage}/AddManager.tsx (78%) rename projects/app/src/components/support/user/team/TeamManageModal/{PermissionManage.tsx => components/PermissionManage/index.tsx} (71%) create mode 100644 projects/app/src/components/support/user/team/TeamManageModal/context.tsx rename projects/app/src/pages/api/core/app/{detail.tsx => detail.ts} (71%) create mode 100644 projects/app/src/web/core/app/api/collaborator.ts diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 5a861de77..b5dd024d7 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -31,7 +31,6 @@ export type FastGPTFeConfigsType = { show_openai_account?: boolean; show_promotion?: boolean; show_team_chat?: boolean; - hide_app_flow?: boolean; concatMd?: string; docUrl?: string; chatbotUrl?: string; diff --git a/packages/global/core/app/collaborator.d.ts b/packages/global/core/app/collaborator.d.ts new file mode 100644 index 000000000..bfc2d8751 --- /dev/null +++ b/packages/global/core/app/collaborator.d.ts @@ -0,0 +1,12 @@ +import { PermissionValueType } from '../../support/permission/type'; + +export type UpdateAppCollaboratorBody = { + appId: string; + tmbIds: string[]; + permission: PermissionValueType; +}; + +export type AppCollaboratorDeleteParams = { + appId: string; + tmbId: string; +}; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 5c8487b83..f297a2f65 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -7,6 +7,8 @@ import { SelectedDatasetType } from '../workflow/api'; import { DatasetSearchModeEnum } from '../dataset/constants'; import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d'; import { StoreEdgeItemType } from '../workflow/type/edge'; +import { PermissionValueType } from '../../support/permission/type'; +import { AppPermission } from '../../support/permission/app/controller'; export type AppSchema = { _id: string; @@ -27,9 +29,9 @@ export type AppSchema = { scheduledTriggerConfig?: AppScheduledTriggerConfigType | null; scheduledTriggerNextTime?: Date; - permission: `${PermissionTypeEnum}`; inited?: boolean; teamTags: string[]; + defaultPermission: PermissionValueType; }; export type AppListItemType = { @@ -37,13 +39,12 @@ export type AppListItemType = { name: string; avatar: string; intro: string; - isOwner: boolean; - permission: `${PermissionTypeEnum}`; + defaultPermission: PermissionValueType; + permission: AppPermission; }; export type AppDetailType = AppSchema & { - isOwner: boolean; - canWrite: boolean; + permission: AppPermission; }; export type AppSimpleEditFormType = { diff --git a/packages/global/support/permission/app/constant.ts b/packages/global/support/permission/app/constant.ts new file mode 100644 index 000000000..a9479ccbc --- /dev/null +++ b/packages/global/support/permission/app/constant.ts @@ -0,0 +1,20 @@ +import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant'; +import { PermissionListType } from '../type'; + +export enum AppPermissionKeyEnum {} +export const AppPermissionList: PermissionListType = { + [PermissionKeyEnum.read]: { + ...PermissionList[PermissionKeyEnum.read], + description: '可使用该应用进行对话' + }, + [PermissionKeyEnum.write]: { + ...PermissionList[PermissionKeyEnum.write], + description: '可查看和编辑应用' + }, + [PermissionKeyEnum.manage]: { + ...PermissionList[PermissionKeyEnum.manage], + description: '写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限' + } +}; + +export const AppDefaultPermission = NullPermission; diff --git a/packages/global/support/permission/app/controller.ts b/packages/global/support/permission/app/controller.ts new file mode 100644 index 000000000..90cf507d7 --- /dev/null +++ b/packages/global/support/permission/app/controller.ts @@ -0,0 +1,15 @@ +import { PerConstructPros, Permission } from '../controller'; +import { AppDefaultPermission } from './constant'; + +export class AppPermission extends Permission { + constructor(props?: PerConstructPros) { + if (!props) { + props = { + per: AppDefaultPermission + }; + } else if (!props?.per) { + props.per = AppDefaultPermission; + } + super(props); + } +} diff --git a/packages/global/support/permission/app/type.d.ts b/packages/global/support/permission/app/type.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/global/support/permission/collaborator.d.ts b/packages/global/support/permission/collaborator.d.ts new file mode 100644 index 000000000..8a3dcff31 --- /dev/null +++ b/packages/global/support/permission/collaborator.d.ts @@ -0,0 +1,9 @@ +import { PermissionValueType } from './type'; + +export type CollaboratorItemType = { + teamId: string; + tmbId: string; + permission: PermissionValueType; + name: string; + avatar: string; +}; diff --git a/packages/global/support/permission/constant.ts b/packages/global/support/permission/constant.ts index 000fa8991..fbd86a27c 100644 --- a/packages/global/support/permission/constant.ts +++ b/packages/global/support/permission/constant.ts @@ -1,3 +1,6 @@ +import { Permission } from './controller'; +import { PermissionListType } from './type'; + export enum AuthUserTypeEnum { token = 'token', root = 'root', @@ -21,8 +24,41 @@ export const PermissionTypeMap = { } }; -export enum ResourceTypeEnum { +export enum PerResourceTypeEnum { team = 'team', app = 'app', dataset = 'dataset' } + +/* new permission */ +export enum PermissionKeyEnum { + read = 'read', + write = 'write', + manage = 'manage' +} +export const PermissionList: PermissionListType = { + [PermissionKeyEnum.read]: { + name: '读权限', + description: '', + value: 0b100, + checkBoxType: 'single' + }, + [PermissionKeyEnum.write]: { + name: '写权限', + description: '', + value: 0b110, // 如果某个资源有特殊要求,再重写这个值 + checkBoxType: 'single' + }, + [PermissionKeyEnum.manage]: { + name: '管理员', + description: '', + value: 0b111, + checkBoxType: 'single' + } +}; + +export const NullPermission = 0; +export const OwnerPermissionVal = ~0 >>> 0; +export const ReadPermissionVal = PermissionList['read'].value; +export const WritePermissionVal = PermissionList['write'].value; +export const ManagePermissionVal = PermissionList['manage'].value; diff --git a/packages/global/support/permission/controller.ts b/packages/global/support/permission/controller.ts new file mode 100644 index 000000000..0666612af --- /dev/null +++ b/packages/global/support/permission/controller.ts @@ -0,0 +1,71 @@ +import { PermissionValueType } from './type'; +import { PermissionList, NullPermission, OwnerPermissionVal } from './constant'; + +export type PerConstructPros = { + per?: PermissionValueType; + isOwner?: boolean; +}; + +// the Permission helper class +export class Permission { + value: PermissionValueType; + isOwner: boolean; + hasManagePer: boolean; + hasWritePer: boolean; + hasReadPer: boolean; + + constructor(props?: PerConstructPros) { + const { per = NullPermission, isOwner = false } = props || {}; + if (isOwner) { + this.value = OwnerPermissionVal; + } else { + this.value = per; + } + + this.isOwner = isOwner; + this.hasManagePer = this.checkPer(PermissionList['manage'].value); + this.hasWritePer = this.checkPer(PermissionList['write'].value); + this.hasReadPer = this.checkPer(PermissionList['read'].value); + } + + // add permission(s) + // it can be chaining called. + // @example + // const perm = new Permission(permission) + // perm.add(PermissionList['read']) + // perm.add(PermissionList['read'], PermissionList['write']) + // perm.add(PermissionList['read']).add(PermissionList['write']) + addPer(...perList: PermissionValueType[]) { + for (let oer of perList) { + this.value = this.value | oer; + } + this.updatePermissions(); + return this.value; + } + + removePer(...perList: PermissionValueType[]) { + for (let per of perList) { + this.value = this.value & ~per; + } + this.updatePermissions(); + return this.value; + } + + checkPer(perm: PermissionValueType): boolean { + // if the permission is owner permission, only owner has this permission. + if (perm === OwnerPermissionVal) { + return this.value === OwnerPermissionVal; + } else if (this.hasManagePer) { + // The manager has all permissions except the owner permission + return true; + } + return (this.value & perm) === perm; + } + + private updatePermissions() { + this.isOwner = this.value === OwnerPermissionVal; + this.hasManagePer = this.checkPer(PermissionList['manage'].value); + this.hasWritePer = this.checkPer(PermissionList['write'].value); + this.hasReadPer = this.checkPer(PermissionList['read'].value); + } +} diff --git a/packages/global/support/permission/type.d.ts b/packages/global/support/permission/type.d.ts index 3e182acd7..9be39d9fe 100644 --- a/packages/global/support/permission/type.d.ts +++ b/packages/global/support/permission/type.d.ts @@ -1,6 +1,20 @@ -import { AuthUserTypeEnum } from './constant'; +import { TeamMemberWithUserSchema } from '../user/team/type'; +import { AuthUserTypeEnum, PermissionKeyEnum } from './constant'; +// PermissionValueType, the type of permission's value is a number, which is a bit field actually. +// It is spired by the permission system in Linux. +// The lowest 3 bits present the permission of reading, writing and managing. +// The higher bits are advanced permissions or extended permissions, which could be customized. export type PermissionValueType = number; +export type PermissionListType = Record< + T | PermissionKeyEnum, + { + name: string; + description: string; + value: PermissionValueType; + checkBoxType: 'single' | 'multiple'; + } +>; export type AuthResponseType = { teamId: string; @@ -17,4 +31,9 @@ export type ResourcePermissionType = { tmbId: string; resourceType: ResourceType; permission: PermissionValueType; + resourceId: string; +}; + +export type ResourcePerWithTmbWithUser = Omit & { + tmbId: TeamMemberWithUserSchema; }; diff --git a/packages/global/support/permission/user/constant.ts b/packages/global/support/permission/user/constant.ts new file mode 100644 index 000000000..001d8ebe9 --- /dev/null +++ b/packages/global/support/permission/user/constant.ts @@ -0,0 +1,16 @@ +import { PermissionKeyEnum, PermissionList, ReadPermissionVal } from '../constant'; + +export const TeamPermissionList = { + [PermissionKeyEnum.read]: { + ...PermissionList[PermissionKeyEnum.read] + }, + [PermissionKeyEnum.write]: { + ...PermissionList[PermissionKeyEnum.write] + }, + [PermissionKeyEnum.manage]: { + ...PermissionList[PermissionKeyEnum.manage], + description: '可邀请, 删除成员' + } +}; + +export const TeamDefaultPermissionVal = ReadPermissionVal; diff --git a/packages/global/support/permission/user/controller.ts b/packages/global/support/permission/user/controller.ts new file mode 100644 index 000000000..ec1c09386 --- /dev/null +++ b/packages/global/support/permission/user/controller.ts @@ -0,0 +1,15 @@ +import { PerConstructPros, Permission } from '../controller'; +import { TeamDefaultPermissionVal } from './constant'; + +export class TeamPermission extends Permission { + constructor(props?: PerConstructPros) { + if (!props) { + props = { + per: TeamDefaultPermissionVal + }; + } else if (!props?.per) { + props.per = TeamDefaultPermissionVal; + } + super(props); + } +} diff --git a/packages/global/support/permission/utils.ts b/packages/global/support/permission/utils.ts index 4e0df70fe..140b4d2ad 100644 --- a/packages/global/support/permission/utils.ts +++ b/packages/global/support/permission/utils.ts @@ -1,22 +1,25 @@ import { TeamMemberRoleEnum } from '../user/team/constant'; import { PermissionTypeEnum } from './constant'; +import { Permission } from './controller'; /* team public source, or owner source in team */ export function mongoRPermission({ teamId, tmbId, - role + permission }: { teamId: string; tmbId: string; - role: `${TeamMemberRoleEnum}`; + permission: Permission; }) { + if (permission.isOwner) { + return { + teamId + }; + } return { teamId, - ...(role === TeamMemberRoleEnum.visitor && { permission: PermissionTypeEnum.public }), - ...(role === TeamMemberRoleEnum.admin && { - $or: [{ permission: PermissionTypeEnum.public }, { tmbId }] - }) + $or: [{ permission: PermissionTypeEnum.public }, { tmbId }] }; } export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId: string }) { diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index 4ea50792e..f80bc9fad 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -1,4 +1,4 @@ -import { PermissionValueType } from 'support/permission/type'; +import { PermissionValueType } from '../../permission/type'; import { TeamMemberRoleEnum } from './constant'; import { LafAccountType, TeamMemberSchema } from './type'; @@ -22,7 +22,6 @@ export type UpdateTeamProps = { /* ------------- member ----------- */ export type DelMemberProps = { - teamId: string; memberId: string; }; export type UpdateTeamMemberProps = { @@ -46,7 +45,6 @@ export type InviteMemberResponse = Record< >; export type UpdateTeamMemberPermissionProps = { - teamId: string; memberIds: string[]; permission: PermissionValueType; }; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 93f0c653d..06db1fca7 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -2,6 +2,7 @@ import type { UserModelSchema } from '../type'; import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant'; import { LafAccountType } from './type'; import { PermissionValueType, ResourcePermissionType } from '../../permission/type'; +import { TeamPermission } from '../../permission/user/controller'; export type TeamSchema = { _id: string; @@ -49,7 +50,7 @@ export type TeamMemberWithTeamAndUserSchema = Omit { + const { permission: tmbPer } = await getTmbInfoByTmbId({ tmbId }); + + const app = await (async () => { + // get app and per + const [app, rp] = await Promise.all([ + MongoApp.findOne({ _id: appId, teamId }).lean(), + getResourcePermission({ + teamId, + tmbId, + resourceId: appId, + resourceType: PerResourceTypeEnum.app + }) // this could be null + ]); + + if (!app) { + return Promise.reject(AppErrEnum.unExist); + } + + const isOwner = tmbPer.isOwner || String(app.tmbId) === tmbId; + const Per = new AppPermission({ per: rp?.permission ?? app.defaultPermission, isOwner }); + + if (!Per.checkPer(per)) { + return Promise.reject(AppErrEnum.unAuthApp); + } + + return { + ...app, + permission: Per + }; + })(); + + return { app }; +}; + +export const authApp = async ({ + appId, + per, + ...props +}: AuthPropsType & { + appId: string; +}): Promise< + AuthResponseType & { + app: AppDetailType; + } +> => { + const result = await parseHeaderCert(props); + const { teamId, tmbId } = result; + + const { app } = await authAppByTmbId({ + teamId, + tmbId, + appId, + per + }); + + return { + ...result, + permission: app.permission, + app + }; +}; diff --git a/packages/service/support/permission/auth/app.ts b/packages/service/support/permission/auth/app.ts deleted file mode 100644 index 81ebf090a..000000000 --- a/packages/service/support/permission/auth/app.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { MongoApp } from '../../../core/app/schema'; -import { AppDetailType } from '@fastgpt/global/core/app/type.d'; -import { AuthModeType } from '../type'; -import { AuthResponseType } from '@fastgpt/global/support/permission/type'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { parseHeaderCert } from '../controller'; -import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; -import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -import { getTmbInfoByTmbId } from '../../user/team/controller'; - -// 模型使用权校验 -export async function authApp({ - appId, - per = 'owner', - ...props -}: AuthModeType & { - appId: string; -}): Promise< - AuthResponseType & { - teamOwner: boolean; - app: AppDetailType; - role: `${TeamMemberRoleEnum}`; - } -> { - const result = await parseHeaderCert(props); - const { teamId, tmbId } = result; - const { role } = await getTmbInfoByTmbId({ tmbId }); - - const { app, isOwner, canWrite } = await (async () => { - // get app - const app = await MongoApp.findOne({ _id: appId, teamId }).lean(); - if (!app) { - return Promise.reject(AppErrEnum.unExist); - } - - const isOwner = String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; - const canWrite = - isOwner || - (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); - - if (per === 'r') { - if (!isOwner && app.permission !== PermissionTypeEnum.public) { - return Promise.reject(AppErrEnum.unAuthApp); - } - } - if (per === 'w' && !canWrite) { - return Promise.reject(AppErrEnum.unAuthApp); - } - if (per === 'owner' && !isOwner) { - return Promise.reject(AppErrEnum.unAuthApp); - } - - return { - app: { - ...app, - isOwner, - canWrite - }, - isOwner, - canWrite - }; - })(); - - return { - ...result, - app, - role, - isOwner, - canWrite, - teamOwner: role === TeamMemberRoleEnum.owner - }; -} diff --git a/packages/service/support/permission/auth/outLink.ts b/packages/service/support/permission/auth/outLink.ts deleted file mode 100644 index f13c921f0..000000000 --- a/packages/service/support/permission/auth/outLink.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { AuthModeType } from '../type'; -import { AuthResponseType } from '@fastgpt/global/support/permission/type'; -import { AppDetailType } from '@fastgpt/global/core/app/type'; -import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; -import { parseHeaderCert } from '../controller'; -import { MongoOutLink } from '../../outLink/schema'; -import { MongoApp } from '../../../core/app/schema'; -import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink'; -import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; -import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -import { getTmbInfoByTmbId } from '../../user/team/controller'; - -/* crud outlink permission */ -export async function authOutLinkCrud({ - outLinkId, - per = 'owner', - ...props -}: AuthModeType & { - outLinkId: string; -}): Promise< - AuthResponseType & { - app: AppDetailType; - outLink: OutLinkSchema; - } -> { - const result = await parseHeaderCert(props); - const { tmbId, teamId } = result; - - const { role } = await getTmbInfoByTmbId({ tmbId }); - - const { app, outLink, isOwner, canWrite } = await (async () => { - const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId }); - - if (!outLink) { - throw new Error(OutLinkErrEnum.unExist); - } - - const app = await MongoApp.findById(outLink.appId); - - if (!app) { - return Promise.reject(AppErrEnum.unExist); - } - - const isOwner = String(outLink.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; - const canWrite = - isOwner || - (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); - - if (per === 'r' && !isOwner && app.permission !== PermissionTypeEnum.public) { - return Promise.reject(OutLinkErrEnum.unAuthLink); - } - if (per === 'w' && !canWrite) { - return Promise.reject(OutLinkErrEnum.unAuthLink); - } - if (per === 'owner' && !isOwner) { - return Promise.reject(OutLinkErrEnum.unAuthLink); - } - - return { - app: { - ...app, - isOwner: String(app.tmbId) === tmbId, - canWrite - }, - outLink, - isOwner, - canWrite - }; - })(); - - return { - ...result, - app, - outLink, - isOwner, - canWrite - }; -} - -/* outLink exist and it app exist */ -export async function authOutLinkValid({ shareId }: { shareId?: string }) { - if (!shareId) { - return Promise.reject(OutLinkErrEnum.linkUnInvalid); - } - const shareChat = await MongoOutLink.findOne({ shareId }); - - if (!shareChat) { - return Promise.reject(OutLinkErrEnum.linkUnInvalid); - } - - return { - appId: shareChat.appId, - shareChat - }; -} diff --git a/packages/service/support/permission/auth/user.ts b/packages/service/support/permission/auth/user.ts deleted file mode 100644 index 5461f9668..000000000 --- a/packages/service/support/permission/auth/user.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { AuthResponseType } from '@fastgpt/global/support/permission/type'; -import { AuthModeType } from '../type'; -import { TeamItemType } from '@fastgpt/global/support/user/team/type'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { parseHeaderCert } from '../controller'; -import { getTmbInfoByTmbId } from '../../user/team/controller'; -import { UserErrEnum } from '../../../../global/common/error/code/user'; -import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; - -export async function authUserNotVisitor(props: AuthModeType): Promise< - AuthResponseType & { - team: TeamItemType; - role: `${TeamMemberRoleEnum}`; - } -> { - const { teamId, tmbId } = await parseHeaderCert(props); - const team = await getTmbInfoByTmbId({ tmbId }); - - if (team.role === TeamMemberRoleEnum.visitor) { - return Promise.reject(UserErrEnum.binVisitor); - } - - return { - teamId, - tmbId, - team, - role: team.role, - isOwner: team.role === TeamMemberRoleEnum.owner, // teamOwner - canWrite: true - }; -} - -/* auth user role */ -export async function authUserRole(props: AuthModeType): Promise< - AuthResponseType & { - role: `${TeamMemberRoleEnum}`; - teamOwner: boolean; - } -> { - const result = await parseHeaderCert(props); - const { role: userRole, canWrite } = await getTmbInfoByTmbId({ tmbId: result.tmbId }); - - return { - ...result, - isOwner: true, - role: userRole, - teamOwner: userRole === TeamMemberRoleEnum.owner, - canWrite - }; -} - -/* auth teamMember in team role */ -export async function authTeamOwner(props: AuthModeType): Promise< - AuthResponseType & { - role: `${TeamMemberRoleEnum}`; - teamOwner: boolean; - } -> { - const authRes = await authUserRole(props); - - if (authRes.role !== TeamMemberRoleEnum.owner) { - return Promise.reject(TeamErrEnum.unAuthTeam); - } - - return authRes; -} diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 82d65aad9..a0fbf632f 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -3,10 +3,76 @@ import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; import jwt from 'jsonwebtoken'; import { NextApiResponse } from 'next'; import type { AuthModeType, ReqHeaderAuthType } from './type.d'; -import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { AuthUserTypeEnum, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import { authOpenApiKey } from '../openapi/auth'; import { FileTokenQuery } from '@fastgpt/global/common/file/type'; +import { MongoResourcePermission } from './schema'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import { mongoSessionRun } from '../../common/mongo/sessionRun'; +export const getResourcePermission = async ({ + resourceType, + teamId, + tmbId, + resourceId +}: { + resourceType: PerResourceTypeEnum; + teamId: string; + tmbId: string; + resourceId?: string; +}) => { + const per = await MongoResourcePermission.findOne({ + tmbId, + teamId, + resourceType, + resourceId + }); + + if (!per) { + return null; + } + return per; +}; +export const delResourcePermissionById = (id: string) => { + return MongoResourcePermission.findByIdAndRemove(id); +}; +export const updateResourcePermission = async ({ + resourceId, + resourceType, + teamId, + tmbIdList, + permission +}: { + resourceId?: string; + resourceType: PerResourceTypeEnum; + teamId: string; + tmbIdList: string[]; + permission: PermissionValueType; +}) => { + await mongoSessionRun((session) => { + return Promise.all( + tmbIdList.map((tmbId) => + MongoResourcePermission.findOneAndUpdate( + { + resourceType, + teamId, + tmbId, + resourceId + }, + { + permission + }, + { + session, + upsert: true + } + ) + ) + ); + }); +}; + +/* 下面代码等迁移 */ /* create token */ export function createJWT(user: { _id?: string; team?: { teamId?: string; tmbId: string } }) { const key = process.env.TOKEN_KEY as string; diff --git a/packages/service/support/permission/publish/authLink.ts b/packages/service/support/permission/publish/authLink.ts new file mode 100644 index 000000000..d7b2b3e92 --- /dev/null +++ b/packages/service/support/permission/publish/authLink.ts @@ -0,0 +1,69 @@ +import { AppDetailType } from '@fastgpt/global/core/app/type'; +import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; +import { parseHeaderCert } from '../controller'; +import { MongoOutLink } from '../../outLink/schema'; +import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink'; +import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { AuthPropsType } from '../type/auth'; +import { AuthResponseType } from '../type/auth'; +import { authAppByTmbId } from '../app/auth'; + +/* crud outlink permission */ +export async function authOutLinkCrud({ + outLinkId, + per, + ...props +}: AuthPropsType & { + outLinkId: string; +}): Promise< + AuthResponseType & { + app: AppDetailType; + outLink: OutLinkSchema; + } +> { + const result = await parseHeaderCert(props); + const { tmbId, teamId } = result; + + const { app, outLink } = await (async () => { + const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId }); + if (!outLink) { + throw new Error(OutLinkErrEnum.unExist); + } + + const { app } = await authAppByTmbId({ + teamId, + tmbId, + appId: outLink.appId, + per: ManagePermissionVal + }); + + return { + outLink, + app + }; + })(); + + return { + ...result, + permission: app.permission, + app, + outLink + }; +} + +/* outLink exist and it app exist */ +export async function authOutLinkValid({ shareId }: { shareId?: string }) { + if (!shareId) { + return Promise.reject(OutLinkErrEnum.linkUnInvalid); + } + const shareChat = await MongoOutLink.findOne({ shareId }); + + if (!shareChat) { + return Promise.reject(OutLinkErrEnum.linkUnInvalid); + } + + return { + appId: shareChat.appId, + shareChat + }; +} diff --git a/packages/service/support/permission/resourcePermission/controller.ts b/packages/service/support/permission/resourcePermission/controller.ts deleted file mode 100644 index 3b482354a..000000000 --- a/packages/service/support/permission/resourcePermission/controller.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ResourcePermissionType } from '@fastgpt/global/support/permission/type'; -import { MongoResourcePermission } from './schema'; -import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; - -export async function getResourcePermission({ - tmbId, - resourceType -}: { - tmbId: string; - resourceType: ResourceTypeEnum; -}) { - return (await MongoResourcePermission.findOne({ - tmbId, - resourceType - })) as ResourcePermissionType; -} diff --git a/packages/service/support/permission/resourcePermission/permisson.ts b/packages/service/support/permission/resourcePermission/permisson.ts deleted file mode 100644 index fb87a3e8b..000000000 --- a/packages/service/support/permission/resourcePermission/permisson.ts +++ /dev/null @@ -1,127 +0,0 @@ -// PermissionValueType, the type of permission's value is a number, which is a bit field actually. -// It is spired by the permission system in Linux. -// The lowest 3 bits present the permission of reading, writing and managing. -// The higher bits are advanced permissions or extended permissions, which could be customized. -export type PermissionValueType = number; -export type PermissionListType = { [key: string]: PermissionValueType }; -export const NullPermission: PermissionValueType = 0; - -// the Permission helper class -export class Permission { - value: PermissionValueType; - constructor(value: PermissionValueType) { - this.value = value; - } - - // add permission(s) - // it can be chaining called. - // @example - // const perm = new Permission(permission) - // perm.add(PermissionList['read']) - // perm.add(PermissionList['read'], PermissionList['write']) - // perm.add(PermissionList['read']).add(PermissionList['write']) - add(...perm: PermissionValueType[]): Permission { - for (let p of perm) { - this.value = addPermission(this.value, p); - } - return this; - } - - remove(...perm: PermissionValueType[]): Permission { - for (let p of perm) { - this.value = removePermission(this.value, p); - } - return this; - } - - check(perm: PermissionValueType): Permission | boolean { - if (checkPermission(this.value, perm)) { - return this; - } else { - return false; - } - } -} - -export function constructPermission(permList: PermissionValueType[]) { - return new Permission(NullPermission).add(...permList); -} - -// The base Permissions List -// It can be extended, for example: -// export const UserPermissionList: PermissionListType = { -// ...PermissionList, -// 'Invite': 0b1000 -// } -export const PermissionList: PermissionListType = { - Read: 0b100, - Write: 0b010, - Manage: 0b001 -}; - -// list of permissions. could be customized. -// ! removal of the basic permissions is not recommended. -// const PermList: Array = [ReadPerm, WritePerm, ManagePerm]; - -// return the list of permissions -// @param Perm(optional): the list of permissions to be added -// export function getPermList(Perm?: PermissionType[]): Array { -// if (Perm === undefined) { -// return PermList; -// } else { -// return PermList.concat(Perm); -// } -// } - -// check the permission -// @param [val]: The permission value to be checked -// @parma [perm]: Which Permission value will be checked -// @returns [booean]: if the [val] has the [perm] -// example: -// const perm = user.permission // get this permisiion from db or somewhere else -// const ok = checkPermission(perm, PermissionList['Read']) -export function checkPermission(val: PermissionValueType, perm: PermissionValueType): boolean { - return (val & perm) === perm; -} - -// add the permission -// it can be chaining called. -// return the new permission value based on [val] added with [perm] -// @param val: PermissionValueType -// @param perm: PermissionValueType -// example: -// const basePerm = 0b001; // Manage only -export function addPermission( - val: PermissionValueType, - perm: PermissionValueType -): PermissionValueType { - return val | perm; -} - -// remove the permission -export function removePermission( - val: PermissionValueType, - perm: PermissionValueType -): PermissionValueType { - return val & ~perm; -} - -// export function parsePermission(val: PermissionValueType, list: PermissionValueType[]) { -// const result: [[string, boolean]] = [] as any; -// list.forEach((perm) => { -// result.push([perm[0], checkPermission(val, perm)]); -// }); -// return result; -// } - -export function hasManage(val: PermissionValueType) { - return checkPermission(val, PermissionList['Manage']); -} - -export function hasWrite(val: PermissionValueType) { - return checkPermission(val, PermissionList['Write']); -} - -export function hasRead(val: PermissionValueType) { - return checkPermission(val, PermissionList['Read']); -} diff --git a/packages/service/support/permission/resourcePermission/schema.ts b/packages/service/support/permission/schema.ts similarity index 64% rename from packages/service/support/permission/resourcePermission/schema.ts rename to packages/service/support/permission/schema.ts index 98be1fcda..f072c5fa9 100644 --- a/packages/service/support/permission/resourcePermission/schema.ts +++ b/packages/service/support/permission/schema.ts @@ -2,11 +2,13 @@ import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { Model, connectionMongo } from '../../../common/mongo'; +import { Model, connectionMongo } from '../../common/mongo'; import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type'; -import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; const { Schema, model, models } = connectionMongo; +export const ResourcePermissionCollectionName = 'resource_permission'; + export const ResourcePermissionSchema = new Schema({ teamId: { type: Schema.Types.ObjectId, @@ -17,30 +19,36 @@ export const ResourcePermissionSchema = new Schema({ ref: TeamMemberCollectionName }, resourceType: { - type: Object.values(ResourceTypeEnum), + type: Object.values(PerResourceTypeEnum), required: true }, permission: { type: Number, required: true + }, + // Resrouce ID: App or DataSet or any other resource type. + // It is null if the resourceType is team. + resourceId: { + type: Schema.Types.ObjectId } }); try { - ResourcePermissionSchema.index({ - teamId: 1, - resourceType: 1 - }); - ResourcePermissionSchema.index({ - tmbId: 1, - resourceType: 1 - }); + ResourcePermissionSchema.index( + { + resourceType: 1, + teamId: 1, + tmbId: 1, + resourceId: 1 + }, + { + unique: true + } + ); } catch (error) { console.log(error); } -export const ResourcePermissionCollectionName = 'resource_permission'; - export const MongoResourcePermission: Model = models[ResourcePermissionCollectionName] || model(ResourcePermissionCollectionName, ResourcePermissionSchema); diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index c64884e99..d75b61c8e 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -1,4 +1,3 @@ -import { getVectorCountByTeamId } from '../../common/vectorStore/controller'; import { getTeamPlanStatus, getTeamStandPlan } from '../../support/wallet/sub/utils'; import { MongoApp } from '../../core/app/schema'; import { MongoPlugin } from '../../core/plugin/schema'; diff --git a/packages/service/support/permission/type.d.ts b/packages/service/support/permission/type.d.ts index cb4ce3d54..98f182a06 100644 --- a/packages/service/support/permission/type.d.ts +++ b/packages/service/support/permission/type.d.ts @@ -1,4 +1,5 @@ import { ApiRequestProps } from '../../type/next'; +import type { PermissionValueType } from '@fastgpt/global/support/permission/type'; export type ReqHeaderAuthType = { cookie?: string; @@ -8,10 +9,11 @@ export type ReqHeaderAuthType = { userid?: string; authorization?: string; }; + export type AuthModeType = { req: ApiRequestProps; authToken?: boolean; authRoot?: boolean; authApiKey?: boolean; - per?: 'r' | 'w' | 'owner'; + per?: PermissionValueType | 'r' | 'w' | 'owner'; // this is for compatibility }; diff --git a/packages/service/support/permission/type/auth.d.ts b/packages/service/support/permission/type/auth.d.ts new file mode 100644 index 000000000..ecf75c6bb --- /dev/null +++ b/packages/service/support/permission/type/auth.d.ts @@ -0,0 +1,21 @@ +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { Permission } from '@fastgpt/global/support/permission/controller'; +import { ApiRequestProps } from '../../../type/next'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; + +export type AuthPropsType = { + req: ApiRequestProps; + authToken?: boolean; + authRoot?: boolean; + authApiKey?: boolean; + per: PermissionValueType; +}; + +export type AuthResponseType = { + teamId: string; + tmbId: string; + authType?: `${AuthUserTypeEnum}`; + appId?: string; + apikey?: string; + permission: Permission; +}; diff --git a/packages/service/support/permission/user/auth.ts b/packages/service/support/permission/user/auth.ts new file mode 100644 index 000000000..f5681c14f --- /dev/null +++ b/packages/service/support/permission/user/auth.ts @@ -0,0 +1,26 @@ +import { AuthResponseType } from '../type/auth.d'; +import { AuthPropsType } from '../type/auth.d'; +import { TeamTmbItemType } from '@fastgpt/global/support/user/team/type'; +import { parseHeaderCert } from '../controller'; +import { getTmbInfoByTmbId } from '../../user/team/controller'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; + +/* auth user role */ +export async function authUserPer(props: AuthPropsType): Promise< + AuthResponseType & { + tmb: TeamTmbItemType; + } +> { + const result = await parseHeaderCert(props); + const tmb = await getTmbInfoByTmbId({ tmbId: result.tmbId }); + + if (!tmb.permission.checkPer(props.per)) { + return Promise.reject(TeamErrEnum.unAuthTeam); + } + + return { + ...result, + permission: tmb.permission, + tmb + }; +} diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index ff2da42ea..43b3ca4d1 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -1,4 +1,4 @@ -import { TeamItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type'; +import { TeamTmbItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type'; import { ClientSession, Types } from '../../../common/mongo'; import { TeamMemberRoleEnum, @@ -8,13 +8,25 @@ import { import { MongoTeamMember } from './teamMemberSchema'; import { MongoTeam } from './teamSchema'; import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller'; +import { getResourcePermission } from '../../permission/controller'; +import { + PerResourceTypeEnum, + ReadPermissionVal +} from '@fastgpt/global/support/permission/constant'; +import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; -async function getTeamMember(match: Record): Promise { +async function getTeamMember(match: Record): Promise { const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema; if (!tmb) { return Promise.reject('member not exist'); } + const tmbPer = await getResourcePermission({ + resourceType: PerResourceTypeEnum.team, + teamId: tmb.teamId._id, + tmbId: tmb._id + }); + return { userId: String(tmb.userId), teamId: String(tmb.teamId._id), @@ -27,9 +39,11 @@ async function getTeamMember(match: Record): Promise role: tmb.role, status: tmb.status, defaultTeam: tmb.defaultTeam, - canWrite: tmb.role !== TeamMemberRoleEnum.visitor, lafAccount: tmb.teamId.lafAccount, - defaultPermission: tmb.teamId.defaultPermission + permission: new TeamPermission({ + per: tmbPer?.permission ?? tmb.teamId.defaultPermission, + isOwner: tmb.role === TeamMemberRoleEnum.owner + }) }; } diff --git a/packages/service/support/user/team/teamSchema.ts b/packages/service/support/user/team/teamSchema.ts index 870a67ef4..f784c1d4e 100644 --- a/packages/service/support/user/team/teamSchema.ts +++ b/packages/service/support/user/team/teamSchema.ts @@ -3,7 +3,7 @@ const { Schema, model, models } = connectionMongo; import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d'; import { userCollectionName } from '../../user/schema'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { NullPermission } from '../../permission/resourcePermission/permisson'; +import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant'; const TeamSchema = new Schema({ name: { @@ -16,7 +16,7 @@ const TeamSchema = new Schema({ }, defaultPermission: { type: Number, - default: NullPermission + default: TeamDefaultPermissionVal }, avatar: { type: String, diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index daa13392e..4cbd370c2 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -180,6 +180,7 @@ export const iconPaths = { kbTest: () => import('./icons/kbTest.svg'), menu: () => import('./icons/menu.svg'), minus: () => import('./icons/minus.svg'), + 'modal/AddClb': () => import('./icons/modal/AddClb.svg'), 'modal/concat': () => import('./icons/modal/concat.svg'), 'modal/confirmPay': () => import('./icons/modal/confirmPay.svg'), 'modal/edit': () => import('./icons/modal/edit.svg'), diff --git a/packages/web/components/common/Icon/icons/modal/AddClb.svg b/packages/web/components/common/Icon/icons/modal/AddClb.svg new file mode 100644 index 000000000..d9c2c1e7c --- /dev/null +++ b/packages/web/components/common/Icon/icons/modal/AddClb.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/permission/collaborator.svg b/packages/web/components/common/Icon/icons/support/permission/collaborator.svg index ed0ad1f81..8f0b1b2a2 100644 --- a/packages/web/components/common/Icon/icons/support/permission/collaborator.svg +++ b/packages/web/components/common/Icon/icons/support/permission/collaborator.svg @@ -1,5 +1,4 @@ - \ No newline at end of file + d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"/> + diff --git a/packages/web/components/common/MyBox/index.tsx b/packages/web/components/common/MyBox/index.tsx index 24745186b..e5f8d70cd 100644 --- a/packages/web/components/common/MyBox/index.tsx +++ b/packages/web/components/common/MyBox/index.tsx @@ -1,16 +1,17 @@ import React, { forwardRef } from 'react'; -import { Box, BoxProps } from '@chakra-ui/react'; +import { Box, BoxProps, SpinnerProps } from '@chakra-ui/react'; import Loading from '../MyLoading'; type Props = BoxProps & { isLoading?: boolean; text?: string; + size?: SpinnerProps['size']; }; -const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => { +const MyBox = ({ text, isLoading, children, size, ...props }: Props, ref: any) => { return ( - {isLoading && } + {isLoading && } {children} ); diff --git a/packages/web/components/common/MyLoading/index.tsx b/packages/web/components/common/MyLoading/index.tsx index 1d5d5ca80..4bc41e253 100644 --- a/packages/web/components/common/MyLoading/index.tsx +++ b/packages/web/components/common/MyLoading/index.tsx @@ -1,16 +1,18 @@ import React from 'react'; -import { Spinner, Flex, Box } from '@chakra-ui/react'; +import { Spinner, Flex, Box, SpinnerProps } from '@chakra-ui/react'; const Loading = ({ fixed = true, text = '', bg = 'rgba(255,255,255,0.5)', - zIndex = 1000 + zIndex = 1000, + size = 'xl' }: { fixed?: boolean; text?: string; bg?: string; zIndex?: number; + size?: SpinnerProps['size']; }) => { return ( {text && ( diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index 2bafc7319..05bebfadb 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -16,12 +16,12 @@ import { useLoading } from '../../../hooks/useLoading'; import MyIcon from '../Icon'; export type SelectProps = ButtonProps & { - value?: string; + value?: string | number; placeholder?: string; list: { alias?: string; label: string | React.ReactNode; - value: string; + value: string | number; }[]; isLoading?: boolean; onchange?: (val: any) => void; diff --git a/projects/app/i18n/en/common.json b/projects/app/i18n/en/common.json index 30d793e8a..18d553807 100644 --- a/projects/app/i18n/en/common.json +++ b/projects/app/i18n/en/common.json @@ -1212,6 +1212,8 @@ "Tools": "Tools" }, "permission": { + "Default permission": "Default permission", + "Manage": "Manage", "Private": "Private", "Private Tip": "Only available to oneself", "Public": "Team", diff --git a/projects/app/i18n/en/user.json b/projects/app/i18n/en/user.json index 0967ef424..5a3d9e94b 100644 --- a/projects/app/i18n/en/user.json +++ b/projects/app/i18n/en/user.json @@ -1 +1,5 @@ -{} +{ + "team": { + "Add manager": "Add manager" + } +} diff --git a/projects/app/i18n/zh/common.json b/projects/app/i18n/zh/common.json index efffb8717..02e211a7a 100644 --- a/projects/app/i18n/zh/common.json +++ b/projects/app/i18n/zh/common.json @@ -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": "移出团队", diff --git a/projects/app/i18n/zh/user.json b/projects/app/i18n/zh/user.json index 0967ef424..377234a7f 100644 --- a/projects/app/i18n/zh/user.json +++ b/projects/app/i18n/zh/user.json @@ -1 +1,5 @@ -{} +{ + "team": { + "Add manager": "添加管理员" + } +} diff --git a/projects/app/public/imgs/modal/edit.svg b/projects/app/public/imgs/modal/edit.svg index a8fad3984..dd6541d3f 100644 --- a/projects/app/public/imgs/modal/edit.svg +++ b/projects/app/public/imgs/modal/edit.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/projects/app/src/components/support/permission/DefaultPerList/index.tsx b/projects/app/src/components/support/permission/DefaultPerList/index.tsx new file mode 100644 index 000000000..839ccfae9 --- /dev/null +++ b/projects/app/src/components/support/permission/DefaultPerList/index.tsx @@ -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 & { + 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 ( + + { + onChange(v); + }} + /> + + ); +}; + +export default DefaultPermissionList; diff --git a/projects/app/src/components/support/permission/IconText/index.tsx b/projects/app/src/components/support/permission/IconText/index.tsx index 8659aeab3..db232e7bd 100644 --- a/projects/app/src/components/support/permission/IconText/index.tsx +++ b/projects/app/src/components/support/permission/IconText/index.tsx @@ -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] ? ( - + - {t(PermissionTypeMap[permission]?.label)} + {t(PermissionTypeMap[per]?.label)} ) : null; diff --git a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx new file mode 100644 index 000000000..17e35e35b --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx @@ -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(''); + 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([]); + 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 ( + + + + + + + + + setSearchText(e.target.value)} + /> + + + {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 ( + + + + + + {member.memberName} + + {!!collaborator && } + + + ); + })} + + + + 已选: {selectedMemberIdList.length} + + {selectedMemberIdList.map((tmbId) => { + const member = filterMembers.find((v) => v.tmbId === tmbId); + return member ? ( + + + + {member.memberName} + + + setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) + } + /> + + ) : null; + })} + + + + + + + {perLabel} + + + } + onChange={(v) => setSelectedPermission(v)} + /> + + + + ); +} diff --git a/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx new file mode 100644 index 000000000..e65c5f4fb --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx @@ -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 ( + + + + + + + + + + + + + {collaboratorList?.map((item) => { + return ( + + + + + + ); + })} + +
名称权限操作
+ + + {item.name} + + + + + {item.tmbId !== userInfo?.team?.tmbId && ( + + } + value={item.permission} + onChange={(per) => { + onUpdate({ + tmbId: item.tmbId, + per + }); + }} + onDelete={() => { + onDelete(item.tmbId); + }} + /> + )} +
+ {collaboratorList?.length === 0 && } +
+
+
+ ); +} + +export default ManageModal; diff --git a/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx b/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx new file mode 100644 index 000000000..a1054f068 --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx @@ -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; + +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(null); + const closeTimer = useRef(); + + 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 ( + + { + if (trigger === 'hover') { + setIsOpen(true); + } + clearTimeout(closeTimer.current); + }} + onMouseLeave={() => { + if (trigger === 'hover') { + closeTimer.current = setTimeout(() => { + setIsOpen(false); + }, 100); + } + }} + > + { + if (trigger === 'click') { + setIsOpen(!isOpen); + } + }} + > + + + {Button} + + + + {/* 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 ( + + + + {item.name} + {item.description} + + + ); + })} + + {/* + + {multipleValues.length > 0 && 其他权限(多选)} */} + + {/* 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 ( + + + + {item.name} + {item.description} + + + ); + })}*/} + {onDelete && ( + <> + + { + onDelete(); + setIsOpen(false); + }} + > + + {t('common.Delete')} + + + )} + + + + ); +} + +export default React.memo(PermissionSelect); diff --git a/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx new file mode 100644 index 000000000..5e09b9909 --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx @@ -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 ( + + {perTagList.map((item) => ( + + {item} + + ))} + + ); +} + +export default PermissionTags; diff --git a/projects/app/src/components/support/permission/MemberManager/context.tsx b/projects/app/src/components/support/permission/MemberManager/context.tsx new file mode 100644 index 000000000..29638fdbc --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/context.tsx @@ -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; + 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({ + 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 { + 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 ( + {children} + ); +}; diff --git a/projects/app/src/components/support/permission/MemberManager/index.tsx b/projects/app/src/components/support/permission/MemberManager/index.tsx new file mode 100644 index 000000000..c83e5e146 --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/index.tsx @@ -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 ( + <> + + 协作者 + + + + + + + {/* member list */} + + {collaboratorList?.length === 0 ? ( + + 暂无协作者 + + ) : ( + + {collaboratorList?.map((member) => { + return ( + + + + {member.name} + + + ); + })} + + )} + + {isOpenAddMember && } + {isOpenManageModal && } + + ); +} + +function Render(props: MemberManagerInputPropsType) { + return ( + + + + ); +} + +export default React.memo(Render); diff --git a/projects/app/src/components/support/user/team/TeamManageModal/MemberTable.tsx b/projects/app/src/components/support/user/team/TeamManageModal/MemberTable.tsx deleted file mode 100644 index 810d40fe0..000000000 --- a/projects/app/src/components/support/user/team/TeamManageModal/MemberTable.tsx +++ /dev/null @@ -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 ( - - - - - - - - - - - - {members.map((item) => ( - - - - - - - ))} - -
{t('common.Username')}{t('user.team.Role')}{t('common.Status')}{t('common.Action')}
- - - {item.memberName} - - {t(TeamMemberRoleMap[item.role]?.label || '')} - {t(TeamMemberStatusMap[item.status]?.label || '')} - - {hasManage( - members.find((item) => item.tmbId === userInfo?.team.tmbId)?.permission! - ) && - item.role !== TeamMemberRoleEnum.owner && - item.tmbId !== userInfo?.team.tmbId && ( - - - - } - 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 - }) - )() - } - ]} - /> - )} -
-
- ); -} - -export default MemberTable; diff --git a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx index db3291847..d2c7d11a9 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx @@ -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, '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: ( - - {t('user.team.Member')} - - {members.length} - - - ), - 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: ( + + {t('user.team.Member')} + + {members.length} + + + ), + 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(Tablist[0].value); return ( { - setTab(v as string); + setTab(v as TabListEnum); }} > + {/* ctrl buttons */} - {hasManage( - members.find((item) => item.tmbId.toString() === userInfo?.team.tmbId.toString()) - ?.permission! - ) && - tab === 'member' && ( - - )} - {userInfo?.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && ( + {tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && ( + + )} + {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && ( diff --git a/projects/app/src/components/support/user/team/TeamManageModal/PermissionManage.tsx b/projects/app/src/components/support/user/team/TeamManageModal/components/PermissionManage/index.tsx similarity index 71% rename from projects/app/src/components/support/user/team/TeamManageModal/PermissionManage.tsx rename to projects/app/src/components/support/user/team/TeamManageModal/components/PermissionManage/index.tsx index 96db5d51b..9440ffd08 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/PermissionManage.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/components/PermissionManage/index.tsx @@ -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 ( - - {isOpenAddManager && ( - - )} - + - 可邀请, 删除成员 + {TeamPermissionList['manage'].description} {userInfo?.team.role === 'owner' && ( @@ -93,7 +80,7 @@ function PermissionManage() { {members.map((member) => { - if (hasManage(member.permission) && member.role !== TeamMemberRoleEnum.owner) { + if (member.permission.hasManagePer && !member.permission.isOwner) { return ( @@ -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() { } })} - + + {isOpenAddManager && ( + + )} + ); } diff --git a/projects/app/src/components/support/user/team/TeamManageModal/context.tsx b/projects/app/src/components/support/user/team/TeamManageModal/context.tsx new file mode 100644 index 000000000..4cceb7e71 --- /dev/null +++ b/projects/app/src/components/support/user/team/TeamManageModal/context.tsx @@ -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>; + members: TeamMemberItemType[]; + refetchMembers: () => void; +}; + +export const TeamModalContext = createContext({ + myTeams: [], + isLoading: false, + onSwitchTeam: function (teamId: string): void { + throw new Error('Function not implemented.'); + }, + setEditTeamData: function (value: React.SetStateAction): 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(); + 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 ( + + {children} + {!!editTeamData && ( + setEditTeamData(undefined)} + onSuccess={() => { + refetchTeams(); + initUserInfo(); + }} + /> + )} + + ); +}; diff --git a/projects/app/src/components/support/user/team/TeamManageModal/index.tsx b/projects/app/src/components/support/user/team/TeamManageModal/index.tsx index 293391294..6a5c84d23 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/index.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/index.tsx @@ -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>; - members: Awaited>; - myTeams: Awaited>; - refetchTeam: ReturnType['refetch']; - onSwitchTeam: ReturnType['mutate']; - refetchMembers: ReturnType['refetch']; - openRemoveMember: ReturnType['openConfirm']; - onOpenInvite: ReturnType['onOpen']; - onOpenTeamTagsAsync: ReturnType['onOpen']; - onRemoveMember: ReturnType['mutate']; - onLeaveTeam: ReturnType['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(); - 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 ? ( - + return ( + <> void }) => { > - - + + + + - {isOpenInvite && userInfo?.team?.teamId && ( - - )} - {isOpenTeamTagsAsync && } - - - ) : null; + + ); }; -export default React.memo(TeamManageModal); +const Render = (props: Props) => { + const { userInfo } = useUserStore(); + + return !!userInfo?.team ? ( + + + + ) : null; +}; +export default React.memo(Render); diff --git a/projects/app/src/components/support/user/team/TeamMenu/index.tsx b/projects/app/src/components/support/user/team/TeamMenu/index.tsx index 8cbc8d145..6c43b553e 100644 --- a/projects/app/src/components/support/user/team/TeamMenu/index.tsx +++ b/projects/app/src/components/support/user/team/TeamMenu/index.tsx @@ -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') }); } }} diff --git a/projects/app/src/global/core/app/api.d.ts b/projects/app/src/global/core/app/api.d.ts index 2528210ac..ddd52828a 100644 --- a/projects/app/src/global/core/app/api.d.ts +++ b/projects/app/src/global/core/app/api.d.ts @@ -19,6 +19,7 @@ export type AppUpdateParams = { chatConfig?: AppSchema['chatConfig']; permission?: AppSchema['permission']; teamTags?: AppSchema['teamTags']; + defaultPermission?: AppSchema['defaultPermission']; }; export type PostPublishAppProps = { diff --git a/projects/app/src/pages/account/components/Info.tsx b/projects/app/src/pages/account/components/Info.tsx index 9f40e6e29..427907b32 100644 --- a/projects/app/src/pages/account/components/Info.tsx +++ b/projects/app/src/pages/account/components/Info.tsx @@ -265,7 +265,7 @@ const MyInfo = () => { {formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)} - {feConfigs?.show_pay && userInfo?.team?.canWrite && ( + {feConfigs?.show_pay && userInfo?.team?.permission.hasWritePer && ( diff --git a/projects/app/src/pages/account/components/UsageTable.tsx b/projects/app/src/pages/account/components/UsageTable.tsx index 9078e4577..35d2f3770 100644 --- a/projects/app/src/pages/account/components/UsageTable.tsx +++ b/projects/app/src/pages/account/components/UsageTable.tsx @@ -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 && ( {t('support.user.team.member')} diff --git a/projects/app/src/pages/account/index.tsx b/projects/app/src/pages/account/index.tsx index 5929d9260..55b0ba529 100644 --- a/projects/app/src/pages/account/index.tsx +++ b/projects/app/src/pages/account/index.tsx @@ -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', diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index a457255f4..d2d45b9b4 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -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) { const { @@ -23,7 +24,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } // 凭证校验 - const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); + const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); // 上限校验 await checkTeamAppLimit(teamId); diff --git a/projects/app/src/pages/api/core/app/del.ts b/projects/app/src/pages/api/core/app/del.ts index e816c2747..550858310 100644 --- a/projects/app/src/pages/api/core/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -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) { const { appId } = req.query as { appId: string }; @@ -17,7 +18,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } // 凭证校验 - await authApp({ req, authToken: true, appId, per: 'owner' }); + await authApp({ req, authToken: true, appId, per: OwnerPermissionVal }); // 删除对应的聊天 await mongoSessionRun(async (session) => { diff --git a/projects/app/src/pages/api/core/app/detail.tsx b/projects/app/src/pages/api/core/app/detail.ts similarity index 71% rename from projects/app/src/pages/api/core/app/detail.tsx rename to projects/app/src/pages/api/core/app/detail.ts index 451ba4305..da64904dd 100644 --- a/projects/app/src/pages/api/core/app/detail.tsx +++ b/projects/app/src/pages/api/core/app/detail.ts @@ -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) { @@ -9,9 +10,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { 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; } diff --git a/projects/app/src/pages/api/core/app/getChatLogs.ts b/projects/app/src/pages/api/core/app/getChatLogs.ts index c35db948c..9efec2e2d 100644 --- a/projects/app/src/pages/api/core/app/getChatLogs.ts +++ b/projects/app/src/pages/api/core/app/getChatLogs.ts @@ -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), diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index b905fbb63..bd8799cb8 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -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): Promise { // 凭证校验 - 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 })); } diff --git a/projects/app/src/pages/api/core/app/update.ts b/projects/app/src/pages/api/core/app/update.ts index 63b168123..da2bfd5d0 100644 --- a/projects/app/src/pages/api/core/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -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) { - 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) { } // 凭证校验 - 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) { avatar, intro, permission, + defaultPermission, ...(teamTags && teamTags), ...(formatNodes && { modules: formatNodes diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts index edfa89333..398503b8d 100644 --- a/projects/app/src/pages/api/core/app/version/publish.ts +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -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): 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 }); diff --git a/projects/app/src/pages/api/core/app/version/revert.ts b/projects/app/src/pages/api/core/app/version/revert.ts index fd6e2f5e7..d7933ad06 100644 --- a/projects/app/src/pages/api/core/app/version/revert.ts +++ b/projects/app/src/pages/api/core/app/version/revert.ts @@ -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): 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, diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index d8991c952..e6af6a537 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -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 diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts index 5b04ed192..380dd4863 100644 --- a/projects/app/src/pages/api/core/chat/init.ts +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -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); } diff --git a/projects/app/src/pages/api/core/chat/inputGuide/create.ts b/projects/app/src/pages/api/core/chat/inputGuide/create.ts index 4cdcc9e6f..e96be45d2 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/create.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/create.ts @@ -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 ): Promise { 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( diff --git a/projects/app/src/pages/api/core/chat/inputGuide/delete.ts b/projects/app/src/pages/api/core/chat/inputGuide/delete.ts index ee4330192..fe19c5d20 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/delete.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/delete.ts @@ -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 ): Promise { 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 }, diff --git a/projects/app/src/pages/api/core/chat/inputGuide/list.ts b/projects/app/src/pages/api/core/chat/inputGuide/list.ts index ac1a2ec82..e923448dd 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/list.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/list.ts @@ -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 { 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, diff --git a/projects/app/src/pages/api/core/chat/inputGuide/update.ts b/projects/app/src/pages/api/core/chat/inputGuide/update.ts index 632134bd1..65e6660d6 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/update.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/update.ts @@ -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 ): Promise { 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( { diff --git a/projects/app/src/pages/api/core/dataset/allDataset.ts b/projects/app/src/pages/api/core/dataset/allDataset.ts index c2192a15e..93bd68f71 100644 --- a/projects/app/src/pages/api/core/dataset/allDataset.ts +++ b/projects/app/src/pages/api/core/dataset/allDataset.ts @@ -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 ): Promise { // 凭证校验 - 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(); diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index 6e3fc50e0..cfc52f08d 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -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) { 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); diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index 8e7ce7609..d9034e1d8 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -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) { 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) { 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) })) ); diff --git a/projects/app/src/pages/api/core/plugin/create.ts b/projects/app/src/pages/api/core/plugin/create.ts index 055211f70..8d992cd1e 100644 --- a/projects/app/src/pages/api/core/plugin/create.ts +++ b/projects/app/src/pages/api/core/plugin/create.ts @@ -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) { 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); diff --git a/projects/app/src/pages/api/core/plugin/delete.ts b/projects/app/src/pages/api/core/plugin/delete.ts index da4def929..dc624a3d1 100644 --- a/projects/app/src/pages/api/core/plugin/delete.ts +++ b/projects/app/src/pages/api/core/plugin/delete.ts @@ -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) { 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) { diff --git a/projects/app/src/pages/api/core/workflow/debug.ts b/projects/app/src/pages/api/core/workflow/debug.ts index ae0c3cda4..53f067a0e 100644 --- a/projects/app/src/pages/api/core/workflow/debug.ts +++ b/projects/app/src/pages/api/core/workflow/debug.ts @@ -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' }) ]); diff --git a/projects/app/src/pages/api/support/openapi/create.ts b/projects/app/src/pages/api/support/openapi/create.ts index 39bb2eb4d..89100bb18 100644 --- a/projects/app/src/pages/api/support/openapi/create.ts +++ b/projects/app/src/pages/api/support/openapi/create.ts @@ -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, diff --git a/projects/app/src/pages/api/support/openapi/list.ts b/projects/app/src/pages/api/support/openapi/list.ts index 03e6a2cae..8d804828d 100644 --- a/projects/app/src/pages/api/support/openapi/list.ts +++ b/projects/app/src/pages/api/support/openapi/list.ts @@ -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, { diff --git a/projects/app/src/pages/api/support/outLink/create.ts b/projects/app/src/pages/api/support/outLink/create.ts index ee9edbd1d..ee3d5530d 100644 --- a/projects/app/src/pages/api/support/outLink/create.ts +++ b/projects/app/src/pages/api/support/outLink/create.ts @@ -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({ diff --git a/projects/app/src/pages/api/support/outLink/delete.ts b/projects/app/src/pages/api/support/outLink/delete.ts index 944ed86c5..18b0d7401 100644 --- a/projects/app/src/pages/api/support/outLink/delete.ts +++ b/projects/app/src/pages/api/support/outLink/delete.ts @@ -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); diff --git a/projects/app/src/pages/api/support/outLink/list.ts b/projects/app/src/pages/api/support/outLink/list.ts index 926b47fcb..5db35e451 100644 --- a/projects/app/src/pages/api/support/outLink/list.ts +++ b/projects/app/src/pages/api/support/outLink/list.ts @@ -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 diff --git a/projects/app/src/pages/api/support/outLink/update.ts b/projects/app/src/pages/api/support/outLink/update.ts index d5248b93f..069b30e55 100644 --- a/projects/app/src/pages/api/support/outLink/update.ts +++ b/projects/app/src/pages/api/support/outLink/update.ts @@ -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, diff --git a/projects/app/src/pages/api/support/user/team/update.ts b/projects/app/src/pages/api/support/user/team/update.ts index 80d39bbc3..1a257ed7d 100644 --- a/projects/app/src/pages/api/support/user/team/update.ts +++ b/projects/app/src/pages/api/support/user/team/update.ts @@ -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, res: ApiResponseType) { 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 }); } diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 8806fc780..df9a7933d 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -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 }; } })(); diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx index c87aa234c..ea29db4e4 100644 --- a/projects/app/src/pages/app/detail/components/InfoModal.tsx +++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx @@ -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 ( {t('core.app.Name and avatar')} {t('core.app.App intro')} - {/* - 该介绍主要用于记忆和在应用市场展示 - */}