mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-22 04:06:18 +00:00
Update permission (#1522)
* Permission (#1442) * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * feat: add permission display in the team manager modal * feat: add permission i18n * feat: let team module acquire permission ablity * feat: add ownerPermission property into metaData * feat: team premission system * feat: extract the resourcePermission from resource schemas * fix: move enum definition to constant * feat: auth member permission handler, invite user * feat: permission manage * feat: adjust the style * feat: team card style - add a new icon * feat: team permission in guest mode * chore: change the type * chore: delete useless file * chore: delete useless code * feat: do not show owner in PermissionManage view * chore: fix style * fix: icon remove fill * feat: adjust the codes --------- Co-authored-by: Archer <545436317@qq.com> * perf: permission modal * lock --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
1
.vscode/i18n-ally-custom-framework.yml
vendored
1
.vscode/i18n-ally-custom-framework.yml
vendored
@@ -24,6 +24,7 @@ usageMatchRegex:
|
|||||||
- "[^\\w\\d]fileT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]fileT\\(['\"`]({key})['\"`]"
|
||||||
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
||||||
- "[^\\w\\d]workflowT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]workflowT\\(['\"`]({key})['\"`]"
|
||||||
|
- "[^\\w\\d]userT\\(['\"`]({key})['\"`]"
|
||||||
|
|
||||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||||
# and works like how the i18next framework identifies the namespace scope from the
|
# and works like how the i18next framework identifies the namespace scope from the
|
||||||
|
@@ -20,3 +20,9 @@ export const PermissionTypeMap = {
|
|||||||
label: 'permission.Public'
|
label: 'permission.Public'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum ResourceTypeEnum {
|
||||||
|
team = 'team',
|
||||||
|
app = 'app',
|
||||||
|
dataset = 'dataset'
|
||||||
|
}
|
||||||
|
9
packages/global/support/permission/type.d.ts
vendored
9
packages/global/support/permission/type.d.ts
vendored
@@ -1,5 +1,7 @@
|
|||||||
import { AuthUserTypeEnum } from './constant';
|
import { AuthUserTypeEnum } from './constant';
|
||||||
|
|
||||||
|
export type PermissionValueType = number;
|
||||||
|
|
||||||
export type AuthResponseType = {
|
export type AuthResponseType = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
@@ -9,3 +11,10 @@ export type AuthResponseType = {
|
|||||||
appId?: string;
|
appId?: string;
|
||||||
apikey?: string;
|
apikey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ResourcePermissionType = {
|
||||||
|
teamId: string;
|
||||||
|
tmbId: string;
|
||||||
|
resourceType: ResourceType;
|
||||||
|
permission: PermissionValueType;
|
||||||
|
};
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { PermissionValueType } from 'support/permission/type';
|
||||||
import { TeamMemberRoleEnum } from './constant';
|
import { TeamMemberRoleEnum } from './constant';
|
||||||
import { LafAccountType, TeamMemberSchema } from './type';
|
import { LafAccountType, TeamMemberSchema } from './type';
|
||||||
|
|
||||||
@@ -44,3 +45,9 @@ export type InviteMemberResponse = Record<
|
|||||||
'invite' | 'inValid' | 'inTeam',
|
'invite' | 'inValid' | 'inTeam',
|
||||||
{ username: string; userId: string }[]
|
{ username: string; userId: string }[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type UpdateTeamMemberPermissionProps = {
|
||||||
|
teamId: string;
|
||||||
|
memberIds: string[];
|
||||||
|
permission: PermissionValueType;
|
||||||
|
};
|
||||||
|
5
packages/global/support/user/team/type.d.ts
vendored
5
packages/global/support/user/team/type.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
import type { UserModelSchema } from '../type';
|
import type { UserModelSchema } from '../type';
|
||||||
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
|
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
|
||||||
import { LafAccountType } from './type';
|
import { LafAccountType } from './type';
|
||||||
|
import { PermissionValueType, ResourcePermissionType } from '../../permission/type';
|
||||||
|
|
||||||
export type TeamSchema = {
|
export type TeamSchema = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -15,6 +16,7 @@ export type TeamSchema = {
|
|||||||
lastWebsiteSyncTime: Date;
|
lastWebsiteSyncTime: Date;
|
||||||
};
|
};
|
||||||
lafAccount: LafAccountType;
|
lafAccount: LafAccountType;
|
||||||
|
defaultPermission: PermissionValueType;
|
||||||
};
|
};
|
||||||
export type tagsType = {
|
export type tagsType = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -61,6 +63,7 @@ export type TeamItemType = {
|
|||||||
status: `${TeamMemberStatusEnum}`;
|
status: `${TeamMemberStatusEnum}`;
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
lafAccount?: LafAccountType;
|
lafAccount?: LafAccountType;
|
||||||
|
defaultPermission: PermissionValueType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TeamMemberItemType = {
|
export type TeamMemberItemType = {
|
||||||
@@ -69,8 +72,10 @@ export type TeamMemberItemType = {
|
|||||||
teamId: string;
|
teamId: string;
|
||||||
memberName: string;
|
memberName: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
// TODO: this should be deprecated.
|
||||||
role: `${TeamMemberRoleEnum}`;
|
role: `${TeamMemberRoleEnum}`;
|
||||||
status: `${TeamMemberStatusEnum}`;
|
status: `${TeamMemberStatusEnum}`;
|
||||||
|
permission: PermissionValueType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TeamTagItemType = {
|
export type TeamTagItemType = {
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
import { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||||
|
import { MongoResourcePermission } from './schema';
|
||||||
|
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||||
|
|
||||||
|
export async function getResourcePermission({
|
||||||
|
tmbId,
|
||||||
|
resourceType
|
||||||
|
}: {
|
||||||
|
tmbId: string;
|
||||||
|
resourceType: ResourceTypeEnum;
|
||||||
|
}) {
|
||||||
|
return (await MongoResourcePermission.findOne({
|
||||||
|
tmbId,
|
||||||
|
resourceType
|
||||||
|
})) as ResourcePermissionType;
|
||||||
|
}
|
@@ -0,0 +1,127 @@
|
|||||||
|
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
|
||||||
|
// It is spired by the permission system in Linux.
|
||||||
|
// The lowest 3 bits present the permission of reading, writing and managing.
|
||||||
|
// The higher bits are advanced permissions or extended permissions, which could be customized.
|
||||||
|
export type PermissionValueType = number;
|
||||||
|
export type PermissionListType = { [key: string]: PermissionValueType };
|
||||||
|
export const NullPermission: PermissionValueType = 0;
|
||||||
|
|
||||||
|
// the Permission helper class
|
||||||
|
export class Permission {
|
||||||
|
value: PermissionValueType;
|
||||||
|
constructor(value: PermissionValueType) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add permission(s)
|
||||||
|
// it can be chaining called.
|
||||||
|
// @example
|
||||||
|
// const perm = new Permission(permission)
|
||||||
|
// perm.add(PermissionList['read'])
|
||||||
|
// perm.add(PermissionList['read'], PermissionList['write'])
|
||||||
|
// perm.add(PermissionList['read']).add(PermissionList['write'])
|
||||||
|
add(...perm: PermissionValueType[]): Permission {
|
||||||
|
for (let p of perm) {
|
||||||
|
this.value = addPermission(this.value, p);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(...perm: PermissionValueType[]): Permission {
|
||||||
|
for (let p of perm) {
|
||||||
|
this.value = removePermission(this.value, p);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(perm: PermissionValueType): Permission | boolean {
|
||||||
|
if (checkPermission(this.value, perm)) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructPermission(permList: PermissionValueType[]) {
|
||||||
|
return new Permission(NullPermission).add(...permList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The base Permissions List
|
||||||
|
// It can be extended, for example:
|
||||||
|
// export const UserPermissionList: PermissionListType = {
|
||||||
|
// ...PermissionList,
|
||||||
|
// 'Invite': 0b1000
|
||||||
|
// }
|
||||||
|
export const PermissionList: PermissionListType = {
|
||||||
|
Read: 0b100,
|
||||||
|
Write: 0b010,
|
||||||
|
Manage: 0b001
|
||||||
|
};
|
||||||
|
|
||||||
|
// list of permissions. could be customized.
|
||||||
|
// ! removal of the basic permissions is not recommended.
|
||||||
|
// const PermList: Array<PermissionType> = [ReadPerm, WritePerm, ManagePerm];
|
||||||
|
|
||||||
|
// return the list of permissions
|
||||||
|
// @param Perm(optional): the list of permissions to be added
|
||||||
|
// export function getPermList(Perm?: PermissionType[]): Array<PermissionType> {
|
||||||
|
// if (Perm === undefined) {
|
||||||
|
// return PermList;
|
||||||
|
// } else {
|
||||||
|
// return PermList.concat(Perm);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// check the permission
|
||||||
|
// @param [val]: The permission value to be checked
|
||||||
|
// @parma [perm]: Which Permission value will be checked
|
||||||
|
// @returns [booean]: if the [val] has the [perm]
|
||||||
|
// example:
|
||||||
|
// const perm = user.permission // get this permisiion from db or somewhere else
|
||||||
|
// const ok = checkPermission(perm, PermissionList['Read'])
|
||||||
|
export function checkPermission(val: PermissionValueType, perm: PermissionValueType): boolean {
|
||||||
|
return (val & perm) === perm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the permission
|
||||||
|
// it can be chaining called.
|
||||||
|
// return the new permission value based on [val] added with [perm]
|
||||||
|
// @param val: PermissionValueType
|
||||||
|
// @param perm: PermissionValueType
|
||||||
|
// example:
|
||||||
|
// const basePerm = 0b001; // Manage only
|
||||||
|
export function addPermission(
|
||||||
|
val: PermissionValueType,
|
||||||
|
perm: PermissionValueType
|
||||||
|
): PermissionValueType {
|
||||||
|
return val | perm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the permission
|
||||||
|
export function removePermission(
|
||||||
|
val: PermissionValueType,
|
||||||
|
perm: PermissionValueType
|
||||||
|
): PermissionValueType {
|
||||||
|
return val & ~perm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function parsePermission(val: PermissionValueType, list: PermissionValueType[]) {
|
||||||
|
// const result: [[string, boolean]] = [] as any;
|
||||||
|
// list.forEach((perm) => {
|
||||||
|
// result.push([perm[0], checkPermission(val, perm)]);
|
||||||
|
// });
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function hasManage(val: PermissionValueType) {
|
||||||
|
return checkPermission(val, PermissionList['Manage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasWrite(val: PermissionValueType) {
|
||||||
|
return checkPermission(val, PermissionList['Write']);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasRead(val: PermissionValueType) {
|
||||||
|
return checkPermission(val, PermissionList['Read']);
|
||||||
|
}
|
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
TeamCollectionName,
|
||||||
|
TeamMemberCollectionName
|
||||||
|
} from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import { Model, connectionMongo } from '../../../common/mongo';
|
||||||
|
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||||
|
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||||
|
const { Schema, model, models } = connectionMongo;
|
||||||
|
|
||||||
|
export const ResourcePermissionSchema = new Schema({
|
||||||
|
teamId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: TeamCollectionName
|
||||||
|
},
|
||||||
|
tmbId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: TeamMemberCollectionName
|
||||||
|
},
|
||||||
|
resourceType: {
|
||||||
|
type: Object.values(ResourceTypeEnum),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
ResourcePermissionSchema.index({
|
||||||
|
teamId: 1,
|
||||||
|
resourceType: 1
|
||||||
|
});
|
||||||
|
ResourcePermissionSchema.index({
|
||||||
|
tmbId: 1,
|
||||||
|
resourceType: 1
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ResourcePermissionCollectionName = 'resource_permission';
|
||||||
|
|
||||||
|
export const MongoResourcePermission: Model<ResourcePermissionType> =
|
||||||
|
models[ResourcePermissionCollectionName] ||
|
||||||
|
model(ResourcePermissionCollectionName, ResourcePermissionSchema);
|
||||||
|
|
||||||
|
MongoResourcePermission.syncIndexes();
|
@@ -27,7 +27,8 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamItemType>
|
|||||||
status: tmb.status,
|
status: tmb.status,
|
||||||
defaultTeam: tmb.defaultTeam,
|
defaultTeam: tmb.defaultTeam,
|
||||||
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
|
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
|
||||||
lafAccount: tmb.teamId.lafAccount
|
lafAccount: tmb.teamId.lafAccount,
|
||||||
|
defaultPermission: tmb.teamId.defaultPermission
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ const { Schema, model, models } = connectionMongo;
|
|||||||
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
|
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
|
||||||
import { userCollectionName } from '../../user/schema';
|
import { userCollectionName } from '../../user/schema';
|
||||||
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import { NullPermission } from '../../permission/resourcePermission/permisson';
|
||||||
|
|
||||||
const TeamSchema = new Schema({
|
const TeamSchema = new Schema({
|
||||||
name: {
|
name: {
|
||||||
@@ -13,6 +14,10 @@ const TeamSchema = new Schema({
|
|||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: userCollectionName
|
ref: userCollectionName
|
||||||
},
|
},
|
||||||
|
defaultPermission: {
|
||||||
|
type: Number,
|
||||||
|
default: NullPermission
|
||||||
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '/icon/logo.svg'
|
default: '/icon/logo.svg'
|
||||||
|
@@ -53,6 +53,7 @@ export const iconPaths = {
|
|||||||
'common/settingLight': () => import('./icons/common/settingLight.svg'),
|
'common/settingLight': () => import('./icons/common/settingLight.svg'),
|
||||||
'common/text/t': () => import('./icons/common/text/t.svg'),
|
'common/text/t': () => import('./icons/common/text/t.svg'),
|
||||||
'common/tickFill': () => import('./icons/common/tickFill.svg'),
|
'common/tickFill': () => import('./icons/common/tickFill.svg'),
|
||||||
|
'common/trash': () => import('./icons/common/trash.svg'),
|
||||||
'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'),
|
'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'),
|
||||||
'common/viewLight': () => import('./icons/common/viewLight.svg'),
|
'common/viewLight': () => import('./icons/common/viewLight.svg'),
|
||||||
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
|
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
|
||||||
@@ -207,8 +208,10 @@ export const iconPaths = {
|
|||||||
'support/outlink/iframeLight': () => import('./icons/support/outlink/iframeLight.svg'),
|
'support/outlink/iframeLight': () => import('./icons/support/outlink/iframeLight.svg'),
|
||||||
'support/outlink/share': () => import('./icons/support/outlink/share.svg'),
|
'support/outlink/share': () => import('./icons/support/outlink/share.svg'),
|
||||||
'support/outlink/shareLight': () => import('./icons/support/outlink/shareLight.svg'),
|
'support/outlink/shareLight': () => import('./icons/support/outlink/shareLight.svg'),
|
||||||
|
'support/permission/collaborator': () => import('./icons/support/permission/collaborator.svg'),
|
||||||
'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'),
|
'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'),
|
||||||
'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'),
|
'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'),
|
||||||
|
'support/team/key': () => import('./icons/support/team/key.svg'),
|
||||||
'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'),
|
'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'),
|
||||||
'support/usage/usageRecordLight': () => import('./icons/support/usage/usageRecordLight.svg'),
|
'support/usage/usageRecordLight': () => import('./icons/support/usage/usageRecordLight.svg'),
|
||||||
'support/user/individuation': () => import('./icons/support/user/individuation.svg'),
|
'support/user/individuation': () => import('./icons/support/user/individuation.svg'),
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.48246 1.18066H8.51749C8.8405 1.18065 9.12217 1.18064 9.35477 1.19965C9.60121 1.21978 9.85067 1.26453 10.0919 1.38746C10.4489 1.56936 10.7392 1.8596 10.9211 2.2166C11.044 2.45787 11.0888 2.70733 11.1089 2.95376C11.1249 3.14983 11.1274 3.38075 11.1278 3.6419H13.5377C13.9059 3.6419 14.2044 3.94037 14.2044 4.30856C14.2044 4.67675 13.9059 4.97523 13.5377 4.97523H12.9738V11.2272C12.9738 11.721 12.9738 12.1316 12.9464 12.4667C12.9179 12.8157 12.8564 13.1412 12.6999 13.4483C12.459 13.921 12.0747 14.3054 11.6019 14.5463C11.2948 14.7028 10.9694 14.7643 10.6203 14.7928C10.2852 14.8202 9.87463 14.8202 9.38088 14.8202H6.61907C6.12531 14.8202 5.71472 14.8202 5.37961 14.7928C5.03057 14.7643 4.70512 14.7028 4.39805 14.5463C3.92527 14.3054 3.5409 13.921 3.3 13.4483C3.14354 13.1412 3.08203 12.8157 3.05351 12.4667C3.02613 12.1316 3.02614 11.721 3.02615 11.2272L3.02615 4.97523H2.4622C2.09401 4.97523 1.79553 4.67675 1.79553 4.30856C1.79553 3.94037 2.09401 3.6419 2.4622 3.6419H4.87214C4.87253 3.38075 4.87503 3.14983 4.89105 2.95376C4.91119 2.70733 4.95593 2.45787 5.07886 2.2166C5.26076 1.8596 5.55101 1.56936 5.90801 1.38746C6.14927 1.26453 6.39874 1.21978 6.64517 1.19965C6.87778 1.18064 7.15945 1.18065 7.48246 1.18066ZM4.35948 4.97523V11.2C4.35948 11.7279 4.36 12.0838 4.38242 12.3581C4.4042 12.6247 4.44328 12.7552 4.48801 12.8429C4.60107 13.0648 4.78148 13.2452 5.00337 13.3583C5.09115 13.403 5.2216 13.4421 5.48818 13.4639C5.76255 13.4863 6.11839 13.4868 6.64629 13.4868H9.35365C9.88156 13.4868 10.2374 13.4863 10.5118 13.4639C10.7783 13.4421 10.9088 13.403 10.9966 13.3583C11.2185 13.2452 11.3989 13.0648 11.5119 12.8429C11.5567 12.7552 11.5957 12.6247 11.6175 12.3581C11.6399 12.0838 11.6405 11.7279 11.6405 11.2V4.97523H4.35948ZM9.79442 3.6419H6.20552C6.206 3.38346 6.20837 3.20419 6.21996 3.06234C6.23336 2.89836 6.25568 2.8439 6.26687 2.82192C6.32094 2.71581 6.40721 2.62953 6.51333 2.57547C6.5353 2.56427 6.58977 2.54195 6.75375 2.52855C6.92551 2.51452 7.15212 2.514 7.50773 2.514H8.49222C8.84782 2.514 9.07443 2.51452 9.2462 2.52855C9.41018 2.54195 9.46464 2.56427 9.48662 2.57547C9.59273 2.62953 9.679 2.71581 9.73307 2.82192C9.74427 2.8439 9.76659 2.89836 9.77999 3.06234C9.79158 3.20419 9.79395 3.38346 9.79442 3.6419ZM6.76936 7.02609C7.13755 7.02609 7.43602 7.32457 7.43602 7.69276V10.7693C7.43602 11.1375 7.13755 11.436 6.76936 11.436C6.40117 11.436 6.10269 11.1375 6.10269 10.7693V7.69276C6.10269 7.32457 6.40117 7.02609 6.76936 7.02609ZM9.23059 7.02609C9.59878 7.02609 9.89726 7.32457 9.89726 7.69276V10.7693C9.89726 11.1375 9.59878 11.436 9.23059 11.436C8.8624 11.436 8.56392 11.1375 8.56392 10.7693V7.69276C8.56392 7.32457 8.8624 7.02609 9.23059 7.02609Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"
|
||||||
|
fill="#3370FF" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.76871 9.91581C11.7759 9.91581 13.4 8.29129 13.4 6.29107C13.4 4.29086 11.7759 2.66634 9.76871 2.66634C7.76153 2.66634 6.1374 4.29086 6.1374 6.29107C6.1374 6.62588 6.18253 6.94805 6.26627 7.25285C6.42305 7.82349 6.29244 8.48531 5.82219 8.95692L2.66824 12.12L2.66824 13.333H4.02373L4.05938 13.2974L4.41851 14.6377C4.34416 14.6566 4.26717 14.6663 4.18917 14.6663H2.1349C1.69307 14.6663 1.3349 14.3082 1.3349 13.8663L1.33491 11.9546C1.33491 11.7075 1.43288 11.4705 1.60733 11.2956L4.87803 8.01546C4.98449 7.90869 5.02052 7.75148 4.98058 7.60608C4.86552 7.18729 4.80407 6.74635 4.80407 6.29107C4.80407 3.55281 7.02682 1.33301 9.76871 1.33301C12.5106 1.33301 14.7334 3.55281 14.7334 6.29107C14.7334 9.02934 12.5106 11.2491 9.76871 11.2491C9.32077 11.2491 8.88667 11.1899 8.47387 11.0788C8.329 11.0398 8.1728 11.0761 8.06661 11.1821L7.12673 12.1201L6.18392 11.1773L7.12477 10.2383C7.59373 9.77034 8.25145 9.63822 8.82031 9.79128C9.12116 9.87223 9.4388 9.91581 9.76871 9.91581Z"/>
|
||||||
|
<path d="M4.91026 13.4301H5.48328C5.81465 13.4301 6.08332 13.1615 6.08332 12.8301L6.08328 12.8229V12.2571H6.74748C7.07885 12.2571 7.34748 11.9884 7.34748 11.6571C7.34748 11.3257 7.07885 11.0571 6.74748 11.0571H5.57442C5.5591 11.0571 5.5439 11.0576 5.52886 11.0588C5.51382 11.0576 5.49861 11.0571 5.48328 11.0571C5.15191 11.0571 4.88328 11.3257 4.88328 11.6571V12.2301H4.31026C3.97889 12.2301 3.71026 12.4987 3.71026 12.8301V14.0554C3.71026 14.3868 3.97889 14.6554 4.31026 14.6554C4.64163 14.6554 4.91026 14.3868 4.91026 14.0554V13.4301Z" />
|
||||||
|
<path d="M11.5713 5.75203C11.5713 6.48841 10.9744 7.08537 10.238 7.08537C9.50161 7.08537 8.90465 6.48841 8.90465 5.75203C8.90465 5.01565 9.50161 4.4187 10.238 4.4187C10.9744 4.4187 11.5713 5.01565 11.5713 5.75203Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1 +1 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698724877512" class="icon" viewBox="0 0 1252 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4699" xmlns:xlink="http://www.w3.org/1999/xlink" width="156.5" height="128"><path d="M1164.136052 546.068492h-143.393656a199.609195 199.609195 0 0 1-70.816241-4.544965 36.927843 36.927843 0 0 1-16.305063-19.884224l-1.619144-8.52181a35.166669 35.166669 0 0 1 29.400245-37.495963c16.049409 0 32.070411 0 48.11982-0.284061h159.698719c41.955711 0 74.395401-5.681207 80.729946 31.246637a31.587509 31.587509 0 0 1-4.033657 20.168283c-13.095181 25.56543-44.767908 19.316103-81.780969 19.316103z m60.135572-199.126293a125.838727 125.838727 0 0 1-29.655899 1.136242H878.71223a100.670982 100.670982 0 0 1-33.405495-2.840604 36.586971 36.586971 0 0 1-18.463921-19.600163c-0.710151-2.556543-1.420302-5.397146-2.130453-8.237749a34.996233 34.996233 0 0 1 28.406033-39.200326 131.179061 131.179061 0 0 1 28.065161-0.568121h337.293238a35.393917 35.393917 0 0 1 5.823237 69.310721z m-618.228906 233.213533A437.736971 437.736971 0 0 1 874.905822 982.669222a41.330778 41.330778 0 0 1-82.633151 0 354.223234 354.223234 0 0 0-344.621994-353.371053h-20.395532A354.223234 354.223234 0 0 0 82.633151 982.669222a41.330778 41.330778 0 0 1-82.633151 0 437.736971 437.736971 0 0 1 267.755269-402.51349 314.710442 314.710442 0 1 1 338.287449 0zM434.015781 83.902332a231.622795 231.622795 0 0 0-6.760636 463.018341h20.395532a231.622795 231.622795 0 0 0-13.634896-463.018341zM1040.797056 682.985572a162.596134 162.596134 0 0 1 66.81099-4.829026h66.697365c40.535409 0 69.594781-3.976845 75.645267 31.246637a32.922592 32.922592 0 0 1-4.033657 20.452343c-12.157782 23.292947-37.80843 19.032042-72.435385 19.032043h-79.224426a158.647695 158.647695 0 0 1-50.534333-3.124664 35.59276 35.59276 0 0 1-18.151455-19.316103l-2.158859-8.521809A35.905226 35.905226 0 0 1 1040.797056 682.985572z" fill="#333333" p-id="4700"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698724877512" class="icon" viewBox="0 0 1252 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4699" xmlns:xlink="http://www.w3.org/1999/xlink" width="156.5" height="128"><path d="M1164.136052 546.068492h-143.393656a199.609195 199.609195 0 0 1-70.816241-4.544965 36.927843 36.927843 0 0 1-16.305063-19.884224l-1.619144-8.52181a35.166669 35.166669 0 0 1 29.400245-37.495963c16.049409 0 32.070411 0 48.11982-0.284061h159.698719c41.955711 0 74.395401-5.681207 80.729946 31.246637a31.587509 31.587509 0 0 1-4.033657 20.168283c-13.095181 25.56543-44.767908 19.316103-81.780969 19.316103z m60.135572-199.126293a125.838727 125.838727 0 0 1-29.655899 1.136242H878.71223a100.670982 100.670982 0 0 1-33.405495-2.840604 36.586971 36.586971 0 0 1-18.463921-19.600163c-0.710151-2.556543-1.420302-5.397146-2.130453-8.237749a34.996233 34.996233 0 0 1 28.406033-39.200326 131.179061 131.179061 0 0 1 28.065161-0.568121h337.293238a35.393917 35.393917 0 0 1 5.823237 69.310721z m-618.228906 233.213533A437.736971 437.736971 0 0 1 874.905822 982.669222a41.330778 41.330778 0 0 1-82.633151 0 354.223234 354.223234 0 0 0-344.621994-353.371053h-20.395532A354.223234 354.223234 0 0 0 82.633151 982.669222a41.330778 41.330778 0 0 1-82.633151 0 437.736971 437.736971 0 0 1 267.755269-402.51349 314.710442 314.710442 0 1 1 338.287449 0zM434.015781 83.902332a231.622795 231.622795 0 0 0-6.760636 463.018341h20.395532a231.622795 231.622795 0 0 0-13.634896-463.018341zM1040.797056 682.985572a162.596134 162.596134 0 0 1 66.81099-4.829026h66.697365c40.535409 0 69.594781-3.976845 75.645267 31.246637a32.922592 32.922592 0 0 1-4.033657 20.452343c-12.157782 23.292947-37.80843 19.032042-72.435385 19.032043h-79.224426a158.647695 158.647695 0 0 1-50.534333-3.124664 35.59276 35.59276 0 0 1-18.151455-19.316103l-2.158859-8.521809A35.905226 35.905226 0 0 1 1040.797056 682.985572z" p-id="4700"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -93,6 +93,7 @@
|
|||||||
"Rename Success": "Rename Success",
|
"Rename Success": "Rename Success",
|
||||||
"Request Error": "Request Error",
|
"Request Error": "Request Error",
|
||||||
"Require Input": "Required Input",
|
"Require Input": "Required Input",
|
||||||
|
"Role": "Role",
|
||||||
"Root folder": "Root folder",
|
"Root folder": "Root folder",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Save Failed": "Save Failed",
|
"Save Failed": "Save Failed",
|
||||||
@@ -1563,7 +1564,7 @@
|
|||||||
"Remove Member Failed": "Failed to remove team member",
|
"Remove Member Failed": "Failed to remove team member",
|
||||||
"Remove Member Success": "Successfully removed team member",
|
"Remove Member Success": "Successfully removed team member",
|
||||||
"Remove Member Tip": "Remove from Team",
|
"Remove Member Tip": "Remove from Team",
|
||||||
"Role": "Role",
|
"Role": "Role(Old)",
|
||||||
"Select Team": "Select Team",
|
"Select Team": "Select Team",
|
||||||
"Set Name": "Name Your Team",
|
"Set Name": "Name Your Team",
|
||||||
"Switch Team Failed": "Failed to switch team",
|
"Switch Team Failed": "Failed to switch team",
|
||||||
|
1
projects/app/i18n/en/user.json
Normal file
1
projects/app/i18n/en/user.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
@@ -82,6 +82,7 @@
|
|||||||
"Output": "输出",
|
"Output": "输出",
|
||||||
"Params": "参数",
|
"Params": "参数",
|
||||||
"Password inconsistency": "两次密码不一致",
|
"Password inconsistency": "两次密码不一致",
|
||||||
|
"Permission": "权限",
|
||||||
"Please Input Name": "请输入名称",
|
"Please Input Name": "请输入名称",
|
||||||
"Price used": "金额消耗",
|
"Price used": "金额消耗",
|
||||||
"Read document": "查看文档",
|
"Read document": "查看文档",
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
"Rename Success": "重命名成功",
|
"Rename Success": "重命名成功",
|
||||||
"Request Error": "请求异常",
|
"Request Error": "请求异常",
|
||||||
"Require Input": "必填",
|
"Require Input": "必填",
|
||||||
|
"Role": "权限",
|
||||||
"Root folder": "根目录",
|
"Root folder": "根目录",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"Save Failed": "保存失败",
|
"Save Failed": "保存失败",
|
||||||
@@ -1569,7 +1571,7 @@
|
|||||||
"Remove Member Failed": "移除团队成员异常",
|
"Remove Member Failed": "移除团队成员异常",
|
||||||
"Remove Member Success": "移除团队成员成功",
|
"Remove Member Success": "移除团队成员成功",
|
||||||
"Remove Member Tip": "移出团队",
|
"Remove Member Tip": "移出团队",
|
||||||
"Role": "身份",
|
"Role": "身份(旧版)",
|
||||||
"Select Team": "团队选择",
|
"Select Team": "团队选择",
|
||||||
"Set Name": "给团队取个名字",
|
"Set Name": "给团队取个名字",
|
||||||
"Switch Team Failed": "切换团队异常",
|
"Switch Team Failed": "切换团队异常",
|
||||||
|
1
projects/app/i18n/zh/user.json
Normal file
1
projects/app/i18n/zh/user.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
48
projects/app/src/components/common/Rowtabs/index.tsx
Normal file
48
projects/app/src/components/common/Rowtabs/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex, Box, BoxProps, border } from '@chakra-ui/react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
|
type Props = BoxProps & {
|
||||||
|
list: {
|
||||||
|
icon?: string;
|
||||||
|
label: string | React.ReactNode;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
value: string;
|
||||||
|
onChange: (e: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
|
||||||
|
return (
|
||||||
|
<Box display={'inline-flex'} px={'3px'} {...props}>
|
||||||
|
{list.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item.value}
|
||||||
|
flex={'1 0 0'}
|
||||||
|
alignItems={'center'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
px={px}
|
||||||
|
py={py}
|
||||||
|
userSelect={'none'}
|
||||||
|
whiteSpace={'noWrap'}
|
||||||
|
borderBottom={'2px solid'}
|
||||||
|
{...(value === item.value
|
||||||
|
? {
|
||||||
|
bg: 'white',
|
||||||
|
color: 'primary.600',
|
||||||
|
borderColor: 'primary.600'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
borderColor: 'myGray.100',
|
||||||
|
onClick: () => onChange(item.value)
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.icon && <MyIcon name={item.icon as any} mr={1} w={'14px'} />}
|
||||||
|
<Box>{item.label}</Box>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RowTabs;
|
@@ -0,0 +1,174 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalFooter,
|
||||||
|
Grid,
|
||||||
|
Input,
|
||||||
|
Flex,
|
||||||
|
Checkbox,
|
||||||
|
CloseButton,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { TeamContext } from '.';
|
||||||
|
import {
|
||||||
|
hasManage,
|
||||||
|
constructPermission,
|
||||||
|
PermissionList
|
||||||
|
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||||
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
|
function AddManagerModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||||
|
const members = useContextSelector(TeamContext, (v) =>
|
||||||
|
v.members.filter((member) => {
|
||||||
|
return member.tmbId != userInfo!.team.tmbId && !hasManage(member.permission);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const [selected, setSelected] = useState<typeof members>([]);
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [searched, setSearched] = useState<typeof members>(members);
|
||||||
|
|
||||||
|
const { mutate: submit, isLoading } = useRequest({
|
||||||
|
mutationFn: async () => {
|
||||||
|
console.log(selected);
|
||||||
|
return updateMemberPermission({
|
||||||
|
teamId: userInfo!.team.teamId,
|
||||||
|
permission: constructPermission([
|
||||||
|
PermissionList['Read'],
|
||||||
|
PermissionList['Write'],
|
||||||
|
PermissionList['Manage']
|
||||||
|
]).value,
|
||||||
|
memberIds: selected.map((item) => {
|
||||||
|
return item.tmbId;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
refetchMembers();
|
||||||
|
onSuccess();
|
||||||
|
},
|
||||||
|
successToast: '成功',
|
||||||
|
errorToast: '失败'
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isOpen
|
||||||
|
iconSrc={'support/permission/collaborator'}
|
||||||
|
maxW={['90vw']}
|
||||||
|
minW={['900px']}
|
||||||
|
overflow={'unset'}
|
||||||
|
title={
|
||||||
|
<Box>
|
||||||
|
<Box>添加管理员</Box>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ModalCloseButton onClick={onClose} />
|
||||||
|
<ModalBody py={6} px={10}>
|
||||||
|
<Grid
|
||||||
|
templateColumns="1fr 1fr"
|
||||||
|
h="448px"
|
||||||
|
borderRadius="8px"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="myGray.200"
|
||||||
|
>
|
||||||
|
<Flex flexDirection="column" p="4">
|
||||||
|
<InputGroup alignItems="center" h="32px" my="2" py="1">
|
||||||
|
<InputLeftElement>
|
||||||
|
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
placeholder="搜索用户名"
|
||||||
|
fontSize="lg"
|
||||||
|
bg={'myGray.50'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearch(e.target.value);
|
||||||
|
setSearched(
|
||||||
|
members.filter((member) => member.memberName.includes(e.target.value))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<Flex flexDirection="column" mt={3}>
|
||||||
|
{searched.map((member) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
py="2"
|
||||||
|
px={3}
|
||||||
|
borderRadius={'md'}
|
||||||
|
fontSize="lg"
|
||||||
|
alignItems="center"
|
||||||
|
key={member.tmbId}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ bg: 'myGray.50' }}
|
||||||
|
_notLast={{ mb: 2 }}
|
||||||
|
onClick={() => {
|
||||||
|
if (selected.indexOf(member) == -1) {
|
||||||
|
setSelected([...selected, member]);
|
||||||
|
} else {
|
||||||
|
setSelected([...selected.filter((item) => item.tmbId != member.tmbId)]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox isChecked={selected.includes(member)} size="lg" />
|
||||||
|
<Avatar src={member.avatar} w="24px" />
|
||||||
|
{member.memberName}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4">
|
||||||
|
<Box mt={3}>已选: {selected.length} 个</Box>
|
||||||
|
<Box mt={5}>
|
||||||
|
{selected.map((member) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
py="2"
|
||||||
|
px={3}
|
||||||
|
borderRadius={'md'}
|
||||||
|
key={member.tmbId}
|
||||||
|
_hover={{ bg: 'myGray.50' }}
|
||||||
|
_notLast={{ mb: 2 }}
|
||||||
|
>
|
||||||
|
<Avatar src={member.avatar} w="24px" />
|
||||||
|
<Box w="full" fontSize="lg">
|
||||||
|
{member.memberName}
|
||||||
|
</Box>
|
||||||
|
<CloseButton
|
||||||
|
onClick={() =>
|
||||||
|
setSelected([...selected.filter((item) => item.tmbId != member.tmbId)])
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Grid>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter alignItems="flex-end">
|
||||||
|
<Button h={'30px'} isLoading={isLoading} onClick={submit}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddManagerModal;
|
@@ -0,0 +1,103 @@
|
|||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { Box, MenuButton, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
TeamMemberRoleEnum,
|
||||||
|
TeamMemberRoleMap,
|
||||||
|
TeamMemberStatusMap
|
||||||
|
} from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { TeamContext } from '.';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||||
|
|
||||||
|
function MemberTable() {
|
||||||
|
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||||
|
const openRemoveMember = useContextSelector(TeamContext, (v) => v.openRemoveMember);
|
||||||
|
const onRemoveMember = useContextSelector(TeamContext, (v) => v.onRemoveMember);
|
||||||
|
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer overflow={'unset'}>
|
||||||
|
<Table overflow={'unset'}>
|
||||||
|
<Thead bg={'myWhite.400'}>
|
||||||
|
<Tr>
|
||||||
|
<Th>{t('common.Username')}</Th>
|
||||||
|
<Th>{t('user.team.Role')}</Th>
|
||||||
|
<Th>{t('common.Status')}</Th>
|
||||||
|
<Th>{t('common.Action')}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{members.map((item) => (
|
||||||
|
<Tr key={item.userId} overflow={'unset'}>
|
||||||
|
<Td display={'flex'} alignItems={'center'}>
|
||||||
|
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||||
|
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||||
|
{item.memberName}
|
||||||
|
</Box>
|
||||||
|
</Td>
|
||||||
|
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||||
|
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||||
|
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{hasManage(
|
||||||
|
members.find((item) => item.tmbId === userInfo?.team.tmbId)?.permission!
|
||||||
|
) &&
|
||||||
|
item.role !== TeamMemberRoleEnum.owner &&
|
||||||
|
item.tmbId !== userInfo?.team.tmbId && (
|
||||||
|
<MyMenu
|
||||||
|
width={20}
|
||||||
|
trigger="hover"
|
||||||
|
Button={
|
||||||
|
<MenuButton
|
||||||
|
_hover={{
|
||||||
|
bg: 'myWhite.600'
|
||||||
|
}}
|
||||||
|
borderRadius={'md'}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
lineHeight={1}
|
||||||
|
>
|
||||||
|
<MyIcon
|
||||||
|
name={'edit'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
w="14px"
|
||||||
|
_hover={{ color: 'primary.500' }}
|
||||||
|
/>
|
||||||
|
</MenuButton>
|
||||||
|
}
|
||||||
|
menuList={[
|
||||||
|
{
|
||||||
|
label: t('user.team.Remove Member Tip'),
|
||||||
|
onClick: () =>
|
||||||
|
openRemoveMember(
|
||||||
|
() =>
|
||||||
|
onRemoveMember({
|
||||||
|
teamId: item.teamId,
|
||||||
|
memberId: item.tmbId
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
t('user.team.Remove Member Confirm Tip', {
|
||||||
|
username: item.memberName
|
||||||
|
})
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MemberTable;
|
@@ -0,0 +1,123 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Button, Flex, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { TeamContext } from '.';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import AddManagerModal from './AddManager';
|
||||||
|
import { updateMemberPermission } from '@/web/support/user/team/api';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import {
|
||||||
|
constructPermission,
|
||||||
|
hasManage,
|
||||||
|
PermissionList
|
||||||
|
} from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||||
|
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
|
||||||
|
function PermissionManage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||||
|
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isOpenAddManager,
|
||||||
|
onOpen: onOpenAddManager,
|
||||||
|
onClose: onCloseAddManager
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
|
const { mutate: removeManager } = useRequest({
|
||||||
|
mutationFn: async (memberId: string) => {
|
||||||
|
return updateMemberPermission({
|
||||||
|
teamId: userInfo!.team.teamId,
|
||||||
|
permission: constructPermission([PermissionList['Read'], PermissionList['Write']]).value,
|
||||||
|
memberIds: [memberId]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
successToast: 'Success',
|
||||||
|
errorToast: 'Error',
|
||||||
|
onSuccess: () => {
|
||||||
|
refetchMembers();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
refetchMembers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDirection={'column'} flex={'1'} h={['auto', '100%']} bg={'white'}>
|
||||||
|
{isOpenAddManager && (
|
||||||
|
<AddManagerModal onClose={onCloseAddManager} onSuccess={onCloseAddManager} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
mx={'5'}
|
||||||
|
flexDirection={'row'}
|
||||||
|
alignItems={'center'}
|
||||||
|
rowGap={'8'}
|
||||||
|
justifyContent={'space-between'}
|
||||||
|
>
|
||||||
|
<Flex>
|
||||||
|
<Box fontSize={['md', 'lg']} fontWeight={'bold'} alignItems={'center'}>
|
||||||
|
{t('user.team.role.Admin')}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
fontSize={['xs']}
|
||||||
|
color={'myGray.500'}
|
||||||
|
bgColor={'myGray.100'}
|
||||||
|
alignItems={'center'}
|
||||||
|
alignContent={'center'}
|
||||||
|
mx={'6'}
|
||||||
|
px={'3'}
|
||||||
|
borderRadius={'sm'}
|
||||||
|
>
|
||||||
|
可邀请, 删除成员
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
{userInfo?.team.role === 'owner' && (
|
||||||
|
<Button
|
||||||
|
variant={'whitePrimary'}
|
||||||
|
size="sm"
|
||||||
|
borderRadius={'md'}
|
||||||
|
ml={3}
|
||||||
|
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
|
||||||
|
onClick={() => {
|
||||||
|
onOpenAddManager();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加管理员
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex mt="4" mx="4">
|
||||||
|
{members.map((member) => {
|
||||||
|
if (hasManage(member.permission) && member.role !== TeamMemberRoleEnum.owner) {
|
||||||
|
return (
|
||||||
|
<Tag key={member.memberName} mx={'2'} px="4" py="2" bg="myGray.100">
|
||||||
|
<Avatar src={member.avatar} w="20px" />
|
||||||
|
<TagLabel fontSize={'md'} alignItems="center" mr="6" ml="2">
|
||||||
|
{member.memberName}
|
||||||
|
</TagLabel>
|
||||||
|
{userInfo?.team.role === 'owner' && (
|
||||||
|
<MyIcon
|
||||||
|
name="common/trash"
|
||||||
|
w="16px"
|
||||||
|
color="myGray.500"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => {
|
||||||
|
removeManager(member.tmbId);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionManage;
|
@@ -0,0 +1,183 @@
|
|||||||
|
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { TeamContext } from '.';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import RowTabs from '../../../../common/Rowtabs';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||||
|
import MemberTable from './MemberTable';
|
||||||
|
import PermissionManage from './PermissionManage';
|
||||||
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
|
import { hasManage } from '@fastgpt/service/support/permission/resourcePermission/permisson';
|
||||||
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
type TabListType = Pick<React.ComponentProps<typeof RowTabs>, 'list'>['list'];
|
||||||
|
enum TabListEnum {
|
||||||
|
member = 'member',
|
||||||
|
permission = 'permission'
|
||||||
|
}
|
||||||
|
|
||||||
|
function TeamCard() {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { userT } = useI18n();
|
||||||
|
const members = useContextSelector(TeamContext, (v) => v.members);
|
||||||
|
const onOpenInvite = useContextSelector(TeamContext, (v) => v.onOpenInvite);
|
||||||
|
const onOpenTeamTagsAsync = useContextSelector(TeamContext, (v) => v.onOpenTeamTagsAsync);
|
||||||
|
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||||
|
const onLeaveTeam = useContextSelector(TeamContext, (v) => v.onLeaveTeam);
|
||||||
|
|
||||||
|
const { userInfo, teamPlanStatus } = useUserStore();
|
||||||
|
|
||||||
|
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||||
|
content: t('user.team.member.Confirm Leave')
|
||||||
|
});
|
||||||
|
|
||||||
|
const { feConfigs } = useSystemStore();
|
||||||
|
|
||||||
|
const Tablist: TabListType = [
|
||||||
|
{
|
||||||
|
icon: 'support/team/memberLight',
|
||||||
|
label: (
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||||
|
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||||
|
{members.length}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
value: TabListEnum.member
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'support/team/key',
|
||||||
|
label: t('common.Role'),
|
||||||
|
value: TabListEnum.permission
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const [tab, setTab] = useState<string>(Tablist[0].value);
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDirection={'column'}
|
||||||
|
flex={'1'}
|
||||||
|
h={['auto', '100%']}
|
||||||
|
bg={'white'}
|
||||||
|
minH={['50vh', 'auto']}
|
||||||
|
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
px={5}
|
||||||
|
py={4}
|
||||||
|
borderBottom={'1.5px solid'}
|
||||||
|
borderBottomColor={'myGray.100'}
|
||||||
|
mb={3}
|
||||||
|
>
|
||||||
|
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
||||||
|
{userInfo?.team.teamName}
|
||||||
|
</Box>
|
||||||
|
{userInfo?.team.role === TeamMemberRoleEnum.owner && (
|
||||||
|
<MyIcon
|
||||||
|
name="edit"
|
||||||
|
w={'14px'}
|
||||||
|
ml={2}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{
|
||||||
|
color: 'primary.500'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!userInfo?.team) return;
|
||||||
|
setEditTeamData({
|
||||||
|
id: userInfo.team.teamId,
|
||||||
|
name: userInfo.team.teamName,
|
||||||
|
avatar: userInfo.team.avatar
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex px={5} alignItems={'center'} justifyContent={'space-between'}>
|
||||||
|
<RowTabs
|
||||||
|
overflow={'auto'}
|
||||||
|
list={Tablist}
|
||||||
|
value={tab}
|
||||||
|
onChange={(v) => {
|
||||||
|
setTab(v as string);
|
||||||
|
}}
|
||||||
|
></RowTabs>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
{hasManage(
|
||||||
|
members.find((item) => item.tmbId.toString() === userInfo?.team.tmbId.toString())
|
||||||
|
?.permission!
|
||||||
|
) &&
|
||||||
|
tab === 'member' && (
|
||||||
|
<Button
|
||||||
|
variant={'whitePrimary'}
|
||||||
|
size="sm"
|
||||||
|
borderRadius={'md'}
|
||||||
|
ml={3}
|
||||||
|
leftIcon={<MyIcon name="common/inviteLight" w={'14px'} color={'primary.500'} />}
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||||
|
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||||
|
) {
|
||||||
|
toast({
|
||||||
|
status: 'warning',
|
||||||
|
title: t('user.team.Over Max Member Tip', {
|
||||||
|
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onOpenInvite();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('user.team.Invite Member')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{userInfo?.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
||||||
|
<Button
|
||||||
|
variant={'whitePrimary'}
|
||||||
|
size="sm"
|
||||||
|
borderRadius={'md'}
|
||||||
|
ml={3}
|
||||||
|
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
||||||
|
onClick={() => {
|
||||||
|
onOpenTeamTagsAsync();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('user.team.Team Tags Async')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{userInfo?.team.role !== TeamMemberRoleEnum.owner && (
|
||||||
|
<Button
|
||||||
|
variant={'whitePrimary'}
|
||||||
|
size="sm"
|
||||||
|
borderRadius={'md'}
|
||||||
|
ml={3}
|
||||||
|
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
|
||||||
|
onClick={() => {
|
||||||
|
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('user.team.Leave Team')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
||||||
|
{tab === 'member' ? <MemberTable /> : <PermissionManage />}
|
||||||
|
</Box>
|
||||||
|
<ConfirmLeaveTeamModal />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamCard;
|
@@ -0,0 +1,117 @@
|
|||||||
|
import { Box, Button, Flex, IconButton } from '@chakra-ui/react';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import EditModal, { defaultForm } from './EditModal';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { TeamContext } from '.';
|
||||||
|
|
||||||
|
function TeamList() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { userInfo, initUserInfo } = useUserStore();
|
||||||
|
const editTeamData = useContextSelector(TeamContext, (v) => v.editTeamData);
|
||||||
|
const setEditTeamData = useContextSelector(TeamContext, (v) => v.setEditTeamData);
|
||||||
|
const myTeams = useContextSelector(TeamContext, (v) => v.myTeams);
|
||||||
|
const refetchTeam = useContextSelector(TeamContext, (v) => v.refetchTeam);
|
||||||
|
const onSwitchTeam = useContextSelector(TeamContext, (v) => v.onSwitchTeam);
|
||||||
|
// get the list of teams
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDirection={'column'}
|
||||||
|
w={['auto', '270px']}
|
||||||
|
h={['auto', '100%']}
|
||||||
|
pt={3}
|
||||||
|
px={5}
|
||||||
|
mb={[2, 0]}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
py={2}
|
||||||
|
h={'40px'}
|
||||||
|
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
|
||||||
|
>
|
||||||
|
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
|
||||||
|
{t('common.Team')}
|
||||||
|
</Box>
|
||||||
|
{/* if there is no team */}
|
||||||
|
{myTeams.length < 1 && (
|
||||||
|
<IconButton
|
||||||
|
variant={'ghost'}
|
||||||
|
border={'none'}
|
||||||
|
icon={
|
||||||
|
<MyIcon
|
||||||
|
name={'common/addCircleLight'}
|
||||||
|
w={['16px', '18px']}
|
||||||
|
color={'primary.500'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
aria-label={''}
|
||||||
|
onClick={() => setEditTeamData(defaultForm)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
|
||||||
|
{myTeams.map((team) => (
|
||||||
|
<Flex
|
||||||
|
key={team.teamId}
|
||||||
|
alignItems={'center'}
|
||||||
|
mt={3}
|
||||||
|
borderRadius={'md'}
|
||||||
|
p={3}
|
||||||
|
cursor={'default'}
|
||||||
|
gap={3}
|
||||||
|
{...(userInfo?.team?.teamId === team.teamId
|
||||||
|
? {
|
||||||
|
bg: 'primary.200'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
_hover: {
|
||||||
|
bg: 'myGray.100'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||||
|
<Box
|
||||||
|
flex={'1 0 0'}
|
||||||
|
w={0}
|
||||||
|
{...(team.role === TeamMemberRoleEnum.owner
|
||||||
|
? {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
>
|
||||||
|
{team.teamName}
|
||||||
|
</Box>
|
||||||
|
{userInfo?.team?.teamId === team.teamId ? (
|
||||||
|
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
size={'xs'}
|
||||||
|
variant={'whitePrimary'}
|
||||||
|
onClick={() => onSwitchTeam(team.teamId)}
|
||||||
|
>
|
||||||
|
{t('user.team.Check Team')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
{!!editTeamData && (
|
||||||
|
<EditModal
|
||||||
|
defaultData={editTeamData}
|
||||||
|
onClose={() => setEditTeamData(undefined)}
|
||||||
|
onSuccess={() => {
|
||||||
|
refetchTeam();
|
||||||
|
initUserInfo();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamList;
|
@@ -2,66 +2,59 @@ import React, { useMemo, useState } from 'react';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
|
||||||
import {
|
import {
|
||||||
getTeamList,
|
|
||||||
getTeamMembers,
|
getTeamMembers,
|
||||||
putSwitchTeam,
|
|
||||||
putUpdateMember,
|
putUpdateMember,
|
||||||
delRemoveMember,
|
delRemoveMember,
|
||||||
delLeaveTeam
|
getTeamList,
|
||||||
|
delLeaveTeam,
|
||||||
|
putSwitchTeam
|
||||||
} from '@/web/support/user/team/api';
|
} from '@/web/support/user/team/api';
|
||||||
import {
|
import { Box, useDisclosure } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
TableContainer,
|
|
||||||
useDisclosure,
|
|
||||||
MenuButton
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import {
|
|
||||||
TeamMemberRoleEnum,
|
|
||||||
TeamMemberRoleMap,
|
|
||||||
TeamMemberStatusEnum,
|
|
||||||
TeamMemberStatusMap
|
|
||||||
} from '@fastgpt/global/support/user/team/constant';
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { setToken } from '@/web/support/user/auth';
|
|
||||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||||
import { FormDataType, defaultForm } from './EditModal';
|
|
||||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
|
||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { createContext } from 'use-context-selector';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import TeamList from './TeamList';
|
||||||
|
import TeamCard from './TeamCard';
|
||||||
|
import { FormDataType } from './EditModal';
|
||||||
|
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
import { setToken } from '@/web/support/user/auth';
|
||||||
|
|
||||||
const EditModal = dynamic(() => import('./EditModal'));
|
|
||||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||||
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
|
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
|
||||||
|
|
||||||
|
export const TeamContext = createContext<{
|
||||||
|
editTeamData?: FormDataType;
|
||||||
|
setEditTeamData: React.Dispatch<React.SetStateAction<any>>;
|
||||||
|
members: Awaited<ReturnType<typeof getTeamMembers>>;
|
||||||
|
myTeams: Awaited<ReturnType<typeof getTeamList>>;
|
||||||
|
refetchTeam: ReturnType<typeof useQuery>['refetch'];
|
||||||
|
onSwitchTeam: ReturnType<typeof useRequest>['mutate'];
|
||||||
|
refetchMembers: ReturnType<typeof useQuery>['refetch'];
|
||||||
|
openRemoveMember: ReturnType<typeof useConfirm>['openConfirm'];
|
||||||
|
onOpenInvite: ReturnType<typeof useDisclosure>['onOpen'];
|
||||||
|
onOpenTeamTagsAsync: ReturnType<typeof useDisclosure>['onOpen'];
|
||||||
|
onRemoveMember: ReturnType<typeof useRequest>['mutate'];
|
||||||
|
onLeaveTeam: ReturnType<typeof useRequest>['mutate'];
|
||||||
|
}>({} as any);
|
||||||
|
|
||||||
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
const { toast } = useToast();
|
|
||||||
const { teamPlanStatus } = useUserStore();
|
|
||||||
const { feConfigs } = useSystemStore();
|
|
||||||
|
|
||||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
|
||||||
content: t('user.team.member.Confirm Leave')
|
|
||||||
});
|
|
||||||
|
|
||||||
const { userInfo, initUserInfo } = useUserStore();
|
const { userInfo, initUserInfo } = useUserStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: myTeams = [],
|
||||||
|
isFetching: isLoadingTeams,
|
||||||
|
refetch: refetchTeam
|
||||||
|
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||||
|
|
||||||
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
||||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||||
const {
|
const {
|
||||||
@@ -70,25 +63,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
onClose: onCloseTeamTagsAsync
|
onClose: onCloseTeamTagsAsync
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const {
|
|
||||||
data: myTeams = [],
|
|
||||||
isFetching: isLoadingTeams,
|
|
||||||
refetch: refetchTeam
|
|
||||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
|
||||||
const defaultTeam = useMemo(
|
|
||||||
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
|
||||||
[myTeams]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
|
||||||
mutationFn: async (teamId: string) => {
|
|
||||||
const token = await putSwitchTeam(teamId);
|
|
||||||
token && setToken(token);
|
|
||||||
return initUserInfo();
|
|
||||||
},
|
|
||||||
errorToast: t('user.team.Switch Team Failed')
|
|
||||||
});
|
|
||||||
|
|
||||||
// member action
|
// member action
|
||||||
const { data: members = [], refetch: refetchMembers } = useQuery(
|
const { data: members = [], refetch: refetchMembers } = useQuery(
|
||||||
['getMembers', userInfo?.team?.teamId],
|
['getMembers', userInfo?.team?.teamId],
|
||||||
@@ -104,6 +78,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
refetchMembers();
|
refetchMembers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
|
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
|
||||||
mutationFn: delRemoveMember,
|
mutationFn: delRemoveMember,
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
@@ -112,13 +87,27 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
successToast: t('user.team.Remove Member Success'),
|
successToast: t('user.team.Remove Member Success'),
|
||||||
errorToast: t('user.team.Remove Member Failed')
|
errorToast: t('user.team.Remove Member Failed')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultTeam = useMemo(
|
||||||
|
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
||||||
|
[myTeams]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
||||||
|
mutationFn: async (teamId: string) => {
|
||||||
|
const token = await putSwitchTeam(teamId);
|
||||||
|
token && setToken(token);
|
||||||
|
return initUserInfo();
|
||||||
|
},
|
||||||
|
errorToast: t('user.team.Switch Team Failed')
|
||||||
|
});
|
||||||
|
|
||||||
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
||||||
mutationFn: async (teamId?: string) => {
|
mutationFn: async (teamId?: string) => {
|
||||||
if (!teamId) return;
|
if (!teamId) return;
|
||||||
// change to personal team
|
// change to personal team
|
||||||
// get members
|
// get members
|
||||||
await onSwitchTeam(defaultTeam.teamId);
|
onSwitchTeam(defaultTeam.teamId);
|
||||||
|
|
||||||
return delLeaveTeam(teamId);
|
return delLeaveTeam(teamId);
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
@@ -128,7 +117,22 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return !!userInfo?.team ? (
|
return !!userInfo?.team ? (
|
||||||
<>
|
<TeamContext.Provider
|
||||||
|
value={{
|
||||||
|
myTeams: myTeams,
|
||||||
|
refetchTeam: refetchTeam,
|
||||||
|
onSwitchTeam: onSwitchTeam,
|
||||||
|
members: members,
|
||||||
|
refetchMembers: refetchMembers,
|
||||||
|
openRemoveMember: openRemoveMember,
|
||||||
|
onOpenInvite: onOpenInvite,
|
||||||
|
onOpenTeamTagsAsync: onOpenTeamTagsAsync,
|
||||||
|
onRemoveMember: onRemoveMember,
|
||||||
|
editTeamData: editTeamData,
|
||||||
|
setEditTeamData: setEditTeamData,
|
||||||
|
onLeaveTeam: onLeaveTeam
|
||||||
|
}}
|
||||||
|
>
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
@@ -140,323 +144,20 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
overflow={'hidden'}
|
overflow={'hidden'}
|
||||||
>
|
>
|
||||||
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
|
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
|
||||||
{/* teams */}
|
<TeamList />
|
||||||
<Flex
|
<TeamCard />
|
||||||
flexDirection={'column'}
|
|
||||||
w={['auto', '270px']}
|
|
||||||
h={['auto', '100%']}
|
|
||||||
pt={3}
|
|
||||||
px={5}
|
|
||||||
mb={[2, 0]}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
alignItems={'center'}
|
|
||||||
py={2}
|
|
||||||
h={'40px'}
|
|
||||||
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
|
|
||||||
>
|
|
||||||
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
|
|
||||||
{t('common.Team')}
|
|
||||||
</Box>
|
|
||||||
{myTeams.length < 1 && (
|
|
||||||
<IconButton
|
|
||||||
variant={'ghost'}
|
|
||||||
border={'none'}
|
|
||||||
icon={
|
|
||||||
<MyIcon
|
|
||||||
name={'common/addCircleLight'}
|
|
||||||
w={['16px', '18px']}
|
|
||||||
color={'primary.500'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
aria-label={''}
|
|
||||||
onClick={() => setEditTeamData(defaultForm)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
|
|
||||||
{myTeams.map((team) => (
|
|
||||||
<Flex
|
|
||||||
key={team.teamId}
|
|
||||||
alignItems={'center'}
|
|
||||||
mt={3}
|
|
||||||
borderRadius={'md'}
|
|
||||||
p={3}
|
|
||||||
cursor={'default'}
|
|
||||||
gap={3}
|
|
||||||
{...(userInfo?.team?.teamId === team.teamId
|
|
||||||
? {
|
|
||||||
bg: 'primary.200'
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
_hover: {
|
|
||||||
bg: 'myGray.100'
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
|
||||||
<Box
|
|
||||||
flex={'1 0 0'}
|
|
||||||
w={0}
|
|
||||||
{...(team.role === TeamMemberRoleEnum.owner
|
|
||||||
? {
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
>
|
|
||||||
{team.teamName}
|
|
||||||
</Box>
|
|
||||||
{userInfo?.team?.teamId === team.teamId ? (
|
|
||||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
size={'xs'}
|
|
||||||
variant={'whitePrimary'}
|
|
||||||
onClick={() => onSwitchTeam(team.teamId)}
|
|
||||||
>
|
|
||||||
{t('user.team.Check Team')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
{/* team card */}
|
|
||||||
<Flex
|
|
||||||
flexDirection={'column'}
|
|
||||||
flex={'1'}
|
|
||||||
h={['auto', '100%']}
|
|
||||||
bg={'white'}
|
|
||||||
minH={['50vh', 'auto']}
|
|
||||||
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
alignItems={'center'}
|
|
||||||
px={5}
|
|
||||||
py={4}
|
|
||||||
borderBottom={'1.5px solid'}
|
|
||||||
borderBottomColor={'myGray.100'}
|
|
||||||
mb={3}
|
|
||||||
>
|
|
||||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
|
||||||
{userInfo.team.teamName}
|
|
||||||
</Box>
|
|
||||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
|
||||||
<MyIcon
|
|
||||||
name="edit"
|
|
||||||
w={'14px'}
|
|
||||||
ml={2}
|
|
||||||
cursor={'pointer'}
|
|
||||||
_hover={{
|
|
||||||
color: 'primary.500'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (!userInfo?.team) return;
|
|
||||||
setEditTeamData({
|
|
||||||
id: userInfo.team.teamId,
|
|
||||||
name: userInfo.team.teamName,
|
|
||||||
avatar: userInfo.team.avatar
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Flex px={5} alignItems={'center'}>
|
|
||||||
<MyIcon name="support/team/memberLight" w={'14px'} />
|
|
||||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
|
||||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
|
||||||
{members.length}
|
|
||||||
</Box>
|
|
||||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
|
||||||
<Button
|
|
||||||
variant={'whitePrimary'}
|
|
||||||
size="sm"
|
|
||||||
borderRadius={'md'}
|
|
||||||
ml={3}
|
|
||||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
|
|
||||||
onClick={() => {
|
|
||||||
if (
|
|
||||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
|
||||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
status: 'warning',
|
|
||||||
title: t('user.team.Over Max Member Tip', {
|
|
||||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onOpenInvite();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('user.team.Invite Member')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{userInfo.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
|
||||||
<Button
|
|
||||||
variant={'whitePrimary'}
|
|
||||||
size="sm"
|
|
||||||
borderRadius={'md'}
|
|
||||||
ml={3}
|
|
||||||
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
|
||||||
onClick={() => {
|
|
||||||
onOpenTeamTagsAsync();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('user.team.Team Tags Async')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Box flex={1} />
|
|
||||||
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
|
|
||||||
<Button
|
|
||||||
variant={'whitePrimary'}
|
|
||||||
size="sm"
|
|
||||||
borderRadius={'md'}
|
|
||||||
ml={3}
|
|
||||||
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
|
|
||||||
onClick={() => {
|
|
||||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('user.team.Leave Team')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
|
||||||
<TableContainer overflow={'unset'}>
|
|
||||||
<Table overflow={'unset'}>
|
|
||||||
<Thead bg={'myWhite.400'}>
|
|
||||||
<Tr>
|
|
||||||
<Th>{t('common.Username')}</Th>
|
|
||||||
<Th>{t('user.team.Role')}</Th>
|
|
||||||
<Th>{t('common.Status')}</Th>
|
|
||||||
<Th></Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{members.map((item) => (
|
|
||||||
<Tr key={item.userId} overflow={'unset'}>
|
|
||||||
<Td display={'flex'} alignItems={'center'}>
|
|
||||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
|
||||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
|
||||||
{item.memberName}
|
|
||||||
</Box>
|
|
||||||
</Td>
|
|
||||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
|
||||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
|
||||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner &&
|
|
||||||
item.role !== TeamMemberRoleEnum.owner && (
|
|
||||||
<MyMenu
|
|
||||||
width={20}
|
|
||||||
trigger="click"
|
|
||||||
Button={
|
|
||||||
<MenuButton
|
|
||||||
_hover={{
|
|
||||||
bg: 'myWhite.600'
|
|
||||||
}}
|
|
||||||
borderRadius={'md'}
|
|
||||||
px={2}
|
|
||||||
py={1}
|
|
||||||
lineHeight={1}
|
|
||||||
>
|
|
||||||
<MyIcon
|
|
||||||
name={'edit'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
w="14px"
|
|
||||||
_hover={{ color: 'primary.500' }}
|
|
||||||
/>
|
|
||||||
</MenuButton>
|
|
||||||
}
|
|
||||||
menuList={[
|
|
||||||
{
|
|
||||||
isActive: item.role === TeamMemberRoleEnum.visitor,
|
|
||||||
label: t('user.team.Invite Role Visitor Tip'),
|
|
||||||
onClick: () => {
|
|
||||||
onUpdateMember({
|
|
||||||
teamId: item.teamId,
|
|
||||||
memberId: item.tmbId,
|
|
||||||
role: TeamMemberRoleEnum.visitor
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isActive: item.role === TeamMemberRoleEnum.admin,
|
|
||||||
label: t('user.team.Invite Role Admin Tip'),
|
|
||||||
onClick: () => {
|
|
||||||
onUpdateMember({
|
|
||||||
teamId: item.teamId,
|
|
||||||
memberId: item.tmbId,
|
|
||||||
role: TeamMemberRoleEnum.admin
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...(item.status === TeamMemberStatusEnum.reject
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: t('user.team.Reinvite'),
|
|
||||||
onClick: () => {
|
|
||||||
onUpdateMember({
|
|
||||||
teamId: item.teamId,
|
|
||||||
memberId: item.tmbId,
|
|
||||||
status: TeamMemberStatusEnum.waiting
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
label: t('user.team.Remove Member Tip'),
|
|
||||||
onClick: () =>
|
|
||||||
openRemoveMember(
|
|
||||||
() =>
|
|
||||||
onRemoveMember({
|
|
||||||
teamId: item.teamId,
|
|
||||||
memberId: item.tmbId
|
|
||||||
}),
|
|
||||||
undefined,
|
|
||||||
t('user.team.Remove Member Confirm Tip', {
|
|
||||||
username: item.memberName
|
|
||||||
})
|
|
||||||
)()
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Loading
|
<Loading
|
||||||
loading={
|
loading={
|
||||||
isSwitchTeam ||
|
|
||||||
isLoadingTeams ||
|
|
||||||
isLoadingUpdateMember ||
|
isLoadingUpdateMember ||
|
||||||
isLoadingRemoveMember ||
|
isLoadingRemoveMember ||
|
||||||
isLoadingLeaveTeam
|
isLoadingTeams ||
|
||||||
|
isLoadingLeaveTeam ||
|
||||||
|
isSwitchTeam
|
||||||
}
|
}
|
||||||
fixed={false}
|
fixed={false}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</MyModal>
|
</MyModal>
|
||||||
{!!editTeamData && (
|
|
||||||
<EditModal
|
|
||||||
defaultData={editTeamData}
|
|
||||||
onClose={() => setEditTeamData(undefined)}
|
|
||||||
onSuccess={() => {
|
|
||||||
refetchTeam();
|
|
||||||
initUserInfo();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOpenInvite && userInfo?.team?.teamId && (
|
{isOpenInvite && userInfo?.team?.teamId && (
|
||||||
<InviteModal
|
<InviteModal
|
||||||
teamId={userInfo.team.teamId}
|
teamId={userInfo.team.teamId}
|
||||||
@@ -466,8 +167,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
)}
|
)}
|
||||||
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
|
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
|
||||||
<ConfirmRemoveMemberModal />
|
<ConfirmRemoveMemberModal />
|
||||||
<ConfirmLeaveTeamModal />
|
</TeamContext.Provider>
|
||||||
</>
|
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -190,7 +190,7 @@ export async function getServerSideProps(content: any) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
currentTab: content?.query?.currentTab || TabEnum.info,
|
currentTab: content?.query?.currentTab || TabEnum.info,
|
||||||
...(await serviceSideProps(content, ['publish']))
|
...(await serviceSideProps(content, ['publish', 'user']))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
2
projects/app/src/types/i18n.d.ts
vendored
2
projects/app/src/types/i18n.d.ts
vendored
@@ -5,6 +5,7 @@ import app from '../../i18n/zh/app.json';
|
|||||||
import file from '../../i18n/zh/file.json';
|
import file from '../../i18n/zh/file.json';
|
||||||
import publish from '../../i18n/zh/publish.json';
|
import publish from '../../i18n/zh/publish.json';
|
||||||
import workflow from '../../i18n/zh/workflow.json';
|
import workflow from '../../i18n/zh/workflow.json';
|
||||||
|
import user from '../../i18n/zh/user.json';
|
||||||
|
|
||||||
export interface I18nNamespaces {
|
export interface I18nNamespaces {
|
||||||
common: typeof common;
|
common: typeof common;
|
||||||
@@ -13,6 +14,7 @@ export interface I18nNamespaces {
|
|||||||
file: typeof file;
|
file: typeof file;
|
||||||
publish: typeof publish;
|
publish: typeof publish;
|
||||||
workflow: typeof workflow;
|
workflow: typeof workflow;
|
||||||
|
user: typeof user;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type I18nNsType = (keyof I18nNamespaces)[];
|
export type I18nNsType = (keyof I18nNamespaces)[];
|
||||||
|
@@ -9,6 +9,7 @@ type I18nContextType = {
|
|||||||
fileT: TFunction<['file'], undefined>;
|
fileT: TFunction<['file'], undefined>;
|
||||||
publishT: TFunction<['publish'], undefined>;
|
publishT: TFunction<['publish'], undefined>;
|
||||||
workflowT: TFunction<['workflow'], undefined>;
|
workflowT: TFunction<['workflow'], undefined>;
|
||||||
|
userT: TFunction<['user'], undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const I18nContext = createContext<I18nContextType>({
|
export const I18nContext = createContext<I18nContextType>({
|
||||||
@@ -23,6 +24,7 @@ const I18nContextProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const { t: fileT } = useTranslation('file');
|
const { t: fileT } = useTranslation('file');
|
||||||
const { t: publishT } = useTranslation('publish');
|
const { t: publishT } = useTranslation('publish');
|
||||||
const { t: workflowT } = useTranslation('workflow');
|
const { t: workflowT } = useTranslation('workflow');
|
||||||
|
const { t: userT } = useTranslation('user');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nContext.Provider
|
<I18nContext.Provider
|
||||||
@@ -32,7 +34,8 @@ const I18nContextProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
datasetT,
|
datasetT,
|
||||||
fileT,
|
fileT,
|
||||||
publishT,
|
publishT,
|
||||||
workflowT
|
workflowT,
|
||||||
|
userT
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@@ -5,6 +5,7 @@ import {
|
|||||||
InviteMemberProps,
|
InviteMemberProps,
|
||||||
InviteMemberResponse,
|
InviteMemberResponse,
|
||||||
UpdateInviteProps,
|
UpdateInviteProps,
|
||||||
|
UpdateTeamMemberPermissionProps,
|
||||||
UpdateTeamMemberProps,
|
UpdateTeamMemberProps,
|
||||||
UpdateTeamProps
|
UpdateTeamProps
|
||||||
} from '@fastgpt/global/support/user/team/controller.d';
|
} from '@fastgpt/global/support/user/team/controller.d';
|
||||||
@@ -41,6 +42,8 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
|
|||||||
PUT('/proApi/support/user/team/member/updateInvite', data);
|
PUT('/proApi/support/user/team/member/updateInvite', data);
|
||||||
export const delLeaveTeam = (teamId: string) =>
|
export const delLeaveTeam = (teamId: string) =>
|
||||||
DELETE('/proApi/support/user/team/member/leave', { teamId });
|
DELETE('/proApi/support/user/team/member/leave', { teamId });
|
||||||
|
export const updateMemberPermission = (data: UpdateTeamMemberPermissionProps) =>
|
||||||
|
PUT('/proApi/support/user/team/member/updatePermission', data);
|
||||||
|
|
||||||
/* --------------- team tags ---------------- */
|
/* --------------- team tags ---------------- */
|
||||||
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
|
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
|
||||||
|
Reference in New Issue
Block a user