@nocobase/app-template-default 1.0.0-beta.87 → 1.0.0-beta.89
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 +3 -1
- package/.agents/rules/notification-templates.md +5 -0
- package/.agents/rules/shell.md +9 -3
- package/AGENTS.md +4 -2
- package/README.MD +11 -5
- package/client/components/back-button.tsx +37 -12
- package/client/components/body-portal.tsx +25 -0
- package/client/components/brand-loading-mark.tsx +5 -2
- package/client/components/brand-mark-geometry.ts +3 -3
- package/client/components/breadcrumbs.tsx +60 -3
- package/client/components/data-table/data-table.tsx +90 -19
- package/client/components/data-table/pagination.tsx +17 -2
- package/client/components/flow-diagram.tsx +2 -2
- package/client/components/list-feedback.tsx +31 -22
- package/client/components/loading.tsx +127 -16
- package/client/components/pending-button.tsx +111 -0
- package/client/components/role-list.tsx +7 -0
- package/client/components/route-child-page.tsx +72 -1
- package/client/components/route-overlay.tsx +149 -15
- package/client/components/task-progress.tsx +155 -0
- package/client/components/use-route-overlay.ts +8 -11
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +62 -32
- package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +1 -0
- package/client/extensions/nocobase-workflow-management/README.md +5 -5
- package/client/extensions/nocobase-workflow-management/definition-list.tsx +2 -9
- package/client/extensions/nocobase-workflow-management/execution-list.tsx +2 -4
- package/client/layouts/app-layout.tsx +8 -8
- package/client/layouts/components/sidebar-footer.tsx +18 -25
- package/client/lib/navigation-transform.ts +156 -22
- package/client/locales/system/en-US.ts +26 -5
- package/client/locales/system/settings-en-US.ts +17 -0
- package/client/locales/system/settings-zh-CN.ts +15 -0
- package/client/locales/system/zh-CN.ts +26 -5
- package/client/pages/auth/register.tsx +39 -22
- package/client/pages/auth/shared.tsx +1 -3
- package/client/pages/settings/default-access.tsx +1 -1
- package/client/pages/settings/hooks.tsx +3 -68
- package/client/pages/settings/notification-routes.ts +109 -0
- package/client/pages/settings/notification-templates.tsx +26 -0
- package/client/pages/settings/permission-management-page.tsx +15 -90
- package/client/pages/settings/roles.tsx +27 -27
- package/client/pages/settings/routes.ts +107 -101
- package/client/pages/settings/settings-home.tsx +109 -0
- package/client/pages/settings/settings-module-page.tsx +158 -0
- package/client/pages/settings/shell-routes.ts +53 -0
- package/client/pages/settings/user-detail.tsx +3 -17
- package/client/pages/settings/user-form.tsx +9 -15
- package/client/pages/settings/users.tsx +11 -27
- package/client/routes.ts +17 -1
- package/client/routing/app-router.tsx +1 -14
- package/client/routing/client-route.tsx +199 -30
- package/client/routing/navigation-destination.ts +42 -0
- package/client/routing/route-context.tsx +30 -3
- package/client/routing/route-leave-boundary.tsx +261 -0
- package/client/routing/route-navigation.ts +72 -8
- package/client/routing/route-tree.tsx +7 -12
- package/client/runtime.ts +2 -2
- package/client/styles.css +18 -1
- package/config.example.yml +1 -1
- package/package.json +13 -13
- package/server/config/auth.ts +1 -1
- package/server/providers/index.ts +2 -0
- package/server/providers/notification-management.ts +27 -0
- package/tests/client/account-permissions.test.tsx +1 -1
- package/tests/client/auth-i18n.test.tsx +1 -1
- package/tests/client/authenticated-client-fixture.test.tsx +37 -0
- package/tests/client/back-button.test.tsx +34 -0
- package/tests/client/client-auth.test.tsx +10 -1
- package/tests/client/client-route-recovery.test.tsx +93 -1
- package/tests/client/client-routes.test.ts +137 -4
- package/tests/client/client-shell.test.tsx +23 -8
- package/tests/client/foundation-integration.test.ts +38 -17
- package/tests/client/layout-i18n.test.tsx +9 -4
- package/tests/client/list-feedback.test.tsx +5 -5
- package/tests/client/list-loading.test.tsx +397 -0
- package/tests/client/loading.test.tsx +20 -11
- package/tests/client/manual-list-retry.test.tsx +2 -2
- package/tests/client/navigation-parent.test.tsx +172 -0
- package/tests/client/pending-button.test.tsx +106 -0
- package/tests/client/permission-management-tabs.test.tsx +458 -17
- package/tests/client/primitive-labels.test.tsx +2 -2
- package/tests/client/registration-feedback.test.tsx +143 -0
- package/tests/client/role-return.test.tsx +21 -3
- package/tests/client/roles.test.tsx +84 -3
- package/tests/client/route-leave-recheck.test.tsx +74 -0
- package/tests/client/route-presentation.test.tsx +557 -0
- package/tests/client/session-preparation.test.tsx +20 -17
- package/tests/client/settings-continuity.test.tsx +35 -11
- package/tests/client/settings-home.test.tsx +112 -0
- package/tests/client/settings-idle-list.test.tsx +124 -0
- package/tests/client/settings-navigation.test.ts +230 -0
- package/tests/client/user-drawer.test.tsx +3 -4
- package/tests/fixtures/dialog-footer.html +12 -0
- package/tests/fixtures/dialog-footer.tsx +215 -0
- package/tests/helpers/authenticated-client.tsx +70 -22
- package/tests/playwright/dialog-footer.test.ts +227 -0
- package/tests/server/notification-templates.test.ts +144 -0
- package/tests/client/permission-management-routes.test.ts +0 -64
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import {
|
|
4
|
+
signIn,
|
|
5
|
+
DEFAULT_ADMIN_CREDENTIALS,
|
|
6
|
+
} from '@nocobase/app-plugin-authentication/testing';
|
|
7
|
+
import { createAppTest } from '@nocobase/app-testing/server';
|
|
8
|
+
import { describe, expect } from 'vitest';
|
|
9
|
+
import { createStandaloneServer } from '../../server/standalone.js';
|
|
10
|
+
|
|
11
|
+
const common = {
|
|
12
|
+
createServer: createStandaloneServer,
|
|
13
|
+
scope: 'test' as const,
|
|
14
|
+
config: {
|
|
15
|
+
app: { publicOrigin: 'http://localhost' },
|
|
16
|
+
spa: {
|
|
17
|
+
indexPath: fileURLToPath(new URL('../../index.html', import.meta.url)),
|
|
18
|
+
},
|
|
19
|
+
auth: {
|
|
20
|
+
secret: 'notification-template-test-secret-at-least-32-characters',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
describe('bare Default', () => {
|
|
25
|
+
const test = createAppTest(common);
|
|
26
|
+
test('keeps the bare Default navigation disabled without an invitation transport', async ({
|
|
27
|
+
app,
|
|
28
|
+
}) => {
|
|
29
|
+
expect(
|
|
30
|
+
app.config.get('client.notificationTemplates.invitationConfigured'),
|
|
31
|
+
).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('configured Default', () => {
|
|
35
|
+
const test = createAppTest({
|
|
36
|
+
...common,
|
|
37
|
+
config: {
|
|
38
|
+
...common.config,
|
|
39
|
+
mailers: {
|
|
40
|
+
default: 'system',
|
|
41
|
+
system: {
|
|
42
|
+
provider: 'smtp',
|
|
43
|
+
host: '127.0.0.1',
|
|
44
|
+
port: 9,
|
|
45
|
+
auth: { user: 'synthetic', pass: 'synthetic-private-password' },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('boots the real Default invitation directory and protects all three endpoints', async ({
|
|
52
|
+
testApp,
|
|
53
|
+
app,
|
|
54
|
+
request,
|
|
55
|
+
}) => {
|
|
56
|
+
expect(
|
|
57
|
+
app.config.get('client.notificationTemplates.invitationConfigured'),
|
|
58
|
+
).toBe(true);
|
|
59
|
+
const page = await testApp.fetch(
|
|
60
|
+
new Request(`http://localhost${testApp.publicBasePath}/`),
|
|
61
|
+
);
|
|
62
|
+
expect(page.status).toBe(200);
|
|
63
|
+
const html = await page.text();
|
|
64
|
+
expect(html).toContain('"invitationConfigured":true');
|
|
65
|
+
expect(html).not.toContain('synthetic-private-password');
|
|
66
|
+
const admin = await signIn(testApp, DEFAULT_ADMIN_CREDENTIALS);
|
|
67
|
+
expect((await request('/notifications/templates')).status).toBe(401);
|
|
68
|
+
const list = await admin.fetch('/notifications/templates');
|
|
69
|
+
expect(list.status).toBe(200);
|
|
70
|
+
const body = await list.json();
|
|
71
|
+
expect(JSON.stringify(body)).not.toContain('synthetic-private-password');
|
|
72
|
+
expect(body.data).toEqual(
|
|
73
|
+
expect.arrayContaining([
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
id: 'users.invitation',
|
|
76
|
+
channels: [
|
|
77
|
+
{ name: 'system', type: 'email', configured: true, enabled: true },
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
const detail = await admin.fetch(
|
|
83
|
+
'/notifications/templates/users.invitation',
|
|
84
|
+
);
|
|
85
|
+
expect(detail.status).toBe(200);
|
|
86
|
+
const logsBefore = await (await admin.fetch('/notifications/logs')).json();
|
|
87
|
+
const preview = await admin.fetch(
|
|
88
|
+
'/notifications/templates/users.invitation/preview',
|
|
89
|
+
{
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
origin: 'http://localhost',
|
|
93
|
+
'content-type': 'application/json',
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({ locale: 'en-US' }),
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
expect(preview.status).toBe(200);
|
|
99
|
+
expect(JSON.stringify(await preview.json())).toContain('example.invalid');
|
|
100
|
+
expect(await (await admin.fetch('/notifications/logs')).json()).toEqual(
|
|
101
|
+
logsBefore,
|
|
102
|
+
);
|
|
103
|
+
const member = {
|
|
104
|
+
username: 'templateviewer',
|
|
105
|
+
email: 'templateviewer@example.invalid',
|
|
106
|
+
password: 'Synthetic-template-viewer-943!',
|
|
107
|
+
};
|
|
108
|
+
const created = await admin.fetch('/users', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
origin: 'http://localhost',
|
|
112
|
+
'content-type': 'application/json',
|
|
113
|
+
'Idempotency-Key': `${Date.now()}.${randomUUID()}`,
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({
|
|
116
|
+
...member,
|
|
117
|
+
name: 'Synthetic restricted viewer',
|
|
118
|
+
roleScopes: {},
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
expect(created.status).toBe(201);
|
|
122
|
+
const restricted = await signIn(testApp, member);
|
|
123
|
+
for (const endpoint of [
|
|
124
|
+
'',
|
|
125
|
+
'/users.invitation',
|
|
126
|
+
'/users.invitation/preview',
|
|
127
|
+
]) {
|
|
128
|
+
const response = await restricted.fetch(
|
|
129
|
+
`/notifications/templates${endpoint}`,
|
|
130
|
+
endpoint.endsWith('/preview')
|
|
131
|
+
? {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
origin: 'http://localhost',
|
|
135
|
+
'content-type': 'application/json',
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({ locale: 'en-US' }),
|
|
138
|
+
}
|
|
139
|
+
: undefined,
|
|
140
|
+
);
|
|
141
|
+
expect(response.status).toBe(403);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { resolveAppClientContributions } from '@nocobase/app-client/plugins';
|
|
2
|
-
import { expect, it } from 'vitest';
|
|
3
|
-
import settings from '../../client/pages/settings/routes.js';
|
|
4
|
-
import {
|
|
5
|
-
buildRouteNavigation,
|
|
6
|
-
selectedNavigationId,
|
|
7
|
-
routeKey,
|
|
8
|
-
} from '../../client/routing/route-navigation.js';
|
|
9
|
-
it('groups existing permission pages without changing their URLs or authorization and hides inaccessible children', () => {
|
|
10
|
-
const { routes } = resolveAppClientContributions([
|
|
11
|
-
{ packageName: 'test', source: 'application', routes: [settings] },
|
|
12
|
-
]);
|
|
13
|
-
const group = routes[0].children!.find(
|
|
14
|
-
(route) => route.name === 'permissionManagement',
|
|
15
|
-
)!;
|
|
16
|
-
expect(group.componentLoader).toBeUndefined();
|
|
17
|
-
expect(
|
|
18
|
-
group.children!.map((route) => [route.name, route.path, route.authz]),
|
|
19
|
-
).toEqual([
|
|
20
|
-
[
|
|
21
|
-
'settingsRoles',
|
|
22
|
-
'/settings/roles',
|
|
23
|
-
{
|
|
24
|
-
resource: { type: 'settings', id: 'authorization.permission-sets' },
|
|
25
|
-
action: 'read',
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
[
|
|
29
|
-
'settingsDefaultAccess',
|
|
30
|
-
'/settings/default-access',
|
|
31
|
-
{
|
|
32
|
-
resource: { type: 'settings', id: 'authorization.default-access' },
|
|
33
|
-
action: 'read',
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
]);
|
|
37
|
-
const visible = buildRouteNavigation(routes, new Set())[0].children.find(
|
|
38
|
-
({ route }) => route.name === 'permissionManagement',
|
|
39
|
-
)!;
|
|
40
|
-
expect(visible.children).toEqual([]);
|
|
41
|
-
expect(visible.route.path).toBe('/settings/roles');
|
|
42
|
-
expect(visible.route.navigation?.title).toBe('settings.permissionManagement');
|
|
43
|
-
expect(selectedNavigationId(routes, '/settings/roles/member/edit')).toBe(
|
|
44
|
-
routeKey(visible.route),
|
|
45
|
-
);
|
|
46
|
-
expect(selectedNavigationId(routes, '/settings/default-access')).toBe(
|
|
47
|
-
routeKey(visible.route),
|
|
48
|
-
);
|
|
49
|
-
const denied = new Set([routeKey(group.children![0])]);
|
|
50
|
-
const onlyDefault = buildRouteNavigation(routes, denied)[0].children.find(
|
|
51
|
-
({ route }) => route.name === 'permissionManagement',
|
|
52
|
-
)!;
|
|
53
|
-
expect(onlyDefault.children).toEqual([]);
|
|
54
|
-
expect(onlyDefault.route.path).toBe('/settings/default-access');
|
|
55
|
-
expect(selectedNavigationId(routes, '/settings/default-access', denied)).toBe(
|
|
56
|
-
routeKey(onlyDefault.route),
|
|
57
|
-
);
|
|
58
|
-
denied.add(routeKey(group.children![1]));
|
|
59
|
-
expect(
|
|
60
|
-
buildRouteNavigation(routes, denied)[0].children.map(
|
|
61
|
-
({ route }) => route.name,
|
|
62
|
-
),
|
|
63
|
-
).toEqual(['settingsUsers']);
|
|
64
|
-
});
|