refactor(plugin-acl): extensible support for role permissions configuration UI (#5216)

This commit is contained in:
Zeke Zhang 2024-09-06 16:20:07 +08:00 committed by GitHub
parent 2459991fbb
commit 44275b5cd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 149 additions and 82 deletions

View File

@ -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: (
<TabLayout>
<GeneralPermissions />
</TabLayout>
),
}),
({ activeKey, t, TabLayout }) => ({
key: 'menu',
label: t('Menu'),
children: (
<TabLayout>
<MenuItemsProvider>
<MenuPermissions active={activeKey === 'menu'} />
</MenuItemsProvider>
</TabLayout>
),
}),
];
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;
});
}
}

View File

@ -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);

View File

@ -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', {

View File

@ -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();

View File

@ -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)) {

View File

@ -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 <div style={{ maxHeight: '60vh', overflowY: 'auto' }}>{props.children}</div>;
@ -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: (
<TabLayout>
<GeneralPermissions active={activeKey === 'general' && active} />
</TabLayout>
),
},
{
key: 'menu',
label: t('Menu'),
children: (
<TabLayout>
<MenuItemsProvider>
<MenuPermissions active={activeKey === 'menu' && active} />
</MenuItemsProvider>
</TabLayout>
),
},
...(DataSourcePermissionManager
? [
{
key: 'dataSource',
label: t('Data sources'),
children: (
<TabLayout>
<MenuItemsProvider>
<DataSourcePermissionManager role={role} active={activeKey === 'dataSource' && active} />
</MenuItemsProvider>
</TabLayout>
),
},
]
: []),
],
[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<Role>(
() =>
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 (
<AvailableActionsProvider>
<Tabs type="card" activeKey={activeKey} onChange={(key) => setActiveKey(key)} items={items} />

View File

@ -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"

View File

@ -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<any>({} as any);
CurrentRolesContext.displayName = 'CurrentRolesContext';
export const DataSourcePermissionManager = ({ role }: any) => {
export const DataSourcePermissionManager = ({ role }) => {
return (
<Card data-testid="acl-pane-card" bordered={false}>
<CurrentRolesContext.Provider value={role}>

View File

@ -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: (
<TabLayout>
<DataSourcePermissionManager role={role} />
</TabLayout>
),
}));
this.app.use(DatabaseConnectionProvider);
this.app.pluginSettingsManager.add(NAMESPACE, {
title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`,