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}" })}}`,