fix: acl scope support variables (#1660)
* fix: acl scope support variables * fix: parse filter * fix: dynamicComponent
This commit is contained in:
parent
9c4f1a8d7f
commit
5930218811
@ -1,7 +1,6 @@
|
|||||||
import { Action } from '@nocobase/resourcer';
|
import { Action } from '@nocobase/resourcer';
|
||||||
import { assign, Toposort, ToposortOptions } from '@nocobase/utils';
|
import { assign, parseFilter, Toposort, ToposortOptions } from '@nocobase/utils';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import parse from 'json-templates';
|
|
||||||
import compose from 'koa-compose';
|
import compose from 'koa-compose';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action';
|
import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action';
|
||||||
@ -135,7 +134,7 @@ export class ACL extends EventEmitter {
|
|||||||
|
|
||||||
if (params && resourcerAction.mergeParams) {
|
if (params && resourcerAction.mergeParams) {
|
||||||
const filteredParams = filterParams(ctx, resourceName, params);
|
const filteredParams = filterParams(ctx, resourceName, params);
|
||||||
const parsedParams = acl.parseJsonTemplate(filteredParams, ctx);
|
const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx);
|
||||||
|
|
||||||
ctx.permission.parsedParams = parsedParams;
|
ctx.permission.parsedParams = parsedParams;
|
||||||
ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams);
|
ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams);
|
||||||
@ -332,12 +331,25 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseJsonTemplate(json: any, ctx: any) {
|
async parseJsonTemplate(json: any, ctx: any) {
|
||||||
return parse(json)({
|
if (json.filter) {
|
||||||
ctx: {
|
ctx.logger?.info?.('parseJsonTemplate.raw', JSON.parse(JSON.stringify(json.filter)));
|
||||||
state: JSON.parse(JSON.stringify(ctx.state)),
|
const timezone = ctx?.get?.('x-timezone');
|
||||||
},
|
const state = JSON.parse(JSON.stringify(ctx.state));
|
||||||
});
|
const filter = await parseFilter(json.filter, {
|
||||||
|
timezone,
|
||||||
|
now: new Date().toISOString(),
|
||||||
|
vars: {
|
||||||
|
ctx: {
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
$user: async () => state.currentUser,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
json.filter = filter;
|
||||||
|
ctx.logger?.info?.('parseJsonTemplate.parsed', filter);
|
||||||
|
}
|
||||||
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware() {
|
middleware() {
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useCompile } from '../../schema-component';
|
||||||
|
import { useFilterOptions } from '../../schema-component/antd/filter';
|
||||||
|
import { useValues } from '../../schema-component/antd/filter/useValues';
|
||||||
|
import { Variable } from '../../schema-component/antd/variable';
|
||||||
|
|
||||||
|
interface GetOptionsParams {
|
||||||
|
schema: any;
|
||||||
|
operator: string;
|
||||||
|
maxDepth: number;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useOptions = (collectionName: string, { schema, operator, maxDepth, count = 1 }: GetOptionsParams) => {
|
||||||
|
if (count > maxDepth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = useFilterOptions(collectionName).map((option) => {
|
||||||
|
if ((option.type !== 'belongsTo' && option.type !== 'hasOne') || !option.target) {
|
||||||
|
return {
|
||||||
|
key: option.name,
|
||||||
|
value: option.name,
|
||||||
|
label: option.title,
|
||||||
|
// TODO: 现在是通过组件的名称来过滤能够被选择的选项,这样的坏处是不够精确,后续可以优化
|
||||||
|
disabled: schema?.['x-component'] !== option.schema['x-component'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const children =
|
||||||
|
useOptions(option.target, {
|
||||||
|
schema,
|
||||||
|
operator,
|
||||||
|
maxDepth,
|
||||||
|
count: count + 1,
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: option.name,
|
||||||
|
value: option.name,
|
||||||
|
label: option.title,
|
||||||
|
children,
|
||||||
|
disabled: children.every((child) => child.disabled),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useUserVariable = ({ schema, operator }) => {
|
||||||
|
const options = useOptions('users', { schema, operator, maxDepth: 1 }) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: `{{t("Current user")}}`,
|
||||||
|
value: '$user',
|
||||||
|
key: '$user',
|
||||||
|
disabled: options.every((option) => option.disabled),
|
||||||
|
children: options,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useVariableOptions = () => {
|
||||||
|
const { operator, schema } = useValues();
|
||||||
|
const userVariable = useUserVariable({ schema, operator });
|
||||||
|
|
||||||
|
if (!operator || !schema) return [];
|
||||||
|
|
||||||
|
return [userVariable];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FilterDynamicComponent(props) {
|
||||||
|
const { value, onChange, renderSchemaComponent } = props;
|
||||||
|
const options = useVariableOptions();
|
||||||
|
const compile = useCompile();
|
||||||
|
const scope = compile(options);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Variable.Input value={value} onChange={onChange} scope={scope}>
|
||||||
|
{renderSchemaComponent()}
|
||||||
|
</Variable.Input>
|
||||||
|
);
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { ISchema } from '@formily/react';
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useFormBlockContext } from '../../../block-provider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { useFilterOptions } from '../../../schema-component';
|
import { useFilterOptions } from '../../../schema-component';
|
||||||
|
import { FilterDynamicComponent } from '../FilterDynamicComponent';
|
||||||
import { RoleResourceCollectionContext } from '../RolesResourcesActions';
|
import { RoleResourceCollectionContext } from '../RolesResourcesActions';
|
||||||
|
|
||||||
export const rolesResourcesScopesCollection = {
|
export const rolesResourcesScopesCollection = {
|
||||||
@ -139,6 +140,7 @@ export const scopesSchema: ISchema = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
dynamicComponent: FilterDynamicComponent,
|
||||||
useProps() {
|
useProps() {
|
||||||
const ctx = useContext(RoleResourceCollectionContext);
|
const ctx = useContext(RoleResourceCollectionContext);
|
||||||
const options = useFilterOptions(ctx.name);
|
const options = useFilterOptions(ctx.name);
|
||||||
@ -267,6 +269,7 @@ export const scopesSchema: ISchema = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
dynamicComponent: FilterDynamicComponent,
|
||||||
useProps() {
|
useProps() {
|
||||||
const ctx = useContext(RoleResourceCollectionContext);
|
const ctx = useContext(RoleResourceCollectionContext);
|
||||||
const options = useFilterOptions(ctx.name);
|
const options = useFilterOptions(ctx.name);
|
||||||
|
@ -537,7 +537,7 @@ export class PluginACL extends Plugin {
|
|||||||
ctx.permission.can = false;
|
ctx.permission.can = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const filter = parseJsonTemplate(action?.params?.filter || {}, ctx);
|
const filter = await parseJsonTemplate(action?.params?.filter || {}, ctx);
|
||||||
const sourceInstance = await ctx.db.getRepository(collectionName).findOne({
|
const sourceInstance = await ctx.db.getRepository(collectionName).findOne({
|
||||||
filterByTk: resourceOf,
|
filterByTk: resourceOf,
|
||||||
filter,
|
filter,
|
||||||
|
Loading…
Reference in New Issue
Block a user