feat: gate permission

This commit is contained in:
archer 2025-05-30 16:04:33 +08:00
parent e74ab643fe
commit 81202c53a8
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
6 changed files with 204 additions and 55 deletions

View File

@ -1,9 +1,18 @@
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
import { type PermissionListType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
export enum AppPermissionKeyEnum {}
export enum AppPermissionKeyEnum {
log = 'log'
log = 'log',
quickGate = 'quickGate',
featuredGate = 'featuredGate'
}
export const AppLogPermission = 0b100000;
export const GateQuickAppPermission = 0b001100;
export const GateFeaturedAppPermission = 0b010100;
export const AppPermissionList: PermissionListType<AppPermissionKeyEnum> = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
@ -15,14 +24,26 @@ export const AppPermissionList: PermissionListType<AppPermissionKeyEnum> = {
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b1111,
value: 0b111111,
description: i18nT('app:permission.des.manage')
},
[AppPermissionKeyEnum.log]: {
name: i18nT('app:permission.name.log'),
value: 0b1000,
value: AppLogPermission,
checkBoxType: 'multiple',
description: i18nT('app:permission.des.log')
},
[AppPermissionKeyEnum.quickGate]: {
name: '门户快捷应用权限',
description: '',
value: GateQuickAppPermission,
checkBoxType: 'multiple' // TODO: 加个隐藏选项
},
[AppPermissionKeyEnum.featuredGate]: {
name: '门户推荐应用权限',
description: '',
value: GateFeaturedAppPermission,
checkBoxType: 'multiple' // TODO: 加个隐藏选项
}
};

View File

