mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-14 07:00:47 +00:00
refactor: permission role & app read chat log permission (#5416)
* refactor: permission role * refactor: permission type * fix: permission manage * fix: group owner cannot be deleted * chore: common per map * chore: openapi * chore: rename * fix: type error * chore: app chat log permission * chore: add initv4112
This commit is contained in:
@@ -1,20 +1,56 @@
|
||||
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
|
||||
import { type PermissionListType } from '../type';
|
||||
import {
|
||||
NullRoleVal,
|
||||
CommonPerKeyEnum,
|
||||
CommonRoleList,
|
||||
CommonPerList,
|
||||
CommonRolePerMap
|
||||
} from '../constant';
|
||||
import type { PermissionListType, PermissionValueType, RolePerMapType } from '../type';
|
||||
import { type RoleListType } from '../type';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
export enum AppPermissionKeyEnum {}
|
||||
export const AppPermissionList: PermissionListType = {
|
||||
[PermissionKeyEnum.read]: {
|
||||
...PermissionList[PermissionKeyEnum.read],
|
||||
import { sumPer } from '../utils';
|
||||
|
||||
export enum AppPermissionKeyEnum {
|
||||
ReadChatLog = 'readChatLog'
|
||||
}
|
||||
|
||||
export const AppPerList: PermissionListType<AppPermissionKeyEnum> = {
|
||||
...CommonPerList,
|
||||
readChatLog: 0b1000
|
||||
};
|
||||
|
||||
export const AppRoleList: RoleListType<AppPermissionKeyEnum> = {
|
||||
[CommonPerKeyEnum.read]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.read],
|
||||
description: i18nT('app:permission.des.read')
|
||||
},
|
||||
[PermissionKeyEnum.write]: {
|
||||
...PermissionList[PermissionKeyEnum.write],
|
||||
[CommonPerKeyEnum.write]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.write],
|
||||
description: i18nT('app:permission.des.write')
|
||||
},
|
||||
[PermissionKeyEnum.manage]: {
|
||||
...PermissionList[PermissionKeyEnum.manage],
|
||||
[CommonPerKeyEnum.manage]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.manage],
|
||||
description: i18nT('app:permission.des.manage')
|
||||
},
|
||||
[AppPermissionKeyEnum.ReadChatLog]: {
|
||||
value: 0b1000,
|
||||
checkBoxType: 'multiple',
|
||||
name: i18nT('app:permission.name.readChatLog'),
|
||||
description: i18nT('app:permission.des.readChatLog')
|
||||
}
|
||||
};
|
||||
|
||||
export const AppDefaultPermissionVal = NullPermission;
|
||||
export const AppRolePerMap: RolePerMapType = new Map([
|
||||
...CommonRolePerMap,
|
||||
[
|
||||
AppRoleList[AppPermissionKeyEnum.ReadChatLog].value,
|
||||
sumPer(
|
||||
CommonPerList[CommonPerKeyEnum.read],
|
||||
AppPerList[AppPermissionKeyEnum.ReadChatLog]
|
||||
) as PermissionValueType
|
||||
]
|
||||
]);
|
||||
|
||||
export const AppDefaultRoleVal = NullRoleVal;
|
||||
export const AppReadChatLogPerVal = AppPerList[AppPermissionKeyEnum.ReadChatLog];
|
||||
export const AppReadChatLogRoleVal = AppRoleList[AppPermissionKeyEnum.ReadChatLog].value;
|
||||
|
@@ -1,15 +1,25 @@
|
||||
import { type PerConstructPros, Permission } from '../controller';
|
||||
import { AppDefaultPermissionVal } from './constant';
|
||||
import { AppDefaultRoleVal, AppPerList, AppRoleList, AppRolePerMap } from './constant';
|
||||
|
||||
export class AppPermission extends Permission {
|
||||
hasReadChatLogPer: boolean = false;
|
||||
hasReadChatLogRole: boolean = false;
|
||||
constructor(props?: PerConstructPros) {
|
||||
if (!props) {
|
||||
props = {
|
||||
per: AppDefaultPermissionVal
|
||||
role: AppDefaultRoleVal
|
||||
};
|
||||
} else if (!props?.per) {
|
||||
props.per = AppDefaultPermissionVal;
|
||||
} else if (!props?.role) {
|
||||
props.role = AppDefaultRoleVal;
|
||||
}
|
||||
props.roleList = AppRoleList;
|
||||
props.rolePerMap = AppRolePerMap;
|
||||
props.perList = AppPerList;
|
||||
super(props);
|
||||
|
||||
this.setUpdatePermissionCallback(() => {
|
||||
this.hasReadChatLogPer = this.checkPer(AppPerList.readChatLog);
|
||||
this.hasReadChatLogRole = this.checkRole(AppRoleList.readChatLog.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { type PermissionListType } from './type';
|
||||
import type { PermissionListType, PermissionValueType, RolePerMapType } from './type';
|
||||
import { type RoleListType } from './type';
|
||||
import { i18nT } from '../../../web/i18n/utils';
|
||||
import { sumPer } from './utils';
|
||||
export enum AuthUserTypeEnum {
|
||||
token = 'token',
|
||||
root = 'root',
|
||||
@@ -15,6 +17,12 @@ export enum PermissionTypeEnum {
|
||||
publicRead = 'publicRead',
|
||||
publicWrite = 'publicWrite'
|
||||
}
|
||||
|
||||
export const NullRoleVal = 0;
|
||||
export const NullPermissionVal = 0;
|
||||
export const OwnerRoleVal = ~0 >>> 0;
|
||||
export const OwnerPermissionVal = ~0 >>> 0;
|
||||
|
||||
export const PermissionTypeMap = {
|
||||
[PermissionTypeEnum.private]: {
|
||||
iconLight: 'support/permission/privateLight',
|
||||
@@ -45,34 +53,63 @@ export enum PerResourceTypeEnum {
|
||||
}
|
||||
|
||||
/* new permission */
|
||||
export enum PermissionKeyEnum {
|
||||
export enum CommonPerKeyEnum {
|
||||
owner = 'owner',
|
||||
read = 'read',
|
||||
write = 'write',
|
||||
manage = 'manage'
|
||||
}
|
||||
export const PermissionList: PermissionListType = {
|
||||
[PermissionKeyEnum.read]: {
|
||||
|
||||
export enum CommonRoleKeyEnum {
|
||||
read = 'read',
|
||||
write = 'write',
|
||||
manage = 'manage'
|
||||
}
|
||||
|
||||
export const CommonPerList: PermissionListType = {
|
||||
[CommonPerKeyEnum.owner]: OwnerRoleVal,
|
||||
[CommonPerKeyEnum.read]: 0b100,
|
||||
[CommonPerKeyEnum.write]: 0b010,
|
||||
[CommonPerKeyEnum.manage]: 0b001
|
||||
} as const;
|
||||
|
||||
export const CommonRoleList: RoleListType = {
|
||||
[CommonRoleKeyEnum.read]: {
|
||||
name: i18nT('common:permission.read'),
|
||||
description: '',
|
||||
value: 0b100,
|
||||
checkBoxType: 'single'
|
||||
},
|
||||
[PermissionKeyEnum.write]: {
|
||||
[CommonRoleKeyEnum.write]: {
|
||||
name: i18nT('common:permission.write'),
|
||||
description: '',
|
||||
value: 0b110,
|
||||
value: 0b010,
|
||||
checkBoxType: 'single'
|
||||
},
|
||||
[PermissionKeyEnum.manage]: {
|
||||
[CommonRoleKeyEnum.manage]: {
|
||||
name: i18nT('common:permission.manager'),
|
||||
description: '',
|
||||
value: 0b111,
|
||||
value: 0b001,
|
||||
checkBoxType: 'single'
|
||||
}
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const NullPermission = 0;
|
||||
export const OwnerPermissionVal = ~0 >>> 0;
|
||||
export const ReadPermissionVal = PermissionList['read'].value;
|
||||
export const WritePermissionVal = PermissionList['write'].value;
|
||||
export const ManagePermissionVal = PermissionList['manage'].value;
|
||||
export const CommonRolePerMap: RolePerMapType = new Map([
|
||||
[CommonRoleList['read'].value, CommonPerList.read],
|
||||
[
|
||||
CommonRoleList['write'].value,
|
||||
sumPer(CommonPerList.write, CommonPerList.read) as PermissionValueType
|
||||
],
|
||||
[
|
||||
CommonRoleList['manage'].value,
|
||||
sumPer(CommonPerList.manage, CommonPerList.write, CommonPerList.read) as PermissionValueType
|
||||
]
|
||||
]);
|
||||
|
||||
export const ReadRoleVal = CommonRoleList['read'].value;
|
||||
export const WriteRoleVal = CommonRoleList['write'].value;
|
||||
export const ManageRoleVal = CommonRoleList['manage'].value;
|
||||
|
||||
export const ManagePermissionVal = CommonPerList.manage;
|
||||
export const ReadPermissionVal = CommonPerList.read;
|
||||
export const WritePermissionVal = CommonPerList.write;
|
||||
|
@@ -1,58 +1,86 @@
|
||||
import { type PermissionListType, type PermissionValueType } from './type';
|
||||
import { PermissionList, NullPermission, OwnerPermissionVal } from './constant';
|
||||
import type {
|
||||
RoleValueType,
|
||||
RoleListType,
|
||||
PermissionValueType,
|
||||
PermissionListType,
|
||||
RolePerMapType
|
||||
} from './type';
|
||||
import {
|
||||
CommonPerList,
|
||||
CommonRoleList,
|
||||
CommonRolePerMap,
|
||||
NullPermissionVal,
|
||||
NullRoleVal,
|
||||
OwnerPermissionVal,
|
||||
OwnerRoleVal
|
||||
} from './constant';
|
||||
|
||||
export type PerConstructPros = {
|
||||
per?: PermissionValueType;
|
||||
role?: RoleValueType;
|
||||
|
||||
isOwner?: boolean;
|
||||
permissionList?: PermissionListType;
|
||||
childUpdatePermissionCallback?: () => void;
|
||||
|
||||
roleList?: RoleListType;
|
||||
perList?: PermissionListType;
|
||||
rolePerMap?: RolePerMapType;
|
||||
};
|
||||
|
||||
// the Permission helper class
|
||||
/**
|
||||
* the Permission helper class
|
||||
*/
|
||||
export class Permission {
|
||||
value: PermissionValueType;
|
||||
role: PermissionValueType;
|
||||
private permission: PermissionValueType = NullRoleVal; // default role
|
||||
|
||||
isOwner: boolean = false;
|
||||
|
||||
hasManagePer: boolean = false;
|
||||
hasWritePer: boolean = false;
|
||||
hasReadPer: boolean = false;
|
||||
_permissionList: PermissionListType;
|
||||
hasManageRole: boolean = false;
|
||||
hasWriteRole: boolean = false;
|
||||
hasReadRole: boolean = false;
|
||||
|
||||
constructor(props?: PerConstructPros) {
|
||||
const { per = NullPermission, isOwner = false, permissionList = PermissionList } = props || {};
|
||||
readonly roleList: RoleListType;
|
||||
readonly perList: PermissionListType;
|
||||
readonly rolePerMap: RolePerMapType;
|
||||
|
||||
constructor({
|
||||
role = NullRoleVal,
|
||||
isOwner = false,
|
||||
roleList = CommonRoleList,
|
||||
perList = CommonPerList,
|
||||
rolePerMap = CommonRolePerMap
|
||||
}: PerConstructPros = {}) {
|
||||
if (isOwner) {
|
||||
this.value = OwnerPermissionVal;
|
||||
this.role = OwnerRoleVal;
|
||||
} else {
|
||||
this.value = per;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
this._permissionList = permissionList;
|
||||
this.roleList = roleList;
|
||||
this.perList = perList;
|
||||
this.rolePerMap = rolePerMap;
|
||||
this.updatePermissions();
|
||||
}
|
||||
|
||||
// add permission(s)
|
||||
// it can be chaining called.
|
||||
// @example
|
||||
// const perm = new Permission(permission)
|
||||
// perm.add(PermissionList['read'])
|
||||
// perm.add(PermissionList['read'], PermissionList['write'])
|
||||
// perm.add(PermissionList['read']).add(PermissionList['write'])
|
||||
addPer(...perList: PermissionValueType[]) {
|
||||
addRole(...roleList: RoleValueType[]) {
|
||||
if (this.isOwner) {
|
||||
return this;
|
||||
}
|
||||
for (const per of perList) {
|
||||
this.value = this.value | per;
|
||||
for (const per of roleList) {
|
||||
this.role = this.role | per;
|
||||
}
|
||||
this.updatePermissions();
|
||||
return this;
|
||||
}
|
||||
|
||||
removePer(...perList: PermissionValueType[]) {
|
||||
removeRole(...roleList: RoleValueType[]) {
|
||||
if (this.isOwner) {
|
||||
return this.value;
|
||||
return this.role;
|
||||
}
|
||||
for (const per of perList) {
|
||||
this.value = this.value & ~per;
|
||||
for (const per of roleList) {
|
||||
this.role = this.role & ~per;
|
||||
}
|
||||
this.updatePermissions();
|
||||
return this;
|
||||
@@ -61,9 +89,16 @@ export class Permission {
|
||||
checkPer(perm: PermissionValueType): boolean {
|
||||
// if the permission is owner permission, only owner has this permission.
|
||||
if (perm === OwnerPermissionVal) {
|
||||
return this.value === OwnerPermissionVal;
|
||||
return this.permission === OwnerPermissionVal;
|
||||
}
|
||||
return (this.value & perm) === perm;
|
||||
return (this.permission & perm) === perm;
|
||||
}
|
||||
|
||||
checkRole(role: RoleValueType): boolean {
|
||||
if (role === OwnerRoleVal) {
|
||||
return this.role === OwnerRoleVal;
|
||||
}
|
||||
return (this.role & role) === role;
|
||||
}
|
||||
|
||||
private updatePermissionCallback?: () => void;
|
||||
@@ -72,15 +107,32 @@ export class Permission {
|
||||
this.updatePermissionCallback = callback;
|
||||
}
|
||||
|
||||
private updatePermissions() {
|
||||
this.isOwner = this.value === OwnerPermissionVal;
|
||||
this.hasManagePer = this.checkPer(this._permissionList['manage'].value);
|
||||
this.hasWritePer = this.checkPer(this._permissionList['write'].value);
|
||||
this.hasReadPer = this.checkPer(this._permissionList['read'].value);
|
||||
this.updatePermissionCallback?.();
|
||||
private calculatePer() {
|
||||
if (this.role === OwnerRoleVal) {
|
||||
this.permission = OwnerPermissionVal;
|
||||
return;
|
||||
}
|
||||
|
||||
let role = this.role;
|
||||
this.permission = 0;
|
||||
while (role > 0) {
|
||||
// Binary Magic
|
||||
this.permission |= this.rolePerMap.get(role & -role) ?? 0;
|
||||
role &= role - 1;
|
||||
}
|
||||
}
|
||||
|
||||
toBinary() {
|
||||
return this.value.toString(2);
|
||||
private updatePermissions() {
|
||||
this.calculatePer();
|
||||
|
||||
this.isOwner = this.permission === OwnerRoleVal;
|
||||
this.hasManagePer = this.checkPer(this.roleList['manage'].value);
|
||||
this.hasWritePer = this.checkPer(this.roleList['write'].value);
|
||||
this.hasReadPer = this.checkPer(this.roleList['read'].value);
|
||||
this.hasManageRole = this.checkRole(this.roleList['manage'].value);
|
||||
this.hasWriteRole = this.checkRole(this.roleList['write'].value);
|
||||
this.hasReadRole = this.checkRole(this.roleList['read'].value);
|
||||
|
||||
this.updatePermissionCallback?.();
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +1,30 @@
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
|
||||
import {
|
||||
NullRoleVal,
|
||||
CommonPerKeyEnum,
|
||||
CommonRoleList,
|
||||
CommonRolePerMap,
|
||||
CommonPerList
|
||||
} from '../constant';
|
||||
import type { RolePerMapType } from '../type';
|
||||
|
||||
export enum DatasetPermissionKeyEnum {}
|
||||
|
||||
export const DatasetPermissionList = {
|
||||
[PermissionKeyEnum.read]: {
|
||||
...PermissionList[PermissionKeyEnum.read],
|
||||
export const DatasetRoleList = {
|
||||
[CommonPerKeyEnum.read]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.read],
|
||||
description: i18nT('dataset:permission.des.read')
|
||||
},
|
||||
[PermissionKeyEnum.write]: {
|
||||
...PermissionList[PermissionKeyEnum.write],
|
||||
[CommonPerKeyEnum.write]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.write],
|
||||
description: i18nT('dataset:permission.des.write')
|
||||
},
|
||||
[PermissionKeyEnum.manage]: {
|
||||
...PermissionList[PermissionKeyEnum.manage],
|
||||
[CommonPerKeyEnum.manage]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.manage],
|
||||
description: i18nT('dataset:permission.des.manage')
|
||||
}
|
||||
};
|
||||
|
||||
export const DatasetDefaultPermissionVal = NullPermission;
|
||||
export const DatasetRolePerMap: RolePerMapType = CommonRolePerMap;
|
||||
|
||||
export const DatasetPerList = CommonPerList;
|
||||
|
||||
export const DataSetDefaultRoleVal = NullRoleVal;
|
||||
|
@@ -1,14 +1,22 @@
|
||||
import { NullPermission } from '../constant';
|
||||
import { type PerConstructPros, Permission } from '../controller';
|
||||
import {
|
||||
DataSetDefaultRoleVal,
|
||||
DatasetPerList,
|
||||
DatasetRoleList,
|
||||
DatasetRolePerMap
|
||||
} from './constant';
|
||||
export class DatasetPermission extends Permission {
|
||||
constructor(props?: PerConstructPros) {
|
||||
if (!props) {
|
||||
props = {
|
||||
per: NullPermission
|
||||
role: DataSetDefaultRoleVal
|
||||
};
|
||||
} else if (!props?.per) {
|
||||
props.per = NullPermission;
|
||||
} else if (!props?.role) {
|
||||
props.role = DataSetDefaultRoleVal;
|
||||
}
|
||||
props.roleList = DatasetRoleList;
|
||||
props.rolePerMap = DatasetRolePerMap;
|
||||
props.perList = DatasetPerList;
|
||||
super(props);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { PermissionKeyEnum, PermissionList } from '../constant';
|
||||
import { type PermissionListType } from '../type';
|
||||
import { CommonPerKeyEnum, CommonRoleList } from '../constant';
|
||||
import { type RoleListType } from '../type';
|
||||
|
||||
export enum GroupMemberRole {
|
||||
owner = 'owner',
|
||||
@@ -7,17 +7,17 @@ export enum GroupMemberRole {
|
||||
member = 'member'
|
||||
}
|
||||
|
||||
export const memberGroupPermissionList: PermissionListType = {
|
||||
[PermissionKeyEnum.read]: {
|
||||
...PermissionList[PermissionKeyEnum.read],
|
||||
export const memberGroupPermissionList: RoleListType = {
|
||||
[CommonPerKeyEnum.read]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.read],
|
||||
value: 0b100
|
||||
},
|
||||
[PermissionKeyEnum.write]: {
|
||||
...PermissionList[PermissionKeyEnum.write],
|
||||
[CommonPerKeyEnum.write]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.write],
|
||||
value: 0b010
|
||||
},
|
||||
[PermissionKeyEnum.manage]: {
|
||||
...PermissionList[PermissionKeyEnum.manage],
|
||||
[CommonPerKeyEnum.manage]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.manage],
|
||||
value: 0b001
|
||||
}
|
||||
};
|
||||
|
56
packages/global/support/permission/type.d.ts
vendored
56
packages/global/support/permission/type.d.ts
vendored
@@ -3,25 +3,63 @@ import type { RequireOnlyOne } from '../../common/type/utils';
|
||||
import type { TeamMemberSchema } from '../user/team/type';
|
||||
import { MemberGroupSchemaType } from './memberGroup/type';
|
||||
import type { TeamMemberWithUserSchema } from '../user/team/type';
|
||||
import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } from './constant';
|
||||
import type { CommonPerKeyEnum, CommonRoleKeyEnum } from './constant';
|
||||
import { AuthUserTypeEnum, type CommonPerKeyEnum, 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.
|
||||
// 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 RoleValueType = number;
|
||||
|
||||
export type ResourceType = `${PerResourceTypeEnum}`;
|
||||
|
||||
export type PermissionListType<T = {}> = Record<
|
||||
T | PermissionKeyEnum,
|
||||
{
|
||||
name: string;
|
||||
description: string;
|
||||
value: PermissionValueType;
|
||||
checkBoxType: 'single' | 'multiple';
|
||||
}
|
||||
/**
|
||||
* Define the roles. Each role is a binary number, only one bit is set to 1.
|
||||
*/
|
||||
export type RoleListType<T = {}> = Readonly<
|
||||
Record<
|
||||
T | CommonRoleKeyEnum,
|
||||
Readonly<{
|
||||
name: string;
|
||||
description: string;
|
||||
value: RoleValueType;
|
||||
checkBoxType: 'single' | 'multiple';
|
||||
}>
|
||||
>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Define the permissions. Each permission is a binary number, only one bit is set to 1.
|
||||
* @example
|
||||
* CommonPerList = {
|
||||
* read: 0b100,
|
||||
* write: 0b010,
|
||||
* manage: 0b001
|
||||
* }
|
||||
* @example_bad
|
||||
* CommonPerList = {
|
||||
* write: 0b110, // bad, should be 0b010
|
||||
* }
|
||||
*/
|
||||
export type PermissionListType<T = {}> = Readonly<
|
||||
Record<T | CommonPerKeyEnum, PermissionValueType>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Define the role-permission map. Each role has a permission.
|
||||
* @key: role (binary number), only one bit is set to 1.
|
||||
* @value: permission (binary number), multiple bits are set to 1.
|
||||
* @example
|
||||
* CommonRolePerMap = {
|
||||
* 0b100: 0b100,
|
||||
* 0b010: 0b110,
|
||||
* 0b001: 0b111
|
||||
* }
|
||||
*/
|
||||
export type RolePerMapType = Readonly<Map<RoleValueType, PermissionValueType>>;
|
||||
|
||||
export type ResourcePermissionType = {
|
||||
teamId: string;
|
||||
resourceType: ResourceType;
|
||||
|
@@ -1,39 +1,59 @@
|
||||
import { PermissionKeyEnum } from '../constant';
|
||||
import { type PermissionListType } from '../type';
|
||||
import { PermissionList } from '../constant';
|
||||
import { CommonPerKeyEnum, CommonRolePerMap } from '../constant';
|
||||
import type {
|
||||
PermissionListType,
|
||||
PermissionValueType,
|
||||
RoleListType,
|
||||
RolePerMapType
|
||||
} from '../type';
|
||||
import { CommonRoleList, CommonPerList } from '../constant';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
export enum TeamPermissionKeyEnum {
|
||||
import { sumPer } from '../utils';
|
||||
|
||||
export enum TeamPerKeyEnum {
|
||||
appCreate = 'appCreate',
|
||||
datasetCreate = 'datasetCreate',
|
||||
apikeyCreate = 'apikeyCreate'
|
||||
}
|
||||
|
||||
export const TeamPermissionList: PermissionListType<TeamPermissionKeyEnum> = {
|
||||
[PermissionKeyEnum.read]: {
|
||||
...PermissionList[PermissionKeyEnum.read],
|
||||
export enum TeamRoleKeyEnum {
|
||||
appCreate = 'appCreate',
|
||||
datasetCreate = 'datasetCreate',
|
||||
apikeyCreate = 'apikeyCreate'
|
||||
}
|
||||
|
||||
export const TeamPerList: PermissionListType<TeamPerKeyEnum> = {
|
||||
...CommonPerList,
|
||||
apikeyCreate: 0b100000,
|
||||
appCreate: 0b001000,
|
||||
datasetCreate: 0b010000
|
||||
};
|
||||
|
||||
export const TeamRoleList: RoleListType<TeamRoleKeyEnum> = {
|
||||
[CommonPerKeyEnum.read]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.read],
|
||||
value: 0b000100
|
||||
},
|
||||
[PermissionKeyEnum.write]: {
|
||||
...PermissionList[PermissionKeyEnum.write],
|
||||
[CommonPerKeyEnum.write]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.write],
|
||||
value: 0b000010
|
||||
},
|
||||
[PermissionKeyEnum.manage]: {
|
||||
...PermissionList[PermissionKeyEnum.manage],
|
||||
[CommonPerKeyEnum.manage]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.manage],
|
||||
value: 0b000001
|
||||
},
|
||||
[TeamPermissionKeyEnum.appCreate]: {
|
||||
[TeamRoleKeyEnum.appCreate]: {
|
||||
checkBoxType: 'multiple',
|
||||
description: '',
|
||||
name: i18nT('account_team:permission_appCreate'),
|
||||
value: 0b001000
|
||||
},
|
||||
[TeamPermissionKeyEnum.datasetCreate]: {
|
||||
[TeamRoleKeyEnum.datasetCreate]: {
|
||||
checkBoxType: 'multiple',
|
||||
description: '',
|
||||
name: i18nT('account_team:permission_datasetCreate'),
|
||||
value: 0b010000
|
||||
},
|
||||
[TeamPermissionKeyEnum.apikeyCreate]: {
|
||||
[TeamRoleKeyEnum.apikeyCreate]: {
|
||||
checkBoxType: 'multiple',
|
||||
description: '',
|
||||
name: i18nT('account_team:permission_apikeyCreate'),
|
||||
@@ -41,10 +61,38 @@ export const TeamPermissionList: PermissionListType<TeamPermissionKeyEnum> = {
|
||||
}
|
||||
};
|
||||
|
||||
export const TeamReadPermissionVal = TeamPermissionList['read'].value;
|
||||
export const TeamWritePermissionVal = TeamPermissionList['write'].value;
|
||||
export const TeamManagePermissionVal = TeamPermissionList['manage'].value;
|
||||
export const TeamAppCreatePermissionVal = TeamPermissionList['appCreate'].value;
|
||||
export const TeamDatasetCreatePermissionVal = TeamPermissionList['datasetCreate'].value;
|
||||
export const TeamApikeyCreatePermissionVal = TeamPermissionList['apikeyCreate'].value;
|
||||
export const TeamRolePerMap: RolePerMapType = new Map([
|
||||
...CommonRolePerMap,
|
||||
[
|
||||
TeamRoleList['appCreate'].value,
|
||||
sumPer(TeamPerList.appCreate, CommonPerList.read, CommonPerList.write) as PermissionValueType
|
||||
],
|
||||
[
|
||||
TeamRoleList['datasetCreate'].value,
|
||||
sumPer(
|
||||
TeamPerList.datasetCreate,
|
||||
CommonPerList.read,
|
||||
CommonPerList.write
|
||||
) as PermissionValueType
|
||||
],
|
||||
[
|
||||
TeamRoleList['apikeyCreate'].value,
|
||||
sumPer(TeamPerList.apikeyCreate, CommonPerList.read, CommonPerList.write) as PermissionValueType
|
||||
]
|
||||
]);
|
||||
|
||||
export const TeamReadRoleVal = TeamRoleList['read'].value;
|
||||
export const TeamWriteRoleVal = TeamRoleList['write'].value;
|
||||
export const TeamManageRoleVal = TeamRoleList['manage'].value;
|
||||
export const TeamAppCreateRoleVal = TeamRoleList['appCreate'].value;
|
||||
export const TeamDatasetCreateRoleVal = TeamRoleList['datasetCreate'].value;
|
||||
export const TeamApikeyCreateRoleVal = TeamRoleList['apikeyCreate'].value;
|
||||
export const TeamDefaultRoleVal = TeamReadRoleVal;
|
||||
|
||||
export const TeamReadPermissionVal = TeamPerList.read;
|
||||
export const TeamWritePermissionVal = TeamPerList.write;
|
||||
export const TeamManagePermissionVal = TeamPerList.manage;
|
||||
export const TeamAppCreatePermissionVal = TeamPerList.appCreate;
|
||||
export const TeamDatasetCreatePermissionVal = TeamPerList.datasetCreate;
|
||||
export const TeamApikeyCreatePermissionVal = TeamPerList.apikeyCreate;
|
||||
export const TeamDefaultPermissionVal = TeamReadPermissionVal;
|
||||
|
@@ -1,13 +1,18 @@
|
||||
import { type PerConstructPros, Permission } from '../controller';
|
||||
import {
|
||||
TeamApikeyCreatePermissionVal,
|
||||
TeamAppCreatePermissionVal,
|
||||
TeamDatasetCreatePermissionVal,
|
||||
TeamDefaultPermissionVal,
|
||||
TeamPermissionList
|
||||
TeamApikeyCreateRoleVal,
|
||||
TeamAppCreateRoleVal,
|
||||
TeamDatasetCreateRoleVal,
|
||||
TeamDefaultRoleVal,
|
||||
TeamPerList,
|
||||
TeamRoleList,
|
||||
TeamRolePerMap
|
||||
} from './constant';
|
||||
|
||||
export class TeamPermission extends Permission {
|
||||
hasAppCreateRole: boolean = false;
|
||||
hasDatasetCreateRole: boolean = false;
|
||||
hasApikeyCreateRole: boolean = false;
|
||||
hasAppCreatePer: boolean = false;
|
||||
hasDatasetCreatePer: boolean = false;
|
||||
hasApikeyCreatePer: boolean = false;
|
||||
@@ -15,18 +20,23 @@ export class TeamPermission extends Permission {
|
||||
constructor(props?: PerConstructPros) {
|
||||
if (!props) {
|
||||
props = {
|
||||
per: TeamDefaultPermissionVal
|
||||
role: TeamDefaultRoleVal
|
||||
};
|
||||
} else if (!props?.per) {
|
||||
props.per = TeamDefaultPermissionVal;
|
||||
} else if (!props?.role) {
|
||||
props.role = TeamDefaultRoleVal;
|
||||
}
|
||||
props.permissionList = TeamPermissionList;
|
||||
props.roleList = TeamRoleList;
|
||||
props.rolePerMap = TeamRolePerMap;
|
||||
props.perList = TeamPerList;
|
||||
super(props);
|
||||
|
||||
this.setUpdatePermissionCallback(() => {
|
||||
this.hasAppCreatePer = this.checkPer(TeamAppCreatePermissionVal);
|
||||
this.hasDatasetCreatePer = this.checkPer(TeamDatasetCreatePermissionVal);
|
||||
this.hasApikeyCreatePer = this.checkPer(TeamApikeyCreatePermissionVal);
|
||||
this.hasAppCreateRole = this.checkRole(TeamAppCreateRoleVal);
|
||||
this.hasDatasetCreateRole = this.checkRole(TeamDatasetCreateRoleVal);
|
||||
this.hasApikeyCreateRole = this.checkRole(TeamApikeyCreateRoleVal);
|
||||
this.hasAppCreatePer = this.checkPer(TeamAppCreateRoleVal);
|
||||
this.hasDatasetCreatePer = this.checkPer(TeamDatasetCreateRoleVal);
|
||||
this.hasApikeyCreatePer = this.checkPer(TeamApikeyCreateRoleVal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { type PermissionValueType } from './type';
|
||||
import { NullPermission, PermissionTypeEnum } from './constant';
|
||||
import { NullRoleVal, PermissionTypeEnum } from './constant';
|
||||
import type { Permission } from './controller';
|
||||
|
||||
/* team public source, or owner source in team */
|
||||
@@ -30,7 +30,7 @@ export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId:
|
||||
}
|
||||
|
||||
// return permission-related schema to define the schema of resources
|
||||
export function getPermissionSchema(defaultPermission: PermissionValueType = NullPermission) {
|
||||
export function getPermissionSchema(defaultPermission: PermissionValueType = NullRoleVal) {
|
||||
return {
|
||||
defaultPermission: {
|
||||
type: Number,
|
||||
@@ -42,3 +42,11 @@ export function getPermissionSchema(defaultPermission: PermissionValueType = Nul
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const sumPer = (...per: PermissionValueType[]) => {
|
||||
if (per.length === 0) {
|
||||
// prevent sum 0 value, to fallback to default value
|
||||
return undefined;
|
||||
}
|
||||
return per.reduce((acc, cur) => acc | cur, 0);
|
||||
};
|
||||
|
@@ -16,17 +16,17 @@ export const filterDatasetsByTmbId = async ({
|
||||
// First get all permissions
|
||||
const permissions = await Promise.all(
|
||||
datasetIds.map(async (datasetId) => {
|
||||
const per = await getResourcePermission({
|
||||
const role = await getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: datasetId,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
});
|
||||
|
||||
if (per === undefined) return false;
|
||||
if (role === undefined) return false;
|
||||
|
||||
const datasetPer = new DatasetPermission({
|
||||
per,
|
||||
role,
|
||||
isOwner: tmbPer.isOwner
|
||||
});
|
||||
|
||||
|
@@ -12,7 +12,6 @@ import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants';
|
||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
import { splitCombinePluginId } from '@fastgpt/global/core/app/plugin/utils';
|
||||
|
||||
export const authPluginByTmbId = async ({
|
||||
@@ -83,16 +82,16 @@ export const authAppByTmbId = async ({
|
||||
app.inheritPermission === false ||
|
||||
!app.parentId
|
||||
) {
|
||||
// 1. is a folder. (Folders have compeletely permission)
|
||||
// 1. is a folder. (Folders have completely permission)
|
||||
// 2. inheritPermission is false.
|
||||
// 3. is root folder/app.
|
||||
const rp = await getResourcePermission({
|
||||
const role = await getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: appId,
|
||||
resourceType: PerResourceTypeEnum.app
|
||||
});
|
||||
const Per = new AppPermission({ per: rp ?? AppDefaultPermissionVal, isOwner });
|
||||
const Per = new AppPermission({ role, isOwner });
|
||||
return {
|
||||
Per
|
||||
};
|
||||
@@ -105,7 +104,7 @@ export const authAppByTmbId = async ({
|
||||
});
|
||||
|
||||
const Per = new AppPermission({
|
||||
per: parent.permission.value,
|
||||
role: parent.permission.role,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
|
@@ -4,7 +4,7 @@ import { parseHeaderCert } from '../controller';
|
||||
import { getFileById } from '../../../common/file/gridfs/controller';
|
||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { OwnerPermissionVal, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { OwnerPermissionVal, ReadRoleVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
export const authCollectionFile = async ({
|
||||
@@ -32,7 +32,7 @@ export const authCollectionFile = async ({
|
||||
}
|
||||
|
||||
const permission = new Permission({
|
||||
per: ReadPermissionVal,
|
||||
role: ReadRoleVal,
|
||||
isOwner: file.metadata?.uid === tmbId || file.metadata?.tmbId === tmbId
|
||||
});
|
||||
|
||||
|
@@ -6,7 +6,6 @@ import { MongoOpenApi } from '../../openapi/schema';
|
||||
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
export async function authOpenApiKeyCrud({
|
||||
id,
|
||||
@@ -49,9 +48,7 @@ export async function authOpenApiKeyCrud({
|
||||
|
||||
return {
|
||||
openapi,
|
||||
permission: new Permission({
|
||||
per
|
||||
})
|
||||
permission: tmbPer
|
||||
};
|
||||
})();
|
||||
|
||||
|
@@ -21,6 +21,7 @@ import { type TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { type OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
|
||||
import { authUserSession } from '../user/session';
|
||||
import { sumPer } from '@fastgpt/global/support/permission/utils';
|
||||
|
||||
/** get resource permission for a team member
|
||||
* If there is no permission for the team member, it will return undefined
|
||||
@@ -102,7 +103,7 @@ export const getResourcePermission = async ({
|
||||
.then((perList) => perList.map((item) => item.permission))
|
||||
]);
|
||||
|
||||
return concatPer([...groupPers, ...orgPers]);
|
||||
return sumPer(...groupPers, ...orgPers);
|
||||
};
|
||||
|
||||
export async function getResourceClbsAndGroups({
|
||||
@@ -402,11 +403,3 @@ export const authFileToken = (token?: string) =>
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export const concatPer = (perList: PermissionValueType[] = []) => {
|
||||
if (perList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Permission().addPer(...perList).value;
|
||||
};
|
||||
|
@@ -7,7 +7,10 @@ import {
|
||||
} from '@fastgpt/global/core/dataset/type';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { MongoDataset } from '../../../core/dataset/schema';
|
||||
import { NullPermission, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import {
|
||||
NullPermissionVal,
|
||||
PerResourceTypeEnum
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller';
|
||||
import { getCollectionWithDataset } from '../../../core/dataset/controller';
|
||||
@@ -15,7 +18,7 @@ import { MongoDatasetData } from '../../../core/dataset/data/schema';
|
||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { DataSetDefaultRoleVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { getDatasetImagePreviewUrl } from '../../../core/dataset/image/utils';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
|
||||
@@ -71,7 +74,7 @@ export const authDatasetByTmbId = async ({
|
||||
dataset.inheritPermission === false ||
|
||||
!dataset.parentId
|
||||
) {
|
||||
// 1. is a folder. (Folders have compeletely permission)
|
||||
// 1. is a folder. (Folders have completely permission)
|
||||
// 2. inheritPermission is false.
|
||||
// 3. is root folder/dataset.
|
||||
const rp = await getResourcePermission({
|
||||
@@ -81,7 +84,7 @@ export const authDatasetByTmbId = async ({
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
});
|
||||
const Per = new DatasetPermission({
|
||||
per: rp ?? DatasetDefaultPermissionVal,
|
||||
role: rp,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
@@ -97,7 +100,7 @@ export const authDatasetByTmbId = async ({
|
||||
});
|
||||
|
||||
const Per = new DatasetPermission({
|
||||
per: parent.permission.value,
|
||||
role: parent.permission.role,
|
||||
isOwner
|
||||
});
|
||||
|
||||
@@ -158,7 +161,7 @@ export const authDataset = async ({
|
||||
// the temporary solution for authDatasetCollection is getting the
|
||||
export async function authDatasetCollection({
|
||||
collectionId,
|
||||
per = NullPermission,
|
||||
per = NullPermissionVal,
|
||||
isRoot = false,
|
||||
...props
|
||||
}: AuthModeType & {
|
||||
@@ -242,7 +245,7 @@ export async function authDatasetCollection({
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
/*
|
||||
DatasetData permission is inherited from collection.
|
||||
*/
|
||||
export async function authDatasetData({
|
||||
|
@@ -3,7 +3,7 @@ import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { NullPermission } from '@fastgpt/global/support/permission/constant';
|
||||
import { NullPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { authCert } from '../auth/common';
|
||||
import { MongoUser } from '../../user/schema';
|
||||
@@ -28,7 +28,7 @@ export async function authUserPer(props: AuthModeType): Promise<
|
||||
tmb
|
||||
};
|
||||
}
|
||||
if (!tmb.permission.checkPer(props.per ?? NullPermission)) {
|
||||
if (!tmb.permission.checkPer(props.per ?? NullPermissionVal)) {
|
||||
return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import { type UpdateTeamProps } from '@fastgpt/global/support/user/team/controll
|
||||
import { getResourcePermission } from '../../permission/controller';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { TeamDefaultRoleVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupSchema';
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
@@ -25,11 +25,12 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
|
||||
return Promise.reject('member not exist');
|
||||
}
|
||||
|
||||
const Per = await getResourcePermission({
|
||||
resourceType: PerResourceTypeEnum.team,
|
||||
teamId: tmb.teamId,
|
||||
tmbId: tmb._id
|
||||
});
|
||||
const role =
|
||||
(await getResourcePermission({
|
||||
resourceType: PerResourceTypeEnum.team,
|
||||
teamId: tmb.teamId,
|
||||
tmbId: tmb._id
|
||||
})) ?? TeamDefaultRoleVal;
|
||||
|
||||
return {
|
||||
userId: String(tmb.userId),
|
||||
@@ -44,7 +45,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
|
||||
role: tmb.role,
|
||||
status: tmb.status,
|
||||
permission: new TeamPermission({
|
||||
per: Per ?? TeamDefaultPermissionVal,
|
||||
role,
|
||||
isOwner: tmb.role === TeamMemberRoleEnum.owner
|
||||
}),
|
||||
notificationAccount: tmb.team.notificationAccount,
|
||||
|
@@ -143,6 +143,8 @@
|
||||
"permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.",
|
||||
"permission.des.read": "Use the app to have conversations",
|
||||
"permission.des.write": "Can view and edit apps",
|
||||
"permission.des.readChatLog": "Can view chat logs",
|
||||
"permission.name.readChatLog": "View chat logs",
|
||||
"plugin.Instructions": "Instructions",
|
||||
"plugin_cost_by_token": "Charged based on token usage",
|
||||
"plugin_cost_per_times": "{{cost}} points/time",
|
||||
|
@@ -64,6 +64,7 @@
|
||||
"Parse": "Analysis",
|
||||
"Permission": "Permission",
|
||||
"Permission_tip": "Individual permissions are greater than group permissions",
|
||||
"permission_other": "Other permissions (multiple)",
|
||||
"Preview": "Preview",
|
||||
"Remove": "Remove",
|
||||
"Rename": "Rename",
|
||||
|
@@ -143,6 +143,8 @@
|
||||
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
|
||||
"permission.des.read": "可使用该应用进行对话",
|
||||
"permission.des.write": "可查看和编辑应用",
|
||||
"permission.des.readChatLog": "可查看对话日志",
|
||||
"permission.name.readChatLog": "查看对话日志",
|
||||
"plugin.Instructions": "使用说明",
|
||||
"plugin_cost_by_token": "依据 token 消耗计费",
|
||||
"plugin_cost_per_times": "{{cost}} 积分/次",
|
||||
|
@@ -64,6 +64,7 @@
|
||||
"Parse": "解析",
|
||||
"Permission": "权限",
|
||||
"Permission_tip": "个人权限大于群组权限",
|
||||
"permission_other": "其他权限(多选)",
|
||||
"Preview": "预览",
|
||||
"Remove": "移除",
|
||||
"Rename": "重命名",
|
||||
|
@@ -143,6 +143,8 @@
|
||||
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
|
||||
"permission.des.read": "可以使用這個應用程式進行對話",
|
||||
"permission.des.write": "可以檢視和編輯應用程式",
|
||||
"permission.des.readChatLog": "可以檢視對話紀錄",
|
||||
"permission.name.readChatLog": "檢視對話紀錄",
|
||||
"plugin.Instructions": "使用說明",
|
||||
"plugin_cost_by_token": "根據 token 消耗計費",
|
||||
"plugin_cost_per_times": "{{cost}} 積分/次",
|
||||
|
@@ -64,6 +64,7 @@
|
||||
"Parse": "解析",
|
||||
"Permission": "權限",
|
||||
"Permission_tip": "個人權限大於群組權限",
|
||||
"permission_other": "其他權限(多選)",
|
||||
"Preview": "預覽",
|
||||
"Remove": "移除",
|
||||
"Rename": "重新命名",
|
||||
|
Reference in New Issue
Block a user