cabloy 5.1.189 → 5.1.191

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.
Files changed (56) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/CLAUDE.md +1 -0
  4. package/README.md +2 -2
  5. package/package.json +1 -1
  6. package/repo-docs/.vitepress/config.mjs +1 -0
  7. package/repo-docs/demo.md +12 -0
  8. package/repo-docs/index.md +3 -0
  9. package/vona/packages-cli/cli/package.json +1 -1
  10. package/vona/packages-cli/cli-set-api/package.json +1 -1
  11. package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +2 -2
  12. package/vona/packages-vona/vona/package.json +1 -1
  13. package/vona/packages-vona/vona-core/package.json +1 -1
  14. package/vona/packages-vona/vona-core/src/lib/core/application.ts +5 -6
  15. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +7 -2
  16. package/vona/packages-vona/vona-mock/package.json +1 -1
  17. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +4 -4
  18. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +10 -3
  19. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +5 -3
  20. package/vona/src/suite/a-home/modules/home-index/src/bean/meta.printTip.ts +1 -1
  21. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +9 -17
  22. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +9 -17
  23. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  24. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/controller/passport.ts +4 -2
  25. package/vona/src/suite-vendor/a-auth/modules/a-auth/test/oauthRequestContext.test.ts +123 -146
  26. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  27. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  28. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +1 -1
  29. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  30. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  31. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
  32. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  33. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  34. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +0 -16
  35. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/meta.runtime.ts +2 -2
  36. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  37. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +1 -0
  38. package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
  39. package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +12 -2
  40. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  41. package/zova/packages-zova/zova/package.json +2 -2
  42. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +2 -7
  43. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionOperationsRow.tsx +19 -13
  44. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +11 -0
  45. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockToolbarBulk/controller.tsx +7 -4
  46. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/formFieldDetails/controller.tsx +14 -0
  47. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/detailsPermissions.ts +65 -0
  48. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/index.ts +1 -0
  49. package/zova/src/suite/cabloy-basic/modules/basic-details/src/lib/utils.ts +3 -3
  50. package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/details.ts +6 -1
  51. package/zova/src/suite/cabloy-basic/modules/basic-details/test/lib/detailsPermissions.test.ts +125 -0
  52. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  53. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/permissions.ts +2 -8
  54. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  55. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +2 -1
  56. package/zova/src/suite-vendor/a-zova/package.json +3 -3
@@ -3,169 +3,146 @@ import { after, before, describe, it } from 'node:test';
3
3
  import { acquireTestLock, app } from 'vona-mock';
4
4
 
