feat: add app log permission (#4932)

* feat: add app log permission

* fix: org search bug
This commit is contained in:
Finley Ge 2025-05-30 17:09:29 +08:00 committed by archer
parent 5a5367d30b
commit d7b9f94270
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
10 changed files with 119 additions and 73 deletions

View File

@ -1,8 +1,10 @@
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
import { type PermissionListType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
export enum AppPermissionKeyEnum {}
export const AppPermissionList: PermissionListType = {
export enum AppPermissionKeyEnum {
log = 'log'
}
export const AppPermissionList: PermissionListType<AppPermissionKeyEnum> = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: i18nT('app:permission.des.read')
@ -13,8 +15,16 @@ export const AppPermissionList: PermissionListType = {
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b1111,
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 AppLogPermissionVal = AppPermissionList[AppPermissionKeyEnum.log].value;

View File

@ -1,7 +1,8 @@
import { type PerConstructPros, Permission } from '../controller';
import { AppDefaultPermissionVal } from './constant';
import { AppDefaultPermissionVal, AppPermissionList } from './constant';
export class AppPermission extends Permission {
hasLogPer: boolean = false;
constructor(props?: PerConstructPros) {
if (!props) {
props = {
@ -11,5 +12,11 @@ export class AppPermission extends Permission {
props.per = AppDefaultPermissionVal;
}
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);
});
}
}

View File

@ -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.read": "Use the app to have conversations",
"permission.des.write": "Can view and edit apps",
"permission.des.log": "Can view conversation logs",
"permission.name.log": "View logs",
"plugin.Instructions": "Instructions",
"plugin_cost_by_token": "Charged based on token usage",
"plugin_cost_per_times": "{{cost}} points/time",

View File

@ -123,6 +123,8 @@
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
"permission.des.read": "可使用该应用进行对话",
"permission.des.write": "可查看和编辑应用",
"permission.des.log": "可查看对话日志",
"permission.name.log": "查看日志",
"plugin.Instructions": "使用说明",
"plugin_cost_by_token": "依据 token 消耗计费",
"plugin_cost_per_times": "{{cost}} 积分/次",

View File

@ -123,6 +123,8 @@
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
"permission.des.read": "可以使用這個應用程式進行對話",
"permission.des.write": "可以檢視和編輯應用程式",
"permission.des.log": "可查看對話日誌",
"permission.name.log": "查看日誌",
"plugin.Instructions": "使用說明",
"plugin_cost_by_token": "根據 token 消耗計費",
"plugin_cost_per_times": "{{cost}} 積分/次",

View File

@ -46,16 +46,22 @@ function MemberModal({
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const {
paths,
paths: orgPaths,
onClickOrg,
members: orgMembers,
MemberScrollData: OrgMemberScrollData,
onPathClick,
onPathClick: onOrgPathClick,
orgs,
searchKey,
setSearchKey
} = useOrg({ withPermission: false });
const onExpandOrg = (org: OrgListItemType) => {
setFilterClass('org');
setSearchKey('');
onClickOrg(org);
};
const {
data: members,
ScrollData: TeamMemberScrollData,
@ -104,8 +110,8 @@ function MemberModal({
permissionList?.read?.value
);
const perLabel = useMemo(() => {
if (selectedPermission === undefined) return '';
return getPerLabelList(selectedPermission!).join('、');
if (selectedPermission === undefined) return [];
return getPerLabelList(selectedPermission!);
}, [getPerLabelList, selectedPermission]);
const onUpdateCollaborators = useContextSelector(
@ -194,6 +200,7 @@ function MemberModal({
placeholder={t('user:search_group_org_user')}
bgColor="myGray.50"
onChange={(e) => setSearchKey(e.target.value)}
value={searchKey}
/>
<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.group.group')
},
...paths
...orgPaths
]}
onClick={(parentId) => {
if (parentId === '') {
setFilterClass(undefined);
onPathClick('');
onOrgPathClick('');
} else if (
parentId === 'member' ||
parentId === 'org' ||
parentId === 'group'
) {
setFilterClass(parentId);
onPathClick('');
onOrgPathClick('');
} else {
onPathClick(parentId);
onOrgPathClick(parentId);
}
}}
rootName={t('common:Team')}
@ -329,8 +336,8 @@ function MemberModal({
bgColor: 'myGray.200'
}}
onClick={(e) => {
onClickOrg(org);
// setPath(getOrgChildrenPath(org));
// onClickOrg(org);
onExpandOrg(org);
e.stopPropagation();
}}
/>
@ -438,7 +445,7 @@ function MemberModal({
borderRadius={'md'}
h={'32px'}
>
{t(perLabel as any)}
{perLabel.map((item) => t(item as any)).join('、')}
<ChevronDownIcon fontSize={'md'} />
</Flex>
}

View File

@ -7,7 +7,8 @@ import {
Radio,
useOutsideClick,
HStack,
MenuButton
MenuButton,
Checkbox
} from '@chakra-ui/react';
import React, { useMemo, useRef, useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
@ -89,19 +90,21 @@ function PermissionSelect({
return permissionList['read'].value;
}, [permissionList, value]);
// const selectedMultipleValues = useMemo(() => {
// const per = new Permission({ per: value });
//
// return permissionSelectList.multipleCheckBoxList
// .filter((item) => {
// return per.checkPer(item.value);
// })
// .map((item) => item.value);
// }, [permissionSelectList.multipleCheckBoxList, value]);
const selectedMultipleValues = useMemo(() => {
const per = new Permission({ per: value });
const onSelectPer = (per: PermissionValueType) => {
if (per === value) return;
onChange(per);
return permissionSelectList.multipleCheckBoxList
.filter((item) => {
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);
};
@ -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 */}
{/* {list
.filter((item) => item.type === 'multiple')
.map((item) => {
const change = () => {
if (checkPermission(valueState, item.value)) {
setValueState(new Permission(valueState).remove(item.value).value);
} else {
setValueState(new Permission(valueState).add(item.value).value);
}
};
return (
<Flex
key={item.value}
{...(checkPermission(valueState, item.value)
? {
color: 'primary.500',
bg: 'myWhite.300'
}
: {})}
whiteSpace="pre-wrap"
flexDirection="row"
justifyContent="start"
p="2"
_hover={{
bg: 'myGray.50'
}}
>
<Checkbox
size="lg"
isChecked={checkPermission(valueState, item.value)}
onChange={change}
/>
<Flex px="4" flexDirection="column" onClick={change}>
<Box fontWeight="500">{item.name}</Box>
<Box fontWeight="400">{item.description}</Box>
</Flex>
{permissionSelectList.multipleCheckBoxList.map((item) => {
const isChecked = selectedMultipleValues.includes(item.value);
const isDisabled = new Permission({ per: selectedSingleValue }).checkPer(item.value);
const change = () => {
if (isDisabled) return;
const per = new Permission({ per: value });
if (isChecked) {
per.removePer(item.value);
} else {
per.addPer(item.value);
}
onChange(per.value);
};
return (
<Flex
key={item.value}
{...(isChecked
? {
color: 'primary.500',
bg: 'myWhite.300'
}
: {})}
whiteSpace="pre-wrap"
flexDirection="row"
justifyContent="start"
p="2"
_hover={{
bg: 'myGray.50'
}}
{...(isDisabled
? {
cursor: 'not-allowed',
opacity: 0.5
}
: {})}
>
<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>
);
})}
{onDelete && (
<>
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />

View File

@ -35,10 +35,10 @@ const RouteTab = () => {
{
label: t('app:publish_channel'),
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]
);

View File

@ -18,6 +18,7 @@ import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSc
import type { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { type AIChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { AppLogPermissionVal } from '@fastgpt/global/support/permission/app/constant';
const formatJsonString = (data: any) => {
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('缺少参数');
}
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([
{ $match: { teamId: new Types.ObjectId(teamId) } },

View File

@ -13,6 +13,7 @@ import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { type PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { AppLogPermissionVal } from '@fastgpt/global/support/permission/app/constant';
async function handler(
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 = {
teamId: new Types.ObjectId(teamId),