mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
96cc54d927
@ -2,9 +2,7 @@
|
||||
"version": "1.6.0-alpha.28",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": [
|
||||
"--ignore-engines"
|
||||
],
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
"command": {
|
||||
"version": {
|
||||
"forcePublish": true,
|
||||
|
@ -304,30 +304,32 @@ export const ACLActionProvider = (props) => {
|
||||
const collection = useCollection();
|
||||
const recordPkValue = useRecordPkValue();
|
||||
const resource = useResourceName();
|
||||
const { parseAction } = useACLRoleContext();
|
||||
const { parseAction, uiButtonSchemasBlacklist } = useACLRoleContext();
|
||||
const schema = useFieldSchema();
|
||||
const currentUid = schema['x-uid'];
|
||||
let actionPath = schema['x-acl-action'];
|
||||
const editablePath = ['create', 'update', 'destroy', 'importXlsx'];
|
||||
|
||||
if (!actionPath && resource && schema['x-action']) {
|
||||
if (!actionPath && resource && schema['x-action'] && editablePath.includes(schema['x-action'])) {
|
||||
actionPath = `${resource}:${schema['x-action']}`;
|
||||
}
|
||||
if (!actionPath?.includes(':')) {
|
||||
if (actionPath && !actionPath?.includes(':')) {
|
||||
actionPath = `${resource}:${actionPath}`;
|
||||
}
|
||||
|
||||
const params = useMemo(
|
||||
() => parseAction(actionPath, { schema, recordPkValue }),
|
||||
() => actionPath && parseAction(actionPath, { schema, recordPkValue }),
|
||||
[parseAction, actionPath, schema, recordPkValue],
|
||||
);
|
||||
|
||||
if (uiButtonSchemasBlacklist.includes(currentUid)) {
|
||||
return <ACLActionParamsContext.Provider value={false}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
if (!actionPath) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
if (!resource) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
if (!params) {
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export const DestroyActionInitializer = (props) => {
|
||||
const schema = {
|
||||
title: '{{ t("Delete") }}',
|
||||
'x-action': 'destroy',
|
||||
'x-acl-action': 'destroy',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useDestroyActionProps',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
|
@ -20,6 +20,7 @@ export const LinkActionInitializer = (props) => {
|
||||
'x-settings': 'actionSettings:link',
|
||||
'x-component': props?.['x-component'] || 'Action.Link',
|
||||
'x-use-component-props': 'useLinkActionProps',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
};
|
||||
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
|
@ -16,7 +16,11 @@ import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { useCollection_deprecated } from '../../../collection-manager';
|
||||
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { SchemaSettingsLinkageRules, SchemaSettingsModalItem } from '../../../schema-settings';
|
||||
import {
|
||||
SchemaSettingsLinkageRules,
|
||||
SchemaSettingsModalItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '../../../schema-settings';
|
||||
import { useURLAndHTMLSchema } from './useURLAndHTMLSchema';
|
||||
|
||||
export const SchemaSettingsActionLinkItem: FC = () => {
|
||||
@ -103,6 +107,7 @@ export const customizeLinkActionSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
SchemaSettingAccessControl,
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -28,6 +28,7 @@ export const PopupActionInitializer = (props) => {
|
||||
openMode: defaultOpenMode,
|
||||
refreshDataBlockRequest: true,
|
||||
},
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
|
@ -20,6 +20,7 @@ export const UpdateActionInitializer = (props) => {
|
||||
type: 'void',
|
||||
title: '{{ t("Edit") }}',
|
||||
'x-action': 'update',
|
||||
'x-acl-action': 'update',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:edit',
|
||||
'x-component': 'Action',
|
||||
|
@ -14,7 +14,7 @@ import { useCollection_deprecated } from '../../../collection-manager';
|
||||
import { useCollection } from '../../../data-source';
|
||||
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
||||
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
import { SchemaSettingsLinkageRules, SchemaSettingAccessControl } from '../../../schema-settings';
|
||||
import { useOpenModeContext } from '../../popup/OpenModeProvider';
|
||||
import { useCurrentPopupRecord } from '../../variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
@ -57,6 +57,7 @@ export const customizePopupActionSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
SchemaSettingAccessControl,
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -29,20 +29,12 @@ export class PMPlugin extends Plugin {
|
||||
}
|
||||
|
||||
addSettings() {
|
||||
// this.app.pluginSettingsManager.add('acl', {
|
||||
// title: '{{t("Access control")}}',
|
||||
// icon: 'LockOutlined',
|
||||
// Component: ACLPane,
|
||||
// aclSnippet: 'pm.acl.roles',
|
||||
// });
|
||||
|
||||
// Replaced by plugin-block-template
|
||||
// this.app.pluginSettingsManager.add('ui-schema-storage', {
|
||||
// title: '{{t("Block templates")}}',
|
||||
// icon: 'LayoutOutlined',
|
||||
// Component: BlockTemplatesPane,
|
||||
// aclSnippet: 'pm.ui-schema-storage.block-templates',
|
||||
// });
|
||||
this.app.pluginSettingsManager.add('ui-schema-storage', {
|
||||
title: '{{t("Block templates")}}',
|
||||
icon: 'LayoutOutlined',
|
||||
Component: BlockTemplatesPane,
|
||||
aclSnippet: 'pm.ui-schema-storage.block-templates',
|
||||
});
|
||||
this.app.pluginSettingsManager.add('system-settings', {
|
||||
icon: 'SettingOutlined',
|
||||
title: '{{t("System settings")}}',
|
||||
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 { useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { App } from 'antd';
|
||||
import { SchemaSettingsActionModalItem } from './SchemaSettings';
|
||||
import { useAPIClient } from '../api-client/hooks/useAPIClient';
|
||||
import { useRequest } from '../api-client';
|
||||
import { useACLContext } from '../acl';
|
||||
|
||||
export function AccessControl() {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const apiClient = useAPIClient();
|
||||
const resource = apiClient.resource('uiSchemas.roles', fieldSchema['x-uid']);
|
||||
const { message } = App.useApp();
|
||||
const { refresh, data }: any = useRequest(
|
||||
{
|
||||
url: `/uiSchemas/${fieldSchema['x-uid']}/roles:list`,
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
const { refresh: refreshRoleCheck } = useACLContext();
|
||||
const AccessControl = (
|
||||
<SchemaSettingsActionModalItem
|
||||
scope={t}
|
||||
title={t('Access control')}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
roles: {
|
||||
type: 'array',
|
||||
title: t('Roles'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
tooltip: t('If not set, all roles can see this action'),
|
||||
},
|
||||
'x-component': 'RemoteSelect',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
objectValue: true,
|
||||
dataSource: 'main',
|
||||
service: {
|
||||
resource: 'roles',
|
||||
},
|
||||
manual: false,
|
||||
fieldNames: {
|
||||
label: 'title',
|
||||
value: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
initialValues={{
|
||||
roles: data?.data,
|
||||
}}
|
||||
beforeOpen={() => !data && refresh()}
|
||||
onSubmit={async ({ roles }) => {
|
||||
await resource.set({ values: roles.map((v) => v.name) });
|
||||
await refreshRoleCheck();
|
||||
return message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return AccessControl;
|
||||
}
|
||||
|
||||
export const SchemaSettingAccessControl = {
|
||||
name: 'accessControl',
|
||||
Component: AccessControl,
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return fieldSchema['x-decorator'] === 'ACLActionProvider';
|
||||
},
|
||||
};
|
@ -725,8 +725,8 @@ export const SchemaSettingsActionModalItem: FC<SchemaSettingsActionModalItemProp
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
await onSubmit?.(cloneDeep(visibleValues));
|
||||
setVisible(false);
|
||||
await onSubmit?.(cloneDeep(visibleValues));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 { expect, test } from '@nocobase/test/e2e';
|
||||
import { accessControlActionWithTable } from './template';
|
||||
|
||||
test.describe('Access control', () => {
|
||||
test('popup、link、custom request support access control', async ({ page, mockPage, mockRecord }) => {
|
||||
const nocoPage = await mockPage(accessControlActionWithTable).waitForInit();
|
||||
await nocoPage.goto();
|
||||
await page.getByLabel('block-item-CardItem-users-').hover();
|
||||
//popup
|
||||
await page.getByLabel('action-Action-Popup-customize').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover();
|
||||
await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible();
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover();
|
||||
await page.mouse.move(300, 0);
|
||||
|
||||
//link
|
||||
await page.getByLabel('action-Action-Link-customize:').hover();
|
||||
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:link-users').hover();
|
||||
await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible();
|
||||
await page.mouse.move(300, 0);
|
||||
|
||||
// custom request
|
||||
await page.getByLabel('action-CustomRequestAction-').hover();
|
||||
await page.getByLabel('designer-schema-settings-CustomRequestAction-actionSettings:customRequest-users').hover();
|
||||
await expect(page.getByRole('menuitem', { name: 'Access control' })).toBeVisible();
|
||||
await page.mouse.move(300, 0);
|
||||
});
|
||||
test('access control with role ', async ({ page, mockPage, mockRecord }) => {
|
||||
const nocoPage = await mockPage(accessControlActionWithTable).waitForInit();
|
||||
await nocoPage.goto();
|
||||
await page.getByLabel('block-item-CardItem-users-').hover();
|
||||
//popup only member can see
|
||||
await page.getByLabel('action-Action-Popup-customize').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:popup-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Access control' }).click();
|
||||
await page.getByLabel('block-item-RemoteSelect-users').click();
|
||||
await page.getByText('Member').click();
|
||||
await page.getByRole('option', { name: 'Member' }).locator('div').click();
|
||||
await page.getByLabel('block-item-RemoteSelect-users').click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
//root 角色有权限
|
||||
await expect(page.getByLabel('action-Action-Popup-customize')).toBeVisible();
|
||||
|
||||
//切换 为admin
|
||||
await page.getByTestId('user-center-button').click();
|
||||
await page.getByText('Switch roleRoot').click();
|
||||
await page.getByText('Admin', { exact: true }).click();
|
||||
await expect(page.getByLabel('action-Action-Popup-customize')).not.toBeVisible();
|
||||
|
||||
// 切换 为 member
|
||||
|
||||
await page.getByTestId('user-center-button').click();
|
||||
await page.getByText('Switch roleAdmin').click();
|
||||
await page.getByText('Member').click();
|
||||
await expect(page.getByLabel('action-Action-Popup-customize')).toBeVisible();
|
||||
});
|
||||
});
|
@ -1964,3 +1964,254 @@ export const whenClearingARelationshipFieldTheValueOfTheAssociatedFieldShouldBeC
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const accessControlActionWithTable = {
|
||||
pageSchema: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
fvgd0c2akgf: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'page:addBlock',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
'0c4zy47hhyq': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
b1y881c771g: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
hccefuo80kx: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-acl-action': 'users:view',
|
||||
'x-decorator': 'DetailsBlockProvider',
|
||||
'x-use-decorator-props': 'useDetailsWithPaginationDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
dataSource: 'main',
|
||||
collection: 'users',
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 1,
|
||||
},
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:detailsWithPagination',
|
||||
'x-component': 'CardItem',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
fctprt7i7ut: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Details',
|
||||
'x-read-pretty': true,
|
||||
'x-use-component-props': 'useDetailsWithPaginationProps',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
b2xrbq4a060: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'details:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
e2ccvx3c7o5: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:popup',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
refreshDataBlockRequest: true,
|
||||
},
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-action-context': {
|
||||
dataSource: 'main',
|
||||
collection: 'users',
|
||||
},
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
drawer: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
tabs: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'popup:addTab',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
tab1: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'popup:common:addBlock',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
'x-uid': '3qfg5qfvsth',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'n0574ydwdua',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'm0039599o9q',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '5t7ol82dt74',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'gn76wlmk619',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
'3jat4vour4y': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
title: '{{ t("Custom request") }}',
|
||||
'x-component': 'CustomRequestAction',
|
||||
'x-action': 'customize:form:request',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:customRequest',
|
||||
'x-decorator': 'CustomRequestAction.Decorator',
|
||||
'x-action-settings': {
|
||||
onSuccess: {
|
||||
manualClose: false,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Request success")}}',
|
||||
},
|
||||
},
|
||||
type: 'void',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
'x-uid': 'gu0shkseqoa',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
'0tevnuro5d4': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Link") }}',
|
||||
'x-action': 'customize:link',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:link',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useLinkActionProps',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
'x-uid': 'swtrz2mpnm4',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
'x-uid': '32u6fnlj0ti',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'details:configureFields',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
'x-uid': '21ksski5wgs',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
pagination: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Pagination',
|
||||
'x-use-component-props': 'useDetailsPaginationProps',
|
||||
'x-app-version': '1.6.0-beta.9',
|
||||
'x-uid': '2cz3ilk7hmv',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
'x-uid': 'f6xosucy75q',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '33tsqeap83o',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'ppglkb7uvt8',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'af78vam04ux',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'askp9xe9uag',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'huon5vcb8u8',
|
||||
'x-async': true,
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ export * from './SchemaSettingsRenderEngine';
|
||||
export * from './hooks/useGetAriaLabelOfDesigner';
|
||||
export * from './hooks/useIsAllowToSetDefaultValue';
|
||||
export * from './SchemaSettingsLayoutItem';
|
||||
export * from './SchemaSettingAccessControl';
|
||||
export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter';
|
||||
export * from './isPatternDisabled';
|
||||
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
||||
|
@ -43,6 +43,23 @@ export async function checkAction(ctx, next) {
|
||||
}
|
||||
|
||||
const availableActions = ctx.app.acl.getAvailableActions();
|
||||
let uiButtonSchemasBlacklist = [];
|
||||
if (currentRole !== 'root') {
|
||||
const eqCurrentRoleList = await ctx.db
|
||||
.getRepository('uiButtonSchemasRoles')
|
||||
.find({
|
||||
filter: { 'roleName.$eq': currentRole },
|
||||
})
|
||||
.then((list) => list.map((v) => v.uid));
|
||||
|
||||
const NECurrentRoleList = await ctx.db
|
||||
.getRepository('uiButtonSchemasRoles')
|
||||
.find({
|
||||
filter: { 'roleName.$ne': currentRole },
|
||||
})
|
||||
.then((list) => list.map((v) => v.uid));
|
||||
uiButtonSchemasBlacklist = NECurrentRoleList.filter((uid) => !eqCurrentRoleList.includes(uid));
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
...role.toJSON(),
|
||||
@ -53,6 +70,7 @@ export async function checkAction(ctx, next) {
|
||||
allowConfigure: roleInstance.get('allowConfigure'),
|
||||
allowMenuItemIds: roleInstance.get('menuUiSchemas').map((uiSchema) => uiSchema.get('x-uid')),
|
||||
allowAnonymous: !!anonymous,
|
||||
uiButtonSchemasBlacklist,
|
||||
};
|
||||
|
||||
await next();
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Action, useAPIClient, useRequest, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Action, useAPIClient, useRequest, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { listByCurrentRoleUrl } from '../constants';
|
||||
@ -16,23 +16,23 @@ import { CustomRequestActionDesigner } from './CustomRequestActionDesigner';
|
||||
|
||||
export const CustomRequestActionACLDecorator = (props) => {
|
||||
const apiClient = useAPIClient();
|
||||
const isRoot = apiClient.auth.role === 'root';
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { data } = useRequest<{ data: string[] }>(
|
||||
{
|
||||
url: listByCurrentRoleUrl,
|
||||
},
|
||||
{
|
||||
manual: isRoot,
|
||||
cacheKey: listByCurrentRoleUrl,
|
||||
},
|
||||
);
|
||||
const requestId = fieldSchema?.['x-custom-request-id'] || fieldSchema?.['x-uid'];
|
||||
if (!isRoot && !data?.data?.includes(requestId)) {
|
||||
return null;
|
||||
}
|
||||
// const isRoot = apiClient.auth.role === 'root';
|
||||
// const fieldSchema = useFieldSchema();
|
||||
// const { data } = useRequest<{ data: string[] }>(
|
||||
// {
|
||||
// url: listByCurrentRoleUrl,
|
||||
// },
|
||||
// {
|
||||
// manual: isRoot,
|
||||
// cacheKey: listByCurrentRoleUrl,
|
||||
// },
|
||||
// );
|
||||
|
||||
return props.children;
|
||||
// // if (!isRoot && !data?.data?.includes(fieldSchema?.['x-uid'])) {
|
||||
// // return null;
|
||||
// // }
|
||||
|
||||
return <ACLActionProvider>{props.children}</ACLActionProvider>;
|
||||
};
|
||||
|
||||
const components = {
|
||||
|
@ -17,15 +17,13 @@ import {
|
||||
useCollection_deprecated,
|
||||
useDataSourceKey,
|
||||
useDesignable,
|
||||
useRequest,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import { App } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { listByCurrentRoleUrl } from '../constants';
|
||||
import { useCustomRequestVariableOptions, useGetCustomRequest } from '../hooks';
|
||||
import { useCustomRequestsResource } from '../hooks/useCustomRequestsResource';
|
||||
import { useTranslation } from '../locale';
|
||||
import { CustomRequestACLSchema, CustomRequestConfigurationFieldsSchema } from '../schemas';
|
||||
import { CustomRequestConfigurationFieldsSchema } from '../schemas';
|
||||
|
||||
export function CustomRequestSettingsItem() {
|
||||
const { t } = useTranslation();
|
||||
@ -89,63 +87,6 @@ export function CustomRequestSettingsItem() {
|
||||
);
|
||||
}
|
||||
|
||||
export function CustomRequestACL() {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const customRequestsResource = useCustomRequestsResource();
|
||||
const { message } = App.useApp();
|
||||
const { data, refresh } = useGetCustomRequest();
|
||||
const { dn } = useDesignable();
|
||||
const { refresh: refreshRoleCustomKeys } = useRequest<{ data: string[] }>(
|
||||
{
|
||||
url: listByCurrentRoleUrl,
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
cacheKey: listByCurrentRoleUrl,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SchemaSettingsActionModalItem
|
||||
title={t('Access control')}
|
||||
schema={CustomRequestACLSchema}
|
||||
initialValues={{
|
||||
roles: data?.data?.roles,
|
||||
}}
|
||||
beforeOpen={() => !data && refresh()}
|
||||
onSubmit={async ({ roles }) => {
|
||||
const isSelfRequest =
|
||||
!fieldSchema['x-custom-request-id'] || fieldSchema['x-custom-request-id'] === fieldSchema['x-uid'];
|
||||
|
||||
if (!isSelfRequest) {
|
||||
fieldSchema['x-custom-request-id'] = fieldSchema['x-uid'];
|
||||
await dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
'x-custom-request-id': fieldSchema['x-uid'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await customRequestsResource.updateOrCreate({
|
||||
values: {
|
||||
key: fieldSchema['x-uid'],
|
||||
roles,
|
||||
},
|
||||
filterKeys: ['key'],
|
||||
});
|
||||
refresh();
|
||||
refreshRoleCustomKeys();
|
||||
dn.refresh();
|
||||
return message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@ -160,10 +101,7 @@ export const customRequestActionSettings = new SchemaSettings({
|
||||
name: 'request settings',
|
||||
Component: CustomRequestSettingsItem,
|
||||
},
|
||||
{
|
||||
name: 'accessControl',
|
||||
Component: CustomRequestACL,
|
||||
},
|
||||
SchemaSettingAccessControl,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -19,8 +19,9 @@ import {
|
||||
useCollection,
|
||||
useCollectionRecord,
|
||||
useSchemaToolbar,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import { CustomRequestACL, CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
||||
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
||||
|
||||
export const customizeCustomRequestActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:customRequest',
|
||||
@ -64,8 +65,10 @@ export const customizeCustomRequestActionSettings = new SchemaSettings({
|
||||
Component: CustomRequestSettingsItem,
|
||||
},
|
||||
{
|
||||
name: 'accessControl',
|
||||
Component: CustomRequestACL,
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refreshDataBlockRequest',
|
||||
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 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 { DEFAULT_DATA_SOURCE_KEY } from '@nocobase/client';
|
||||
import { generateNTemplate } from '../locale';
|
||||
|
||||
export const CustomRequestACLSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roles: {
|
||||
type: 'array',
|
||||
title: generateNTemplate('Roles'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
tooltip: generateNTemplate('If not set, all roles can see this action'),
|
||||
},
|
||||
'x-component': 'RemoteSelect',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
objectValue: true,
|
||||
dataSource: DEFAULT_DATA_SOURCE_KEY,
|
||||
service: {
|
||||
resource: 'roles',
|
||||
},
|
||||
manual: false,
|
||||
fieldNames: {
|
||||
label: 'title',
|
||||
value: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -8,4 +8,3 @@
|
||||
*/
|
||||
|
||||
export * from './CustomRequestConfigurationFields';
|
||||
export * from './CustomRequestACL';
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client';
|
||||
import { Avatar } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useContext } from 'react';
|
||||
@ -46,9 +46,9 @@ function Button() {
|
||||
const compile = useCompile();
|
||||
const title = compile(fieldSchema.title);
|
||||
return layout === WorkbenchLayout.Grid ? (
|
||||
<div title={fieldSchema.title} className={cx(styles.avatar)}>
|
||||
<div title={title} className={cx(styles.avatar)}>
|
||||
<Avatar style={{ backgroundColor }} size={48} icon={<Icon type={icon} />} />
|
||||
<div className={cx(styles.title)}>{fieldSchema.title}</div>
|
||||
<div className={cx(styles.title)}>{title}</div>
|
||||
</div>
|
||||
) : (
|
||||
<span>{title}</span>
|
||||
@ -61,6 +61,7 @@ export const WorkbenchAction = withDynamicSchemaProps((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const Component = useComponent(props?.targetComponent) || Action;
|
||||
return (
|
||||
<ACLActionProvider>
|
||||
<Component
|
||||
className={cx(className, styles.action, 'nb-action-panel')}
|
||||
{...others}
|
||||
@ -69,5 +70,6 @@ export const WorkbenchAction = withDynamicSchemaProps((props) => {
|
||||
title={<Button />}
|
||||
confirmTitle={fieldSchema.title}
|
||||
/>
|
||||
</ACLActionProvider>
|
||||
);
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
SchemaSettingsActionLinkItem,
|
||||
useSchemaInitializer,
|
||||
ModalActionSchemaInitializerItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -30,6 +31,12 @@ export const workbenchActionSettingsCustomRequest = new SchemaSettings({
|
||||
name: 'editLink',
|
||||
Component: SchemaSettingsActionLinkItem,
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
sort: 800,
|
||||
name: 'd1',
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
ModalActionSchemaInitializerItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -32,6 +33,12 @@ export const workbenchActionSettingsLink = new SchemaSettings({
|
||||
name: 'editLink',
|
||||
Component: SchemaSettingsActionLinkItem,
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
sort: 800,
|
||||
name: 'd1',
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
useSchemaInitializer,
|
||||
useOpenModeContext,
|
||||
ModalActionSchemaInitializerItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -44,6 +45,12 @@ export const workbenchActionSettingsPopup = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
sort: 800,
|
||||
name: 'd1',
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
ModalActionSchemaInitializerItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -28,6 +29,12 @@ export const workbenchActionSettingsScanQrCode = new SchemaSettings({
|
||||
return { hasIconColor: true };
|
||||
},
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'd1',
|
||||
type: 'divider',
|
||||
|
@ -264,7 +264,7 @@ describe('view collection', function () {
|
||||
|
||||
const Role = await collectionRepository.create({
|
||||
values: {
|
||||
name: 'roles',
|
||||
name: 'my_roles',
|
||||
fields: [{ name: 'name', type: 'string' }],
|
||||
},
|
||||
context: {},
|
||||
@ -276,7 +276,7 @@ describe('view collection', function () {
|
||||
values: [{ name: 'u1' }, { name: 'u2' }],
|
||||
});
|
||||
|
||||
await db.getRepository('roles').create({
|
||||
await db.getRepository('my_roles').create({
|
||||
values: [{ name: 'r1' }, { name: 'r2' }],
|
||||
});
|
||||
|
||||
@ -327,7 +327,7 @@ describe('view collection', function () {
|
||||
collectionName: 'users',
|
||||
name: 'roles',
|
||||
type: 'belongsToMany',
|
||||
target: 'roles',
|
||||
target: 'my_roles',
|
||||
through: 'test_view',
|
||||
foreignKey: 'user_id',
|
||||
otherKey: 'role_id',
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import { Plugin } from '@nocobase/client';
|
||||
|
||||
class PluginUISchemaStorageClient extends Plugin {
|
||||
async load() {}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { defineCollection } from '@nocobase/database';
|
||||
|
||||
export default defineCollection({
|
||||
name: 'uiButtonSchemasRoles',
|
||||
dumpRules: 'required',
|
||||
migrationRules: ['overwrite', 'schema-only'],
|
||||
});
|
@ -39,5 +39,16 @@ export default {
|
||||
name: 'schema',
|
||||
defaultValue: {},
|
||||
},
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'roles',
|
||||
onDelete: 'CASCADE',
|
||||
through: 'uiButtonSchemasRoles',
|
||||
target: 'roles',
|
||||
foreignKey: 'uid',
|
||||
otherKey: 'roleName',
|
||||
sourceKey: 'x-uid',
|
||||
targetKey: 'name',
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
||||
|
@ -42,7 +42,7 @@ export class PluginUISchemaStorageServer extends Plugin {
|
||||
|
||||
this.app.acl.registerSnippet({
|
||||
name: 'ui.uiSchemas',
|
||||
actions: ['uiSchemas:*'],
|
||||
actions: ['uiSchemas:*', 'uiSchemas.roles:list', 'uiSchemas.roles:set'],
|
||||
});
|
||||
|
||||
db.on('uiSchemas.beforeCreate', function setUid(model) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user