5
5
  describe('oauthRequestContext.test.ts', { concurrency: false }, () => {
6
- const releases: Array<() => void> = [];
6
+ let release: () => void;
7
7
 
8
8
  before(async () => {
9
- releases.push(await acquireTestLock('a-security'));
9
+ release = await acquireTestLock('a-security');
10
10
  });
11
11
 
12
12
  after(() => {
13
- for (const release of releases.reverse()) release();
13
+ release();
14
14
  });
15
+
15
16
  it('passes the callback request through an isolated context without rewriting it', async () => {
16
17
  await app.bean.executor.mockCtx(async () => {
17
- const proxyPrevious = app.proxy;
18
- app.proxy = true;
19
- try {
20
- const req = {
21
- method: 'GET',
22
- url: '/api/auth/passport/callback?a=1&b=2',
23
- headers: {
24
- 'host': 'origin.internal.test',
25
- 'x-forwarded-host': 'demo.example.test',
26
- 'x-forwarded-proto': 'https',
27
- 'user-agent': 'oauth-callback-test',
28
- },
29
- httpVersionMajor: 1,
30
- socket: {
31
- remoteAddress: '192.0.2.10',
32
- remotePort: 443,
33
- },
34
- };
35
- const originalUrl = req.url;
36
- const originalHeaders = { ...req.headers };
37
- const outerContext = app.ctx;
38
- const result = await app.bean.executor.newCtx(
39
- async () => {
40
- return {
41
- context: app.ctx,
42
- request: app.ctx.req,
43
- method: app.ctx.method,
44
- host: app.ctx.host,
45
- protocol: app.ctx.protocol,
46
- secure: app.ctx.secure,
47
- query: app.ctx.request.query,
48
- userAgent: app.ctx.get('user-agent'),
49
- };
50
- },
51
- {
52
- instanceName: outerContext.instanceName!,
53
- req,
54
- },
55
- );
18
+ const req = {
19
+ method: 'GET',
20
+ url: '/api/auth/passport/callback?a=1&b=2',
21
+ headers: {
22
+ 'host': 'demo.example.test',
23
+ 'user-agent': 'oauth-callback-test',
24
+ },
25
+ httpVersionMajor: 1,
26
+ socket: {
27
+ encrypted: true,
28
+ remoteAddress: '192.0.2.10',
29
+ remotePort: 443,
30
+ },
31
+ };
32
+ const originalUrl = req.url;
33
+ const originalHeaders = { ...req.headers };
34
+ const outerContext = app.ctx;
35
+ const result = await app.bean.executor.newCtx(
36
+ async () => {
37
+ return {
38
+ context: app.ctx,
39
+ request: app.ctx.req,
40
+ method: app.ctx.method,
41
+ host: app.ctx.host,
42
+ protocol: app.ctx.protocol,
43
+ secure: app.ctx.secure,
44
+ query: app.ctx.request.query,
45
+ userAgent: app.ctx.get('user-agent'),
46
+ };
47
+ },
48
+ {
49
+ instanceName: outerContext.instanceName!,
50
+ req,
51
+ },
52
+ );
56
53
 
57
- assert.notEqual(result.context, outerContext);
58
- assert.equal(result.request, req);
59
- assert.equal(result.method, 'GET');
60
- assert.equal(result.host, 'demo.example.test');
61
- assert.equal(result.protocol, 'https');
62
- assert.equal(result.secure, true);
63
- assert.deepEqual(result.query, { a: '1', b: '2' });
64
- assert.equal(result.userAgent, 'oauth-callback-test');
65
- assert.equal(req.url, originalUrl);
66
- assert.deepEqual(req.headers, originalHeaders);
67
- assert.equal(app.ctx, outerContext);
68
- } finally {
69
- app.proxy = proxyPrevious;
70
- }
54
+ assert.notEqual(result.context, outerContext);
55
+ assert.equal(result.request, req);
56
+ assert.equal(result.method, 'GET');
57
+ assert.equal(result.host, 'demo.example.test');
58
+ assert.equal(result.protocol, 'https');
59
+ assert.equal(result.secure, true);
60
+ assert.deepEqual(result.query, { a: '1', b: '2' });
61
+ assert.equal(result.userAgent, 'oauth-callback-test');
62
+ assert.equal(req.url, originalUrl);
63
+ assert.deepEqual(req.headers, originalHeaders);
64
+ assert.equal(app.ctx, outerContext);
71
65
  });
72
66
  });
73
67
 
74
- it('logs rejected redirects against the canonical server origin', async () => {
75
- await app.bean.executor.mockCtx(async () => {
76
- const proxyPrevious = app.proxy;
77
- const serve = app.ctx.config.server.serve;
78
- const servePrevious = { ...serve };
79
- app.proxy = true;
80
- (serve as any).host = 'canonical.example.test';
81
- (serve as any).protocol = 'http';
82
- try {
83
- const req = {
84
- method: 'GET',
85
- url: '/api/auth/passport/callback',
86
- headers: {
87
- 'host': 'origin.internal.test',
88
- 'x-forwarded-host': 'edge.example.test',
89
- 'x-forwarded-proto': 'https',
90
- },
91
- httpVersionMajor: 1,
92
- socket: {
93
- remoteAddress: '192.0.2.10',
94
- remotePort: 443,
95
- },
96
- };
97
- await app.bean.executor.newCtx(
98
- async () => {
99
- assert.equal(app.ctx.host, 'edge.example.test');
100
- assert.equal(app.ctx.protocol, 'https');
101
- assert.equal(app.util.host, 'canonical.example.test');
102
- assert.equal(app.util.protocol, 'http');
68
+ it('logs rejected redirects against the effective request origin', async () => {
69
+ const req = {
70
+ method: 'GET',
71
+ url: '/api/auth/passport/callback',
72
+ headers: {
73
+ host: 'edge.example.test',
74
+ },
75
+ httpVersionMajor: 1,
76
+ socket: {
77
+ encrypted: true,
78
+ remoteAddress: '192.0.2.10',
79
+ remotePort: 443,
80
+ },
81
+ };
82
+ await app.bean.executor.mockCtx(
83
+ async () => {
84
+ assert.equal(app.ctx.host, 'edge.example.test');
85
+ assert.equal(app.ctx.protocol, 'https');
86
+ assert.equal(app.util.host, 'edge.example.test');
87
+ assert.equal(app.util.protocol, 'https');
103
88
 
104
- const service = app.scope('a-auth').service.auth;
105
- const logger = (service as any).$logger;
106
- const warnPrevious = logger.warn;
107
- const warnings: unknown[][] = [];
108
- logger.warn = (...args: unknown[]) => warnings.push(args);
109
- try {
110
- (service as any)._logOauthRedirectRejected(
111
- {
112
- id: -1,
113
- providerName: 'auth-oauth:oauth',
114
- clientName: 'github',
115
- },
116
- {
117
- authProviderId: -1,
118
- instanceName: app.ctx.instanceName!,
119
- locale: app.ctx.locale,
120
- redirect: 'https://edge.example.test/oauth/callback',
121
- },
122
- 'origin_disallowed',
123
- );
124
- } finally {
125
- logger.warn = warnPrevious;
126
- }
89
+ const service = app.scope('a-auth').service.auth;
90
+ const logger = (service as any).$logger;
91
+ const warnPrevious = logger.warn;
92
+ const warnings: unknown[][] = [];
93
+ logger.warn = (...args: unknown[]) => warnings.push(args);
94
+ try {
95
+ (service as any)._logOauthRedirectRejected(
96
+ {
97
+ id: -1,
98
+ providerName: 'auth-oauth:oauth',
99
+ clientName: 'github',
100
+ },
101
+ {
102
+ authProviderId: -1,
103
+ instanceName: app.ctx.instanceName!,
104
+ locale: app.ctx.locale,
105
+ redirect: 'https://redirect.example.test/oauth/callback',
106
+ },
107
+ 'origin_disallowed',
108
+ );
109
+ } finally {
110
+ logger.warn = warnPrevious;
111
+ }
127
112
 
128
- assert.equal(warnings.length, 1);
129
- assert.equal(warnings[0][0], 'OAuth callback final redirect rejected');
130
- const diagnostic = warnings[0][1] as Record<string, unknown>;
131
- assert.deepEqual(
132
- {
133
- event: diagnostic.event,
134
- stage: diagnostic.stage,
135
- outcome: diagnostic.outcome,
136
- reason: diagnostic.reason,
137
- requestHost: diagnostic.requestHost,
138
- requestProtocol: diagnostic.requestProtocol,
139
- serveHost: diagnostic.serveHost,
140
- serveProtocol: diagnostic.serveProtocol,
141
- proxyEnabled: diagnostic.proxyEnabled,
142
- redirectHost: diagnostic.redirectHost,
143
- hostMatches: diagnostic.hostMatches,
144
- },
145
- {
146
- event: 'auth.oauth_final_redirect_rejected',
147
- stage: 'redirect_validation',
148
- outcome: 'rejected',
149
- reason: 'origin_disallowed',
150
- requestHost: 'canonical.example.test',
151
- requestProtocol: 'http',
152
- serveHost: 'canonical.example.test',
153
- serveProtocol: 'http',
154
- proxyEnabled: true,
155
- redirectHost: 'edge.example.test',
156
- hostMatches: false,
157
- },
158
- );
113
+ assert.equal(warnings.length, 1);
114
+ assert.equal(warnings[0][0], 'OAuth callback final redirect rejected');
115
+ const diagnostic = warnings[0][1] as Record<string, unknown>;
116
+ assert.deepEqual(
117
+ {
118
+ event: diagnostic.event,
119
+ stage: diagnostic.stage,
120
+ outcome: diagnostic.outcome,
121
+ reason: diagnostic.reason,
122
+ requestHost: diagnostic.requestHost,
123
+ requestProtocol: diagnostic.requestProtocol,
124
+ serveHost: diagnostic.serveHost,
125
+ serveProtocol: diagnostic.serveProtocol,
126
+ proxyEnabled: diagnostic.proxyEnabled,
127
+ redirectHost: diagnostic.redirectHost,
128
+ hostMatches: diagnostic.hostMatches,
159
129
  },
160
130
  {
161
- instanceName: app.ctx.instanceName!,
162
- req,
131
+ event: 'auth.oauth_final_redirect_rejected',
132
+ stage: 'redirect_validation',
133
+ outcome: 'rejected',
134
+ reason: 'origin_disallowed',
135
+ requestHost: 'edge.example.test',
136
+ requestProtocol: 'https',
137
+ serveHost: null,
138
+ serveProtocol: null,
139
+ proxyEnabled: false,
140
+ redirectHost: 'redirect.example.test',
141
+ hostMatches: false,
163
142
  },
164
143
  );
165
- } finally {
166
- app.proxy = proxyPrevious;
167
- Object.assign(serve, servePrevious);
168
- }
169
- });
144
+ },
145
+ { req },
146
+ );
170
147
  });
171
148
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-auth",
3
- "version": "5.1.28",
3
+ "version": "5.1.29",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-pay",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -88,7 +88,7 @@ export class ServicePaymentCallback extends BeanBase {
88
88
 
89
89
  private _assertTrustedOrigin() {
90
90
  const { protocol, host } = this.app.config.server.serve;
91
- if (!protocol || !host) {
91
+ if (!this.app.meta.isTest && (!protocol || !host)) {
92
92
  this.app.throw(503, 'payment callbacks require configured server serve protocol and host');
93
93
  }
94
94
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-mock",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-paypal",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-stripe",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "vona-suite-a-pay",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "vona-module-a-pay": "^5.0.8",
10
- "vona-module-pay-mock": "^5.0.8",
11
- "vona-module-pay-paypal": "^5.0.8",
12
- "vona-module-pay-stripe": "^5.0.8"
9
+ "vona-module-a-pay": "^5.0.9",
10
+ "vona-module-pay-mock": "^5.0.9",
11
+ "vona-module-pay-paypal": "^5.0.9",
12
+ "vona-module-pay-stripe": "^5.0.9"
13
13
  },
14
14
  "title": "a-pay"
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-core",
3
- "version": "5.1.55",
3
+ "version": "5.1.56",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -5,23 +5,7 @@ import type { TypeContextFetch } from '../types/fetch.ts';
5
5
 
6
6
  @Bean()
7
7
  export class BeanCore extends BeanBase {
8
- get protocol() {
9
- return this.app.util.protocol;
10
- }
11
-
12
- get host() {
13
- return this.app.util.host;
14
- }
15
-
16
8
  get fetch(): TypeContextFetch {
17
9
  return this.ctx.state.fetch ?? globalThis.fetch;
18
10
  }
19
-
20
- getAbsoluteUrl(path?: string) {
21
- return this.app.util.getAbsoluteUrl(path);
22
- }
23
-
24
- getAbsoluteUrlByApiPath(path: string) {
25
- return this.app.util.getAbsoluteUrlByApiPath(path);
26
- }
27
11
  }
@@ -12,8 +12,8 @@ export interface TypeMetaRuntimeResult {
12
12
  export class MetaRuntime extends BeanBase implements IMetaRuntimeExecute {
13
13
  async execute(): Promise<TypeMetaRuntimeResult> {
14
14
  return {
15
- protocol: this.bean.core.protocol,
16
- host: this.bean.core.host,
15
+ protocol: this.app.util.protocol,
16
+ host: this.app.util.host,
17
17
  };
18
18
  }
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-ormdialect",
3
- "version": "5.1.17",
3
+ "version": "5.1.18",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -98,6 +98,7 @@ export class DatabaseDialectBetterSqlite3 extends ServiceDatabaseDialectBase {
98
98
  }
99
99
 
100
100
  async function sqlite3_afterCreate(conn) {
101
+ conn.pragma('busy_timeout = 5000');
101
102
  const pragma: { journal_mode: string }[] = conn.pragma('journal_mode');
102
103
  if (pragma[0]?.journal_mode?.toLocaleLowerCase() !== 'wal') {
103
104
  conn.pragma('journal_mode = wal');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-security",
3
- "version": "5.1.14",
3
+ "version": "5.1.15",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,8 +1,18 @@
1
1
  import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { app } from 'vona-mock';
2
+ import { after, before, describe, it } from 'node:test';
3
+ import { acquireTestLock, app } from 'vona-mock';
4
4
 
5
5
  describe('origin.test.ts', { concurrency: false }, () => {
6
+ let release: () => void;
7
+
8
+ before(async () => {
9
+ release = await acquireTestLock('a-security');
10
+ });
11
+
12
+ after(() => {
13
+ release();
14
+ });
15
+
6
16
  it('keeps normal CORS separate from exact credential-link authorization', async () => {
7
17
  await withWhiteList('*', async () => {
8
18
  assert.equal(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.91",
3
+ "version": "5.1.92",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova",
3
- "version": "5.1.166",
3
+ "version": "5.1.167",
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.165"
49
+ "zova-suite-a-zova": "^5.1.166"
50
50
  },
51
51
  "devDependencies": {
52
52
  "clean-package": "^2.2.0",
@@ -10,12 +10,7 @@ import type {
10
10
  import { combineQueries, isNil } from '@cabloy/utils';
11
11
  import { SchemaObject } from 'openapi3-ts/oas31';
12
12
  import { $QueryEnsureFresh, $QueryGetFresh, BeanModelBase, Model } from 'zova-module-a-model';
13
- import {
14
- IPermissionHintGeneral,
15
- IResourceFormActionRowNameRecord,
16
- IResourceTableActionNameRecord,
17
- TypeOpenapiPermissions,
18
- } from 'zova-module-a-openapi';
13
+ import { IPermissionHintGeneral, TypeOpenapiPermissions } from 'zova-module-a-openapi';
19
14
  import { ApiApiHomeUserPassportloginOauthPath, OpenApiBaseURL } from 'zova-module-home-api';
20
15
  import { isRoleSiteAdmitted } from 'zova-module-home-base';
21
16
 
@@ -272,7 +267,7 @@ export class ModelPassport extends BeanModelBase {
272
267
 
273
268
  public checkPermission(
274
269
  permissions: TypeOpenapiPermissions | undefined,
275
- actionName?: keyof (IResourceTableActionNameRecord & IResourceFormActionRowNameRecord),
270
+ actionName?: string,
276
271
  permissionHint?: IPermissionHintGeneral,
277
272
  currentData?: Record<string, unknown> | readonly Record<string, unknown>[],
278
273
  ): boolean {
@@ -14,7 +14,8 @@ import { VNode } from 'vue';
14
14
  import { BeanBase } from 'zova';
15
15
  import { TableCell } from 'zova-module-a-table';
16
16
 
17
- import { checkPermission } from '../lib/utils.js';
17
+ import { filterDetailsRowActions } from '../lib/detailsPermissions.js';
18
+ import { checkFormScene } from '../lib/utils.js';
18
19
 
19
20
  declare module 'zova-module-a-openapi' {
20
21
  export interface IResourceDetailsActionRowRecord {
@@ -37,16 +38,16 @@ export class TableCellActionOperationsRow extends BeanBase implements ITableCell
37
38
  const { $celScope, $$table } = renderContext;
38
39
  const actions = options.actions;
39
40
  if (!actions || actions.length === 0) return false;
40
- // renders
41
+ const $$details = $celScope.$$details!;
42
+ const actionsVisible = actions.filter(action => {
43
+ return checkFormScene($$details.formScene, action.options?.permission);
44
+ });
41
45
  const renders: TypeTableCellRenderComponent[] = [];
42
- for (const action of actions) {
46
+ for (const action of actionsVisible) {
43
47
  const actionName = action.name;
44
48
  const actionRender = action.render;
45
- const permissionHint = action.options?.permission;
46
- if (checkPermission($celScope.formMeta!.formScene!, permissionHint)) {
47
- if (!actionRender) throw new Error(`should specify action render: ${actionName}`);
48
- renders.push(actionRender);
49
- }
49
+ if (!actionRender) throw new Error(`should specify action render: ${actionName}`);
50
+ renders.push(actionRender);
50
51
  }
51
52
  await $$table.cellRenderPrepare(renders);
52
53
  return renders.length > 0;
@@ -60,13 +61,18 @@ export class TableCellActionOperationsRow extends BeanBase implements ITableCell
60
61
  const { $celScope, $$table } = renderContext;
61
62
  const actions = options.actions;
62
63
  if (!actions || actions.length === 0) return;
64
+ const $$details = $celScope.$$details!;
65
+ const actionsAllowed = filterDetailsRowActions(
66
+ $$details.formScene,
67
+ $$details.checkPermission.bind($$details),
68
+ actions,
69
+ );
63
70
  const domActions: VNode[] = [];
64
- actions.forEach((action, index) => {
65
- const permissionHint = action.options?.permission;
66
- if (!checkPermission($celScope.formMeta!.formScene!, permissionHint)) return;
67
- const options2 = Object.assign({ key: index }, action.options);
68
- domActions.push($$table.cellRender(action.render!, options2, renderContext));
71
+ actionsAllowed.forEach((action, index) => {
72
+ const actionOptions = Object.assign({ key: index }, action.options);
73
+ domActions.push($$table.cellRender(action.render!, actionOptions, renderContext));
69
74
  });
75
+ if (domActions.length === 0) return;
70
76
  return <div class={options.class}>{domActions}</div>;
71
77
  }
72
78
  }
@@ -13,6 +13,8 @@ import {
13
13
  } from 'zova-module-a-openapi';
14
14
  import { BeanControllerTableBase } from 'zova-module-a-table';
15
15
 
16
+ import type { TypeDetailsCheckPermission } from '../../types/details.js';
17
+
16
18
  declare module 'zova-module-a-openapi' {
17
19
  export interface IResourceBlockRecord {
18
20
  'basic-details:blockDetails'?: ControllerBlockDetailsProps;
@@ -26,6 +28,7 @@ export interface ControllerBlockDetailsProps<
26
28
  formMeta?: IFormMeta;
27
29
  schemaRow?: ISchemaObjectExtensionField;
28
30
  schemaForm?: ISchemaObjectExtensionField;
31
+ checkPermission?: TypeDetailsCheckPermission;
29
32
  getDetailItems?: () => TData[] | undefined;
30
33
  setDetailItems?: (detailItems: TData[]) => void;
31
34
  }
@@ -70,6 +73,14 @@ export class ControllerBlockDetails<TData extends {} = {}> extends BeanControlle
70
73
  return this.$props.schemaForm!;
71
74
  }
72
75
 
76
+ checkPermission(...args: Parameters<TypeDetailsCheckPermission>) {
77
+ const checkPermission = this.$props.checkPermission;
78
+ if (!checkPermission) {
79
+ throw new Error('should provide enclosing Resource permission checker for details');
80
+ }
81
+ return checkPermission(...args);
82
+ }
83
+
73
84
  public async setTableRef(tableRef: BeanControllerTableBase<TData> | undefined) {
74
85
  this.tableRef = tableRef;
75
86
  }
@@ -9,7 +9,7 @@ import { VNode } from 'vue';
9
9
  import { BeanControllerBase, Use } from 'zova';
10
10
  import { Controller } from 'zova-module-a-bean';
11
11
 
12
- import { checkPermission } from '../../lib/utils.js';
12
+ import { filterDetailsBulkActions } from '../../lib/detailsPermissions.js';
13
13
 
14
14
  declare module 'zova-module-a-openapi' {
15
15
  export interface IResourceBlockRecord {
@@ -46,9 +46,12 @@ export class ControllerBlockToolbarBulk extends BeanControllerBase {
46
46
  const actions = this.$props.actions;
47
47
  if (!actions || actions.length === 0) return;
48
48
  const domActions: VNode[] = [];
49
- actions.forEach((action, index) => {
50
- const permissionHint = action.options?.permission;
51
- if (!checkPermission($$details.formScene, permissionHint)) return;
49
+ const allowedActions = filterDetailsBulkActions(
50
+ $$details.formScene,
51
+ $$details.checkPermission.bind($$details),
52
+ actions,
53
+ );
54
+ allowedActions.forEach((action, index) => {
52
55
  const options = Object.assign({ key: index }, action.options);
53
56
  const domAction = $jsx.render(action.render!, options, $celScope, this.$$renderContext);
54
57
  if (!domAction) return;