@nocobase/portal-template-default 1.0.9 → 1.0.10

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.
Files changed (304) hide show
  1. package/.env.example +5 -0
  2. package/.github/workflows/publish.yml +106 -0
  3. package/.gitignore +33 -0
  4. package/AGENTS.md +19 -0
  5. package/README.MD +61 -180
  6. package/package.json +3 -5
  7. package/scripts/build-html.mjs +12 -0
  8. package/src/App.css +2 -0
  9. package/src/App.tsx +35 -186
  10. package/src/app/development.tsx +154 -0
  11. package/src/app/extension.ts +5 -0
  12. package/src/app/extensions.tsx +18 -4
  13. package/src/app/routes.tsx +65 -0
  14. package/src/components/access-control/acl-bootstrap.tsx +11 -3
  15. package/src/components/access-control/acl-gate.tsx +7 -6
  16. package/src/components/access-control/can-access.tsx +17 -0
  17. package/src/components/access-control/navigate-to-accessible-resource.tsx +8 -3
  18. package/src/components/app-shell/sidebar.tsx +21 -5
  19. package/src/components/auth/auto-redirect-provider.tsx +2 -1
  20. package/src/extensions/nocobase-acl/README.md +74 -0
  21. package/{registry → src/extensions}/nocobase-acl/components/acl-boundary.tsx +22 -25
  22. package/{registry → src/extensions}/nocobase-acl/components/role-switcher.tsx +26 -23
  23. package/src/extensions/nocobase-acl/demo/acl-preview-provider.tsx +81 -0
  24. package/{registry → src/extensions}/nocobase-acl/demo/boundary-api.tsx +58 -0
  25. package/{registry → src/extensions}/nocobase-acl/demo/index.tsx +243 -105
  26. package/{registry → src/extensions}/nocobase-acl/demo/prompt-generator.tsx +3 -1
  27. package/src/extensions/nocobase-acl/extension.tsx +73 -0
  28. package/{registry → src/extensions}/nocobase-ai/README.md +21 -5
  29. package/{registry → src/extensions}/nocobase-ai/components/chat/chat-composer.tsx +3 -1
  30. package/{registry → src/extensions}/nocobase-ai/components/chat/chat-messages.tsx +38 -3
  31. package/{registry → src/extensions}/nocobase-ai/components/chat/tool-call-card.tsx +1 -9
  32. package/{registry → src/extensions}/nocobase-ai/components/index.ts +5 -0
  33. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +17 -0
  34. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +35 -0
  35. package/src/extensions/nocobase-ai/components/surfaces/chat-surface.tsx +108 -0
  36. package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +2 -2
  37. package/{registry → src/extensions}/nocobase-ai/demo/configuration-gate.tsx +4 -1
  38. package/{registry → src/extensions}/nocobase-ai/demo/container-showcase.tsx +2 -2
  39. package/{registry → src/extensions}/nocobase-ai/demo/floating.tsx +45 -15
  40. package/{registry → src/extensions}/nocobase-ai/demo/index.tsx +116 -74
  41. package/{registry → src/extensions}/nocobase-ai/demo/prompt-generator.tsx +3 -3
  42. package/{registry → src/extensions}/nocobase-ai/demo/tool-cards.tsx +2 -3
  43. package/src/extensions/nocobase-ai/extension.tsx +160 -0
  44. package/{registry → src/extensions}/nocobase-ai/global-ai-chat.tsx +12 -28
  45. package/{registry → src/extensions}/nocobase-ai/providers/ai-provider.tsx +14 -1
  46. package/{registry → src/extensions}/nocobase-ai/providers/chat-context.tsx +1 -0
  47. package/{registry → src/extensions}/nocobase-ai/providers/chat-provider.tsx +29 -8
  48. package/{registry → src/extensions}/nocobase-ai/providers/use-chat-attachments.ts +6 -1
  49. package/{registry → src/extensions}/nocobase-ai/providers/use-chat-work-context.ts +4 -1
  50. package/{registry → src/extensions}/nocobase-route-surfaces/README.md +2 -1
  51. package/{registry → src/extensions}/nocobase-route-surfaces/demo/index.tsx +33 -7
  52. package/{registry → src/extensions}/nocobase-route-surfaces/demo/scenarios.ts +20 -14
  53. package/{registry → src/extensions}/nocobase-route-surfaces/extension.tsx +55 -52
  54. package/src/{pages/users → extensions/nocobase-users-example}/create.tsx +4 -4
  55. package/src/{pages/users → extensions/nocobase-users-example}/edit.tsx +5 -9
  56. package/src/extensions/nocobase-users-example/extension.tsx +109 -0
  57. package/src/{pages/users → extensions/nocobase-users-example}/form-context.ts +11 -9
  58. package/src/extensions/nocobase-users-example/layout.tsx +16 -0
  59. package/src/{pages/users → extensions/nocobase-users-example}/list.tsx +1 -1
  60. package/src/extensions/nocobase-users-example/locales/en-US.ts +77 -0
  61. package/src/extensions/nocobase-users-example/locales/index.ts +8 -0
  62. package/src/extensions/nocobase-users-example/locales/zh-CN.ts +73 -0
  63. package/src/extensions/nocobase-users-example/optional-ai.ts +67 -0
  64. package/src/{pages/users → extensions/nocobase-users-example}/role-detail.tsx +4 -15
  65. package/src/{pages/users → extensions/nocobase-users-example}/routes.ts +1 -4
  66. package/src/extensions/nocobase-users-example/show.tsx +266 -0
  67. package/src/lib/nocobase/acl/context.ts +32 -0
  68. package/src/lib/nocobase/acl/evaluator.ts +186 -0
  69. package/src/lib/nocobase/acl/hooks.ts +40 -0
  70. package/src/lib/nocobase/acl/index.ts +4 -1
  71. package/src/lib/nocobase/acl/menu.ts +6 -6
  72. package/src/lib/nocobase/acl/record-permissions.ts +37 -7
  73. package/src/lib/nocobase/acl/store-provider.tsx +14 -0
  74. package/src/lib/nocobase/acl/store.ts +92 -86
  75. package/src/lib/nocobase/acl/types.ts +52 -32
  76. package/src/lib/nocobase/auth-session.ts +128 -0
  77. package/src/lib/nocobase/client.ts +48 -50
  78. package/src/locales/en-US.ts +0 -78
  79. package/src/locales/index.ts +2 -7
  80. package/src/locales/zh-CN.ts +0 -74
  81. package/src/providers/access-control.ts +43 -8
  82. package/src/providers/auth-callback.ts +1 -0
  83. package/src/providers/auth.ts +24 -26
  84. package/src/providers/constants.ts +0 -4
  85. package/src/providers/data.ts +14 -26
  86. package/src/providers/runtime-config.ts +40 -0
  87. package/src/vite-env.d.ts +9 -0
  88. package/tests/auth-session-regression.mjs +101 -0
  89. package/tsconfig.json +2 -1
  90. package/vite.config.ts +9 -1
  91. package/dist/assets/CoordinateSystem-C8P-Cleg.js +0 -1
  92. package/dist/assets/LegendVisualProvider-Bdgkckq-.js +0 -1
  93. package/dist/assets/LineDraw-C0m1GONT.js +0 -1
  94. package/dist/assets/SymbolDraw-CCospe5G.js +0 -1
  95. package/dist/assets/VisualMapping-Cbv1v7Cd.js +0 -1
  96. package/dist/assets/ai-employee-shortcut-B253WWow.js +0 -1
  97. package/dist/assets/calendar-B3dXXyIv.js +0 -1
  98. package/dist/assets/chat-inline-xpQ9iq4p.js +0 -1
  99. package/dist/assets/chat-side-panel-layout-BVWsp5rr.js +0 -1
  100. package/dist/assets/components-ChhJaNRc.js +0 -20
  101. package/dist/assets/configuration-gate-Dwdq_lxf.js +0 -1
  102. package/dist/assets/createClipPathFromCoordSys-uEftn1Mq.js +0 -1
  103. package/dist/assets/createSeriesDataSimply-CI7l7Ufv.js +0 -1
  104. package/dist/assets/customGraphicKeyframeAnimation-DE3jSfMd.js +0 -3
  105. package/dist/assets/echarts-preview-C-cNUtJF.js +0 -1
  106. package/dist/assets/echarts-runtime-CEgJbv68.js +0 -58
  107. package/dist/assets/echarts-runtime-advanced-Buhm2BHz.js +0 -1
  108. package/dist/assets/echarts-runtime-common-DLAdBAIa.js +0 -1
  109. package/dist/assets/echarts-runtime-components-ypnyLkYx.js +0 -1
  110. package/dist/assets/echarts-runtime-hierarchy-ClStquCF.js +0 -1
  111. package/dist/assets/floating-BarTmn-b.js +0 -13
  112. package/dist/assets/index-BJIcYTdc.js +0 -53
  113. package/dist/assets/index-C6xNKxpg.js +0 -656
  114. package/dist/assets/index-D4JQi1TM.js +0 -54
  115. package/dist/assets/index-YA7-AUO8.css +0 -1
  116. package/dist/assets/index-g8zWVpP9.js +0 -23
  117. package/dist/assets/labelHelper-C137W5gU.js +0 -1
  118. package/dist/assets/nocobase-ai-chat-VTMaZFQB.svg +0 -50
  119. package/dist/assets/page-context-Cxx11gzg.js +0 -73
  120. package/dist/assets/prompt-card-BsYMW_rq.js +0 -1
  121. package/dist/assets/sectorHelper-Fqgz3uEk.js +0 -1
  122. package/dist/assets/shortcut-xtghDP63.js +0 -47
  123. package/dist/assets/static.browser-CfQQzERe.js +0 -30
  124. package/dist/assets/switch-DrxImI8F.js +0 -1
  125. package/dist/assets/tool-cards-BGfsY8JF.js +0 -41
  126. package/dist/favicon.ico +0 -0
  127. package/dist/index.html +0 -40
  128. package/dist/index.raw.html +0 -46
  129. package/dist/logo-dark.png +0 -0
  130. package/dist/logo-mark-dark.png +0 -0
  131. package/dist/logo-mark.png +0 -0
  132. package/dist/logo.png +0 -0
  133. package/registry/nocobase-acl/README.md +0 -19
  134. package/registry/nocobase-acl/extension.tsx +0 -71
  135. package/registry/nocobase-acl/tests/acl-regression.mjs +0 -314
  136. package/registry/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
  137. package/registry/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
  138. package/registry/nocobase-ai/extension.tsx +0 -158
  139. package/registry/nocobase-ai/tests/business-report-regression.mjs +0 -70
  140. package/registry/nocobase-ai/tests/form-filler-regression.mjs +0 -129
  141. package/registry/nocobase-ai/tests/frontend-tool-regression.mjs +0 -128
  142. package/registry/nocobase-ai/tests/page-context-regression.mjs +0 -48
  143. package/registry/nocobase-ai/tests/stream-regression.mjs +0 -207
  144. package/registry/nocobase-auth-cas/README.md +0 -6
  145. package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +0 -41
  146. package/registry/nocobase-auth-cas/demo.tsx +0 -20
  147. package/registry/nocobase-auth-cas/extension.tsx +0 -46
  148. package/registry/nocobase-auth-cas/index.ts +0 -1
  149. package/registry/nocobase-auth-cas/use-cas-sign-in.ts +0 -29
  150. package/registry/nocobase-auth-dingtalk/README.md +0 -7
  151. package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +0 -154
  152. package/registry/nocobase-auth-dingtalk/demo.tsx +0 -23
  153. package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +0 -45
  154. package/registry/nocobase-auth-dingtalk/extension.tsx +0 -55
  155. package/registry/nocobase-auth-dingtalk/index.ts +0 -4
  156. package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +0 -64
  157. package/registry/nocobase-auth-ldap/README.md +0 -6
  158. package/registry/nocobase-auth-ldap/demo.tsx +0 -21
  159. package/registry/nocobase-auth-ldap/extension.tsx +0 -46
  160. package/registry/nocobase-auth-ldap/index.ts +0 -1
  161. package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +0 -69
  162. package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +0 -5
  163. package/registry/nocobase-auth-oidc/README.md +0 -11
  164. package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +0 -13
  165. package/registry/nocobase-auth-oidc/demo.tsx +0 -20
  166. package/registry/nocobase-auth-oidc/extension.tsx +0 -51
  167. package/registry/nocobase-auth-oidc/index.ts +0 -1
  168. package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +0 -44
  169. package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +0 -58
  170. package/registry/nocobase-auth-saml/README.md +0 -6
  171. package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +0 -13
  172. package/registry/nocobase-auth-saml/demo.tsx +0 -20
  173. package/registry/nocobase-auth-saml/extension.tsx +0 -51
  174. package/registry/nocobase-auth-saml/index.ts +0 -1
  175. package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +0 -43
  176. package/registry/nocobase-auth-saml/use-saml-sign-in.ts +0 -51
  177. package/registry/nocobase-auth-sms/README.md +0 -7
  178. package/registry/nocobase-auth-sms/demo.tsx +0 -25
  179. package/registry/nocobase-auth-sms/extension.tsx +0 -46
  180. package/registry/nocobase-auth-sms/index.ts +0 -1
  181. package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +0 -107
  182. package/registry/nocobase-auth-sms/use-sms-sign-in.ts +0 -88
  183. package/registry/nocobase-auth-wecom/README.md +0 -6
  184. package/registry/nocobase-auth-wecom/auto-login-provider.tsx +0 -21
  185. package/registry/nocobase-auth-wecom/demo.tsx +0 -23
  186. package/registry/nocobase-auth-wecom/extension.tsx +0 -49
  187. package/registry/nocobase-auth-wecom/index.ts +0 -1
  188. package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +0 -62
  189. package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +0 -70
  190. package/registry/nocobase-i18n/README.md +0 -44
  191. package/registry/nocobase-i18n/components/index.ts +0 -1
  192. package/registry/nocobase-i18n/components/language-switcher.tsx +0 -138
  193. package/registry/nocobase-i18n/demo/index.tsx +0 -209
  194. package/registry/nocobase-i18n/demo/prompt-generator.tsx +0 -132
  195. package/registry/nocobase-i18n/extension.tsx +0 -45
  196. package/registry/nocobase-i18n/index.ts +0 -7
  197. package/registry/nocobase-i18n/locales/en-US.ts +0 -39
  198. package/registry/nocobase-i18n/locales/index.ts +0 -8
  199. package/registry/nocobase-i18n/locales/zh-CN.ts +0 -35
  200. package/registry/nocobase-i18n/provider.tsx +0 -44
  201. package/registry/nocobase-i18n/server-resources.ts +0 -166
  202. package/registry/nocobase-i18n/tests/i18n-regression.mjs +0 -51
  203. package/registry.config.json +0 -302
  204. package/scripts/registry.mjs +0 -164
  205. package/src/components/access-control/resource-access-guard.tsx +0 -21
  206. package/src/components/access-control/use-acl-runtime.ts +0 -28
  207. package/src/lib/nocobase/acl/action.ts +0 -147
  208. package/src/pages/users/layout.tsx +0 -15
  209. package/src/pages/users/show.tsx +0 -278
  210. /package/{registry → src/extensions}/nocobase-acl/components/index.ts +0 -0
  211. /package/{registry → src/extensions}/nocobase-acl/components/role-options.ts +0 -0
  212. /package/{registry → src/extensions}/nocobase-acl/demo/components.tsx +0 -0
  213. /package/{registry → src/extensions}/nocobase-acl/demo/lazy-route.tsx +0 -0
  214. /package/{registry → src/extensions}/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -0
  215. /package/{registry → src/extensions}/nocobase-acl/demo/scenario-section.tsx +0 -0
  216. /package/{registry → src/extensions}/nocobase-acl/index.ts +0 -0
  217. /package/{registry → src/extensions}/nocobase-ai/adapters/react-hook-form.ts +0 -0
  218. /package/{registry → src/extensions}/nocobase-ai/assets/nocobase-ai-chat.svg +0 -0
  219. /package/{registry → src/extensions}/nocobase-ai/components/ai-root-provider.tsx +0 -0
  220. /package/{registry → src/extensions}/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -0
  221. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-attachment.tsx +0 -0
  222. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-compact.tsx +0 -0
  223. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-empty-state.tsx +0 -0
  224. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-header.tsx +0 -0
  225. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -0
  226. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-message.tsx +0 -0
  227. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-window.tsx +0 -0
  228. /package/{registry → src/extensions}/nocobase-ai/components/chat/conversation-list.tsx +0 -0
  229. /package/{registry → src/extensions}/nocobase-ai/components/chat/markdown-message.tsx +0 -0
  230. /package/{registry → src/extensions}/nocobase-ai/components/chat/model-select-options.tsx +0 -0
  231. /package/{registry → src/extensions}/nocobase-ai/components/chat/reasoning-panel.tsx +0 -0
  232. /package/{registry → src/extensions}/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -0
  233. /package/{registry → src/extensions}/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -0
  234. /package/{registry → src/extensions}/nocobase-ai/components/chat/work-context-chip.tsx +0 -0
  235. /package/{registry → src/extensions}/nocobase-ai/components/page-elements/ai-form.tsx +0 -0
  236. /package/{registry → src/extensions}/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -0
  237. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-inline.tsx +0 -0
  238. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-page.tsx +0 -0
  239. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -0
  240. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -0
  241. /package/{registry → src/extensions}/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -0
  242. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-dialog.tsx +0 -0
  243. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-renderer.tsx +0 -0
  244. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-utils.tsx +0 -0
  245. /package/{registry → src/extensions}/nocobase-ai/components/tools/chart-renderer.tsx +0 -0
  246. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-preview.tsx +0 -0
  247. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -0
  248. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -0
  249. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -0
  250. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -0
  251. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime.ts +0 -0
  252. /package/{registry → src/extensions}/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -0
  253. /package/{registry → src/extensions}/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -0
  254. /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -0
  255. /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -0
  256. /package/{registry → src/extensions}/nocobase-ai/components/tools/workflow-renderer.tsx +0 -0
  257. /package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -0
  258. /package/{registry → src/extensions}/nocobase-ai/demo/interaction-showcase.tsx +0 -0
  259. /package/{registry → src/extensions}/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -0
  260. /package/{registry → src/extensions}/nocobase-ai/demo/page-context.tsx +0 -0
  261. /package/{registry → src/extensions}/nocobase-ai/demo/page-element-showcase.tsx +0 -0
  262. /package/{registry → src/extensions}/nocobase-ai/demo/prompt-card.tsx +0 -0
  263. /package/{registry → src/extensions}/nocobase-ai/demo/shortcut.tsx +0 -0
  264. /package/{registry → src/extensions}/nocobase-ai/index.ts +0 -0
  265. /package/{registry → src/extensions}/nocobase-ai/providers/avatars.ts +0 -0
  266. /package/{registry → src/extensions}/nocobase-ai/providers/chat-controller.ts +0 -0
  267. /package/{registry → src/extensions}/nocobase-ai/providers/chat-message-utils.ts +0 -0
  268. /package/{registry → src/extensions}/nocobase-ai/providers/chat-reducer.ts +0 -0
  269. /package/{registry → src/extensions}/nocobase-ai/providers/chat-task-utils.ts +0 -0
  270. /package/{registry → src/extensions}/nocobase-ai/providers/chat-transport.ts +0 -0
  271. /package/{registry → src/extensions}/nocobase-ai/providers/form-registry.tsx +0 -0
  272. /package/{registry → src/extensions}/nocobase-ai/providers/frontend-tool-registry.tsx +0 -0
  273. /package/{registry → src/extensions}/nocobase-ai/providers/index.ts +0 -0
  274. /package/{registry → src/extensions}/nocobase-ai/providers/model.ts +0 -0
  275. /package/{registry → src/extensions}/nocobase-ai/providers/page-context.tsx +0 -0
  276. /package/{registry → src/extensions}/nocobase-ai/providers/stream-coalescer.ts +0 -0
  277. /package/{registry → src/extensions}/nocobase-ai/providers/stream-event-utils.ts +0 -0
  278. /package/{registry → src/extensions}/nocobase-ai/providers/stream-parser.ts +0 -0
  279. /package/{registry → src/extensions}/nocobase-ai/providers/sub-agent-stream.ts +0 -0
  280. /package/{registry → src/extensions}/nocobase-ai/providers/types.ts +0 -0
  281. /package/{registry → src/extensions}/nocobase-ai/providers/ui-message-stream.ts +0 -0
  282. /package/{registry → src/extensions}/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -0
  283. /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-message-actions.ts +0 -0
  284. /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-runtime.ts +0 -0
  285. /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-catalog.ts +0 -0
  286. /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-history.ts +0 -0
  287. /package/{registry → src/extensions}/nocobase-ai/services/index.ts +0 -0
  288. /package/{registry → src/extensions}/nocobase-ai/services/nocobase-ai-service.ts +0 -0
  289. /package/{registry → src/extensions}/nocobase-ai/services/types.ts +0 -0
  290. /package/{registry → src/extensions}/nocobase-route-surfaces/demo/lazy-route.tsx +0 -0
  291. /package/{registry → src/extensions}/nocobase-route-surfaces/demo/prompt-generator.tsx +0 -0
  292. /package/{registry → src/extensions}/nocobase-route-surfaces/index.ts +0 -0
  293. /package/{registry → src/extensions}/nocobase-route-surfaces/route-dialog.tsx +0 -0
  294. /package/{registry → src/extensions}/nocobase-route-surfaces/route-drawer.tsx +0 -0
  295. /package/{registry → src/extensions}/nocobase-route-surfaces/route-page.tsx +0 -0
  296. /package/{registry → src/extensions}/nocobase-route-surfaces/route-surface-context.ts +0 -0
  297. /package/{registry → src/extensions}/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +0 -0
  298. /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-close.ts +0 -0
  299. /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-state.ts +0 -0
  300. /package/src/{pages/users → extensions/nocobase-users-example}/form-fields.tsx +0 -0
  301. /package/src/{pages/users → extensions/nocobase-users-example}/index.ts +0 -0
  302. /package/src/{pages/users → extensions/nocobase-users-example}/role-badges.tsx +0 -0
  303. /package/src/{pages/users → extensions/nocobase-users-example}/role-utils.ts +0 -0
  304. /package/src/{pages/users → extensions/nocobase-users-example}/types.ts +0 -0
