@nocobase/portal-template-default 1.0.1 → 1.0.3
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/README.MD +22 -3
- package/package.json +4 -2
- package/registry/nocobase-acl/components/role-options.ts +7 -0
- package/registry/nocobase-acl/components/role-switcher.tsx +9 -4
- package/registry/nocobase-acl/demo/boundary-api.tsx +87 -16
- package/registry/nocobase-acl/demo/components.tsx +32 -11
- package/registry/nocobase-acl/demo/index.tsx +45 -14
- package/registry/nocobase-acl/demo/lazy-route.tsx +2 -7
- package/registry/nocobase-acl/demo/prompt-generator.tsx +52 -33
- package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +28 -9
- package/registry/nocobase-acl/extension.tsx +1 -1
- package/registry/nocobase-acl/tests/acl-regression.mjs +7 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +3 -4
- package/registry/nocobase-ai/components/chat/conversation-list.tsx +2 -4
- package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +16 -19
- package/registry/nocobase-ai/components/tools/chart-renderer.tsx +3 -14
- package/registry/nocobase-ai/components/tools/echarts-preview.tsx +2 -5
- package/registry/nocobase-ai/demo/configuration-gate.tsx +9 -12
- package/registry/nocobase-ai/extension.tsx +2 -7
- package/registry/nocobase-i18n/README.md +53 -0
- package/registry/nocobase-i18n/components/index.ts +1 -0
- package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
- package/registry/nocobase-i18n/demo/index.tsx +209 -0
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
- package/registry/nocobase-i18n/extension.tsx +46 -0
- package/registry/nocobase-i18n/i18n-provider.ts +9 -0
- package/registry/nocobase-i18n/index.ts +6 -0
- package/registry/nocobase-i18n/locale-store.ts +134 -0
- package/registry/nocobase-i18n/locales/en-US.ts +89 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
- package/registry/nocobase-i18n/provider.tsx +32 -0
- package/registry/nocobase-i18n/runtime.ts +118 -0
- package/registry/nocobase-i18n/server-resources.ts +184 -0
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
- package/registry.config.json +34 -14
- package/scripts/registry.mjs +5 -0
- package/src/App.tsx +27 -86
- package/src/app/extension.ts +3 -1
- package/src/app/extensions.tsx +21 -0
- package/src/components/access-control/acl-gate.tsx +4 -10
- package/src/components/app-shell/breadcrumb.tsx +39 -3
- package/src/components/app-shell/document-title-handler.tsx +11 -20
- package/src/components/app-shell/header.tsx +18 -3
- package/src/components/app-shell/loading-overlay.tsx +2 -4
- package/src/components/app-shell/loading-state.tsx +15 -0
- package/src/components/app-shell/sidebar.tsx +75 -47
- package/src/components/data-table/data-table-pagination.tsx +27 -7
- package/src/components/data-table/data-table-styles.ts +57 -0
- package/src/components/data-table/data-table.tsx +14 -59
- package/src/components/resources/resource-label.ts +101 -0
- package/src/components/resources/views/create-view.tsx +17 -14
- package/src/components/resources/views/edit-view.tsx +17 -15
- package/src/components/resources/views/list-view.tsx +20 -10
- package/src/components/resources/views/show-view.tsx +20 -18
- package/src/extensions/nocobase-acl/README.md +19 -0
- package/src/extensions/nocobase-acl/components/acl-boundary.tsx +106 -0
- package/src/extensions/nocobase-acl/components/index.ts +3 -0
- package/src/extensions/nocobase-acl/components/role-options.ts +36 -0
- package/src/extensions/nocobase-acl/components/role-switcher.tsx +135 -0
- package/src/extensions/nocobase-acl/demo/boundary-api.tsx +186 -0
- package/src/extensions/nocobase-acl/demo/components.tsx +231 -0
- package/src/extensions/nocobase-acl/demo/index.tsx +505 -0
- package/src/extensions/nocobase-acl/demo/lazy-route.tsx +10 -0
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +134 -0
- package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
- package/src/extensions/nocobase-acl/demo/scenario-section.tsx +37 -0
- package/src/extensions/nocobase-acl/extension.tsx +71 -0
- package/src/extensions/nocobase-acl/index.ts +1 -0
- package/src/extensions/nocobase-ai/README.md +70 -0
- package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +20 -0
- package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
- package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +32 -0
- package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
- package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
- package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +90 -0
- package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +429 -0
- package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
- package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +88 -0
- package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
- package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +252 -0
- package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +173 -0
- package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +118 -0
- package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +343 -0
- package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +165 -0
- package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +30 -0
- package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
- package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
- package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
- package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
- package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
- package/src/extensions/nocobase-ai/components/index.ts +59 -0
- package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
- package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
- package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
- package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
- package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
- package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
- package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
- package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
- package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
- package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
- package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +49 -0
- package/src/extensions/nocobase-ai/demo/container-showcase.tsx +210 -0
- package/src/extensions/nocobase-ai/demo/floating.tsx +134 -0
- package/src/extensions/nocobase-ai/demo/index.tsx +346 -0
- package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +151 -0
- package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
- package/src/extensions/nocobase-ai/demo/page-context.tsx +884 -0
- package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +197 -0
- package/src/extensions/nocobase-ai/demo/prompt-card.tsx +41 -0
- package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +452 -0
- package/src/extensions/nocobase-ai/demo/shortcut.tsx +1207 -0
- package/src/extensions/nocobase-ai/demo/tool-cards.tsx +530 -0
- package/src/extensions/nocobase-ai/extension.tsx +158 -0
- package/src/extensions/nocobase-ai/global-ai-chat.tsx +213 -0
- package/src/extensions/nocobase-ai/index.ts +3 -0
- package/src/extensions/nocobase-ai/providers/ai-provider.tsx +323 -0
- package/src/extensions/nocobase-ai/providers/avatars.ts +817 -0
- package/src/extensions/nocobase-ai/providers/chat-context.tsx +109 -0
- package/src/extensions/nocobase-ai/providers/chat-controller.ts +84 -0
- package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +198 -0
- package/src/extensions/nocobase-ai/providers/chat-provider.tsx +871 -0
- package/src/extensions/nocobase-ai/providers/chat-reducer.ts +147 -0
- package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +105 -0
- package/src/extensions/nocobase-ai/providers/chat-transport.ts +184 -0
- package/src/extensions/nocobase-ai/providers/form-registry.tsx +271 -0
- package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
- package/src/extensions/nocobase-ai/providers/index.ts +61 -0
- package/src/extensions/nocobase-ai/providers/model.ts +32 -0
- package/src/extensions/nocobase-ai/providers/page-context.tsx +136 -0
- package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +60 -0
- package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +82 -0
- package/src/extensions/nocobase-ai/providers/stream-parser.ts +61 -0
- package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +316 -0
- package/src/extensions/nocobase-ai/providers/types.ts +183 -0
- package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +463 -0
- package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
- package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +206 -0
- package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
- package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +210 -0
- package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +93 -0
- package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
- package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +178 -0
- package/src/extensions/nocobase-ai/services/index.ts +7 -0
- package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +454 -0
- package/src/extensions/nocobase-ai/services/types.ts +82 -0
- package/src/index.tsx +2 -3
- package/src/lib/i18n.ts +161 -0
- package/src/lib/nocobase/client.ts +19 -2
- package/src/locales/en-US.ts +82 -0
- package/src/locales/index.ts +13 -0
- package/src/locales/zh-CN.ts +79 -0
- package/src/pages/users/create.tsx +98 -0
- package/src/pages/users/edit.tsx +92 -0
- package/src/pages/users/form-context.ts +57 -0
- package/src/pages/users/form-fields.tsx +211 -0
- package/src/pages/users/list.tsx +205 -0
- package/src/pages/users/show.tsx +187 -0
- package/src/pages/users/types.ts +20 -0
- package/src/providers/constants.ts +1 -0
- package/src/pages/blog-posts/create.tsx +0 -177
- package/src/pages/blog-posts/edit.tsx +0 -197
- package/src/pages/blog-posts/list.tsx +0 -131
- package/src/pages/blog-posts/show.tsx +0 -81
- package/src/pages/categories/create.tsx +0 -73
- package/src/pages/categories/edit.tsx +0 -73
- package/src/pages/categories/index.ts +0 -4
- package/src/pages/categories/list.tsx +0 -86
- package/src/pages/categories/show.tsx +0 -37
- /package/src/pages/{blog-posts → users}/index.ts +0 -0
package/README.MD
CHANGED
|
@@ -14,7 +14,15 @@ NOCOBASE_API_URL=https://develop.v2.test.nocobase.com/nocobase/api/__app/cms
|
|
|
14
14
|
|
|
15
15
|
In development, the app auto-detects the proxy target from `NOCOBASE_API_URL` when that value is an absolute URL. The configured API URL can stay on the original sub-app path such as `/nocobase/api/__app/cms`.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Application resource names must match NocoBase collection names. The included
|
|
18
|
+
Users example targets the default `users` collection, so it works without a
|
|
19
|
+
project-specific business collection.
|
|
20
|
+
|
|
21
|
+
Application-owned React translations live in `src/locales`. The entry registers
|
|
22
|
+
an `app` namespace for business pages and augments the default `starter`
|
|
23
|
+
namespace for shared controls. Feature-specific Registry translations remain
|
|
24
|
+
inside their own extension namespace and are not mixed into application locale
|
|
25
|
+
files.
|
|
18
26
|
|
|
19
27
|
## Available Scripts
|
|
20
28
|
|
|
@@ -48,6 +56,8 @@ Shadcn Registry item fields, each item has a local `source` mapping used to
|
|
|
48
56
|
generate its standard `files` array. The same mapping lets the starter's
|
|
49
57
|
`postinstall` hook copy a locally authored extension only when its target is
|
|
50
58
|
missing. Existing installed source is never compared, updated, or overwritten.
|
|
59
|
+
Optional entries can set `source.install` to `false`; they remain available to
|
|
60
|
+
Registry builds and local previews but are not added by `postinstall`.
|
|
51
61
|
|
|
52
62
|
You can run the same missing-only installation explicitly with:
|
|
53
63
|
|
|
@@ -55,7 +65,16 @@ You can run the same missing-only installation explicitly with:
|
|
|
55
65
|
pnpm registry:install
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
This repository
|
|
68
|
+
This repository authors optional NocoBase integration entries. Internationalization
|
|
69
|
+
is delivered as one cohesive Registry entry:
|
|
70
|
+
|
|
71
|
+
- `i18n` provides frontend-owned UI resources, selected server-generated
|
|
72
|
+
namespaces such as `lm-collections`, the application translation provider,
|
|
73
|
+
locale persistence, `X-Locale` synchronization, legacy `{{t(...)}}`
|
|
74
|
+
compatibility, the language switcher, and the lazy-loaded showcase with its
|
|
75
|
+
prompt generator.
|
|
76
|
+
|
|
77
|
+
The AI integration is available through four incremental entries:
|
|
59
78
|
|
|
60
79
|
- `ai-runtime` provides AI services, providers, transports, and
|
|
61
80
|
controllers. Its NocoBase requests use the starter's shared
|
|
@@ -64,7 +83,7 @@ This repository currently authors four incremental NocoBase AI entries:
|
|
|
64
83
|
runtime entry.
|
|
65
84
|
- `ai-react-hook-form` adds the optional React Hook Form adapter for the fixed
|
|
66
85
|
Form filler Tool.
|
|
67
|
-
- `ai` adds the
|
|
86
|
+
- `ai` adds the application extension integration and Demo pages and depends on the
|
|
68
87
|
chat entry.
|
|
69
88
|
|
|
70
89
|
To refresh the local preview from the canonical Registry source, run:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/portal-template-default",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@ai-sdk/react": "2.0.0",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"echarts-for-react": "3.0.2",
|
|
30
30
|
"embla-carousel-react": "^8.6.0",
|
|
31
31
|
"eventsource-parser": "3.1.0",
|
|
32
|
-
"input-otp": "^1.4.2",
|
|
33
32
|
"ignore": "^7.0.6",
|
|
33
|
+
"input-otp": "^1.4.2",
|
|
34
34
|
"lucide-react": "^0.487.0",
|
|
35
35
|
"next-themes": "^0.4.6",
|
|
36
36
|
"postcss": "^8.5.3",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
61
61
|
"eslint-plugin-react-refresh": "^0.4.19",
|
|
62
62
|
"globals": "^16.0.0",
|
|
63
|
+
"i18next": "^25.3.2",
|
|
63
64
|
"tailwindcss": "^4.1.4",
|
|
64
65
|
"typescript": "^5.8.3",
|
|
65
66
|
"typescript-eslint": "^8.30.1",
|
|
@@ -80,6 +81,7 @@
|
|
|
80
81
|
"registry:preview": "node ./scripts/registry.mjs preview",
|
|
81
82
|
"registry:build": "node ./scripts/registry.mjs build && shadcn build ./registry.json -o ./public/r",
|
|
82
83
|
"test:acl": "node registry/nocobase-acl/tests/acl-regression.mjs",
|
|
84
|
+
"test:i18n": "node registry/nocobase-i18n/tests/i18n-regression.mjs",
|
|
83
85
|
"start": "refine start",
|
|
84
86
|
"refine": "refine"
|
|
85
87
|
},
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { Role, RoleMode } from "@/lib/nocobase/acl";
|
|
2
|
+
import { resolveTranslatableText } from "@/lib/i18n";
|
|
2
3
|
|
|
3
4
|
export const UNION_ROLE = "__union__";
|
|
4
5
|
export const ANONYMOUS_ROLE = "anonymous";
|
|
5
6
|
|
|
7
|
+
export function resolveRoleTitle(role?: Role) {
|
|
8
|
+
return resolveTranslatableText(role?.title || role?.name || "Role", {
|
|
9
|
+
ns: "starter",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
export function getRoleOptions({
|
|
7
14
|
roles,
|
|
8
15
|
roleMode,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type Role,
|
|
19
19
|
} from "@/lib/nocobase/acl";
|
|
20
20
|
import { cn } from "@/lib/utils";
|
|
21
|
-
import { getRoleOptions, UNION_ROLE } from "./role-options";
|
|
21
|
+
import { getRoleOptions, resolveRoleTitle, UNION_ROLE } from "./role-options";
|
|
22
22
|
|
|
23
23
|
export type RoleSwitcherProps = {
|
|
24
24
|
className?: string;
|
|
@@ -82,7 +82,9 @@ export function RoleSwitcher({
|
|
|
82
82
|
.then(() => window.location.reload())
|
|
83
83
|
.catch((reason) => {
|
|
84
84
|
setError(
|
|
85
|
-
reason instanceof Error
|
|
85
|
+
reason instanceof Error
|
|
86
|
+
? reason.message
|
|
87
|
+
: "Unable to switch role"
|
|
86
88
|
);
|
|
87
89
|
setSwitching(false);
|
|
88
90
|
});
|
|
@@ -120,11 +122,14 @@ function RoleOption({
|
|
|
120
122
|
return (
|
|
121
123
|
<>
|
|
122
124
|
{showSeparator ? <SelectSeparator /> : null}
|
|
123
|
-
<SelectItem value={role.name}>{role
|
|
125
|
+
<SelectItem value={role.name}>{resolveRoleTitle(role)}</SelectItem>
|
|
124
126
|
</>
|
|
125
127
|
);
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
function getRoleTitle(roles: Role[], roleName?: string) {
|
|
129
|
-
return
|
|
131
|
+
return resolveRoleTitle(
|
|
132
|
+
roles.find((role) => role.name === roleName) ??
|
|
133
|
+
(roleName ? { name: roleName } : undefined)
|
|
134
|
+
);
|
|
130
135
|
}
|
|
@@ -8,30 +8,95 @@ import {
|
|
|
8
8
|
TableRow,
|
|
9
9
|
} from "@/components/ui/table";
|
|
10
10
|
|
|
11
|
-
type PropRow = [
|
|
11
|
+
type PropRow = [
|
|
12
|
+
name: string,
|
|
13
|
+
type: string,
|
|
14
|
+
defaultValue: string,
|
|
15
|
+
description: string
|
|
16
|
+
];
|
|
12
17
|
|
|
13
18
|
const pageProps: PropRow[] = [
|
|
14
|
-
[
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
[
|
|
20
|
+
"anyOf",
|
|
21
|
+
"AclPermission[]",
|
|
22
|
+
"undefined",
|
|
23
|
+
"Allows the page when any listed permission is available.",
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"allOf",
|
|
27
|
+
"AclPermission[]",
|
|
28
|
+
"undefined",
|
|
29
|
+
"Requires every listed permission before rendering the page.",
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"fallback",
|
|
33
|
+
"ReactNode",
|
|
34
|
+
"AccessDenied",
|
|
35
|
+
"Replaces the complete page when its permission test fails.",
|
|
36
|
+
],
|
|
17
37
|
["children", "ReactNode", "required", "The protected page content."],
|
|
18
38
|
];
|
|
19
39
|
|
|
20
40
|
const regionProps: PropRow[] = [
|
|
21
|
-
[
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
[
|
|
42
|
+
"resource",
|
|
43
|
+
"string",
|
|
44
|
+
"required",
|
|
45
|
+
"NocoBase collection or ACL resource name.",
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"action",
|
|
49
|
+
"string",
|
|
50
|
+
"required",
|
|
51
|
+
"Resource action such as list, show, create, edit, or delete.",
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"id",
|
|
55
|
+
"BaseKey",
|
|
56
|
+
"undefined",
|
|
57
|
+
"Optional record ID for record-scoped permission checks.",
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"dataSourceKey",
|
|
61
|
+
"string",
|
|
62
|
+
"undefined",
|
|
63
|
+
"Selects a non-main NocoBase data source.",
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"fallback",
|
|
67
|
+
'"hidden" | "forbidden" | ReactNode',
|
|
68
|
+
'"hidden"',
|
|
69
|
+
"Hides the region or replaces it with a local denied state.",
|
|
70
|
+
],
|
|
26
71
|
["children", "ReactNode", "required", "The protected page region."],
|
|
27
72
|
];
|
|
28
73
|
|
|
29
74
|
const fieldProps: PropRow[] = [
|
|
30
|
-
[
|
|
31
|
-
|
|
75
|
+
[
|
|
76
|
+
"resource",
|
|
77
|
+
"string",
|
|
78
|
+
"required",
|
|
79
|
+
"NocoBase collection containing the field.",
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"action",
|
|
83
|
+
"string",
|
|
84
|
+
"required",
|
|
85
|
+
"The action whose field whitelist should be checked.",
|
|
86
|
+
],
|
|
32
87
|
["field", "string", "required", "Exact NocoBase field name."],
|
|
33
|
-
[
|
|
34
|
-
|
|
88
|
+
[
|
|
89
|
+
"dataSourceKey",
|
|
90
|
+
"string",
|
|
91
|
+
"undefined",
|
|
92
|
+
"Selects a non-main NocoBase data source.",
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"fallback",
|
|
96
|
+
"ReactNode",
|
|
97
|
+
"null",
|
|
98
|
+
"Optional read-only replacement for a denied field.",
|
|
99
|
+
],
|
|
35
100
|
["children", "ReactNode", "required", "The protected field or field group."],
|
|
36
101
|
];
|
|
37
102
|
|
|
@@ -99,9 +164,15 @@ function ComponentApi({
|
|
|
99
164
|
<TableBody>
|
|
100
165
|
{rows.map(([name, type, defaultValue, propDescription]) => (
|
|
101
166
|
<TableRow key={name}>
|
|
102
|
-
<TableCell className="font-mono text-xs font-medium">
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
<TableCell className="font-mono text-xs font-medium">
|
|
168
|
+
{name}
|
|
169
|
+
</TableCell>
|
|
170
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
171
|
+
{type}
|
|
172
|
+
</TableCell>
|
|
173
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
174
|
+
{defaultValue}
|
|
175
|
+
</TableCell>
|
|
105
176
|
<TableCell className="min-w-72 whitespace-normal text-muted-foreground">
|
|
106
177
|
{propDescription}
|
|
107
178
|
</TableCell>
|
|
@@ -19,7 +19,12 @@ import {
|
|
|
19
19
|
import { RoleSwitcher } from "../components";
|
|
20
20
|
import { RoleSwitcherPromptGenerator } from "./role-switcher-prompt-generator";
|
|
21
21
|
|
|
22
|
-
type PropRow = [
|
|
22
|
+
type PropRow = [
|
|
23
|
+
name: string,
|
|
24
|
+
type: string,
|
|
25
|
+
defaultValue: string,
|
|
26
|
+
description: string
|
|
27
|
+
];
|
|
23
28
|
|
|
24
29
|
const roleSwitcherProps: PropRow[] = [
|
|
25
30
|
[
|
|
@@ -49,11 +54,20 @@ const roleSwitcherProps: PropRow[] = [
|
|
|
49
54
|
];
|
|
50
55
|
|
|
51
56
|
const roleBehaviors = [
|
|
52
|
-
[
|
|
57
|
+
[
|
|
58
|
+
"Multiple assigned roles",
|
|
59
|
+
"Renders a Select using roles from the signed-in identity.",
|
|
60
|
+
],
|
|
53
61
|
["allow-use-union", "Prepends Full permissions using the __union__ role."],
|
|
54
|
-
[
|
|
62
|
+
[
|
|
63
|
+
"only-use-union",
|
|
64
|
+
"Shows Full permissions as the current non-interactive role.",
|
|
65
|
+
],
|
|
55
66
|
["allowAnonymous", "Appends Anonymous to the available role options."],
|
|
56
|
-
[
|
|
67
|
+
[
|
|
68
|
+
"One available role",
|
|
69
|
+
"Returns null, or shows the current-role badge when showWhenUnavailable is true.",
|
|
70
|
+
],
|
|
57
71
|
];
|
|
58
72
|
|
|
59
73
|
export function AclComponentsPage() {
|
|
@@ -64,8 +78,8 @@ export function AclComponentsPage() {
|
|
|
64
78
|
Role switcher
|
|
65
79
|
</h1>
|
|
66
80
|
<p className="max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
67
|
-
A reusable role selector backed by the Starter's NocoBase ACL
|
|
68
|
-
and
|
|
81
|
+
A reusable role selector backed by the Starter's NocoBase ACL
|
|
82
|
+
store and signed-in identity.
|
|
69
83
|
</p>
|
|
70
84
|
</header>
|
|
71
85
|
|
|
@@ -146,14 +160,15 @@ export function AclComponentsPage() {
|
|
|
146
160
|
{roleBehaviors.map(([condition, behavior]) => (
|
|
147
161
|
<TableRow key={condition}>
|
|
148
162
|
<TableCell className="font-medium">{condition}</TableCell>
|
|
149
|
-
<TableCell className="text-muted-foreground">
|
|
163
|
+
<TableCell className="text-muted-foreground">
|
|
164
|
+
{behavior}
|
|
165
|
+
</TableCell>
|
|
150
166
|
</TableRow>
|
|
151
167
|
))}
|
|
152
168
|
</TableBody>
|
|
153
169
|
</Table>
|
|
154
170
|
</Card>
|
|
155
171
|
</section>
|
|
156
|
-
|
|
157
172
|
</div>
|
|
158
173
|
);
|
|
159
174
|
}
|
|
@@ -173,9 +188,15 @@ function PropsTable({ rows }: { rows: PropRow[] }) {
|
|
|
173
188
|
<TableBody>
|
|
174
189
|
{rows.map(([name, type, defaultValue, description]) => (
|
|
175
190
|
<TableRow key={name}>
|
|
176
|
-
<TableCell className="font-mono text-xs font-medium">
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
<TableCell className="font-mono text-xs font-medium">
|
|
192
|
+
{name}
|
|
193
|
+
</TableCell>
|
|
194
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
195
|
+
{type}
|
|
196
|
+
</TableCell>
|
|
197
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
198
|
+
{defaultValue}
|
|
199
|
+
</TableCell>
|
|
179
200
|
<TableCell className="min-w-72 whitespace-normal text-muted-foreground">
|
|
180
201
|
{description}
|
|
181
202
|
</TableCell>
|
|
@@ -70,7 +70,7 @@ export function AclPatternsPage() {
|
|
|
70
70
|
prompt={{
|
|
71
71
|
title: "Resource navigation",
|
|
72
72
|
description:
|
|
73
|
-
"Generate
|
|
73
|
+
"Generate application resources, sidebar behavior, and guarded routes backed by NocoBase collections.",
|
|
74
74
|
defaultScene: "User and role administration",
|
|
75
75
|
defaultTarget:
|
|
76
76
|
"Allow the Users resource but remove Roles from navigation and block its direct URL.",
|
|
@@ -98,7 +98,8 @@ export function AclPatternsPage() {
|
|
|
98
98
|
description:
|
|
99
99
|
"Generate a complete page whose availability depends on NocoBase permissions.",
|
|
100
100
|
defaultScene: "User directory and account administration page",
|
|
101
|
-
defaultTarget:
|
|
101
|
+
defaultTarget:
|
|
102
|
+
"Require users:list before rendering the complete page.",
|
|
102
103
|
requirements: `- Wrap the complete business page with AclPage.
|
|
103
104
|
- Use anyOf when one accessible collection is enough, or allOf when every permission is required.
|
|
104
105
|
- Provide a useful page-level fallback instead of rendering an empty screen.
|
|
@@ -151,7 +152,7 @@ export function AclPatternsPage() {
|
|
|
151
152
|
defaultTarget:
|
|
152
153
|
"Allow all actions for one user record and show a view-only row when update and delete are denied.",
|
|
153
154
|
requirements: `- Use the Starter dataProvider so X-With-ACL-Meta captures NocoBase allowedActions from list, get, and getMany responses.
|
|
154
|
-
- Use
|
|
155
|
+
- Use the built-in Create, Show, Edit, and Delete buttons; do not manually compare user IDs or role names.
|
|
155
156
|
- Collection-level denial hides the action everywhere.
|
|
156
157
|
- Record-level allowedActions must hide or disable only the affected row action.
|
|
157
158
|
- Keep 403 responses authenticated and let NocoBase perform the final action validation.`,
|
|
@@ -177,7 +178,7 @@ export function AclPatternsPage() {
|
|
|
177
178
|
defaultTarget:
|
|
178
179
|
"Allow nickname editing, make email read-only, and hide phone when those fields are denied.",
|
|
179
180
|
requirements: `- Wrap each protected field or field group with AclField.
|
|
180
|
-
- Pass the collection resource,
|
|
181
|
+
- Pass the collection resource, application action, and exact field name.
|
|
181
182
|
- Choose whether a denied field is hidden, replaced by read-only output, or shown with a custom fallback.
|
|
182
183
|
- Do not infer field access from the overall edit permission.
|
|
183
184
|
- Submit only values the current UI is allowed to edit; NocoBase still performs final field validation.`,
|
|
@@ -231,9 +232,13 @@ function NavigationPreview({ showUsers }: { showUsers: boolean }) {
|
|
|
231
232
|
return (
|
|
232
233
|
<div className="grid min-h-48 grid-cols-[150px_minmax(0,1fr)] overflow-hidden rounded-xl border bg-background">
|
|
233
234
|
<aside className="border-r bg-muted/35 p-3">
|
|
234
|
-
<div className="mb-3 text-xs font-semibold text-muted-foreground">
|
|
235
|
+
<div className="mb-3 text-xs font-semibold text-muted-foreground">
|
|
236
|
+
Workspace
|
|
237
|
+
</div>
|
|
235
238
|
<NavItem icon={<LayoutPanelTop />} label="Dashboard" />
|
|
236
|
-
{showUsers ?
|
|
239
|
+
{showUsers ? (
|
|
240
|
+
<NavItem icon={<UsersRound />} label="Users" active />
|
|
241
|
+
) : null}
|
|
237
242
|
<NavItem icon={<UserRoundCog />} label="Roles" />
|
|
238
243
|
</aside>
|
|
239
244
|
<div className="flex items-center justify-center p-5 text-center">
|
|
@@ -241,7 +246,9 @@ function NavigationPreview({ showUsers }: { showUsers: boolean }) {
|
|
|
241
246
|
<div>
|
|
242
247
|
<UsersRound className="mx-auto size-7 text-primary" />
|
|
243
248
|
<div className="mt-2 font-medium">Users</div>
|
|
244
|
-
<div className="mt-1 text-xs text-muted-foreground">
|
|
249
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
250
|
+
Route is available
|
|
251
|
+
</div>
|
|
245
252
|
</div>
|
|
246
253
|
) : (
|
|
247
254
|
<div>
|
|
@@ -372,15 +379,27 @@ function ActionsPreview({ allowed }: { allowed: boolean }) {
|
|
|
372
379
|
<td className="py-3 text-muted-foreground">{user.role}</td>
|
|
373
380
|
<td className="py-3">
|
|
374
381
|
<div className="flex justify-end gap-1">
|
|
375
|
-
<Button
|
|
382
|
+
<Button
|
|
383
|
+
variant="ghost"
|
|
384
|
+
size="icon-sm"
|
|
385
|
+
aria-label="View user"
|
|
386
|
+
>
|
|
376
387
|
<Eye />
|
|
377
388
|
</Button>
|
|
378
389
|
{allowed ? (
|
|
379
390
|
<>
|
|
380
|
-
<Button
|
|
391
|
+
<Button
|
|
392
|
+
variant="ghost"
|
|
393
|
+
size="icon-sm"
|
|
394
|
+
aria-label="Edit user"
|
|
395
|
+
>
|
|
381
396
|
<Pencil />
|
|
382
397
|
</Button>
|
|
383
|
-
<Button
|
|
398
|
+
<Button
|
|
399
|
+
variant="ghost"
|
|
400
|
+
size="icon-sm"
|
|
401
|
+
aria-label="Delete user"
|
|
402
|
+
>
|
|
384
403
|
<Trash2 />
|
|
385
404
|
</Button>
|
|
386
405
|
</>
|
|
@@ -412,7 +431,9 @@ function ProfileFormPreview({ restricted }: { restricted: boolean }) {
|
|
|
412
431
|
{restricted ? (
|
|
413
432
|
<div className="rounded-md border bg-muted/45 px-3 py-2 text-sm text-muted-foreground">
|
|
414
433
|
{user.email}
|
|
415
|
-
<Badge className="ml-2" variant="outline">
|
|
434
|
+
<Badge className="ml-2" variant="outline">
|
|
435
|
+
Read only
|
|
436
|
+
</Badge>
|
|
416
437
|
</div>
|
|
417
438
|
) : (
|
|
418
439
|
<Input defaultValue={user.email} />
|
|
@@ -429,7 +450,13 @@ function ProfileFormPreview({ restricted }: { restricted: boolean }) {
|
|
|
429
450
|
);
|
|
430
451
|
}
|
|
431
452
|
|
|
432
|
-
function DemoField({
|
|
453
|
+
function DemoField({
|
|
454
|
+
label,
|
|
455
|
+
children,
|
|
456
|
+
}: {
|
|
457
|
+
label: string;
|
|
458
|
+
children: React.ReactNode;
|
|
459
|
+
}) {
|
|
433
460
|
return (
|
|
434
461
|
<label className="block space-y-2">
|
|
435
462
|
<span className="text-xs font-medium">{label}</span>
|
|
@@ -453,7 +480,9 @@ function CompactUsersTable({ limit = 3 }: { limit?: number }) {
|
|
|
453
480
|
<tr key={user.id} className="border-b last:border-0">
|
|
454
481
|
<td className="py-3">
|
|
455
482
|
<div className="font-medium">{user.name}</div>
|
|
456
|
-
<div className="text-xs text-muted-foreground">
|
|
483
|
+
<div className="text-xs text-muted-foreground">
|
|
484
|
+
{user.username}
|
|
485
|
+
</div>
|
|
457
486
|
</td>
|
|
458
487
|
<td className="py-3 text-muted-foreground">{user.email}</td>
|
|
459
488
|
</tr>
|
|
@@ -468,7 +497,9 @@ function RoleRow({ title, name }: { title: string; name: string }) {
|
|
|
468
497
|
return (
|
|
469
498
|
<div className="rounded-lg border px-3 py-2">
|
|
470
499
|
<div className="text-sm font-medium">{title}</div>
|
|
471
|
-
<div className="mt-0.5 font-mono text-xs text-muted-foreground">
|
|
500
|
+
<div className="mt-0.5 font-mono text-xs text-muted-foreground">
|
|
501
|
+
{name}
|
|
502
|
+
</div>
|
|
472
503
|
</div>
|
|
473
504
|
);
|
|
474
505
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { Suspense, type ReactNode } from "react";
|
|
2
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
2
3
|
|
|
3
4
|
export function LazyAclRoute({ children }: { children: ReactNode }) {
|
|
4
5
|
return (
|
|
5
|
-
<Suspense
|
|
6
|
-
fallback={
|
|
7
|
-
<div className="flex min-h-80 items-center justify-center text-sm text-muted-foreground">
|
|
8
|
-
Loading access control demo…
|
|
9
|
-
</div>
|
|
10
|
-
}
|
|
11
|
-
>
|
|
6
|
+
<Suspense fallback={<LoadingState className="min-h-80" />}>
|
|
12
7
|
{children}
|
|
13
8
|
</Suspense>
|
|
14
9
|
);
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { Check, Copy, Sparkles } from "lucide-react";
|
|
1
|
+
import { Check, ChevronDown, Copy, Sparkles } from "lucide-react";
|
|
2
2
|
import { useMemo, useState } from "react";
|
|
3
3
|
|
|
4
4
|
import { Button } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/components/ui/card";
|
|
5
12
|
import { Input } from "@/components/ui/input";
|
|
6
13
|
import { Label } from "@/components/ui/label";
|
|
7
14
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -25,7 +32,7 @@ export function AclScenarioPromptGenerator({
|
|
|
25
32
|
const [copyError, setCopyError] = useState(false);
|
|
26
33
|
|
|
27
34
|
const prompt = useMemo(
|
|
28
|
-
() => `Build a complete NocoBase ACL scene in the
|
|
35
|
+
() => `Build a complete NocoBase ACL scene in the NocoBase Admin Starter.
|
|
29
36
|
|
|
30
37
|
Business scene
|
|
31
38
|
- Page or feature: ${scene}
|
|
@@ -41,7 +48,7 @@ Implementation contract
|
|
|
41
48
|
- NocoBase roles:check is the source of truth. Do not create a second permission store or hard-code role names.
|
|
42
49
|
- Keep backend ACL enforcement in place; frontend checks only control presentation and navigation.
|
|
43
50
|
- Import reusable components from the installed local entry point at @/extensions/nocobase-acl when page composition is needed.
|
|
44
|
-
- Resource actions use
|
|
51
|
+
- Resource actions use the application names list, show, create, edit, and delete. Let the Starter map them to NocoBase list/get/create/update/destroy.
|
|
45
52
|
- Preserve dataSourceKey when the collection belongs to a non-main data source.
|
|
46
53
|
- Show an understandable forbidden or hidden state where appropriate.
|
|
47
54
|
- Deliver complete React component code, resource metadata, route integration, and the visible sample UI.`,
|
|
@@ -56,33 +63,44 @@ Implementation contract
|
|
|
56
63
|
<span className="ml-auto text-xs font-normal text-muted-foreground">
|
|
57
64
|
{config.title}
|
|
58
65
|
</span>
|
|
66
|
+
<ChevronDown className="size-4 text-muted-foreground transition-transform group-open:rotate-180" />
|
|
59
67
|
</summary>
|
|
60
|
-
<div className="grid gap-
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
<div className="grid items-start gap-5 border-t p-4 xl:grid-cols-[360px_minmax(0,1fr)]">
|
|
69
|
+
<Card className="gap-0 py-0">
|
|
70
|
+
<CardHeader className="border-b py-4">
|
|
71
|
+
<CardTitle className="text-base">Scenario settings</CardTitle>
|
|
72
|
+
<CardDescription>{config.description}</CardDescription>
|
|
73
|
+
</CardHeader>
|
|
74
|
+
<CardContent className="space-y-5 py-5">
|
|
75
|
+
<div className="space-y-2">
|
|
76
|
+
<Label htmlFor={`${config.title}-scene`}>Business scene</Label>
|
|
77
|
+
<Input
|
|
78
|
+
id={`${config.title}-scene`}
|
|
79
|
+
value={scene}
|
|
80
|
+
onChange={(event) => setScene(event.target.value)}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
<div className="space-y-2">
|
|
84
|
+
<Label htmlFor={`${config.title}-target`}>
|
|
85
|
+
Permission target
|
|
86
|
+
</Label>
|
|
87
|
+
<Textarea
|
|
88
|
+
id={`${config.title}-target`}
|
|
89
|
+
value={target}
|
|
90
|
+
onChange={(event) => setTarget(event.target.value)}
|
|
91
|
+
className="min-h-24"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</CardContent>
|
|
95
|
+
</Card>
|
|
96
|
+
<Card className="min-w-0 gap-0 overflow-hidden py-0">
|
|
97
|
+
<div className="flex items-center justify-between gap-3 border-b px-4 py-3">
|
|
98
|
+
<div>
|
|
99
|
+
<div className="text-sm font-medium">Generated prompt</div>
|
|
100
|
+
<div className="text-xs text-muted-foreground">
|
|
101
|
+
Updates as you change the business scene and permission target.
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
86
104
|
<Button
|
|
87
105
|
size="sm"
|
|
88
106
|
onClick={async () => {
|
|
@@ -101,14 +119,15 @@ Implementation contract
|
|
|
101
119
|
</Button>
|
|
102
120
|
</div>
|
|
103
121
|
{copyError ? (
|
|
104
|
-
<p className="border-b px-
|
|
105
|
-
Clipboard access failed. Select the prompt text and copy it
|
|
122
|
+
<p className="border-b px-4 py-2 text-xs text-destructive">
|
|
123
|
+
Clipboard access failed. Select the prompt text and copy it
|
|
124
|
+
manually.
|
|
106
125
|
</p>
|
|
107
126
|
) : null}
|
|
108
|
-
<pre className="max-h-
|
|
127
|
+
<pre className="max-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
|
|
109
128
|
{prompt}
|
|
110
129
|
</pre>
|
|
111
|
-
</
|
|
130
|
+
</Card>
|
|
112
131
|
</div>
|
|
113
132
|
</details>
|
|
114
133
|
);
|