@nocobase/app-template-default 1.0.0-beta.73 → 1.0.0-beta.75

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 (38) hide show
  1. package/AGENTS.md +8 -2
  2. package/README.MD +3 -1
  3. package/cli/plugins.ts +2 -1
  4. package/client/components/default-access-editor.tsx +183 -0
  5. package/client/components/permission-editor.tsx +216 -150
  6. package/client/components/role-list.tsx +85 -0
  7. package/client/lib/navigation-transform.ts +29 -1
  8. package/client/locales/system/settings-en-US.ts +74 -2
  9. package/client/locales/system/settings-zh-CN.ts +66 -2
  10. package/client/pages/settings/default-access-form.tsx +499 -0
  11. package/client/pages/settings/default-access-model.ts +154 -0
  12. package/client/pages/settings/default-access.tsx +407 -0
  13. package/client/pages/settings/permission-filters.tsx +43 -37
  14. package/client/pages/settings/permission-management-page.tsx +105 -0
  15. package/client/pages/settings/permissions-model.ts +37 -1
  16. package/client/pages/settings/role-form.tsx +46 -18
  17. package/client/pages/settings/roles.tsx +26 -43
  18. package/client/pages/settings/routes.ts +78 -19
  19. package/client/pages/settings/shared.tsx +3 -0
  20. package/client/pages/settings/users.tsx +1 -1
  21. package/client/plugins.ts +2 -0
  22. package/client/routing/route-navigation.ts +21 -1
  23. package/package.json +25 -23
  24. package/server/plugins.ts +2 -0
  25. package/tests/components/default-access.test.tsx +554 -0
  26. package/tests/components/page-harness.test.tsx +132 -112
  27. package/tests/components/permission-editor.test.tsx +233 -73
  28. package/tests/components/permission-management-tabs.test.tsx +128 -0
  29. package/tests/components/role-list.test.tsx +136 -0
  30. package/tests/components/roles.test.tsx +85 -0
  31. package/tests/components/user-permissions.test.tsx +12 -0
  32. package/tests/logic/authorization-assignment-safety.test.ts +82 -0
  33. package/tests/logic/client-routes.test.ts +8 -0
  34. package/tests/logic/default-access-model.test.ts +167 -0
  35. package/tests/logic/foundation-integration.test.ts +62 -0
  36. package/tests/logic/permission-management-routes.test.ts +64 -0
  37. package/tests/logic/settings-permissions.test.ts +89 -0
  38. package/vitest.config.ts +2 -2