@@ -0,0 +1,186 @@
1
+ import ignore from "ignore";
2
+
3
+ import { resolveAclDataSourceKey } from "./data-source";
4
+ import { getRecordActionPermission } from "./record-permissions";
5
+ import type {
6
+ AclAccessRequest,
7
+ AclActionParams,
8
+ AclPermissionSet,
9
+ AclResourcePermissions,
10
+ ResourceAcl,
11
+ RoleConstraint,
12
+ } from "./types";
13
+
14
+ const REFINE_ACTION_MAP: Record<string, string> = {
15
+ list: "list",
16
+ show: "get",
17
+ create: "create",
18
+ edit: "update",
19
+ delete: "destroy",
20
+ clone: "create",
21
+ };
22
+
23
+ const getResourceAcl = (request: AclAccessRequest) =>
24
+ request.resourceItem?.meta?.acl as ResourceAcl | undefined;
25
+
26
+ export const mapRefineAction = (action: string) =>
27
+ REFINE_ACTION_MAP[action] ?? action;
28
+
29
+ export const getPermissionsForDataSource = (
30
+ permissions: AclPermissionSet,
31
+ dataSourceKey = "main"
32
+ ): AclResourcePermissions => {
33
+ const dataSourcePermissions = permissions.dataSources?.[dataSourceKey];
34
+ return dataSourcePermissions
35
+ ? {
36
+ ...permissions,
37
+ ...dataSourcePermissions,
38
+ snippets: permissions.snippets,
39
+ }
40
+ : permissions;
41
+ };
42
+
43
+ export const resolveActionPermission = ({
44
+ permissions,
45
+ resource,
46
+ action,
47
+ dataSourceKey = "main",
48
+ }: {
49
+ permissions: AclPermissionSet;
50
+ resource: string;
51
+ action: string;
52
+ dataSourceKey?: string;
53
+ }): AclActionParams | null => {
54
+ const data = getPermissionsForDataSource(permissions, dataSourceKey);
55
+ if (data.allowAll) return {};
56
+
57
+ const canonicalAction = data.actionAlias?.[action] ?? action;
58
+ if (data.resources?.includes(resource)) {
59
+ return (
60
+ data.actions?.[`${resource}:${canonicalAction}`] ??
61
+ data.actions?.[`${resource}:${action}`] ??
62
+ null
63
+ );
64
+ }
65
+
66
+ const strategyAllowed = data.strategy?.actions?.some(
67
+ (item) => item.split(":")[0] === canonicalAction
68
+ );
69
+ return strategyAllowed ? {} : null;
70
+ };
71
+
72
+ const matchesSnippet = (snippets: string[], target: string) =>
73
+ !target || target === "*" || ignore().add(snippets).ignores(target);
74
+
75
+ export const getEffectiveRoles = (permissions: AclPermissionSet) =>
76
+ permissions.roles;
77
+
78
+ export const matchesRoleConstraint = (
79
+ permissions: AclPermissionSet,
80
+ constraint?: RoleConstraint
81
+ ) => {
82
+ if (!constraint || permissions.allowAll) return true;
83
+
84
+ const roles = new Set(getEffectiveRoles(permissions));
85
+
86
+ return (
87
+ (!constraint.anyOf?.length ||
88
+ constraint.anyOf.some((role) => roles.has(role))) &&
89
+ (!constraint.allOf?.length ||
90
+ constraint.allOf.every((role) => roles.has(role))) &&
91
+ (!constraint.noneOf?.length ||
92
+ constraint.noneOf.every((role) => !roles.has(role)))
93
+ );
94
+ };
95
+
96
+ export type RecordPermissionResolver = typeof getRecordActionPermission;
97
+
98
+ export const evaluateAccess = (
99
+ permissions: AclPermissionSet,
100
+ request: AclAccessRequest,
101
+ getRecordPermission: RecordPermissionResolver = getRecordActionPermission
102
+ ) => {
103
+ const resourceAcl = getResourceAcl(request);
104
+ if (
105
+ !matchesRoleConstraint(
106
+ permissions,
107
+ resourceAcl === false ? undefined : resourceAcl?.roles
108
+ ) ||
109
+ !matchesRoleConstraint(permissions, request.roles)
110
+ ) {
111
+ return false;
112
+ }
113
+
114
+ if (resourceAcl === false || resourceAcl?.type === "authenticated") {
115
+ return true;
116
+ }
117
+
118
+ if (resourceAcl?.type === "snippet") {
119
+ return (
120
+ permissions.allowAll ||
121
+ matchesSnippet(permissions.snippets ?? [], resourceAcl.name)
122
+ );
123
+ }
124
+
125
+ if (resourceAcl?.type === "route") {
126
+ return (
127
+ permissions.allowAll ||
128
+ (permissions.allowMenuItemIds ?? [])
129
+ .map(String)
130
+ .includes(String(resourceAcl.routeId))
131
+ );
132
+ }
133
+
134
+ const targetResource =
135
+ resourceAcl?.type === "collection" && resourceAcl.resource
136
+ ? resourceAcl.resource
137
+ : request.resource;
138
+ if (!targetResource) return true;
139
+
140
+ const dataSourceKey =
141
+ resolveAclDataSourceKey(
142
+ request,
143
+ request.meta as
144
+ | { dataSourceKey?: unknown; acl?: ResourceAcl }
145
+ | undefined,
146
+ request.resourceItem?.meta as
147
+ | { dataSourceKey?: unknown; acl?: ResourceAcl }
148
+ | undefined
149
+ ) ?? "main";
150
+ const requestedAction = request.action ?? "list";
151
+ const mappedAction =
152
+ (resourceAcl?.type === "collection" &&
153
+ resourceAcl.actionMap?.[requestedAction]) ||
154
+ mapRefineAction(requestedAction);
155
+ const permission = resolveActionPermission({
156
+ permissions,
157
+ resource: targetResource,
158
+ action: mappedAction,
159
+ dataSourceKey,
160
+ });
161
+ if (!permission) return false;
162
+
163
+ if (request.id !== undefined) {
164
+ const recordPermission = getRecordPermission({
165
+ dataSourceKey,
166
+ resource: targetResource,
167
+ action: mappedAction,
168
+ id: request.id,
169
+ actionAlias: getPermissionsForDataSource(permissions, dataSourceKey)
170
+ .actionAlias,
171
+ });
172
+ if (recordPermission !== undefined) return recordPermission;
173
+ }
174
+
175
+ if (request.field) {
176
+ const fields = [
177
+ ...(permission.fields ?? []),
178
+ ...(permission.whitelist ?? []),
179
+ ...(permission.appends ?? []),
180
+ ];
181
+ if (!fields.length) return true;
182
+ return fields.includes(request.field);
183
+ }
184
+
185
+ return true;
186
+ };
@@ -0,0 +1,40 @@
1
+ import { useSyncExternalStore } from "react";
2
+
3
+ import { useAclState, useAclStore } from "./context";
4
+ import { evaluateAccess } from "./evaluator";
5
+ import type { AclAccessRequest } from "./types";
6
+
7
+ export const useAclEvaluator = () => {
8
+ const store = useAclStore();
9
+ const state = useAclState();
10
+ useSyncExternalStore(
11
+ store.recordPermissions.subscribe,
12
+ store.recordPermissions.getState,
13
+ store.recordPermissions.getState
14
+ );
15
+
16
+ return (request: AclAccessRequest) =>
17
+ state.status === "ready" &&
18
+ evaluateAccess(
19
+ state.permissions,
20
+ request,
21
+ store.recordPermissions.getPermission
22
+ );
23
+ };
24
+
25
+ export const useCanAccess = (request: AclAccessRequest) =>
26
+ useAclEvaluator()(request);
27
+
28
+ export const useGetRoles = () => {
29
+ const state = useAclState();
30
+ const isLoading = state.status === "idle" || state.status === "loading";
31
+ const isError = state.status === "error";
32
+
33
+ return {
34
+ data: state.status === "ready" ? state.permissions.roles : undefined,
35
+ error: isError ? state.error : undefined,
36
+ isError,
37
+ isLoading,
38
+ isPending: isLoading,
39
+ };
40
+ };
@@ -1,6 +1,9 @@
1
- export * from "./action";
1
+ export * from "./context";
2
2
  export * from "./data-source";
