mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 03:56:16 +00:00
refactor(plugin-acl): extensible support for role permissions configuration UI (#5216)
This commit is contained in:
parent
2459991fbb
commit
44275b5cd7
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,25 @@
|
|||||||
|
|
||||||
import { createContext } from 'react';
|
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<{
|
export const RolesManagerContext = createContext<{
|
||||||
role: any;
|
role: Role;
|
||||||
setRole: (role: any) => void;
|
setRole: (role: Role) => void;
|
||||||
}>({
|
}>({
|
||||||
role: null,
|
role: null,
|
||||||
} as any);
|
} as any);
|
||||||
|
@ -8,11 +8,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin } from '@nocobase/client';
|
import { Plugin } from '@nocobase/client';
|
||||||
|
import { ACLSettingsUI } from './ACLSettingsUI';
|
||||||
import { RolesManagement } from './RolesManagement';
|
import { RolesManagement } from './RolesManagement';
|
||||||
import { RolesManager } from './roles-manager';
|
import { RolesManager } from './roles-manager';
|
||||||
|
|
||||||
export class PluginACLClient extends Plugin {
|
export class PluginACLClient extends Plugin {
|
||||||
rolesManager = new RolesManager();
|
rolesManager = new RolesManager();
|
||||||
|
settingsUI = new ACLSettingsUI();
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.pluginSettingsManager.add('users-permissions.roles', {
|
this.pluginSettingsManager.add('users-permissions.roles', {
|
||||||
|
@ -7,19 +7,19 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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 { 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 { Checkbox, message } from 'antd';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { useACLTranslation } from '../locale';
|
||||||
|
import { RolesManagerContext } from '../RolesManagerProvider';
|
||||||
import { PluginPermissions } from './PluginPermissions';
|
import { PluginPermissions } from './PluginPermissions';
|
||||||
|
import { StrategyActions } from './StrategyActions';
|
||||||
|
|
||||||
const SnippetCheckboxGroup = connect((props) => {
|
const SnippetCheckboxGroup = connect((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -65,9 +65,7 @@ const SnippetCheckboxGroup = connect((props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GeneralPermissions: React.FC<{
|
export const GeneralPermissions: React.FC = () => {
|
||||||
active: boolean;
|
|
||||||
}> = ({ active }) => {
|
|
||||||
const { role, setRole } = useContext(RolesManagerContext);
|
const { role, setRole } = useContext(RolesManagerContext);
|
||||||
const { t } = useACLTranslation();
|
const { t } = useACLTranslation();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
|
@ -7,17 +7,17 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Checkbox, message, Table } from 'antd';
|
import { createForm, Form, onFormValuesChange } from '@formily/core';
|
||||||
import { onFormValuesChange, createForm, Form } from '@formily/core';
|
|
||||||
import { uniq } from 'lodash';
|
|
||||||
import React, { useContext, useState, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { useAPIClient, SchemaComponent, useRequest } from '@nocobase/client';
|
import { SchemaComponent, useAPIClient, useRequest } from '@nocobase/client';
|
||||||
import { useStyles } from './style';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
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 { RolesManagerContext } from '../RolesManagerProvider';
|
||||||
import { useMenuItems } from './MenuItemsProvider';
|
import { useMenuItems } from './MenuItemsProvider';
|
||||||
|
import { useStyles } from './style';
|
||||||
|
|
||||||
const findUids = (items) => {
|
const findUids = (items) => {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
|
@ -7,15 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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 { Tabs } from 'antd';
|
||||||
import React, { useContext, useEffect, useMemo } from 'react';
|
import React, { useContext, useEffect, useMemo } from 'react';
|
||||||
import { RolesManagerContext } from '../RolesManagerProvider';
|
import PluginACLClient from '..';
|
||||||
|
import { Role, RolesManagerContext } from '../RolesManagerProvider';
|
||||||
import { useACLTranslation } from '../locale';
|
import { useACLTranslation } from '../locale';
|
||||||
import { AvailableActionsProvider } from './AvailableActions';
|
import { AvailableActionsProvider } from './AvailableActions';
|
||||||
import { GeneralPermissions } from './GeneralPermissions';
|
|
||||||
import { MenuItemsProvider } from './MenuItemsProvider';
|
|
||||||
import { MenuPermissions } from './MenuPermissions';
|
|
||||||
|
|
||||||
const TabLayout: React.FC = (props) => {
|
const TabLayout: React.FC = (props) => {
|
||||||
return <div style={{ maxHeight: '60vh', overflowY: 'auto' }}>{props.children}</div>;
|
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 { t } = useACLTranslation();
|
||||||
const [activeKey, setActiveKey] = React.useState('general');
|
const [activeKey, setActiveKey] = React.useState('general');
|
||||||
const { role, setRole } = useContext(RolesManagerContext);
|
const { role, setRole } = useContext(RolesManagerContext);
|
||||||
const pm = role?.snippets?.includes('pm.*');
|
const pluginACLClient = usePlugin(PluginACLClient);
|
||||||
const app = useApp();
|
|
||||||
const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager');
|
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
() => [
|
() => pluginACLClient.settingsUI.getPermissionsTabs({ t, activeKey, TabLayout, role }),
|
||||||
{
|
[activeKey, pluginACLClient.settingsUI, role, t],
|
||||||
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],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { data } = useRequest(
|
const { data } = useRequest<Role>(
|
||||||
() =>
|
() =>
|
||||||
api
|
api
|
||||||
.resource('roles')
|
.resource('roles')
|
||||||
@ -89,13 +50,15 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
|
|||||||
refreshDeps: [role?.name],
|
refreshDeps: [role?.name],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveKey('general');
|
setActiveKey('general');
|
||||||
}, [role?.name]);
|
}, [role?.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRole(data);
|
setRole(data);
|
||||||
}, [data]);
|
}, [data, setRole]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AvailableActionsProvider>
|
<AvailableActionsProvider>
|
||||||
<Tabs type="card" activeKey={activeKey} onChange={(key) => setActiveKey(key)} items={items} />
|
<Tabs type="card" activeKey={activeKey} onChange={(key) => setActiveKey(key)} items={items} />
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@nocobase/client": "1.x",
|
"@nocobase/client": "1.x",
|
||||||
"@nocobase/server": "1.x",
|
"@nocobase/server": "1.x",
|
||||||
"@nocobase/test": "1.x"
|
"@nocobase/test": "1.x",
|
||||||
|
"@nocobase/plugin-acl": "1.x"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Data model tools"
|
"Data model tools"
|
||||||
|
@ -9,20 +9,20 @@
|
|||||||
|
|
||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
|
import {
|
||||||
|
MenuConfigure,
|
||||||
|
ResourceActionProvider,
|
||||||
|
SchemaComponent,
|
||||||
|
SettingCenterProvider,
|
||||||
|
SettingsCenterConfigure,
|
||||||
|
} from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import {
|
|
||||||
SchemaComponent,
|
|
||||||
MenuConfigure,
|
|
||||||
SettingsCenterConfigure,
|
|
||||||
SettingCenterProvider,
|
|
||||||
ResourceActionProvider,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import { DataSourceTable } from './DataSourceTable';
|
import { DataSourceTable } from './DataSourceTable';
|
||||||
import { RoleConfigure } from './RoleConfigure';
|
|
||||||
import { StrategyActions } from './StrategyActions';
|
|
||||||
import { RolesResourcesActions } from './RolesResourcesActions';
|
|
||||||
import { RoleRecordProvider } from './PermisionProvider';
|
import { RoleRecordProvider } from './PermisionProvider';
|
||||||
|
import { RoleConfigure } from './RoleConfigure';
|
||||||
|
import { RolesResourcesActions } from './RolesResourcesActions';
|
||||||
|
import { StrategyActions } from './StrategyActions';
|
||||||
|
|
||||||
const schema2: ISchema = {
|
const schema2: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -36,7 +36,7 @@ const schema2: ISchema = {
|
|||||||
export const CurrentRolesContext = createContext<any>({} as any);
|
export const CurrentRolesContext = createContext<any>({} as any);
|
||||||
CurrentRolesContext.displayName = 'CurrentRolesContext';
|
CurrentRolesContext.displayName = 'CurrentRolesContext';
|
||||||
|
|
||||||
export const DataSourcePermissionManager = ({ role }: any) => {
|
export const DataSourcePermissionManager = ({ role }) => {
|
||||||
return (
|
return (
|
||||||
<Card data-testid="acl-pane-card" bordered={false}>
|
<Card data-testid="acl-pane-card" bordered={false}>
|
||||||
<CurrentRolesContext.Provider value={role}>
|
<CurrentRolesContext.Provider value={role}>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Plugin } from '@nocobase/client';
|
import { Plugin } from '@nocobase/client';
|
||||||
|
import PluginACLClient from '@nocobase/plugin-acl/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DatabaseConnectionProvider } from './DatabaseConnectionProvider';
|
import { DatabaseConnectionProvider } from './DatabaseConnectionProvider';
|
||||||
import { ThirdDataSource } from './ThridDataSource';
|
import { ThirdDataSource } from './ThridDataSource';
|
||||||
@ -21,10 +22,17 @@ import { NAMESPACE } from './locale';
|
|||||||
export class PluginDataSourceManagerClient extends Plugin {
|
export class PluginDataSourceManagerClient extends Plugin {
|
||||||
types = new Map();
|
types = new Map();
|
||||||
async load() {
|
async load() {
|
||||||
// 注册组件
|
// register a configuration item in the Users & Permissions management page
|
||||||
this.app.addComponents({
|
this.app.pm.get(PluginACLClient).settingsUI.addPermissionsTab(({ t, TabLayout, role }) => ({
|
||||||
DataSourcePermissionManager,
|
key: 'dataSource',
|
||||||
});
|
label: t('Data sources'),
|
||||||
|
children: (
|
||||||
|
<TabLayout>
|
||||||
|
<DataSourcePermissionManager role={role} />
|
||||||
|
</TabLayout>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
this.app.use(DatabaseConnectionProvider);
|
this.app.use(DatabaseConnectionProvider);
|
||||||
this.app.pluginSettingsManager.add(NAMESPACE, {
|
this.app.pluginSettingsManager.add(NAMESPACE, {
|
||||||
title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
Loading…
Reference in New Issue
Block a user