feat: org CRUD (#3380)
* feat: add org schema * feat: org manage UI * feat: OrgInfoModal * feat: org tree view * feat: org management * fix: init root org * feat: org permission for app * feat: org support for dataset * fix: disable org role control * styles: opt type signatures * fix: remove unused permission * feat: delete org collaborator
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ErrType } from '../errorCode';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
import type { ErrType } from '../errorCode';
|
||||
/* team: 500000 */
|
||||
export enum TeamErrEnum {
|
||||
teamOverSize = 'teamOverSize',
|
||||
@@ -14,6 +14,13 @@ export enum TeamErrEnum {
|
||||
groupNameEmpty = 'groupNameEmpty',
|
||||
groupNameDuplicate = 'groupNameDuplicate',
|
||||
groupNotExist = 'groupNotExist',
|
||||
orgMemberNotExist = 'orgMemberNotExist',
|
||||
orgMemberDuplicated = 'orgMemberDuplicated',
|
||||
orgNotExist = 'orgNotExist',
|
||||
orgParentNotExist = 'orgParentNotExist',
|
||||
cannotMoveToSubPath = 'cannotMoveToSubPath',
|
||||
cannotModifyRootOrg = 'cannotModifyRootOrg',
|
||||
cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg',
|
||||
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
|
||||
userNotActive = 'userNotActive'
|
||||
}
|
||||
@@ -71,6 +78,34 @@ const teamErr = [
|
||||
{
|
||||
statusText: TeamErrEnum.userNotActive,
|
||||
message: i18nT('common:code_error.team_error.user_not_active')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.orgMemberNotExist,
|
||||
message: i18nT('common:code_error.team_error.org_member_not_exist')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.orgMemberDuplicated,
|
||||
message: i18nT('common:code_error.team_error.org_member_duplicated')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.orgNotExist,
|
||||
message: i18nT('common:code_error.team_error.org_not_exist')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.orgParentNotExist,
|
||||
message: i18nT('common:code_error.team_error.org_parent_not_exist')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.cannotMoveToSubPath,
|
||||
message: i18nT('common:code_error.team_error.cannot_move_to_sub_path')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.cannotModifyRootOrg,
|
||||
message: i18nT('common:code_error.team_error.cannot_modify_root_org')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.cannotDeleteNonEmptyOrg,
|
||||
message: i18nT('common:code_error.team_error.cannot_delete_non_empty_org')
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum MongoImageTypeEnum {
|
||||
userAvatar = 'userAvatar',
|
||||
teamAvatar = 'teamAvatar',
|
||||
groupAvatar = 'groupAvatar',
|
||||
orgAvatar = 'orgAvatar',
|
||||
|
||||
chatImage = 'chatImage',
|
||||
collectionImage = 'collectionImage'
|
||||
@@ -41,6 +42,10 @@ export const mongoImageTypeMap = {
|
||||
label: 'groupAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.orgAvatar]: {
|
||||
label: 'orgAvatar',
|
||||
unique: true
|
||||
},
|
||||
|
||||
[MongoImageTypeEnum.chatImage]: {
|
||||
label: 'chatImage',
|
||||
|
||||
@@ -2,5 +2,6 @@ export const HUMAN_ICON = `/icon/human.svg`;
|
||||
export const LOGO_ICON = `/icon/logo.svg`;
|
||||
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`;
|
||||
export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`;
|
||||
export const DEFAULT_ORG_AVATAR = '/imgs/avatar/defaultOrgAvatar.svg';
|
||||
|
||||
export const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
5
packages/global/core/app/collaborator.d.ts
vendored
5
packages/global/core/app/collaborator.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { RequireOnlyOne } from '../../common/type/utils';
|
||||
import type { RequireOnlyOne } from '../../common/type/utils';
|
||||
import {
|
||||
UpdateClbPermissionProps,
|
||||
type UpdateClbPermissionProps,
|
||||
UpdatePermissionBody
|
||||
} from '../../support/permission/collaborator';
|
||||
import { PermissionValueType } from '../../support/permission/type';
|
||||
@@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
@@ -10,17 +10,20 @@ export type CollaboratorItemType = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type UpdateClbPermissionProps = {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
permission: PermissionValueType;
|
||||
};
|
||||
|
||||
export type DeleteClbPermissionProps = RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type UpdatePermissionBody = {
|
||||
@@ -28,4 +31,5 @@ export type UpdatePermissionBody = {
|
||||
} & RequireOnlyOne<{
|
||||
memberId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
4
packages/global/support/permission/type.d.ts
vendored
4
packages/global/support/permission/type.d.ts
vendored
@@ -1,8 +1,9 @@
|
||||
import { UserModelSchema } from '../user/type';
|
||||
import { RequireOnlyOne } from '../../common/type/utils';
|
||||
import { TeamMemberSchema } from '../user/team/type';
|
||||
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
|
||||
import { MemberGroupSchemaType } from './memberGroup/type';
|
||||
import type { TeamMemberWithUserSchema } from '../user/team/type';
|
||||
import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } 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.
|
||||
@@ -29,6 +30,7 @@ export type ResourcePermissionType = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
|
||||
|
||||
38
packages/global/support/user/team/org/api.d.ts
vendored
Normal file
38
packages/global/support/user/team/org/api.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
export type postCreateOrgData = {
|
||||
name: string;
|
||||
parentId: string;
|
||||
description?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
export type putUpdateOrgMembersData = {
|
||||
orgId: string;
|
||||
members: {
|
||||
tmbId: string;
|
||||
// role: `${OrgMemberRole}`;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type putUpdateOrgData = {
|
||||
orgId: string;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type putMoveOrgData = {
|
||||
orgId: string;
|
||||
parentId: string;
|
||||
};
|
||||
|
||||
export type putMoveOrgMemberData = {
|
||||
orgId: string;
|
||||
tmbId: string;
|
||||
newOrgId: string;
|
||||
};
|
||||
|
||||
// type putChnageOrgOwnerData = {
|
||||
// orgId: string;
|
||||
// tmbId: string;
|
||||
// toAdmin?: boolean;
|
||||
// };
|
||||
8
packages/global/support/user/team/org/constant.ts
Normal file
8
packages/global/support/user/team/org/constant.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const OrgCollectionName = 'team_orgs';
|
||||
export const OrgMemberCollectionName = 'team_org_members';
|
||||
|
||||
// export enum OrgMemberRole {
|
||||
// owner = 'owner',
|
||||
// admin = 'admin',
|
||||
// member = 'member'
|
||||
// }
|
||||
23
packages/global/support/user/team/org/type.d.ts
vendored
Normal file
23
packages/global/support/user/team/org/type.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { TeamPermission } from 'support/permission/user/controller';
|
||||
import { ResourcePermissionType } from '../type';
|
||||
|
||||
type OrgSchemaType = {
|
||||
_id: string;
|
||||
teamId: string;
|
||||
path: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
updateTime: Date;
|
||||
};
|
||||
|
||||
type OrgMemberSchemaType = {
|
||||
teamId: string;
|
||||
orgId: string;
|
||||
tmbId: string;
|
||||
};
|
||||
|
||||
type OrgType = Omit<OrgSchemaType, 'avatar'> & {
|
||||
avatar: string;
|
||||
members: OrgMemberSchemaType[];
|
||||
};
|
||||
58
packages/service/support/permission/auth/org.ts
Normal file
58
packages/service/support/permission/auth/org.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
|
||||
export const authOrgMember = async ({
|
||||
orgIds,
|
||||
req,
|
||||
authToken = false,
|
||||
authRoot = false,
|
||||
authApiKey = false
|
||||
}: {
|
||||
orgIds: string | string[];
|
||||
} & AuthModeType): Promise<AuthResponseType> => {
|
||||
const result = await parseHeaderCert({ req, authToken, authApiKey, authRoot });
|
||||
const { teamId, tmbId, isRoot } = result;
|
||||
if (isRoot) {
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
userId: result.userId,
|
||||
appId: result.appId,
|
||||
apikey: result.apikey,
|
||||
isRoot,
|
||||
authType: result.authType,
|
||||
permission: new TeamPermission({ isOwner: true })
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.isArray(orgIds)) {
|
||||
orgIds = [orgIds];
|
||||
}
|
||||
|
||||
// const promises = orgIds.map((orgId) => getOrgMemberRole({ orgId, tmbId }));
|
||||
|
||||
const tmb = await getTmbInfoByTmbId({ tmbId });
|
||||
if (tmb.permission.hasManagePer) {
|
||||
return {
|
||||
...result,
|
||||
permission: tmb.permission
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
|
||||
// const targetRole = OrgMemberRole[role];
|
||||
// for (const orgRole of orgRoles) {
|
||||
// if (!orgRole || checkOrgRole(orgRole, targetRole)) {
|
||||
// return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return {
|
||||
// ...result,
|
||||
// permission: tmb.permission
|
||||
// };
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
|
||||
/** get resource permission for a team member
|
||||
* If there is no permission for the team member, it will return undefined
|
||||
@@ -186,6 +187,16 @@ export const getClbsAndGroupsWithInfo = async ({
|
||||
}
|
||||
})
|
||||
.populate<{ group: MemberGroupSchemaType }>('group', 'name avatar')
|
||||
.lean(),
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
orgId: {
|
||||
$exists: true
|
||||
}
|
||||
})
|
||||
.populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' })
|
||||
.lean()
|
||||
]);
|
||||
|
||||
@@ -196,6 +207,7 @@ export const delResourcePermission = ({
|
||||
session,
|
||||
tmbId,
|
||||
groupId,
|
||||
orgId,
|
||||
...props
|
||||
}: {
|
||||
resourceType: PerResourceTypeEnum;
|
||||
@@ -204,15 +216,18 @@ export const delResourcePermission = ({
|
||||
session?: ClientSession;
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}) => {
|
||||
// tmbId or groupId only one and not both
|
||||
if (!!tmbId === !!groupId) {
|
||||
// either tmbId or groupId or orgId must be provided
|
||||
if (!tmbId && !groupId && !orgId) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
return MongoResourcePermission.deleteOne(
|
||||
{
|
||||
...(tmbId ? { tmbId } : {}),
|
||||
...(groupId ? { groupId } : {}),
|
||||
...(orgId ? { orgId } : {}),
|
||||
...props
|
||||
},
|
||||
{ session }
|
||||
@@ -250,7 +265,7 @@ export function authJWT(token: string) {
|
||||
}>((resolve, reject) => {
|
||||
const key = process.env.TOKEN_KEY as string;
|
||||
|
||||
jwt.verify(token, key, function (err, decoded: any) {
|
||||
jwt.verify(token, key, (err, decoded: any) => {
|
||||
if (err || !decoded?.userId) {
|
||||
reject(ERROR_ENUM.unAuthorization);
|
||||
return;
|
||||
@@ -436,7 +451,7 @@ export const authFileToken = (token?: string) =>
|
||||
}
|
||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||
|
||||
jwt.verify(token, key, function (err, decoded: any) {
|
||||
jwt.verify(token, key, (err, decoded: any) => {
|
||||
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
|
||||
reject(ERROR_ENUM.unAuthFile);
|
||||
return;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { mongoSessionRun } from '../../common/mongo/sessionRun';
|
||||
import { MongoResourcePermission } from './schema';
|
||||
import { ClientSession, Model } from 'mongoose';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import type { ClientSession, Model } from 'mongoose';
|
||||
import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { getResourceClbsAndGroups } from './controller';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
|
||||
export type SyncChildrenPermissionResourceType = {
|
||||
_id: string;
|
||||
@@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
// sync the permission to all children folders.
|
||||
@@ -161,7 +162,7 @@ export async function resumeInheritPermission({
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
Delete all the collaborators and then insert the new collaborators.
|
||||
*/
|
||||
export async function syncCollaborators({
|
||||
|
||||
174
packages/service/support/permission/org/controllers.ts
Normal file
174
packages/service/support/permission/org/controllers.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import type {
|
||||
OrgMemberSchemaType,
|
||||
OrgSchemaType
|
||||
} from '@fastgpt/global/support/user/team/org/type';
|
||||
import type { ClientSession } from 'mongoose';
|
||||
import { MongoOrgModel } from './orgSchema';
|
||||
import { MongoOrgMemberModel } from './orgMemberSchema';
|
||||
|
||||
// if role1 > role2, return 1
|
||||
// if role1 < role2, return -1
|
||||
// else return 0
|
||||
// export const compareRole = (role1: OrgMemberRole, role2: OrgMemberRole) => {
|
||||
// if (role1 === OrgMemberRole.owner) {
|
||||
// if (role2 === OrgMemberRole.owner) {
|
||||
// return 0;
|
||||
// }
|
||||
// return 1;
|
||||
// }
|
||||
// if (role2 === OrgMemberRole.owner) {
|
||||
// return -1;
|
||||
// }
|
||||
// if (role1 === OrgMemberRole.admin) {
|
||||
// if (role2 === OrgMemberRole.admin) {
|
||||
// return 0;
|
||||
// }
|
||||
// return 1;
|
||||
// }
|
||||
// if (role2 === OrgMemberRole.admin) {
|
||||
// return -1;
|
||||
// }
|
||||
// return 0;
|
||||
// };
|
||||
|
||||
// export const checkOrgRole = (role: OrgMemberRole, targetRole: OrgMemberRole) => {
|
||||
// return compareRole(role, targetRole) >= 0;
|
||||
// };
|
||||
|
||||
export const getOrgsByTeamId = async (teamId: string) => {
|
||||
const orgs = await MongoOrgModel.find({
|
||||
teamId
|
||||
})
|
||||
.populate<{ members: OrgMemberSchemaType }>('members')
|
||||
.lean();
|
||||
|
||||
return orgs;
|
||||
};
|
||||
|
||||
export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
|
||||
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
|
||||
|
||||
export const getChildrenByOrg = async ({
|
||||
org,
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
org: OrgSchemaType;
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) => {
|
||||
const children = await MongoOrgModel.find(
|
||||
{ teamId, path: { $regex: `^${org.path}/${org._id}` } },
|
||||
undefined,
|
||||
{
|
||||
session
|
||||
}
|
||||
).lean();
|
||||
return children;
|
||||
};
|
||||
|
||||
export const getOrgAndChildren = async ({
|
||||
orgId,
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
orgId: string;
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) => {
|
||||
const org = await MongoOrgModel.findOne({ _id: orgId, teamId }, undefined, { session }).lean();
|
||||
if (!org) {
|
||||
return Promise.reject(TeamErrEnum.orgNotExist);
|
||||
}
|
||||
const children = await getChildrenByOrg({ org, teamId, session });
|
||||
return { org, children };
|
||||
};
|
||||
|
||||
export async function createRootOrg({
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) {
|
||||
// Create the root org
|
||||
const [org] = await MongoOrgModel.create(
|
||||
[
|
||||
{
|
||||
teamId,
|
||||
name: 'ROOT',
|
||||
path: ''
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
);
|
||||
// Find the team's owner
|
||||
// const owner = await MongoTeamMember.findOne({ teamId, role: 'owner' }, undefined);
|
||||
// if (!owner) {
|
||||
// return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
// }
|
||||
|
||||
// Set the owner as the org admin
|
||||
// await MongoOrgMemberModel.create(
|
||||
// [
|
||||
// {
|
||||
// orgId: org._id,
|
||||
// tmbId: owner._id
|
||||
|
||||
// }
|
||||
// ],
|
||||
// { session }
|
||||
// );
|
||||
}
|
||||
|
||||
// export const getOrgMemberRole = async ({
|
||||
// orgId,
|
||||
// tmbId
|
||||
// }: {
|
||||
// orgId: string;
|
||||
// tmbId: string;
|
||||
// }): Promise<OrgMemberRole | undefined> => {
|
||||
// let role: OrgMemberRole | undefined;
|
||||
// const orgMember = await MongoOrgMemberModel.findOne({
|
||||
// orgId,
|
||||
// tmbId
|
||||
// })
|
||||
// .populate('orgId')
|
||||
// .lean();
|
||||
// if (orgMember) {
|
||||
// role = OrgMemberRole[orgMember.role];
|
||||
// } else {
|
||||
// return role;
|
||||
// }
|
||||
// if (role === OrgMemberRole.owner) {
|
||||
// return role;
|
||||
// }
|
||||
// // Check the parent orgs
|
||||
// const org = orgMember.orgId as unknown as OrgSchemaType;
|
||||
// if (!org) {
|
||||
// return Promise.reject(TeamErrEnum.orgNotExist);
|
||||
// }
|
||||
// const parentIds = org.path.split('/').filter((id) => id);
|
||||
// if (parentIds.length === 0) {
|
||||
// return role;
|
||||
// }
|
||||
// const parentOrgMembers = await MongoOrgMemberModel.find({
|
||||
// orgId: {
|
||||
// $in: parentIds
|
||||
// },
|
||||
// tmbId
|
||||
// }).lean();
|
||||
// // Update the role to the highest role
|
||||
// for (const parentOrgMember of parentOrgMembers) {
|
||||
// const parentRole = OrgMemberRole[parentOrgMember.role];
|
||||
// if (parentRole === OrgMemberRole.owner) {
|
||||
// role = parentRole;
|
||||
// break;
|
||||
// }
|
||||
// if (parentRole === OrgMemberRole.admin && role === OrgMemberRole.member) {
|
||||
// role = parentRole;
|
||||
// }
|
||||
// }
|
||||
// return role;
|
||||
// };
|
||||
58
packages/service/support/permission/org/orgMemberSchema.ts
Normal file
58
packages/service/support/permission/org/orgMemberSchema.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
import {
|
||||
TeamCollectionName,
|
||||
TeamMemberCollectionName
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import { OrgMemberSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const OrgMemberCollectionName = 'team_org_members';
|
||||
|
||||
export const OrgMemberSchema = new Schema({
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
orgId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: OrgCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
}
|
||||
// role: {
|
||||
// type: String,
|
||||
// enum: Object.values(OrgMemberRole),
|
||||
// required: true,
|
||||
// default: OrgMemberRole.member
|
||||
// }
|
||||
});
|
||||
|
||||
try {
|
||||
OrgMemberSchema.index(
|
||||
{
|
||||
teamId: 1,
|
||||
orgId: 1,
|
||||
tmbId: 1
|
||||
},
|
||||
{
|
||||
unique: true
|
||||
}
|
||||
);
|
||||
OrgMemberSchema.index({
|
||||
teamId: 1,
|
||||
tmbId: 1
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
export const MongoOrgMemberModel = getMongoModel<OrgMemberSchemaType>(
|
||||
OrgMemberCollectionName,
|
||||
OrgMemberSchema
|
||||
);
|
||||
69
packages/service/support/permission/org/orgSchema.ts
Normal file
69
packages/service/support/permission/org/orgSchema.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
import { ResourcePermissionCollectionName } from '../schema';
|
||||
import { OrgMemberCollectionName } from './orgMemberSchema';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
function requiredStringPath(this: OrgSchemaType) {
|
||||
return typeof this.path !== 'string';
|
||||
}
|
||||
|
||||
export const OrgSchema = new Schema(
|
||||
{
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: requiredStringPath // allow empty string, but not null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
avatar: String,
|
||||
description: String,
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
}
|
||||
},
|
||||
{
|
||||
// Auto update updateTime
|
||||
timestamps: {
|
||||
updatedAt: 'updateTime'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
OrgSchema.virtual('members', {
|
||||
ref: OrgMemberCollectionName,
|
||||
localField: '_id',
|
||||
foreignField: 'orgId'
|
||||
});
|
||||
OrgSchema.virtual('permission', {
|
||||
ref: ResourcePermissionCollectionName,
|
||||
localField: '_id',
|
||||
foreignField: 'orgId',
|
||||
justOne: true
|
||||
});
|
||||
|
||||
try {
|
||||
OrgSchema.index(
|
||||
{
|
||||
teamId: 1,
|
||||
path: 1
|
||||
},
|
||||
{
|
||||
unique: true
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
export const MongoOrgModel = getMongoModel<OrgSchemaType>(OrgCollectionName, OrgSchema);
|
||||
@@ -6,6 +6,7 @@ import { connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema';
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const ResourcePermissionCollectionName = 'resource_permissions';
|
||||
@@ -23,6 +24,10 @@ export const ResourcePermissionSchema = new Schema({
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: MemberGroupCollectionName
|
||||
},
|
||||
orgId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: OrgCollectionName
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
enum: Object.values(PerResourceTypeEnum),
|
||||
@@ -51,6 +56,12 @@ ResourcePermissionSchema.virtual('group', {
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
ResourcePermissionSchema.virtual('org', {
|
||||
ref: OrgCollectionName,
|
||||
localField: 'orgId',
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
|
||||
try {
|
||||
ResourcePermissionSchema.index(
|
||||
@@ -70,6 +81,23 @@ try {
|
||||
}
|
||||
);
|
||||
|
||||
ResourcePermissionSchema.index(
|
||||
{
|
||||
resourceType: 1,
|
||||
teamId: 1,
|
||||
resourceId: 1,
|
||||
orgId: 1
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
partialFilterExpression: {
|
||||
orgId: {
|
||||
$exists: true
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ResourcePermissionSchema.index(
|
||||
{
|
||||
resourceType: 1,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupS
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { getAIApi, openaiBaseUrl } from '../../../core/ai/config';
|
||||
import { createRootOrg } from '../../permission/org/controllers';
|
||||
|
||||
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
|
||||
const tmb = await MongoTeamMember.findOne(match).populate<{ team: TeamSchema }>('team').lean();
|
||||
@@ -132,7 +133,8 @@ export async function createDefaultTeam({
|
||||
],
|
||||
{ session }
|
||||
);
|
||||
console.log('create default team and group', userId);
|
||||
await createRootOrg({ teamId: tmb.teamId, session });
|
||||
console.log('create default team, group and root org', userId);
|
||||
return tmb;
|
||||
} else {
|
||||
console.log('default team exist', userId);
|
||||
|
||||
@@ -73,6 +73,7 @@ export const iconPaths = {
|
||||
'common/resultLight': () => import('./icons/common/resultLight.svg'),
|
||||
'common/retryLight': () => import('./icons/common/retryLight.svg'),
|
||||
'common/rightArrowFill': () => import('./icons/common/rightArrowFill.svg'),
|
||||
'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'),
|
||||
'common/rightArrowLight': () => import('./icons/common/rightArrowLight.svg'),
|
||||
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
|
||||
'common/saveFill': () => import('./icons/common/saveFill.svg'),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon/solid/chevron-down">
|
||||
<path id="Rectangle 3101" d="M11.4695 5.33325C12.6574 5.33325 13.2523 6.76944 12.4123 7.60939L9.01223 11.0095C8.49154 11.5302 7.64732 11.5302 7.12662 11.0095L3.72653 7.60939C2.88657 6.76944 3.48146 5.33325 4.66933 5.33325H11.4695Z" fill="#667085"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -2,11 +2,23 @@
|
||||
"action": "operate",
|
||||
"confirm_delete_group": "Confirm to delete group?",
|
||||
"confirm_leave_team": "Confirmed to leave the team? \n \nAfter you log out, all your resources in the team (applications, knowledge bases, folders, managed groups, etc.) will be transferred to the team owner.",
|
||||
"confirm_delete_org": "Confirm to delete organization?",
|
||||
"confirm_delete_member": "Confirm to delete member?",
|
||||
"create_group": "Create group",
|
||||
"delete": "delete",
|
||||
"edit_info": "Edit information",
|
||||
"group": "group",
|
||||
"group_name": "Group name",
|
||||
"org": "organization",
|
||||
"org_name": "Organization name",
|
||||
"org_description": "Organization description",
|
||||
"create_org": "Create organization",
|
||||
"create_sub_org": "Create sub-organization",
|
||||
"edit_org_info": "Edit organization information",
|
||||
"move_org": "Move organization",
|
||||
"move_member": "Move member",
|
||||
"delete_org": "Delete organization",
|
||||
"remark": "remark",
|
||||
"label_sync": "Tag sync",
|
||||
"leave_team_failed": "Leaving the team exception",
|
||||
"manage_member": "Managing members",
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
"code_error.team_error.un_auth": "Unauthorized to Operate This Team",
|
||||
"code_error.team_error.user_not_active": "The user did not accept or has left the team",
|
||||
"code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync",
|
||||
"code_error.team_error.org_member_not_exist": "Organization member does not exist",
|
||||
"code_error.team_error.org_member_duplicated": "Duplicate organization member",
|
||||
"code_error.team_error.org_not_exist": "Organization does not exist",
|
||||
"code_error.team_error.org_parent_not_exist": "Parent organization does not exist",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "Cannot move to same or subdirectory",
|
||||
"code_error.team_error.cannot_modify_root_org": "Cannot modify root organization",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "Cannot delete non-empty organization",
|
||||
"code_error.token_error_code.403": "Invalid Login Status, Please Re-login",
|
||||
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
|
||||
"code_error.user_error.bin_visitor": "Identity Verification Failed",
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
{
|
||||
"total_team_members": "共 {{amount}} 名成员",
|
||||
"member": "成员",
|
||||
"group": "群组",
|
||||
"permission": "权限",
|
||||
"user_name": "用户名",
|
||||
"member_group": "所属成员组",
|
||||
"action": "操作",
|
||||
"waiting": "待接受",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?",
|
||||
|
||||
"confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"label_sync": "标签同步",
|
||||
"user_team_invite_member": "邀请成员",
|
||||
"user_team_leave_team": "离开团队",
|
||||
"user_team_leave_team_failed": "离开团队失败",
|
||||
"create_group": "创建群组",
|
||||
"search_member_group_name": "搜索成员/群组名称",
|
||||
"confirm_delete_group": "确认删除群组?",
|
||||
"group_name": "群组名称",
|
||||
"owner": "所有者",
|
||||
"manage_member": "管理成员",
|
||||
"edit_info": "编辑信息",
|
||||
|
||||
"transfer_ownership": "转让所有者",
|
||||
"delete": "删除",
|
||||
"retain_admin_permissions": "保留管理员权限"
|
||||
}
|
||||
{
|
||||
"total_team_members": "共 {{amount}} 名成员",
|
||||
"member": "成员",
|
||||
"group": "群组",
|
||||
"org": "组织",
|
||||
"org_name": "组织名称",
|
||||
"org_description": "介绍",
|
||||
"permission": "权限",
|
||||
"user_name": "用户名",
|
||||
"member_group": "所属成员组",
|
||||
"action": "操作",
|
||||
"remark": "备注",
|
||||
"waiting": "待接受",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?",
|
||||
|
||||
"confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"label_sync": "标签同步",
|
||||
"user_team_invite_member": "邀请成员",
|
||||
"user_team_leave_team": "离开团队",
|
||||
"user_team_leave_team_failed": "离开团队失败",
|
||||
"create_group": "创建群组",
|
||||
"search_member_group_name": "搜索成员/群组名称",
|
||||
"confirm_delete_group": "确认删除群组?",
|
||||
"group_name": "群组名称",
|
||||
"owner": "所有者",
|
||||
"manage_member": "管理成员",
|
||||
"edit_info": "编辑信息",
|
||||
"create_org": "创建组织",
|
||||
"create_sub_org": "创建子组织",
|
||||
"edit_org_info": "编辑组织信息",
|
||||
"move_org": "移动组织",
|
||||
"move_member": "移动成员",
|
||||
"delete_org": "删除组织",
|
||||
"confirm_delete_org": "确认删除组织?",
|
||||
"confirm_delete_member": "确认删除成员?",
|
||||
|
||||
"transfer_ownership": "转让所有者",
|
||||
"delete": "删除",
|
||||
"retain_admin_permissions": "保留管理员权限"
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@
|
||||
"code_error.team_error.un_auth": "无权操作该团队",
|
||||
"code_error.team_error.user_not_active": "用户未接受或已离开团队",
|
||||
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
|
||||
"code_error.team_error.org_member_not_exist": "组织成员不存在",
|
||||
"code_error.team_error.org_member_duplicated": "重复的组织成员",
|
||||
"code_error.team_error.org_not_exist": "组织不存在",
|
||||
"code_error.team_error.org_parent_not_exist": "父组织不存在",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "不能移动到相同或子目录",
|
||||
"code_error.team_error.cannot_modify_root_org": "不能修改根组织",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "不能删除非空组织",
|
||||
"code_error.token_error_code.403": "登录状态无效,请重新登录",
|
||||
"code_error.user_error.balance_not_enough": "账号余额不足~",
|
||||
"code_error.user_error.bin_visitor": "您的身份校验未通过",
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
"action": "操作",
|
||||
"confirm_delete_group": "確認刪除群組?",
|
||||
"confirm_leave_team": "確認離開該團隊? \n \n退出後,您在該團隊所有的資源( 應用程式、知識庫、資料夾、管理的群組等)均轉讓給團隊所有者。",
|
||||
"confirm_delete_org": "確認刪除組織?",
|
||||
"confirm_delete_member": "確認刪除成員?",
|
||||
"create_group": "建立群組",
|
||||
"delete": "刪除",
|
||||
"edit_info": "編輯訊息",
|
||||
"group": "群組",
|
||||
"group_name": "群組名稱",
|
||||
"org": "組織",
|
||||
"org_name": "組織名稱",
|
||||
"org_description": "介紹",
|
||||
"create_org": "建立組織",
|
||||
"create_sub_org": "建立子組織",
|
||||
"edit_org_info": "編輯組織訊息",
|
||||
"move_org": "移動組織",
|
||||
"move_member": "移動成員",
|
||||
"delete_org": "刪除組織",
|
||||
"remark": "備註",
|
||||
"label_sync": "標籤同步",
|
||||
"leave_team_failed": "離開團隊異常",
|
||||
"manage_member": "管理成員",
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
"code_error.team_error.un_auth": "無權操作此團隊",
|
||||
"code_error.team_error.user_not_active": "使用者未接受或已離開團隊",
|
||||
"code_error.team_error.website_sync_not_enough": "無權使用網站同步",
|
||||
"code_error.team_error.org_member_not_exist": "組織成員不存在",
|
||||
"code_error.team_error.org_member_duplicated": "重複的組織成員",
|
||||
"code_error.team_error.org_not_exist": "組織不存在",
|
||||
"code_error.team_error.org_parent_not_exist": "父組織不存在",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "無法移動到相同或子目錄",
|
||||
"code_error.team_error.cannot_modify_root_org": "無法修改根組織",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "無法刪除非空組織",
|
||||
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
|
||||
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
|
||||
"code_error.user_error.bin_visitor": "身份驗證未通過",
|
||||
|
||||
10
projects/app/public/imgs/avatar/defaultOrgAvatar.svg
Normal file
10
projects/app/public/imgs/avatar/defaultOrgAvatar.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_13996_748)"/>
|
||||
<path d="M14.904 10.8976C14.904 10.2323 14.904 9.89961 15.0335 9.64549C15.1474 9.42195 15.3291 9.24021 15.5527 9.12631C15.8068 8.99683 16.1395 8.99683 16.8048 8.99683H19.1952C19.8605 8.99683 20.1932 8.99683 20.4473 9.12631C20.6709 9.24021 20.8526 9.42195 20.9665 9.64549C21.096 9.89961 21.096 10.2323 21.096 10.8976V12.598C21.096 13.2633 21.096 13.596 20.9665 13.8501C20.8526 14.0737 20.6709 14.2554 20.4473 14.3693C20.1932 14.4988 19.8605 14.4988 19.1952 14.4988H18.8506V17.1972H23.215C24.1296 17.1972 24.871 17.9386 24.871 18.8532V21.4949H25.0992C25.7645 21.4949 26.0972 21.4949 26.3513 21.6244C26.5749 21.7383 26.7566 21.92 26.8705 22.1435C27 22.3977 27 22.7303 27 23.3957V25.096C27 25.7614 27 26.0941 26.8705 26.3482C26.7566 26.5717 26.5749 26.7535 26.3513 26.8674C26.0972 26.9968 25.7645 26.9968 25.0992 26.9968H22.7088C22.0435 26.9968 21.7108 26.9968 21.4567 26.8674C21.2331 26.7535 21.0514 26.5717 20.9375 26.3482C20.808 26.0941 20.808 25.7614 20.808 25.096V23.3957C20.808 22.7303 20.808 22.3977 20.9375 22.1435C21.0514 21.92 21.2331 21.7383 21.4567 21.6244C21.7108 21.4949 22.0435 21.4949 22.7088 21.4949H22.999V19.0692H13.001V21.4949H13.2912C13.9565 21.4949 14.2892 21.4949 14.5433 21.6244C14.7669 21.7383 14.9486 21.92 15.0625 22.1435C15.192 22.3977 15.192 22.7303 15.192 23.3957V25.1027C15.192 25.768 15.192 26.1007 15.0625 26.3548C14.9486 26.5783 14.7669 26.7601 14.5433 26.874C14.2892 27.0035 13.9565 27.0035 13.2912 27.0035H10.9008C10.2355 27.0035 9.90279 27.0035 9.64866 26.874C9.42512 26.7601 9.24338 26.5783 9.12948 26.3548C9 26.1007 9 25.768 9 25.1027V23.3957C9 22.7303 9 22.3977 9.12948 22.1435C9.24338 21.92 9.42512 21.7383 9.64866 21.6244C9.90279 21.4949 10.2355 21.4949 10.9008 21.4949H11.129V18.8532C11.129 17.9386 11.8704 17.1972 12.785 17.1972H16.9657V14.4988H16.8048C16.1395 14.4988 15.8068 14.4988 15.5527 14.3693C15.3291 14.2554 15.1474 14.0737 15.0335 13.8501C14.904 13.596 14.904 13.2633 14.904 12.598V10.8976Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13996_748" x1="36" y1="1.07288e-06" x2="-1.07288e-06" y2="36" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFDCF3"/>
|
||||
<stop offset="1" stop-color="#00C2D8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,27 +1,27 @@
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
ModalBody,
|
||||
Checkbox,
|
||||
ModalFooter,
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} 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 { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
|
||||
export type AddModalPropsType = {
|
||||
onClose: () => void;
|
||||
@@ -30,22 +30,28 @@ export type AddModalPropsType = {
|
||||
|
||||
function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups } = useUserStore();
|
||||
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs, myOrgs } =
|
||||
useUserStore();
|
||||
|
||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const { data: [members = [], groups = []] = [], loading: loadingMembersAndGroups } = useRequest2(
|
||||
async () => {
|
||||
if (!userInfo?.team?.teamId) return [[], []];
|
||||
return await Promise.all([loadAndGetTeamMembers(true), loadAndGetGroups(true)]);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
}
|
||||
);
|
||||
const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } =
|
||||
useRequest2(
|
||||
async () => {
|
||||
if (!userInfo?.team?.teamId) return [[], []];
|
||||
return Promise.all([
|
||||
loadAndGetTeamMembers(true),
|
||||
loadAndGetGroups(true),
|
||||
loadAndGetOrgs(true)
|
||||
]);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
}
|
||||
);
|
||||
|
||||
const filterMembers = useMemo(() => {
|
||||
return members.filter((item) => {
|
||||
@@ -65,8 +71,20 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
});
|
||||
}, [groups, searchText, myGroups, mode, permission]);
|
||||
|
||||
const filterOrgs = useMemo(() => {
|
||||
if (mode !== 'all') return [];
|
||||
return orgs.filter((item) => {
|
||||
if (item.path === '') return false; // exclude root org
|
||||
if (!permission.isOwner && myOrgs.find((i) => String(i._id) !== String(item._id)))
|
||||
return false;
|
||||
if (!searchText) return true;
|
||||
return item.name.includes(searchText);
|
||||
});
|
||||
}, [orgs, searchText, myOrgs, mode, permission]);
|
||||
|
||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
|
||||
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
|
||||
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
||||
const perLabel = useMemo(() => {
|
||||
return getPerLabelList(selectedPermission).join('、');
|
||||
@@ -77,6 +95,7 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
onUpdateCollaborators({
|
||||
members: selectedMemberIdList,
|
||||
groups: selectedGroupIdList,
|
||||
orgs: selectedOrgIdList,
|
||||
permission: selectedPermission
|
||||
}),
|
||||
{
|
||||
@@ -115,6 +134,44 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
/>
|
||||
|
||||
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
|
||||
{filterOrgs.map((org) => {
|
||||
const onChange = () => {
|
||||
setSelectedOrgIdList((state) => {
|
||||
if (state.includes(org._id)) {
|
||||
return state.filter((v) => v !== org._id);
|
||||
}
|
||||
return [...state, org._id];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={org._id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer',
|
||||
...(!selectedOrgIdList.includes(org._id)
|
||||
? { svg: { color: 'myGray.50' } }
|
||||
: {})
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox isChecked={selectedOrgIdList.includes(org._id)} />
|
||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box ml="2" w="full">
|
||||
{org.name}
|
||||
</Box>
|
||||
{!!collaborator && (
|
||||
<PermissionTags permission={collaborator.permission.value} />
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{filterGroups.map((group) => {
|
||||
const onChange = () => {
|
||||
setSelectedGroupIdList((state) => {
|
||||
@@ -198,10 +255,44 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
</Flex>
|
||||
<Flex p="4" flexDirection="column">
|
||||
<Box>
|
||||
{t('user:has_chosen') + ': '}{' '}
|
||||
{selectedMemberIdList.length + selectedGroupIdList.length}
|
||||
{`${t('user:has_chosen')}: `}
|
||||
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
|
||||
</Box>
|
||||
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
|
||||
{selectedOrgIdList.map((orgId) => {
|
||||
const org = orgs.find((v) => String(v._id) === orgId);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={orgId}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer',
|
||||
...(!selectedOrgIdList.includes(orgId) ? { svg: { color: 'myGray.50' } } : {})
|
||||
}}
|
||||
onClick={() =>
|
||||
setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId))
|
||||
}
|
||||
>
|
||||
<MyAvatar src={org?.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{org?.name}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="16px"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{selectedGroupIdList.map((groupId) => {
|
||||
const onChange = () => {
|
||||
setSelectedGroupIdList((state) => {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { ModalBody, Table, TableContainer, Tbody, Th, Thead, Tr, Td, Flex } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { Flex, ModalBody, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { CollaboratorContext } from './context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
export type ManageModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
@@ -65,7 +65,7 @@ function ManageModal({ onClose }: ManageModalProps) {
|
||||
>
|
||||
<Td border="none">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={item.avatar} w="24px" mr={2} />
|
||||
<Avatar src={item.avatar} rounded={'50%'} w="24px" mr={2} />
|
||||
{item.name === DefaultGroupName ? userInfo?.team.teamName : item.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
@@ -85,14 +85,20 @@ function ManageModal({ onClose }: ManageModalProps) {
|
||||
onUpdate({
|
||||
members: item.tmbId ? [item.tmbId] : undefined,
|
||||
groups: item.groupId ? [item.groupId] : undefined,
|
||||
orgs: item.orgId ? [item.orgId] : undefined,
|
||||
permission
|
||||
});
|
||||
}}
|
||||
onDelete={() => {
|
||||
onDelete({
|
||||
tmbId: item.tmbId,
|
||||
groupId: item.groupId
|
||||
} as RequireOnlyOne<{ tmbId: string; groupId: string }>);
|
||||
groupId: item.groupId,
|
||||
orgId: item.orgId
|
||||
} as RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { Box, type BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import Tag, { type TagProps } from '@fastgpt/web/components/common/Tag';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorContext } from './context';
|
||||
import Tag, { TagProps } from '@fastgpt/web/components/common/Tag';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
export type MemberListCardProps = BoxProps & { tagStyle?: Omit<TagProps, 'children'> };
|
||||
|
||||
@@ -31,12 +31,12 @@ const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => {
|
||||
{collaboratorList?.map((member) => {
|
||||
return (
|
||||
<Tag
|
||||
key={member.tmbId || member.groupId}
|
||||
key={member.tmbId || member.groupId || member.orgId}
|
||||
type={'fill'}
|
||||
colorSchema="white"
|
||||
{...tagStyle}
|
||||
>
|
||||
<Avatar src={member.avatar} w="1.25rem" />
|
||||
<Avatar src={member.avatar} w="1.25rem" rounded={'50%'} />
|
||||
<Box fontSize={'sm'} ml={1}>
|
||||
{member.name === DefaultGroupName ? userInfo?.team.teamName : member.name}
|
||||
</Box>
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import {
|
||||
import type {
|
||||
CollaboratorItemType,
|
||||
UpdateClbPermissionProps
|
||||
} 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 { ReactNode, useCallback } from 'react';
|
||||
import type {
|
||||
PermissionListType,
|
||||
PermissionValueType
|
||||
} from '@fastgpt/global/support/permission/type';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MemberListCard, { MemberListCardProps } from './MemberListCard';
|
||||
import MemberListCard, { type MemberListCardProps } from './MemberListCard';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
|
||||
const ManageModal = dynamic(() => import('./ManageModal'));
|
||||
|
||||
@@ -24,7 +27,9 @@ export type MemberManagerInputPropsType = {
|
||||
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
||||
permissionList: PermissionListType;
|
||||
onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise<any>;
|
||||
onDelOneCollaborator: (props: RequireOnlyOne<{ tmbId: string; groupId: string }>) => Promise<any>;
|
||||
onDelOneCollaborator: (
|
||||
props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>
|
||||
) => Promise<any>;
|
||||
refreshDeps?: any[];
|
||||
mode?: 'member' | 'all';
|
||||
};
|
||||
@@ -46,19 +51,19 @@ type CollaboratorContextType = MemberManagerPropsType & {};
|
||||
export const CollaboratorContext = createContext<CollaboratorContextType>({
|
||||
collaboratorList: [],
|
||||
permissionList: PermissionList,
|
||||
onUpdateCollaborators: function () {
|
||||
onUpdateCollaborators: () => {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onDelOneCollaborator: function () {
|
||||
onDelOneCollaborator: () => {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
getPerLabelList: function (): string[] {
|
||||
getPerLabelList: (): string[] => {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
refetchCollaboratorList: function (): void {
|
||||
refetchCollaboratorList: (): void => {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
|
||||
onGetCollaboratorList: (): Promise<CollaboratorItemType[]> => {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isFetchingCollaborator: false,
|
||||
@@ -88,7 +93,7 @@ const CollaboratorContextProvider = ({
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThen = async (
|
||||
props: RequireOnlyOne<{ tmbId: string; groupId: string }>
|
||||
props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>
|
||||
) => {
|
||||
await onDelOneCollaborator(props);
|
||||
refetchCollaboratorList();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||
|
||||
function IconButton({ name, onClick }: { name: IconNameType; onClick: () => void }) {
|
||||
return (
|
||||
<MyIcon
|
||||
name={name}
|
||||
w={'1rem'}
|
||||
h={'1rem'}
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
p="1"
|
||||
rounded={'sm'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default IconButton;
|
||||
@@ -0,0 +1,150 @@
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { postCreateOrg, putUpdateOrg } from '@/web/support/user/team/org/api';
|
||||
import { Button, HStack, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
export type OrgFormType = {
|
||||
avatar: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
function OrgInfoModal({
|
||||
editOrg,
|
||||
createOrgParentId: parentId,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
editOrg?: OrgType;
|
||||
createOrgParentId?: string;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { File: AvatarSelect, onOpen: onOpenSelectAvatar } = useSelectFile({
|
||||
fileType: '.jpg, .jpeg, .png',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const { register, handleSubmit, getValues, setValue } = useForm<OrgFormType>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
avatar: DEFAULT_ORG_AVATAR,
|
||||
description: undefined
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setValue('name', editOrg?.name ?? '');
|
||||
setValue('avatar', editOrg?.avatar || DEFAULT_ORG_AVATAR);
|
||||
setValue('description', editOrg?.description);
|
||||
}, [editOrg, setValue]);
|
||||
|
||||
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
||||
(data: OrgFormType, parentId: string) => {
|
||||
return postCreateOrg({
|
||||
name: data.name,
|
||||
avatar: data.avatar,
|
||||
parentId,
|
||||
description: data.description
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||
(data: OrgFormType, orgId: string) => {
|
||||
return putUpdateOrg({
|
||||
orgId,
|
||||
name: data.name,
|
||||
avatar: data.avatar,
|
||||
description: data.description
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2(
|
||||
async (file: File[]) => {
|
||||
const src = await compressImgFileAndUpload({
|
||||
type: MongoImageTypeEnum.groupAvatar,
|
||||
file: file[0],
|
||||
maxW: 300,
|
||||
maxH: 300
|
||||
});
|
||||
return src;
|
||||
},
|
||||
{
|
||||
onSuccess: (src: string) => {
|
||||
setValue('avatar', src);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isLoading = uploadingAvatar;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={!!(editOrg || parentId)}
|
||||
onClose={onClose}
|
||||
title={editOrg ? t('account_team:edit_org_info') : t('account_team:create_org')}
|
||||
iconSrc={editOrg?.avatar || DEFAULT_ORG_AVATAR}
|
||||
>
|
||||
<ModalBody flex={1} overflow={'auto'} display={'flex'} flexDirection={'column'} gap={4}>
|
||||
<FormLabel w="80px">{t('user:team.avatar_and_name')}</FormLabel>
|
||||
<HStack>
|
||||
<Avatar
|
||||
src={getValues('avatar') || DEFAULT_ORG_AVATAR}
|
||||
onClick={onOpenSelectAvatar}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
/>
|
||||
<Input
|
||||
bgColor="myGray.50"
|
||||
{...register('name', { required: true })}
|
||||
placeholder={t('account_team:org_name')}
|
||||
/>
|
||||
</HStack>
|
||||
<FormLabel w="80px">{t('account_team:org_description')}</FormLabel>
|
||||
<Textarea {...register('description')} placeholder={t('account_team:org_description')} />
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
onClick={handleSubmit((data) => {
|
||||
if (editOrg) {
|
||||
onUpdate(data, editOrg._id);
|
||||
} else if (parentId) {
|
||||
onCreate(data, parentId);
|
||||
}
|
||||
})}
|
||||
>
|
||||
{editOrg ? t('common:common.Save') : t('common:new_create')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
<AvatarSelect onSelect={onSelectAvatar} />
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgInfoModal;
|
||||
@@ -0,0 +1,202 @@
|
||||
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type React from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '../context';
|
||||
|
||||
export type GroupFormType = {
|
||||
members: {
|
||||
tmbId: string;
|
||||
role: `${GroupMemberRole}`;
|
||||
}[];
|
||||
};
|
||||
|
||||
function CheckboxIcon({
|
||||
name
|
||||
}: {
|
||||
isChecked?: boolean;
|
||||
isIndeterminate?: boolean;
|
||||
name: IconNameType;
|
||||
}) {
|
||||
return <MyIcon name={name} w="12px" />;
|
||||
}
|
||||
|
||||
function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId?: string }) {
|
||||
// 1. Owner can not be deleted, toast
|
||||
// 2. Owner/Admin can manage members
|
||||
// 3. Owner can add/remove admins
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
members: allMembers,
|
||||
orgs,
|
||||
refetchOrgs,
|
||||
refetchMembers
|
||||
} = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const org = useMemo(() => orgs.find((item) => item._id === editOrgId), [editOrgId, orgs]);
|
||||
|
||||
const [members, setMembers] = useState<{ tmbId: string }[]>(org?.members || []);
|
||||
|
||||
useEffect(() => {
|
||||
setMembers(org?.members || []);
|
||||
}, [org]);
|
||||
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const filtered = useMemo(() => {
|
||||
return [
|
||||
...allMembers.filter((member) => {
|
||||
if (member.memberName.toLowerCase().includes(searchKey.toLowerCase())) return true;
|
||||
return false;
|
||||
})
|
||||
];
|
||||
}, [searchKey, allMembers]);
|
||||
|
||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||
async () => {
|
||||
if (!editOrgId) return;
|
||||
return putUpdateOrgMembers({
|
||||
orgId: editOrgId,
|
||||
members
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: () => Promise.all([onClose(), refetchOrgs(), refetchMembers()])
|
||||
}
|
||||
);
|
||||
|
||||
const isSelected = (memberId: string) => {
|
||||
return members.find((item) => item.tmbId === memberId);
|
||||
};
|
||||
|
||||
const handleToggleSelect = (memberId: string) => {
|
||||
if (isSelected(memberId)) {
|
||||
setMembers(members.filter((item) => item.tmbId !== memberId));
|
||||
} else {
|
||||
setMembers([...members, { tmbId: memberId }]);
|
||||
}
|
||||
};
|
||||
|
||||
const isLoading = isLoadingUpdate;
|
||||
return (
|
||||
<MyModal
|
||||
onClose={onClose}
|
||||
isOpen={!!editOrgId}
|
||||
title={t('user:team.group.manage_member')}
|
||||
iconSrc={org?.avatar}
|
||||
iconColor="primary.600"
|
||||
minW="800px"
|
||||
h={'100%'}
|
||||
isCentered
|
||||
>
|
||||
<ModalBody flex={1}>
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex flexDirection="column" p="4">
|
||||
<SearchInput
|
||||
placeholder={t('user:search_user')}
|
||||
fontSize="sm"
|
||||
bg={'myGray.50'}
|
||||
onChange={(e) => {
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
{filtered.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
alignItems="center"
|
||||
key={member.tmbId}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.50',
|
||||
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
|
||||
}}
|
||||
_notLast={{ mb: 2 }}
|
||||
onClick={() => handleToggleSelect(member.tmbId)}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={!!isSelected(member.tmbId)}
|
||||
icon={<CheckboxIcon name={'common/check'} />}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box>{member.memberName}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
||||
<Box mt={2}>{`${t('common:chosen')}:${members.length}`}</Box>
|
||||
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
{members.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
key={member.tmbId}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
>
|
||||
<HStack>
|
||||
<Avatar
|
||||
src={allMembers.find((item) => item.tmbId === member.tmbId)?.avatar}
|
||||
w="1.5rem"
|
||||
borderRadius={'md'}
|
||||
/>
|
||||
<Box>
|
||||
{allMembers.find((item) => item.tmbId === member.tmbId)?.memberName}
|
||||
</Box>
|
||||
</HStack>
|
||||
<MyIcon
|
||||
name={'common/closeLight'}
|
||||
w={'1rem'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => handleToggleSelect(member.tmbId)}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter alignItems="flex-end">
|
||||
<Button isLoading={isLoading} onClick={onUpdate}>
|
||||
{t('common:common.Save')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgMemberModal;
|
||||
@@ -0,0 +1,80 @@
|
||||
import { putMoveOrg, putMoveOrgMember } from '@/web/support/user/team/org/api';
|
||||
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import type { TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import OrgTree from './OrgTree';
|
||||
|
||||
function OrgMoveModal({
|
||||
movingOrg,
|
||||
movingTmb,
|
||||
orgs,
|
||||
team,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
movingOrg?: OrgType;
|
||||
movingTmb?: { tmbId: string; orgId: string };
|
||||
orgs: OrgType[];
|
||||
team: TeamTmbItemType;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedOrg, selectOrg] = useState<OrgType>();
|
||||
|
||||
const { runAsync: moveOrg, loading: loadingOrg } = useRequest2(putMoveOrg, {
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
onSuccess();
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: moveTmb, loading: loadingTmb } = useRequest2(putMoveOrgMember, {
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
onSuccess();
|
||||
}
|
||||
});
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!selectedOrg) return;
|
||||
if (movingTmb) {
|
||||
moveTmb({ orgId: movingTmb.orgId, tmbId: movingTmb.tmbId, newOrgId: selectedOrg._id });
|
||||
} else if (movingOrg) {
|
||||
moveOrg(movingOrg._id, selectedOrg._id);
|
||||
}
|
||||
};
|
||||
|
||||
const loading = loadingOrg || loadingTmb;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={!!movingOrg || !!movingTmb}
|
||||
onClose={onClose}
|
||||
title={movingOrg ? t('account_team:move_org') : t('account_team:move_member')}
|
||||
iconSrc="common/file/move"
|
||||
iconColor="blue.600"
|
||||
>
|
||||
<ModalBody>
|
||||
<OrgTree
|
||||
orgs={orgs}
|
||||
teamName={team.teamName}
|
||||
teamAvatar={team.avatar}
|
||||
selectedOrg={selectedOrg}
|
||||
selectOrg={selectOrg}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isDisabled={!selectedOrg} isLoading={loading} onClick={() => handleConfirm()}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgMoveModal;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Box, HStack, Text, VStack } from '@chakra-ui/react';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useToggle } from 'ahooks';
|
||||
import { useMemo, useState } from 'react';
|
||||
import IconButton from './IconButton';
|
||||
|
||||
function OrgTreeNode({
|
||||
org,
|
||||
list,
|
||||
selectedOrg,
|
||||
selectOrg,
|
||||
indent = 0
|
||||
}: {
|
||||
org: OrgType;
|
||||
list: OrgType[];
|
||||
selectedOrg?: OrgType;
|
||||
selectOrg?: (org?: OrgType) => void;
|
||||
indent?: number;
|
||||
}) {
|
||||
const children = useMemo(
|
||||
() => list.filter((item) => item.path === `${org.path}/${org._id}`),
|
||||
[org, list]
|
||||
);
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
|
||||
return (
|
||||
<VStack alignItems={'start'} w="full" gap={'8px'}>
|
||||
<HStack
|
||||
w="full"
|
||||
_hover={{ bgColor: selectedOrg === org ? 'blue.200' : 'gray.100' }}
|
||||
borderRadius="4px"
|
||||
boxSizing="border-box"
|
||||
py="4px"
|
||||
pl={`calc(${indent}rem + 4px)`}
|
||||
transition={'background 0.1s'}
|
||||
{...(selectedOrg === org ? { bgColor: 'blue.100' } : {})}
|
||||
>
|
||||
{children.length > 0 ? (
|
||||
<IconButton
|
||||
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
|
||||
onClick={() => toggleIsExpanded.toggle()}
|
||||
/>
|
||||
) : (
|
||||
<Box w={'1rem'} h={'1rem'} m="1" />
|
||||
)}
|
||||
<HStack onClick={() => selectOrg?.(org)} cursor="pointer">
|
||||
<Avatar src={org.avatar} w="20px" h="20px" rounded={'50%'} />
|
||||
<Text>{org.name}</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
{isExpanded &&
|
||||
children.length > 0 &&
|
||||
children.map((child) => (
|
||||
<OrgTreeNode
|
||||
key={child._id}
|
||||
org={child}
|
||||
indent={indent + 1}
|
||||
list={list}
|
||||
selectedOrg={selectedOrg}
|
||||
selectOrg={selectOrg}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
function OrgTree({
|
||||
orgs,
|
||||
teamName,
|
||||
teamAvatar,
|
||||
selectedOrg,
|
||||
selectOrg
|
||||
}: {
|
||||
orgs: OrgType[];
|
||||
teamAvatar: string;
|
||||
teamName: string;
|
||||
selectedOrg?: OrgType;
|
||||
selectOrg?: (org?: OrgType) => void;
|
||||
}) {
|
||||
const root = orgs[0];
|
||||
if (!root) return null;
|
||||
const children = useMemo(
|
||||
() => orgs.filter((item) => item.path === `${root.path}/${root._id}`),
|
||||
[root, orgs]
|
||||
);
|
||||
return (
|
||||
<VStack alignItems={'start'} gap={'8px'}>
|
||||
<HStack
|
||||
w="full"
|
||||
onClick={() => selectOrg?.(root)}
|
||||
cursor="pointer"
|
||||
_hover={{ bgColor: selectedOrg === root ? 'blue.200' : 'gray.100' }}
|
||||
borderRadius="4px"
|
||||
p="4px"
|
||||
transition={'background 0.1s'}
|
||||
{...(selectedOrg === root ? { bgColor: 'blue.100' } : {})}
|
||||
>
|
||||
<Avatar src={teamAvatar} w="20px" h="20px" rounded={'50%'} />
|
||||
<Text>{teamName}</Text>
|
||||
</HStack>
|
||||
{children.map((child) => (
|
||||
<OrgTreeNode
|
||||
key={child._id}
|
||||
org={child}
|
||||
list={orgs}
|
||||
selectOrg={selectOrg}
|
||||
selectedOrg={selectedOrg}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgTree;
|
||||
@@ -0,0 +1,370 @@
|
||||
import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Box,
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
Divider,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tag,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
VStack,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MemberTag from '../../../../../components/support/user/team/Info/MemberTag';
|
||||
import { TeamContext } from '../context';
|
||||
import IconButton from './IconButton';
|
||||
import OrgInfoModal from './OrgInfoModal';
|
||||
import OrgMemberModal from './OrgMemberModal';
|
||||
import OrgMoveModal from './OrgMoveModal';
|
||||
|
||||
function ActionButton({
|
||||
icon,
|
||||
text,
|
||||
onClick
|
||||
}: {
|
||||
icon: IconNameType;
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<HStack
|
||||
gap={'8px'}
|
||||
w="100%"
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
p="4px"
|
||||
rounded={'sm'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MyIcon name={icon} w="16px" h="16px" p="1" />
|
||||
<Text fontSize={'12px'} lineHeight={'16px'}>
|
||||
{text}
|
||||
</Text>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
function MemberTable() {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { orgs, refetchOrgs, members, refetchMembers, isLoading } = useContextSelector(
|
||||
TeamContext,
|
||||
(v) => v
|
||||
);
|
||||
const [currentOrg, setCurrentOrg] = useState<OrgType>();
|
||||
|
||||
// Set current org by hash
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash.substring(1);
|
||||
const initialOrg = orgs.find((org) => org._id === hash) || orgs[0];
|
||||
setCurrentOrg(initialOrg);
|
||||
}, [orgs, isLoading]);
|
||||
// Update hash when current org changes
|
||||
useEffect(() => {
|
||||
if (currentOrg) {
|
||||
window.location.hash = currentOrg._id;
|
||||
}
|
||||
}, [currentOrg]);
|
||||
|
||||
const currentPath = useMemo<{ path: string; parents: OrgType[] }>(
|
||||
() => ({
|
||||
path: currentOrg ? `${currentOrg.path}/${currentOrg._id}` : '',
|
||||
parents: currentOrg
|
||||
? currentOrg.path
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.map((orgId) => orgs.find((org) => org._id === orgId)!)
|
||||
: []
|
||||
}),
|
||||
[orgs, currentOrg]
|
||||
);
|
||||
|
||||
const orgList = useMemo(
|
||||
() =>
|
||||
orgs
|
||||
.filter((org) => org.path === currentPath.path)
|
||||
.map((org) => {
|
||||
// calc org members count
|
||||
let count = org.members.length;
|
||||
for (const item of orgs.filter((item) =>
|
||||
item.path.startsWith(`${org.path}/${org._id}`)
|
||||
)) {
|
||||
count += item.members.length;
|
||||
}
|
||||
|
||||
return { ...org, count };
|
||||
}),
|
||||
[orgs, currentPath]
|
||||
);
|
||||
|
||||
const [editOrg, setEditOrg] = useState<OrgType | undefined>();
|
||||
const [editMemberOrgId, setEditMemberOrgId] = useState<string | undefined>();
|
||||
const [movingOrg, setMovingOrg] = useState<OrgType | undefined>();
|
||||
const [movingTmb, setMovingTmb] = useState<{ tmbId: string; orgId: string } | undefined>();
|
||||
const [createOrgParentId, setCreateOrgParentId] = useState<string | undefined>();
|
||||
|
||||
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_org')
|
||||
});
|
||||
|
||||
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_member')
|
||||
});
|
||||
|
||||
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
||||
const deleteMemberHandler = (orgId: string, tmbId: string) =>
|
||||
openDeleteMemberModal(() => deleteMemberReq(orgId, tmbId))();
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<Breadcrumb mr={'auto'}>
|
||||
{currentPath.parents.map((parent) => (
|
||||
<BreadcrumbItem key={parent._id}>
|
||||
<BreadcrumbLink onClick={() => setCurrentOrg(parent)}>
|
||||
{parent.path === '' ? userInfo?.team.teamName : parent.name}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
))}
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
<BreadcrumbLink color="myGray.900" fontWeight={500}>
|
||||
{currentOrg?.path === '' ? userInfo?.team.teamName : currentOrg?.name}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
<HStack w={'100%'} gap={'16px'} alignItems={'start'}>
|
||||
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bg={'white !important'}>
|
||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||
{t('common:Name')}
|
||||
</Th>
|
||||
<Th bg="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{orgList.map((org) => (
|
||||
<Tr key={org._id} overflow={'unset'}>
|
||||
<Td>
|
||||
<HStack w="fit-content" cursor={'pointer'} onClick={() => setCurrentOrg(org)}>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
<Tag size="sm">{org.count}</Tag>
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w={'12px'}
|
||||
h={'12px'}
|
||||
color={'myGray.400'}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
|
||||
<Td w={'6rem'}>
|
||||
<MyMenu
|
||||
trigger="click"
|
||||
Button={
|
||||
<MyIcon name="more" w={'1rem'} cursor={'pointer'} p="1" rounded={'sm'} />
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('account_team:edit_info'),
|
||||
onClick: () => setEditOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
label: t('common:Move'),
|
||||
onClick: () => setMovingOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('account_team:delete'),
|
||||
type: 'danger',
|
||||
onClick: () => deleteOrgHandler(org._id)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
{currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
return (
|
||||
<Tr key={member.tmbId} overflow={'unset'}>
|
||||
<Td>
|
||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||
</Td>
|
||||
<Td w={'6rem'}>
|
||||
<MyMenu
|
||||
trigger={'click'}
|
||||
Button={
|
||||
<MyIcon name="more" w={'1rem'} cursor={'pointer'} p="1" rounded={'sm'} />
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
// {
|
||||
// icon: 'edit',
|
||||
// label: t('account_team:remark'),
|
||||
// onClick: () => {
|
||||
// // TODO
|
||||
// console.log(member.tmbId);
|
||||
// }
|
||||
// },
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
label: t('common:Move'),
|
||||
onClick: () =>
|
||||
setMovingTmb({ tmbId: member.tmbId, orgId: currentOrg!._id })
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('account_team:delete'),
|
||||
type: 'danger',
|
||||
onClick: () => deleteMemberHandler(currentOrg!._id, member.tmbId)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<VStack w={'220px'} alignItems={'start'}>
|
||||
<HStack gap={'6px'}>
|
||||
<Avatar
|
||||
src={currentOrg?.path === '' ? userInfo?.team.avatar : currentOrg?.avatar}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
rounded={'20%'}
|
||||
/>
|
||||
<Text fontWeight={500} fontSize={'14px'} color={'myGray.900'} lineHeight={'20px'}>
|
||||
{currentOrg?.path === '' ? userInfo?.team.teamName : currentOrg?.name}
|
||||
</Text>
|
||||
{currentOrg?.path !== '' && (
|
||||
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
||||
)}
|
||||
</HStack>
|
||||
<Text fontSize={12} lineHeight={'16px'} w={'full'}>
|
||||
{currentOrg?.description ?? t('common:common.no_intro')}
|
||||
</Text>
|
||||
|
||||
<Divider my={'20px'} />
|
||||
|
||||
<Text fontWeight={500} mb="13px" fontSize="14px" color="myGray.900" lineHeight="20px">
|
||||
{t('common:common.Action')}
|
||||
</Text>
|
||||
<VStack gap="13px" w="100%">
|
||||
<ActionButton
|
||||
icon="common/add2"
|
||||
text={t('account_team:create_sub_org')}
|
||||
onClick={() => {
|
||||
setCreateOrgParentId(currentOrg?._id);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
icon="common/administrator"
|
||||
text={t('account_team:manage_member')}
|
||||
onClick={() => setEditMemberOrgId(currentOrg?._id)}
|
||||
/>
|
||||
{currentOrg?.path !== '' && (
|
||||
<>
|
||||
<ActionButton
|
||||
icon="common/file/move"
|
||||
text={t('account_team:move_org')}
|
||||
onClick={() => setMovingOrg(currentOrg)}
|
||||
/>
|
||||
<ActionButton
|
||||
icon="delete"
|
||||
text={t('account_team:delete_org')}
|
||||
onClick={() => deleteOrgHandler(currentOrg?._id ?? '')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<OrgInfoModal
|
||||
editOrg={editOrg}
|
||||
createOrgParentId={createOrgParentId}
|
||||
onClose={() => {
|
||||
setEditOrg(undefined);
|
||||
setCreateOrgParentId(undefined);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}}
|
||||
/>
|
||||
<OrgMoveModal
|
||||
orgs={orgs}
|
||||
team={userInfo?.team!}
|
||||
movingOrg={movingOrg}
|
||||
movingTmb={movingTmb}
|
||||
onClose={() => {
|
||||
setMovingOrg(undefined);
|
||||
setMovingTmb(undefined);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}}
|
||||
/>
|
||||
<OrgMemberModal editOrgId={editMemberOrgId} onClose={() => setEditMemberOrgId(undefined)} />
|
||||
<ConfirmDeleteOrgModal />
|
||||
<ConfirmDeleteMember />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberTable;
|
||||
@@ -9,7 +9,9 @@ import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/suppor
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||
import { getOrgList } from '@/web/support/user/team/org/api';
|
||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
|
||||
const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
||||
|
||||
@@ -17,6 +19,7 @@ type TeamModalContextType = {
|
||||
myTeams: TeamTmbItemType[];
|
||||
members: TeamMemberItemType[];
|
||||
groups: MemberGroupListType;
|
||||
orgs: OrgType[];
|
||||
isLoading: boolean;
|
||||
onSwitchTeam: (teamId: string) => void;
|
||||
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
||||
@@ -24,6 +27,7 @@ type TeamModalContextType = {
|
||||
refetchMembers: () => void;
|
||||
refetchTeams: () => void;
|
||||
refetchGroups: () => void;
|
||||
refetchOrgs: () => void;
|
||||
searchKey: string;
|
||||
setSearchKey: React.Dispatch<React.SetStateAction<string>>;
|
||||
teamSize: number;
|
||||
@@ -33,6 +37,7 @@ export const TeamContext = createContext<TeamModalContextType>({
|
||||
myTeams: [],
|
||||
groups: [],
|
||||
members: [],
|
||||
orgs: [],
|
||||
isLoading: false,
|
||||
onSwitchTeam: function (_teamId: string): void {
|
||||
throw new Error('Function not implemented.');
|
||||
@@ -49,6 +54,9 @@ export const TeamContext = createContext<TeamModalContextType>({
|
||||
refetchGroups: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
refetchOrgs: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
|
||||
searchKey: '',
|
||||
setSearchKey: function (_value: React.SetStateAction<string>): void {
|
||||
@@ -107,7 +115,17 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups;
|
||||
const {
|
||||
data: orgs = [],
|
||||
loading: isLoadingOrgs,
|
||||
refresh: refetchOrgs
|
||||
} = useRequest2(getOrgList, {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
const isLoading =
|
||||
isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups || isLoadingOrgs;
|
||||
|
||||
const contextValue = {
|
||||
myTeams,
|
||||
@@ -123,6 +141,8 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
refetchMembers,
|
||||
groups,
|
||||
refetchGroups,
|
||||
orgs,
|
||||
refetchOrgs,
|
||||
teamSize: members.length
|
||||
};
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ import MemberTable from './components/MemberTable';
|
||||
const InviteModal = dynamic(() => import('./components/InviteModal'));
|
||||
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
|
||||
const GroupManage = dynamic(() => import('./components/GroupManage/index'));
|
||||
const OrgManage = dynamic(() => import('./components/OrgManage/index'));
|
||||
const GroupInfoModal = dynamic(() => import('./components/GroupManage/GroupInfoModal'));
|
||||
const ManageGroupMemberModal = dynamic(() => import('./components/GroupManage/GroupManageMember'));
|
||||
|
||||
export enum TeamTabEnum {
|
||||
member = 'member',
|
||||
org = 'org',
|
||||
group = 'group',
|
||||
permission = 'permission'
|
||||
}
|
||||
@@ -172,6 +174,7 @@ const Team = () => {
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{ label: t('account_team:member'), value: TeamTabEnum.member },
|
||||
{ label: t('account_team:org'), value: TeamTabEnum.org },
|
||||
{ label: t('account_team:group'), value: TeamTabEnum.group },
|
||||
{ label: t('account_team:permission'), value: TeamTabEnum.permission }
|
||||
]}
|
||||
@@ -274,6 +277,7 @@ const Team = () => {
|
||||
{teamTab === TeamTabEnum.group && (
|
||||
<GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} />
|
||||
)}
|
||||
{teamTab === TeamTabEnum.org && <OrgManage />}
|
||||
{teamTab === TeamTabEnum.permission && <PermissionManage />}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import CollaboratorContextProvider from '@/components/support/permission/MemberManager/context';
|
||||
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { resumeInheritPer } from '@/web/core/app/api';
|
||||
import {
|
||||
deleteAppCollaborators,
|
||||
getCollaboratorList,
|
||||
postUpdateAppCollaborators
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
Input,
|
||||
Textarea,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalBody
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import CollaboratorContextProvider from '@/components/support/permission/MemberManager/context';
|
||||
import {
|
||||
postUpdateAppCollaborators,
|
||||
deleteAppCollaborators,
|
||||
getCollaboratorList
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { resumeInheritPer } from '@/web/core/app/api';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -126,20 +126,23 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const onUpdateCollaborators = ({
|
||||
members,
|
||||
groups,
|
||||
orgs,
|
||||
permission
|
||||
}: {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
permission: PermissionValueType;
|
||||
}) =>
|
||||
postUpdateAppCollaborators({
|
||||
members,
|
||||
groups,
|
||||
permission,
|
||||
orgs,
|
||||
appId: appDetail._id
|
||||
});
|
||||
|
||||
const onDelCollaborator = async (props: RequireOnlyOne<{ tmbId: string; groupId: string }>) =>
|
||||
const onDelCollaborator = async (props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>) =>
|
||||
deleteAppCollaborators({
|
||||
appId: appDetail._id,
|
||||
...props
|
||||
@@ -211,7 +214,8 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onUpdateCollaborators({
|
||||
permission: props.permission,
|
||||
members: props.members,
|
||||
groups: props.groups
|
||||
groups: props.groups,
|
||||
orgs: props.orgs
|
||||
})
|
||||
}
|
||||
onDelOneCollaborator={onDelCollaborator}
|
||||
|
||||
@@ -258,20 +258,20 @@ const ListItem = () => {
|
||||
{(AppFolderTypeList.includes(app.type)
|
||||
? app.permission.hasManagePer
|
||||
: app.permission.hasWritePer) && (
|
||||
<Box className="more" display={['', 'none']}>
|
||||
<MyMenu
|
||||
size={'xs'}
|
||||
Button={
|
||||
<IconButton
|
||||
size={'xsSquare'}
|
||||
variant={'transparentBase'}
|
||||
icon={<MyIcon name={'more'} w={'0.875rem'} color={'myGray.500'} />}
|
||||
aria-label={''}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
...([AppTypeEnum.simple, AppTypeEnum.workflow].includes(app.type)
|
||||
? [
|
||||
<Box className="more" display={['', 'none']}>
|
||||
<MyMenu
|
||||
size={'xs'}
|
||||
Button={
|
||||
<IconButton
|
||||
size={'xsSquare'}
|
||||
variant={'transparentBase'}
|
||||
icon={<MyIcon name={'more'} w={'0.875rem'} color={'myGray.500'} />}
|
||||
aria-label={''}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
...([AppTypeEnum.simple, AppTypeEnum.workflow].includes(app.type)
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -285,9 +285,9 @@ const ListItem = () => {
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...([AppTypeEnum.plugin].includes(app.type)
|
||||
? [
|
||||
: []),
|
||||
...([AppTypeEnum.plugin].includes(app.type)
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -301,9 +301,9 @@ const ListItem = () => {
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(app.permission.hasManagePer
|
||||
? [
|
||||
: []),
|
||||
...(app.permission.hasManagePer
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -330,34 +330,34 @@ const ListItem = () => {
|
||||
}
|
||||
},
|
||||
...(folderDetail?.type === AppTypeEnum.httpPlugin &&
|
||||
!(parentApp ? parentApp.permission : app.permission)
|
||||
.hasManagePer
|
||||
!(parentApp ? parentApp.permission : app.permission)
|
||||
.hasManagePer
|
||||
? []
|
||||
: [
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
type: 'grayBg' as MenuItemType,
|
||||
label: t('common:common.folder.Move to'),
|
||||
onClick: () => setMoveAppId(app._id)
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
type: 'grayBg' as MenuItemType,
|
||||
label: t('common:common.folder.Move to'),
|
||||
onClick: () => setMoveAppId(app._id)
|
||||
}
|
||||
]),
|
||||
...(app.permission.hasManagePer
|
||||
? [
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
type: 'grayBg' as MenuItemType,
|
||||
label: t('common:permission.Permission'),
|
||||
onClick: () => setEditPerAppIndex(index)
|
||||
}
|
||||
]
|
||||
{
|
||||
icon: 'support/team/key',
|
||||
type: 'grayBg' as MenuItemType,
|
||||
label: t('common:permission.Permission'),
|
||||
onClick: () => setEditPerAppIndex(index)
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(AppFolderTypeList.includes(app.type)
|
||||
? []
|
||||
: [
|
||||
: []),
|
||||
...(AppFolderTypeList.includes(app.type)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -370,8 +370,8 @@ const ListItem = () => {
|
||||
]
|
||||
}
|
||||
]),
|
||||
...(app.permission.isOwner
|
||||
? [
|
||||
...(app.permission.isOwner
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
@@ -390,11 +390,11 @@ const ListItem = () => {
|
||||
]
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
@@ -438,6 +438,7 @@ const ListItem = () => {
|
||||
onUpdateCollaborators: (props: {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
permission: number;
|
||||
}) =>
|
||||
postUpdateAppCollaborators({
|
||||
@@ -448,6 +449,7 @@ const ListItem = () => {
|
||||
props: RequireOnlyOne<{
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}>
|
||||
) =>
|
||||
deleteAppCollaborators({
|
||||
|
||||
@@ -324,10 +324,12 @@ const MyApps = () => {
|
||||
refreshDeps: [folderDetail._id, folderDetail.inheritPermission],
|
||||
onDelOneCollaborator: async ({
|
||||
tmbId,
|
||||
groupId
|
||||
groupId,
|
||||
orgId
|
||||
}: {
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}) => {
|
||||
if (tmbId) {
|
||||
return deleteAppCollaborators({
|
||||
@@ -339,6 +341,11 @@ const MyApps = () => {
|
||||
appId: folderDetail._id,
|
||||
groupId
|
||||
});
|
||||
} else if (orgId) {
|
||||
return deleteAppCollaborators({
|
||||
appId: folderDetail._id,
|
||||
orgId
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -392,7 +392,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
...body,
|
||||
datasetId
|
||||
}),
|
||||
onDelOneCollaborator: async ({ groupId, tmbId }) => {
|
||||
onDelOneCollaborator: async ({ groupId, tmbId, orgId }) => {
|
||||
if (tmbId) {
|
||||
return deleteDatasetCollaborators({
|
||||
datasetId,
|
||||
@@ -403,6 +403,11 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
datasetId,
|
||||
groupId
|
||||
});
|
||||
} else if (orgId) {
|
||||
return deleteDatasetCollaborators({
|
||||
datasetId,
|
||||
orgId
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -257,7 +257,7 @@ const Dataset = () => {
|
||||
permission,
|
||||
datasetId: folderDetail._id
|
||||
}),
|
||||
onDelOneCollaborator: async ({ tmbId, groupId }) => {
|
||||
onDelOneCollaborator: async ({ tmbId, groupId, orgId }) => {
|
||||
if (tmbId) {
|
||||
return deleteDatasetCollaborators({
|
||||
datasetId: folderDetail._id,
|
||||
@@ -268,6 +268,11 @@ const Dataset = () => {
|
||||
datasetId: folderDetail._id,
|
||||
groupId
|
||||
});
|
||||
} else if (orgId) {
|
||||
return deleteDatasetCollaborators({
|
||||
datasetId: folderDetail._id,
|
||||
orgId
|
||||
});
|
||||
}
|
||||
},
|
||||
refreshDeps: [folderDetail._id, folderDetail.inheritPermission]
|
||||
|
||||
34
projects/app/src/web/support/user/team/org/api.ts
Normal file
34
projects/app/src/web/support/user/team/org/api.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DELETE, GET, POST, PUT } from '@/web/common/api/request';
|
||||
import type {
|
||||
postCreateOrgData,
|
||||
putUpdateOrgData,
|
||||
putUpdateOrgMembersData,
|
||||
putMoveOrgMemberData
|
||||
} from '@fastgpt/global/support/user/team/org/api';
|
||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
|
||||
export const getOrgList = () => GET<OrgType[]>('/proApi/support/user/team/org/list');
|
||||
|
||||
export const postCreateOrg = (data: postCreateOrgData) =>
|
||||
POST('/proApi/support/user/team/org/create', data);
|
||||
|
||||
export const deleteOrg = (orgId: string) =>
|
||||
DELETE('/proApi/support/user/team/org/delete', { orgId });
|
||||
|
||||
export const deleteOrgMember = (orgId: string, tmbId: string) =>
|
||||
DELETE('/proApi/support/user/team/org/deleteMember', { orgId, tmbId });
|
||||
|
||||
export const putMoveOrg = (orgId: string, parentId: string) =>
|
||||
PUT('/proApi/support/user/team/org/move', { orgId, parentId });
|
||||
|
||||
export const putMoveOrgMember = (data: putMoveOrgMemberData) =>
|
||||
PUT('/proApi/support/user/team/org/moveMember', data);
|
||||
|
||||
export const putUpdateOrg = (data: putUpdateOrgData) =>
|
||||
PUT('/proApi/support/user/team/org/update', data);
|
||||
|
||||
export const putUpdateOrgMembers = (data: putUpdateOrgMembersData) =>
|
||||
PUT('/proApi/support/user/team/org/updateMembers', data);
|
||||
|
||||
// export const putChnageOrgOwner = (data: putChnageOrgOwnerData) =>
|
||||
// PUT('/proApi/support/user/team/org/changeOwner', data);
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { UserUpdateParams } from '@/types/user';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getTokenLogin, putUserInfo } from '@/web/support/user/api';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import type { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import type { OrgMemberSchemaType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import type { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import type { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import type { UserUpdateParams } from '@/types/user';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { getTokenLogin, putUserInfo } from '@/web/support/user/api';
|
||||
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { getTeamPlanStatus } from './team/api';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { getGroupList } from './team/group/api';
|
||||
import { getOrgList } from './team/org/api';
|
||||
|
||||
type State = {
|
||||
systemMsgReadId: string;
|
||||
@@ -30,6 +32,10 @@ type State = {
|
||||
teamMemberGroups: MemberGroupListType;
|
||||
myGroups: MemberGroupListType;
|
||||
loadAndGetGroups: (init?: boolean) => Promise<MemberGroupListType>;
|
||||
|
||||
teamOrgs: OrgType[];
|
||||
myOrgs: OrgType[];
|
||||
loadAndGetOrgs: (init?: boolean) => Promise<OrgType[]>;
|
||||
};
|
||||
|
||||
export const useUserStore = create<State>()(
|
||||
@@ -107,6 +113,7 @@ export const useUserStore = create<State>()(
|
||||
return res;
|
||||
},
|
||||
teamMemberGroups: [],
|
||||
teamOrgs: [],
|
||||
myGroups: [],
|
||||
loadAndGetGroups: async (init = false) => {
|
||||
if (!useSystemStore.getState()?.feConfigs?.isPlus) return [];
|
||||
@@ -123,6 +130,23 @@ export const useUserStore = create<State>()(
|
||||
);
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
myOrgs: [],
|
||||
loadAndGetOrgs: async (init = false) => {
|
||||
if (!useSystemStore.getState()?.feConfigs?.isPlus) return [];
|
||||
|
||||
const randomRefresh = Math.random() > 0.7;
|
||||
if (!randomRefresh && !init && get().myOrgs.length) return Promise.resolve(get().myOrgs);
|
||||
|
||||
const res = await getOrgList();
|
||||
set((state) => {
|
||||
state.teamOrgs = res;
|
||||
state.myOrgs = res.filter((item) =>
|
||||
item.members.map((i) => String(i.tmbId)).includes(String(state.userInfo?.team?.tmbId))
|
||||
);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
})),
|
||||
|
||||
Reference in New Issue
Block a user