@nocobase/app-template-default 1.0.0-beta.76 → 1.0.0-beta.77
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/client/components/default-access-editor.tsx +79 -52
- package/client/components/long-text.tsx +200 -0
- package/client/components/permission-editor.tsx +534 -341
- package/client/locales/system/settings-en-US.ts +14 -3
- package/client/locales/system/settings-zh-CN.ts +13 -3
- package/client/pages/settings/default-access-form.tsx +10 -1
- package/client/pages/settings/default-access-model.ts +6 -2
- package/client/pages/settings/page-permissions.ts +166 -1
- package/client/pages/settings/permission-filters.tsx +9 -2
- package/client/pages/settings/permission-workspace.ts +330 -0
- package/client/pages/settings/permissions-model.ts +62 -53
- package/client/pages/settings/role-form.tsx +30 -94
- package/package.json +11 -11
- package/server/config/app.ts +1 -1
- package/server/config/session.ts +7 -5
- package/tests/components/default-access-editor.test.tsx +130 -0
- package/tests/components/permission-editor.test.tsx +613 -572
- package/tests/components/permission-filter-binding.test.tsx +20 -0
- package/tests/components/settings.test.tsx +33 -8
- package/tests/components/user-permissions.test.tsx +47 -13
- package/tests/logic/config.test.ts +3 -1
- package/tests/logic/page-permission-navigation.test.ts +394 -0
- package/tests/logic/permission-workspace.test.ts +570 -0
- package/tests/logic/session-isolation.test.ts +250 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/app-template-default",
|
|
3
3
|
"displayName": "Default Template",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.77",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"prettier": "@nocobase/dev-config/prettier",
|
|
7
7
|
"engines": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"nocobase": {
|
|
11
11
|
"templateKind": "app",
|
|
12
|
-
"defaultTemplateVersion": "1.0.0-beta.
|
|
12
|
+
"defaultTemplateVersion": "1.0.0-beta.77"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@ai-sdk/react": "4.0.131",
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"tar": "^7.5.22",
|
|
73
73
|
"@xyflow/react": "^12.11.5",
|
|
74
74
|
"elkjs": "^0.10.2",
|
|
75
|
-
"@nocobase/app-client": "^4.0.0-beta.
|
|
75
|
+
"@nocobase/app-client": "^4.0.0-beta.2",
|
|
76
76
|
"@nocobase/app-plugin-template-print": "0.0.2-beta.2",
|
|
77
|
-
"@nocobase/app-skills": "0.1.0-beta.
|
|
77
|
+
"@nocobase/app-skills": "0.1.0-beta.46",
|
|
78
78
|
"@nocobase/app-testing": "0.1.0-beta.6",
|
|
79
79
|
"@nocobase/app-websocket": "^0.1.0-beta.1",
|
|
80
80
|
"@nocobase/dev-config": "0.1.0-beta.24",
|
|
@@ -139,24 +139,24 @@
|
|
|
139
139
|
"@oclif/core": "^4.14.0",
|
|
140
140
|
"better-auth": "1.7.5",
|
|
141
141
|
"hono": "^4.13.2",
|
|
142
|
-
"@nocobase/app-plugin-agents": "^1.0.0-beta.
|
|
142
|
+
"@nocobase/app-plugin-agents": "^1.0.0-beta.19",
|
|
143
143
|
"@nocobase/app-plugin-api-keys": "^1.0.0-beta.18",
|
|
144
|
-
"@nocobase/app-plugin-
|
|
145
|
-
"@nocobase/app-plugin-
|
|
144
|
+
"@nocobase/app-plugin-authorization": "^1.0.0-beta.31",
|
|
145
|
+
"@nocobase/app-plugin-authentication": "^2.0.0-beta.13",
|
|
146
146
|
"@nocobase/app-plugin-database-explorer": "^1.0.0-beta.16",
|
|
147
147
|
"@nocobase/app-plugin-file": "^1.0.0-beta.26",
|
|
148
148
|
"@nocobase/app-plugin-foundation": "^0.1.0-beta.1",
|
|
149
149
|
"@nocobase/app-plugin-i18n": "^1.0.0-beta.18",
|
|
150
|
-
"@nocobase/app-plugin-knowledge": "^0.1.0-beta.
|
|
150
|
+
"@nocobase/app-plugin-knowledge": "^0.1.0-beta.10",
|
|
151
151
|
"@nocobase/app-plugin-mail": "^1.0.0-beta.16",
|
|
152
152
|
"@nocobase/app-plugin-notification": "^2.0.0-beta.0",
|
|
153
153
|
"@nocobase/app-plugin-notification-in-app": "^2.0.0-beta.0",
|
|
154
154
|
"@nocobase/app-plugin-notification-providers": "^1.0.0-beta.15",
|
|
155
155
|
"@nocobase/app-plugin-scheduler": "^1.0.0-beta.19",
|
|
156
156
|
"@nocobase/app-plugin-users": "^2.0.0-beta.9",
|
|
157
|
-
"@nocobase/app-server": "^3.0.0-beta.
|
|
157
|
+
"@nocobase/app-server": "^3.0.0-beta.3",
|
|
158
158
|
"@nocobase/config": "^0.1.0-beta.3",
|
|
159
|
-
"@nocobase/db": "^1.0.0-beta.
|
|
159
|
+
"@nocobase/db": "^1.0.0-beta.22",
|
|
160
160
|
"@nocobase/db-sqlite": "^0.1.0-beta.6",
|
|
161
161
|
"@nocobase/service-provider": "^0.0.2-beta.2",
|
|
162
162
|
"@nocobase/snowflake": "^1.0.0-beta.4",
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"@nocobase/i18n": "^2.0.0-beta.1",
|
|
171
171
|
"@nocobase/caching": "^0.1.0-beta.3",
|
|
172
172
|
"@nocobase/repository-input": "^0.1.0-beta.3",
|
|
173
|
-
"@nocobase/app-cli": "^1.0.0-beta.
|
|
173
|
+
"@nocobase/app-cli": "^1.0.0-beta.27",
|
|
174
174
|
"@nocobase/realtime": "^0.1.0-beta.3"
|
|
175
175
|
},
|
|
176
176
|
"description": "NocoBase 3 application template with the standard product capabilities.",
|
package/server/config/app.ts
CHANGED
|
@@ -27,7 +27,7 @@ const app: AppConfigFactory<AppIdentityConfig> = defineAppConfig({
|
|
|
27
27
|
},
|
|
28
28
|
defaults: (runtime) => {
|
|
29
29
|
const { routing } = runtime;
|
|
30
|
-
const publicBasePath = normalizeBasePath(routing.publicBasePath
|
|
30
|
+
const publicBasePath = normalizeBasePath(routing.publicBasePath);
|
|
31
31
|
return {
|
|
32
32
|
name: routing.name || resolveAppNameFromBasePath(publicBasePath, 'main'),
|
|
33
33
|
publicBasePath,
|
package/server/config/session.ts
CHANGED
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
type AppConfigFactory,
|
|
5
5
|
} from '@nocobase/app-server/config';
|
|
6
6
|
import type { AppSessionConfigInput } from '@nocobase/app-server/session';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createAppCookieScope,
|
|
9
|
+
normalizeBasePath,
|
|
10
|
+
} from '@nocobase/app-server/support';
|
|
8
11
|
|
|
9
12
|
const session: AppConfigFactory<AppSessionConfigInput> = defineAppConfig({
|
|
10
13
|
env: {
|
|
@@ -19,10 +22,9 @@ const session: AppConfigFactory<AppSessionConfigInput> = defineAppConfig({
|
|
|
19
22
|
enabled: true,
|
|
20
23
|
default: 'memory',
|
|
21
24
|
cookie: {
|
|
22
|
-
name:
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
path: normalizeBasePath(routing.publicBasePath || '/main'),
|
|
25
|
+
name: `nocobase_session_${createAppCookieScope(routing.publicBasePath)}`,
|
|
26
|
+
// A distinct name also keeps a child application from reading a parent's cookie sent under an overlapping path.
|
|
27
|
+
path: normalizeBasePath(routing.publicBasePath) || '/',
|
|
26
28
|
secure: env.NODE_ENV === 'production',
|
|
27
29
|
httpOnly: true,
|
|
28
30
|
sameSite: 'lax' as const,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expect, it, vi } from 'vitest';
|
|
4
|
+
import { DefaultAccessEditor } from '../../client/components/default-access-editor.js';
|
|
5
|
+
|
|
6
|
+
it('reports one explicit scope choice and does not mutate its controlled fields during browsing', async () => {
|
|
7
|
+
const user = userEvent.setup();
|
|
8
|
+
const onScopeChange = vi.fn();
|
|
9
|
+
const props = {
|
|
10
|
+
ruleKey: 'baseline',
|
|
11
|
+
resource: 'projects',
|
|
12
|
+
resources: [{ value: 'projects', label: 'Projects' }],
|
|
13
|
+
scopes: [
|
|
14
|
+
{
|
|
15
|
+
id: 'view-projects',
|
|
16
|
+
action: 'View',
|
|
17
|
+
scope: 'Projects',
|
|
18
|
+
value: 'own',
|
|
19
|
+
options: [
|
|
20
|
+
{ value: 'own', label: 'Own records' },
|
|
21
|
+
{ value: 'all', label: 'All records' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
labels: {
|
|
26
|
+
key: 'Key',
|
|
27
|
+
resource: 'Business object',
|
|
28
|
+
chooseResource: 'Choose an object',
|
|
29
|
+
action: 'Operation',
|
|
30
|
+
scope: 'Data scope',
|
|
31
|
+
noScopes: 'Choose an object',
|
|
32
|
+
},
|
|
33
|
+
onKeyChange: vi.fn(),
|
|
34
|
+
onResourceChange: vi.fn(),
|
|
35
|
+
onScopeChange,
|
|
36
|
+
};
|
|
37
|
+
render(<DefaultAccessEditor {...props} existing />);
|
|
38
|
+
expect(screen.getByRole('textbox', { name: 'Key *' })).toBeDisabled();
|
|
39
|
+
expect(props.onKeyChange).not.toHaveBeenCalled();
|
|
40
|
+
await user.click(screen.getByRole('combobox', { name: 'View · Projects' }));
|
|
41
|
+
expect(onScopeChange).not.toHaveBeenCalled();
|
|
42
|
+
await user.click(await screen.findByRole('option', { name: 'All records' }));
|
|
43
|
+
expect(onScopeChange).toHaveBeenCalledExactlyOnceWith('view-projects', 'all');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('groups ranges by operation, keeps independent choices and preserves explicit operation identities', async () => {
|
|
47
|
+
const change = vi.fn();
|
|
48
|
+
const options = [
|
|
49
|
+
{ value: '', label: 'No default scope' },
|
|
50
|
+
{ value: 'all', label: 'All records' },
|
|
51
|
+
];
|
|
52
|
+
const props = {
|
|
53
|
+
ruleKey: 'baseline',
|
|
54
|
+
resource: 'library',
|
|
55
|
+
resources: [{ value: 'library', label: 'Library' }],
|
|
56
|
+
scopes: [
|
|
57
|
+
{
|
|
58
|
+
id: 'books',
|
|
59
|
+
actionId: 'browse',
|
|
60
|
+
action: 'Browse holdings',
|
|
61
|
+
scope: 'Book information',
|
|
62
|
+
value: '',
|
|
63
|
+
options,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'copies',
|
|
67
|
+
actionId: 'browse',
|
|
68
|
+
action: 'Browse holdings',
|
|
69
|
+
scope: 'Physical copies',
|
|
70
|
+
value: '',
|
|
71
|
+
options,
|
|
72
|
+
configuration: <button type='button'>Configure conditions</button>,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'manage',
|
|
76
|
+
actionId: 'manage',
|
|
77
|
+
action: 'Manage',
|
|
78
|
+
scope: 'Book information',
|
|
79
|
+
value: '',
|
|
80
|
+
options,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'settings',
|
|
84
|
+
actionId: 'settings',
|
|
85
|
+
action: 'Manage',
|
|
86
|
+
scope: 'Settings',
|
|
87
|
+
value: '',
|
|
88
|
+
options,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
labels: {
|
|
92
|
+
key: 'Key',
|
|
93
|
+
resource: 'Business object',
|
|
94
|
+
chooseResource: 'Choose',
|
|
95
|
+
action: 'Operation',
|
|
96
|
+
scope: 'Scope',
|
|
97
|
+
noScopes: 'Choose an object',
|
|
98
|
+
},
|
|
99
|
+
onKeyChange: vi.fn(),
|
|
100
|
+
onResourceChange: vi.fn(),
|
|
101
|
+
onScopeChange: change,
|
|
102
|
+
};
|
|
103
|
+
const view = render(<DefaultAccessEditor {...props} />);
|
|
104
|
+
expect(
|
|
105
|
+
screen.getAllByRole('heading', { name: 'Browse holdings' }),
|
|
106
|
+
).toHaveLength(1);
|
|
107
|
+
expect(screen.getAllByRole('region', { name: 'Manage' })).toHaveLength(2);
|
|
108
|
+
await userEvent.click(
|
|
109
|
+
screen.getByRole('button', { name: 'Configure conditions' }),
|
|
110
|
+
);
|
|
111
|
+
expect(change).not.toHaveBeenCalled();
|
|
112
|
+
await userEvent.click(
|
|
113
|
+
screen.getByRole('combobox', { name: 'Browse holdings · Physical copies' }),
|
|
114
|
+
);
|
|
115
|
+
await userEvent.click(
|
|
116
|
+
await screen.findByRole('option', { name: 'All records' }),
|
|
117
|
+
);
|
|
118
|
+
expect(change).toHaveBeenCalledExactlyOnceWith('copies', 'all');
|
|
119
|
+
expect(
|
|
120
|
+
screen.getByRole('combobox', {
|
|
121
|
+
name: 'Browse holdings · Book information',
|
|
122
|
+
}),
|
|
123
|
+
).toHaveTextContent('No default scope');
|
|
124
|
+
view.rerender(<DefaultAccessEditor {...props} readOnly />);
|
|
125
|
+
expect(
|
|
126
|
+
screen
|
|
127
|
+
.getAllByRole('combobox')
|
|
128
|
+
.every((item) => item.hasAttribute('disabled')),
|
|
129
|
+
).toBe(true);
|
|
130
|
+
});
|