@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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useSearchParams } from "react-router";
|
|
3
|
+
|
|
4
|
+
import type { Authenticator } from "@/components/auth/types";
|
|
5
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
6
|
+
|
|
7
|
+
type WecomAuthUrlResponse = {
|
|
8
|
+
url?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function isWecomBrowser() {
|
|
12
|
+
return (
|
|
13
|
+
typeof navigator !== "undefined" &&
|
|
14
|
+
navigator.userAgent.toLowerCase().includes("wxwork")
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useWecomSignIn(authenticator: Authenticator) {
|
|
19
|
+
const [searchParams] = useSearchParams();
|
|
20
|
+
const [isPending, setIsPending] = useState(false);
|
|
21
|
+
const [error, setError] = useState<Error>();
|
|
22
|
+
const callbackError = useMemo(() => {
|
|
23
|
+
if (searchParams.get("authenticator") !== authenticator.name) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const message = searchParams.get("errorMsg");
|
|
27
|
+
return message ? new Error(message) : undefined;
|
|
28
|
+
}, [authenticator.name, searchParams]);
|
|
29
|
+
|
|
30
|
+
const signIn = useCallback(async () => {
|
|
31
|
+
setError(undefined);
|
|
32
|
+
setIsPending(true);
|
|
33
|
+
try {
|
|
34
|
+
const redirect =
|
|
35
|
+
searchParams.get("to") || searchParams.get("redirect") || "/";
|
|
36
|
+
const result = await nocobaseClient.action<WecomAuthUrlResponse>(
|
|
37
|
+
"wecom",
|
|
38
|
+
"getAuthUrl",
|
|
39
|
+
{
|
|
40
|
+
method: "POST",
|
|
41
|
+
authenticator: authenticator.name,
|
|
42
|
+
includeRole: false,
|
|
43
|
+
withAclMeta: false,
|
|
44
|
+
body: { redirect, isWeComBrowser: isWecomBrowser() },
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
if (!result?.url) {
|
|
48
|
+
throw new Error("NocoBase did not return a WeCom authorization URL.");
|
|
49
|
+
}
|
|
50
|
+
window.location.replace(nocobaseClient.resolveUrl(result.url));
|
|
51
|
+
} catch (cause) {
|
|
52
|
+
setError(
|
|
53
|
+
cause instanceof Error
|
|
54
|
+
? cause
|
|
55
|
+
: new Error("Unable to start WeCom sign-in.")
|
|
56
|
+
);
|
|
57
|
+
setIsPending(false);
|
|
58
|
+
}
|
|
59
|
+
}, [authenticator.name, searchParams]);
|
|
60
|
+
|
|
61
|
+
return { signIn, isPending, error: error ?? callbackError };
|
|
62
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Building2 } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import type { AuthenticatorComponentProps } from "@/components/auth/types";
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertDescription,
|
|
7
|
+
AlertTitle,
|
|
8
|
+
} from "@/components/ui/alert";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import {
|
|
11
|
+
Tooltip,
|
|
12
|
+
TooltipContent,
|
|
13
|
+
TooltipTrigger,
|
|
14
|
+
} from "@/components/ui/tooltip";
|
|
15
|
+
import { resolveTranslatableText } from "@/lib/i18n";
|
|
16
|
+
|
|
17
|
+
import { useWecomSignIn } from "./use-wecom-sign-in";
|
|
18
|
+
|
|
19
|
+
function plainText(value: unknown) {
|
|
20
|
+
return typeof value === "string"
|
|
21
|
+
? value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
|
|
22
|
+
: "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function WecomSignInButton({
|
|
26
|
+
authenticator,
|
|
27
|
+
onSignIn,
|
|
28
|
+
}: AuthenticatorComponentProps & { onSignIn?: () => void }) {
|
|
29
|
+
const { signIn, isPending, error } = useWecomSignIn(authenticator);
|
|
30
|
+
const tooltip = plainText(authenticator.options?.btnTooltip);
|
|
31
|
+
const button = (
|
|
32
|
+
<Button
|
|
33
|
+
type="button"
|
|
34
|
+
variant="outline"
|
|
35
|
+
className="w-full"
|
|
36
|
+
disabled={!onSignIn && isPending}
|
|
37
|
+
onClick={onSignIn ?? signIn}
|
|
38
|
+
>
|
|
39
|
+
<Building2 />
|
|
40
|
+
{!onSignIn && isPending
|
|
41
|
+
? "Redirecting…"
|
|
42
|
+
: resolveTranslatableText(
|
|
43
|
+
authenticator.title ||
|
|
44
|
+
authenticator.authTypeTitle ||
|
|
45
|
+
"Sign in via WeCom"
|
|
46
|
+
)}
|
|
47
|
+
</Button>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="space-y-3">
|
|
52
|
+
{error && (
|
|
53
|
+
<Alert variant="destructive">
|
|
54
|
+
<AlertTitle>WeCom sign-in failed</AlertTitle>
|
|
55
|
+
<AlertDescription>{error.message}</AlertDescription>
|
|
56
|
+
</Alert>
|
|
57
|
+
)}
|
|
58
|
+
{tooltip ? (
|
|
59
|
+
<Tooltip>
|
|
60
|
+
<TooltipTrigger render={button} />
|
|
61
|
+
<TooltipContent side="right" className="max-w-xs">
|
|
62
|
+
{tooltip}
|
|
63
|
+
</TooltipContent>
|
|
64
|
+
</Tooltip>
|
|
65
|
+
) : (
|
|
66
|
+
button
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useTranslate } from "@refinedev/core";
|
|
2
|
-
import {
|
|
2
|
+
import { ChevronDown, Sparkles } from "lucide-react";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { PromptOutput } from "@/components/demo/prompt-output";
|
|
6
6
|
import {
|
|
7
7
|
Card,
|
|
8
8
|
CardContent,
|
|
@@ -18,8 +18,6 @@ export function I18nPromptGenerator() {
|
|
|
18
18
|
const t = useTranslate();
|
|
19
19
|
const [scene, setScene] = useState("A customer support workspace");
|
|
20
20
|
const [languages, setLanguages] = useState("English and Simplified Chinese");
|
|
21
|
-
const [copied, setCopied] = useState(false);
|
|
22
|
-
const [copyError, setCopyError] = useState(false);
|
|
23
21
|
|
|
24
22
|
const prompt = useMemo(
|
|
25
23
|
() => `Build a complete multilingual scene in the NocoBase Admin Starter.
|
|
@@ -100,56 +98,34 @@ Implementation contract
|
|
|
100
98
|
</div>
|
|
101
99
|
</CardContent>
|
|
102
100
|
</Card>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
{copied ? <Check /> : <Copy />}
|
|
135
|
-
{copied
|
|
136
|
-
? t("demo.prompt.copied", { ns: "nocobase-i18n" }, "Copied")
|
|
137
|
-
: t("demo.prompt.copy", { ns: "nocobase-i18n" }, "Copy prompt")}
|
|
138
|
-
</Button>
|
|
139
|
-
</div>
|
|
140
|
-
{copyError ? (
|
|
141
|
-
<p className="border-b px-4 py-2 text-xs text-destructive">
|
|
142
|
-
{t(
|
|
143
|
-
"demo.prompt.copyError",
|
|
144
|
-
{ ns: "nocobase-i18n" },
|
|
145
|
-
"Clipboard access failed. Select the prompt and copy it manually."
|
|
146
|
-
)}
|
|
147
|
-
</p>
|
|
148
|
-
) : null}
|
|
149
|
-
<pre className="max-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
|
|
150
|
-
{prompt}
|
|
151
|
-
</pre>
|
|
152
|
-
</Card>
|
|
101
|
+
<PromptOutput
|
|
102
|
+
title={t(
|
|
103
|
+
"demo.prompt.output",
|
|
104
|
+
{ ns: "nocobase-i18n" },
|
|
105
|
+
"Generated prompt"
|
|
106
|
+
)}
|
|
107
|
+
description={t(
|
|
108
|
+
"demo.prompt.outputDescription",
|
|
109
|
+
{ ns: "nocobase-i18n" },
|
|
110
|
+
"Updates as you change the scene and language requirements."
|
|
111
|
+
)}
|
|
112
|
+
prompt={prompt}
|
|
113
|
+
copyLabel={t(
|
|
114
|
+
"demo.prompt.copy",
|
|
115
|
+
{ ns: "nocobase-i18n" },
|
|
116
|
+
"Copy prompt"
|
|
117
|
+
)}
|
|
118
|
+
copiedLabel={t(
|
|
119
|
+
"demo.prompt.copied",
|
|
120
|
+
{ ns: "nocobase-i18n" },
|
|
121
|
+
"Copied"
|
|
122
|
+
)}
|
|
123
|
+
copyErrorLabel={t(
|
|
124
|
+
"demo.prompt.copyError",
|
|
125
|
+
{ ns: "nocobase-i18n" },
|
|
126
|
+
"Clipboard access failed. Select the prompt and copy it manually."
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
153
129
|
</div>
|
|
154
130
|
</details>
|
|
155
131
|
);
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { Check, Copy } from "lucide-react";
|
|
2
1
|
import { useMemo, useState } from "react";
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { PromptOutput } from "@/components/demo/prompt-output";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "@/components/ui/card";
|
|
6
11
|
import { Input } from "@/components/ui/input";
|
|
7
12
|
import { Label } from "@/components/ui/label";
|
|
8
13
|
import {
|
|
@@ -12,7 +17,6 @@ import {
|
|
|
12
17
|
SelectTrigger,
|
|
13
18
|
SelectValue,
|
|
14
19
|
} from "@/components/ui/select";
|
|
15
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
16
20
|
import {
|
|
17
21
|
getRouteSurfacePrompt,
|
|
18
22
|
routeSurfaceScenarios,
|
|
@@ -23,7 +27,6 @@ export function RouteSurfacePromptGenerator() {
|
|
|
23
27
|
const [scenarioId, setScenarioId] =
|
|
24
28
|
useState<RouteSurfaceScenarioId>("drawer");
|
|
25
29
|
const [target, setTarget] = useState("a customer detail workflow");
|
|
26
|
-
const [copied, setCopied] = useState(false);
|
|
27
30
|
const scenario =
|
|
28
31
|
routeSurfaceScenarios.find((item) => item.id === scenarioId) ??
|
|
29
32
|
routeSurfaceScenarios[0];
|
|
@@ -32,22 +35,17 @@ export function RouteSurfacePromptGenerator() {
|
|
|
32
35
|
[scenario, target]
|
|
33
36
|
);
|
|
34
37
|
|
|
35
|
-
const copyPrompt = async () => {
|
|
36
|
-
await navigator.clipboard.writeText(prompt);
|
|
37
|
-
setCopied(true);
|
|
38
|
-
window.setTimeout(() => setCopied(false), 1500);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
38
|
return (
|
|
42
39
|
<Card>
|
|
43
40
|
<CardHeader>
|
|
44
41
|
<CardTitle>Prompt generator</CardTitle>
|
|
45
42
|
<CardDescription>
|
|
46
|
-
Generate a complete routing scenario, not just an isolated overlay
|
|
43
|
+
Generate a complete routing scenario, not just an isolated overlay
|
|
44
|
+
component.
|
|
47
45
|
</CardDescription>
|
|
48
46
|
</CardHeader>
|
|
49
|
-
<CardContent className="
|
|
50
|
-
<div className="
|
|
47
|
+
<CardContent className="grid items-start gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
|
|
48
|
+
<div className="space-y-4">
|
|
51
49
|
<div className="space-y-2">
|
|
52
50
|
<Label htmlFor="route-surface-scenario">Scenario</Label>
|
|
53
51
|
<Select
|
|
@@ -77,13 +75,12 @@ export function RouteSurfacePromptGenerator() {
|
|
|
77
75
|
/>
|
|
78
76
|
</div>
|
|
79
77
|
</div>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
</div>
|
|
78
|
+
<PromptOutput
|
|
79
|
+
title="Generated routing prompt"
|
|
80
|
+
description="Updates as you change the scenario and business target."
|
|
81
|
+
prompt={prompt}
|
|
82
|
+
promptClassName="min-h-80"
|
|
83
|
+
/>
|
|
87
84
|
</CardContent>
|
|
88
85
|
</Card>
|
|
89
86
|
);
|
package/registry.config.json
CHANGED
|
@@ -58,6 +58,118 @@
|
|
|
58
58
|
"include": ["."]
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
"name": "auth-oidc",
|
|
63
|
+
"type": "registry:block",
|
|
64
|
+
"title": "NocoBase OIDC Authentication",
|
|
65
|
+
"description": "Dynamic OIDC sign-in adapter, headless sign-in hook, and integrated component demo.",
|
|
66
|
+
"dependencies": [
|
|
67
|
+
"lucide-react@^0.487.0",
|
|
68
|
+
"react-router@^7.0.2"
|
|
69
|
+
],
|
|
70
|
+
"docs": "Requires the Starter authentication runtime and an enabled OIDC authenticator in NocoBase. The default UI is discovered automatically and can be replaced per authentication type or authenticator instance from the application login page.",
|
|
71
|
+
"source": {
|
|
72
|
+
"root": "registry/nocobase-auth-oidc",
|
|
73
|
+
"target": "src/extensions/nocobase-auth-oidc",
|
|
74
|
+
"install": false,
|
|
75
|
+
"include": ["."]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "auth-saml",
|
|
80
|
+
"type": "registry:block",
|
|
81
|
+
"title": "NocoBase SAML Authentication",
|
|
82
|
+
"description": "Dynamic SAML sign-in adapter, automatic redirect provider, headless hook, and integrated demo.",
|
|
83
|
+
"dependencies": [
|
|
84
|
+
"lucide-react@^0.487.0",
|
|
85
|
+
"react-router@^7.0.2"
|
|
86
|
+
],
|
|
87
|
+
"docs": "Requires the Starter authentication runtime and an enabled SAML authenticator in NocoBase.",
|
|
88
|
+
"source": {
|
|
89
|
+
"root": "registry/nocobase-auth-saml",
|
|
90
|
+
"target": "src/extensions/nocobase-auth-saml",
|
|
91
|
+
"install": false,
|
|
92
|
+
"include": ["."]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "auth-cas",
|
|
97
|
+
"type": "registry:block",
|
|
98
|
+
"title": "NocoBase CAS Authentication",
|
|
99
|
+
"description": "Dynamic CAS sign-in adapter, headless sign-in hook, and integrated component demo.",
|
|
100
|
+
"dependencies": [
|
|
101
|
+
"lucide-react@^0.487.0",
|
|
102
|
+
"react-router@^7.0.2"
|
|
103
|
+
],
|
|
104
|
+
"docs": "Requires the Starter authentication runtime and an enabled CAS authenticator in NocoBase.",
|
|
105
|
+
"source": {
|
|
106
|
+
"root": "registry/nocobase-auth-cas",
|
|
107
|
+
"target": "src/extensions/nocobase-auth-cas",
|
|
108
|
+
"install": false,
|
|
109
|
+
"include": ["."]
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "auth-ldap",
|
|
114
|
+
"type": "registry:block",
|
|
115
|
+
"title": "NocoBase LDAP Authentication",
|
|
116
|
+
"description": "Dynamic LDAP account/password form, headless sign-in hook, and integrated component demo.",
|
|
117
|
+
"docs": "Requires the Starter authentication runtime and an enabled LDAP authenticator in NocoBase.",
|
|
118
|
+
"source": {
|
|
119
|
+
"root": "registry/nocobase-auth-ldap",
|
|
120
|
+
"target": "src/extensions/nocobase-auth-ldap",
|
|
121
|
+
"install": false,
|
|
122
|
+
"include": ["."]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "auth-sms",
|
|
127
|
+
"type": "registry:block",
|
|
128
|
+
"title": "NocoBase SMS Authentication",
|
|
129
|
+
"description": "Dynamic SMS verification-code form, delivery countdown, headless hook, and integrated demo.",
|
|
130
|
+
"docs": "Requires the Starter authentication runtime, an enabled SMS authenticator, and a configured public SMS verifier in NocoBase.",
|
|
131
|
+
"source": {
|
|
132
|
+
"root": "registry/nocobase-auth-sms",
|
|
133
|
+
"target": "src/extensions/nocobase-auth-sms",
|
|
134
|
+
"install": false,
|
|
135
|
+
"include": ["."]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "auth-wecom",
|
|
140
|
+
"type": "registry:block",
|
|
141
|
+
"title": "NocoBase WeCom Authentication",
|
|
142
|
+
"description": "Dynamic WeCom sign-in adapter, browser automatic login, headless hook, and integrated demo.",
|
|
143
|
+
"dependencies": [
|
|
144
|
+
"lucide-react@^0.487.0",
|
|
145
|
+
"react-router@^7.0.2"
|
|
146
|
+
],
|
|
147
|
+
"docs": "Requires the Starter authentication runtime and an enabled WeCom authenticator in NocoBase.",
|
|
148
|
+
"source": {
|
|
149
|
+
"root": "registry/nocobase-auth-wecom",
|
|
150
|
+
"target": "src/extensions/nocobase-auth-wecom",
|
|
151
|
+
"install": false,
|
|
152
|
+
"include": ["."]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "auth-dingtalk",
|
|
157
|
+
"type": "registry:block",
|
|
158
|
+
"title": "NocoBase DingTalk Authentication",
|
|
159
|
+
"description": "Dynamic DingTalk sign-in adapter, automatic login, headless hook, and integrated demo.",
|
|
160
|
+
"dependencies": [
|
|
161
|
+
"dingtalk-jsapi@3.1.1",
|
|
162
|
+
"lucide-react@^0.487.0",
|
|
163
|
+
"react-router@^7.0.2"
|
|
164
|
+
],
|
|
165
|
+
"docs": "Requires the Starter authentication runtime and an enabled DingTalk authenticator in NocoBase. DingTalk JSAPI is loaded lazily for internal micro-app login.",
|
|
166
|
+
"source": {
|
|
167
|
+
"root": "registry/nocobase-auth-dingtalk",
|
|
168
|
+
"target": "src/extensions/nocobase-auth-dingtalk",
|
|
169
|
+
"install": false,
|
|
170
|
+
"include": ["."]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
61
173
|
{
|
|
62
174
|
"name": "ai-runtime",
|
|
63
175
|
"type": "registry:lib",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const rootDir = path.resolve(__dirname, "..");
|
|
7
|
+
const distDir = path.join(rootDir, "dist");
|
|
8
|
+
const rawIndexPath = path.join(distDir, "index.raw.html");
|
|
9
|
+
const indexPath = path.join(distDir, "index.html");
|
|
10
|
+
|
|
11
|
+
const startMarker = "<!-- nocobase-runtime-config:start -->";
|
|
12
|
+
const endMarker = "<!-- nocobase-runtime-config:end -->";
|
|
13
|
+
|
|
14
|
+
const normalizePortalBase = (base) => {
|
|
15
|
+
const normalized = String(base || "/").trim();
|
|
16
|
+
if (!normalized || normalized === "/") return "/";
|
|
17
|
+
return `/${normalized.replace(/^\/+|\/+$/g, "")}/`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getBasePrefix = (base) => base.replace(/\/$/, "");
|
|
21
|
+
|
|
22
|
+
const getRawPortalBase = (html) => {
|
|
23
|
+
const moduleScriptPattern =
|
|
24
|
+
/<script\s+[^>]*type=["']module["'][^>]*\bsrc=(["'])([^"']+)\1[^>]*>/i;
|
|
25
|
+
const match = html.match(moduleScriptPattern);
|
|
26
|
+
const src = match?.[2];
|
|
27
|
+
|
|
28
|
+
if (!src?.startsWith("/")) return "/";
|
|
29
|
+
|
|
30
|
+
const assetsIndex = src.indexOf("/assets/");
|
|
31
|
+
if (assetsIndex === -1) return "/";
|
|
32
|
+
|
|
33
|
+
return normalizePortalBase(src.slice(0, assetsIndex) || "/");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const rewriteHtmlFilePaths = (html, portalBase, rawPortalBase) => {
|
|
37
|
+
const portalPrefix = getBasePrefix(portalBase);
|
|
38
|
+
const rawPrefix = getBasePrefix(rawPortalBase);
|
|
39
|
+
const attributePattern = /\b(src|href|content)=(["'])\/(?!\/)([^"']*)\2/g;
|
|
40
|
+
|
|
41
|
+
if (!portalPrefix && !rawPrefix) return html;
|
|
42
|
+
|
|
43
|
+
return html.replace(attributePattern, (match, attribute, quote, path) => {
|
|
44
|
+
let value = `/${path}`;
|
|
45
|
+
|
|
46
|
+
if (rawPrefix && value.startsWith(`${rawPrefix}/`)) {
|
|
47
|
+
value = value.slice(rawPrefix.length) || "/";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!portalPrefix || value.startsWith(`${portalPrefix}/`)) {
|
|
51
|
+
return `${attribute}=${quote}${value}${quote}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `${attribute}=${quote}${portalPrefix}${value}${quote}`;
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const stripExistingRuntimeConfig = (html) => {
|
|
59
|
+
const pattern = new RegExp(
|
|
60
|
+
`${startMarker}[\\s\\S]*?${endMarker}\\s*`,
|
|
61
|
+
"g"
|
|
62
|
+
);
|
|
63
|
+
return html.replace(pattern, "");
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const portalBase = normalizePortalBase(process.env.NOCOBASE_PORTAL_BASE);
|
|
67
|
+
const apiUrl = String(process.env.NOCOBASE_API_URL || "/api").trim() || "/api";
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(rawIndexPath)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Missing ${path.relative(rootDir, rawIndexPath)}. Run pnpm build first.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const runtimeConfig = `${startMarker}
|
|
76
|
+
<script>
|
|
77
|
+
window.NOCOBASE_PORTAL_BASE = ${JSON.stringify(portalBase)};
|
|
78
|
+
window.NOCOBASE_API_URL = ${JSON.stringify(apiUrl)};
|
|
79
|
+
</script>
|
|
80
|
+
${endMarker}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const rawHtml = fs.readFileSync(rawIndexPath, "utf8");
|
|
84
|
+
const html = rewriteHtmlFilePaths(
|
|
85
|
+
stripExistingRuntimeConfig(rawHtml),
|
|
86
|
+
portalBase,
|
|
87
|
+
getRawPortalBase(rawHtml)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const moduleScriptPattern = /<script\s+[^>]*type=["']module["'][^>]*>/i;
|
|
91
|
+
const moduleScriptMatch = html.match(moduleScriptPattern);
|
|
92
|
+
|
|
93
|
+
if (moduleScriptMatch?.index === undefined) {
|
|
94
|
+
throw new Error("Could not find the module script in dist/index.raw.html.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const outputHtml = `${html.slice(0, moduleScriptMatch.index)}${runtimeConfig}${html.slice(moduleScriptMatch.index)}`;
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(indexPath, outputHtml);
|
|
100
|
+
|
|
101
|
+
console.log(
|
|
102
|
+
`Generated ${path.relative(rootDir, indexPath)} from ${path.relative(
|
|
103
|
+
rootDir,
|
|
104
|
+
rawIndexPath
|
|
105
|
+
)}`
|
|
106
|
+
);
|
package/src/App.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import { TooltipProvider } from "./components/ui/tooltip";
|
|
|
27
27
|
import { BrandLogo } from "./components/app-shell/brand";
|
|
28
28
|
import {
|
|
29
29
|
AppExtensionProviders,
|
|
30
|
+
AppAuthRuntimeProviders,
|
|
30
31
|
extensionResources,
|
|
31
32
|
extensionRouteElements,
|
|
32
33
|
} from "./app/extensions";
|
|
@@ -36,9 +37,11 @@ import { accessControlProvider } from "./providers/access-control";
|
|
|
36
37
|
import { AclBootstrap } from "./components/access-control/acl-bootstrap";
|
|
37
38
|
import { ResourceAccessGuard } from "./components/access-control/resource-access-guard";
|
|
38
39
|
import { NavigateToAccessibleResource } from "./components/access-control/navigate-to-accessible-resource";
|
|
39
|
-
import { UsersRound } from "lucide-react";
|
|
40
|
+
import { KeyRound, PanelsTopLeft, UsersRound } from "lucide-react";
|
|
40
41
|
import { i18nProvider } from "./providers/i18n";
|
|
41
42
|
import { SystemSettingsProvider } from "./providers/system-settings";
|
|
43
|
+
import { AuthDemoPage } from "./components/auth/demo";
|
|
44
|
+
import { getPortalBase } from "./providers/runtime-config";
|
|
42
45
|
|
|
43
46
|
const coreResources: ResourceProps[] = [
|
|
44
47
|
{
|
|
@@ -64,16 +67,36 @@ const coreResources: ResourceProps[] = [
|
|
|
64
67
|
},
|
|
65
68
|
},
|
|
66
69
|
},
|
|
70
|
+
{
|
|
71
|
+
name: "auth-components",
|
|
72
|
+
meta: {
|
|
73
|
+
label: "Authentication",
|
|
74
|
+
icon: <KeyRound />,
|
|
75
|
+
description: "NocoBase authentication UI and integration patterns.",
|
|
76
|
+
acl: { type: "authenticated" },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "auth-patterns",
|
|
81
|
+
list: "/auth",
|
|
82
|
+
meta: {
|
|
83
|
+
parent: "auth-components",
|
|
84
|
+
label: "Login composition",
|
|
85
|
+
icon: <PanelsTopLeft />,
|
|
86
|
+
acl: { type: "authenticated" },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
67
89
|
];
|
|
68
90
|
|
|
69
|
-
const basename =
|
|
91
|
+
const basename = getPortalBase().replace(/\/+$/, "");
|
|
70
92
|
|
|
71
93
|
function App() {
|
|
72
94
|
return (
|
|
73
95
|
<BrowserRouter basename={basename || undefined}>
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
<
|
|
96
|
+
<AppAuthRuntimeProviders>
|
|
97
|
+
<ThemeProvider>
|
|
98
|
+
<TooltipProvider>
|
|
99
|
+
<SystemSettingsProvider>
|
|
77
100
|
<Refine
|
|
78
101
|
dataProvider={dataProvider}
|
|
79
102
|
notificationProvider={useNotificationProvider()}
|
|
@@ -110,6 +133,7 @@ function App() {
|
|
|
110
133
|
}
|
|
111
134
|
>
|
|
112
135
|
<Route index element={<NavigateToAccessibleResource />} />
|
|
136
|
+
<Route path="/auth" element={<AuthDemoPage />} />
|
|
113
137
|
<Route
|
|
114
138
|
path="/users"
|
|
115
139
|
element={<UserResourceLayout />}
|
|
@@ -180,6 +204,7 @@ function App() {
|
|
|
180
204
|
}
|
|
181
205
|
>
|
|
182
206
|
<Route path="/login" element={<Login />} />
|
|
207
|
+
<Route path="/signin" element={<Login />} />
|
|
183
208
|
<Route path="/register" element={<Register />} />
|
|
184
209
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
|
185
210
|
</Route>
|
|
@@ -189,9 +214,10 @@ function App() {
|
|
|
189
214
|
<UnsavedChangesNotifier />
|
|
190
215
|
<DocumentTitleHandler appName="NocoBase" />
|
|
191
216
|
</Refine>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
217
|
+
</SystemSettingsProvider>
|
|
218
|
+
</TooltipProvider>
|
|
219
|
+
</ThemeProvider>
|
|
220
|
+
</AppAuthRuntimeProviders>
|
|
195
221
|
</BrowserRouter>
|
|
196
222
|
);
|
|
197
223
|
}
|
package/src/app/extension.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ResourceProps } from "@refinedev/core";
|
|
2
2
|
import type { ComponentType, PropsWithChildren, ReactElement } from "react";
|
|
3
|
+
import type { AuthenticatorAdapter } from "@/components/auth/types";
|
|
3
4
|
|
|
4
5
|
export type AppExtension = {
|
|
5
6
|
id: string;
|
|
6
7
|
resources?: ResourceProps[];
|
|
7
8
|
routes?: ReactElement;
|
|
8
9
|
Provider?: ComponentType<PropsWithChildren>;
|
|
10
|
+
AuthRuntimeProvider?: ComponentType<PropsWithChildren>;
|
|
11
|
+
authRuntimePriority?: number;
|
|
9
12
|
UserMenuItems?: ComponentType;
|
|
13
|
+
authAdapters?: AuthenticatorAdapter[];
|
|
10
14
|
};
|