@nocobase/portal-template-default 1.0.16 → 1.0.17
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/AGENTS.md +8 -0
- package/README.MD +4 -0
- package/dist/assets/{LineDraw-vPRTWiQ5.js → LineDraw-BBB5hwuJ.js} +1 -1
- package/dist/assets/{SymbolDraw-DOl_Tss3.js → SymbolDraw-CKS3uUb_.js} +1 -1
- package/dist/assets/{VisualMapping-j08W3KBm.js → VisualMapping-BCwaW3TD.js} +1 -1
- package/dist/assets/auth-method-demo-BdCCvEEz.js +1 -0
- package/dist/assets/{auto-login-provider-Bukw7zEe.js → auto-login-provider-DO5Il6Dp.js} +2 -2
- package/dist/assets/auto-login-provider-TQ08B6-i.js +1 -0
- package/dist/assets/auto-redirect-provider-ButXp0A3.js +1 -0
- package/dist/assets/auto-redirect-provider-DnGGP2Vn.js +1 -0
- package/dist/assets/auto-redirect-provider-ja-vFPaF.js +1 -0
- package/dist/assets/calendar-C7xnRgOT.js +1 -0
- package/dist/assets/{cas-sign-in-button-BQn3Mttx.js → cas-sign-in-button-BZCa4lR6.js} +1 -1
- package/dist/assets/{components-BT5mL9gW.js → components-_g-GjQeV.js} +3 -3
- package/dist/assets/{configuration-gate-BJoadoA6.js → configuration-gate-B4XVz7tS.js} +1 -1
- package/dist/assets/{createClipPathFromCoordSys-m8IbnJ1z.js → createClipPathFromCoordSys-z9Kmh-tu.js} +1 -1
- package/dist/assets/createSeriesDataSimply-CPqysF0K.js +1 -0
- package/dist/assets/{customGraphicKeyframeAnimation-CYLPurwK.js → customGraphicKeyframeAnimation-DMU5crAu.js} +1 -1
- package/dist/assets/demo-BPlae2bp.js +1 -0
- package/dist/assets/demo-Bj_Y0RrW.js +1 -0
- package/dist/assets/demo-CKPAQOib.js +1 -0
- package/dist/assets/demo-CXRTPvIx.js +1 -0
- package/dist/assets/demo-DJuVDu1n.js +1 -0
- package/dist/assets/demo-DUmWU7ik.js +1 -0
- package/dist/assets/demo-NhIzFRE1.js +1 -0
- package/dist/assets/{dingtalk-sign-in-button-DSluFaP9.js → dingtalk-sign-in-button-DMPgE5kT.js} +1 -1
- package/dist/assets/{echarts-preview-mRXoEciY.js → echarts-preview-R96Pbqmz.js} +1 -1
- package/dist/assets/{echarts-runtime-CvLr4Slh.js → echarts-runtime-Ce_z2eX_.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-BC1tvfQ0.js → echarts-runtime-advanced-Dr80KHJR.js} +1 -1
- package/dist/assets/{echarts-runtime-common-DhNaV5B8.js → echarts-runtime-common-DB95Yfc1.js} +1 -1
- package/dist/assets/{echarts-runtime-components-BiioS8t1.js → echarts-runtime-components-DCJEaTJk.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-CAWqYvFh.js → echarts-runtime-hierarchy-CezgZs9O.js} +1 -1
- package/dist/assets/{floating-RVbHvIiD.js → floating-O9WmiDXk.js} +2 -2
- package/dist/assets/index-BBKV6O5d.css +1 -0
- package/dist/assets/{index-Dw20Lzpp.js → index-CPzehPGe.js} +3 -3
- package/dist/assets/index-Cc6awvdg.js +1064 -0
- package/dist/assets/index-ChA0z9ke.js +38 -0
- package/dist/assets/{index-BysibBZu.js → index-DgB7uTMs.js} +2 -2
- package/dist/assets/{index-C-tdHirk.js → index-DroUAK80.js} +1 -1
- package/dist/assets/index-WKvygTBp.js +59 -0
- package/dist/assets/{labelHelper-DbITmOUW.js → labelHelper-Dk5jkiD-.js} +1 -1
- package/dist/assets/ldap-sign-in-form-CKN4l7qM.js +1 -0
- package/dist/assets/{oidc-sign-in-button-Cgpa8Qzj.js → oidc-sign-in-button-BIRonrpC.js} +1 -1
- package/dist/assets/{page-context-AU6RU0Qi.js → page-context-DAiW_dId.js} +4 -4
- package/dist/assets/prompt-card-rGZqpqkF.js +1 -0
- package/dist/assets/prompt-output-B4h3uLWf.js +1 -0
- package/dist/assets/saml-sign-in-button-B4ugaLIb.js +1 -0
- package/dist/assets/{sectorHelper-C6L782Ue.js → sectorHelper-BuxrrJrA.js} +1 -1
- package/dist/assets/shortcut-BhKXxJLs.js +47 -0
- package/dist/assets/{sms-sign-in-form-Bo2WLZuP.js → sms-sign-in-form-DKpxRDdZ.js} +1 -1
- package/dist/assets/{static.browser-CoPxsICt.js → static.browser-kzzpH0Xt.js} +1 -1
- package/dist/assets/switch-BdVxPIVU.js +1 -0
- package/dist/assets/{tool-cards-ipgJ_3Ec.js → tool-cards-CpczIke-.js} +3 -3
- package/dist/assets/{use-authenticator-sign-in-BzFeh7qe.js → use-authenticator-sign-in-CIj210ry.js} +1 -1
- package/dist/assets/use-dingtalk-sign-in-BW4kkXD3.js +1 -0
- package/dist/assets/{use-wecom-sign-in-PPIMLvGa.js → use-wecom-sign-in-dIqMtcN9.js} +1 -1
- package/dist/assets/{wecom-sign-in-button-rUq9RW8R.js → wecom-sign-in-button-Dn_2zDab.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/index.raw.html +2 -2
- package/package.json +9 -1
- package/src/App.tsx +5 -2
- package/src/app/extension.ts +2 -0
- package/src/app/extensions.tsx +18 -6
- package/src/app/route-access-guard.tsx +22 -0
- package/src/app/route-runtime.ts +173 -0
- package/src/app/routes.tsx +2 -2
- package/src/components/app-shell/header.tsx +56 -1
- package/src/components/app-shell/sidebar.tsx +14 -2
- package/src/extensions/nocobase-acl/README.md +28 -14
- package/src/extensions/nocobase-acl/demo/index.tsx +87 -15
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +1 -0
- package/src/extensions/nocobase-mail/README.md +74 -0
- package/src/extensions/nocobase-mail/components/index.ts +79 -0
- package/src/extensions/nocobase-mail/components/mail-api.ts +423 -0
- package/src/extensions/nocobase-mail/components/mail-attachment-list.tsx +67 -0
- package/src/extensions/nocobase-mail/components/mail-compose-attachments.tsx +166 -0
- package/src/extensions/nocobase-mail/components/mail-compose.tsx +1071 -0
- package/src/extensions/nocobase-mail/components/mail-detail.tsx +653 -0
- package/src/extensions/nocobase-mail/components/mail-draft-autosave.ts +26 -0
- package/src/extensions/nocobase-mail/components/mail-empty.tsx +48 -0
- package/src/extensions/nocobase-mail/components/mail-filters.tsx +277 -0
- package/src/extensions/nocobase-mail/components/mail-inbox.tsx +544 -0
- package/src/extensions/nocobase-mail/components/mail-inline-images.ts +69 -0
- package/src/extensions/nocobase-mail/components/mail-label-badge.tsx +25 -0
- package/src/extensions/nocobase-mail/components/mail-label-manager.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-labels-editor.tsx +267 -0
- package/src/extensions/nocobase-mail/components/mail-mass-tracking.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-note-editor.tsx +99 -0
- package/src/extensions/nocobase-mail/components/mail-recipient-input.tsx +260 -0
- package/src/extensions/nocobase-mail/components/mail-reply-quote.ts +28 -0
- package/src/extensions/nocobase-mail/components/mail-rich-editor.tsx +287 -0
- package/src/extensions/nocobase-mail/components/mail-send-actions.tsx +238 -0
- package/src/extensions/nocobase-mail/components/mail-senders.ts +61 -0
- package/src/extensions/nocobase-mail/components/mail-settings-drawer.tsx +708 -0
- package/src/extensions/nocobase-mail/components/mail-showcase-layout.tsx +64 -0
- package/src/extensions/nocobase-mail/components/mail-signature-manager.tsx +258 -0
- package/src/extensions/nocobase-mail/components/mail-signatures.ts +25 -0
- package/src/extensions/nocobase-mail/components/mail-table.tsx +476 -0
- package/src/extensions/nocobase-mail/components/mail-template-manager.tsx +204 -0
- package/src/extensions/nocobase-mail/components/mail-toolbar.tsx +155 -0
- package/src/extensions/nocobase-mail/components/mail-unread.tsx +111 -0
- package/src/extensions/nocobase-mail/components/types.ts +297 -0
- package/src/extensions/nocobase-mail/components/use-mail-compose.tsx +262 -0
- package/src/extensions/nocobase-mail/components/use-mail-mass-messages.ts +54 -0
- package/src/extensions/nocobase-mail/components/use-mail-messages.ts +113 -0
- package/src/extensions/nocobase-mail/components/use-mail-signatures.ts +122 -0
- package/src/extensions/nocobase-mail/components/use-mail-templates.ts +54 -0
- package/src/extensions/nocobase-mail/extension.tsx +105 -0
- package/src/extensions/nocobase-mail/index.ts +1 -0
- package/src/extensions/nocobase-mail/mail-demo-pages.tsx +503 -0
- package/src/extensions/nocobase-mail/mail-pages.tsx +201 -0
- package/src/extensions/nocobase-mail/tests/mail-quote-html-regression.mjs +51 -0
- package/src/extensions/nocobase-mail/tests/mail-regression.mjs +649 -0
- package/src/extensions/nocobase-users-example/app-routes.tsx +121 -0
- package/src/extensions/nocobase-users-example/extension.tsx +2 -100
- package/src/extensions/nocobase-users-example/index.ts +1 -0
- package/src/lib/nocobase/acl/menu.ts +17 -3
- package/src/lib/nocobase/acl/types.ts +4 -0
- package/src/locales/en-US.ts +2 -1
- package/src/locales/zh-CN.ts +2 -1
- package/src/providers/runtime-config.ts +18 -0
- package/src/routes.tsx +10 -0
- package/src/vite-env.d.ts +3 -0
- package/vite.config.ts +8 -0
- package/dist/assets/auth-method-demo-B2qcpY8z.js +0 -1
- package/dist/assets/auto-login-provider-BMtzvdxG.js +0 -1
- package/dist/assets/auto-redirect-provider-C6GNPg5r.js +0 -1
- package/dist/assets/auto-redirect-provider-D5c6kN2R.js +0 -1
- package/dist/assets/auto-redirect-provider-iTzGEDpU.js +0 -1
- package/dist/assets/calendar-Dv2RNnsr.js +0 -1
- package/dist/assets/card-C9Hpeq1c.js +0 -1
- package/dist/assets/createSeriesDataSimply-DKUV5b5e.js +0 -1
- package/dist/assets/demo-BWGOwqtJ.js +0 -1
- package/dist/assets/demo-BWbcvkya.js +0 -1
- package/dist/assets/demo-DIASSPgn.js +0 -1
- package/dist/assets/demo-DuZ6asUU.js +0 -1
- package/dist/assets/demo-Dxcy4bDE.js +0 -1
- package/dist/assets/demo-YpEC76uw.js +0 -1
- package/dist/assets/demo-wzkoR8T2.js +0 -1
- package/dist/assets/index-CBZbj45X.js +0 -58
- package/dist/assets/index-CgkdZ6Oc.js +0 -686
- package/dist/assets/index-NH3aqqQz.css +0 -1
- package/dist/assets/index-hSBzW-VA.js +0 -43
- package/dist/assets/ldap-sign-in-form-BOMNDTkK.js +0 -1
- package/dist/assets/prompt-card-FWugRgi-.js +0 -1
- package/dist/assets/prompt-output-DMTuTNab.js +0 -1
- package/dist/assets/saml-sign-in-button-qt8bMeoQ.js +0 -1
- package/dist/assets/shortcut-Bu7jT_-w.js +0 -47
- package/dist/assets/switch-DQkkIE7G.js +0 -1
- package/dist/assets/use-dingtalk-sign-in-mrq6K5Yx.js +0 -1
package/dist/assets/{use-authenticator-sign-in-BzFeh7qe.js → use-authenticator-sign-in-CIj210ry.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{bf as i}from"./index-Cc6awvdg.js";function o(r){const n=i();return{signIn:t=>n.mutateAsync({...t,authenticator:r}),isPending:n.isPending,error:n.error}}export{o as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b5 as g,b as a,b6 as d,b7 as i}from"./index-Cc6awvdg.js";function w(){return typeof navigator<"u"&&navigator.userAgent.toLowerCase().includes("dingtalk")}function b(n){const[r]=g(),[c,o]=a.useState(!1),[l,s]=a.useState(),u=a.useMemo(()=>{if(r.get("authenticator")!==n.name)return;const e=r.get("error");return e?new Error(e):void 0},[n.name,r]);return{signIn:a.useCallback(async()=>{s(void 0),o(!0);try{const e=d(r.get("to")||r.get("redirect")||"/"),t=await i.action("dingtalk","getAuthUrl",{method:"POST",authenticator:n.name,includeRole:!1,withAclMeta:!1,body:{redirect:e}});if(!(t!=null&&t.url))throw new Error("NocoBase did not return a DingTalk authorization URL.");window.location.replace(i.resolveUrl(t.url))}catch(e){s(e instanceof Error?e:new Error("Unable to start DingTalk sign-in.")),o(!1)}},[n.name,r]),isPending:c,error:l??u}}export{w as i,b as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{b5 as m,b as n,b6 as d,b7 as i}from"./index-Cc6awvdg.js";function g(){return typeof navigator<"u"&&navigator.userAgent.toLowerCase().includes("wxwork")}function b(o){const[r]=m(),[c,s]=n.useState(!1),[l,a]=n.useState(),u=n.useMemo(()=>{if(r.get("authenticator")!==o.name)return;const e=r.get("errorMsg");return e?new Error(e):void 0},[o.name,r]);return{signIn:n.useCallback(async()=>{a(void 0),s(!0);try{const e=d(r.get("to")||r.get("redirect")||"/"),t=await i.action("wecom","getAuthUrl",{method:"POST",authenticator:o.name,includeRole:!1,withAclMeta:!1,body:{redirect:e,isWeComBrowser:g()}});if(!(t!=null&&t.url))throw new Error("NocoBase did not return a WeCom authorization URL.");window.location.replace(i.resolveUrl(t.url))}catch(e){a(e instanceof Error?e:new Error("Unable to start WeCom sign-in.")),s(!1)}},[o.name,r]),isPending:c,error:l??u}}export{g as i,b as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as s,
|
|
1
|
+
import{j as s,K as c,bg as d,r as p,aN as x,aP as m,aQ as g,bh as u,bi as j,bj as T}from"./index-Cc6awvdg.js";import{u as b}from"./use-wecom-sign-in-dIqMtcN9.js";function f(e){return typeof e=="string"?e.replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim():""}function y({authenticator:e,onSignIn:i}){var o;const{signIn:a,isPending:t,error:n}=b(e),r=f((o=e.options)==null?void 0:o.btnTooltip),l=s.jsxs(c,{type:"button",variant:"outline",className:"w-full",disabled:!i&&t,onClick:i??a,children:[s.jsx(d,{}),!i&&t?"Redirecting…":p(e.title||e.authTypeTitle||"Sign in via WeCom")]});return s.jsxs("div",{className:"space-y-3",children:[n&&s.jsxs(x,{variant:"destructive",children:[s.jsx(m,{children:"WeCom sign-in failed"}),s.jsx(g,{children:n.message})]}),r?s.jsxs(u,{children:[s.jsx(j,{render:l}),s.jsx(T,{side:"right",className:"max-w-xs",children:r})]}):l]})}export{y as default};
|
package/dist/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
content="/logo-mark.png"
|
|
21
21
|
/>
|
|
22
22
|
<title>NocoBase</title>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-Cc6awvdg.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BBKV6O5d.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/dist/index.raw.html
CHANGED
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
window.__nocobase_api_client_share_token__ = window.__nocobase_api_client_share_token__ ?? false;
|
|
30
30
|
</script>
|
|
31
31
|
<!-- nocobase-runtime-config:end -->
|
|
32
|
-
<script type="module" crossorigin src="/assets/index-
|
|
33
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
32
|
+
<script type="module" crossorigin src="/assets/index-Cc6awvdg.js"></script>
|
|
33
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BBKV6O5d.css">
|
|
34
34
|
</head>
|
|
35
35
|
<body>
|
|
36
36
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/portal-template-default",
|
|
3
|
-
"
|
|
3
|
+
"displayName": "Default Template",
|
|
4
|
+
"version": "1.0.17",
|
|
4
5
|
"packageManager": "pnpm@10.28.0",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"devDependencies": {
|
|
@@ -24,6 +25,12 @@
|
|
|
24
25
|
"@tailwindcss/vite": "^4.1.8",
|
|
25
26
|
"@tanstack/react-query": "^5.81.5",
|
|
26
27
|
"@tanstack/react-table": "^8.2.6",
|
|
28
|
+
"@tiptap/extension-link": "^3.29.0",
|
|
29
|
+
"@tiptap/extension-placeholder": "^3.29.0",
|
|
30
|
+
"@tiptap/extension-text-style": "3.29.0",
|
|
31
|
+
"@tiptap/pm": "^3.29.0",
|
|
32
|
+
"@tiptap/react": "^3.29.0",
|
|
33
|
+
"@tiptap/starter-kit": "^3.29.0",
|
|
27
34
|
"@types/node": "^20",
|
|
28
35
|
"@types/react": "^19.1.0",
|
|
29
36
|
"@types/react-dom": "^19.1.0",
|
|
@@ -84,6 +91,7 @@
|
|
|
84
91
|
"test:auth": "node tests/auth-session-regression.mjs",
|
|
85
92
|
"test:acl": "node registry/nocobase-acl/tests/acl-regression.mjs",
|
|
86
93
|
"test:i18n": "node registry/nocobase-i18n/tests/i18n-regression.mjs",
|
|
94
|
+
"test:mail": "node registry/nocobase-mail/tests/mail-regression.mjs",
|
|
87
95
|
"start": "refine start",
|
|
88
96
|
"refine": "refine"
|
|
89
97
|
},
|
package/src/App.tsx
CHANGED
|
@@ -11,7 +11,10 @@ import { Toaster } from "./components/notifications/toaster";
|
|
|
11
11
|
import { ThemeProvider } from "./components/theme/theme-provider";
|
|
12
12
|
import { TooltipProvider } from "./components/ui/tooltip";
|
|
13
13
|
import { BrandLogo } from "./components/app-shell/brand";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AppAuthRuntimeProviders,
|
|
16
|
+
configuredResources,
|
|
17
|
+
} from "./app/extensions";
|
|
15
18
|
import "./App.css";
|
|
16
19
|
import { authProvider } from "./providers/auth";
|
|
17
20
|
import { accessControlProvider } from "./providers/access-control";
|
|
@@ -24,7 +27,7 @@ import { AppRoutes } from "./app/routes";
|
|
|
24
27
|
const getResourcePriority = (resource: ResourceProps) =>
|
|
25
28
|
typeof resource.meta?.priority === "number" ? resource.meta.priority : 100;
|
|
26
29
|
|
|
27
|
-
const appResources = [...
|
|
30
|
+
const appResources = [...configuredResources].sort(
|
|
28
31
|
(left, right) => getResourcePriority(left) - getResourcePriority(right)
|
|
29
32
|
);
|
|
30
33
|
|
package/src/app/extension.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { ResourceProps } from "@refinedev/core";
|
|
2
2
|
import type { ComponentType, PropsWithChildren, ReactElement } from "react";
|
|
3
3
|
import type { AuthenticatorAdapter } from "@/components/auth/types";
|
|
4
|
+
import type { AppRouteDefinition } from "./route-runtime";
|
|
4
5
|
|
|
5
6
|
export type AppExtension = {
|
|
6
7
|
id: string;
|
|
7
8
|
priority?: number;
|
|
8
9
|
resources?: ResourceProps[];
|
|
9
10
|
routes?: ReactElement;
|
|
11
|
+
appRoutes?: AppRouteDefinition[];
|
|
10
12
|
dev?: {
|
|
11
13
|
resources?: ResourceProps[];
|
|
12
14
|
routes?: ReactElement;
|
package/src/app/extensions.tsx
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
} from "react";
|
|
7
7
|
import type { AppExtension } from "./extension";
|
|
8
8
|
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
9
|
+
import { appRoutes, registryRoutesEnabled } from "@/routes";
|
|
9
10
|
import { createDevelopmentRoute } from "./development";
|
|
11
|
+
import { buildRouteResources, renderAppRoutes } from "./route-runtime";
|
|
10
12
|
|
|
11
13
|
const extensionModules = import.meta.glob<{ default: AppExtension }>(
|
|
12
14
|
"@/extensions/*/extension.tsx",
|
|
@@ -21,13 +23,23 @@ export const appExtensions = Object.values(extensionModules)
|
|
|
21
23
|
left.id.localeCompare(right.id)
|
|
22
24
|
);
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const routeExtensions = registryRoutesEnabled ? appExtensions : [];
|
|
27
|
+
const routeDefinitions = [
|
|
28
|
+
...appRoutes,
|
|
29
|
+
...routeExtensions.flatMap((extension) => extension.appRoutes ?? []),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const configuredResources = [
|
|
33
|
+
...buildRouteResources(routeDefinitions),
|
|
34
|
+
...routeExtensions.flatMap((extension) => extension.resources ?? []),
|
|
35
|
+
];
|
|
27
36
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
export const configuredRouteElements = [
|
|
38
|
+
...renderAppRoutes(routeDefinitions),
|
|
39
|
+
...routeExtensions
|
|
40
|
+
.map((extension) => extension.routes)
|
|
41
|
+
.filter((routes): routes is ReactElement => Boolean(routes)),
|
|
42
|
+
];
|
|
31
43
|
|
|
32
44
|
export const extensionStandaloneRouteElements = import.meta.env.DEV
|
|
33
45
|
? [createDevelopmentRoute(appExtensions)]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PropsWithChildren, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { AccessDenied } from "@/components/access-control/access-denied";
|
|
4
|
+
import { CanAccess } from "@/components/access-control/can-access";
|
|
5
|
+
import type { RouteAccessConstraint } from "@/lib/nocobase/acl";
|
|
6
|
+
|
|
7
|
+
export function RouteAccessGuard({
|
|
8
|
+
access,
|
|
9
|
+
children,
|
|
10
|
+
fallback = <AccessDenied />,
|
|
11
|
+
}: PropsWithChildren<{
|
|
12
|
+
access?: RouteAccessConstraint;
|
|
13
|
+
fallback?: ReactNode;
|
|
14
|
+
}>) {
|
|
15
|
+
if (!access) return children;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<CanAccess roles={access.roles} fallback={fallback}>
|
|
19
|
+
{children}
|
|
20
|
+
</CanAccess>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ResourceProps } from "@refinedev/core";
|
|
2
|
+
import { createElement, type ReactElement, type ReactNode } from "react";
|
|
3
|
+
import { Outlet, Route } from "react-router";
|
|
4
|
+
|
|
5
|
+
import type { ResourceAcl, RouteAccessConstraint } from "@/lib/nocobase/acl";
|
|
6
|
+
import { RouteAccessGuard } from "./route-access-guard";
|
|
7
|
+
|
|
8
|
+
export { RouteAccessGuard } from "./route-access-guard";
|
|
9
|
+
|
|
10
|
+
type ResourceRouteAction = "create" | "edit" | "show";
|
|
11
|
+
|
|
12
|
+
export type AppRouteResource = Omit<
|
|
13
|
+
ResourceProps,
|
|
14
|
+
"name" | "list" | ResourceRouteAction
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
type AppRouteBase = {
|
|
18
|
+
name: string;
|
|
19
|
+
element?: ReactNode;
|
|
20
|
+
access?: RouteAccessConstraint;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type AppRouteResourceBinding =
|
|
24
|
+
| {
|
|
25
|
+
resource: AppRouteResource;
|
|
26
|
+
resourceAction?: never;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
resource?: never;
|
|
30
|
+
resourceAction: ResourceRouteAction;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
resource?: never;
|
|
34
|
+
resourceAction?: never;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type AppIndexRouteDefinition = AppRouteBase &
|
|
38
|
+
AppRouteResourceBinding & {
|
|
39
|
+
index: true;
|
|
40
|
+
path?: never;
|
|
41
|
+
children?: never;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type AppPathRouteDefinition = AppRouteBase &
|
|
45
|
+
AppRouteResourceBinding & {
|
|
46
|
+
index?: false;
|
|
47
|
+
path?: string;
|
|
48
|
+
children?: AppRouteDefinition[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type AppRouteDefinition =
|
|
52
|
+
| AppIndexRouteDefinition
|
|
53
|
+
| AppPathRouteDefinition;
|
|
54
|
+
|
|
55
|
+
export function defineAppRoutes<const Routes extends AppRouteDefinition[]>(
|
|
56
|
+
routes: Routes
|
|
57
|
+
) {
|
|
58
|
+
return routes;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const joinRoutePath = (parentPath: string, path?: string) => {
|
|
62
|
+
if (!path) return parentPath || "/";
|
|
63
|
+
if (path.startsWith("/")) return path;
|
|
64
|
+
return `${parentPath.replace(/\/$/, "")}/${path}`.replace(/\/+/g, "/");
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getResourceActionRoutes = (
|
|
68
|
+
routes: AppRouteDefinition[],
|
|
69
|
+
parentPath: string,
|
|
70
|
+
resourceName: string,
|
|
71
|
+
actions: Partial<Record<ResourceRouteAction, string>> = {}
|
|
72
|
+
): Partial<Record<ResourceRouteAction, string>> => {
|
|
73
|
+
for (const route of routes) {
|
|
74
|
+
if (route.resource) continue;
|
|
75
|
+
const fullPath = joinRoutePath(parentPath, route.path);
|
|
76
|
+
if (route.resourceAction) {
|
|
77
|
+
if (actions[route.resourceAction] !== undefined) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Resource "${resourceName}" declares multiple ${route.resourceAction} routes.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
actions[route.resourceAction] = fullPath;
|
|
83
|
+
}
|
|
84
|
+
getResourceActionRoutes(
|
|
85
|
+
route.children ?? [],
|
|
86
|
+
fullPath,
|
|
87
|
+
resourceName,
|
|
88
|
+
actions
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return actions;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getDefaultResourceAcl = (meta?: ResourceProps["meta"]): ResourceAcl =>
|
|
96
|
+
typeof meta?.acl === "undefined"
|
|
97
|
+
? { type: "authenticated" }
|
|
98
|
+
: (meta.acl as ResourceAcl);
|
|
99
|
+
|
|
100
|
+
export function buildRouteResources(
|
|
101
|
+
routes: AppRouteDefinition[],
|
|
102
|
+
parentPath = "",
|
|
103
|
+
parentResource?: string,
|
|
104
|
+
inheritedAccess: RouteAccessConstraint[] = []
|
|
105
|
+
): ResourceProps[] {
|
|
106
|
+
return routes.flatMap((route) => {
|
|
107
|
+
if (route.resourceAction && !parentResource) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Route "${route.name}" declares resourceAction without a parent resource.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const fullPath = joinRoutePath(parentPath, route.path);
|
|
114
|
+
const routeAccess = route.access
|
|
115
|
+
? [...inheritedAccess, route.access]
|
|
116
|
+
: inheritedAccess;
|
|
117
|
+
const resource = route.resource
|
|
118
|
+
? {
|
|
119
|
+
name: route.name,
|
|
120
|
+
list: route.index
|
|
121
|
+
? parentPath || "/"
|
|
122
|
+
: route.path
|
|
123
|
+
? fullPath
|
|
124
|
+
: undefined,
|
|
125
|
+
...getResourceActionRoutes(
|
|
126
|
+
route.children ?? [],
|
|
127
|
+
fullPath,
|
|
128
|
+
route.name
|
|
129
|
+
),
|
|
130
|
+
...route.resource,
|
|
131
|
+
meta: {
|
|
132
|
+
...route.resource.meta,
|
|
133
|
+
parent: route.resource.meta?.parent ?? parentResource,
|
|
134
|
+
acl: getDefaultResourceAcl(route.resource.meta),
|
|
135
|
+
routeAccess: routeAccess.length ? routeAccess : undefined,
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
: undefined;
|
|
139
|
+
const childResources = buildRouteResources(
|
|
140
|
+
route.children ?? [],
|
|
141
|
+
fullPath,
|
|
142
|
+
resource ? route.name : parentResource,
|
|
143
|
+
routeAccess
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return resource ? [resource, ...childResources] : childResources;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function renderAppRoutes(routes: AppRouteDefinition[]): ReactElement[] {
|
|
151
|
+
return routes.map((route) => {
|
|
152
|
+
const content = route.element ?? createElement(Outlet);
|
|
153
|
+
const element = route.access
|
|
154
|
+
? createElement(RouteAccessGuard, { access: route.access }, content)
|
|
155
|
+
: content;
|
|
156
|
+
|
|
157
|
+
return route.index
|
|
158
|
+
? createElement(Route, {
|
|
159
|
+
key: route.name,
|
|
160
|
+
index: true,
|
|
161
|
+
element,
|
|
162
|
+
})
|
|
163
|
+
: createElement(
|
|
164
|
+
Route,
|
|
165
|
+
{
|
|
166
|
+
key: route.name,
|
|
167
|
+
path: route.path,
|
|
168
|
+
element,
|
|
169
|
+
},
|
|
170
|
+
renderAppRoutes(route.children ?? [])
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
}
|
package/src/app/routes.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { Login } from "@/pages/login";
|
|
|
11
11
|
import { Register } from "@/pages/register";
|
|
12
12
|
import {
|
|
13
13
|
AppExtensionProviders,
|
|
14
|
-
|
|
14
|
+
configuredRouteElements,
|
|
15
15
|
extensionStandaloneRouteElements,
|
|
16
16
|
} from "./extensions";
|
|
17
17
|
|
|
@@ -41,7 +41,7 @@ export function AppRoutes() {
|
|
|
41
41
|
}
|
|
42
42
|
>
|
|
43
43
|
<Route index element={<NavigateToAccessibleResource />} />
|
|
44
|
-
{
|
|
44
|
+
{configuredRouteElements}
|
|
45
45
|
<Route path="*" element={<ErrorComponent />} />
|
|
46
46
|
</Route>
|
|
47
47
|
</Route>
|
|
@@ -11,13 +11,31 @@ import {
|
|
|
11
11
|
} from "@/components/ui/dropdown-menu";
|
|
12
12
|
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
13
13
|
import { ThemeToggle } from "@/components/theme/theme-toggle";
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import {
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipContent,
|
|
18
|
+
TooltipTrigger,
|
|
19
|
+
} from "@/components/ui/tooltip";
|
|
14
20
|
import { UserAvatar } from "@/components/app-shell/user-avatar";
|
|
15
21
|
import { UserInfo } from "@/components/app-shell/user-info";
|
|
22
|
+
import { CanAccess } from "@/components/access-control/can-access";
|
|
16
23
|
import { useSidebar, SidebarTrigger } from "@/components/ui/sidebar";
|
|
17
|
-
import { LogOutIcon } from "lucide-react";
|
|
24
|
+
import { LogOutIcon, SettingsIcon } from "lucide-react";
|
|
18
25
|
import { cn } from "@/lib/utils";
|
|
19
26
|
import { Brand } from "@/components/app-shell/brand";
|
|
20
27
|
import { extensionUserMenuItems } from "@/app/extensions";
|
|
28
|
+
import { resolveNocoBaseServerUrl } from "@/providers/runtime-config";
|
|
29
|
+
|
|
30
|
+
const pluginSettingsResource = {
|
|
31
|
+
name: "plugin-settings",
|
|
32
|
+
meta: {
|
|
33
|
+
acl: {
|
|
34
|
+
type: "snippet",
|
|
35
|
+
name: "pm.*",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
} as const;
|
|
21
39
|
|
|
22
40
|
export const Header = () => {
|
|
23
41
|
const { isMobile } = useSidebar();
|
|
@@ -55,6 +73,7 @@ function DesktopHeader() {
|
|
|
55
73
|
</span>
|
|
56
74
|
</div>
|
|
57
75
|
<div className="flex items-center gap-2">
|
|
76
|
+
<SettingsLink />
|
|
58
77
|
<ThemeToggle />
|
|
59
78
|
<UserDropdown />
|
|
60
79
|
</div>
|
|
@@ -92,6 +111,7 @@ function MobileHeader() {
|
|
|
92
111
|
/>
|
|
93
112
|
<Brand logoClassName="h-6" />
|
|
94
113
|
<div className="flex shrink-0 items-center gap-1">
|
|
114
|
+
<SettingsLink className="size-9" />
|
|
95
115
|
<ThemeToggle className="size-9" />
|
|
96
116
|
<UserDropdown />
|
|
97
117
|
</div>
|
|
@@ -99,6 +119,41 @@ function MobileHeader() {
|
|
|
99
119
|
);
|
|
100
120
|
}
|
|
101
121
|
|
|
122
|
+
function SettingsLink({ className }: { className?: string }) {
|
|
123
|
+
const translate = useTranslate();
|
|
124
|
+
const label = translate("shell.settings", "System settings");
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<CanAccess resourceItem={pluginSettingsResource}>
|
|
128
|
+
<Tooltip>
|
|
129
|
+
<TooltipTrigger
|
|
130
|
+
render={
|
|
131
|
+
<Button
|
|
132
|
+
render={
|
|
133
|
+
<a
|
|
134
|
+
href={resolveNocoBaseServerUrl("/settings")}
|
|
135
|
+
target="_blank"
|
|
136
|
+
rel="noopener noreferrer"
|
|
137
|
+
/>
|
|
138
|
+
}
|
|
139
|
+
variant="outline"
|
|
140
|
+
size="icon"
|
|
141
|
+
className={cn(
|
|
142
|
+
"size-10 rounded-xl border-border/70 bg-background/60",
|
|
143
|
+
className
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
<SettingsIcon />
|
|
147
|
+
<span className="sr-only">{label}</span>
|
|
148
|
+
</Button>
|
|
149
|
+
}
|
|
150
|
+
/>
|
|
151
|
+
<TooltipContent>{label}</TooltipContent>
|
|
152
|
+
</Tooltip>
|
|
153
|
+
</CanAccess>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
102
157
|
const UserDropdown = () => {
|
|
103
158
|
const { mutate: logout, isPending: isLoggingOut } = useLogout();
|
|
104
159
|
const translate = useTranslate();
|
|
@@ -294,6 +294,7 @@ function SidebarFooter() {
|
|
|
294
294
|
return (
|
|
295
295
|
<ShadcnSidebarFooter className="border-t border-sidebar-border/70 p-0">
|
|
296
296
|
<div
|
|
297
|
+
title={`${__PORTAL_TEMPLATE_NAME__} v${__PORTAL_TEMPLATE_VERSION__}`}
|
|
297
298
|
className={cn(
|
|
298
299
|
"flex min-h-16 items-center",
|
|
299
300
|
open ? "gap-3 px-5 py-3" : "justify-center px-2"
|
|
@@ -306,11 +307,22 @@ function SidebarFooter() {
|
|
|
306
307
|
{translate("shell.footer.freedom", "AI builds freely.")}
|
|
307
308
|
</div>
|
|
308
309
|
<div className="text-muted-foreground">
|
|
310
|
+
<a
|
|
311
|
+
href="https://nocobase.com"
|
|
312
|
+
target="_blank"
|
|
313
|
+
rel="noopener noreferrer"
|
|
314
|
+
className="font-medium text-sidebar-foreground hover:underline"
|
|
315
|
+
>
|
|
316
|
+
NocoBase
|
|
317
|
+
</a>{" "}
|
|
309
318
|
{translate(
|
|
310
|
-
"shell.footer.
|
|
311
|
-
"
|
|
319
|
+
"shell.footer.reliabilitySuffix",
|
|
320
|
+
"keeps it reliable."
|
|
312
321
|
)}
|
|
313
322
|
</div>
|
|
323
|
+
<div className="mt-1 font-mono text-[10px] text-muted-foreground/70">
|
|
324
|
+
{__PORTAL_TEMPLATE_NAME__} v{__PORTAL_TEMPLATE_VERSION__}
|
|
325
|
+
</div>
|
|
314
326
|
</div>
|
|
315
327
|
)}
|
|
316
328
|
</div>
|
|
@@ -12,11 +12,17 @@ After installation, import the components from `@/extensions/nocobase-acl`.
|
|
|
12
12
|
normal roles, Anonymous when enabled, and Full permissions through the
|
|
13
13
|
`__union__` role when the system role mode allows it.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
`allOf`, and `noneOf`.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Application route definitions and `AclPage` accept a `roles` constraint with
|
|
16
|
+
`anyOf`, `allOf`, and `noneOf`. Put route-level constraints in `access.roles`
|
|
17
|
+
through `defineAppRoutes`; the Starter applies the same constraint to menu
|
|
18
|
+
visibility and direct URL access. Role constraints use the roles in the current
|
|
19
|
+
effective ACL context. In union mode this is the set of roles participating in
|
|
20
|
+
the union; in single-role mode it is only the selected role. Regions, fields,
|
|
21
|
+
and actions continue to use resource permissions.
|
|
22
|
+
|
|
23
|
+
Nested routes inherit their parents' route access. The generated navigation
|
|
24
|
+
item evaluates every constraint in that route chain, matching the nested route
|
|
25
|
+
guards used for direct URL access.
|
|
20
26
|
|
|
21
27
|
Use the Starter's `useGetRoles()` hook when application UI needs the same
|
|
22
28
|
effective role names. Its result follows the familiar Refine query shape with
|
|
@@ -35,15 +41,23 @@ function CurrentAccessContext() {
|
|
|
35
41
|
```
|
|
36
42
|
|
|
37
43
|
```tsx
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
import { defineAppRoutes } from "@/app/route-runtime";
|
|
45
|
+
|
|
46
|
+
export const appRoutes = defineAppRoutes([
|
|
47
|
+
{
|
|
48
|
+
name: "administration",
|
|
49
|
+
path: "/administration",
|
|
50
|
+
element: <AdministrationPage />,
|
|
51
|
+
resource: {
|
|
52
|
+
meta: {
|
|
53
|
+
label: "Administration",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
access: {
|
|
43
57
|
roles: { anyOf: ["admin"] },
|
|
44
58
|
},
|
|
45
59
|
},
|
|
46
|
-
|
|
60
|
+
]);
|
|
47
61
|
|
|
48
62
|
function AuditLogsRoute() {
|
|
49
63
|
return (
|
|
@@ -56,9 +70,9 @@ function AuditLogsRoute() {
|
|
|
56
70
|
}
|
|
57
71
|
```
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
the `collection`, `snippet`, and `route` ACL metadata variants when
|
|
61
|
-
should participate in NocoBase permission checks.
|
|
73
|
+
The route runtime marks generated non-collection resources as authenticated.
|
|
74
|
+
Use the `collection`, `snippet`, and `route` ACL metadata variants when a
|
|
75
|
+
resource should also participate in NocoBase permission checks.
|
|
62
76
|
|
|
63
77
|
The underlying `accessControlProvider`, `roles:check` cache, record permissions,
|
|
64
78
|
and Refine integration are provided by the compatible Starter.
|