@nocobase/app-template-default 1.0.0-beta.68 → 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.
Files changed (76) hide show
  1. package/.agents/rules/components.md +5 -1
  2. package/.agents/rules/shell.md +3 -1
  3. package/AGENTS.md +7 -3
  4. package/Dockerfile.dockerignore +0 -1
  5. package/README.MD +3 -1
  6. package/client/app.ts +15 -0
  7. package/client/components/data-table/column-header.tsx +91 -0
  8. package/client/components/data-table/data-table.tsx +216 -0
  9. package/client/components/data-table/index.ts +13 -0
  10. package/client/components/data-table/pagination.tsx +165 -0
  11. package/client/components/data-table/view-options.tsx +77 -0
  12. package/client/components/flow-diagram-css.d.ts +2 -0
  13. package/client/components/flow-diagram.tsx +674 -0
  14. package/client/components/flow-layout.ts +250 -0
  15. package/client/components/lifecycle-diagram-model.ts +217 -0
  16. package/client/components/lifecycle-diagram.tsx +310 -0
  17. package/client/components/permission-editor.tsx +589 -0
  18. package/client/components/permission-filter.tsx +455 -0
  19. package/client/components/route-overlay.tsx +26 -13
  20. package/client/components/ui/checkbox.tsx +25 -0
  21. package/client/components/ui/sidebar.tsx +1 -4
  22. package/client/components/ui/switch.tsx +30 -0
  23. package/client/components/ui/table.tsx +113 -0
  24. package/client/extensions/nocobase-workflow-management/README.md +70 -0
  25. package/client/extensions/nocobase-workflow-management/definition-list.tsx +148 -0
  26. package/client/extensions/nocobase-workflow-management/execution-detail.tsx +230 -0
  27. package/client/extensions/nocobase-workflow-management/execution-list.tsx +175 -0
  28. package/client/extensions/nocobase-workflow-management/layout.tsx +95 -0
  29. package/client/layouts/components/app-sidebar.tsx +1 -1
  30. package/client/layouts/use-sidebar-preference.ts +62 -42
  31. package/client/locales/system/en-US.ts +29 -0
  32. package/client/locales/system/settings-en-US.ts +142 -0
  33. package/client/locales/system/settings-zh-CN.ts +136 -0
  34. package/client/locales/system/zh-CN.ts +29 -0
  35. package/client/pages/auth/auth-page.css +15 -0
  36. package/client/pages/auth/login.tsx +1 -1
  37. package/client/pages/auth/shared.tsx +3 -1
  38. package/client/pages/settings/hooks.tsx +137 -0
  39. package/client/pages/settings/page-permission-titles.ts +7 -0
  40. package/client/pages/settings/page-permissions.ts +42 -0
  41. package/client/pages/settings/permission-filters.tsx +119 -0
  42. package/client/pages/settings/permissions-model.ts +260 -0
  43. package/client/pages/settings/role-form.tsx +405 -0
  44. package/client/pages/settings/roles.tsx +163 -0
  45. package/client/pages/settings/routes.ts +79 -0
  46. package/client/pages/settings/shared.tsx +119 -0
  47. package/client/pages/settings/user-detail.tsx +268 -0
  48. package/client/pages/settings/user-form.tsx +427 -0
  49. package/client/pages/settings/users.tsx +451 -0
  50. package/client/routing/app-router.tsx +4 -1
  51. package/client/routing/session-preparation.tsx +65 -0
  52. package/client/styles.css +4 -7
  53. package/package.json +27 -24
  54. package/tests/components/app-layout-routes.test.tsx +14 -7
  55. package/tests/components/data-table.test.tsx +127 -0
  56. package/tests/components/layout-i18n.test.tsx +8 -1
  57. package/tests/components/navigation-menu.test.tsx +24 -14
  58. package/tests/components/permission-editor.test.tsx +477 -0
  59. package/tests/components/permission-filter-binding.test.tsx +150 -0
  60. package/tests/components/permission-filter.test.tsx +202 -0
  61. package/tests/components/settings.test.tsx +294 -0
  62. package/tests/components/sidebar-permissions.test.tsx +12 -4
  63. package/tests/components/sidebar-preference.test.tsx +209 -14
  64. package/tests/components/user-permissions.test.tsx +142 -0
  65. package/tests/fixtures/theme-tokens.tsx +9 -1
  66. package/tests/logic/asset-url-build.test.ts +0 -1
  67. package/tests/logic/client-routes.test.ts +40 -0
  68. package/tests/logic/client-shell.test.tsx +10 -2
  69. package/tests/logic/config-example.test.ts +30 -0
  70. package/tests/logic/dev-client-watch.test.ts +8 -1
  71. package/tests/logic/page-permission-titles.test.ts +223 -0
  72. package/tests/logic/route-overlay.test.tsx +42 -0
  73. package/tests/logic/session-preparation.test.tsx +223 -0
  74. package/tests/logic/settings-permissions.test.ts +168 -0
  75. package/vite.config.ts +0 -22
  76. package/client/agent-annotations-host.ts +0 -25
