@nocobase/portal-template-default 1.0.4 → 1.0.6
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 +77 -0
- package/package.json +23 -17
- package/registry/nocobase-acl/demo/prompt-generator.tsx +6 -39
- package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +6 -42
- package/registry/nocobase-ai/demo/page-context-prompt-generator.tsx +1 -1
- package/registry/nocobase-ai/demo/prompt-card.tsx +7 -28
- package/registry/nocobase-ai/demo/prompt-generator.tsx +9 -30
- package/registry/nocobase-ai/demo/shortcut.tsx +1 -1
- package/registry/nocobase-ai/demo/tool-cards.tsx +10 -30
- package/registry/nocobase-auth-cas/README.md +6 -0
- package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +41 -0
- package/registry/nocobase-auth-cas/demo.tsx +20 -0
- package/registry/nocobase-auth-cas/extension.tsx +46 -0
- package/registry/nocobase-auth-cas/index.ts +1 -0
- package/registry/nocobase-auth-cas/use-cas-sign-in.ts +29 -0
- package/registry/nocobase-auth-dingtalk/README.md +7 -0
- package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +154 -0
- package/registry/nocobase-auth-dingtalk/demo.tsx +23 -0
- package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +45 -0
- package/registry/nocobase-auth-dingtalk/extension.tsx +55 -0
- package/registry/nocobase-auth-dingtalk/index.ts +4 -0
- package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +64 -0
- package/registry/nocobase-auth-ldap/README.md +6 -0
- package/registry/nocobase-auth-ldap/demo.tsx +21 -0
- package/registry/nocobase-auth-ldap/extension.tsx +46 -0
- package/registry/nocobase-auth-ldap/index.ts +1 -0
- package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +69 -0
- package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +5 -0
- package/registry/nocobase-auth-oidc/README.md +11 -0
- package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +13 -0
- package/registry/nocobase-auth-oidc/demo.tsx +20 -0
- package/registry/nocobase-auth-oidc/extension.tsx +51 -0
- package/registry/nocobase-auth-oidc/index.ts +1 -0
- package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +44 -0
- package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +58 -0
- package/registry/nocobase-auth-saml/README.md +6 -0
- package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +13 -0
- package/registry/nocobase-auth-saml/demo.tsx +20 -0
- package/registry/nocobase-auth-saml/extension.tsx +51 -0
- package/registry/nocobase-auth-saml/index.ts +1 -0
- package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +43 -0
- package/registry/nocobase-auth-saml/use-saml-sign-in.ts +51 -0
- package/registry/nocobase-auth-sms/README.md +7 -0
- package/registry/nocobase-auth-sms/demo.tsx +25 -0
- package/registry/nocobase-auth-sms/extension.tsx +46 -0
- package/registry/nocobase-auth-sms/index.ts +1 -0
- package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +107 -0
- package/registry/nocobase-auth-sms/use-sms-sign-in.ts +88 -0
- package/registry/nocobase-auth-wecom/README.md +6 -0
- package/registry/nocobase-auth-wecom/auto-login-provider.tsx +21 -0
- package/registry/nocobase-auth-wecom/demo.tsx +23 -0
- package/registry/nocobase-auth-wecom/extension.tsx +49 -0
- package/registry/nocobase-auth-wecom/index.ts +1 -0
- package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +62 -0
- package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +70 -0
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +30 -54
- package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +18 -21
- package/registry.config.json +112 -0
- package/scripts/build-html.mjs +106 -0
- package/src/App.tsx +34 -8
- package/src/app/extension.ts +4 -0
- package/src/app/extensions.tsx +24 -1
- package/src/components/auth/authenticator-adapters.ts +20 -0
- package/src/components/auth/auto-redirect-provider.tsx +84 -0
- package/src/components/auth/basic-sign-in-form.tsx +101 -0
- package/src/components/auth/default-sign-in-page.tsx +20 -0
- package/src/components/auth/demo/auth-demo-page.tsx +236 -0
- package/src/components/auth/demo/auth-demo-prompt-generator.tsx +93 -0
- package/src/components/auth/demo/auth-demo-route.tsx +11 -0
- package/src/components/auth/demo/auth-method-demo.tsx +48 -0
- package/src/components/auth/demo/index.ts +4 -0
- package/src/components/auth/dynamic-sign-in.tsx +191 -0
- package/src/components/auth/forgot-password-form.tsx +13 -5
- package/src/components/auth/index.ts +13 -0
- package/src/components/auth/sign-in-form.tsx +1 -111
- package/src/components/auth/sign-up-form.tsx +14 -37
- package/src/components/auth/types.ts +38 -0
- package/src/components/auth/use-authenticator-sign-in.ts +14 -0
- package/src/components/auth/use-public-authenticators.ts +26 -0
- package/src/components/data-table/data-table-filter.tsx +107 -153
- package/src/components/demo/prompt-output.tsx +71 -0
- package/src/components/ui/alert-dialog.tsx +2 -0
- package/src/components/ui/attachment.tsx +207 -0
- package/src/components/ui/bubble.tsx +128 -0
- package/src/components/ui/button-group.tsx +87 -0
- package/src/components/ui/button.tsx +6 -6
- package/src/components/ui/calendar.tsx +0 -2
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/carousel.tsx +2 -0
- package/src/components/ui/combobox.tsx +297 -0
- package/src/components/ui/command.tsx +43 -41
- package/src/components/ui/dialog.tsx +0 -2
- package/src/components/ui/direction.tsx +4 -0
- package/src/components/ui/drawer.tsx +2 -0
- package/src/components/ui/dropdown-menu.tsx +0 -2
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/hover-card.tsx +2 -0
- package/src/components/ui/input-group.tsx +0 -2
- package/src/components/ui/input-otp.tsx +0 -2
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/item.tsx +201 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +2 -0
- package/src/components/ui/marker.tsx +71 -0
- package/src/components/ui/message-scroller.tsx +131 -0
- package/src/components/ui/message.tsx +92 -0
- package/src/components/ui/native-select.tsx +61 -0
- package/src/components/ui/popover.tsx +0 -2
- package/src/components/ui/progress.tsx +2 -0
- package/src/components/ui/radio-group.tsx +0 -2
- package/src/components/ui/resizable.tsx +2 -0
- package/src/components/ui/scroll-area.tsx +0 -2
- package/src/components/ui/select.tsx +6 -4
- package/src/components/ui/separator.tsx +0 -2
- package/src/components/ui/sheet.tsx +21 -22
- package/src/components/ui/sidebar.tsx +1 -3
- package/src/components/ui/sonner.tsx +2 -0
- package/src/components/ui/spinner.tsx +10 -0
- package/src/components/ui/switch.tsx +0 -2
- package/src/components/ui/table.tsx +4 -2
- package/src/components/ui/tabs.tsx +0 -2
- package/src/components/ui/textarea.tsx +1 -1
- package/src/components/ui/toggle-group.tsx +2 -0
- package/src/components/ui/toggle.tsx +2 -0
- package/src/components/ui/tooltip.tsx +0 -2
- package/src/index.tsx +3 -0
- package/src/lib/nocobase/client.ts +32 -2
- package/src/lib/utils.ts +2 -1
- package/src/locales/en-US.ts +4 -2
- package/src/locales/zh-CN.ts +4 -2
- package/src/pages/login/index.tsx +2 -2
- package/src/pages/users/list.tsx +144 -16
- package/src/providers/auth-callback.ts +21 -0
- package/src/providers/auth.ts +108 -22
- package/src/providers/constants.ts +4 -1
- package/src/providers/runtime-config.ts +19 -0
- package/src/vite-env.d.ts +5 -0
- package/vite.config.ts +55 -2
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
3
3
|
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
import { Button } from "@/components/ui/button"
|
|
6
|
-
import { XIcon } from "lucide-react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { XIcon } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
9
|
-
return <SheetPrimitive.Root data-slot="sheet" {...props}
|
|
9
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
13
|
-
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props}
|
|
13
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
17
|
-
return <SheetPrimitive.Close data-slot="sheet-close" {...props}
|
|
17
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
21
|
-
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props}
|
|
21
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
@@ -31,7 +31,7 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
|
31
31
|
)}
|
|
32
32
|
{...props}
|
|
33
33
|
/>
|
|
34
|
-
)
|
|
34
|
+
)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function SheetContent({
|
|
@@ -39,12 +39,10 @@ function SheetContent({
|
|
|
39
39
|
children,
|
|
40
40
|
side = "right",
|
|
41
41
|
showCloseButton = true,
|
|
42
|
-
closeLabel = "Close",
|
|
43
42
|
...props
|
|
44
43
|
}: SheetPrimitive.Popup.Props & {
|
|
45
|
-
side?: "top" | "right" | "bottom" | "left"
|
|
46
|
-
showCloseButton?: boolean
|
|
47
|
-
closeLabel?: string;
|
|
44
|
+
side?: "top" | "right" | "bottom" | "left"
|
|
45
|
+
showCloseButton?: boolean
|
|
48
46
|
}) {
|
|
49
47
|
return (
|
|
50
48
|
<SheetPortal>
|
|
@@ -70,13 +68,14 @@ function SheetContent({
|
|
|
70
68
|
/>
|
|
71
69
|
}
|
|
72
70
|
>
|
|
73
|
-
<XIcon
|
|
74
|
-
|
|
71
|
+
<XIcon
|
|
72
|
+
/>
|
|
73
|
+
<span className="sr-only">Close</span>
|
|
75
74
|
</SheetPrimitive.Close>
|
|
76
75
|
)}
|
|
77
76
|
</SheetPrimitive.Popup>
|
|
78
77
|
</SheetPortal>
|
|
79
|
-
)
|
|
78
|
+
)
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -86,7 +85,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
86
85
|
className={cn("flex flex-col gap-0.5 p-4", className)}
|
|
87
86
|
{...props}
|
|
88
87
|
/>
|
|
89
|
-
)
|
|
88
|
+
)
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -96,7 +95,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
96
95
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
97
96
|
{...props}
|
|
98
97
|
/>
|
|
99
|
-
)
|
|
98
|
+
)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
@@ -109,7 +108,7 @@ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
|
109
108
|
)}
|
|
110
109
|
{...props}
|
|
111
110
|
/>
|
|
112
|
-
)
|
|
111
|
+
)
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
function SheetDescription({
|
|
@@ -122,7 +121,7 @@ function SheetDescription({
|
|
|
122
121
|
className={cn("text-sm text-muted-foreground", className)}
|
|
123
122
|
{...props}
|
|
124
123
|
/>
|
|
125
|
-
)
|
|
124
|
+
)
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
export {
|
|
@@ -134,4 +133,4 @@ export {
|
|
|
134
133
|
SheetFooter,
|
|
135
134
|
SheetTitle,
|
|
136
135
|
SheetDescription,
|
|
137
|
-
}
|
|
136
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import * as React from "react"
|
|
4
2
|
import { mergeProps } from "@base-ui/react/merge-props"
|
|
5
3
|
import { useRender } from "@base-ui/react/use-render"
|
|
@@ -29,7 +27,7 @@ const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
|
29
27
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
|
30
28
|
const SIDEBAR_WIDTH = "16rem"
|
|
31
29
|
const SIDEBAR_WIDTH_MOBILE = "18rem"
|
|
32
|
-
const SIDEBAR_WIDTH_ICON = "
|
|
30
|
+
const SIDEBAR_WIDTH_ICON = "3rem"
|
|
33
31
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
|
34
32
|
|
|
35
33
|
type SidebarContextProps = {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import { Loader2Icon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
5
|
+
return (
|
|
6
|
+
<Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
|
7
|
+
)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { Spinner }
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import * as React from "react"
|
|
2
4
|
|
|
3
5
|
import { cn } from "@/lib/utils"
|
|
@@ -68,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
68
70
|
<th
|
|
69
71
|
data-slot="table-head"
|
|
70
72
|
className={cn(
|
|
71
|
-
"h-
|
|
73
|
+
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
|
72
74
|
className
|
|
73
75
|
)}
|
|
74
76
|
{...props}
|
|
@@ -81,7 +83,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
|
81
83
|
<td
|
|
82
84
|
data-slot="table-cell"
|
|
83
85
|
className={cn(
|
|
84
|
-
"
|
|
86
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
85
87
|
className
|
|
86
88
|
)}
|
|
87
89
|
{...props}
|
|
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex field-sizing-content min-h-
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
package/src/index.tsx
CHANGED
|
@@ -3,6 +3,9 @@ import { createRoot } from "react-dom/client";
|
|
|
3
3
|
|
|
4
4
|
import "./locales";
|
|
5
5
|
import App from "./App";
|
|
6
|
+
import { captureAuthenticationCallback } from "./providers/auth-callback";
|
|
7
|
+
|
|
8
|
+
captureAuthenticationCallback();
|
|
6
9
|
|
|
7
10
|
const container = document.getElementById("root") as HTMLElement;
|
|
8
11
|
const root = createRoot(container);
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
API_ORIGIN,
|
|
3
3
|
API_URL,
|
|
4
4
|
NOCOBASE_AUTHENTICATOR,
|
|
5
|
+
NOCOBASE_AUTHENTICATOR_KEY,
|
|
5
6
|
NOCOBASE_LOCALE_KEY,
|
|
6
7
|
NOCOBASE_ROLE_KEY,
|
|
7
8
|
NOCOBASE_TOKEN_KEY,
|
|
@@ -22,6 +23,7 @@ export type NocoBaseRequestOptions = {
|
|
|
22
23
|
body?: unknown;
|
|
23
24
|
signal?: AbortSignal;
|
|
24
25
|
token?: string;
|
|
26
|
+
authenticator?: string | null;
|
|
25
27
|
role?: string;
|
|
26
28
|
includeRole?: boolean;
|
|
27
29
|
includeAuthenticator?: boolean;
|
|
@@ -93,6 +95,25 @@ export class NocoBaseClient {
|
|
|
93
95
|
);
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
getStoredAuthenticator() {
|
|
99
|
+
return typeof localStorage === "undefined"
|
|
100
|
+
? undefined
|
|
101
|
+
: localStorage.getItem(NOCOBASE_AUTHENTICATOR_KEY) ?? undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getAuthenticator() {
|
|
105
|
+
return this.getStoredAuthenticator() ?? NOCOBASE_AUTHENTICATOR;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setAuthenticator(authenticator?: string | null) {
|
|
109
|
+
if (typeof localStorage === "undefined") return;
|
|
110
|
+
if (authenticator) {
|
|
111
|
+
localStorage.setItem(NOCOBASE_AUTHENTICATOR_KEY, authenticator);
|
|
112
|
+
} else {
|
|
113
|
+
localStorage.removeItem(NOCOBASE_AUTHENTICATOR_KEY);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
96
117
|
setToken(token?: string | null) {
|
|
97
118
|
if (typeof localStorage === "undefined") return;
|
|
98
119
|
if (token) localStorage.setItem(NOCOBASE_TOKEN_KEY, token);
|
|
@@ -153,6 +174,7 @@ export class NocoBaseClient {
|
|
|
153
174
|
|
|
154
175
|
getHeaders({
|
|
155
176
|
token = this.getToken(),
|
|
177
|
+
authenticator,
|
|
156
178
|
role = this.getRole(),
|
|
157
179
|
includeRole = true,
|
|
158
180
|
includeAuthenticator = false,
|
|
@@ -163,6 +185,7 @@ export class NocoBaseClient {
|
|
|
163
185
|
}: Pick<
|
|
164
186
|
NocoBaseRequestOptions,
|
|
165
187
|
| "token"
|
|
188
|
+
| "authenticator"
|
|
166
189
|
| "role"
|
|
167
190
|
| "includeRole"
|
|
168
191
|
| "includeAuthenticator"
|
|
@@ -171,6 +194,13 @@ export class NocoBaseClient {
|
|
|
171
194
|
| "accept"
|
|
172
195
|
| "body"
|
|
173
196
|
> = {}) {
|
|
197
|
+
const resolvedAuthenticator =
|
|
198
|
+
authenticator === null
|
|
199
|
+
? undefined
|
|
200
|
+
: authenticator ??
|
|
201
|
+
(includeAuthenticator
|
|
202
|
+
? this.getAuthenticator()
|
|
203
|
+
: this.getStoredAuthenticator());
|
|
174
204
|
const locale = this.getLocale();
|
|
175
205
|
const formData =
|
|
176
206
|
typeof FormData !== "undefined" && body instanceof FormData;
|
|
@@ -182,8 +212,8 @@ export class NocoBaseClient {
|
|
|
182
212
|
...(accept === "stream"
|
|
183
213
|
? { "Cache-Control": "no-cache", Pragma: "no-cache" }
|
|
184
214
|
: {}),
|
|
185
|
-
...(
|
|
186
|
-
? { "X-Authenticator":
|
|
215
|
+
...(resolvedAuthenticator
|
|
216
|
+
? { "X-Authenticator": resolvedAuthenticator }
|
|
187
217
|
: {}),
|
|
188
218
|
...(includeRole && role ? { "X-Role": role } : {}),
|
|
189
219
|
...(withAclMeta ? { "X-With-ACL-Meta": "true" } : {}),
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx"
|
|
2
2
|
import { twMerge } from "tailwind-merge"
|
|
3
|
+
import { getPortalBase } from "@/providers/runtime-config"
|
|
3
4
|
|
|
4
5
|
export function cn(...inputs: ClassValue[]) {
|
|
5
6
|
return twMerge(clsx(inputs))
|
|
@@ -8,7 +9,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
8
9
|
export function assetUrl(path: string) {
|
|
9
10
|
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(path)) return path
|
|
10
11
|
|
|
11
|
-
const base =
|
|
12
|
+
const base = getPortalBase().replace(/\/+$/, "")
|
|
12
13
|
const assetPath = `/${path.replace(/^\/+/, "")}`
|
|
13
14
|
|
|
14
15
|
if (base && (assetPath === base || assetPath.startsWith(`${base}/`))) {
|
package/src/locales/en-US.ts
CHANGED
|
@@ -23,8 +23,8 @@ export const starter = {
|
|
|
23
23
|
"table.filter.operator.gt": "Greater than",
|
|
24
24
|
"table.filter.operator.lte": "Less than or equal",
|
|
25
25
|
"table.filter.operator.gte": "Greater than or equal",
|
|
26
|
-
"table.filter.operator.in": "Includes
|
|
27
|
-
"table.filter.operator.nin": "
|
|
26
|
+
"table.filter.operator.in": "Includes",
|
|
27
|
+
"table.filter.operator.nin": "Excludes",
|
|
28
28
|
"table.filter.operator.ina": "Includes in an array (case sensitive)",
|
|
29
29
|
"table.filter.operator.nina": "Not includes in an array (case sensitive)",
|
|
30
30
|
"table.filter.operator.contains": "Contains",
|
|
@@ -101,6 +101,8 @@ export const app = {
|
|
|
101
101
|
"users.fields.createdAt": "Created at",
|
|
102
102
|
"users.fields.updatedAt": "Updated at",
|
|
103
103
|
"users.fields.actions": "Actions",
|
|
104
|
+
"users.filters.roles.placeholder": "Select roles...",
|
|
105
|
+
"users.filters.roles.noResults": "No roles found.",
|
|
104
106
|
"users.actions.view": "View user",
|
|
105
107
|
"users.actions.edit": "Edit user",
|
|
106
108
|
"users.actions.delete": "Delete user",
|
package/src/locales/zh-CN.ts
CHANGED
|
@@ -23,8 +23,8 @@ export const starter = {
|
|
|
23
23
|
"table.filter.operator.gt": "大于",
|
|
24
24
|
"table.filter.operator.lte": "小于或等于",
|
|
25
25
|
"table.filter.operator.gte": "大于或等于",
|
|
26
|
-
"table.filter.operator.in": "
|
|
27
|
-
"table.filter.operator.nin": "
|
|
26
|
+
"table.filter.operator.in": "包含",
|
|
27
|
+
"table.filter.operator.nin": "排除",
|
|
28
28
|
"table.filter.operator.ina": "包含于数组(区分大小写)",
|
|
29
29
|
"table.filter.operator.nina": "不包含于数组(区分大小写)",
|
|
30
30
|
"table.filter.operator.contains": "包含",
|
|
@@ -97,6 +97,8 @@ export const app = {
|
|
|
97
97
|
"users.fields.createdAt": "创建时间",
|
|
98
98
|
"users.fields.updatedAt": "更新时间",
|
|
99
99
|
"users.fields.actions": "操作",
|
|
100
|
+
"users.filters.roles.placeholder": "选择角色…",
|
|
101
|
+
"users.filters.roles.noResults": "未找到角色。",
|
|
100
102
|
"users.actions.view": "查看用户",
|
|
101
103
|
"users.actions.edit": "编辑用户",
|
|
102
104
|
"users.actions.delete": "删除用户",
|
package/src/pages/users/list.tsx
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { useGetLocale, useTranslate } from "@refinedev/core";
|
|
1
|
+
import { useGetLocale, useList, useTranslate } from "@refinedev/core";
|
|
2
2
|
import { useTable } from "@refinedev/react-table";
|
|
3
|
-
import { createColumnHelper } from "@tanstack/react-table";
|
|
3
|
+
import { createColumnHelper, type Column } from "@tanstack/react-table";
|
|
4
4
|
import { Eye, Pencil, Trash2 } from "lucide-react";
|
|
5
|
-
import { useCallback, useMemo } from "react";
|
|
5
|
+
import { useCallback, useMemo, type ReactNode } from "react";
|
|
6
6
|
import { useNavigate } from "react-router";
|
|
7
7
|
|
|
8
8
|
import { DataTable } from "@/components/data-table/data-table";
|
|
9
|
+
import {
|
|
10
|
+
DataTableFilterCombobox,
|
|
11
|
+
DataTableFilterDropdownText,
|
|
12
|
+
} from "@/components/data-table/data-table-filter";
|
|
13
|
+
import { DataTableSorter } from "@/components/data-table/data-table-sorter";
|
|
9
14
|
import { DeleteButton } from "@/components/resources/buttons/delete";
|
|
10
15
|
import { EditButton } from "@/components/resources/buttons/edit";
|
|
11
16
|
import { ShowButton } from "@/components/resources/buttons/show";
|
|
@@ -15,11 +20,31 @@ import type { Role } from "@/lib/nocobase/acl";
|
|
|
15
20
|
import { RoleBadges } from "./role-badges";
|
|
16
21
|
import { resolveRoleLabel } from "./role-utils";
|
|
17
22
|
import { getRolePath } from "./routes";
|
|
18
|
-
import type { UserRecord } from "./types";
|
|
23
|
+
import type { RoleRecord, UserRecord } from "./types";
|
|
19
24
|
|
|
20
25
|
const isRootUser = (record: UserRecord) =>
|
|
21
26
|
record.roles?.some((role) => role.name === "root") ?? false;
|
|
22
27
|
|
|
28
|
+
function UserColumnHeader<TValue>({
|
|
29
|
+
children,
|
|
30
|
+
column,
|
|
31
|
+
label,
|
|
32
|
+
sortable = true,
|
|
33
|
+
}: {
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
column: Column<UserRecord, TValue>;
|
|
36
|
+
label: string;
|
|
37
|
+
sortable?: boolean;
|
|
38
|
+
}) {
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex items-center gap-1">
|
|
41
|
+
<span>{label}</span>
|
|
42
|
+
{sortable ? <DataTableSorter column={column} /> : null}
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
export const UserList = () => {
|
|
24
49
|
const translate = useTranslate();
|
|
25
50
|
const getLocale = useGetLocale();
|
|
@@ -29,6 +54,23 @@ export const UserList = () => {
|
|
|
29
54
|
(role: Role) => navigate(getRolePath(role.name)),
|
|
30
55
|
[navigate]
|
|
31
56
|
);
|
|
57
|
+
const { result: rolesResult } = useList<RoleRecord>({
|
|
58
|
+
resource: "roles",
|
|
59
|
+
pagination: { mode: "server", currentPage: 1, pageSize: 200 },
|
|
60
|
+
errorNotification: false,
|
|
61
|
+
queryOptions: { retry: false },
|
|
62
|
+
});
|
|
63
|
+
const roleOptions = useMemo(
|
|
64
|
+
() =>
|
|
65
|
+
rolesResult.data
|
|
66
|
+
.filter((role) => role.name)
|
|
67
|
+
.map((role) => ({
|
|
68
|
+
value: role.name,
|
|
69
|
+
label: resolveRoleLabel(role),
|
|
70
|
+
}))
|
|
71
|
+
.sort((left, right) => left.label.localeCompare(right.label, locale)),
|
|
72
|
+
[locale, rolesResult.data]
|
|
73
|
+
);
|
|
32
74
|
|
|
33
75
|
const columns = useMemo(() => {
|
|
34
76
|
const columnHelper = createColumnHelper<UserRecord>();
|
|
@@ -36,32 +78,113 @@ export const UserList = () => {
|
|
|
36
78
|
return [
|
|
37
79
|
columnHelper.accessor("nickname", {
|
|
38
80
|
id: "nickname",
|
|
39
|
-
header:
|
|
81
|
+
header: ({ column, table }) => (
|
|
82
|
+
<UserColumnHeader
|
|
83
|
+
column={column}
|
|
84
|
+
label={translate(
|
|
85
|
+
"users.fields.nickname",
|
|
86
|
+
{ ns: "app" },
|
|
87
|
+
"Nickname"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<DataTableFilterDropdownText
|
|
91
|
+
column={column}
|
|
92
|
+
table={table}
|
|
93
|
+
defaultOperator="contains"
|
|
94
|
+
operators={["contains", "eq", "startswith", "endswith"]}
|
|
95
|
+
/>
|
|
96
|
+
</UserColumnHeader>
|
|
97
|
+
),
|
|
40
98
|
enableSorting: true,
|
|
41
99
|
cell: ({ row, getValue }) =>
|
|
42
100
|
getValue() || row.original.username || row.original.email || "-",
|
|
43
101
|
}),
|
|
44
102
|
columnHelper.accessor("username", {
|
|
45
103
|
id: "username",
|
|
46
|
-
header:
|
|
104
|
+
header: ({ column, table }) => (
|
|
105
|
+
<UserColumnHeader
|
|
106
|
+
column={column}
|
|
107
|
+
label={translate(
|
|
108
|
+
"users.fields.username",
|
|
109
|
+
{ ns: "app" },
|
|
110
|
+
"Username"
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
<DataTableFilterDropdownText
|
|
114
|
+
column={column}
|
|
115
|
+
table={table}
|
|
116
|
+
defaultOperator="contains"
|
|
117
|
+
operators={["contains", "eq", "startswith", "endswith"]}
|
|
118
|
+
/>
|
|
119
|
+
</UserColumnHeader>
|
|
120
|
+
),
|
|
47
121
|
enableSorting: true,
|
|
48
122
|
cell: ({ getValue }) => getValue() || "-",
|
|
49
123
|
}),
|
|
50
124
|
columnHelper.accessor("email", {
|
|
51
125
|
id: "email",
|
|
52
|
-
header:
|
|
126
|
+
header: ({ column, table }) => (
|
|
127
|
+
<UserColumnHeader
|
|
128
|
+
column={column}
|
|
129
|
+
label={translate("users.fields.email", { ns: "app" }, "Email")}
|
|
130
|
+
>
|
|
131
|
+
<DataTableFilterDropdownText
|
|
132
|
+
column={column}
|
|
133
|
+
table={table}
|
|
134
|
+
defaultOperator="contains"
|
|
135
|
+
operators={["contains", "eq", "startswith", "endswith"]}
|
|
136
|
+
/>
|
|
137
|
+
</UserColumnHeader>
|
|
138
|
+
),
|
|
53
139
|
enableSorting: true,
|
|
54
140
|
cell: ({ getValue }) => getValue() || "-",
|
|
55
141
|
}),
|
|
56
142
|
columnHelper.accessor("phone", {
|
|
57
143
|
id: "phone",
|
|
58
|
-
header:
|
|
144
|
+
header: ({ column, table }) => (
|
|
145
|
+
<UserColumnHeader
|
|
146
|
+
column={column}
|
|
147
|
+
label={translate("users.fields.phone", { ns: "app" }, "Phone")}
|
|
148
|
+
>
|
|
149
|
+
<DataTableFilterDropdownText
|
|
150
|
+
column={column}
|
|
151
|
+
table={table}
|
|
152
|
+
defaultOperator="contains"
|
|
153
|
+
operators={["contains", "eq", "startswith", "endswith"]}
|
|
154
|
+
/>
|
|
155
|
+
</UserColumnHeader>
|
|
156
|
+
),
|
|
59
157
|
enableSorting: true,
|
|
60
158
|
cell: ({ getValue }) => getValue() || "-",
|
|
61
159
|
}),
|
|
62
|
-
columnHelper.accessor(
|
|
63
|
-
id: "roles",
|
|
64
|
-
header:
|
|
160
|
+
columnHelper.accessor((record) => record.roles, {
|
|
161
|
+
id: "roles.name",
|
|
162
|
+
header: ({ column, table }) => (
|
|
163
|
+
<UserColumnHeader
|
|
164
|
+
column={column}
|
|
165
|
+
label={translate("users.fields.roles", { ns: "app" }, "Roles")}
|
|
166
|
+
sortable={false}
|
|
167
|
+
>
|
|
168
|
+
<DataTableFilterCombobox
|
|
169
|
+
column={column}
|
|
170
|
+
table={table}
|
|
171
|
+
options={roleOptions}
|
|
172
|
+
defaultOperator="in"
|
|
173
|
+
operators={["in", "nin"]}
|
|
174
|
+
placeholder={translate(
|
|
175
|
+
"users.filters.roles.placeholder",
|
|
176
|
+
{ ns: "app" },
|
|
177
|
+
"Select roles..."
|
|
178
|
+
)}
|
|
179
|
+
noResultsText={translate(
|
|
180
|
+
"users.filters.roles.noResults",
|
|
181
|
+
{ ns: "app" },
|
|
182
|
+
"No roles found."
|
|
183
|
+
)}
|
|
184
|
+
multiple
|
|
185
|
+
/>
|
|
186
|
+
</UserColumnHeader>
|
|
187
|
+
),
|
|
65
188
|
enableSorting: false,
|
|
66
189
|
cell: ({ getValue }) => {
|
|
67
190
|
const roles = getValue() ?? [];
|
|
@@ -70,10 +193,15 @@ export const UserList = () => {
|
|
|
70
193
|
}),
|
|
71
194
|
columnHelper.accessor("createdAt", {
|
|
72
195
|
id: "createdAt",
|
|
73
|
-
header:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
196
|
+
header: ({ column }) => (
|
|
197
|
+
<UserColumnHeader
|
|
198
|
+
column={column}
|
|
199
|
+
label={translate(
|
|
200
|
+
"users.fields.createdAt",
|
|
201
|
+
{ ns: "app" },
|
|
202
|
+
"Created at"
|
|
203
|
+
)}
|
|
204
|
+
/>
|
|
77
205
|
),
|
|
78
206
|
enableSorting: true,
|
|
79
207
|
cell: ({ getValue }) => {
|
|
@@ -153,7 +281,7 @@ export const UserList = () => {
|
|
|
153
281
|
size: 144,
|
|
154
282
|
}),
|
|
155
283
|
];
|
|
156
|
-
}, [locale, showRole, translate]);
|
|
284
|
+
}, [locale, roleOptions, showRole, translate]);
|
|
157
285
|
|
|
158
286
|
const table = useTable<UserRecord>({
|
|
159
287
|
columns,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { clearAcl } from "@/lib/nocobase/acl";
|
|
2
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
3
|
+
|
|
4
|
+
export function captureAuthenticationCallback() {
|
|
5
|
+
if (typeof window === "undefined") return false;
|
|
6
|
+
|
|
7
|
+
const url = new URL(window.location.href);
|
|
8
|
+
const token = url.searchParams.get("token");
|
|
9
|
+
const authenticator = url.searchParams.get("authenticator");
|
|
10
|
+
|
|
11
|
+
if (!token) return false;
|
|
12
|
+
|
|
13
|
+
nocobaseClient.setToken(token);
|
|
14
|
+
nocobaseClient.setAuthenticator(authenticator);
|
|
15
|
+
clearAcl();
|
|
16
|
+
|
|
17
|
+
url.searchParams.delete("token");
|
|
18
|
+
url.searchParams.delete("authenticator");
|
|
19
|
+
window.history.replaceState(window.history.state, "", url);
|
|
20
|
+
return true;
|
|
21
|
+
}
|