@nocobase/app-template-default 1.0.0-beta.69 → 1.0.0-beta.70
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/.agents/rules/components.md +4 -0
- package/AGENTS.md +3 -1
- package/client/components/flow-diagram-css.d.ts +2 -0
- package/client/components/flow-diagram.tsx +674 -0
- package/client/components/flow-layout.ts +250 -0
- package/client/components/lifecycle-diagram-model.ts +217 -0
- package/client/components/lifecycle-diagram.tsx +310 -0
- package/client/extensions/nocobase-workflow-management/README.md +70 -0
- package/client/extensions/nocobase-workflow-management/definition-list.tsx +148 -0
- package/client/extensions/nocobase-workflow-management/execution-detail.tsx +230 -0
- package/client/extensions/nocobase-workflow-management/execution-list.tsx +175 -0
- package/client/extensions/nocobase-workflow-management/layout.tsx +95 -0
- package/client/locales/system/en-US.ts +22 -0
- package/client/locales/system/settings-en-US.ts +1 -0
- package/client/locales/system/settings-zh-CN.ts +1 -0
- package/client/locales/system/zh-CN.ts +22 -0
- package/client/pages/settings/page-permission-titles.ts +7 -0
- package/client/pages/settings/page-permissions.ts +42 -0
- package/client/pages/settings/role-form.tsx +8 -29
- package/package.json +13 -11
- package/tests/logic/page-permission-titles.test.ts +223 -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.70",
|
|
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.70"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@ai-sdk/react": "2.0.0",
|
|
@@ -75,15 +75,17 @@
|
|
|
75
75
|
"vite": "^6.3.5",
|
|
76
76
|
"vitest": "^4.1.10",
|
|
77
77
|
"tar": "^7.5.22",
|
|
78
|
+
"@xyflow/react": "^12.11.5",
|
|
79
|
+
"elkjs": "^0.10.2",
|
|
78
80
|
"@nocobase/app-client": "^3.0.0-beta.2",
|
|
79
81
|
"@nocobase/app-plugin-template-print": "0.0.2-beta.2",
|
|
80
|
-
"@nocobase/app-skills": "0.1.0-beta.
|
|
82
|
+
"@nocobase/app-skills": "0.1.0-beta.38",
|
|
81
83
|
"@nocobase/app-testing": "0.1.0-beta.4",
|
|
82
84
|
"@nocobase/app-websocket": "^0.0.2-beta.0",
|
|
83
|
-
"@nocobase/drive": "^0.1.0-beta.6",
|
|
84
85
|
"@nocobase/dev-config": "0.1.0-beta.23",
|
|
85
|
-
"@nocobase/
|
|
86
|
-
"@nocobase/session": "^0.1.0-beta.3"
|
|
86
|
+
"@nocobase/drive": "^0.1.0-beta.6",
|
|
87
|
+
"@nocobase/session": "^0.1.0-beta.3",
|
|
88
|
+
"@nocobase/logging": "^0.1.0-beta.7"
|
|
87
89
|
},
|
|
88
90
|
"files": [
|
|
89
91
|
".prettierignore",
|
|
@@ -146,19 +148,19 @@
|
|
|
146
148
|
"@nocobase/app-plugin-authentication": "^2.0.0-beta.10",
|
|
147
149
|
"@nocobase/app-plugin-authorization": "^1.0.0-beta.28",
|
|
148
150
|
"@nocobase/app-plugin-database-explorer": "^1.0.0-beta.15",
|
|
151
|
+
"@nocobase/app-plugin-file": "^1.0.0-beta.24",
|
|
149
152
|
"@nocobase/app-plugin-i18n": "^1.0.0-beta.16",
|
|
150
153
|
"@nocobase/app-plugin-mail": "^1.0.0-beta.14",
|
|
151
|
-
"@nocobase/app-plugin-file": "^1.0.0-beta.24",
|
|
152
154
|
"@nocobase/app-plugin-notification": "^1.0.0-beta.27",
|
|
153
155
|
"@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.25",
|
|
154
156
|
"@nocobase/app-plugin-notification-providers": "^0.2.0-beta.12",
|
|
155
157
|
"@nocobase/app-plugin-scheduler": "^1.0.0-beta.17",
|
|
156
158
|
"@nocobase/app-plugin-users": "^2.0.0-beta.5",
|
|
157
|
-
"@nocobase/config": "^0.1.0-beta.3",
|
|
158
159
|
"@nocobase/app-server": "^2.0.0-beta.8",
|
|
160
|
+
"@nocobase/config": "^0.1.0-beta.3",
|
|
159
161
|
"@nocobase/db": "^1.0.0-beta.21",
|
|
160
|
-
"@nocobase/service-provider": "^0.0.2-beta.2",
|
|
161
162
|
"@nocobase/db-sqlite": "^0.1.0-beta.6",
|
|
163
|
+
"@nocobase/service-provider": "^0.0.2-beta.2",
|
|
162
164
|
"@nocobase/snowflake": "^1.0.0-beta.4",
|
|
163
165
|
"@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.13",
|
|
164
166
|
"@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.13",
|
|
@@ -168,9 +170,9 @@
|
|
|
168
170
|
"@nocobase/jobs": "^0.1.0-beta.3",
|
|
169
171
|
"@nocobase/lifecycle": "^0.1.0-beta.2",
|
|
170
172
|
"@nocobase/i18n": "^1.0.0-beta.7",
|
|
171
|
-
"@nocobase/repository-input": "^0.1.0-beta.3",
|
|
172
173
|
"@nocobase/caching": "^0.1.0-beta.3",
|
|
173
|
-
"@nocobase/
|
|
174
|
+
"@nocobase/repository-input": "^0.1.0-beta.3",
|
|
175
|
+
"@nocobase/app-cli": "^1.0.0-beta.24"
|
|
174
176
|
},
|
|
175
177
|
"description": "NocoBase 3 application template with the standard product capabilities.",
|
|
176
178
|
"imports": {
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveAppClientContributions,
|
|
4
|
+
type AppClientRegisteredRoute,
|
|
5
|
+
} from '@nocobase/app-client/plugins';
|
|
6
|
+
import applicationRoutes from '../../client/routes.js';
|
|
7
|
+
import enUS from '../../client/locales/en-US.js';
|
|
8
|
+
import zhCN from '../../client/locales/zh-CN.js';
|
|
9
|
+
import {
|
|
10
|
+
pagePermissionResources,
|
|
11
|
+
pagePermissionTitle,
|
|
12
|
+
} from '../../client/pages/settings/page-permissions.js';
|
|
13
|
+
import { pagePermissionTitles } from '../../client/pages/settings/page-permission-titles.js';
|
|
14
|
+
|
|
15
|
+
const flatten = (
|
|
16
|
+
routes: readonly AppClientRegisteredRoute[],
|
|
17
|
+
): AppClientRegisteredRoute[] =>
|
|
18
|
+
routes.flatMap((route) => [route, ...flatten(route.children ?? [])]);
|
|
19
|
+
const localized = (resource: unknown, key: string): unknown =>
|
|
20
|
+
key
|
|
21
|
+
.split('.')
|
|
22
|
+
.reduce<unknown>(
|
|
23
|
+
(value, part) =>
|
|
24
|
+
value && typeof value === 'object'
|
|
25
|
+
? Reflect.get(value, part)
|
|
26
|
+
: undefined,
|
|
27
|
+
resource,
|
|
28
|
+
);
|
|
29
|
+
const application = resolveAppClientContributions([
|
|
30
|
+
{
|
|
31
|
+
packageName: 'title-check-app',
|
|
32
|
+
source: 'application',
|
|
33
|
+
routes: applicationRoutes,
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
const pages = flatten(application.routes).filter(
|
|
37
|
+
(route) =>
|
|
38
|
+
route.source === 'application' &&
|
|
39
|
+
route.componentLoader &&
|
|
40
|
+
typeof route.authz === 'object' &&
|
|
41
|
+
route.authz.resource.type === 'page',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
describe('page permission labels', () => {
|
|
45
|
+
it('names every application page permission and translates it in both languages', () => {
|
|
46
|
+
const missing = pages
|
|
47
|
+
.filter((route) => !pagePermissionTitle(route, pagePermissionTitles))
|
|
48
|
+
.map((route) => route.name);
|
|
49
|
+
expect(
|
|
50
|
+
missing,
|
|
51
|
+
'Add a navigation/breadcrumb title or a page-permission-titles entry; never rename authorization ids to fix labels.',
|
|
52
|
+
).toEqual([]);
|
|
53
|
+
for (const route of pages) {
|
|
54
|
+
const title = pagePermissionTitle(route, pagePermissionTitles);
|
|
55
|
+
if (!title || typeof title === 'string')
|
|
56
|
+
throw new Error(`Missing permission title: ${route.name}`);
|
|
57
|
+
for (const locale of [enUS, zhCN]) {
|
|
58
|
+
const value = localized(locale, title.key);
|
|
59
|
+
expect(value, `${route.name}: ${title.key}`).toBeTypeOf('string');
|
|
60
|
+
expect(String(value).trim()).not.toBe('');
|
|
61
|
+
expect(value).not.toBe(title.key);
|
|
62
|
+
expect(value).not.toBe(route.id);
|
|
63
|
+
expect(String(value)).not.toContain('{{');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const names = new Set(
|
|
67
|
+
flatten(application.routes).map((route) => route.name),
|
|
68
|
+
);
|
|
69
|
+
for (const name of Object.keys(pagePermissionTitles))
|
|
70
|
+
expect(names.has(name), `Stale permission title: ${name}`).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('preserves the complete application page-resource identity list', () => {
|
|
74
|
+
const expected = pages.map((route) => {
|
|
75
|
+
if (typeof route.authz !== 'object')
|
|
76
|
+
throw new Error('Expected page authorization');
|
|
77
|
+
return { type: 'page', id: route.authz.resource.id, actions: ['access'] };
|
|
78
|
+
});
|
|
79
|
+
expect(
|
|
80
|
+
pagePermissionResources(
|
|
81
|
+
application.routes,
|
|
82
|
+
pagePermissionTitles,
|
|
83
|
+
(path) => `Unnamed (${path})`,
|
|
84
|
+
).map(({ type, id, actions }) => ({
|
|
85
|
+
type,
|
|
86
|
+
id,
|
|
87
|
+
actions: actions.map((action) => action.name),
|
|
88
|
+
})),
|
|
89
|
+
).toEqual(expected);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const fixture = () =>
|
|
93
|
+
resolveAppClientContributions([
|
|
94
|
+
{
|
|
95
|
+
packageName: 'private-app',
|
|
96
|
+
source: 'application',
|
|
97
|
+
routes: [
|
|
98
|
+
{
|
|
99
|
+
parent: 'app',
|
|
100
|
+
routes: [
|
|
101
|
+
{
|
|
102
|
+
name: 'expenses',
|
|
103
|
+
path: '/expenses',
|
|
104
|
+
auth: 'required',
|
|
105
|
+
authz: {
|
|
106
|
+
resource: { type: 'page', id: 'legacy-expenses' },
|
|
107
|
+
action: 'access',
|
|
108
|
+
},
|
|
109
|
+
navigation: { title: 'expenses.title' },
|
|
110
|
+
componentLoader: async () => ({ default: () => null }),
|
|
111
|
+
children: [
|
|
112
|
+
{
|
|
113
|
+
name: 'expenseEdit',
|
|
114
|
+
path: ':id/edit',
|
|
115
|
+
authz: {
|
|
116
|
+
resource: { type: 'page', id: 'legacy-edit' },
|
|
117
|
+
action: 'access',
|
|
118
|
+
},
|
|
119
|
+
componentLoader: async () => ({ default: () => null }),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
it('names an overlay without adding navigation/breadcrumbs or changing resources', () => {
|
|
130
|
+
const input = fixture();
|
|
131
|
+
const route = input.routes[0]!.children![0]!;
|
|
132
|
+
const identities = flatten(input.routes).map(
|
|
133
|
+
({ id, name, path, authz }) => ({ id, name, path, authz }),
|
|
134
|
+
);
|
|
135
|
+
const result = pagePermissionResources(
|
|
136
|
+
input.routes,
|
|
137
|
+
{ expenseEdit: 'expenses.editTitle' },
|
|
138
|
+
(path) => `Unnamed (${path})`,
|
|
139
|
+
);
|
|
140
|
+
expect(result.map(({ id, actions }) => ({ id, actions }))).toEqual([
|
|
141
|
+
{
|
|
142
|
+
id: 'legacy-expenses',
|
|
143
|
+
actions: [{ name: 'access', title: { key: 'settings.access' } }],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'legacy-edit',
|
|
147
|
+
actions: [{ name: 'access', title: { key: 'settings.access' } }],
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
expect(result[1]!.title).toEqual({
|
|
151
|
+
key: 'expenses.editTitle',
|
|
152
|
+
ns: 'private-app',
|
|
153
|
+
});
|
|
154
|
+
expect(route.navigation).toBeUndefined();
|
|
155
|
+
expect(route.breadcrumb).toBeUndefined();
|
|
156
|
+
expect(
|
|
157
|
+
flatten(input.routes).map(({ id, name, path, authz }) => ({
|
|
158
|
+
id,
|
|
159
|
+
name,
|
|
160
|
+
path,
|
|
161
|
+
authz,
|
|
162
|
+
})),
|
|
163
|
+
).toEqual(identities);
|
|
164
|
+
expect(
|
|
165
|
+
pagePermissionResources(
|
|
166
|
+
input.routes,
|
|
167
|
+
{ expenseEdit: 'expenses.editTitle' },
|
|
168
|
+
(path) => `Unnamed (${path})`,
|
|
169
|
+
),
|
|
170
|
+
).toEqual(result);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('reports missing metadata and renders a localized fallback without dropping the permission', () => {
|
|
174
|
+
const input = fixture();
|
|
175
|
+
const overlay = input.routes[0]!.children![0]!;
|
|
176
|
+
expect(pagePermissionTitle(overlay, {})).toBeUndefined();
|
|
177
|
+
const resources = pagePermissionResources(
|
|
178
|
+
input.routes,
|
|
179
|
+
{},
|
|
180
|
+
(path) => `未命名页面(${path})`,
|
|
181
|
+
);
|
|
182
|
+
expect(resources[1]!.id).toBe('legacy-edit');
|
|
183
|
+
expect(resources[1]!.title).toBe('未命名页面(/expenses/:id/edit)');
|
|
184
|
+
expect(resources[1]!.title).not.toContain('private-app:');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('does not treat object prototype properties as configured labels', () => {
|
|
188
|
+
const route = fixture().routes[0]!.children![0]!;
|
|
189
|
+
expect(
|
|
190
|
+
pagePermissionTitle({ ...route, name: 'constructor' }, {}),
|
|
191
|
+
).toBeUndefined();
|
|
192
|
+
expect(
|
|
193
|
+
pagePermissionTitle({ ...route, name: 'toString' }, {}),
|
|
194
|
+
).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('does not apply application-name overrides to plugin routes or expose non-page resources', () => {
|
|
198
|
+
const input = fixture();
|
|
199
|
+
const overlay = input.routes[0]!.children![0]!;
|
|
200
|
+
expect(
|
|
201
|
+
pagePermissionTitle(
|
|
202
|
+
{ ...overlay, source: 'plugin', packageName: 'plugin' },
|
|
203
|
+
{ expenseEdit: 'expenses.editTitle' },
|
|
204
|
+
),
|
|
205
|
+
).toBeUndefined();
|
|
206
|
+
expect(
|
|
207
|
+
pagePermissionResources(
|
|
208
|
+
[
|
|
209
|
+
{ ...overlay, authz: 'skip' },
|
|
210
|
+
{
|
|
211
|
+
...overlay,
|
|
212
|
+
authz: {
|
|
213
|
+
resource: { type: 'settings', id: 'members' },
|
|
214
|
+
action: 'read',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
{},
|
|
219
|
+
() => 'Unnamed',
|
|
220
|
+
),
|
|
221
|
+
).toEqual([]);
|
|
222
|
+
});
|
|
223
|
+
});
|