chore: menu permissions & plugins setting permissions (#3822)

* chore: menu permissions

* chore: plugins setting  permissions

* chore: plugins setting  permissions

* chore: plugins setting  permissions
This commit is contained in:
katherinehhh 2024-03-27 12:03:24 +08:00 committed by GitHub
parent d04a60d386
commit d3bd0c71dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 156 additions and 108 deletions

View File

@ -5,11 +5,12 @@ 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 { RolesManagerContext } from '../RolesManagerProvider';
import { StrategyActions } from './StrategyActions'; import { StrategyActions } from './StrategyActions';
import { useACLTranslation } from '../locale'; import { useACLTranslation } from '../locale';
import { uid } from '@formily/shared'; import { PluginPermissions } from './PluginPermissions';
import { useMemoizedFn } from 'ahooks';
const SnippetCheckboxGroup = connect((props) => { const SnippetCheckboxGroup = connect((props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -61,25 +62,8 @@ export const GeneralPermissions: React.FC<{
const { role, setRole } = useContext(RolesManagerContext); const { role, setRole } = useContext(RolesManagerContext);
const { t } = useACLTranslation(); const { t } = useACLTranslation();
const api = useAPIClient(); const api = useAPIClient();
const { data } = useRequest( const pm = role?.snippets?.includes('pm.*');
() =>
api
.resource('roles')
.get({
filterByTk: role?.name,
})
.then((res) => {
const record = res?.data?.data;
record.snippets?.forEach((key: string) => {
record[key] = true;
});
return record;
}),
{
ready: active,
refreshDeps: [role?.name],
},
);
const update = useMemoizedFn(async (form: Form) => { const update = useMemoizedFn(async (form: Form) => {
await api.resource('roles').update({ await api.resource('roles').update({
filterByTk: role.name, filterByTk: role.name,
@ -90,37 +74,48 @@ export const GeneralPermissions: React.FC<{
}); });
const form = useMemo(() => { const form = useMemo(() => {
return createForm({ return createForm({
values: data, values: role,
effects() { effects() {
onFormValuesChange(async (form) => { onFormValuesChange(async (form) => {
await update(form); await update(form);
}); });
}, },
}); });
}, [data, update]); }, [role, update]);
return ( return (
<SchemaComponent <SchemaComponent
components={{ SnippetCheckboxGroup, StrategyActions }} components={{ SnippetCheckboxGroup, StrategyActions, PluginPermissions }}
scope={{ pm }}
schema={{ schema={{
type: 'void', type: 'void',
name: uid(), name: uid(),
'x-component': 'FormV2', 'x-component': 'div',
'x-component-props': {
form,
},
properties: { properties: {
snippets: { general: {
title: t('Configure permissions'), 'x-component': 'FormV2',
type: 'boolean', 'x-component-props': {
'x-decorator': 'FormItem', form,
'x-component': 'SnippetCheckboxGroup', },
properties: {
snippets: {
title: t('Configure permissions'),
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'SnippetCheckboxGroup',
},
},
}, },
allowNewMenu: { plugins: {
title: t('Menu permissions'), 'x-component': 'FormV2',
'x-decorator': 'FormItem', properties: {
'x-component': 'Checkbox', pluginSettings: {
'x-content': t('New menu items are allowed to be accessed by default.'), title: t('Plugin settings'),
'x-decorator': 'FormItem',
'x-component': 'PluginPermissions',
'x-visible': '{{pm}}',
},
},
}, },
}, },
}} }}

View File

@ -1,9 +1,12 @@
import { Checkbox, message, Table } from 'antd'; import { Checkbox, message, Table } from 'antd';
import { onFormValuesChange, createForm, Form } from '@formily/core';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import React, { useContext, useState } from 'react'; import React, { useContext, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { uid } from '@formily/shared';
import { useAPIClient, SchemaComponent, useRequest } from '@nocobase/client';
import { useStyles } from './style'; import { useStyles } from './style';
import { useAPIClient, useCollectionRecord, useRequest } from '@nocobase/client'; import { useMemoizedFn } from 'ahooks';
import { RolesManagerContext } from '../RolesManagerProvider'; import { RolesManagerContext } from '../RolesManagerProvider';
import { useMenuItems } from './MenuItemsProvider'; import { useMenuItems } from './MenuItemsProvider';
@ -43,7 +46,7 @@ export const MenuPermissions: React.FC<{
active: boolean; active: boolean;
}> = ({ active }) => { }> = ({ active }) => {
const { styles } = useStyles(); const { styles } = useStyles();
const { role } = useContext(RolesManagerContext); const { role, setRole } = useContext(RolesManagerContext);
const api = useAPIClient(); const api = useAPIClient();
const { items } = useMenuItems(); const { items } = useMenuItems();
const { t } = useTranslation(); const { t } = useTranslation();
@ -107,51 +110,89 @@ export const MenuPermissions: React.FC<{
}; };
}); });
}; };
const update = useMemoizedFn(async (form: Form) => {
await api.resource('roles').update({
filterByTk: role.name,
values: form.values,
});
setRole({ ...role, ...form.values });
message.success(t('Saved successfully'));
});
const form = useMemo(() => {
return createForm({
values: role,
effects() {
onFormValuesChange(async (form) => {
await update(form);
});
},
});
}, [role, update]);
return ( return (
<Table <>
className={styles} <SchemaComponent
loading={loading} schema={{
rowKey={'uid'} type: 'void',
pagination={false} name: uid(),
expandable={{ 'x-component': 'FormV2',
defaultExpandAllRows: true, 'x-component-props': {
}} form,
columns={[
{
dataIndex: 'title',
title: t('Menu item title'),
},
{
dataIndex: 'accessible',
title: (
<>
<Checkbox
checked={allChecked}
onChange={async (value) => {
if (allChecked) {
await resource.set({
values: [],
});
} else {
await resource.set({
values: allUids,
});
}
refresh();
message.success(t('Saved successfully'));
}}
/>{' '}
{t('Accessible')}
</>
),
render: (_, schema) => {
const checked = uids.includes(schema.uid);
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
}, },
}, properties: {
]} allowNewMenu: {
dataSource={translateTitle(items)} title: t('Menu permissions'),
/> 'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': t('New menu items are allowed to be accessed by default.'),
},
},
}}
/>
<Table
className={styles}
loading={loading}
rowKey={'uid'}
pagination={false}
expandable={{
defaultExpandAllRows: true,
}}
columns={[
{
dataIndex: 'title',
title: t('Menu item title'),
},
{
dataIndex: 'accessible',
title: (
<>
<Checkbox
checked={allChecked}
onChange={async (value) => {
if (allChecked) {
await resource.set({
values: [],
});
} else {
await resource.set({
values: allUids,
});
}
refresh();
message.success(t('Saved successfully'));
}}
/>{' '}
{t('Accessible')}
</>
),
render: (_, schema) => {
const checked = uids.includes(schema.uid);
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
},
},
]}
dataSource={translateTitle(items)}
/>
</>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useApp } from '@nocobase/client'; import { useApp, useRequest, useAPIClient } 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 { RolesManagerContext } from '../RolesManagerProvider';
@ -7,7 +7,6 @@ import { AvailableActionsProvider } from './AvailableActions';
import { GeneralPermissions } from './GeneralPermissions'; import { GeneralPermissions } from './GeneralPermissions';
import { MenuItemsProvider } from './MenuItemsProvider'; import { MenuItemsProvider } from './MenuItemsProvider';
import { MenuPermissions } from './MenuPermissions'; import { MenuPermissions } from './MenuPermissions';
import { PluginPermissions } from './PluginPermissions';
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>;
@ -16,7 +15,7 @@ const TabLayout: React.FC = (props) => {
export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { 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 } = useContext(RolesManagerContext); const { role, setRole } = useContext(RolesManagerContext);
const pm = role?.snippets?.includes('pm.*'); const pm = role?.snippets?.includes('pm.*');
const app = useApp(); const app = useApp();
const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager'); const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager');
@ -25,7 +24,7 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
() => [ () => [
{ {
key: 'general', key: 'general',
label: t('General'), label: t('System'),
children: ( children: (
<TabLayout> <TabLayout>
<GeneralPermissions active={activeKey === 'general' && active} /> <GeneralPermissions active={activeKey === 'general' && active} />
@ -43,19 +42,6 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
</TabLayout> </TabLayout>
), ),
}, },
...(pm
? [
{
key: 'plugin',
label: t('Plugin settings'),
children: (
<TabLayout>
<PluginPermissions active={activeKey === 'plugin' && active} />
</TabLayout>
),
},
]
: []),
...(DataSourcePermissionManager ...(DataSourcePermissionManager
? [ ? [
{ {
@ -74,10 +60,33 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
], ],
[pm, activeKey, active, t], [pm, activeKey, active, t],
); );
const api = useAPIClient();
const { data } = useRequest(
() =>
api
.resource('roles')
.get({
filterByTk: role?.name,
})
.then((res) => {
const record = res?.data?.data;
record.snippets?.forEach((key: string) => {
record[key] = true;
});
return record;
}),
{
ready: active && !!role,
refreshDeps: [role?.name],
},
);
useEffect(() => { useEffect(() => {
setActiveKey('general'); setActiveKey('general');
}, [role?.name]); }, [role?.name]);
useEffect(() => {
setRole(data);
}, [data]);
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} />

View File

@ -38,7 +38,7 @@ export const PluginPermissions: React.FC<{
const compile = useCompile(); const compile = useCompile();
const settings = app.pluginSettingsManager.getList(false); const settings = app.pluginSettingsManager.getList(false);
const allAclSnippets = app.pluginSettingsManager.getAclSnippets(); const allAclSnippets = app.pluginSettingsManager.getAclSnippets();
const [snippets, setSnippets] = useState<string[]>([]); const [snippets, setSnippets] = useState<string[]>(role?.snippets || []);
const flatPlugins = useMemo(() => { const flatPlugins = useMemo(() => {
return flatMap(settings, (item) => { return flatMap(settings, (item) => {
if (item.children) { if (item.children) {
@ -52,12 +52,11 @@ export const PluginPermissions: React.FC<{
() => snippets.includes('pm.*') && snippets.every((item) => !item.startsWith('!pm.')), () => snippets.includes('pm.*') && snippets.every((item) => !item.startsWith('!pm.')),
[snippets], [snippets],
); );
const { t } = useTranslation(); const { t } = useTranslation();
const { loading, refresh } = useRequest( const { loading, refresh } = useRequest(
{ {
resource: 'roles.snippets', resource: 'roles.snippets',
resourceOf: role.name, resourceOf: role?.name,
action: 'list', action: 'list',
params: { params: {
paginate: false, paginate: false,
@ -66,6 +65,7 @@ export const PluginPermissions: React.FC<{
{ {
ready: !!role && active, ready: !!role && active,
refreshDeps: [role?.name], refreshDeps: [role?.name],
manual: true,
onSuccess(data) { onSuccess(data) {
setSnippets( setSnippets(
data?.data.filter((v) => { data?.data.filter((v) => {
@ -75,7 +75,10 @@ export const PluginPermissions: React.FC<{
}, },
}, },
); );
const resource = api.resource('roles.snippets', role.name); if (!role) {
return;
}
const resource = api.resource('roles.snippets', role?.name);
const handleChange = async (checked, record) => { const handleChange = async (checked, record) => {
const childrenKeys = getChildrenKeys(record?.children, []); const childrenKeys = getChildrenKeys(record?.children, []);
const totalKeys = childrenKeys.concat(record.aclSnippet); const totalKeys = childrenKeys.concat(record.aclSnippet);
@ -129,7 +132,7 @@ export const PluginPermissions: React.FC<{
refresh(); refresh();
message.success(t('Saved successfully')); message.success(t('Saved successfully'));
}} }}
/>{' '} />
{t('Accessible')} {t('Accessible')}
</> </>
), ),