From 44275b5cd755f945cc1f5ee0f26249df646ee7b7 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Fri, 6 Sep 2024 16:20:07 +0800 Subject: [PATCH] refactor(plugin-acl): extensible support for role permissions configuration UI (#5216) --- .../plugin-acl/src/client/ACLSettingsUI.tsx | 79 +++++++++++++++++++ .../src/client/RolesManagerProvider.tsx | 20 ++++- .../@nocobase/plugin-acl/src/client/index.ts | 2 + .../client/permissions/GeneralPermissions.tsx | 16 ++-- .../client/permissions/MenuPermissions.tsx | 14 ++-- .../src/client/permissions/Permissions.tsx | 59 +++----------- .../plugin-data-source-manager/package.json | 3 +- .../component/PermissionManager/index.tsx | 22 +++--- .../src/client/index.tsx | 16 +++- 9 files changed, 149 insertions(+), 82 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx new file mode 100644 index 0000000000..ec6cc22b23 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx @@ -0,0 +1,79 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { TabsProps } from 'antd/es/tabs/index'; +import React from 'react'; +import { GeneralPermissions } from './permissions/GeneralPermissions'; +import { MenuItemsProvider } from './permissions/MenuItemsProvider'; +import { MenuPermissions } from './permissions/MenuPermissions'; +import { Role } from './RolesManagerProvider'; + +interface PermissionsTabsProps { + /** + * the key of the currently active tab panel + */ + activeKey: string; + /** + * the currently selected role + */ + role: Role; + /** + * translation function + */ + t: (key: string) => string; + /** + * used to constrain the size of the container in the Tab + */ + TabLayout: React.FC; +} + +type Tab = TabsProps['items'][0]; + +type TabCallback = (props: PermissionsTabsProps) => Tab; + +/** + * the extension API for ACL settings page + */ +export class ACLSettingsUI { + private permissionsTabs: (Tab | TabCallback)[] = [ + ({ t, TabLayout }) => ({ + key: 'general', + label: t('System'), + children: ( + + + + ), + }), + ({ activeKey, t, TabLayout }) => ({ + key: 'menu', + label: t('Menu'), + children: ( + + + + + + ), + }), + ]; + + addPermissionsTab(tab: Tab | TabCallback): void { + this.permissionsTabs.push(tab); + } + + getPermissionsTabs(props: PermissionsTabsProps): Tab[] { + return this.permissionsTabs.map((tab) => { + if (typeof tab === 'function') { + return tab(props); + } + return tab; + }); + } +} diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx index e32c968fdb..abd635ebfd 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx @@ -9,9 +9,25 @@ import { createContext } from 'react'; +export interface Role { + createdAt: string; + updatedAt: string; + name: string; + title: string; + description: string; + strategy: { + actions: string[]; + }; + default: boolean; + hidden: boolean; + allowConfigure: boolean; + allowNewMenu: boolean; + snippets: string[]; +} + export const RolesManagerContext = createContext<{ - role: any; - setRole: (role: any) => void; + role: Role; + setRole: (role: Role) => void; }>({ role: null, } as any); diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/index.ts b/packages/plugins/@nocobase/plugin-acl/src/client/index.ts index 9df8d9cfc9..129c613363 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/index.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/client/index.ts @@ -8,11 +8,13 @@ */ import { Plugin } from '@nocobase/client'; +import { ACLSettingsUI } from './ACLSettingsUI'; import { RolesManagement } from './RolesManagement'; import { RolesManager } from './roles-manager'; export class PluginACLClient extends Plugin { rolesManager = new RolesManager(); + settingsUI = new ACLSettingsUI(); async load() { this.pluginSettingsManager.add('users-permissions.roles', { diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx index 7fec43175c..0cc34c6c8a 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx @@ -7,19 +7,19 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { onFormValuesChange, createForm, Form, onFieldChange } from '@formily/core'; +import { createForm, Form, onFormValuesChange } from '@formily/core'; import { connect } from '@formily/react'; -import { SchemaComponent, useAPIClient, useRequest } from '@nocobase/client'; +import { uid } from '@formily/shared'; +import { SchemaComponent, useAPIClient } from '@nocobase/client'; +import { useMemoizedFn } from 'ahooks'; import { Checkbox, message } from 'antd'; import uniq from 'lodash/uniq'; import React, { useContext, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { uid } from '@formily/shared'; -import { useMemoizedFn } from 'ahooks'; -import { RolesManagerContext } from '../RolesManagerProvider'; -import { StrategyActions } from './StrategyActions'; import { useACLTranslation } from '../locale'; +import { RolesManagerContext } from '../RolesManagerProvider'; import { PluginPermissions } from './PluginPermissions'; +import { StrategyActions } from './StrategyActions'; const SnippetCheckboxGroup = connect((props) => { const { t } = useTranslation(); @@ -65,9 +65,7 @@ const SnippetCheckboxGroup = connect((props) => { ); }); -export const GeneralPermissions: React.FC<{ - active: boolean; -}> = ({ active }) => { +export const GeneralPermissions: React.FC = () => { const { role, setRole } = useContext(RolesManagerContext); const { t } = useACLTranslation(); const api = useAPIClient(); diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx index f22e39de48..644e29aadd 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx @@ -7,17 +7,17 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Checkbox, message, Table } from 'antd'; -import { onFormValuesChange, createForm, Form } from '@formily/core'; -import { uniq } from 'lodash'; -import React, { useContext, useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; +import { createForm, Form, onFormValuesChange } from '@formily/core'; import { uid } from '@formily/shared'; -import { useAPIClient, SchemaComponent, useRequest } from '@nocobase/client'; -import { useStyles } from './style'; +import { SchemaComponent, useAPIClient, useRequest } from '@nocobase/client'; import { useMemoizedFn } from 'ahooks'; +import { Checkbox, message, Table } from 'antd'; +import { uniq } from 'lodash'; +import React, { useContext, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { RolesManagerContext } from '../RolesManagerProvider'; import { useMenuItems } from './MenuItemsProvider'; +import { useStyles } from './style'; const findUids = (items) => { if (!Array.isArray(items)) { diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx index 23f820fb26..d286a68006 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx @@ -7,15 +7,13 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useApp, useRequest, useAPIClient } from '@nocobase/client'; +import { useAPIClient, usePlugin, useRequest } from '@nocobase/client'; import { Tabs } from 'antd'; import React, { useContext, useEffect, useMemo } from 'react'; -import { RolesManagerContext } from '../RolesManagerProvider'; +import PluginACLClient from '..'; +import { Role, RolesManagerContext } from '../RolesManagerProvider'; import { useACLTranslation } from '../locale'; import { AvailableActionsProvider } from './AvailableActions'; -import { GeneralPermissions } from './GeneralPermissions'; -import { MenuItemsProvider } from './MenuItemsProvider'; -import { MenuPermissions } from './MenuPermissions'; const TabLayout: React.FC = (props) => { return
{props.children}
; @@ -25,52 +23,15 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { const { t } = useACLTranslation(); const [activeKey, setActiveKey] = React.useState('general'); const { role, setRole } = useContext(RolesManagerContext); - const pm = role?.snippets?.includes('pm.*'); - const app = useApp(); - const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager'); + const pluginACLClient = usePlugin(PluginACLClient); const items = useMemo( - () => [ - { - key: 'general', - label: t('System'), - children: ( - - - - ), - }, - { - key: 'menu', - label: t('Menu'), - children: ( - - - - - - ), - }, - ...(DataSourcePermissionManager - ? [ - { - key: 'dataSource', - label: t('Data sources'), - children: ( - - - - - - ), - }, - ] - : []), - ], - [pm, activeKey, active, t], + () => pluginACLClient.settingsUI.getPermissionsTabs({ t, activeKey, TabLayout, role }), + [activeKey, pluginACLClient.settingsUI, role, t], ); + const api = useAPIClient(); - const { data } = useRequest( + const { data } = useRequest( () => api .resource('roles') @@ -89,13 +50,15 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { refreshDeps: [role?.name], }, ); + useEffect(() => { setActiveKey('general'); }, [role?.name]); useEffect(() => { setRole(data); - }, [data]); + }, [data, setRole]); + return ( setActiveKey(key)} items={items} /> diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/package.json b/packages/plugins/@nocobase/plugin-data-source-manager/package.json index e3cf266288..3eed8a1045 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/package.json +++ b/packages/plugins/@nocobase/plugin-data-source-manager/package.json @@ -11,7 +11,8 @@ "peerDependencies": { "@nocobase/client": "1.x", "@nocobase/server": "1.x", - "@nocobase/test": "1.x" + "@nocobase/test": "1.x", + "@nocobase/plugin-acl": "1.x" }, "keywords": [ "Data model tools" diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx index b5ec3ddfbe..83f4291545 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx @@ -9,20 +9,20 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; +import { + MenuConfigure, + ResourceActionProvider, + SchemaComponent, + SettingCenterProvider, + SettingsCenterConfigure, +} from '@nocobase/client'; import { Card } from 'antd'; import React, { createContext } from 'react'; -import { - SchemaComponent, - MenuConfigure, - SettingsCenterConfigure, - SettingCenterProvider, - ResourceActionProvider, -} from '@nocobase/client'; import { DataSourceTable } from './DataSourceTable'; -import { RoleConfigure } from './RoleConfigure'; -import { StrategyActions } from './StrategyActions'; -import { RolesResourcesActions } from './RolesResourcesActions'; import { RoleRecordProvider } from './PermisionProvider'; +import { RoleConfigure } from './RoleConfigure'; +import { RolesResourcesActions } from './RolesResourcesActions'; +import { StrategyActions } from './StrategyActions'; const schema2: ISchema = { type: 'object', @@ -36,7 +36,7 @@ const schema2: ISchema = { export const CurrentRolesContext = createContext({} as any); CurrentRolesContext.displayName = 'CurrentRolesContext'; -export const DataSourcePermissionManager = ({ role }: any) => { +export const DataSourcePermissionManager = ({ role }) => { return ( diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx index 3d5e7ecc47..473ffb8c17 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx @@ -8,6 +8,7 @@ */ import { Plugin } from '@nocobase/client'; +import PluginACLClient from '@nocobase/plugin-acl/client'; import React from 'react'; import { DatabaseConnectionProvider } from './DatabaseConnectionProvider'; import { ThirdDataSource } from './ThridDataSource'; @@ -21,10 +22,17 @@ import { NAMESPACE } from './locale'; export class PluginDataSourceManagerClient extends Plugin { types = new Map(); async load() { - // 注册组件 - this.app.addComponents({ - DataSourcePermissionManager, - }); + // register a configuration item in the Users & Permissions management page + this.app.pm.get(PluginACLClient).settingsUI.addPermissionsTab(({ t, TabLayout, role }) => ({ + key: 'dataSource', + label: t('Data sources'), + children: ( + + + + ), + })); + this.app.use(DatabaseConnectionProvider); this.app.pluginSettingsManager.add(NAMESPACE, { title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`,