cabloy 5.1.211 → 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 +12 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +3 -3
- package/repo-agent-governance/skills/cabloy-contract-loop/SKILL.md +2 -2
- 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
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
{
|
|
61
61
|
"adapter": "codex",
|
|
62
62
|
"category": "skill",
|
|
63
|
-
"sha256": "
|
|
63
|
+
"sha256": "27888ed8bb2c177a7bbe836a494d6185580f2e12e9de809809e3b38820084f94",
|
|
64
64
|
"source": "skills/cabloy-contract-loop/SKILL.md",
|
|
65
65
|
"target": ".agents/skills/cabloy-contract-loop/SKILL.md"
|
|
66
66
|
},
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
{
|
|
397
397
|
"adapter": "claude",
|
|
398
398
|
"category": "skill",
|
|
399
|
-
"sha256": "
|
|
399
|
+
"sha256": "27888ed8bb2c177a7bbe836a494d6185580f2e12e9de809809e3b38820084f94",
|
|
400
400
|
"source": "skills/cabloy-contract-loop/SKILL.md",
|
|
401
401
|
"target": ".claude/skills/cabloy-contract-loop/SKILL.md"
|
|
402
402
|
},
|
|
@@ -746,7 +746,7 @@
|
|
|
746
746
|
{
|
|
747
747
|
"adapter": "cursor",
|
|
748
748
|
"category": "skill",
|
|
749
|
-
"sha256": "
|
|
749
|
+
"sha256": "27888ed8bb2c177a7bbe836a494d6185580f2e12e9de809809e3b38820084f94",
|
|
750
750
|
"source": "skills/cabloy-contract-loop/SKILL.md",
|
|
751
751
|
"target": ".cursor/skills/cabloy-contract-loop/SKILL.md"
|
|
752
752
|
},
|
|
@@ -220,13 +220,13 @@ Use this when the frontend consumes generated API contracts.
|
|
|
220
220
|
|
|
221
221
|
For AI development, **default to managed isolated generation**, even when a development Swagger service is currently reachable. An independently managed development service may stop at any time; owning the service lifecycle keeps the generation task continuous and its contract source consistent.
|
|
222
222
|
|
|
223
|
-
1.
|
|
223
|
+
1. Both Cabloy Basic and Cabloy Start support managed isolated generation through the same root command:
|
|
224
224
|
|
|
225
225
|
```bash
|
|
226
226
|
npm run contract:generate:isolated -- <module-name>
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
Detect the active edition for edition-specific paths and follow-up builds, not to choose a different SDK generation default. For older project versions, verify the root entrypoint and source resolver are up to date.
|
|
230
230
|
|
|
231
231
|
2. Initialize module configuration with `npm run zova :openapi:config <module-name>` when needed. Keep module ownership constrained with `operations.match` unless the module intentionally owns a broad API surface; the isolated command reuses the existing generator and ownership checks.
|
|
232
232
|
3. Confirm the effective source uses the isolated service. The command supplies a child-scoped `OPENAPI_SOURCE_URL` to the project default source resolver; explicit module sources or project `modules` overrides retain their precedence. Inspect the project resolver and align conflicting sources deliberately before claiming isolated generation. Do not edit `.env` files or SDK runtime API targets to inject the temporary port.
|
|
@@ -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",
|