@@ -0,0 +1,451 @@
1
+ import { DataTable, DataTableColumnHeader } from '#components/data-table';
2
+ import type { ColumnDef } from '@tanstack/react-table';
3
+ import { useSettingsData, useUserPermission } from './hooks.js';
4
+ import { useApiClient, useToaster } from '@nocobase/app-client';
5
+ import { useTranslation } from '@nocobase/i18n/client';
6
+ import {
7
+ UsersClient,
8
+ type ManagedUser,
9
+ } from '@nocobase/app-plugin-users/client/user-client';
10
+ import {
11
+ useCallback,
12
+ useMemo,
13
+ useState,
14
+ useEffect,
15
+ type ReactElement,
16
+ } from 'react';
17
+ import { Link, Outlet, useLocation, useSearchParams } from 'react-router';
18
+ import { MoreHorizontalIcon, PlusIcon, UsersIcon } from 'lucide-react';
19
+ import { PageContainer } from '#components/page-container';
20
+ import { PageHeader } from '#components/page-header';
21
+ import { Button } from '#components/ui/button';
22
+ import { Input } from '#components/ui/input';
23
+ import { Badge } from '#components/ui/badge';
24
+ import {
25
+ Select,
26
+ SelectTrigger,
27
+ SelectValue,
28
+ SelectContent,
29
+ SelectItem,
30
+ } from '#components/ui/select';
31
+ import {
32
+ DropdownMenu,
33
+ DropdownMenuTrigger,
34
+ DropdownMenuContent,
35
+ DropdownMenuItem,
36
+ DropdownMenuSeparator,
37
+ } from '#components/ui/dropdown-menu';
38
+ import {
39
+ Empty,
40
+ EmptyMedia,
41
+ EmptyTitle,
42
+ EmptyDescription,
43
+ EmptyContent,
44
+ } from '#components/ui/empty';
45
+ import { ConfirmAction, SettingsError, SettingsLoading } from './shared.js';
46
+
47
+ function UserActions({
48
+ user,
49
+ reload,
50
+ }: {
51
+ readonly user: ManagedUser;
52
+ readonly reload: (updated?: ManagedUser) => void;
53
+ }): ReactElement {
54
+ const { t } = useTranslation();
55
+ const api = useApiClient();
56
+ const toaster = useToaster();
57
+ const client = useMemo(() => new UsersClient(api), [api]);
58
+ const canUpdate = useUserPermission('update', user.id);
59
+ const canDisable = useUserPermission('disable', user.id);
60
+ const canEnable = useUserPermission('enable', user.id);
61
+ const canRevoke = useUserPermission('revoke-sessions', user.id);
62
+ const location = useLocation();
63
+ const [action, setAction] = useState<
64
+ 'disable' | 'enable' | 'revokeSessions'
65
+ >();
66
+ const [busy, setBusy] = useState(false);
67
+ const [error, setError] = useState<unknown>();
68
+ const run = async () => {
69
+ if (!action) return;
70
+ setBusy(true);
71
+ setError(undefined);
72
+ try {
73
+ const updated = await client[action](user.id);
74
+ toaster.show({
75
+ type: 'success',
76
+ title: t('settings.actionSaved', { name: user.name }),
77
+ });
78
+ setAction(undefined);
79
+ reload(updated ?? undefined);
80
+ } catch (next) {
81
+ setError(next);
82
+ } finally {
83
+ setBusy(false);
84
+ }
85
+ };
86
+ return (
87
+ <>
88
+ <DropdownMenu>
89
+ <DropdownMenuTrigger
90
+ render={
91
+ <Button
92
+ size='icon-sm'
93
+ variant='ghost'
94
+ aria-label={t('settings.actionsFor', { name: user.name })}
95
+ >
96
+ <MoreHorizontalIcon />
97
+ </Button>
98
+ }
99
+ />
100
+ <DropdownMenuContent align='end' className='w-auto min-w-40'>
101
+ {canUpdate && (
102
+ <DropdownMenuItem
103
+ render={
104
+ <Link
105
+ to={{ pathname: `${user.id}/edit`, search: location.search }}
106
+ />
107
+ }
108
+ >
109
+ {t('settings.edit')}
110
+ </DropdownMenuItem>
111
+ )}
112
+ {(canDisable || canEnable || canRevoke) && <DropdownMenuSeparator />}
113
+ {!user.disabledAt && canDisable && (
114
+ <DropdownMenuItem
115
+ variant='destructive'
116
+ onClick={() => setAction('disable')}
117
+ >
118
+ {t('settings.disable')}
119
+ </DropdownMenuItem>
120
+ )}
121
+ {user.disabledAt && canEnable && (
122
+ <DropdownMenuItem onClick={() => setAction('enable')}>
123
+ {t('settings.enable')}
124
+ </DropdownMenuItem>
125
+ )}
126
+ {canRevoke && (
127
+ <DropdownMenuItem
128
+ variant='destructive'
129
+ onClick={() => setAction('revokeSessions')}
130
+ >
131
+ {t('settings.revokeSessions')}
132
+ </DropdownMenuItem>
133
+ )}
134
+ </DropdownMenuContent>
135
+ </DropdownMenu>
136
+ <ConfirmAction
137
+ open={Boolean(action)}
138
+ title={t(
139
+ `settings.${action === 'disable' ? 'disableTitle' : action === 'enable' ? 'enableTitle' : 'revokeTitle'}`,
140
+ { name: user.name },
141
+ )}
142
+ description={
143
+ <>
144
+ <p>
145
+ {t(
146
+ action === 'disable'
147
+ ? 'settings.disableHint'
148
+ : action === 'enable'
149
+ ? 'settings.enableHint'
150
+ : 'settings.revokeHint',
151
+ )}
152
+ </p>
153
+ <SettingsError error={error} />
154
+ </>
155
+ }
156
+ confirmLabel={t(`settings.${action ?? 'confirm'}`)}
157
+ busy={busy}
158
+ onCancel={() => setAction(undefined)}
159
+ onConfirm={() => void run()}
160
+ />
161
+ </>
162
+ );
163
+ }
164
+
165
+ export default function UsersPage(): ReactElement {
166
+ const { t } = useTranslation();
167
+ const [params, setParams] = useSearchParams();
168
+ const location = useLocation();
169
+ const [queryDraft, setQueryDraft] = useState<{
170
+ key: string;
171
+ value: string;
172
+ }>();
173
+ const query =
174
+ queryDraft?.key === location.key
175
+ ? queryDraft.value
176
+ : (params.get('q') ?? '');
177
+ const setQuery = (value: string) =>
178
+ setQueryDraft({ key: location.key, value });
179
+ const [composing, setComposing] = useState(false);
180
+ const page = Math.max(1, Number(params.get('page')) || 1);
181
+ const status = params.get('status') ?? 'all';
182
+ const pageSize = [20, 50, 100].includes(Number(params.get('pageSize')))
183
+ ? Number(params.get('pageSize'))
184
+ : 20;
185
+ const update = (key: string, value: string) => {
186
+ setParams(
187
+ (old) => {
188
+ const next = new URLSearchParams(old);
189
+ if (value && value !== 'all') next.set(key, value);
190
+ else next.delete(key);
191
+ next.delete('page');
192
+ return next;
193
+ },
194
+ { replace: true },
195
+ );
196
+ };
197
+ useEffect(() => {
198
+ if (composing || query === (params.get('q') ?? '')) return;
199
+ const timer = setTimeout(() => {
200
+ setParams(
201
+ (old) => {
202
+ const next = new URLSearchParams(old);
203
+ if (query) next.set('q', query);
204
+ else next.delete('q');
205
+ next.delete('page');
206
+ return next;
207
+ },
208
+ { replace: true },
209
+ );
210
+ }, 300);
211
+ return () => clearTimeout(timer);
212
+ }, [query, composing, params, setParams]);
213
+ const remote = new URLSearchParams({
214
+ page: String(page),
215
+ pageSize: String(pageSize),
216
+ ...(params.get('q') ? { q: params.get('q')! } : {}),
217
+ ...(status !== 'all' ? { status } : {}),
218
+ ...(params.get('order')
219
+ ? { sort: 'name', order: params.get('order')! }
220
+ : {}),
221
+ });
222
+ const request = useSettingsData<{
223
+ data: readonly ManagedUser[];
224
+ meta: { total: number };
225
+ }>(`users?${remote}`, true);
226
+ const { data: pageData, replaceData, reload: refreshUsers } = request;
227
+ const reload = useCallback(
228
+ (updated?: ManagedUser) => {
229
+ if (updated && pageData)
230
+ replaceData({
231
+ ...pageData,
232
+ data: pageData.data.map((row) =>
233
+ row.id === updated.id ? updated : row,
234
+ ),
235
+ });
236
+ refreshUsers();
237
+ },
238
+ [pageData, replaceData, refreshUsers],
239
+ );
240
+ const canCreate = useUserPermission('create');
241
+ const canAssign = useUserPermission('assign-role');
242
+ const rows = request.data?.data ?? [];
243
+ const columns = useMemo<ColumnDef<ManagedUser>[]>(
244
+ () => [
245
+ {
246
+ accessorKey: 'name',
247
+ header: ({ column }) => (
248
+ <DataTableColumnHeader column={column} title={t('settings.name')} />
249
+ ),
250
+ enableHiding: false,
251
+ cell: ({ row }) => (
252
+ <Link
253
+ className='font-medium text-primary hover:underline'
254
+ to={{
255
+ pathname: row.original.id,
256
+ search: location.search,
257
+ }}
258
+ >
259
+ {row.original.name}
260
+ </Link>
261
+ ),
262
+ },
263
+ {
264
+ accessorKey: 'email',
265
+ header: t('settings.email'),
266
+ enableSorting: false,
267
+ cell: ({ row }) => (
268
+ <span className='block max-w-64 truncate' title={row.original.email}>
269
+ {row.original.email}
270
+ </span>
271
+ ),
272
+ },
273
+ {
274
+ id: 'status',
275
+ header: t('settings.status'),
276
+ enableSorting: false,
277
+ cell: ({ row }) => (
278
+ <Badge variant='secondary'>
279
+ {t(
280
+ row.original.disabledAt
281
+ ? 'settings.disabled'
282
+ : 'settings.enabled',
283
+ )}
284
+ </Badge>
285
+ ),
286
+ },
287
+ {
288
+ id: 'roles',
289
+ header: t('settings.assignedRoles'),
290
+ enableSorting: false,
291
+ cell: ({ row }) =>
292
+ Object.values(row.original.roleScopes).flat().join(', ') || '—',
293
+ },
294
+ {
295
+ id: 'actions',
296
+ header: () => <span className='sr-only'>{t('settings.actions')}</span>,
297
+ enableSorting: false,
298
+ cell: ({ row }) => <UserActions user={row.original} reload={reload} />,
299
+ },
300
+ ],
301
+ [t, location.search, reload],
302
+ );
303
+ const create =
304
+ canCreate && canAssign ? (
305
+ <Button
306
+ nativeButton={false}
307
+ render={<Link to={{ pathname: 'create', search: location.search }} />}
308
+ >
309
+ <PlusIcon />
310
+ {t('settings.newUser')}
311
+ </Button>
312
+ ) : null;
313
+ const filtered = Boolean(params.get('q')) || status !== 'all';
314
+ return (
315
+ <>
316
+ <PageContainer>
317
+ <PageHeader
318
+ title={t('settings.users')}
319
+ description={t('settings.usersDescription')}
320
+ actions={rows.length || filtered ? create : undefined}
321
+ />
322
+ <div className='flex flex-wrap items-center gap-3'>
323
+ <Input
324
+ className='w-full sm:max-w-xs'
325
+ value={query}
326
+ aria-label={t('settings.userSearch')}
327
+ placeholder={t('settings.userSearch')}
328
+ onCompositionStart={() => setComposing(true)}
329
+ onCompositionEnd={() => setComposing(false)}
330
+ onChange={(event) => setQuery(event.target.value)}
331
+ />
332
+ <Select
333
+ items={['all', 'enabled', 'disabled'].map((value) => ({
334
+ value,
335
+ label: t(`settings.${value}`),
336
+ }))}
337
+ value={status}
338
+ onValueChange={(value) => update('status', String(value))}
339
+ >
340
+ <SelectTrigger aria-label={t('settings.status')}>
341
+ <SelectValue />
342
+ </SelectTrigger>
343
+ <SelectContent className='w-auto max-w-[min(var(--container-sm),var(--available-width))] min-w-(--anchor-width) [&_[data-slot=select-item]>:first-child]:whitespace-normal'>
344
+ {['all', 'enabled', 'disabled'].map((item) => (
345
+ <SelectItem key={item} value={item}>
346
+ {t(`settings.${item}`)}
347
+ </SelectItem>
348
+ ))}
349
+ </SelectContent>
350
+ </Select>
351
+ {filtered && (
352
+ <Button
353
+ variant='ghost'
354
+ onClick={() => {
355
+ setQuery('');
356
+ setParams({});
357
+ }}
358
+ >
359
+ {t('settings.clear')}
360
+ </Button>
361
+ )}
362
+ </div>
363
+ {request.data && request.loading && (
364
+ <span role='status' className='text-sm text-muted-foreground'>
365
+ {t('settings.loading')}
366
+ </span>
367
+ )}
368
+ <SettingsError error={request.error} retry={request.reload} />
369
+ {request.loading && !request.data ? (
370
+ <SettingsLoading />
371
+ ) : (
372
+ !request.error &&
373
+ (rows.length ? (
374
+ <DataTable<ManagedUser>
375
+ columns={columns}
376
+ data={rows}
377
+ getRowId={(row) => row.id}
378
+ manualPagination
379
+ rowCount={request.data?.meta.total ?? 0}
380
+ paginationState={{ pageIndex: page - 1, pageSize }}
381
+ onPaginationChange={(updater) => {
382
+ const next =
383
+ typeof updater === 'function'
384
+ ? updater({ pageIndex: page - 1, pageSize })
385
+ : updater;
386
+ setParams((old) => {
387
+ const result = new URLSearchParams(old);
388
+ result.set('page', String(next.pageIndex + 1));
389
+ result.set('pageSize', String(next.pageSize));
390
+ return result;
391
+ });
392
+ }}
393
+ manualSorting
394
+ sortingState={
395
+ params.get('order')
396
+ ? [{ id: 'name', desc: params.get('order') === 'desc' }]
397
+ : []
398
+ }
399
+ onSortingChange={(updater) => {
400
+ const previous = params.get('order')
401
+ ? [{ id: 'name', desc: params.get('order') === 'desc' }]
402
+ : [];
403
+ const next =
404
+ typeof updater === 'function' ? updater(previous) : updater;
405
+ update('order', next[0] ? (next[0].desc ? 'desc' : 'asc') : '');
406
+ }}
407
+ pageSizeOptions={[20, 50, 100]}
408
+ showSelectedCount={false}
409
+ loading={request.loading}
410
+ />
411
+ ) : (
412
+ <Empty className='min-h-48 border border-dashed'>
413
+ <EmptyMedia variant='icon'>
414
+ <UsersIcon />
415
+ </EmptyMedia>
416
+ <EmptyTitle>
417
+ {t(filtered ? 'settings.noResults' : 'settings.noUsers')}
418
+ </EmptyTitle>
419
+ <EmptyDescription>
420
+ {t(
421
+ filtered ? 'settings.clearHint' : 'settings.usersDescription',
422
+ )}
423
+ </EmptyDescription>
424
+ <EmptyContent>
425
+ {filtered ? (
426
+ <Button
427
+ variant='outline'
428
+ onClick={() => {
429
+ setQuery('');
430
+ setParams({});
431
+ }}
432
+ >
433
+ {t('settings.clear')}
434
+ </Button>
435
+ ) : (
436
+ create
437
+ )}
438
+ </EmptyContent>
439
+ </Empty>
440
+ ))
441
+ )}
442
+ {request.data && (
443
+ <p className='text-sm text-muted-foreground'>
444
+ {t('settings.total', { count: request.data.meta.total })}
445
+ </p>
446
+ )}
447
+ </PageContainer>
448
+ <Outlet context={{ reload }} />
449
+ </>
450
+ );
451
+ }
@@ -11,6 +11,7 @@ import { EMPTY_ARRAY } from '#lib/constants';
11
11
  import { AppLayout } from '../layouts/app-layout.js';
