Merge branch 'next' into develop

This commit is contained in:
katherinehhh 2025-02-26 17:28:41 +08:00
commit 96cc54d927
30 changed files with 550 additions and 167 deletions

View File

@ -2,9 +2,7 @@
"version": "1.6.0-alpha.28",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"npmClientArgs": ["--ignore-engines"],
"command": {
"version": {
"forcePublish": true,

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,7 @@ export const PopupActionInitializer = (props) => {
openMode: defaultOpenMode,
refreshDataBlockRequest: true,
},
'x-decorator': 'ACLActionProvider',
properties: {
drawer: {
type: 'void',

View File

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

View File

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

View File

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

View File

@ -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';
},
};

View File

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

View File

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

View File

@ -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,
},
};

View File

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

View File

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

View File

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

View File

@ -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,
],
},
],

View File

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

View File

@ -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',
},
},
},
},
};

View File

@ -8,4 +8,3 @@
*/
export * from './CustomRequestConfigurationFields';
export * from './CustomRequestACL';

View File

@ -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,13 +61,15 @@ export const WorkbenchAction = withDynamicSchemaProps((props) => {
const fieldSchema = useFieldSchema();
const Component = useComponent(props?.targetComponent) || Action;
return (
<Component
className={cx(className, styles.action, 'nb-action-panel')}
{...others}
type="text"
icon={null}
title={<Button />}
confirmTitle={fieldSchema.title}
/>
<ACLActionProvider>
<Component
className={cx(className, styles.action, 'nb-action-panel')}
{...others}
type="text"
icon={null}
title={<Button />}
confirmTitle={fieldSchema.title}
/>
</ACLActionProvider>
);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@
*/
import { Plugin } from '@nocobase/client';
class PluginUISchemaStorageClient extends Plugin {
async load() {}
}

View File

@ -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'],
});

View File

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

View File

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