feat: add app log permission (#4932)
* feat: add app log permission * fix: org search bug
This commit is contained in:
parent
5a5367d30b
commit
d7b9f94270
@ -1,8 +1,10 @@
|
|||||||
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
|
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
|
||||||
import { type PermissionListType } from '../type';
|
import { type PermissionListType } from '../type';
|
||||||
import { i18nT } from '../../../../web/i18n/utils';
|
import { i18nT } from '../../../../web/i18n/utils';
|
||||||
export enum AppPermissionKeyEnum {}
|
export enum AppPermissionKeyEnum {
|
||||||
export const AppPermissionList: PermissionListType = {
|
log = 'log'
|
||||||
|
}
|
||||||
|
export const AppPermissionList: PermissionListType<AppPermissionKeyEnum> = {
|
||||||
[PermissionKeyEnum.read]: {
|
[PermissionKeyEnum.read]: {
|
||||||
...PermissionList[PermissionKeyEnum.read],
|
...PermissionList[PermissionKeyEnum.read],
|
||||||
description: i18nT('app:permission.des.read')
|
description: i18nT('app:permission.des.read')
|
||||||
@ -13,8 +15,16 @@ export const AppPermissionList: PermissionListType = {
|
|||||||
},
|
},
|
||||||
[PermissionKeyEnum.manage]: {
|
[PermissionKeyEnum.manage]: {
|
||||||
...PermissionList[PermissionKeyEnum.manage],
|
...PermissionList[PermissionKeyEnum.manage],
|
||||||
|
value: 0b1111,
|
||||||
description: i18nT('app:permission.des.manage')
|
description: i18nT('app:permission.des.manage')
|
||||||
|
},
|
||||||
|
[AppPermissionKeyEnum.log]: {
|
||||||
|
name: i18nT('app:permission.name.log'),
|
||||||
|
value: 0b1000,
|
||||||
|
checkBoxType: 'multiple',
|
||||||
|
description: i18nT('app:permission.des.log')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppDefaultPermissionVal = NullPermission;
|
export const AppDefaultPermissionVal = NullPermission;
|
||||||
|
export const AppLogPermissionVal = AppPermissionList[AppPermissionKeyEnum.log].value;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { type PerConstructPros, Permission } from '../controller';
|
import { type PerConstructPros, Permission } from '../controller';
|
||||||
import { AppDefaultPermissionVal } from './constant';
|
import { AppDefaultPermissionVal, AppPermissionList } from './constant';
|
||||||
|
|
||||||
export class AppPermission extends Permission {
|
export class AppPermission extends Permission {
|
||||||
|
hasLogPer: boolean = false;
|
||||||
constructor(props?: PerConstructPros) {
|
constructor(props?: PerConstructPros) {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
props = {
|
props = {
|
||||||
@ -11,5 +12,11 @@ export class AppPermission extends Permission {
|
|||||||
props.per = AppDefaultPermissionVal;
|
props.per = AppDefaultPermissionVal;
|
||||||
}
|
}
|
||||||
super(props);
|
super(props);
|
||||||
|
this.setUpdatePermissionCallback(() => {
|
||||||
|
this.hasReadPer = this.checkPer(AppPermissionList.read.value);
|
||||||
|
this.hasWritePer = this.checkPer(AppPermissionList.write.value);
|
||||||
|
this.hasManagePer = this.checkPer(AppPermissionList.manage.value);
|
||||||
|
this.hasLogPer = this.checkPer(AppPermissionList.log.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,6 +123,8 @@
|
|||||||
"permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.",
|
"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.read": "Use the app to have conversations",
|
||||||
"permission.des.write": "Can view and edit apps",
|
"permission.des.write": "Can view and edit apps",
|
||||||
|
"permission.des.log": "Can view conversation logs",
|
||||||
|
"permission.name.log": "View logs",
|
||||||
"plugin.Instructions": "Instructions",
|
"plugin.Instructions": "Instructions",
|
||||||
"plugin_cost_by_token": "Charged based on token usage",
|
"plugin_cost_by_token": "Charged based on token usage",
|
||||||
"plugin_cost_per_times": "{{cost}} points/time",
|
"plugin_cost_per_times": "{{cost}} points/time",
|
||||||
|
|||||||
@ -123,6 +123,8 @@
|
|||||||
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
|
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
|
||||||
"permission.des.read": "可使用该应用进行对话",
|
"permission.des.read": "可使用该应用进行对话",
|
||||||
"permission.des.write": "可查看和编辑应用",
|
"permission.des.write": "可查看和编辑应用",
|
||||||
|
"permission.des.log": "可查看对话日志",
|
||||||
|
"permission.name.log": "查看日志",
|
||||||
"plugin.Instructions": "使用说明",
|
"plugin.Instructions": "使用说明",
|
||||||
"plugin_cost_by_token": "依据 token 消耗计费",
|
"plugin_cost_by_token": "依据 token 消耗计费",
|
||||||
"plugin_cost_per_times": "{{cost}} 积分/次",
|
"plugin_cost_per_times": "{{cost}} 积分/次",
|
||||||
|
|||||||
@ -123,6 +123,8 @@
|
|||||||
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
|
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
|
||||||
"permission.des.read": "可以使用這個應用程式進行對話",
|
"permission.des.read": "可以使用這個應用程式進行對話",
|
||||||
"permission.des.write": "可以檢視和編輯應用程式",
|
"permission.des.write": "可以檢視和編輯應用程式",
|
||||||
|
"permission.des.log": "可查看對話日誌",
|
||||||
|
"permission.name.log": "查看日誌",
|
||||||
"plugin.Instructions": "使用說明",
|
"plugin.Instructions": "使用說明",
|
||||||
"plugin_cost_by_token": "根據 token 消耗計費",
|
"plugin_cost_by_token": "根據 token 消耗計費",
|
||||||
"plugin_cost_per_times": "{{cost}} 積分/次",
|
"plugin_cost_per_times": "{{cost}} 積分/次",
|
||||||
|
|||||||
@ -46,16 +46,22 @@ function MemberModal({
|
|||||||
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
||||||
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
||||||
const {
|
const {
|
||||||
paths,
|
paths: orgPaths,
|
||||||
onClickOrg,
|
onClickOrg,
|
||||||
members: orgMembers,
|
members: orgMembers,
|
||||||
MemberScrollData: OrgMemberScrollData,
|
MemberScrollData: OrgMemberScrollData,
|
||||||
onPathClick,
|
onPathClick: onOrgPathClick,
|
||||||
orgs,
|
orgs,
|
||||||
searchKey,
|
searchKey,
|
||||||
setSearchKey
|
setSearchKey
|
||||||
} = useOrg({ withPermission: false });
|
} = useOrg({ withPermission: false });
|
||||||
|
|
||||||
|
const onExpandOrg = (org: OrgListItemType) => {
|
||||||
|
setFilterClass('org');
|
||||||
|
setSearchKey('');
|
||||||
|
onClickOrg(org);
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: members,
|
data: members,
|
||||||
ScrollData: TeamMemberScrollData,
|
ScrollData: TeamMemberScrollData,
|
||||||
@ -104,8 +110,8 @@ function MemberModal({
|
|||||||
permissionList?.read?.value
|
permissionList?.read?.value
|
||||||
);
|
);
|
||||||
const perLabel = useMemo(() => {
|
const perLabel = useMemo(() => {
|
||||||
if (selectedPermission === undefined) return '';
|
if (selectedPermission === undefined) return [];
|
||||||
return getPerLabelList(selectedPermission!).join('、');
|
return getPerLabelList(selectedPermission!);
|
||||||
}, [getPerLabelList, selectedPermission]);
|
}, [getPerLabelList, selectedPermission]);
|
||||||
|
|
||||||
const onUpdateCollaborators = useContextSelector(
|
const onUpdateCollaborators = useContextSelector(
|
||||||
@ -194,6 +200,7 @@ function MemberModal({
|
|||||||
placeholder={t('user:search_group_org_user')}
|
placeholder={t('user:search_group_org_user')}
|
||||||
bgColor="myGray.50"
|
bgColor="myGray.50"
|
||||||
onChange={(e) => setSearchKey(e.target.value)}
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
|
value={searchKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||||
@ -238,21 +245,21 @@ function MemberModal({
|
|||||||
? t('user:team.org.org')
|
? t('user:team.org.org')
|
||||||
: t('user:team.group.group')
|
: t('user:team.group.group')
|
||||||
},
|
},
|
||||||
...paths
|
...orgPaths
|
||||||
]}
|
]}
|
||||||
onClick={(parentId) => {
|
onClick={(parentId) => {
|
||||||
if (parentId === '') {
|
if (parentId === '') {
|
||||||
setFilterClass(undefined);
|
setFilterClass(undefined);
|
||||||
onPathClick('');
|
onOrgPathClick('');
|
||||||
} else if (
|
} else if (
|
||||||
parentId === 'member' ||
|
parentId === 'member' ||
|
||||||
parentId === 'org' ||
|
parentId === 'org' ||
|
||||||
parentId === 'group'
|
parentId === 'group'
|
||||||
) {
|
) {
|
||||||
setFilterClass(parentId);
|
setFilterClass(parentId);
|
||||||
onPathClick('');
|
onOrgPathClick('');
|
||||||
} else {
|
} else {
|
||||||
onPathClick(parentId);
|
onOrgPathClick(parentId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
rootName={t('common:Team')}
|
rootName={t('common:Team')}
|
||||||
@ -329,8 +336,8 @@ function MemberModal({
|
|||||||
bgColor: 'myGray.200'
|
bgColor: 'myGray.200'
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onClickOrg(org);
|
// onClickOrg(org);
|
||||||
// setPath(getOrgChildrenPath(org));
|
onExpandOrg(org);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -438,7 +445,7 @@ function MemberModal({
|
|||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
h={'32px'}
|
h={'32px'}
|
||||||
>
|
>
|
||||||
{t(perLabel as any)}
|
{perLabel.map((item) => t(item as any)).join('、')}
|
||||||
<ChevronDownIcon fontSize={'md'} />
|
<ChevronDownIcon fontSize={'md'} />
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
useOutsideClick,
|
useOutsideClick,
|
||||||
HStack,
|
HStack,
|
||||||
MenuButton
|
MenuButton,
|
||||||
|
Checkbox
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
@ -89,19 +90,21 @@ function PermissionSelect({
|
|||||||
|
|
||||||
return permissionList['read'].value;
|
return permissionList['read'].value;
|
||||||
}, [permissionList, value]);
|
}, [permissionList, value]);
|
||||||
// const selectedMultipleValues = useMemo(() => {
|
const selectedMultipleValues = useMemo(() => {
|
||||||
// const per = new Permission({ per: value });
|
const per = new Permission({ per: value });
|
||||||
//
|
|
||||||
// return permissionSelectList.multipleCheckBoxList
|
|
||||||
// .filter((item) => {
|
|
||||||
// return per.checkPer(item.value);
|
|
||||||
// })
|
|
||||||
// .map((item) => item.value);
|
|
||||||
// }, [permissionSelectList.multipleCheckBoxList, value]);
|
|
||||||
|
|
||||||
const onSelectPer = (per: PermissionValueType) => {
|
return permissionSelectList.multipleCheckBoxList
|
||||||
if (per === value) return;
|
.filter((item) => {
|
||||||
onChange(per);
|
return per.checkPer(item.value);
|
||||||
|
})
|
||||||
|
.map((item) => item.value);
|
||||||
|
}, [permissionSelectList.multipleCheckBoxList, value]);
|
||||||
|
|
||||||
|
const onSelectPer = (perValue: PermissionValueType) => {
|
||||||
|
if (perValue === value) return;
|
||||||
|
const per = new Permission({ per: perValue });
|
||||||
|
per.addPer(...selectedMultipleValues);
|
||||||
|
onChange(per.value);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -184,50 +187,61 @@ function PermissionSelect({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* <MyDivider my={3} />
|
<MyDivider my={2} />
|
||||||
|
|
||||||
{multipleValues.length > 0 && <Box m="4">其他权限(多选)</Box>} */}
|
{/* {permissionSelectList.multipleCheckBoxList.length > 0 && (
|
||||||
|
<Box m="4">其他权限(多选)</Box>
|
||||||
|
)} */}
|
||||||
|
|
||||||
{/* The list of multiple select permissions */}
|
{permissionSelectList.multipleCheckBoxList.map((item) => {
|
||||||
{/* {list
|
const isChecked = selectedMultipleValues.includes(item.value);
|
||||||
.filter((item) => item.type === 'multiple')
|
const isDisabled = new Permission({ per: selectedSingleValue }).checkPer(item.value);
|
||||||
.map((item) => {
|
const change = () => {
|
||||||
const change = () => {
|
if (isDisabled) return;
|
||||||
if (checkPermission(valueState, item.value)) {
|
const per = new Permission({ per: value });
|
||||||
setValueState(new Permission(valueState).remove(item.value).value);
|
if (isChecked) {
|
||||||
} else {
|
per.removePer(item.value);
|
||||||
setValueState(new Permission(valueState).add(item.value).value);
|
} else {
|
||||||
}
|
per.addPer(item.value);
|
||||||
};
|
}
|
||||||
return (
|
onChange(per.value);
|
||||||
<Flex
|
};
|
||||||
key={item.value}
|
return (
|
||||||
{...(checkPermission(valueState, item.value)
|
<Flex
|
||||||
? {
|
key={item.value}
|
||||||
color: 'primary.500',
|
{...(isChecked
|
||||||
bg: 'myWhite.300'
|
? {
|
||||||
}
|
color: 'primary.500',
|
||||||
: {})}
|
bg: 'myWhite.300'
|
||||||
whiteSpace="pre-wrap"
|
}
|
||||||
flexDirection="row"
|
: {})}
|
||||||
justifyContent="start"
|
whiteSpace="pre-wrap"
|
||||||
p="2"
|
flexDirection="row"
|
||||||
_hover={{
|
justifyContent="start"
|
||||||
bg: 'myGray.50'
|
p="2"
|
||||||
}}
|
_hover={{
|
||||||
>
|
bg: 'myGray.50'
|
||||||
<Checkbox
|
}}
|
||||||
size="lg"
|
{...(isDisabled
|
||||||
isChecked={checkPermission(valueState, item.value)}
|
? {
|
||||||
onChange={change}
|
cursor: 'not-allowed',
|
||||||
/>
|
opacity: 0.5
|
||||||
<Flex px="4" flexDirection="column" onClick={change}>
|
}
|
||||||
<Box fontWeight="500">{item.name}</Box>
|
: {})}
|
||||||
<Box fontWeight="400">{item.description}</Box>
|
>
|
||||||
</Flex>
|
<Checkbox
|
||||||
|
size="lg"
|
||||||
|
isChecked={isChecked}
|
||||||
|
onChange={change}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
<Flex px="4" flexDirection="column" onClick={change}>
|
||||||
|
<Box fontWeight="500">{t(item.name as any)}</Box>
|
||||||
|
<Box fontWeight="400">{t(item.description as any)}</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
</Flex>
|
||||||
})}*/}
|
);
|
||||||
|
})}
|
||||||
{onDelete && (
|
{onDelete && (
|
||||||
<>
|
<>
|
||||||
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />
|
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />
|
||||||
|
|||||||
@ -35,10 +35,10 @@ const RouteTab = () => {
|
|||||||
{
|
{
|
||||||
label: t('app:publish_channel'),
|
label: t('app:publish_channel'),
|
||||||
id: TabEnum.publish
|
id: TabEnum.publish
|
||||||
},
|
}
|
||||||
{ label: t('app:chat_logs'), id: TabEnum.logs }
|
|
||||||
]
|
]
|
||||||
: [])
|
: []),
|
||||||
|
...(appDetail.permission.hasLogPer ? [{ label: t('app:chat_logs'), id: TabEnum.logs }] : [])
|
||||||
],
|
],
|
||||||
[appDetail.permission.hasManagePer, appDetail.type]
|
[appDetail.permission.hasManagePer, appDetail.type]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSc
|
|||||||
import type { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
import type { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { type AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
import { type AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import { AppLogPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
|
||||||
const formatJsonString = (data: any) => {
|
const formatJsonString = (data: any) => {
|
||||||
return JSON.stringify(data).replace(/"/g, '""').replace(/\n/g, '\\n');
|
return JSON.stringify(data).replace(/"/g, '""').replace(/\n/g, '\\n');
|
||||||
@ -44,7 +45,7 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
|
|||||||
throw new Error('缺少参数');
|
throw new Error('缺少参数');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { teamId } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
const { teamId } = await authApp({ req, authToken: true, appId, per: AppLogPermissionVal });
|
||||||
|
|
||||||
const teamMemberWithContact = await MongoTeamMember.aggregate([
|
const teamMemberWithContact = await MongoTeamMember.aggregate([
|
||||||
{ $match: { teamId: new Types.ObjectId(teamId) } },
|
{ $match: { teamId: new Types.ObjectId(teamId) } },
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
|
|||||||
import { type PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
import { type PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
import { addSourceMember } from '@fastgpt/service/support/user/utils';
|
import { addSourceMember } from '@fastgpt/service/support/user/utils';
|
||||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||||
|
import { AppLogPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
|
||||||
async function handler(
|
async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -33,7 +34,7 @@ async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const { teamId } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
const { teamId } = await authApp({ req, authToken: true, appId, per: AppLogPermissionVal });
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
teamId: new Types.ObjectId(teamId),
|
teamId: new Types.ObjectId(teamId),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user