@nocobase/portal-template-default 1.0.3 → 1.0.5
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 +10 -5
- package/package.json +17 -17
- package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
- package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
- package/registry/nocobase-i18n/README.md +23 -32
- package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
- package/registry/nocobase-i18n/demo/index.tsx +2 -2
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
- package/registry/nocobase-i18n/extension.tsx +3 -4
- package/registry/nocobase-i18n/index.ts +4 -3
- package/registry/nocobase-i18n/locales/en-US.ts +2 -52
- package/registry/nocobase-i18n/locales/index.ts +8 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
- package/registry/nocobase-i18n/provider.tsx +18 -6
- package/registry/nocobase-i18n/server-resources.ts +13 -31
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
- package/registry/nocobase-route-surfaces/README.md +21 -0
- package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
- package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
- package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
- package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
- package/registry/nocobase-route-surfaces/extension.tsx +119 -0
- package/registry/nocobase-route-surfaces/index.ts +7 -0
- package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
- package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
- package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
- package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
- package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
- package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
- package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
- package/registry.config.json +30 -3
- package/scripts/registry.mjs +0 -2
- package/src/App.tsx +65 -35
- package/src/app/extension.ts +1 -2
- package/src/app/extensions.tsx +0 -14
- package/src/components/app-shell/breadcrumb.tsx +67 -2
- package/src/components/app-shell/sidebar.tsx +2 -2
- package/src/components/resources/views/list-view.tsx +3 -2
- package/src/components/ui/sheet.tsx +22 -21
- package/src/lib/nocobase/client.ts +6 -1
- package/src/locales/en-US.ts +81 -0
- package/src/locales/zh-CN.ts +77 -0
- package/src/pages/users/create.tsx +76 -53
- package/src/pages/users/edit.tsx +87 -56
- package/src/pages/users/index.ts +3 -0
- package/src/pages/users/layout.tsx +15 -0
- package/src/pages/users/list.tsx +19 -21
- package/src/pages/users/role-badges.tsx +43 -0
- package/src/pages/users/role-detail.tsx +272 -0
- package/src/pages/users/role-utils.ts +6 -0
- package/src/pages/users/routes.ts +24 -0
- package/src/pages/users/show.tsx +195 -104
- package/src/pages/users/types.ts +13 -0
- package/src/providers/i18n/index.ts +3 -0
- package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
- package/src/providers/system-settings/context.ts +26 -0
- package/src/providers/system-settings/index.ts +2 -0
- package/src/providers/system-settings/provider.tsx +94 -0
- package/src/extensions/nocobase-acl/README.md +0 -19
- package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
- package/src/extensions/nocobase-acl/components/index.ts +0 -3
- package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
- package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
- package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
- package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
- package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
- package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
- package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
- package/src/extensions/nocobase-acl/extension.tsx +0 -71
- package/src/extensions/nocobase-acl/index.ts +0 -1
- package/src/extensions/nocobase-ai/README.md +0 -70
- package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
- package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
- package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
- package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
- package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
- package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
- package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
- package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
- package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
- package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
- package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
- package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
- package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
- package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
- package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
- package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
- package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
- package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
- package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
- package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
- package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
- package/src/extensions/nocobase-ai/components/index.ts +0 -59
- package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
- package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
- package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
- package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
- package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
- package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
- package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
- package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
- package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
- package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
- package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
- package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
- package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
- package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
- package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
- package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
- package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
- package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
- package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
- package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
- package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
- package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
- package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
- package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
- package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
- package/src/extensions/nocobase-ai/extension.tsx +0 -158
- package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
- package/src/extensions/nocobase-ai/index.ts +0 -3
- package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
- package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
- package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
- package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
- package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
- package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
- package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
- package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
- package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
- package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
- package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
- package/src/extensions/nocobase-ai/providers/index.ts +0 -61
- package/src/extensions/nocobase-ai/providers/model.ts +0 -32
- package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
- package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
- package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
- package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
- package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
- package/src/extensions/nocobase-ai/providers/types.ts +0 -183
- package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
- package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
- package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
- package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
- package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
- package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
- package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
- package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
- package/src/extensions/nocobase-ai/services/index.ts +0 -7
- package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
- package/src/extensions/nocobase-ai/services/types.ts +0 -82
- /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
- /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import type { BaseKey } from "@refinedev/core";
|
|
2
|
-
import type { PropsWithChildren, ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
import { AccessDenied } from "@/components/access-control/access-denied";
|
|
5
|
-
import {
|
|
6
|
-
canAccessWithSnapshot,
|
|
7
|
-
useAclSnapshot,
|
|
8
|
-
} from "@/lib/nocobase/acl";
|
|
9
|
-
|
|
10
|
-
export type AclPermission = {
|
|
11
|
-
resource: string;
|
|
12
|
-
action: string;
|
|
13
|
-
id?: BaseKey;
|
|
14
|
-
field?: string;
|
|
15
|
-
dataSourceKey?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type AclPageProps = PropsWithChildren<{
|
|
19
|
-
anyOf?: AclPermission[];
|
|
20
|
-
allOf?: AclPermission[];
|
|
21
|
-
fallback?: ReactNode;
|
|
22
|
-
}>;
|
|
23
|
-
|
|
24
|
-
export type AclRegionProps = PropsWithChildren<
|
|
25
|
-
Omit<AclPermission, "field"> & {
|
|
26
|
-
fallback?: "hidden" | "forbidden" | ReactNode;
|
|
27
|
-
}
|
|
28
|
-
>;
|
|
29
|
-
|
|
30
|
-
export type AclFieldProps = PropsWithChildren<{
|
|
31
|
-
resource: string;
|
|
32
|
-
action: string;
|
|
33
|
-
field: string;
|
|
34
|
-
dataSourceKey?: string;
|
|
35
|
-
fallback?: ReactNode;
|
|
36
|
-
}>;
|
|
37
|
-
|
|
38
|
-
const canAccess = (
|
|
39
|
-
snapshot: ReturnType<typeof useAclSnapshot>,
|
|
40
|
-
permission: AclPermission
|
|
41
|
-
) =>
|
|
42
|
-
canAccessWithSnapshot(snapshot, {
|
|
43
|
-
resource: permission.resource,
|
|
44
|
-
action: permission.action,
|
|
45
|
-
params: {
|
|
46
|
-
id: permission.id,
|
|
47
|
-
field: permission.field,
|
|
48
|
-
dataSourceKey: permission.dataSourceKey,
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export function AclPage({
|
|
53
|
-
children,
|
|
54
|
-
anyOf,
|
|
55
|
-
allOf,
|
|
56
|
-
fallback = <AccessDenied />,
|
|
57
|
-
}: AclPageProps) {
|
|
58
|
-
const snapshot = useAclSnapshot();
|
|
59
|
-
const anyAllowed = !anyOf?.length || anyOf.some((item) => canAccess(snapshot, item));
|
|
60
|
-
const allAllowed = !allOf?.length || allOf.every((item) => canAccess(snapshot, item));
|
|
61
|
-
|
|
62
|
-
return anyAllowed && allAllowed ? children : fallback;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function AclRegion({
|
|
66
|
-
children,
|
|
67
|
-
resource,
|
|
68
|
-
action,
|
|
69
|
-
id,
|
|
70
|
-
dataSourceKey,
|
|
71
|
-
fallback = "hidden",
|
|
72
|
-
}: AclRegionProps) {
|
|
73
|
-
const snapshot = useAclSnapshot();
|
|
74
|
-
const allowed = canAccess(snapshot, {
|
|
75
|
-
resource,
|
|
76
|
-
action,
|
|
77
|
-
id,
|
|
78
|
-
dataSourceKey,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (allowed) return children;
|
|
82
|
-
if (fallback === "hidden") return null;
|
|
83
|
-
if (fallback === "forbidden") {
|
|
84
|
-
return <AccessDenied className="min-h-40" />;
|
|
85
|
-
}
|
|
86
|
-
return fallback;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function AclField({
|
|
90
|
-
children,
|
|
91
|
-
resource,
|
|
92
|
-
action,
|
|
93
|
-
field,
|
|
94
|
-
dataSourceKey,
|
|
95
|
-
fallback = null,
|
|
96
|
-
}: AclFieldProps) {
|
|
97
|
-
const snapshot = useAclSnapshot();
|
|
98
|
-
return canAccess(snapshot, {
|
|
99
|
-
resource,
|
|
100
|
-
action,
|
|
101
|
-
field,
|
|
102
|
-
dataSourceKey,
|
|
103
|
-
})
|
|
104
|
-
? children
|
|
105
|
-
: fallback;
|
|
106
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Role, RoleMode } from "@/lib/nocobase/acl";
|
|
2
|
-
import { resolveTranslatableText } from "@/lib/i18n";
|
|
3
|
-
|
|
4
|
-
export const UNION_ROLE = "__union__";
|
|
5
|
-
export const ANONYMOUS_ROLE = "anonymous";
|
|
6
|
-
|
|
7
|
-
export function resolveRoleTitle(role?: Role) {
|
|
8
|
-
return resolveTranslatableText(role?.title || role?.name || "Role", {
|
|
9
|
-
ns: "starter",
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getRoleOptions({
|
|
14
|
-
roles,
|
|
15
|
-
roleMode,
|
|
16
|
-
allowAnonymous = false,
|
|
17
|
-
}: {
|
|
18
|
-
roles: Role[];
|
|
19
|
-
roleMode?: RoleMode;
|
|
20
|
-
allowAnonymous?: boolean;
|
|
21
|
-
}) {
|
|
22
|
-
if (roleMode === "only-use-union") {
|
|
23
|
-
return [{ name: UNION_ROLE, title: "Full permissions" }];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const options = roles.filter(
|
|
27
|
-
(role) => role.name !== UNION_ROLE && role.name !== ANONYMOUS_ROLE
|
|
28
|
-
);
|
|
29
|
-
if (allowAnonymous) {
|
|
30
|
-
options.push({ name: ANONYMOUS_ROLE, title: "Anonymous" });
|
|
31
|
-
}
|
|
32
|
-
if (roleMode === "allow-use-union") {
|
|
33
|
-
options.unshift({ name: UNION_ROLE, title: "Full permissions" });
|
|
34
|
-
}
|
|
35
|
-
return options;
|
|
36
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { useGetIdentity } from "@refinedev/core";
|
|
2
|
-
import { Loader2, ShieldCheck } from "lucide-react";
|
|
3
|
-
import { useMemo, useState, type ReactNode } from "react";
|
|
4
|
-
|
|
5
|
-
import { Badge } from "@/components/ui/badge";
|
|
6
|
-
import {
|
|
7
|
-
Select,
|
|
8
|
-
SelectContent,
|
|
9
|
-
SelectItem,
|
|
10
|
-
SelectSeparator,
|
|
11
|
-
SelectTrigger,
|
|
12
|
-
SelectValue,
|
|
13
|
-
} from "@/components/ui/select";
|
|
14
|
-
import {
|
|
15
|
-
switchRole,
|
|
16
|
-
useAclSnapshot,
|
|
17
|
-
type AclIdentity,
|
|
18
|
-
type Role,
|
|
19
|
-
} from "@/lib/nocobase/acl";
|
|
20
|
-
import { cn } from "@/lib/utils";
|
|
21
|
-
import { getRoleOptions, resolveRoleTitle, UNION_ROLE } from "./role-options";
|
|
22
|
-
|
|
23
|
-
export type RoleSwitcherProps = {
|
|
24
|
-
className?: string;
|
|
25
|
-
triggerClassName?: string;
|
|
26
|
-
label?: ReactNode | false;
|
|
27
|
-
showWhenUnavailable?: boolean;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export function RoleSwitcher({
|
|
31
|
-
className,
|
|
32
|
-
triggerClassName,
|
|
33
|
-
label = "Switch role",
|
|
34
|
-
showWhenUnavailable = false,
|
|
35
|
-
}: RoleSwitcherProps) {
|
|
36
|
-
const { data: identity, isLoading } = useGetIdentity<AclIdentity>();
|
|
37
|
-
const acl = useAclSnapshot();
|
|
38
|
-
const [switching, setSwitching] = useState(false);
|
|
39
|
-
const [error, setError] = useState<string>();
|
|
40
|
-
|
|
41
|
-
const roles = useMemo(
|
|
42
|
-
() =>
|
|
43
|
-
getRoleOptions({
|
|
44
|
-
roles: identity?.roles ?? [],
|
|
45
|
-
roleMode: acl.data.roleMode,
|
|
46
|
-
allowAnonymous: acl.data.allowAnonymous,
|
|
47
|
-
}),
|
|
48
|
-
[acl.data.allowAnonymous, acl.data.roleMode, identity?.roles]
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const currentRole = acl.data.role ?? roles[0]?.name;
|
|
52
|
-
const canSwitch = roles.length > 1 && acl.data.roleMode !== "only-use-union";
|
|
53
|
-
|
|
54
|
-
if (isLoading) {
|
|
55
|
-
return <Loader2 className="size-4 animate-spin text-muted-foreground" />;
|
|
56
|
-
}
|
|
57
|
-
if (!canSwitch && !showWhenUnavailable) return null;
|
|
58
|
-
|
|
59
|
-
if (!canSwitch) {
|
|
60
|
-
return (
|
|
61
|
-
<div className={cn("flex items-center gap-2 text-sm", className)}>
|
|
62
|
-
<ShieldCheck className="size-4 text-muted-foreground" />
|
|
63
|
-
<span className="text-muted-foreground">Current role</span>
|
|
64
|
-
<Badge variant="secondary">{getRoleTitle(roles, currentRole)}</Badge>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className={cn("space-y-2", className)}>
|
|
71
|
-
{label === false ? null : (
|
|
72
|
-
<p className="text-xs font-medium text-muted-foreground">{label}</p>
|
|
73
|
-
)}
|
|
74
|
-
<Select
|
|
75
|
-
value={currentRole}
|
|
76
|
-
disabled={switching}
|
|
77
|
-
onValueChange={(value) => {
|
|
78
|
-
if (!value || value === currentRole) return;
|
|
79
|
-
setSwitching(true);
|
|
80
|
-
setError(undefined);
|
|
81
|
-
void switchRole(value, { reloadAcl: false })
|
|
82
|
-
.then(() => window.location.reload())
|
|
83
|
-
.catch((reason) => {
|
|
84
|
-
setError(
|
|
85
|
-
reason instanceof Error
|
|
86
|
-
? reason.message
|
|
87
|
-
: "Unable to switch role"
|
|
88
|
-
);
|
|
89
|
-
setSwitching(false);
|
|
90
|
-
});
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
<SelectTrigger
|
|
94
|
-
className={cn("w-full min-w-52", triggerClassName)}
|
|
95
|
-
aria-label="Switch role"
|
|
96
|
-
>
|
|
97
|
-
{switching ? <Loader2 className="animate-spin" /> : <ShieldCheck />}
|
|
98
|
-
<SelectValue>{getRoleTitle(roles, currentRole)}</SelectValue>
|
|
99
|
-
</SelectTrigger>
|
|
100
|
-
<SelectContent>
|
|
101
|
-
{roles.map((role, index) => (
|
|
102
|
-
<RoleOption
|
|
103
|
-
key={role.name}
|
|
104
|
-
role={role}
|
|
105
|
-
showSeparator={index === 1 && roles[0]?.name === UNION_ROLE}
|
|
106
|
-
/>
|
|
107
|
-
))}
|
|
108
|
-
</SelectContent>
|
|
109
|
-
</Select>
|
|
110
|
-
{error ? <p className="text-xs text-destructive">{error}</p> : null}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function RoleOption({
|
|
116
|
-
role,
|
|
117
|
-
showSeparator,
|
|
118
|
-
}: {
|
|
119
|
-
role: Role;
|
|
120
|
-
showSeparator: boolean;
|
|
121
|
-
}) {
|
|
122
|
-
return (
|
|
123
|
-
<>
|
|
124
|
-
{showSeparator ? <SelectSeparator /> : null}
|
|
125
|
-
<SelectItem value={role.name}>{resolveRoleTitle(role)}</SelectItem>
|
|
126
|
-
</>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function getRoleTitle(roles: Role[], roleName?: string) {
|
|
131
|
-
return resolveRoleTitle(
|
|
132
|
-
roles.find((role) => role.name === roleName) ??
|
|
133
|
-
(roleName ? { name: roleName } : undefined)
|
|
134
|
-
);
|
|
135
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { Card } from "@/components/ui/card";
|
|
2
|
-
import {
|
|
3
|
-
Table,
|
|
4
|
-
TableBody,
|
|
5
|
-
TableCell,
|
|
6
|
-
TableHead,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableRow,
|
|
9
|
-
} from "@/components/ui/table";
|
|
10
|
-
|
|
11
|
-
type PropRow = [
|
|
12
|
-
name: string,
|
|
13
|
-
type: string,
|
|
14
|
-
defaultValue: string,
|
|
15
|
-
description: string
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
const pageProps: PropRow[] = [
|
|
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
|
-
],
|
|
37
|
-
["children", "ReactNode", "required", "The protected page content."],
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
const regionProps: PropRow[] = [
|
|
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
|
-
],
|
|
71
|
-
["children", "ReactNode", "required", "The protected page region."],
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
const fieldProps: PropRow[] = [
|
|
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
|
-
],
|
|
87
|
-
["field", "string", "required", "Exact NocoBase field name."],
|
|
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
|
-
],
|
|
100
|
-
["children", "ReactNode", "required", "The protected field or field group."],
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
export function AclBoundaryApi() {
|
|
104
|
-
return (
|
|
105
|
-
<section className="space-y-5 border-t pt-8">
|
|
106
|
-
<div>
|
|
107
|
-
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
108
|
-
Permission boundaries
|
|
109
|
-
</p>
|
|
110
|
-
<h2 className="mt-2 text-xl font-semibold tracking-tight">
|
|
111
|
-
Components behind the patterns
|
|
112
|
-
</h2>
|
|
113
|
-
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
114
|
-
Use the smallest boundary that matches the intended user experience:
|
|
115
|
-
complete page, independent region, or individual field.
|
|
116
|
-
</p>
|
|
117
|
-
</div>
|
|
118
|
-
<div className="space-y-7">
|
|
119
|
-
<ComponentApi
|
|
120
|
-
title="AclPage"
|
|
121
|
-
description="Protects a complete route or business page. This corresponds to the Page permission pattern above."
|
|
122
|
-
rows={pageProps}
|
|
123
|
-
/>
|
|
124
|
-
<ComponentApi
|
|
125
|
-
title="AclRegion"
|
|
126
|
-
description="Protects one independent area inside a larger page. This corresponds to the Region permission pattern."
|
|
127
|
-
rows={regionProps}
|
|
128
|
-
/>
|
|
129
|
-
<ComponentApi
|
|
130
|
-
title="AclField"
|
|
131
|
-
description="Protects one field or field group. This corresponds to the Field permission pattern."
|
|
132
|
-
rows={fieldProps}
|
|
133
|
-
/>
|
|
134
|
-
</div>
|
|
135
|
-
</section>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function ComponentApi({
|
|
140
|
-
title,
|
|
141
|
-
description,
|
|
142
|
-
rows,
|
|
143
|
-
}: {
|
|
144
|
-
title: string;
|
|
145
|
-
description: string;
|
|
146
|
-
rows: PropRow[];
|
|
147
|
-
}) {
|
|
148
|
-
return (
|
|
149
|
-
<div className="space-y-3">
|
|
150
|
-
<div>
|
|
151
|
-
<h3 className="font-semibold">{title}</h3>
|
|
152
|
-
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
|
153
|
-
</div>
|
|
154
|
-
<Card className="gap-0 overflow-hidden py-0">
|
|
155
|
-
<Table>
|
|
156
|
-
<TableHeader>
|
|
157
|
-
<TableRow>
|
|
158
|
-
<TableHead>Prop</TableHead>
|
|
159
|
-
<TableHead>Type</TableHead>
|
|
160
|
-
<TableHead>Default</TableHead>
|
|
161
|
-
<TableHead>Description</TableHead>
|
|
162
|
-
</TableRow>
|
|
163
|
-
</TableHeader>
|
|
164
|
-
<TableBody>
|
|
165
|
-
{rows.map(([name, type, defaultValue, propDescription]) => (
|
|
166
|
-
<TableRow key={name}>
|
|
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>
|
|
176
|
-
<TableCell className="min-w-72 whitespace-normal text-muted-foreground">
|
|
177
|
-
{propDescription}
|
|
178
|
-
</TableCell>
|
|
179
|
-
</TableRow>
|
|
180
|
-
))}
|
|
181
|
-
</TableBody>
|
|
182
|
-
</Table>
|
|
183
|
-
</Card>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { ShieldCheck } from "lucide-react";
|
|
2
|
-
|
|
3
|
-
import { Badge } from "@/components/ui/badge";
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardContent,
|
|
7
|
-
CardDescription,
|
|
8
|
-
CardHeader,
|
|
9
|
-
CardTitle,
|
|
10
|
-
} from "@/components/ui/card";
|
|
11
|
-
import {
|
|
12
|
-
Table,
|
|
13
|
-
TableBody,
|
|
14
|
-
TableCell,
|
|
15
|
-
TableHead,
|
|
16
|
-
TableHeader,
|
|
17
|
-
TableRow,
|
|
18
|
-
} from "@/components/ui/table";
|
|
19
|
-
import { RoleSwitcher } from "../components";
|
|
20
|
-
import { RoleSwitcherPromptGenerator } from "./role-switcher-prompt-generator";
|
|
21
|
-
|
|
22
|
-
type PropRow = [
|
|
23
|
-
name: string,
|
|
24
|
-
type: string,
|
|
25
|
-
defaultValue: string,
|
|
26
|
-
description: string
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const roleSwitcherProps: PropRow[] = [
|
|
30
|
-
[
|
|
31
|
-
"className",
|
|
32
|
-
"string",
|
|
33
|
-
"undefined",
|
|
34
|
-
"Styles the component wrapper so it can fit a toolbar, menu, or settings layout.",
|
|
35
|
-
],
|
|
36
|
-
[
|
|
37
|
-
"triggerClassName",
|
|
38
|
-
"string",
|
|
39
|
-
"undefined",
|
|
40
|
-
"Styles only the Select trigger without changing the wrapper.",
|
|
41
|
-
],
|
|
42
|
-
[
|
|
43
|
-
"label",
|
|
44
|
-
"ReactNode | false",
|
|
45
|
-
'"Switch role"',
|
|
46
|
-
"Replaces the field label. Pass false for a compact control without a label.",
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
"showWhenUnavailable",
|
|
50
|
-
"boolean",
|
|
51
|
-
"false",
|
|
52
|
-
"Shows the current role as a non-interactive badge when the user cannot switch roles.",
|
|
53
|
-
],
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
const roleBehaviors = [
|
|
57
|
-
[
|
|
58
|
-
"Multiple assigned roles",
|
|
59
|
-
"Renders a Select using roles from the signed-in identity.",
|
|
60
|
-
],
|
|
61
|
-
["allow-use-union", "Prepends Full permissions using the __union__ role."],
|
|
62
|
-
[
|
|
63
|
-
"only-use-union",
|
|
64
|
-
"Shows Full permissions as the current non-interactive role.",
|
|
65
|
-
],
|
|
66
|
-
["allowAnonymous", "Appends Anonymous to the available role options."],
|
|
67
|
-
[
|
|
68
|
-
"One available role",
|
|
69
|
-
"Returns null, or shows the current-role badge when showWhenUnavailable is true.",
|
|
70
|
-
],
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
export function AclComponentsPage() {
|
|
74
|
-
return (
|
|
75
|
-
<div className="space-y-12 pb-12">
|
|
76
|
-
<header className="space-y-2">
|
|
77
|
-
<h1 className="text-2xl font-semibold tracking-[-0.025em] sm:text-3xl">
|
|
78
|
-
Role switcher
|
|
79
|
-
</h1>
|
|
80
|
-
<p className="max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
81
|
-
A reusable role selector backed by the Starter's NocoBase ACL
|
|
82
|
-
store and signed-in identity.
|
|
83
|
-
</p>
|
|
84
|
-
</header>
|
|
85
|
-
|
|
86
|
-
<section className="space-y-5 border-t pt-8">
|
|
87
|
-
<SectionTitle
|
|
88
|
-
eyebrow="Role selection"
|
|
89
|
-
title="RoleSwitcher"
|
|
90
|
-
description="The component resolves the signed-in user's roles and NocoBase role mode automatically. Business pages only decide its placement and presentation."
|
|
91
|
-
/>
|
|
92
|
-
<div className="grid items-start gap-5 xl:grid-cols-2">
|
|
93
|
-
<Card>
|
|
94
|
-
<CardHeader>
|
|
95
|
-
<CardTitle>Live component</CardTitle>
|
|
96
|
-
<CardDescription>
|
|
97
|
-
Uses the current identity and active NocoBase ACL response.
|
|
98
|
-
</CardDescription>
|
|
99
|
-
</CardHeader>
|
|
100
|
-
<CardContent>
|
|
101
|
-
<RoleSwitcher
|
|
102
|
-
showWhenUnavailable
|
|
103
|
-
className="w-full max-w-sm"
|
|
104
|
-
label="Working as"
|
|
105
|
-
/>
|
|
106
|
-
</CardContent>
|
|
107
|
-
</Card>
|
|
108
|
-
<Card>
|
|
109
|
-
<CardHeader>
|
|
110
|
-
<CardTitle>Unavailable state</CardTitle>
|
|
111
|
-
<CardDescription>
|
|
112
|
-
With one usable role, showWhenUnavailable keeps identity visible
|
|
113
|
-
without presenting a disabled Select.
|
|
114
|
-
</CardDescription>
|
|
115
|
-
</CardHeader>
|
|
116
|
-
<CardContent>
|
|
117
|
-
<div className="flex min-h-9 items-center gap-2 text-sm">
|
|
118
|
-
<ShieldCheck className="size-4 text-muted-foreground" />
|
|
119
|
-
<span className="text-muted-foreground">Current role</span>
|
|
120
|
-
<Badge variant="secondary">Member</Badge>
|
|
121
|
-
</div>
|
|
122
|
-
</CardContent>
|
|
123
|
-
</Card>
|
|
124
|
-
</div>
|
|
125
|
-
</section>
|
|
126
|
-
|
|
127
|
-
<section className="space-y-5 border-t pt-8">
|
|
128
|
-
<SectionTitle
|
|
129
|
-
eyebrow="Prompt generator"
|
|
130
|
-
title="Place the role switcher in an existing application surface"
|
|
131
|
-
description="Describe the target placement and presentation. The generated prompt integrates the exported component without rebuilding its role-loading or switching behavior."
|
|
132
|
-
/>
|
|
133
|
-
<RoleSwitcherPromptGenerator />
|
|
134
|
-
</section>
|
|
135
|
-
|
|
136
|
-
<section className="space-y-5 border-t pt-8">
|
|
137
|
-
<SectionTitle
|
|
138
|
-
eyebrow="Component API"
|
|
139
|
-
title="Role switcher props"
|
|
140
|
-
description="Role data, union mode, Anonymous, persistence, and reload behavior are built in rather than exposed as duplicated application props."
|
|
141
|
-
/>
|
|
142
|
-
<PropsTable rows={roleSwitcherProps} />
|
|
143
|
-
</section>
|
|
144
|
-
|
|
145
|
-
<section className="space-y-5 border-t pt-8">
|
|
146
|
-
<SectionTitle
|
|
147
|
-
eyebrow="Automatic behavior"
|
|
148
|
-
title="Role options follow NocoBase configuration"
|
|
149
|
-
description="These states are derived from the identity and roles:check response, so applications do not maintain a second role list."
|
|
150
|
-
/>
|
|
151
|
-
<Card className="gap-0 overflow-hidden py-0">
|
|
152
|
-
<Table>
|
|
153
|
-
<TableHeader>
|
|
154
|
-
<TableRow>
|
|
155
|
-
<TableHead>Condition</TableHead>
|
|
156
|
-
<TableHead>Rendered behavior</TableHead>
|
|
157
|
-
</TableRow>
|
|
158
|
-
</TableHeader>
|
|
159
|
-
<TableBody>
|
|
160
|
-
{roleBehaviors.map(([condition, behavior]) => (
|
|
161
|
-
<TableRow key={condition}>
|
|
162
|
-
<TableCell className="font-medium">{condition}</TableCell>
|
|
163
|
-
<TableCell className="text-muted-foreground">
|
|
164
|
-
{behavior}
|
|
165
|
-
</TableCell>
|
|
166
|
-
</TableRow>
|
|
167
|
-
))}
|
|
168
|
-
</TableBody>
|
|
169
|
-
</Table>
|
|
170
|
-
</Card>
|
|
171
|
-
</section>
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function PropsTable({ rows }: { rows: PropRow[] }) {
|
|
177
|
-
return (
|
|
178
|
-
<Card className="gap-0 overflow-hidden py-0">
|
|
179
|
-
<Table>
|
|
180
|
-
<TableHeader>
|
|
181
|
-
<TableRow>
|
|
182
|
-
<TableHead>Prop</TableHead>
|
|
183
|
-
<TableHead>Type</TableHead>
|
|
184
|
-
<TableHead>Default</TableHead>
|
|
185
|
-
<TableHead>Description</TableHead>
|
|
186
|
-
</TableRow>
|
|
187
|
-
</TableHeader>
|
|
188
|
-
<TableBody>
|
|
189
|
-
{rows.map(([name, type, defaultValue, description]) => (
|
|
190
|
-
<TableRow key={name}>
|
|
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>
|
|
200
|
-
<TableCell className="min-w-72 whitespace-normal text-muted-foreground">
|
|
201
|
-
{description}
|
|
202
|
-
</TableCell>
|
|
203
|
-
</TableRow>
|
|
204
|
-
))}
|
|
205
|
-
</TableBody>
|
|
206
|
-
</Table>
|
|
207
|
-
</Card>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function SectionTitle({
|
|
212
|
-
eyebrow,
|
|
213
|
-
title,
|
|
214
|
-
description,
|
|
215
|
-
}: {
|
|
216
|
-
eyebrow: string;
|
|
217
|
-
title: string;
|
|
218
|
-
description: string;
|
|
219
|
-
}) {
|
|
220
|
-
return (
|
|
221
|
-
<div>
|
|
222
|
-
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
223
|
-
{eyebrow}
|
|
224
|
-
</p>
|
|
225
|
-
<h2 className="mt-2 text-xl font-semibold tracking-tight">{title}</h2>
|
|
226
|
-
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
227
|
-
{description}
|
|
228
|
-
</p>
|
|
229
|
-
</div>
|
|
230
|
-
);
|
|
231
|
-
}
|