@@ -11,6 +11,7 @@ import {
11
11
  import { useTranslation } from '@nocobase/i18n/client';
12
12
  import {
13
13
  useAuthorizationClient,
14
+ useCan,
14
15
  type AuthorizationOptionsResponse,
15
16
  type LocalizedText,
16
17
  type PermissionGrant,
@@ -20,6 +21,7 @@ import { pagePermissionResources } from './page-permissions.js';
20
21
  import { pagePermissionTitles } from './page-permission-titles.js';
21
22
  import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
22
23
  import {
24
+ Link,
23
25
  useLocation,
24
26
  useNavigate,
25
27
  useOutletContext,
@@ -72,6 +74,10 @@ function RoleEditor(): ReactElement {
72
74
  const options = useSettingsData<{ data: AuthorizationOptionsResponse }>(
73
75
  'authorization/permissionSets/options',
74
76
  );
77
+ const canReadDefaults = useCan({
78
+ resource: { type: 'settings', id: 'authorization.default-access' },
79
+ action: 'read',
80
+ }).can;
75
81
  const canCreate = useRolePermission('create');
76
82
  const canUpdate = useRolePermission('update');
77
83
  const [nameDraft, setNameDraft] = useState<string>();
@@ -113,14 +119,35 @@ function RoleEditor(): ReactElement {
113
119
  title: { key: 'settings.userPermissions' },
114
120
  order: 0,
115
121
  subsections: [
116
- {
117
- name: 'users.accounts',
118
- title: { key: 'settings.users' },
122
+ ...[
123
+ {
124
+ name: 'users.information',
125
+ title: 'userInformation',
126
+ actions: ['read', 'create', 'update', 'invite', 'delete'],
127
+ },
128
+ {
129
+ name: 'users.security',
130
+ title: 'accountSecurity',
131
+ actions: [
132
+ 'reset-password',
133
+ 'disable',
134
+ 'enable',
135
+ 'revoke-sessions',
136
+ ],
137
+ },
138
+ {
139
+ name: 'users.roles',
140
+ title: 'roleAssignment',
141
+ actions: ['assign-role'],
142
+ },
143
+ ].map((group) => ({
144
+ name: group.name,
145
+ title: { key: `settings.${group.title}` },
119
146
  resources: [
120
147
  {
121
148
  type: 'user',
122
149
  id: '*',
123
- title: { key: 'settings.users' },
150
+ title: { key: `settings.${group.title}` },
124
151
  actions: [
125
152
  { name: 'read', title: { key: 'settings.readUsers' } },
126
153
  { name: 'create', title: { key: 'settings.createUsers' } },
@@ -141,10 +168,10 @@ function RoleEditor(): ReactElement {
141
168
  name: 'revoke-sessions',
142
169
  title: { key: 'settings.revokeSessions' },
143
170
  },
144
- ],
171
+ ].filter((action) => group.actions.includes(action.name)),
145
172
  },
146
173
  ],
147
- },
174
+ })),
148
175
  ],
149
176
  },
150
177
  ...source.sections.map((section) => ({
@@ -316,13 +343,23 @@ function RoleEditor(): ReactElement {
316
343
  {t('settings.protectedHint')}
317
344
  </p>
318
345
  )}
346
+ {canReadDefaults && (
347
+ <div>
348
+ <Link
349
+ to='/default-access'
350
+ className='text-sm text-foreground underline-offset-4 hover:underline'
351
+ >
352
+ {t('settings.defaultAccess.manage')}
353
+ </Link>
354
+ </div>
355
+ )}
319
356
  <PermissionEditor
320
357
  sections={sections}
321
358
  readOnly={!allowed || busy}
322
359
  labels={{
323
- selectAll: t('settings.selectAllPermissions'),
324
- selectVisible: (name) =>
325
- t('settings.selectVisiblePermissions', { name }),
360
+ dataRanges: (count) => t('settings.dataRanges', { count }),
361
+ scopeExplanation: t('settings.scopeExplanation'),
362
+ scopeHintFor: (name) => t('settings.scopeHintFor', { name }),
326
363
  levelFor: (name) => t('settings.levelFor', { name }),
327
364
  search: t('settings.permissionSearch'),
328
365
  enabledOnly: t('settings.enabledOnly'),
@@ -330,8 +367,6 @@ function RoleEditor(): ReactElement {
330
367
  empty: t('settings.noPermissions'),
331
368
  noResults: t('settings.noResults'),
332
369
  navigation: t('settings.permissionCategories'),
333
- summary: (enabled, total) =>
334
- t('settings.permissionSummary', { enabled, total }),
335
370
  }}
336
371
  onEnabledChange={(id, enabled) => {
337
372
  if (!catalog) return;
@@ -342,13 +377,6 @@ function RoleEditor(): ReactElement {
342
377
  );
343
378
  setDirty(true);
344
379
  }}
345
- onEnabledBatchChange={(changes) => {
346
- if (!catalog) return;
347
- setGrantsDraft((current) =>
348
- setPermissions(current ?? grants, catalog, changes),
349
- );
350
- setDirty(true);
351
- }}
352
380
  onLevelChange={(id, value) => {
353
381
  if (value === 'customFilter') {
354
382
  setEditingScope(id);
@@ -7,11 +7,10 @@ import type {
7
7
  import { type ReactElement } from 'react';
8
8
  import { Link, Outlet, useLocation, useSearchParams } from 'react-router';
9
9
  import { ShieldCheckIcon, PlusIcon } from 'lucide-react';
10
- import { PageContainer } from '#components/page-container';
11
- import { PageHeader } from '#components/page-header';
10
+ import { PermissionManagementPage } from './permission-management-page.js';
12
11
  import { Input } from '#components/ui/input';
13
12
  import { Button } from '#components/ui/button';
14
- import { Badge } from '#components/ui/badge';
13
+ import { RoleList } from '#components/role-list';
15
14
  import {
16
15
  Empty,
17
16
  EmptyTitle,
@@ -60,12 +59,10 @@ export default function RolesPage(): ReactElement {
60
59
  ) : null;
61
60
  return (
62
61
  <>
63
- <PageContainer>
64
- <PageHeader
65
- title={t('settings.roles')}
66
- description={t('settings.rolesDescription')}
67
- actions={rows.length || search ? create : undefined}
68
- />
62
+ <PermissionManagementPage
63
+ tab='roles'
64
+ actions={rows.length || search ? create : undefined}
65
+ >
69
66
  <div className='flex items-center gap-3'>
70
67
  <Input
71
68
  className='sm:max-w-xs'
@@ -95,39 +92,25 @@ export default function RolesPage(): ReactElement {
95
92
  ) : (
96
93
  !request.error &&
97
94
  (rows.length ? (
98
- <div className='divide-y overflow-hidden rounded-xl border bg-card'>
99
- {rows.map((role) => (
100
- <Link
101
- key={role.key}
102
- to={{
103
- pathname: `${encodeURIComponent(role.key)}/edit`,
104
- search: location.search,
105
- }}
106
- className='flex items-center justify-between gap-4 p-4 transition-colors hover:bg-muted/30 focus-visible:ring-2 focus-visible:ring-ring'
107
- >
108
- <div className='flex min-w-0 items-center gap-3'>
109
- <div className='rounded-lg bg-primary/10 p-2 text-primary'>
110
- <ShieldCheckIcon className='size-4' />
111
- </div>
112
- <div className='min-w-0'>
113
- <p className='font-medium'>
114
- {text(role.title ?? role.key)}
115
- </p>
116
- <p className='text-xs text-muted-foreground'>
117
- {role.key}
118
- </p>
119
- </div>
120
- </div>
121
- <Badge variant='secondary'>
122
- {t(
123
- role.protection
124
- ? 'settings.protected'
125
- : 'settings.custom',
126
- )}
127
- </Badge>
128
- </Link>
129
- ))}
130
- </div>
95
+ <RoleList
96
+ ariaLabel={t('settings.roles')}
97
+ roles={rows.map((role) => ({
98
+ key: role.key,
99
+ title: text(role.title ?? role.key),
100
+ to: {
101
+ pathname: `${encodeURIComponent(role.key)}/edit`,
102
+ search: location.search,
103
+ },
104
+ protected: Boolean(role.protection),
105
+ }))}
106
+ labels={{
107
+ name: t('settings.roleName'),
108
+ key: t('settings.key'),
109
+ type: t('settings.roleType'),
110
+ protected: t('settings.protected'),
111
+ custom: t('settings.custom'),
112
+ }}
113
+ />
131
114
  ) : (
132
115
  <Empty className='min-h-48 border border-dashed'>
133
116
  <EmptyMedia variant='icon'>
@@ -156,7 +139,7 @@ export default function RolesPage(): ReactElement {
156
139
  </Empty>
157
140
  ))
158
141
  )}
159
- </PageContainer>
142
+ </PermissionManagementPage>
160
143
  <Outlet context={{ reload: request.reload }} />
161
144
  </>
162
145
  );
@@ -1,4 +1,10 @@
1
- import { Settings, Users, ShieldCheck } from 'lucide-react';
1
+ import {
2
+ Settings,
3
+ Users,
4
+ ShieldCheck,
5
+ KeyRound,
6
+ ListFilter,
7
+ } from 'lucide-react';
2
8
  import {
3
9
  defineAppRoutes,
4
10
  type AppClientRouteContribution,
@@ -41,35 +47,88 @@ const systemSettingsRoutes: AppClientRouteContribution = defineAppRoutes([
41
47
  ],
42
48
  },
43
49
  {
44
- name: 'settingsRoles',
45
- path: '/roles',
46
- auth: 'required',
47
- authz: {
48
- resource: { type: 'settings', id: 'authorization.permission-sets' },
49
- action: 'read',
50
+ name: 'permissionManagement',
51
+ navigation: {
52
+ title: 'settings.permissionManagement',
53
+ icon: ShieldCheck,
50
54
  },
51
- navigation: { title: 'settings.roles', icon: ShieldCheck },
52
- breadcrumb: { title: 'settings.roles' },
53
- componentLoader: () => import('./roles.js'),
55
+ breadcrumb: { title: 'settings.permissionManagement' },
54
56
  children: [
55
57
  {
56
- name: 'settingsRoleCreate',
57
- path: 'create',
58
+ name: 'settingsRoles',
59
+ path: '/roles',
60
+ auth: 'required',
58
61
  authz: {
59
62
  resource: {
60
63
  type: 'settings',
61
64
  id: 'authorization.permission-sets',
62
65
  },
63
- action: 'create',
66
+ action: 'read',
64
67
  },
65
- breadcrumb: { title: 'settings.newRole' },
66
- componentLoader: () => import('./role-form.js'),
68
+ navigation: { title: 'settings.roles', icon: KeyRound },
69
+ breadcrumb: { title: 'settings.roles' },
70
+ componentLoader: () => import('./roles.js'),
71
+ children: [
72
+ {
73
+ name: 'settingsRoleCreate',
74
+ path: 'create',
75
+ authz: {
76
+ resource: {
77
+ type: 'settings',
78
+ id: 'authorization.permission-sets',
79
+ },
80
+ action: 'create',
81
+ },
82
+ breadcrumb: { title: 'settings.newRole' },
83
+ componentLoader: () => import('./role-form.js'),
84
+ },
85
+ {
86
+ name: 'settingsRoleEdit',
87
+ path: ':key/edit',
88
+ breadcrumb: { title: 'settings.editRole' },
89
+ componentLoader: () => import('./role-form.js'),
90
+ },
91
+ ],
67
92
  },
68
93
  {
69
- name: 'settingsRoleEdit',
70
- path: ':key/edit',
71
- breadcrumb: { title: 'settings.editRole' },
72
- componentLoader: () => import('./role-form.js'),
94
+ name: 'settingsDefaultAccess',
95
+ path: '/default-access',
96
+ auth: 'required',
97
+ authz: {
98
+ resource: {
99
+ type: 'settings',
100
+ id: 'authorization.default-access',
101
+ },
102
+ action: 'read',
103
+ },
104
+ navigation: {
105
+ title: 'settings.defaultAccess.title',
106
+ icon: ListFilter,
107
+ },
108
+ breadcrumb: { title: 'settings.defaultAccess.title' },
109
+ componentLoader: () => import('./default-access.js'),
110
+ children: [
111
+ {
112
+ name: 'settingsDefaultAccessCreate',
113
+ path: 'create',
114
+ authz: {
115
+ resource: {
116
+ type: 'settings',
117
+ id: 'authorization.default-access',
118
+ },
119
+ action: 'create',
120
+ },
121
+ breadcrumb: { title: 'settings.defaultAccess.new' },
122
+ componentLoader: () => import('./default-access-form.js'),
123
+ },
124
+ // Read permission opens the full rule even when the viewer may not update it.
125
+ {
126
+ name: 'settingsDefaultAccessEdit',
127
+ path: ':key/edit',
128
+ breadcrumb: { title: 'settings.defaultAccess.edit' },
129
+ componentLoader: () => import('./default-access-form.js'),
130
+ },
131
+ ],
73
132
  },
74
133
  ],
75
134
  },
@@ -78,6 +78,7 @@ export function ConfirmAction({
78
78
  title,
79
79
  description,
80
80
  confirmLabel,
81
+ error,
81
82
  busy,
82
83
  onCancel,
83
84
  onConfirm,
@@ -86,6 +87,7 @@ export function ConfirmAction({
86
87
  readonly title: string;
87
88
  readonly description: ReactNode;
88
89
  readonly confirmLabel?: string;
90
+ readonly error?: ReactNode;
89
91
  readonly busy: boolean;
90
92
  readonly onCancel: () => void;
91
93
  readonly onConfirm: () => void;
@@ -102,6 +104,7 @@ export function ConfirmAction({
102
104
  <AlertDialogHeader>
103
105
  <AlertDialogTitle>{title}</AlertDialogTitle>
104
106
  <AlertDialogDescription>{description}</AlertDialogDescription>
107
+ {error}
105
108
  </AlertDialogHeader>
106
109
  <AlertDialogFooter>
107
110
  <AlertDialogCancel disabled={busy}>
@@ -250,7 +250,7 @@ export default function UsersPage(): ReactElement {
250
250
  enableHiding: false,
251
251
  cell: ({ row }) => (
252
252
  <Link
253
- className='font-medium text-primary hover:underline'
253
+ className='font-medium text-foreground underline-offset-4 hover:underline'
254
254
  to={{
255
255
  pathname: row.original.id,
256
256
  search: location.search,
package/client/plugins.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import foundation from '@nocobase/app-plugin-foundation/client';
1
2
  import defaultAccess from '@nocobase/app-plugin-authz-default-access/client';
2
3
  import sharingRules from '@nocobase/app-plugin-authz-sharing-rules/client';
3
4
  import restrictionRules from '@nocobase/app-plugin-authz-restriction-rules/client';
@@ -34,6 +35,7 @@ const clientPlugins: AppClientPlugins = defineClientPlugins([
34
35
  file(),
35
36
  scheduler(),
36
37
  mail(),
38
+ foundation({ settingsGroup: true }),
37
39
  ]);
38
40
 
39
41
  export default clientPlugins;
@@ -11,6 +11,8 @@ import { EMPTY_ARRAY } from '#lib/constants';
11
11
  import { transformNavigation } from '#lib/navigation-transform';
12
12
 
13
13
  export interface RouteNavigationItem {
14
+ /** Route keys represented by a single sidebar entry whose content uses tabs. */
15
+ readonly activeRouteKeys?: readonly string[];
14
16
  readonly section?: boolean;
15
17
  readonly route: AppClientRegisteredRoute;
16
18
  readonly children: readonly RouteNavigationItem[];
@@ -45,7 +47,25 @@ export function selectedNavigationId(
45
47
  denied: ReadonlySet<string> = new Set(),
46
48
  ): string | undefined {
47
49
  const matches = matchRouteTree(routes, pathname);
48
- const pages = navigationPages(buildRouteNavigation(routes, denied));
50
+ const items = buildRouteNavigation(routes, denied);
51
+ const represented = (
52
+ nodes: readonly RouteNavigationItem[],
53
+ ): AppClientRegisteredRoute | undefined => {
54
+ for (const item of nodes) {
55
+ if (
56
+ item.activeRouteKeys?.some((key) =>
57
+ matches?.some(({ route }) => routeKey(route) === key),
58
+ )
59
+ )
60
+ return item.route;
61
+ const child = represented(item.children);
62
+ if (child) return child;
63
+ }
64
+ return undefined;
65
+ };
66
+ const active = represented(items);
67
+ if (active) return routeKey(active);
68
+ const pages = navigationPages(items);
49
69
  const visible = new Set(pages.map(routeKey));
50
70
  const selected =
51
71
  matches
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.73",
4
+ "version": "1.0.0-beta.75",
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.73"
12
+ "defaultTemplateVersion": "1.0.0-beta.75"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ai-sdk/react": "4.0.131",
@@ -72,10 +72,10 @@
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.0",
75
+ "@nocobase/app-client": "^4.0.0-beta.1",
76
76
  "@nocobase/app-plugin-template-print": "0.0.2-beta.2",
77
- "@nocobase/app-skills": "0.1.0-beta.41",
78
- "@nocobase/app-testing": "0.1.0-beta.5",
77
+ "@nocobase/app-skills": "0.1.0-beta.43",
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",
81
81
  "@nocobase/drive": "^0.1.0-beta.7",
@@ -139,36 +139,38 @@
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.15",
142
+ "@nocobase/app-plugin-agents": "^1.0.0-beta.17",
143
143
  "@nocobase/app-plugin-api-keys": "^1.0.0-beta.18",
144
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.11",
145
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.29",
144
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.12",
145
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.30",
146
146
  "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.16",
147
- "@nocobase/app-plugin-file": "^1.0.0-beta.25",
148
- "@nocobase/app-plugin-i18n": "^1.0.0-beta.17",
149
- "@nocobase/app-plugin-mail": "^1.0.0-beta.15",
150
- "@nocobase/app-plugin-notification": "^1.0.0-beta.28",
151
- "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.26",
152
- "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.13",
153
- "@nocobase/app-plugin-scheduler": "^1.0.0-beta.18",
154
- "@nocobase/app-plugin-users": "^2.0.0-beta.6",
155
- "@nocobase/app-server": "^3.0.0-beta.0",
147
+ "@nocobase/app-plugin-file": "^1.0.0-beta.26",
148
+ "@nocobase/app-plugin-foundation": "^0.1.0-beta.0",
149
+ "@nocobase/app-plugin-i18n": "^1.0.0-beta.18",
150
+ "@nocobase/app-plugin-knowledge": "^0.1.0-beta.9",
151
+ "@nocobase/app-plugin-notification": "^1.0.0-beta.29",
152
+ "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.27",
153
+ "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.14",
154
+ "@nocobase/app-plugin-scheduler": "^1.0.0-beta.19",
155
+ "@nocobase/app-plugin-users": "^2.0.0-beta.8",
156
+ "@nocobase/app-server": "^3.0.0-beta.1",
156
157
  "@nocobase/config": "^0.1.0-beta.3",
157
158
  "@nocobase/db": "^1.0.0-beta.21",
158
159
  "@nocobase/db-sqlite": "^0.1.0-beta.6",
159
160
  "@nocobase/service-provider": "^0.0.2-beta.2",
160
161
  "@nocobase/snowflake": "^1.0.0-beta.4",
161
- "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.14",
162
- "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.14",
163
- "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.13",
164
- "@nocobase/authorization": "^1.0.0-beta.14",
162
+ "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.15",
163
+ "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.15",
164
+ "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.14",
165
+ "@nocobase/authorization": "^1.0.0-beta.15",
165
166
  "@nocobase/queue": "^0.1.0-beta.9",
166
167
  "@nocobase/jobs": "^0.1.0-beta.3",
167
168
  "@nocobase/lifecycle": "^0.1.0-beta.3",
168
- "@nocobase/i18n": "^2.0.0-beta.0",
169
+ "@nocobase/i18n": "^2.0.0-beta.1",
169
170
  "@nocobase/caching": "^0.1.0-beta.3",
170
171
  "@nocobase/repository-input": "^0.1.0-beta.3",
171
- "@nocobase/app-cli": "^1.0.0-beta.25",
172
+ "@nocobase/app-cli": "^1.0.0-beta.26",
173
+ "@nocobase/app-plugin-mail": "^1.0.0-beta.16",
172
174
  "@nocobase/realtime": "^0.1.0-beta.3"
173
175
  },
174
176
  "description": "NocoBase 3 application template with the standard product capabilities.",
package/server/plugins.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import foundation from '@nocobase/app-plugin-foundation/server';
1
2
  import defaultAccess from '@nocobase/app-plugin-authz-default-access/server';
2
3
  import sharingRules from '@nocobase/app-plugin-authz-sharing-rules/server';
3
4
  import restrictionRules from '@nocobase/app-plugin-authz-restriction-rules/server';
@@ -34,6 +35,7 @@ const serverPlugins: AppServerPlugins = defineServerPlugins([
34
35
  file,
35
36
  scheduler,
36
37
  mail,
38
+ foundation,
37
39
  ]);
38
40
 
39
41
  export default serverPlugins;