3
+ export * from "./evaluator";
4
+ export * from "./hooks";
3
5
  export * from "./menu";
4
6
  export * from "./record-permissions";
5
7
  export * from "./store";
8
+ export * from "./store-provider";
6
9
  export * from "./types";
@@ -1,20 +1,20 @@
1
1
  import type { TreeMenuItem } from "@refinedev/core";
2
2
 
3
- import { canAccessWithSnapshot } from "./action";
4
- import type { AclSnapshot } from "./types";
3
+ import { evaluateAccess } from "./evaluator";
4
+ import type { AclPermissionSet } from "./types";
5
5
 
6
6
  export const filterMenuItemsByAcl = (
7
7
  items: TreeMenuItem[],
8
- acl: AclSnapshot
8
+ permissions: AclPermissionSet
9
9
  ): TreeMenuItem[] =>
10
10
  items.flatMap((item) => {
11
- const children = filterMenuItemsByAcl(item.children ?? [], acl);
11
+ const children = filterMenuItemsByAcl(item.children ?? [], permissions);
12
12
  const isContainer = Boolean(item.meta?.group || item.children?.length);
13
13
  const canAccessItem = item.route
14
- ? canAccessWithSnapshot(acl, {
14
+ ? evaluateAccess(permissions, {
15
15
  resource: item.name,
16
16
  action: "list",
17
- params: { resource: item },
17
+ resourceItem: item,
18
18
  })
19
19
  : true;
20
20
 
@@ -5,15 +5,25 @@ type RecordPermissionEntry = {
5
5
  knownIds: Map<string, Set<string>>;
6
6
  };
7
7
 
8
- const permissions = new Map<string, RecordPermissionEntry>();
8
+ let permissions = new Map<string, RecordPermissionEntry>();
9
+ const listeners = new Set<() => void>();
9
10
 
10
11
  const getKey = (dataSourceKey: string, resource: string) =>
11
12
  `${dataSourceKey}:${resource}`;
12
13
 
13
14
  export const clearRecordPermissions = () => {
14
- permissions.clear();
15
+ if (!permissions.size) return;
16
+ permissions = new Map();
17
+ listeners.forEach((listener) => listener());
15
18
  };
16
19
 
20
+ export const subscribeRecordPermissions = (listener: () => void) => {
21
+ listeners.add(listener);
22
+ return () => listeners.delete(listener);
23
+ };
24
+
25
+ export const getRecordPermissions = () => permissions;
26
+
17
27
  export const updateRecordPermissions = ({
18
28
  dataSourceKey = "main",
19
29
  resource,
@@ -28,10 +38,26 @@ export const updateRecordPermissions = ({
28
38
  if (!allowedActions) return false;
29
39
 
30
40
  const key = getKey(dataSourceKey, resource);
31
- const entry = permissions.get(key) ?? {
32
- allowedIds: new Map<string, Set<string>>(),
33
- knownIds: new Map<string, Set<string>>(),
34
- };
41
+ const existingEntry = permissions.get(key);
42
+ const entry = existingEntry
43
+ ? {
44
+ allowedIds: new Map(
45
+ Array.from(existingEntry.allowedIds, ([action, ids]) => [
46
+ action,
47
+ new Set(ids),
48
+ ])
49
+ ),
50
+ knownIds: new Map(
51
+ Array.from(existingEntry.knownIds, ([action, ids]) => [
52
+ action,
53
+ new Set(ids),
54
+ ])
55
+ ),
56
+ }
57
+ : {
58
+ allowedIds: new Map<string, Set<string>>(),
59
+ knownIds: new Map<string, Set<string>>(),
60
+ };
35
61
  const normalizedIds = new Set(recordIds.map(String));
36
62
  const normalizedAllowedActions = new Map(
37
63
  Object.entries(allowedActions).map(([action, ids]) => [
@@ -81,7 +107,11 @@ export const updateRecordPermissions = ({
81
107
  entry.knownIds.set(action, knownIds);
82
108
  }
83
109
 
84
- permissions.set(key, entry);
110
+ if (changed) {
111
+ permissions = new Map(permissions);
112
+ permissions.set(key, entry);
113
+ listeners.forEach((listener) => listener());
114
+ }
85
115
  return changed;
86
116
  };
87
117
 
@@ -0,0 +1,14 @@
1
+ import type { PropsWithChildren } from "react";
2
+
3
+ import { AclStoreContext, type AclStore } from "./context";
4
+
5
+ export function AclStoreProvider({
6
+ children,
7
+ store,
8
+ }: PropsWithChildren<{ store: AclStore }>) {
9
+ return (
10
+ <AclStoreContext.Provider value={store}>
11
+ {children}
12
+ </AclStoreContext.Provider>
13
+ );
14
+ }
@@ -1,44 +1,38 @@
1
- import { useSyncExternalStore } from "react";
2
-
3
1
  import { nocobaseClient } from "@/lib/nocobase/client";
4
2
  import { NocoBaseHttpError } from "@/lib/nocobase/error";
5
- import { clearRecordPermissions } from "./record-permissions";
6
- import type {
7
- AclResponse,
8
- AclSnapshot,
9
- } from "./types";
3
+ import type { AclStore } from "./context";
4
+ import {
5
+ clearRecordPermissions,
6
+ getRecordActionPermission,
7
+ getRecordPermissions,
8
+ subscribeRecordPermissions,
9
+ } from "./record-permissions";
10
+ import type { AclPermissionSet, AclResponse, AclState } from "./types";
10
11
 
11
12
  const listeners = new Set<() => void>();
12
- let request:
13
+ let activeRequest:
13
14
  | {
14
15
  id: number;
15
- identity: string;
16
- promise: Promise<AclSnapshot>;
16
+ sessionKey: string;
17
+ promise: Promise<AclState>;
17
18
  }
18
19
  | undefined;
19
20
  let requestId = 0;
20
- let snapshot: AclSnapshot = {
21
- status: "idle",
22
- data: {},
23
- meta: {},
24
- version: 0,
25
- };
21
+ let loadedSessionKey: string | undefined;
22
+ let state: AclState = { status: "idle" };
26
23
 
27
24
  const emit = () => listeners.forEach((listener) => listener());
28
25
 
29
- const setSnapshot = (next: Partial<AclSnapshot>) => {
30
- snapshot = {
31
- ...snapshot,
32
- ...next,
33
- version: snapshot.version + 1,
34
- };
26
+ const setState = (next: AclState) => {
27
+ state = next;
35
28
  emit();
36
- return snapshot;
29
+ return state;
37
30
  };
38
31
 
39
- const getIdentity = () => {
40
- const token = nocobaseClient.getToken();
41
- return token ? `${token}:${nocobaseClient.getRole() ?? ""}` : undefined;
32
+ const getSessionKey = () => {
33
+ return `${nocobaseClient.getToken() ?? "cookie"}:${
34
+ nocobaseClient.getRole() ?? ""
35
+ }`;
42
36
  };
43
37
 
44
38
  const requestAcl = async (role?: string) =>
@@ -68,51 +62,45 @@ const isStaleRoleError = (error: unknown) => {
68
62
  );
69
63
  };
70
64
 
71
- export const getAclSnapshot = () => snapshot;
65
+ const normalizePermissions = (
66
+ response: AclResponse,
67
+ fallbackRole?: string
68
+ ): AclPermissionSet => {
69
+ const { role, roles, ...permissions } = response.data ?? {};
70
+ const currentRole = role ?? fallbackRole;
71
+ return {
72
+ ...permissions,
73
+ currentRole,
74
+ roles: roles ?? (currentRole ? [currentRole] : []),
75
+ dataSources: response.meta?.dataSources,
76
+ };
77
+ };
78
+
79
+ export const getAclState = () => state;
72
80
 
73
81
  export const subscribeAcl = (listener: () => void) => {
74
82
  listeners.add(listener);
75
83
  return () => listeners.delete(listener);
76
84
  };
77
85
 
78
- export const useAclSnapshot = () =>
79
- useSyncExternalStore(
80
- subscribeAcl,
81
- getAclSnapshot,
82
- getAclSnapshot
83
- );
84
-
85
- export const clearAcl = ({ keepRole = false } = {}) => {
86
+ export const clearAcl = () => {
86
87
  requestId += 1;
87
- request = undefined;
88
+ activeRequest = undefined;
89
+ loadedSessionKey = undefined;
88
90
  clearRecordPermissions();
89
- if (!keepRole) nocobaseClient.setRole(null);
90
- setSnapshot({
91
- status: "idle",
92
- identity: undefined,
93
- data: {},
94
- meta: {},
95
- error: undefined,
96
- });
97
- };
98
-
99
- export const notifyRecordPermissionsChanged = () => {
100
- setSnapshot({});
91
+ setState({ status: "idle" });
101
92
  };
102
93
 
103
- export const loadAcl = async ({ force = false } = {}) => {
104
- const identity = getIdentity();
105
- if (!identity) {
106
- clearAcl();
107
- return snapshot;
94
+ const load = async ({ force = false } = {}) => {
95
+ const sessionKey = getSessionKey();
96
+ if (!force && state.status === "ready" && loadedSessionKey === sessionKey) {
97
+ return state;
108
98
  }
109
-
110
- if (!force && snapshot.status === "ready" && snapshot.identity === identity) {
111
- return snapshot;
99
+ if (!force && activeRequest?.sessionKey === sessionKey) {
100
+ return activeRequest.promise;
112
101
  }
113
- if (!force && request?.identity === identity) return request.promise;
114
102
 
115
- setSnapshot({ status: "loading", identity, error: undefined });
103
+ setState({ status: "loading" });
116
104
  const currentRequestId = ++requestId;
117
105
  const requestedRole = nocobaseClient.getRole();
118
106
  const promise = (async () => {
@@ -127,37 +115,34 @@ export const loadAcl = async ({ force = false } = {}) => {
127
115
  response = await requestAcl();
128
116
  }
129
117
 
130
- if (currentRequestId !== requestId) return snapshot;
118
+ if (currentRequestId !== requestId) return state;
131
119
 
132
- const data = response.data ?? {};
133
- nocobaseClient.setRole(data.role ?? effectiveRole ?? null);
120
+ const permissions = normalizePermissions(response, effectiveRole);
121
+ nocobaseClient.setRole(permissions.currentRole ?? null);
134
122
  clearRecordPermissions();
135
- return setSnapshot({
136
- status: "ready",
137
- identity: getIdentity(),
138
- data,
139
- meta: response.meta ?? {},
140
- error: undefined,
141
- });
123
+ loadedSessionKey = getSessionKey();
124
+ return setState({ status: "ready", permissions });
142
125
  } catch (error) {
143
126
  const normalizedError =
144
- error instanceof Error ? error : new Error("Unable to load permissions");
145
- if (currentRequestId !== requestId) return snapshot;
146
- setSnapshot({ status: "error", error: normalizedError });
147
- throw normalizedError;
127
+ error instanceof Error
128
+ ? error
129
+ : new Error("Unable to load permissions");
130
+ if (currentRequestId !== requestId) return state;
131
+ loadedSessionKey = undefined;
132
+ return setState({ status: "error", error: normalizedError });
148
133
  } finally {
149
- if (request?.id === currentRequestId) request = undefined;
134
+ if (activeRequest?.id === currentRequestId) activeRequest = undefined;
150
135
  }
151
136
  })();
152
- request = { id: currentRequestId, identity, promise };
153
-
137
+ activeRequest = { id: currentRequestId, sessionKey, promise };
154
138
  return promise;
155
139
  };
156
140
 
157
- export const switchRole = async (
158
- roleName: string,
159
- { reloadAcl = true } = {}
160
- ) => {
141
+ export const loadAcl = () => load();
142
+
143
+ export const retryAcl = () => load({ force: true });
144
+
145
+ export const switchRole = async (roleName: string) => {
161
146
  const previousRole = nocobaseClient.getRole();
162
147
  nocobaseClient.setRole(roleName);
163
148
 
@@ -167,16 +152,37 @@ export const switchRole = async (
167
152
  body: { roleName },
168
153
  withAclMeta: false,
169
154
  });
170
- if (!reloadAcl) {
171
- clearRecordPermissions();
172
- return snapshot;
173
- }
174
- return await loadAcl({ force: true });
155
+ await nocobaseClient.action("auth", "syncCookies", {
156
+ method: "POST",
157
+ withAclMeta: false,
158
+ });
175
159
  } catch (error) {
176
160
  nocobaseClient.setRole(previousRole);
177
- if (reloadAcl) {
178
- await loadAcl({ force: true }).catch(() => undefined);
161
+ try {
162
+ await nocobaseClient.action("auth", "syncCookies", {
163
+ method: "POST",
164
+ withAclMeta: false,
165
+ });
166
+ } catch {
167
+ // Preserve the original role-switch error. The stored role still rolls
168
+ // back and will override a stale role cookie on subsequent requests.
179
169
  }
180
170
  throw error;
181
171
  }
172
+
173
+ clearAcl();
174
+ await loadAcl();
175
+ };
176
+
177
+ export const aclStore: AclStore = {
178
+ getState: getAclState,
179
+ subscribe: subscribeAcl,
180
+ load: loadAcl,
181
+ retry: retryAcl,
182
+ clear: clearAcl,
183
+ recordPermissions: {
184
+ getState: getRecordPermissions,
185
+ subscribe: subscribeRecordPermissions,
186
+ getPermission: getRecordActionPermission,
187
+ },
182
188
  };