cabloy 5.1.212 → 5.1.213
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/repo-docs/frontend/permission-formscene-action-visibility-guide.md +7 -5
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoResItemName%>.tsx_ +3 -3
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-record/src/dto/detailRecordSubjectResItem.tsx +3 -3
- package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordResItem.tsx +3 -3
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionOperationsRow.tsx +6 -5
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionView.tsx +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/actionCreate/controller.tsx +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/formFieldDetails/controller.tsx +20 -8
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/detailsPermissions.ts +6 -4
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/detail.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/details.ts +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/dialogForm.ts +3 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/test/lib/detailsPermissions.test.ts +196 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/permissions.ts +8 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.213
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -214,11 +214,11 @@ The current source confirms:
|
|
|
214
214
|
|
|
215
215
|
That means the details action family is scene-aware in a different way from `tableActionBulk`.
|
|
216
216
|
|
|
217
|
-
A practical
|
|
217
|
+
A practical details example from backend DTO metadata is:
|
|
218
218
|
|
|
219
219
|
```tsx
|
|
220
220
|
ZovaRender.detailsActionBulk('basic-details:actionCreate', {
|
|
221
|
-
permission: { formScene: ['create', 'edit'] },
|
|
221
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
222
222
|
});
|
|
223
223
|
```
|
|
224
224
|
|
|
@@ -226,15 +226,17 @@ and:
|
|
|
226
226
|
|
|
227
227
|
```tsx
|
|
228
228
|
ZovaRender.detailsActionRow('basic-details:actionDelete', {
|
|
229
|
-
permission: { formScene: ['create', 'edit'] },
|
|
229
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
230
230
|
});
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
+
For default detail CRUD actions, `permission.mode: 'formScene'` makes the intent explicit: the enclosing form scene controls visibility and no independent action permission check is performed. Custom detail actions should use `permission.mode: 'action'`; an explicit `actionInherit` also takes the action-checking path. If `mode` is omitted, the runtime uses the conservative action-checking path rather than inferring behavior from the action name.
|
|
234
|
+
|
|
233
235
|
So the correct distinction is:
|
|
234
236
|
|
|
235
237
|
- `tableActionBulk` does not currently support `formScene`
|
|
236
|
-
- `detailsActionBulk`
|
|
237
|
-
- `detailsActionRow`
|
|
238
|
+
- `detailsActionBulk` supports `formScene` and the detail-specific `mode`
|
|
239
|
+
- `detailsActionRow` supports `formScene` and the detail-specific `mode`
|
|
238
240
|
|
|
239
241
|
## 7. Why bulk actions are different today
|
|
240
242
|
|
|
@@ -19,7 +19,7 @@ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoRes
|
|
|
19
19
|
actions: [
|
|
20
20
|
ZovaRender.detailsActionBulk('<%=argv.detailsModuleName%>:actionCreate', {
|
|
21
21
|
dialogOptions: { title: $locale('Add<%=argv.detailDialogTitleCapitalize%>') },
|
|
22
|
-
permission: { formScene: ['create', 'edit'] },
|
|
22
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
23
23
|
}),
|
|
24
24
|
],
|
|
25
25
|
}),
|
|
@@ -48,10 +48,10 @@ export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%>
|
|
|
48
48
|
actions: [
|
|
49
49
|
ZovaRender.detailsActionRow('<%=argv.detailsModuleName%>:actionUpdate', {
|
|
50
50
|
dialogOptions: { title: $locale('Edit<%=argv.detailDialogTitleCapitalize%>') },
|
|
51
|
-
permission: { formScene: ['create', 'edit'] },
|
|
51
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
52
52
|
}),
|
|
53
53
|
ZovaRender.detailsActionRow('<%=argv.detailsModuleName%>:actionDelete', {
|
|
54
|
-
permission: { formScene: ['create', 'edit'] },
|
|
54
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
55
55
|
}),
|
|
56
56
|
],
|
|
57
57
|
}),
|
package/vona/src/suite/a-training/modules/training-record/src/dto/detailRecordSubjectResItem.tsx
CHANGED
|
@@ -19,7 +19,7 @@ export interface IDtoOptionsDetailRecordSubjectResItem extends IDecoratorDtoOpti
|
|
|
19
19
|
actions: [
|
|
20
20
|
ZovaRender.detailsActionBulk('basic-details:actionCreate', {
|
|
21
21
|
dialogOptions: { title: $locale('AddTrainingRecordSubject') },
|
|
22
|
-
permission: { formScene: ['create', 'edit'] },
|
|
22
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
23
23
|
}),
|
|
24
24
|
],
|
|
25
25
|
}),
|
|
@@ -53,10 +53,10 @@ export class DtoDetailRecordSubjectResItem extends $Dto.get(() => ModelSubject,
|
|
|
53
53
|
actions: [
|
|
54
54
|
ZovaRender.detailsActionRow('basic-details:actionUpdate', {
|
|
55
55
|
dialogOptions: { title: $locale('EditTrainingRecordSubject') },
|
|
56
|
-
permission: { formScene: ['create', 'edit'] },
|
|
56
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
57
57
|
}),
|
|
58
58
|
ZovaRender.detailsActionRow('basic-details:actionDelete', {
|
|
59
|
-
permission: { formScene: ['create', 'edit'] },
|
|
59
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
60
60
|
}),
|
|
61
61
|
],
|
|
62
62
|
}),
|
|
@@ -19,7 +19,7 @@ export interface IDtoOptionsDetailRecordResItem extends IDecoratorDtoOptions {}
|
|
|
19
19
|
actions: [
|
|
20
20
|
ZovaRender.detailsActionBulk('basic-details:actionCreate', {
|
|
21
21
|
dialogOptions: { title: $locale('AddTrainingRecord') },
|
|
22
|
-
permission: { formScene: ['create', 'edit'] },
|
|
22
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
23
23
|
}),
|
|
24
24
|
],
|
|
25
25
|
}),
|
|
@@ -54,10 +54,10 @@ export class DtoDetailRecordResItem extends $Dto.get(() => ModelRecord, {
|
|
|
54
54
|
actions: [
|
|
55
55
|
ZovaRender.detailsActionRow('basic-details:actionUpdate', {
|
|
56
56
|
dialogOptions: { title: $locale('EditTrainingRecord') },
|
|
57
|
-
permission: { formScene: ['create', 'edit'] },
|
|
57
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
58
58
|
}),
|
|
59
59
|
ZovaRender.detailsActionRow('basic-details:actionDelete', {
|
|
60
|
-
permission: { formScene: ['create', 'edit'] },
|
|
60
|
+
permission: { mode: 'formScene', formScene: ['create', 'edit'] },
|
|
61
61
|
}),
|
|
62
62
|
],
|
|
63
63
|
}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.171",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"zova-core": "^5.1.92",
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
49
|
+
"zova-suite-a-zova": "^5.1.170"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|
package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionOperationsRow.tsx
CHANGED
|
@@ -15,7 +15,6 @@ import { BeanBase } from 'zova';
|
|
|
15
15
|
import { TableCell } from 'zova-module-a-table';
|
|
16
16
|
|
|
17
17
|
import { filterDetailsRowActions } from '../lib/detailsPermissions.js';
|
|
18
|
-
import { checkFormScene } from '../lib/utils.js';
|
|
19
18
|
|
|
20
19
|
declare module 'zova-module-a-openapi' {
|
|
21
20
|
export interface IResourceDetailsActionRowRecord {
|
|
@@ -39,11 +38,13 @@ export class TableCellActionOperationsRow extends BeanBase implements ITableCell
|
|
|
39
38
|
const actions = options.actions;
|
|
40
39
|
if (!actions || actions.length === 0) return false;
|
|
41
40
|
const $$details = $celScope.$$details!;
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const actionsAllowed = filterDetailsRowActions(
|
|
42
|
+
$$details.formScene,
|
|
43
|
+
$$details.checkPermission.bind($$details),
|
|
44
|
+
actions,
|
|
45
|
+
);
|
|
45
46
|
const renders: TypeTableCellRenderComponent[] = [];
|
|
46
|
-
for (const action of
|
|
47
|
+
for (const action of actionsAllowed) {
|
|
47
48
|
const actionName = action.name;
|
|
48
49
|
const actionRender = action.render;
|
|
49
50
|
if (!actionRender) throw new Error(`should specify action render: ${actionName}`);
|
package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx
CHANGED
|
@@ -53,6 +53,7 @@ export class TableCellActionUpdate extends BeanBase implements ITableCellRender
|
|
|
53
53
|
icon: options.dialogOptions?.icon,
|
|
54
54
|
title: options.dialogOptions?.title ?? this.scope.locale.EditDetail(),
|
|
55
55
|
dialogOptions: options.dialogOptions,
|
|
56
|
+
checkPermission: $$details.checkPermission.bind($$details),
|
|
56
57
|
onSubmitData: (
|
|
57
58
|
data: TypeFormOnSubmitData<Record<string, any>>,
|
|
58
59
|
dialog: AppModalItem,
|
|
@@ -53,6 +53,7 @@ export class TableCellActionView extends BeanBase implements ITableCellRender {
|
|
|
53
53
|
icon: options.dialogOptions?.icon,
|
|
54
54
|
title: options.dialogOptions?.title ?? this.scope.locale.ViewDetail(),
|
|
55
55
|
dialogOptions: options.dialogOptions,
|
|
56
|
+
checkPermission: $$details.checkPermission.bind($$details),
|
|
56
57
|
} satisfies IDialogFormOptions);
|
|
57
58
|
serverDetail.openDialogForm();
|
|
58
59
|
}}
|
package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/actionCreate/controller.tsx
CHANGED
|
@@ -47,6 +47,7 @@ export class ControllerActionCreate extends BeanControllerBase {
|
|
|
47
47
|
icon: this.$props.dialogOptions?.icon,
|
|
48
48
|
title: this.$props.dialogOptions?.title ?? this.scope.locale.AddDetail(),
|
|
49
49
|
dialogOptions: this.$props.dialogOptions,
|
|
50
|
+
checkPermission: $$details.checkPermission.bind($$details),
|
|
50
51
|
onSubmitData: (
|
|
51
52
|
data: TypeFormOnSubmitData<Record<string, any>>,
|
|
52
53
|
dialog: AppModalItem,
|
|
@@ -81,14 +81,26 @@ export class ControllerFormFieldDetails extends BeanControllerBase {
|
|
|
81
81
|
// blocks
|
|
82
82
|
const blocks = schemaRow?.rest?.blocks;
|
|
83
83
|
if (!blocks || blocks.length === 0) return;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
const inheritedCheckPermission = (
|
|
85
|
+
formFieldRenderContext.celScope as {
|
|
86
|
+
checkPermission?: TypeDetailsCheckPermission;
|
|
87
|
+
}
|
|
88
|
+
).checkPermission;
|
|
89
|
+
const checkPermission: TypeDetailsCheckPermission =
|
|
90
|
+
inheritedCheckPermission ??
|
|
91
|
+
createDetailsPermissionChecker(
|
|
92
|
+
() =>
|
|
93
|
+
(formFieldRenderContext.celScope as { permissions?: TypeOpenapiPermissions }).permissions,
|
|
94
|
+
() => $$formField.$$form.formState.values as Record<string, unknown>,
|
|
95
|
+
(permissions, actionName, permissionHint, currentData) => {
|
|
96
|
+
return this.$passport.checkPermission(
|
|
97
|
+
permissions,
|
|
98
|
+
actionName,
|
|
99
|
+
permissionHint,
|
|
100
|
+
currentData,
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
92
104
|
const domBlocks: VNode[] = [];
|
|
93
105
|
blocks.forEach((block, index) => {
|
|
94
106
|
const options = deepExtend(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
IPermissionHintDetailsAction,
|
|
3
3
|
IResourceRenderDetailsActionBulkOptionsAction,
|
|
4
4
|
IResourceRenderDetailsActionRowOptionsAction,
|
|
5
5
|
TypeFormScene,
|
|
@@ -12,7 +12,7 @@ import { checkFormScene } from './utils.js';
|
|
|
12
12
|
|
|
13
13
|
export interface IDetailsPermissionAction {
|
|
14
14
|
name?: string;
|
|
15
|
-
options?: { permission?:
|
|
15
|
+
options?: { permission?: IPermissionHintDetailsAction };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function createDetailsPermissionChecker(
|
|
@@ -21,7 +21,7 @@ export function createDetailsPermissionChecker(
|
|
|
21
21
|
checkPermission: (
|
|
22
22
|
permissions: TypeOpenapiPermissions | undefined,
|
|
23
23
|
actionName?: string,
|
|
24
|
-
permissionHint?:
|
|
24
|
+
permissionHint?: IPermissionHintDetailsAction,
|
|
25
25
|
currentData?: Record<string, unknown>,
|
|
26
26
|
) => boolean,
|
|
27
27
|
): TypeDetailsCheckPermission {
|
|
@@ -36,7 +36,9 @@ export function isDetailsActionAllowed(
|
|
|
36
36
|
action: IDetailsPermissionAction,
|
|
37
37
|
): boolean {
|
|
38
38
|
const permissionHint = action.options?.permission;
|
|
39
|
-
|
|
39
|
+
if (!checkFormScene(formScene, permissionHint)) return false;
|
|
40
|
+
if (permissionHint?.mode === 'formScene' && !permissionHint.actionInherit) return true;
|
|
41
|
+
return checkPermission(action.name, permissionHint);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export function filterDetailsActions<TAction extends IDetailsPermissionAction>(
|
|
@@ -3,6 +3,7 @@ import 'zova-module-a-form';
|
|
|
3
3
|
import 'zova-module-a-openapi';
|
|
4
4
|
|
|
5
5
|
import type { ServiceDetail } from '../service/detail.jsx';
|
|
6
|
+
import type { TypeDetailsCheckPermission } from './details.js';
|
|
6
7
|
|
|
7
8
|
declare module 'zova-module-a-openapi' {
|
|
8
9
|
export interface IJsxRenderContextDetail<TData extends {} = {}> {
|
|
@@ -11,6 +12,7 @@ declare module 'zova-module-a-openapi' {
|
|
|
11
12
|
|
|
12
13
|
export interface IDetailScope {
|
|
13
14
|
$$detail?: ServiceDetail;
|
|
15
|
+
checkPermission?: TypeDetailsCheckPermission;
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import 'zova-module-a-table';
|
|
2
2
|
import 'zova-module-a-openapi';
|
|
3
|
-
import type { IDetailsScope,
|
|
3
|
+
import type { IDetailsScope, IPermissionHintDetailsAction } from 'zova-module-a-openapi';
|
|
4
4
|
|
|
5
5
|
import { ControllerBlockDetails } from '../component/blockDetails/controller.jsx';
|
|
6
6
|
|
|
7
7
|
export type TypeDetailsCheckPermission = (
|
|
8
8
|
actionName?: string,
|
|
9
|
-
permissionHint?:
|
|
9
|
+
permissionHint?: IPermissionHintDetailsAction,
|
|
10
10
|
) => boolean;
|
|
11
11
|
|
|
12
12
|
declare module 'zova-module-a-openapi' {
|
|
@@ -3,6 +3,8 @@ import { IIconRecord } from 'zova-module-a-icon';
|
|
|
3
3
|
import { TypeFormScene, TypeFormSchemaScene } from 'zova-module-a-openapi';
|
|
4
4
|
import { AppModalItem, IModalDialogOptions } from 'zova-module-basic-app';
|
|
5
5
|
|
|
6
|
+
import type { TypeDetailsCheckPermission } from './details.js';
|
|
7
|
+
|
|
6
8
|
export interface IDialogFormOptions<TData extends {} = {}> {
|
|
7
9
|
locale: {
|
|
8
10
|
Cancel: () => string;
|
|
@@ -15,6 +17,7 @@ export interface IDialogFormOptions<TData extends {} = {}> {
|
|
|
15
17
|
dialogOptions?: IModalDialogOptions;
|
|
16
18
|
icon?: keyof IIconRecord;
|
|
17
19
|
title: string;
|
|
20
|
+
checkPermission?: TypeDetailsCheckPermission;
|
|
18
21
|
onSubmitData?: (
|
|
19
22
|
data: TypeFormOnSubmitData<TData>,
|
|
20
23
|
dialogInstance: AppModalItem,
|
package/zova/src/suite/cabloy-basic/modules/basic-details/test/lib/detailsPermissions.test.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IPermissionHintDetailsAction,
|
|
3
|
+
IPermissionHintGeneral,
|
|
4
|
+
TypeOpenapiPermissions,
|
|
5
|
+
} from 'zova-module-a-openapi';
|
|
2
6
|
|
|
3
7
|
import assert from 'node:assert/strict';
|
|
4
8
|
import test from 'node:test';
|
|
@@ -123,3 +127,194 @@ test('row details actions use the same enclosing Resource checker and form-scene
|
|
|
123
127
|
);
|
|
124
128
|
assert.deepEqual(calls, ['allowed', 'denied']);
|
|
125
129
|
});
|
|
130
|
+
|
|
131
|
+
test('form-scene CRUD actions do not invoke the enclosing permission checker', () => {
|
|
132
|
+
const calls: string[] = [];
|
|
133
|
+
const actions = ['create', 'update', 'delete'].map(name => ({
|
|
134
|
+
name,
|
|
135
|
+
options: { permission: { mode: 'formScene', formScene: ['create', 'edit'] } },
|
|
136
|
+
}));
|
|
137
|
+
const allowed = filterDetailsRowActions(
|
|
138
|
+
'edit',
|
|
139
|
+
actionName => {
|
|
140
|
+
calls.push(actionName!);
|
|
141
|
+
return false;
|
|
142
|
+
},
|
|
143
|
+
actions as any,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
assert.deepEqual(
|
|
147
|
+
allowed.map(action => action.name),
|
|
148
|
+
['create', 'update', 'delete'],
|
|
149
|
+
);
|
|
150
|
+
assert.deepEqual(calls, []);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('form-scene CRUD actions are filtered before permission checks in view', () => {
|
|
154
|
+
const calls: string[] = [];
|
|
155
|
+
const action = {
|
|
156
|
+
name: 'update',
|
|
157
|
+
options: { permission: { mode: 'formScene', formScene: ['create', 'edit'] } },
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const allowed = filterDetailsRowActions(
|
|
161
|
+
'view',
|
|
162
|
+
actionName => {
|
|
163
|
+
calls.push(actionName!);
|
|
164
|
+
return true;
|
|
165
|
+
},
|
|
166
|
+
[action] as any,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
assert.deepEqual(allowed, []);
|
|
170
|
+
assert.deepEqual(calls, []);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('action mode uses the literal action name and inherited hint', () => {
|
|
174
|
+
const calls: Array<[string | undefined, IPermissionHintDetailsAction | undefined]> = [];
|
|
175
|
+
const permissionHint = {
|
|
176
|
+
mode: 'action',
|
|
177
|
+
actionInherit: 'approve',
|
|
178
|
+
formScene: ['create', 'edit'],
|
|
179
|
+
};
|
|
180
|
+
const allowed = filterDetailsRowActions(
|
|
181
|
+
'edit',
|
|
182
|
+
(actionName, hint) => {
|
|
183
|
+
calls.push([actionName, hint]);
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
[{ name: 'approve', options: { permission: permissionHint } }] as any,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
assert.deepEqual(
|
|
190
|
+
allowed.map(action => action.name),
|
|
191
|
+
['approve'],
|
|
192
|
+
);
|
|
193
|
+
assert.deepEqual(calls, [['approve', permissionHint]]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('omitted mode checks custom actions without inferring CRUD permissions', () => {
|
|
197
|
+
const calls: string[] = [];
|
|
198
|
+
const actions = ['create', 'update', 'delete'].map(name => ({ name }));
|
|
199
|
+
const allowed = filterDetailsRowActions(
|
|
200
|
+
'create',
|
|
201
|
+
actionName => {
|
|
202
|
+
calls.push(actionName!);
|
|
203
|
+
return actionName === 'update';
|
|
204
|
+
},
|
|
205
|
+
actions as any,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
assert.deepEqual(
|
|
209
|
+
allowed.map(action => action.name),
|
|
210
|
+
['update'],
|
|
211
|
+
);
|
|
212
|
+
assert.deepEqual(calls, ['create', 'update', 'delete']);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('form-scene mode with action inheritance still invokes the checker', () => {
|
|
216
|
+
const calls: Array<[string | undefined, IPermissionHintDetailsAction | undefined]> = [];
|
|
217
|
+
const permissionHint = {
|
|
218
|
+
mode: 'formScene',
|
|
219
|
+
actionInherit: 'grant',
|
|
220
|
+
formScene: ['create', 'edit'],
|
|
221
|
+
};
|
|
222
|
+
const allowed = filterDetailsRowActions(
|
|
223
|
+
'create',
|
|
224
|
+
(actionName, hint) => {
|
|
225
|
+
calls.push([actionName, hint]);
|
|
226
|
+
return actionName === 'update';
|
|
227
|
+
},
|
|
228
|
+
[{ name: 'update', options: { permission: permissionHint } }] as any,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
assert.deepEqual(
|
|
232
|
+
allowed.map(action => action.name),
|
|
233
|
+
['update'],
|
|
234
|
+
);
|
|
235
|
+
assert.deepEqual(calls, [['update', permissionHint]]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('view scene hides nested write actions before permission checks', () => {
|
|
239
|
+
const calls: string[] = [];
|
|
240
|
+
const actions = [
|
|
241
|
+
{
|
|
242
|
+
name: 'create',
|
|
243
|
+
options: { permission: { mode: 'formScene', formScene: ['create', 'edit'] } },
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'update',
|
|
247
|
+
options: { permission: { mode: 'formScene', formScene: ['create', 'edit'] } },
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'delete',
|
|
251
|
+
options: { permission: { mode: 'formScene', formScene: ['create', 'edit'] } },
|
|
252
|
+
},
|
|
253
|
+
{ name: 'view', options: { permission: { actionInherit: 'view' } } },
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const allowed = filterDetailsRowActions(
|
|
257
|
+
'view',
|
|
258
|
+
actionName => {
|
|
259
|
+
calls.push(actionName!);
|
|
260
|
+
return actionName === 'view';
|
|
261
|
+
},
|
|
262
|
+
actions as any,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
assert.deepEqual(
|
|
266
|
+
allowed.map(action => action.name),
|
|
267
|
+
['view'],
|
|
268
|
+
);
|
|
269
|
+
assert.deepEqual(calls, ['view']);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('view scene preserves explicit inherited write actions', () => {
|
|
273
|
+
const calls: string[] = [];
|
|
274
|
+
const actions = [
|
|
275
|
+
{ name: 'update', options: { permission: { actionInherit: 'grant', formScene: ['view'] } } },
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
const allowed = filterDetailsRowActions(
|
|
279
|
+
'view',
|
|
280
|
+
(actionName, permissionHint) => {
|
|
281
|
+
calls.push(actionName!);
|
|
282
|
+
return actionName === 'update' && permissionHint?.actionInherit === 'grant';
|
|
283
|
+
},
|
|
284
|
+
actions as any,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
assert.deepEqual(
|
|
288
|
+
allowed.map(action => action.name),
|
|
289
|
+
['update'],
|
|
290
|
+
);
|
|
291
|
+
assert.deepEqual(calls, ['update']);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('explicit action inheritance and custom actions are preserved', () => {
|
|
295
|
+
const calls: Array<[string | undefined, IPermissionHintGeneral | undefined]> = [];
|
|
296
|
+
const explicitHint = { actionInherit: 'grant', formScene: ['create', 'edit'] };
|
|
297
|
+
const customHint = { mode: 'action', formScene: ['create', 'edit'] };
|
|
298
|
+
const actions = [
|
|
299
|
+
{ name: 'update', options: { permission: explicitHint } },
|
|
300
|
+
{ name: 'archive', options: { permission: customHint } },
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
const allowed = filterDetailsRowActions(
|
|
304
|
+
'create',
|
|
305
|
+
(actionName, permissionHint) => {
|
|
306
|
+
calls.push([actionName, permissionHint]);
|
|
307
|
+
return true;
|
|
308
|
+
},
|
|
309
|
+
actions as any,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
assert.deepEqual(
|
|
313
|
+
allowed.map(action => action.name),
|
|
314
|
+
['update', 'archive'],
|
|
315
|
+
);
|
|
316
|
+
assert.deepEqual(calls, [
|
|
317
|
+
['update', explicitHint],
|
|
318
|
+
['archive', customHint],
|
|
319
|
+
]);
|
|
320
|
+
});
|
|
@@ -52,6 +52,12 @@ export interface IPermissionHintTableActionBulk {
|
|
|
52
52
|
public?: boolean;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export
|
|
55
|
+
export type TypeDetailsPermissionMode = 'formScene' | 'action';
|
|
56
56
|
|
|
57
|
-
export interface
|
|
57
|
+
export interface IPermissionHintDetailsAction extends IPermissionHintGeneral {
|
|
58
|
+
mode?: TypeDetailsPermissionMode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface IPermissionHintDetailsActionRow extends IPermissionHintDetailsAction {}
|
|
62
|
+
|
|
63
|
+
export interface IPermissionHintDetailsActionBulk extends IPermissionHintDetailsAction {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.170",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "zova",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"zova-module-a-logger": "^5.1.28",
|
|
22
22
|
"zova-module-a-meta": "^5.1.22",
|
|
23
23
|
"zova-module-a-model": "^5.1.38",
|
|
24
|
-
"zova-module-a-openapi": "^5.1.
|
|
24
|
+
"zova-module-a-openapi": "^5.1.57",
|
|
25
25
|
"zova-module-a-router": "^5.1.42",
|
|
26
26
|
"zova-module-a-routerstack": "^5.1.29",
|
|
27
27
|
"zova-module-a-routertabs": "^5.1.35",
|