fix(custom-request): permission issues (#3306)

* fix(custom-request-plugin): cannot see custom request action in non-root role when acl doesn't set

* fix: list all roles

* feat: display all roles

* feat: support

* fix: remove unused code

* fix: options is null

* fix: translation

* fix: migration error

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Dunqing 2024-01-13 18:13:18 +08:00 committed by GitHub
parent 1adaa53c2b
commit 8ab69500c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 43 additions and 48 deletions

View File

@ -191,6 +191,7 @@
"Insert if not exists, or update": "Insert if not exists, or update", "Insert if not exists, or update": "Insert if not exists, or update",
"Determine whether a record exists by the following fields": "Determine whether a record exists by the following fields", "Determine whether a record exists by the following fields": "Determine whether a record exists by the following fields",
"Update": "Update", "Update": "Update",
"Update record": "Update record",
"View": "View", "View": "View",
"View record": "View record", "View record": "View record",
"Refresh": "Refresh", "Refresh": "Refresh",

View File

@ -200,6 +200,7 @@
"Action type": "操作类型", "Action type": "操作类型",
"Actions": "操作", "Actions": "操作",
"Update": "更新", "Update": "更新",
"Update record": "更新数据",
"View": "查看", "View": "查看",
"View record": "查看数据", "View record": "查看数据",
"Refresh": "刷新", "Refresh": "刷新",

View File

@ -13,6 +13,7 @@ export const BlockInitializer = (props) => {
const s = merge(schema || {}, item.schema || {}); const s = merge(schema || {}, item.schema || {});
item?.schemaInitialize?.(s); item?.schemaInitialize?.(s);
insert(s); insert(s);
props.onClick?.(s);
}} }}
/> />
); );

View File

