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:
a.e.
2024-12-30 13:49:56 +08:00
committed by archer
parent bb669ca3ff
commit 1fc77a126a
46 changed files with 1934 additions and 191 deletions

View File

@@ -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')
}
];

View File

@@ -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',

View File

@@ -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';

View File

@@ -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;
}>;

View File

@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;

View File

@@ -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;
}>;

View File

@@ -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'> & {

View 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;
// };

View 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'
// }

View 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[];
};

View 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
// };
};

View File

@@ -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;

View File

@@ -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({

View 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;
// };

View 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
);

View 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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -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'),

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "保留管理员权限"
}

View File

@@ -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": "您的身份校验未通过",

View File

@@ -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": "管理成員",

View File

@@ -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": "身份驗證未通過",