12
12
  import { renderRouteTree } from './route-tree.js';
13
13
  import { StandalonePageLayout } from './standalone-page-layout.js';
14
+ import { SessionPreparation } from './session-preparation.js';
14
15
 
15
16
  export interface AppRouterProps {
16
17
  readonly clientRoutes: readonly AppClientRegisteredRoute[];
@@ -34,7 +35,9 @@ export function AppRouter({ clientRoutes }: AppRouterProps): ReactElement {
34
35
  <Route
35
36
  element={
36
37
  <RequiredAuthentication>
37
- <Outlet />
38
+ <SessionPreparation>
39
+ <Outlet />
40
+ </SessionPreparation>
38
41
  </RequiredAuthentication>
39
42
  }
40
43
  >
@@ -0,0 +1,65 @@
1
+ import { useAuthentication } from '@nocobase/app-plugin-authentication/client';
2
+ import { useTranslation } from '@nocobase/i18n/client';
3
+ import { useState, type PropsWithChildren, type ReactElement } from 'react';
4
+
5
+ import { Loading } from '#components/loading';
6
+ import { Button } from '#components/ui/button';
7
+
8
+ /** The first identity handshake must invalidate permissions before pages mount. */
9
+ export function SessionPreparation({
10
+ children,
11
+ }: PropsWithChildren): ReactElement {
12
+ const { preparation, retryPreparation, client, refresh } =
13
+ useAuthentication();
14
+ const { t } = useTranslation();
15
+ const [signingOut, setSigningOut] = useState(false);
16
+ const [signOutFailed, setSignOutFailed] = useState(false);
17
+
18
+ if (preparation === 'ready') return <>{children}</>;
19
+ if (preparation === 'preparing') {
20
+ return <Loading className='min-h-svh' label={t('auth.preparingSession')} />;
21
+ }
22
+
23
+ return (
24
+ <section className='grid min-h-svh place-items-center px-6'>
25
+ <div className='w-full max-w-lg space-y-4 text-center'>
26
+ <h1 className='text-xl font-semibold'>{t('auth.connectionFailed')}</h1>
27
+ <p className='text-sm text-muted-foreground'>
28
+ {t('auth.connectionFailedDescription')}
29
+ </p>
30
+ {signOutFailed ? (
31
+ <p role='alert'>{t('account.signOutFailed')}</p>
32
+ ) : null}
33
+ <div className='flex justify-center gap-3'>
34
+ <Button disabled={signingOut} onClick={retryPreparation}>
35
+ {t('auth.retry')}
36
+ </Button>
37
+ <Button
38
+ variant='outline'
39
+ disabled={signingOut}
40
+ onClick={() => {
41
+ void (async () => {
42
+ setSigningOut(true);
43
+ setSignOutFailed(false);
44
+ try {
45
+ const result = await client.signOut();
46
+ if (result.error) {
47
+ setSignOutFailed(true);
48
+ return;
49
+ }
50
+ await refresh();
51
+ } catch {
52
+ setSignOutFailed(true);
53
+ } finally {
54
+ setSigningOut(false);
55
+ }
56
+ })();
57
+ }}
58
+ >
59
+ {t(signingOut ? 'account.signingOut' : 'account.signOut')}
60
+ </Button>
61
+ </div>
62
+ </div>
63
+ </section>
64
+ );
65
+ }
package/client/styles.css CHANGED
@@ -252,16 +252,13 @@ label:has(> .sr-only:focus-visible),
252
252
  :is([data-slot='dialog-description'], [data-slot='alert-dialog-description']) {
253
253
  line-height: 1.65;
254
254
  }
255
- :is(
256
- [data-slot='dialog-footer'],
257
- [data-slot='alert-dialog-footer'],
258
- [data-slot='route-overlay-footer']
259
- )
260
- > button {
255
+ :is([data-slot='dialog-footer'], [data-slot='alert-dialog-footer']) > button {
261
256
  min-height: calc(var(--spacing) * 10);
262
257
  padding-inline: calc(var(--spacing) * 4);
263
258
  }
264
- [data-slot='dialog-content'] > [data-slot='dialog-close'] {
259
+ /* Route overlays compose the dialog primitives, so their close button sits where a dialog's does. */
260
+ :is([data-slot='dialog-content'], [data-slot='route-overlay-content'])
261
+ > [data-slot='dialog-close'] {
265
262
  top: calc(var(--spacing) * 4);
266
263
  right: calc(var(--spacing) * 4);
267
264
  }
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.68",
4
+ "version": "1.0.0-beta.70",
5
5
  "type": "module",
6
6
  "prettier": "@nocobase/dev-config/prettier",
7
7
  "engines": {
@@ -9,13 +9,12 @@
9
9
  },
10
10
  "nocobase": {
11
11
  "templateKind": "app",
12
- "defaultTemplateVersion": "1.0.0-beta.68"
12
+ "defaultTemplateVersion": "1.0.0-beta.70"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ai-sdk/react": "2.0.0",
16
16
  "@base-ui/react": "^1.6.0",
17
17
  "@dicebear/core": "^9.4.3",
18
- "@gchust/agent-annotations": "0.1.9",
19
18
  "@hookform/resolvers": "^5.0.1",
20
19
  "@playwright/test": "^1.62.1",
21
20
  "@refinedev/cli": "^2.16.48",
@@ -27,6 +26,7 @@
27
26
  "@tailwindcss/postcss": "^4.1.4",
28
27
  "@tailwindcss/vite": "^4.1.8",
29
28
  "@tanstack/react-query": "^5.81.5",
29
+ "@tanstack/react-table": "^8.2.6",
30
30
  "@testing-library/jest-dom": "^7.0.0",
31
31
  "@testing-library/react": "^16.3.2",
32
32
  "@testing-library/user-event": "^14.6.3",
@@ -75,15 +75,17 @@
75
75
  "vite": "^6.3.5",
76
76
  "vitest": "^4.1.10",
77
77
  "tar": "^7.5.22",
78
- "@nocobase/app-client": "^3.0.0-beta.1",
78
+ "@xyflow/react": "^12.11.5",
79
+ "elkjs": "^0.10.2",
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.35",
81
- "@nocobase/app-testing": "0.1.0-beta.3",
82
+ "@nocobase/app-skills": "0.1.0-beta.38",
83
+ "@nocobase/app-testing": "0.1.0-beta.4",
82
84
  "@nocobase/app-websocket": "^0.0.2-beta.0",
83
- "@nocobase/dev-config": "0.1.0-beta.22",
84
- "@nocobase/drive": "^0.1.0-beta.5",
85
- "@nocobase/logging": "^0.1.0-beta.7",
86
- "@nocobase/session": "^0.1.0-beta.3"
85
+ "@nocobase/dev-config": "0.1.0-beta.23",
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",
@@ -142,35 +144,35 @@
142
144
  "@oclif/core": "^4.14.0",
143
145
  "better-auth": "1.7.5",
144
146
  "hono": "^4.13.2",
145
- "@nocobase/app-plugin-api-keys": "^1.0.0-beta.16",
146
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.8",
147
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.27",
148
- "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.14",
149
- "@nocobase/app-plugin-file": "^1.0.0-beta.23",
147
+ "@nocobase/app-plugin-api-keys": "^1.0.0-beta.17",
148
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.10",
149
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.28",
150
+ "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.15",
151
+ "@nocobase/app-plugin-file": "^1.0.0-beta.24",
150
152
  "@nocobase/app-plugin-i18n": "^1.0.0-beta.16",
151
- "@nocobase/app-plugin-mail": "^1.0.0-beta.13",
153
+ "@nocobase/app-plugin-mail": "^1.0.0-beta.14",
152
154
  "@nocobase/app-plugin-notification": "^1.0.0-beta.27",
153
- "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.24",
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
- "@nocobase/app-plugin-users": "^2.0.0-beta.4",
157
- "@nocobase/app-server": "^2.0.0-beta.6",
158
+ "@nocobase/app-plugin-users": "^2.0.0-beta.5",
159
+ "@nocobase/app-server": "^2.0.0-beta.8",
158
160
  "@nocobase/config": "^0.1.0-beta.3",
159
- "@nocobase/db": "^1.0.0-beta.19",
160
- "@nocobase/db-sqlite": "^0.1.0-beta.5",
161
+ "@nocobase/db": "^1.0.0-beta.21",
162
+ "@nocobase/db-sqlite": "^0.1.0-beta.6",
161
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",
165
167
  "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.12",
166
- "@nocobase/authorization": "^1.0.0-beta.13",
168
+ "@nocobase/authorization": "^1.0.0-beta.14",
167
169
  "@nocobase/queue": "^0.1.0-beta.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
173
  "@nocobase/caching": "^0.1.0-beta.3",
172
174
  "@nocobase/repository-input": "^0.1.0-beta.3",
173
- "@nocobase/app-cli": "^1.0.0-beta.21"
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": {
@@ -178,7 +180,8 @@
178
180
  "#hooks/*": "./client/hooks/*.js",
179
181
  "#lib/*": "./client/lib/*.js",
180
182
  "#extensions/*": "./client/extensions/*.js",
181
- "#pages/*": "./client/pages/*.js"
183
+ "#pages/*": "./client/pages/*.js",
184
+ "#components/data-table": "./client/components/data-table/index.js"
182
185
  },
183
186
  "scripts": {
184
187
  "postinstall": "nocobase skills sync",