@ -1,5 +1,86 @@
import { MongoTeamGate, gateCollectionName } from './schema';
import { MongoTeamGate } from './schema';
import { Types } from '../../../../common/mongo';
import type { ClientSession } from '../../../../common/mongo';
import { mongoSessionRun } from '../../../../common/mongo/sessionRun';
import {
GateFeaturedAppPermission,
GateQuickAppPermission
} from '@fastgpt/global/support/permission/app/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { MongoMemberGroupModel } from '../../../permission/memberGroup/memberGroupSchema';
import { MongoResourcePermission } from '../../../permission/schema';
export const addGatePermission = async ({
teamId,
appId,
per,
session
}: {
teamId: string;
appId: string;
per: number;
session?: ClientSession;
}) => {
// 1. 先找全员组
const teamGroup = await MongoMemberGroupModel.findOne({
teamId,
name: DefaultGroupName
});
if (!teamGroup) {
return Promise.reject('找不到全员组');
}
// 2. 加权限
await MongoResourcePermission.updateOne(
{
teamId,
groupId: teamGroup?._id,
resourceType: PerResourceTypeEnum.app,
resourceId: appId
},
{
permission: per
},
{
session,
upsert: true
}
);
};
export const removeGatePermission = async ({
teamId,
appId,
per,
session
}: {
teamId: string;
appId: string;
per: number;
session?: ClientSession;
}) => {
// 1. 先找全员组
const teamGroup = await MongoMemberGroupModel.findOne({
teamId,
name: DefaultGroupName
});
if (!teamGroup) {
return Promise.reject('找不到全员组');
}
await MongoResourcePermission.deleteOne(
{
teamId,
groupId: teamGroup?._id,
resourceType: PerResourceTypeEnum.app,
resourceId: appId,
permission: per
},
{
session
}
);
};
/**
*
@ -133,8 +214,20 @@ export const addFeaturedApp = async ({ teamId, appId }: { teamId: string; appId:
/**
*
*/
export const removeFeaturedApp = async ({ teamId, appId }: { teamId: string; appId: string }) => {
await MongoTeamGate.updateOne({ teamId }, { $pull: { featuredApps: new Types.ObjectId(appId) } });
export const removeFeaturedApp = async ({
teamId,
appId,
session
}: {
teamId: string;
appId: string;
session?: ClientSession;
}) => {
await MongoTeamGate.updateOne(
{ teamId },
{ $pull: { featuredApps: new Types.ObjectId(appId) } },
{ session }
);
return MongoTeamGate.findOne({ teamId }).lean();
};
@ -252,7 +345,34 @@ export const batchUpdateFeaturedApps = async (
return true;
}
await MongoTeamGate.bulkWrite(operations);
const teamId = updates[0]?.teamId;
const gateConfig = await MongoTeamGate.findOne({ teamId });
if (!gateConfig) return Promise.reject('无 gate 配置');
const updatedAppId = updates[0].featuredApps;
const deleteAppId = gateConfig.featuredApps.filter((id) => !updatedAppId.includes(id));
await mongoSessionRun(async (session) => {
await MongoTeamGate.bulkWrite(operations, { session });
for (const id of deleteAppId) {
await removeGatePermission({
teamId,
appId: id,
per: GateFeaturedAppPermission,
session
});
}
for (const id of updatedAppId) {
await addGatePermission({
teamId,
appId: id,
per: GateFeaturedAppPermission,
session
});
}
});
return true;
};
@ -291,10 +411,12 @@ export const batchUpdateToolsOrder = async (
*/
export const batchDeleteFeaturedApps = async ({
teamId,
appIds
appIds,
session
}: {
teamId: string;
appIds: string[];
session?: ClientSession;
}) => {
if (!appIds || appIds.length === 0) {
return false;
@ -302,7 +424,10 @@ export const batchDeleteFeaturedApps = async ({
await MongoTeamGate.updateOne(
{ teamId },
{ $pull: { featuredApps: { $in: appIds.map((id) => new Types.ObjectId(id)) } } }
{ $pull: { featuredApps: { $in: appIds.map((id) => new Types.ObjectId(id)) } } },
{
session
}
);
return true;
};
@ -384,31 +509,39 @@ export const moveQuickAppToPosition = async ({
/**
*
*/
export const batchUpdateQuickApps = async (
updates: {
teamId: string;
quickApps: string[];
}[]
) => {
const operations = updates.map((update) => {
const { teamId, quickApps } = update;
// 将字符串数组转换为 ObjectId 数组
const objectIdArray = quickApps.map((id) => new Types.ObjectId(id));
return {
updateOne: {
filter: { teamId },
update: { $set: { quickApps: objectIdArray } },
upsert: true
}
};
});
if (operations.length === 0) {
return true;
export const batchUpdateQuickApps = async (teamId: string, quickApps: string[]) => {
const gateConfig = await MongoTeamGate.findOne({ teamId });
if (!gateConfig) {
return false;
}
await MongoTeamGate.bulkWrite(operations);
return true;
// 计算删除的appId
const deleteAppIds = gateConfig.quickApps.filter((id) => !quickApps.includes(id.toString()));
return mongoSessionRun(async (session) => {
// 1. 删除权限
for (const id of deleteAppIds) {
await removeGatePermission({
teamId,
appId: id,
per: GateQuickAppPermission,
session
});
}
// 加权限
for (const id of quickApps) {
await addGatePermission({
teamId,
appId: id,
per: GateQuickAppPermission,
session
});
}
gateConfig.quickApps = quickApps;
await gateConfig.save({ session });
return true;
});
};
/**

View File

@ -62,11 +62,15 @@ const AccountContainer = ({
label: t('account:usage_records'),
value: TabEnum.usage
},
{
icon: 'support/gate/gateLight',
label: t('account:gateways'),
value: TabEnum.gateway
}
...(userInfo?.team?.permission.hasManagePer
? [
{
icon: 'support/gate/gateLight',
label: t('account:gateways'),
value: TabEnum.gateway
}
]
: [])
]
: []),
...(feConfigs?.show_pay && userInfo?.team?.permission.hasManagePer

View File

@ -338,7 +338,8 @@ const AppTable = () => {
);
const { openConfirm: openConfirmDel, ConfirmModal: DelConfirmModal } = useConfirm({
type: 'delete'
type: 'delete',
title: '确认删除该应用?'
});
const { runAsync: onDeleteApp } = useRequest2(delAppById, {
@ -487,12 +488,11 @@ const AppTable = () => {
flexDirection={{ base: 'column', md: 'row' }}
alignItems={{ base: 'stretch', md: 'center' }}
>
<Flex flex={1} gap={4}>
<Flex gap={4}>
<SearchInput
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t('app:search_app')}
flex={1}
/>
<Box w="200px">
<Menu closeOnSelect={false}>

View File

@ -158,12 +158,7 @@ const CopyrightTable = ({
{t('account_gate:gate_logo')}
</Text>
<Flex
gap={{ base: 4, md: 8 }}
alignItems="center"
justifyContent="flex-start"
flexDirection={{ base: 'column', md: 'row' }}
>
<Flex gap={{ base: 4, md: 8 }} alignItems="center" justifyContent="flex-start">
{/* 左侧 Banner 显示 - 带文字 */}
<Flex direction="column" gap={2} alignItems="center">
<Box
@ -369,8 +364,8 @@ const CopyrightTable = ({
<LogoFile
onSelect={(e: File[]) =>
onSelectLogoImage(e, {
maxH: 300,
maxW: 300,
maxH: 3000,
maxW: 3000,
callback: (e: string) => {
setValue('logo', e);
handleGateLogoChange(e);
@ -382,8 +377,8 @@ const CopyrightTable = ({
<BannerFile
onSelect={(e: File[]) =>
onSelectBannerImage(e, {
maxH: 300,
maxW: 300,
maxH: 3000,
maxW: 3000,
callback: (e: string) => {
setValue('banner', e);
handleGateBannerChange(e);

View File

@ -35,9 +35,7 @@ const GatewayConfig = () => {
//从 appForm 中获取 selectedTools的 id 组成 string 数组
//gateConfig?.tools 改成
const [copyRightConfig, setCopyRightConfig] = useState<
getGateConfigCopyRightResponse | undefined
>(undefined);
const [copyRightConfig, setCopyRightConfig] = useState<getGateConfigCopyRightResponse>();
const [tab, setTab] = useState<TabType>('home');
const [isLoadingApps, setIsLoadingApps] = useState(true);
const [gateApps, setGateApps] = useState<AppListItemType[]>([]);
@ -247,8 +245,6 @@ const GatewayConfig = () => {
}, [
gateConfig,
copyRightConfig,
isLoadingApps,
gateApps,
Tab,
isAppTab,
tab,