@nocobase/portal-template-default 1.0.16 → 1.0.18
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/.env.example +0 -4
- package/AGENTS.md +10 -0
- package/README.MD +4 -0
- package/dist/assets/{LineDraw-vPRTWiQ5.js → LineDraw-B3HsIvjF.js} +1 -1
- package/dist/assets/{SymbolDraw-DOl_Tss3.js → SymbolDraw-C5oA6gYo.js} +1 -1
- package/dist/assets/{VisualMapping-j08W3KBm.js → VisualMapping-CjyVXYl-.js} +1 -1
- package/dist/assets/auth-method-demo-B4sxmO2B.js +1 -0
- package/dist/assets/auto-login-provider-DPtJmWC-.js +1 -0
- package/dist/assets/{auto-login-provider-Bukw7zEe.js → auto-login-provider-pxf3kSYY.js} +2 -2
- package/dist/assets/auto-redirect-provider-B51i6R_U.js +1 -0
- package/dist/assets/auto-redirect-provider-CDA_97es.js +1 -0
- package/dist/assets/auto-redirect-provider-CeJOAYfx.js +1 -0
- package/dist/assets/calendar-B41MfAkB.js +1 -0
- package/dist/assets/{cas-sign-in-button-BQn3Mttx.js → cas-sign-in-button-DmkMYnGb.js} +1 -1
- package/dist/assets/{components-BT5mL9gW.js → components-DLLYm5Qd.js} +3 -3
- package/dist/assets/{configuration-gate-BJoadoA6.js → configuration-gate-CdktspSj.js} +1 -1
- package/dist/assets/{createClipPathFromCoordSys-m8IbnJ1z.js → createClipPathFromCoordSys-BKzf1iWj.js} +1 -1
- package/dist/assets/createSeriesDataSimply-Dcpkokbc.js +1 -0
- package/dist/assets/{customGraphicKeyframeAnimation-CYLPurwK.js → customGraphicKeyframeAnimation-BYcy-PGj.js} +1 -1
- package/dist/assets/demo-BpC2e3zt.js +1 -0
- package/dist/assets/demo-CEA7jJM2.js +1 -0
- package/dist/assets/demo-CX_Zf30k.js +1 -0
- package/dist/assets/demo-CyvGiwq2.js +1 -0
- package/dist/assets/demo-DJgGXOii.js +1 -0
- package/dist/assets/demo-GTMcYVzl.js +1 -0
- package/dist/assets/demo-O2QwJ70o.js +1 -0
- package/dist/assets/{dingtalk-sign-in-button-DSluFaP9.js → dingtalk-sign-in-button-K_2ZYQNC.js} +1 -1
- package/dist/assets/{echarts-preview-mRXoEciY.js → echarts-preview-CTj0x24f.js} +1 -1
- package/dist/assets/{echarts-runtime-CvLr4Slh.js → echarts-runtime-BTfAtt-4.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-BC1tvfQ0.js → echarts-runtime-advanced-Xg4WXsLl.js} +1 -1
- package/dist/assets/{echarts-runtime-common-DhNaV5B8.js → echarts-runtime-common-Dfe5ONoi.js} +1 -1
- package/dist/assets/{echarts-runtime-components-BiioS8t1.js → echarts-runtime-components-BNoMvPY0.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-CAWqYvFh.js → echarts-runtime-hierarchy-bEvX8V8l.js} +1 -1
- package/dist/assets/{floating-RVbHvIiD.js → floating-CVEm6jgD.js} +2 -2
- package/dist/assets/index-0O4dSmNz.js +59 -0
- package/dist/assets/index-BBKV6O5d.css +1 -0
- package/dist/assets/{index-BysibBZu.js → index-Bliz6Sy2.js} +2 -2
- package/dist/assets/index-CMoFG_fD.js +1064 -0
- package/dist/assets/{index-Dw20Lzpp.js → index-N5q0Lo-n.js} +3 -3
- package/dist/assets/{index-C-tdHirk.js → index-dDkRfkPI.js} +1 -1
- package/dist/assets/index-s-Gc7krA.js +38 -0
- package/dist/assets/{labelHelper-DbITmOUW.js → labelHelper-CSRwI3Ku.js} +1 -1
- package/dist/assets/ldap-sign-in-form-BoVMVmwm.js +1 -0
- package/dist/assets/{oidc-sign-in-button-Cgpa8Qzj.js → oidc-sign-in-button-5pd4bZSW.js} +1 -1
- package/dist/assets/{page-context-AU6RU0Qi.js → page-context-o6ISleo3.js} +4 -4
- package/dist/assets/prompt-card-xoH-b2hL.js +1 -0
- package/dist/assets/prompt-output-7ZS2-d1_.js +1 -0
- package/dist/assets/saml-sign-in-button-CN2l8pSN.js +1 -0
- package/dist/assets/{sectorHelper-C6L782Ue.js → sectorHelper-D1jHcZDN.js} +1 -1
- package/dist/assets/shortcut-DUGrhOUX.js +47 -0
- package/dist/assets/{sms-sign-in-form-Bo2WLZuP.js → sms-sign-in-form-ajsTToFF.js} +1 -1
- package/dist/assets/{static.browser-CoPxsICt.js → static.browser-exfE5oYi.js} +1 -1
- package/dist/assets/switch-BVTKHDeA.js +1 -0
- package/dist/assets/{tool-cards-ipgJ_3Ec.js → tool-cards-CQklNnUU.js} +3 -3
- package/dist/assets/{use-authenticator-sign-in-BzFeh7qe.js → use-authenticator-sign-in-B8OPLh-w.js} +1 -1
- package/dist/assets/use-dingtalk-sign-in-DsX00uPU.js +1 -0
- package/dist/assets/{use-wecom-sign-in-PPIMLvGa.js → use-wecom-sign-in-Di3WFrU1.js} +1 -1
- package/dist/assets/{wecom-sign-in-button-rUq9RW8R.js → wecom-sign-in-button-BO8h0tS8.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 +200 -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/components/data-table/data-table-filter.tsx +7 -4
- 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 +125 -0
- package/src/extensions/nocobase-users-example/extension.tsx +2 -100
- package/src/extensions/nocobase-users-example/index.ts +1 -1
- 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/src/extensions/nocobase-users-example/layout.tsx +0 -16
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as e,
|
|
1
|
+
import{j as e,ae as o,af as l,b as c,a6 as h,B as d,C as m,ag as p,ac as g,$ as x,ad as u}from"./index-CMoFG_fD.js";import{P as f}from"./prompt-card-xoH-b2hL.js";import{A as j}from"./configuration-gate-CdktspSj.js";import"./prompt-output-7ZS2-d1_.js";const b=`Add the standard NocoBase AI floating chat entry to the application layout.
|
|
2
2
|
|
|
3
3
|
Requirements:
|
|
4
4
|
- Render AIChatFloatingTrigger at the lower-right edge of the application.
|
|
@@ -12,4 +12,4 @@ Requirements:
|
|
|
12
12
|
- Put ChatSurfaceActions in the AIChatWindow header so expand/collapse and close stay in the upper-right corner.
|
|
13
13
|
- Keep one AIChatWindow mounted and change ChatSurface.variant to "dialog" when expanded.
|
|
14
14
|
- Keep the trigger and ChatSurface separate from AIChatWindow.
|
|
15
|
-
- Use shadcn/Base UI components and the existing AIProvider/AIChatProvider runtime.`;function
|
|
15
|
+
- Use shadcn/Base UI components and the existing AIProvider/AIChatProvider runtime.`;function I(){return e.jsx(j,{children:e.jsx(v,{})})}function v(){const a=o(),{open:s}=l(a),[t,n]=c.useState(!1),i=r=>{r||n(!1),a.setOpen(r)};return e.jsx(h,{id:"floating-chat-demo",controller:a,children:e.jsxs("div",{"data-open":s&&!t,"data-side":"right",className:"chat-side-panel-layout @container min-w-0",style:{"--chat-side-panel-width":"450px"},children:[e.jsxs("div",{className:"space-y-12 pb-12",children:[e.jsx("section",{className:"flex flex-wrap items-start justify-between gap-5 border-b pb-8",children:e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(d,{variant:"secondary",children:"AI Components"}),e.jsx(d,{variant:"outline",children:"Global entry"})]}),e.jsx("h1",{className:"mt-4 text-3xl font-semibold tracking-[-0.035em]",children:"Floating AI Chat"}),e.jsx("p",{className:"mt-3 max-w-3xl text-sm leading-6 text-muted-foreground",children:"A global lower-right entry for the shared AI conversation. It opens as a push side panel and can expand into a focused dialog without remounting the chat window."})]})}),e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Interactive preview"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Global launcher and adaptive chat surface"}),e.jsx("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:"The trigger is constrained to this simulated application in the demo. In production its default positioning is fixed to the viewport."})]}),e.jsxs(m,{className:"gap-0 overflow-hidden py-0",children:[e.jsxs("div",{className:"flex h-12 items-center justify-between border-b px-4",children:[e.jsx("span",{className:"text-sm font-medium",children:"Application shell"}),e.jsx("code",{className:"rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground",children:"AIChatFloatingTrigger → ChatSurface · side-panel/dialog"})]}),e.jsxs("div",{className:"relative grid min-h-[560px] grid-cols-[190px_minmax(0,1fr)] overflow-hidden bg-background",children:[e.jsxs("div",{className:"border-r bg-muted/25 p-4",children:[e.jsx("div",{className:"h-7 w-28 rounded bg-muted"}),e.jsxs("div",{className:"mt-8 space-y-3",children:[e.jsx("div",{className:"h-9 rounded-lg bg-muted"}),e.jsx("div",{className:"h-9 rounded-lg bg-muted/65"}),e.jsx("div",{className:"h-9 rounded-lg bg-muted/65"})]})]}),e.jsxs("div",{className:"p-6",children:[e.jsxs("div",{className:"grid gap-4 sm:grid-cols-3",children:[e.jsx("div",{className:"h-28 rounded-xl border bg-muted/15"}),e.jsx("div",{className:"h-28 rounded-xl border bg-muted/15"}),e.jsx("div",{className:"h-28 rounded-xl border bg-muted/15"})]}),e.jsx("div",{className:"mt-5 h-72 rounded-xl border bg-muted/10"})]}),e.jsx(p,{controller:a,unreadCount:3,position:"absolute"})]})]})]}),e.jsxs("section",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Floating chat prompt"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Generate the global launcher separately"})]}),e.jsx(f,{title:"Add a lower-right AI floating entry",description:"This prompt configures the global trigger and its switchable side-panel/dialog surface.",prompt:b})]})]}),e.jsx(g,{open:s,variant:t?"dialog":"side-panel",onOpenChange:i,width:450,children:e.jsx(x,{headerActions:e.jsx(u,{expanded:t,onExpandedChange:n,onClose:()=>i(!1)})})})]})})}export{I as FloatingChatPage};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import{y as S,z as U,j as e,A as E,b as A,D as Q,C as c,p as X,q as Y,t as T,v as w,w as Z,x as v,E as ee,F as se,k as u,l as h,m as P,n as p,o as D,I as k,T as te,B as F,G as re,a as ne,H as ae,R as ie,U as M,J as y,K as m,P as oe,M as le,N as de,O as ce,Q as me,V as ue}from"./index-CMoFG_fD.js";import{P as he}from"./prompt-output-7ZS2-d1_.js";/**
|
|
2
|
+
* @license lucide-react v0.487.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const pe=[["rect",{width:"18",height:"7",x:"3",y:"3",rx:"1",key:"f1a2em"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1",key:"1bb6yr"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1",key:"nxv5o0"}]],_=S("layout-panel-top",pe);/**
|
|
7
|
+
* @license lucide-react v0.487.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const xe=[["circle",{cx:"12",cy:"16",r:"1",key:"1au0dj"}],["rect",{x:"3",y:"10",width:"18",height:"12",rx:"2",key:"6s8ecr"}],["path",{d:"M7 10V7a5 5 0 0 1 10 0v3",key:"1pqi11"}]],G=S("lock-keyhole",xe);/**
|
|
12
|
+
* @license lucide-react v0.487.0 - ISC
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the ISC license.
|
|
15
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
+
*/const fe=[["path",{d:"M2 21a8 8 0 0 1 10.434-7.62",key:"1yezr2"}],["circle",{cx:"10",cy:"8",r:"5",key:"o932ke"}],["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["path",{d:"m19.5 14.3-.4.9",key:"1eb35c"}],["path",{d:"m16.9 20.8-.4.9",key:"dfjc4z"}],["path",{d:"m21.7 19.5-.9-.4",key:"q4dx6b"}],["path",{d:"m15.2 16.9-.9-.4",key:"1r0w5f"}],["path",{d:"m21.7 16.5-.9.4",key:"1knoei"}],["path",{d:"m15.2 19.1-.9.4",key:"j188fs"}],["path",{d:"m19.5 21.7-.4-.9",key:"1tonu5"}],["path",{d:"m16.9 15.2-.4-.9",key:"699xu"}]],ge=S("user-round-cog",fe),B=s=>({resource:s.resource,action:s.action,id:s.id,field:s.field,dataSourceKey:s.dataSourceKey});function je({children:s,anyOf:t,allOf:r,roles:a,fallback:n=e.jsx(E,{})}){const i=U(),o=!(t!=null&&t.length)||t.some(f=>i(B(f))),x=!(r!=null&&r.length)||r.every(f=>i(B(f)));return i({roles:a})&&o&&x?s:n}function L({children:s,resource:t,action:r,id:a,dataSourceKey:n,fallback:i="hidden"}){return U()({resource:t,action:r,id:a,dataSourceKey:n})?s:i==="hidden"?null:i==="forbidden"?e.jsx(E,{className:"min-h-40"}):i}function C({children:s,resource:t,action:r,field:a,dataSourceKey:n,fallback:i=null}){return U()({resource:t,action:r,field:a,dataSourceKey:n})?s:i}function d({children:s,roles:t,permissions:r={},recordPermissions:a=[]}){const n=A.useMemo(()=>{const i=Array.from(new Set(Object.keys(r).map(l=>l.slice(0,l.lastIndexOf(":"))))),o={status:"ready",permissions:{currentRole:t.length>1?"__union__":t[0],roles:t,roleMode:t.length>1?"allow-use-union":"default",resources:i,actions:r,actionAlias:{list:"list",get:"get",create:"create",update:"update",destroy:"destroy"},snippets:[]}},x=new Map(a.map(l=>[`${l.dataSourceKey??"main"}:${l.resource}:${l.action}:${l.id}`,l.allowed]));return{getState:()=>o,subscribe:()=>()=>{},load:async()=>o,retry:async()=>o,clear:()=>{},recordPermissions:{getState:()=>x,subscribe:()=>()=>{},getPermission:({dataSourceKey:l="main",resource:f,action:H,id:J})=>x.get(`${l}:${f}:${H}:${J}`)}}},[r,a,t]);return e.jsx(Q,{store:n,children:s})}const be=[["roles","RoleConstraint","undefined","Checks the current effective roles with anyOf, allOf, and noneOf."],["resource / action","string","undefined","Checks a NocoBase resource action. When roles are also present, both checks must pass."],["id / field","BaseKey / string","undefined","Optionally narrows the decision to one record or field."],["fallback","ReactNode","null","Renders when the access request is denied."]],ye=[["anyOf","AclPermission[]","undefined","Allows the page when any listed permission is available."],["allOf","AclPermission[]","undefined","Requires every listed permission before rendering the page."],["roles","RoleConstraint","undefined","Requires the current effective roles to match anyOf, allOf, and noneOf constraints."],["fallback","ReactNode","AccessDenied","Replaces the complete page when its permission test fails."],["children","ReactNode","required","The protected page content."]],we=[["resource","string","required","NocoBase collection or ACL resource name."],["action","string","required","Resource action such as list, show, create, edit, or delete."],["id","BaseKey","undefined","Optional record ID for record-scoped permission checks."],["dataSourceKey","string","undefined","Selects a non-main NocoBase data source."],["fallback",'"hidden" | "forbidden" | ReactNode','"hidden"',"Hides the region or replaces it with a local denied state."],["children","ReactNode","required","The protected page region."]],ve=[["resource","string","required","NocoBase collection containing the field."],["action","string","required","The action whose field whitelist should be checked."],["field","string","required","Exact NocoBase field name."],["dataSourceKey","string","undefined","Selects a non-main NocoBase data source."],["fallback","ReactNode","null","Optional read-only replacement for a denied field."],["children","ReactNode","required","The protected field or field group."]],Ne=[["AclStoreProvider","{ store, children }","required","Connects React ACL hooks and components to the application aclStore. The Starter mounts it at the application root."],["useGetRoles","() => QueryResult<string[]>","-","Returns the current effective ACL role names, including all participating roles in union mode."]];function Ae(){return e.jsxs("section",{className:"space-y-5 border-t pt-8",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground",children:"Permission boundaries"}),e.jsx("h2",{className:"mt-2 text-xl font-semibold tracking-tight",children:"Components behind the patterns"}),e.jsx("p",{className:"mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground",children:"Use the smallest boundary that matches the intended user experience: complete page, independent region, or individual field."})]}),e.jsxs("div",{className:"space-y-7",children:[e.jsx(g,{title:"CanAccess",description:"The Starter access boundary for role-only, resource-only, or combined permission checks.",rows:be}),e.jsx(g,{title:"AclPage",description:"Protects a complete route or business page. This corresponds to the Page permission pattern above.",rows:ye}),e.jsx(g,{title:"AclRegion",description:"Protects one independent area inside a larger page. This corresponds to the Region permission pattern.",rows:we}),e.jsx(g,{title:"AclField",description:"Protects one field or field group. This corresponds to the Field permission pattern.",rows:ve}),e.jsx(g,{title:"ACL context APIs",description:"Read the active role context from the application ACL store. Demo previews use an isolated in-memory store with the same evaluator.",rows:Ne})]})]})}function g({title:s,description:t,rows:r}){return e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-semibold",children:s}),e.jsx("p",{className:"mt-1 text-sm text-muted-foreground",children:t})]}),e.jsx(c,{className:"gap-0 overflow-hidden py-0",children:e.jsxs(X,{children:[e.jsx(Y,{children:e.jsxs(T,{children:[e.jsx(w,{children:"Prop"}),e.jsx(w,{children:"Type"}),e.jsx(w,{children:"Default"}),e.jsx(w,{children:"Description"})]})}),e.jsx(Z,{children:r.map(([a,n,i,o])=>e.jsxs(T,{children:[e.jsx(v,{className:"font-mono text-xs font-medium",children:a}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:n}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:i}),e.jsx(v,{className:"min-w-72 whitespace-normal text-muted-foreground",children:o})]},a))})]})})]})}function ke({config:s}){const[t,r]=A.useState(s.defaultScene),[a,n]=A.useState(s.defaultTarget),i=A.useMemo(()=>`Build a complete NocoBase ACL scene in the NocoBase Admin Starter.
|
|
17
|
+
|
|
18
|
+
Business scene
|
|
19
|
+
- Page or feature: ${t}
|
|
20
|
+
- Permission target: ${a}
|
|
21
|
+
- Use realistic fixed sample content so the permission behavior is visible immediately.
|
|
22
|
+
- Build the complete page section and user flow, not an isolated ACL snippet.
|
|
23
|
+
|
|
24
|
+
ACL scenario
|
|
25
|
+
${s.requirements}
|
|
26
|
+
|
|
27
|
+
Implementation contract
|
|
28
|
+
- Use the Starter's built-in NocoBase accessControlProvider and ACL store.
|
|
29
|
+
- NocoBase roles:check is the source of truth. Do not create a second permission store. Use configured role names only when the requirement explicitly includes a role constraint.
|
|
30
|
+
- Import the Starter CanAccess from @/components/access-control/can-access. Use it with AclPage, AclRegion, and AclField for permission presentation. Do not copy their checks into local booleans or role comparisons.
|
|
31
|
+
- For route-level role access, define the route once with defineAppRoutes in src/routes.tsx and put the constraint in access.roles. Let the route runtime control both its menu item and direct URL.
|
|
32
|
+
- Use useGetRoles only when UI needs to display or pass through the current effective ACL roles; it is not a replacement permission evaluator.
|
|
33
|
+
- Keep backend ACL enforcement in place; frontend checks only control presentation and navigation.
|
|
34
|
+
- Import reusable components from the installed local entry point at @/extensions/nocobase-acl when page composition is needed.
|
|
35
|
+
- Resource actions use the application names list, show, create, edit, and delete. Let the Starter map them to NocoBase list/get/create/update/destroy.
|
|
36
|
+
- Preserve dataSourceKey when the collection belongs to a non-main data source.
|
|
37
|
+
- Show an understandable forbidden or hidden state where appropriate.
|
|
38
|
+
- Deliver complete React component code, resource metadata, route integration, and the visible sample UI.`,[s.requirements,t,a]);return e.jsxs("details",{className:"group rounded-xl border bg-muted/15",children:[e.jsxs("summary",{className:"flex cursor-pointer list-none items-center gap-2 px-4 py-3 text-sm font-medium marker:hidden",children:[e.jsx(ee,{className:"size-4 text-primary"}),"Prompt generator",e.jsx("span",{className:"ml-auto text-xs font-normal text-muted-foreground",children:s.title}),e.jsx(se,{className:"size-4 text-muted-foreground transition-transform group-open:rotate-180"})]}),e.jsxs("div",{className:"grid items-start gap-5 border-t p-4 xl:grid-cols-[360px_minmax(0,1fr)]",children:[e.jsxs(c,{className:"gap-0 py-0",children:[e.jsxs(u,{className:"border-b py-4",children:[e.jsx(h,{className:"text-base",children:"Scenario settings"}),e.jsx(P,{children:s.description})]}),e.jsxs(p,{className:"space-y-5 py-5",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(D,{htmlFor:`${s.title}-scene`,children:"Business scene"}),e.jsx(k,{id:`${s.title}-scene`,value:t,onChange:o=>r(o.target.value)})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(D,{htmlFor:`${s.title}-target`,children:"Permission target"}),e.jsx(te,{id:`${s.title}-target`,value:a,onChange:o=>n(o.target.value),className:"min-h-24"})]})]})]}),e.jsx(he,{description:"Updates as you change the business scene and permission target.",prompt:i})]})]})}function j({eyebrow:s,title:t,description:r,children:a,prompt:n}){return e.jsxs("section",{className:"space-y-5 border-t pt-8",children:[e.jsxs("div",{children:[e.jsx(F,{variant:"outline",children:s}),e.jsx("h2",{className:"mt-3 text-2xl font-semibold tracking-[-0.025em]",children:t}),e.jsx("p",{className:"mt-2 max-w-3xl text-sm leading-6 text-muted-foreground",children:r})]}),a,e.jsx(ke,{config:n})]})}const R=[{id:1,name:"Alex Johnson",username:"alex",email:"alex@example.com",phone:"+1 202 555 0148",role:"Administrator"},{id:2,name:"Jamie Chen",username:"jamie",email:"jamie@example.com",phone:"+1 202 555 0186",role:"Member"},{id:3,name:"Morgan Lee",username:"morgan",email:"morgan@example.com",phone:"+1 202 555 0162",role:"Member"}],Pe={anyOf:["admin"]},V=me([{name:"dashboard",path:"/dashboard",resource:{meta:{label:"Dashboard",icon:e.jsx(_,{})}}},{name:"users",path:"/users",resource:{meta:{label:"Users",icon:e.jsx(M,{})}},access:{roles:Pe}},{name:"roles",path:"/roles",resource:{meta:{label:"Roles",icon:e.jsx(ge,{})}}}]),Re=ue(V),Ce=Re.map(s=>({key:s.name,name:s.name,route:typeof s.list=="string"?s.list:void 0,meta:s.meta,children:[]})),Se={"users:list":{},"departments:list":{}},Ue={"users:list":{}},Te={"users:get":{},"users:create":{},"users:update":{},"users:destroy":{}},De={"users:get":{},"users:update":{},"users:destroy":{}},Be=R.flatMap(s=>[{resource:"users",action:"update",id:s.id,allowed:!1},{resource:"users",action:"destroy",id:s.id,allowed:!1}]),Le={"users:update":{fields:["nickname","email","phone"]}},qe={"users:update":{fields:["nickname"]}};function Ee(){return e.jsxs("div",{className:"space-y-10 pb-12",children:[e.jsxs("header",{className:"space-y-2",children:[e.jsx("h1",{className:"text-2xl font-semibold tracking-[-0.025em] sm:text-3xl",children:"Permission patterns"}),e.jsx("p",{className:"max-w-3xl text-sm leading-6 text-muted-foreground",children:"Compare the authorized and restricted outcomes of navigation, pages, regions, record actions, and form fields using the same fixed data."})]}),e.jsx(j,{eyebrow:"Navigation permission",title:"Role-restricted routes disappear from navigation",description:"A route outside the current effective role is removed from the sidebar and its direct URL is guarded. Other accessible navigation remains unchanged.",prompt:{title:"Resource navigation",description:"Generate application resources, sidebar behavior, and guarded routes backed by NocoBase collections.",defaultScene:"User and role administration",defaultTarget:"Allow administrators to open Users, but remove it from navigation and block its direct URL for other roles.",requirements:`- Define application-owned navigation once through defineAppRoutes in src/routes.tsx.
|
|
39
|
+
- Put role constraints in the route access.roles field using anyOf, allOf, or noneOf.
|
|
40
|
+
- Let the route runtime generate the Refine resource, filter the sidebar item, and wrap the direct route with CanAccess.
|
|
41
|
+
- Do not repeat the same role list in Resource meta or a manually written route guard.
|
|
42
|
+
- Keep resource/action, record, region, and field permissions in their dedicated ACL boundaries.
|
|
43
|
+
- Let the Starter choose the first accessible route after inaccessible leaves and empty groups are removed.`},children:e.jsx(b,{allowedTitle:"Users permission granted",deniedTitle:"Users permission denied",allowed:e.jsx(d,{roles:["admin"],children:e.jsx(q,{})}),denied:e.jsx(d,{roles:["member"],children:e.jsx(q,{})})})}),e.jsx(j,{eyebrow:"Page permission",title:"A complete page is either available or replaced",description:"Page-level permission is useful when the whole route has one business purpose. Denial produces an understandable destination instead of a blank screen.",prompt:{title:"Page permission",description:"Generate a complete page whose availability depends on NocoBase permissions.",defaultScene:"User directory and account administration page",defaultTarget:"Allow administrators and user managers to render the complete page.",requirements:`- Wrap the complete business page with AclPage.
|
|
44
|
+
- Use the AclPage roles prop with anyOf, allOf, or noneOf for page-level role access.
|
|
45
|
+
- Keep anyOf and allOf for resource permissions when the page also needs collection access.
|
|
46
|
+
- Provide a useful page-level fallback instead of rendering an empty screen.
|
|
47
|
+
- Keep each inner data region independently protected when the page combines multiple collections.`},children:e.jsx(b,{allowedTitle:"Page allowed",deniedTitle:"Page denied",allowed:e.jsx(d,{roles:["user-manager"],children:e.jsx(O,{})}),denied:e.jsx(d,{roles:["member"],children:e.jsx(O,{})})})}),e.jsx(j,{eyebrow:"Region permission",title:"Only the protected region changes",description:"On a page with multiple resources, denying Departments hides only that panel and must not affect the Users table.",prompt:{title:"Region permission",description:"Generate a multi-resource page where each region follows its own collection ACL.",defaultScene:"Organization management dashboard",defaultTarget:"Keep Users visible while hiding the denied Departments panel.",requirements:`- Keep the outer page visible when its page-level permission is satisfied.
|
|
48
|
+
- Wrap every collection-backed panel in its own AclRegion.
|
|
49
|
+
- Enable each data query only when the corresponding list permission is allowed.
|
|
50
|
+
- Use fallback="hidden" so denied regions do not reveal unavailable resources or placeholders.
|
|
51
|
+
- Never use permission for one collection to hide unrelated tables or controls.`},children:e.jsx(b,{allowedTitle:"Both regions allowed",deniedTitle:"Departments resource hidden",allowed:e.jsx(d,{roles:["member"],permissions:Se,children:e.jsx(z,{})}),denied:e.jsx(d,{roles:["member"],permissions:Ue,children:e.jsx(z,{})})})}),e.jsx(j,{eyebrow:"Action and record permission",title:"Only unavailable actions are removed",description:"Collection permission controls global actions such as Create. Record ACL metadata controls Edit and Delete for each individual row.",prompt:{title:"Action permission",description:"Generate a data table whose toolbar and row actions respond to collection and record scopes.",defaultScene:"User administration table",defaultTarget:"Allow all actions for one user record and show a view-only row when update and delete are denied.",requirements:`- Use the Starter dataProvider so X-With-ACL-Meta captures NocoBase allowedActions from list, get, and getMany responses.
|
|
52
|
+
- Use the built-in Create, Show, Edit, and Delete buttons; do not manually compare user IDs or role names.
|
|
53
|
+
- Collection-level denial hides the action everywhere.
|
|
54
|
+
- Record-level allowedActions must hide or disable only the affected row action.
|
|
55
|
+
- Keep 403 responses authenticated and let NocoBase perform the final action validation.`},children:e.jsx(b,{allowedTitle:"Create and row actions allowed",deniedTitle:"Create, Update, and Delete denied",allowed:e.jsx(d,{roles:["admin"],permissions:Te,children:e.jsx($,{})}),denied:e.jsx(d,{roles:["viewer"],permissions:De,recordPermissions:Be,children:e.jsx($,{})})})}),e.jsx(j,{eyebrow:"Field permission",title:"A form can mix editable, read-only, and hidden fields",description:"Field ACL does not have to hide the complete form. Only protected inputs change, while permitted fields remain usable.",prompt:{title:"Field permission",description:"Generate a form or detail page whose individual fields follow NocoBase field ACL.",defaultScene:"User profile edit form",defaultTarget:"Allow nickname editing, make email read-only, and hide phone when those fields are denied.",requirements:`- Wrap each protected field or field group with AclField.
|
|
56
|
+
- Pass the collection resource, application action, and exact field name.
|
|
57
|
+
- Choose whether a denied field is hidden, replaced by read-only output, or shown with a custom fallback.
|
|
58
|
+
- Do not infer field access from the overall edit permission.
|
|
59
|
+
- Submit only values the current UI is allowed to edit; NocoBase still performs final field validation.`},children:e.jsx(b,{allowedTitle:"All fields editable",deniedTitle:"Email read-only and phone hidden",allowed:e.jsx(d,{roles:["admin"],permissions:Le,children:e.jsx(I,{})}),denied:e.jsx(d,{roles:["member"],permissions:qe,children:e.jsx(I,{})})})}),e.jsx(Ae,{})]})}function b({allowedTitle:s,deniedTitle:t,allowed:r,denied:a}){return e.jsxs("div",{className:"grid items-start gap-5 xl:grid-cols-2",children:[e.jsxs("div",{className:"min-w-0 space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm font-medium text-emerald-700 dark:text-emerald-400",children:[e.jsx(re,{className:"size-4"}),s]}),r]}),e.jsxs("div",{className:"min-w-0 space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm font-medium text-amber-700 dark:text-amber-400",children:[e.jsx(G,{className:"size-4"}),t]}),a]})]})}function q(){const s=ne(),t=s.status==="ready"?ae(Ce,s.permissions):[],r=t.some(n=>n.name==="users"),a=V.find(n=>n.name==="users");return e.jsxs("div",{className:"grid min-h-48 grid-cols-[150px_minmax(0,1fr)] overflow-hidden rounded-xl border bg-background",children:[e.jsxs("aside",{className:"border-r bg-muted/35 p-3",children:[e.jsx("div",{className:"mb-3 text-xs font-semibold text-muted-foreground",children:"Workspace"}),t.map(n=>{var i,o;return e.jsx(Oe,{icon:(i=n.meta)==null?void 0:i.icon,label:String(((o=n.meta)==null?void 0:o.label)??n.name),active:r?n.name==="users":n.name==="dashboard"},n.name)})]}),e.jsx("div",{className:"flex items-center justify-center p-5 text-center",children:e.jsx(ie,{access:a==null?void 0:a.access,fallback:e.jsxs("div",{children:[e.jsx(_,{className:"mx-auto size-7 text-muted-foreground"}),e.jsx("div",{className:"mt-2 font-medium",children:"Dashboard"}),e.jsx("div",{className:"mt-1 max-w-48 text-xs text-muted-foreground",children:"Users is absent from navigation and its direct route is guarded."})]}),children:e.jsxs("div",{children:[e.jsx(M,{className:"mx-auto size-7 text-primary"}),e.jsx("div",{className:"mt-2 font-medium",children:"Users"}),e.jsx("div",{className:"mt-1 text-xs text-muted-foreground",children:"Route is available"})]})})})]})}function Oe({icon:s,label:t,active:r=!1}){return e.jsxs("div",{className:`mb-1 flex items-center gap-2 rounded-md px-2 py-2 text-xs ${r?"bg-primary text-primary-foreground":"text-muted-foreground"}`,children:[e.jsx("span",{className:"[&_svg]:size-3.5",children:s}),t]})}function O(){return e.jsx(je,{roles:{anyOf:["admin","user-manager"]},fallback:e.jsx($e,{}),children:e.jsx(ze,{})})}function ze(){return e.jsxs(c,{children:[e.jsxs(u,{children:[e.jsx(h,{children:"User directory"}),e.jsx(P,{children:"3 users"})]}),e.jsx(p,{children:e.jsx(W,{})})]})}function $e(){return e.jsx("div",{className:"flex min-h-64 items-center justify-center rounded-xl border border-dashed bg-background p-6 text-center",children:e.jsxs("div",{children:[e.jsx(G,{className:"mx-auto size-8 text-muted-foreground"}),e.jsx("div",{className:"mt-3 font-medium",children:"You cannot open this page"}),e.jsx("p",{className:"mx-auto mt-1 max-w-xs text-sm text-muted-foreground",children:"Ask an administrator for access to the user directory or return to an available page."}),e.jsx(m,{className:"mt-4",variant:"outline",size:"sm",children:"Return to dashboard"})]})})}function z(){return e.jsxs("div",{className:"flex flex-col gap-3 rounded-xl border bg-background p-3 md:flex-row",children:[e.jsx(L,{resource:"users",action:"list",children:e.jsxs(c,{size:"sm",className:"min-w-0 flex-1",children:[e.jsx(u,{children:e.jsx(h,{children:"Users"})}),e.jsx(p,{children:e.jsx(W,{limit:2})})]})}),e.jsx(L,{resource:"departments",action:"list",children:e.jsxs(c,{size:"sm",className:"md:w-56",children:[e.jsx(u,{children:e.jsx(h,{children:"Departments"})}),e.jsxs(p,{className:"space-y-2",children:[e.jsx(K,{title:"Engineering",name:"engineering"}),e.jsx(K,{title:"Operations",name:"operations"})]})]})})]})}function $(){return e.jsxs(c,{children:[e.jsxs(u,{className:"flex-row items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx(h,{children:"Users"}),e.jsx(P,{children:"Record actions"})]}),e.jsx(y,{resource:"users",action:"create",children:e.jsxs(m,{size:"sm",children:[e.jsx(oe,{})," New user"]})})]}),e.jsx(p,{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full min-w-[420px] text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b text-left text-muted-foreground",children:[e.jsx("th",{className:"pb-3 font-medium",children:"User"}),e.jsx("th",{className:"pb-3 font-medium",children:"Role"}),e.jsx("th",{className:"pb-3 text-right font-medium",children:"Actions"})]})}),e.jsx("tbody",{children:R.slice(0,2).map(s=>e.jsxs("tr",{className:"border-b last:border-0",children:[e.jsx("td",{className:"py-3 font-medium",children:s.name}),e.jsx("td",{className:"py-3 text-muted-foreground",children:s.role}),e.jsx("td",{className:"py-3",children:e.jsxs("div",{className:"flex justify-end gap-1",children:[e.jsx(y,{resource:"users",action:"show",id:s.id,children:e.jsx(m,{variant:"ghost",size:"icon-sm","aria-label":"View user",children:e.jsx(le,{})})}),e.jsx(y,{resource:"users",action:"edit",id:s.id,children:e.jsx(m,{variant:"ghost",size:"icon-sm","aria-label":"Edit user",children:e.jsx(de,{})})}),e.jsx(y,{resource:"users",action:"delete",id:s.id,children:e.jsx(m,{variant:"ghost",size:"icon-sm","aria-label":"Delete user",children:e.jsx(ce,{})})})]})})]},s.id))})]})})]})}function I(){const s=R[1];return e.jsxs(c,{children:[e.jsxs(u,{children:[e.jsx(h,{children:"Edit profile"}),e.jsx(P,{children:s.name})]}),e.jsxs(p,{className:"space-y-4",children:[e.jsx(C,{resource:"users",action:"edit",field:"nickname",children:e.jsx(N,{label:"Nickname",children:e.jsx(k,{defaultValue:s.name})})}),e.jsx(C,{resource:"users",action:"edit",field:"email",fallback:e.jsx(N,{label:"Email",children:e.jsxs("div",{className:"rounded-md border bg-muted/45 px-3 py-2 text-sm text-muted-foreground",children:[s.email,e.jsx(F,{className:"ml-2",variant:"outline",children:"Read only"})]})}),children:e.jsx(N,{label:"Email",children:e.jsx(k,{defaultValue:s.email})})}),e.jsx(C,{resource:"users",action:"edit",field:"phone",children:e.jsx(N,{label:"Phone",children:e.jsx(k,{defaultValue:s.phone})})}),e.jsx(y,{resource:"users",action:"edit",children:e.jsx(m,{size:"sm",children:"Save changes"})})]})]})}function N({label:s,children:t}){return e.jsxs("label",{className:"block space-y-2",children:[e.jsx("span",{className:"text-xs font-medium",children:s}),t]})}function W({limit:s=3}){return e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full min-w-[360px] text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b text-left text-muted-foreground",children:[e.jsx("th",{className:"pb-3 font-medium",children:"User"}),e.jsx("th",{className:"pb-3 font-medium",children:"Email"})]})}),e.jsx("tbody",{children:R.slice(0,s).map(t=>e.jsxs("tr",{className:"border-b last:border-0",children:[e.jsxs("td",{className:"py-3",children:[e.jsx("div",{className:"font-medium",children:t.name}),e.jsx("div",{className:"text-xs text-muted-foreground",children:t.username})]}),e.jsx("td",{className:"py-3 text-muted-foreground",children:t.email})]},t.id))})]})})}function K({title:s,name:t}){return e.jsxs("div",{className:"rounded-lg border px-3 py-2",children:[e.jsx("div",{className:"text-sm font-medium",children:s}),e.jsx("div",{className:"mt-0.5 font-mono text-xs text-muted-foreground",children:t})]})}export{Ee as AclPatternsPage};
|