@ -1,9 +1,9 @@
import { Action, useAPIClient, useRequest } from '@nocobase/client'; import { Action, useAPIClient, useRequest } from '@nocobase/client';
import React from 'react'; import React from 'react';
import { CustomRequestActionDesigner } from './CustomRequestActionDesigner';
import { useFieldSchema } from '@formily/react'; import { useFieldSchema } from '@formily/react';
import { listByCurrentRoleUrl } from '../constants'; import { listByCurrentRoleUrl } from '../constants';
import { useCustomizeRequestActionProps } from '../hooks'; import { useCustomizeRequestActionProps } from '../hooks';
import { CustomRequestActionDesigner } from './CustomRequestActionDesigner';
export const CustomRequestActionACLDecorator = (props) => { export const CustomRequestActionACLDecorator = (props) => {
const apiClient = useAPIClient(); const apiClient = useAPIClient();

View File

@ -6,7 +6,6 @@ import {
SchemaSettingsActionModalItem, SchemaSettingsActionModalItem,
actionSettingsItems, actionSettingsItems,
useCollection, useCollection,
useCurrentRoles,
useRequest, useRequest,
} from '@nocobase/client'; } from '@nocobase/client';
import React from 'react'; import React from 'react';
@ -74,14 +73,11 @@ function CustomRequestACL() {
}, },
); );
const currentRoles = useCurrentRoles();
return ( return (
<> <>
<SchemaSettingsActionModalItem <SchemaSettingsActionModalItem
title={t('Access Control')} title={t('Access Control')}
schema={CustomRequestACLSchema} schema={CustomRequestACLSchema}
scope={{ currentRoles }}
initialValues={{ initialValues={{
roles: data?.data?.roles, roles: data?.data?.roles,
}} }}

View File

@ -1,6 +1,6 @@
import { useCollection, useCollectionFilterOptions, useCompile } from '@nocobase/client'; import { useCollection, useCollectionFilterOptions, useCompile } from '@nocobase/client';
import { useTranslation } from '../locale';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from '../locale';
export const useCustomRequestVariableOptions = () => { export const useCustomRequestVariableOptions = () => {
const collection = useCollection(); const collection = useCollection();
@ -16,17 +16,17 @@ export const useCustomRequestVariableOptions = () => {
return [ return [
{ {
name: 'currentRecord', name: 'currentRecord',
title: t('Current record'), title: t('Current record', { ns: 'client' }),
children: [...fields], children: [...fields],
}, },
{ {
name: 'currentUser', name: 'currentUser',
title: t('Current user'), title: t('Current user', { ns: 'client' }),
children: userFields, children: userFields,
}, },
{ {
name: 'currentTime', name: 'currentTime',
title: t('Current time'), title: t('Current time', { ns: 'client' }),
children: null, children: null,
}, },
]; ];

View File

@ -24,12 +24,10 @@ export const useCustomizeRequestActionProps = () => {
const actionSchema = useFieldSchema(); const actionSchema = useFieldSchema();
const compile = useCompile(); const compile = useCompile();
const form = useForm(); const form = useForm();
const { fields, getField, getPrimaryKey } = useCollection(); const { getPrimaryKey } = useCollection();
const { field, resource, __parent, service } = useBlockRequestContext(); const { resource, __parent, service } = useBlockRequestContext();
const { getActiveFieldsName } = useFormActiveFields() || {};
const record = useRecord(); const record = useRecord();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { data, runAsync } = useGetCustomRequest();
const actionField = useField(); const actionField = useField();
const { setVisible } = useActionContext(); const { setVisible } = useActionContext();
const { modal, message } = App.useApp(); const { modal, message } = App.useApp();
@ -38,29 +36,14 @@ export const useCustomizeRequestActionProps = () => {
return { return {
async onClick() { async onClick() {
const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {}; const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {};
const options = data ? data?.data?.options : (await runAsync())?.data?.options;
if (!options?.['url']) {
return message.error(t('Please configure the request settings first'));
}
const xAction = actionSchema?.['x-action']; const xAction = actionSchema?.['x-action'];
if (skipValidator !== true && xAction === 'customize:form:request') { if (skipValidator !== true && xAction === 'customize:form:request') {
await form.submit(); await form.submit();
} }
let formValues = {}; let formValues = {};
const methods = ['POST', 'PUT', 'PATCH']; if (xAction === 'customize:form:request') {
if (xAction === 'customize:form:request' && methods.includes(options['method'])) { formValues = form.values;
const fieldNames = fields.map((field) => field.name);
const values = getFormValues({
filterByTk,
field,
form,
fieldNames,
getField,
resource,
actionFields: getActiveFieldsName?.('form') || [],
});
formValues = values;
} }
actionField.data ??= {}; actionField.data ??= {};

View File

@ -27,14 +27,13 @@ export const CustomRequestInitializer: React.FC<any> = (props) => {
<BlockInitializer <BlockInitializer
{...itemConfig} {...itemConfig}
item={itemConfig} item={itemConfig}
insert={async (s) => { onClick={async (s) => {
await customRequestsResource.updateOrCreate({ // create a custom request
await customRequestsResource.create({
values: { values: {
key: s['x-uid'], key: s['x-uid'],
}, },
filterKeys: ['key'],
}); });
await props?.insert(s);
}} }}
schema={schema} schema={schema}
/> />

View File

@ -10,15 +10,18 @@ export const CustomRequestACLSchema = {
'x-decorator-props': { 'x-decorator-props': {
tooltip: generateNTemplate('If not set, all roles can see this action'), tooltip: generateNTemplate('If not set, all roles can see this action'),
}, },
'x-component': 'Select', 'x-component': 'RemoteSelect',
'x-component-props': { 'x-component-props': {
multiple: true, multiple: true,
objectValue: true,
service: {
resource: 'roles',
},
manual: false,
fieldNames: { fieldNames: {
label: 'title', label: 'title',
value: 'name', value: 'name',
}, },
objectValue: true,
options: '{{ currentRoles }}',
}, },
}, },
}, },

View File

@ -1,9 +1,9 @@
import { Context, Next } from '@nocobase/actions'; import { Context, Next } from '@nocobase/actions';
import { parse } from '@nocobase/utils'; import { parse } from '@nocobase/utils';
import { appendArrayColumn } from '@nocobase/evaluators';
import axios from 'axios'; import axios from 'axios';
import CustomRequestPlugin from '../plugin'; import CustomRequestPlugin from '../plugin';
import { appendArrayColumn } from '@nocobase/evaluators';
const getHeaders = (headers: Record<string, any>) => { const getHeaders = (headers: Record<string, any>) => {
return Object.keys(headers).reduce((hds, key) => { return Object.keys(headers).reduce((hds, key) => {
@ -84,7 +84,10 @@ export async function send(this: CustomRequestPlugin, ctx: Context, next: Next)
ctx.withoutDataWrapping = true; ctx.withoutDataWrapping = true;
const { collectionName, url, headers = [], params = [], data = {}, ...options } = requestConfig.options; const { collectionName, url, headers = [], params = [], data = {}, ...options } = requestConfig.options || {};
if (!url) {
return ctx.throw(400, ctx.t('Please configure the request settings first', { ns: 'custom-request' }));
}
let currentRecordValues = {}; let currentRecordValues = {};
if (collectionName && typeof currentRecord.id !== 'undefined') { if (collectionName && typeof currentRecord.id !== 'undefined') {
const recordRepo = ctx.db.getRepository(collectionName); const recordRepo = ctx.db.getRepository(collectionName);

View File

@ -36,7 +36,7 @@ export class CustomRequestPlugin extends Plugin {
this.app.acl.registerSnippet({ this.app.acl.registerSnippet({
name: `ui.${this.name}`, name: `ui.${this.name}`,
actions: ['customRequests:*'], actions: ['customRequests:*', 'roles:list'],
}); });
this.app.acl.allow('customRequests', ['send', 'listByCurrentRole'], 'loggedIn'); this.app.acl.allow('customRequests', ['send', 'listByCurrentRole'], 'loggedIn');

View File

@ -23,10 +23,14 @@ export default class AddUsersPhoneMigration extends Migration {
type: DataTypes.STRING, type: DataTypes.STRING,
}); });
} }
await this.db.sequelize.getQueryInterface().addConstraint(tableNameWithSchema, { try {
type: 'unique', await this.db.sequelize.getQueryInterface().addConstraint(tableNameWithSchema, {
fields: [field.columnName()], type: 'unique',
}); fields: [field.columnName()],
});
} catch (error) {
//
}
this.db.removeCollection('users'); this.db.removeCollection('users');
} }

View File

@ -23,10 +23,14 @@ export default class AddUserNameMigration extends Migration {
type: DataTypes.STRING, type: DataTypes.STRING,
}); });
} }
await this.db.sequelize.getQueryInterface().addConstraint(tableNameWithSchema, { try {
type: 'unique', await this.db.sequelize.getQueryInterface().addConstraint(tableNameWithSchema, {
fields: [field.columnName()], type: 'unique',
}); fields: [field.columnName()],
});
} catch (error) {
//
}
this.db.removeCollection('users'); this.db.removeCollection('users');
} }
} }