@team-codeme/crud-forge 0.0.0-stage → 1.0.1

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 (415) hide show
  1. package/.cursor/agents/generator-engineer.md +31 -0
  2. package/.cursor/agents/keycloak-engineer.md +33 -0
  3. package/.cursor/agents/qa-engineer.md +32 -0
  4. package/.cursor/agents/security-reviewer.md +30 -0
  5. package/.cursor/mcp.json +13 -0
  6. package/.cursor/rules/crudforge-precautions.mdc +16 -0
  7. package/.cursor/rules/playwright-browser.mdc +30 -0
  8. package/.cursor/rules/readme-todo-sync.mdc +13 -0
  9. package/.env.qa.example +19 -0
  10. package/.github/workflows/qa-live.yml +32 -0
  11. package/.github/workflows/qa.yml +27 -0
  12. package/.vscode/settings.json +32 -0
  13. package/QA/IAM-PLAN.md +25 -0
  14. package/QA/IAM-RESULTS.md +39 -0
  15. package/QA/IAM-TESTCASES.md +36 -0
  16. package/QA/IAM-TODO.md +7 -0
  17. package/QA/KEYCLOAK-BOOTSTRAP.md +105 -0
  18. package/QA/PLAN.md +43 -0
  19. package/QA/RESULTS.md +59 -0
  20. package/QA/TESTCASES.md +65 -0
  21. package/QA/TODO.md +14 -0
  22. package/QA/pmb-dev/README.md +49 -0
  23. package/QA/pmb-dev/config/config.yml +152 -0
  24. package/QA/pmb-dev/gdl/i.gdl +17 -0
  25. package/README.md +851 -2
  26. package/Todo.md +207 -0
  27. package/assets/branding/crudforge-logo.png +0 -0
  28. package/assets/branding/crudforge-mark.png +0 -0
  29. package/branding/poster-crudforge.jpg +0 -0
  30. package/chat/last-chat/Investigating Mock Data Configuration.md +428 -0
  31. package/cli.js +167 -0
  32. package/config/default-config.yml +245 -0
  33. package/docs/performance/README.md +217 -0
  34. package/docs/performance/after.json +111 -0
  35. package/docs/performance/before.json +111 -0
  36. package/docs/quality/README.md +276 -0
  37. package/e2e/crudforge.e2e.yml +47 -0
  38. package/e2e/crudforge.field-profile.yml +33 -0
  39. package/e2e/crudforge.iam-live.yml +124 -0
  40. package/e2e/crudforge.iam.yml +62 -0
  41. package/e2e/tests/field-profile.spec.js +152 -0
  42. package/e2e/tests/generated-crud.spec.js +251 -0
  43. package/e2e/tests/iam-live.spec.js +149 -0
  44. package/e2e/tests/iam-pack.spec.js +151 -0
  45. package/e2e/tests/list-state.spec.js +73 -0
  46. package/features.csv +27 -0
  47. package/fuse-starter-pack/branding/crudforge-mark-source.jpg +0 -0
  48. package/fuse-starter-pack/branding/crudforge-mark-source.png +0 -0
  49. package/fuse-starter-pack/branding/crudforge-mark.png +0 -0
  50. package/fuse-starter-pack/branding/crudforge-wordmark-source.jpg +0 -0
  51. package/fuse-starter-pack/branding/crudforge-wordmark-source.png +0 -0
  52. package/fuse-starter-pack/branding/crudforge-wordmark.png +0 -0
  53. package/fuse-starter-pack/fuse-v21.1/Dockerfile +14 -0
  54. package/fuse-starter-pack/fuse-v21.1/LICENSE.md +6 -0
  55. package/fuse-starter-pack/fuse-v21.1/README.md +59 -0
  56. package/fuse-starter-pack/fuse-v21.1/angular.json +113 -0
  57. package/fuse-starter-pack/fuse-v21.1/eslint.config.ts +150 -0
  58. package/fuse-starter-pack/fuse-v21.1/nginx.conf +23 -0
  59. package/fuse-starter-pack/fuse-v21.1/nginx.dev.conf +18 -0
  60. package/fuse-starter-pack/fuse-v21.1/package-lock.json +11513 -0
  61. package/fuse-starter-pack/fuse-v21.1/package.json +69 -0
  62. package/fuse-starter-pack/fuse-v21.1/postcss.config.json +5 -0
  63. package/fuse-starter-pack/fuse-v21.1/prettier.config.ts +15 -0
  64. package/fuse-starter-pack/fuse-v21.1/proxy.conf.json +8 -0
  65. package/fuse-starter-pack/fuse-v21.1/proxy.dev.conf.json +8 -0
  66. package/fuse-starter-pack/fuse-v21.1/public/favicon.png +0 -0
  67. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/Geist-Italic.woff2 +0 -0
  68. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/Geist.woff2 +0 -0
  69. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/GeistMono-Italic.woff2 +0 -0
  70. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/GeistMono.woff2 +0 -0
  71. package/fuse-starter-pack/fuse-v21.1/public/i18n/en.json +8 -0
  72. package/fuse-starter-pack/fuse-v21.1/public/i18n/es.json +8 -0
  73. package/fuse-starter-pack/fuse-v21.1/public/images/flags/ES.svg +79 -0
  74. package/fuse-starter-pack/fuse-v21.1/public/images/flags/US.svg +14 -0
  75. package/fuse-starter-pack/fuse-v21.1/public/images/flags/where-to-find-other-flags.txt +5 -0
  76. package/fuse-starter-pack/fuse-v21.1/public/images/logo/logo-mark.png +0 -0
  77. package/fuse-starter-pack/fuse-v21.1/public/images/logo/logo.png +0 -0
  78. package/fuse-starter-pack/fuse-v21.1/push.sh +27 -0
  79. package/fuse-starter-pack/fuse-v21.1/src/app/app.config.server.ts +10 -0
  80. package/fuse-starter-pack/fuse-v21.1/src/app/app.config.ts +64 -0
  81. package/fuse-starter-pack/fuse-v21.1/src/app/app.routes.server.ts +8 -0
  82. package/fuse-starter-pack/fuse-v21.1/src/app/app.routes.ts +39 -0
  83. package/fuse-starter-pack/fuse-v21.1/src/app/app.ts +22 -0
  84. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/index.ts +1 -0
  85. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/lucide.ts +1 -0
  86. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/provider.ts +23 -0
  87. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/index.ts +2 -0
  88. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/local-storage.ts +109 -0
  89. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/provider.ts +15 -0
  90. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/index.ts +2 -0
  91. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/media.ts +64 -0
  92. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/provider.ts +15 -0
  93. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/index.ts +3 -0
  94. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/models/theming.ts +13 -0
  95. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/palette.ts +209 -0
  96. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/provider.ts +24 -0
  97. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/theming.ts +129 -0
  98. package/fuse-starter-pack/fuse-v21.1/src/app/core/transloco/transloco-http-loader.ts +12 -0
  99. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/data/navigation.ts +176 -0
  100. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/layout.ts +77 -0
  101. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/language-switcher.ts +91 -0
  102. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/navigation.ts +238 -0
  103. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/notifications.ts +252 -0
  104. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/scheme-switcher.ts +58 -0
  105. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/sidebar.ts +31 -0
  106. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/user.ts +132 -0
  107. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/data/academy.ts +753 -0
  108. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/data/model.ts +27 -0
  109. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/features/course.ts +318 -0
  110. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/features/courses.ts +228 -0
  111. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/routes.ts +14 -0
  112. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/data/contacts.ts +4372 -0
  113. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/data/model.ts +34 -0
  114. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/features/contact.ts +239 -0
  115. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/features/contacts.ts +185 -0
  116. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/routes.ts +16 -0
  117. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/data/file-manager.ts +261 -0
  118. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/data/model.ts +12 -0
  119. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/features/file-manager.ts +131 -0
  120. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/routes.ts +15 -0
  121. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/ui/list.ts +164 -0
  122. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/faq.ts +288 -0
  123. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/guide.ts +134 -0
  124. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/home.ts +219 -0
  125. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/support.ts +161 -0
  126. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/routes.ts +33 -0
  127. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/data/model.ts +18 -0
  128. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/data/notes.ts +324 -0
  129. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/features/note.ts +152 -0
  130. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/features/notes.ts +118 -0
  131. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/routes.ts +16 -0
  132. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/data/model.ts +17 -0
  133. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/data/tasks.ts +562 -0
  134. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/features/task.ts +195 -0
  135. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/features/tasks.ts +271 -0
  136. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/routes.ts +16 -0
  137. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/analytics.ts +1620 -0
  138. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/finance.ts +1065 -0
  139. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/project.ts +376 -0
  140. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/analytics/analytics.ts +1096 -0
  141. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/finance/finance.ts +658 -0
  142. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/project/budget-table.ts +188 -0
  143. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/project/project.ts +565 -0
  144. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/routes.ts +23 -0
  145. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/changelog/changelog.ts +932 -0
  146. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/building.ts +33 -0
  147. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/development.ts +42 -0
  148. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/installation.ts +47 -0
  149. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/layout.ts +9 -0
  150. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/routes.ts +45 -0
  151. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/error/features/error-page.ts +70 -0
  152. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/error/routes.ts +21 -0
  153. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/notifications/features/notifications.ts +262 -0
  154. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/notifications/routes.ts +10 -0
  155. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/account.ts +238 -0
  156. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/notifications.ts +134 -0
  157. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/plan-and-billing.ts +238 -0
  158. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/security.ts +211 -0
  159. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/team.ts +188 -0
  160. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/layout.ts +109 -0
  161. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/routes.ts +37 -0
  162. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/welcome/welcome.component.ts +22 -0
  163. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/routes.ts +95 -0
  164. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/forgot-password/forgot-password.html +54 -0
  165. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/forgot-password/forgot-password.ts +51 -0
  166. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/reset-password/reset-password.html +82 -0
  167. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/reset-password/reset-password.ts +69 -0
  168. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-in/sign-in.html +132 -0
  169. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-in/sign-in.ts +54 -0
  170. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-up/sign-up.html +183 -0
  171. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-up/sign-up.ts +56 -0
  172. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/layout.ts +9 -0
  173. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/routes.ts +34 -0
  174. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/features/coming-soon.ts +156 -0
  175. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/layout.ts +9 -0
  176. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/routes.ts +16 -0
  177. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/layout/layout.ts +9 -0
  178. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/modules/home/features/home.ts +23 -0
  179. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/modules/home/routes.ts +10 -0
  180. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/routes.ts +30 -0
  181. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.mock.ts +5 -0
  182. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.prod.ts +5 -0
  183. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.ts +5 -0
  184. package/fuse-starter-pack/fuse-v21.1/src/index.html +66 -0
  185. package/fuse-starter-pack/fuse-v21.1/src/main.server.ts +11 -0
  186. package/fuse-starter-pack/fuse-v21.1/src/main.ts +5 -0
  187. package/fuse-starter-pack/fuse-v21.1/src/server.ts +70 -0
  188. package/fuse-starter-pack/fuse-v21.1/src/styles/base/base.css +63 -0
  189. package/fuse-starter-pack/fuse-v21.1/src/styles/base/typography.css +54 -0
  190. package/fuse-starter-pack/fuse-v21.1/src/styles/components/a.css +18 -0
  191. package/fuse-starter-pack/fuse-v21.1/src/styles/components/input.css +22 -0
  192. package/fuse-starter-pack/fuse-v21.1/src/styles/components/kbd.css +55 -0
  193. package/fuse-starter-pack/fuse-v21.1/src/styles/components/material.css +1149 -0
  194. package/fuse-starter-pack/fuse-v21.1/src/styles/styles.css +31 -0
  195. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/theme.css +147 -0
  196. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/utilities.css +563 -0
  197. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/variants.css +4 -0
  198. package/fuse-starter-pack/fuse-v21.1/src/styles/third-party/lucide-icons.css +12 -0
  199. package/fuse-starter-pack/fuse-v21.1/tsconfig.app.json +16 -0
  200. package/fuse-starter-pack/fuse-v21.1/tsconfig.json +37 -0
  201. package/fuse-starter-pack/fuse-v21.1/tsconfig.spec.json +10 -0
  202. package/generator/README.md +12 -0
  203. package/generator/issues/.gitkeep +0 -0
  204. package/package.json +56 -4
  205. package/playwright.config.js +29 -0
  206. package/playwright.field-profile.config.js +6 -0
  207. package/playwright.iam-live.config.js +31 -0
  208. package/playwright.iam.config.js +29 -0
  209. package/playwright.list-state.config.js +6 -0
  210. package/playwright.performance.config.js +1 -0
  211. package/run-config-tests.sh +5 -0
  212. package/scripts/e2e-paths.js +89 -0
  213. package/scripts/e2e-prepare-field-profile.js +17 -0
  214. package/scripts/e2e-prepare-iam-live.js +135 -0
  215. package/scripts/e2e-prepare-iam.js +65 -0
  216. package/scripts/e2e-prepare.js +72 -0
  217. package/scripts/e2e-serve-iam-live.js +28 -0
  218. package/scripts/e2e-serve-iam.js +28 -0
  219. package/scripts/e2e-serve.js +28 -0
  220. package/scripts/performance/generate.js +17 -0
  221. package/scripts/performance/measure.js +55 -0
  222. package/scripts/postinstall.js +9 -0
  223. package/scripts/prepare-quality-apps.js +34 -0
  224. package/scripts/run-config-tests.js +63 -0
  225. package/src/assets/logo-with-lable.jpeg +0 -0
  226. package/src/assets/logo.jpeg +0 -0
  227. package/src/commands/bootstrap.js +92 -0
  228. package/src/commands/doctor.js +403 -0
  229. package/src/commands/generate.js +301 -0
  230. package/src/commands/init-config.js +310 -0
  231. package/src/commands/keycloak-options.js +182 -0
  232. package/src/commands/validate.js +52 -0
  233. package/src/config/auth-config.js +183 -0
  234. package/src/config/default-values.js +235 -0
  235. package/src/config/env-refs.js +71 -0
  236. package/src/config/field-preferences.js +99 -0
  237. package/src/config/input-sources.js +104 -0
  238. package/src/config/load-config.js +166 -0
  239. package/src/config/proxy-routes.js +44 -0
  240. package/src/generator/access-control-tools.js +334 -0
  241. package/src/generator/ai-docs.js +691 -0
  242. package/src/generator/application-tests.js +252 -0
  243. package/src/generator/auth-pack.js +965 -0
  244. package/src/generator/embed-config.js +431 -0
  245. package/src/generator/entity-layout.js +30 -0
  246. package/src/generator/fuse-build.js +57 -0
  247. package/src/generator/fuse-runtime.js +344 -0
  248. package/src/generator/generated-banner.js +137 -0
  249. package/src/generator/index.js +874 -0
  250. package/src/generator/project-output.js +126 -0
  251. package/src/generator/team-pack.js +220 -0
  252. package/src/index.js +68 -0
  253. package/src/parser/gdl-parser.js +169 -0
  254. package/src/parser/jdl-parser.js +171 -0
  255. package/src/presets/pmb-test.js +139 -0
  256. package/src/presets/static-source.js +73 -0
  257. package/src/presets/store.js +96 -0
  258. package/src/provision/admin-service.js +148 -0
  259. package/src/provision/bootstrap.js +147 -0
  260. package/src/provision/http.js +123 -0
  261. package/src/provision/keycloak-admin-rest.js +335 -0
  262. package/src/provision/state.js +51 -0
  263. package/src/provision/token.js +52 -0
  264. package/src/templates/angular/auth/access-control.service.ts.ejs +179 -0
  265. package/src/templates/angular/auth/access-ctrl.pipe.ts.ejs +20 -0
  266. package/src/templates/angular/auth/auth.guard.ts.ejs +27 -0
  267. package/src/templates/angular/auth/auth.interceptor.ts.ejs +53 -0
  268. package/src/templates/angular/auth/auth.service.ts.ejs +594 -0
  269. package/src/templates/angular/auth/auth.utils.ts.ejs +28 -0
  270. package/src/templates/angular/auth/current-user.ts.ejs +24 -0
  271. package/src/templates/angular/auth/disabled-account-overlay.component.css.ejs +95 -0
  272. package/src/templates/angular/auth/disabled-account-overlay.component.html.ejs +33 -0
  273. package/src/templates/angular/auth/disabled-account-overlay.component.ts.ejs +23 -0
  274. package/src/templates/angular/auth/fuse-account.ts.ejs +234 -0
  275. package/src/templates/angular/auth/fuse-user.ts.ejs +264 -0
  276. package/src/templates/angular/auth/has-permission.directive.ts.ejs +51 -0
  277. package/src/templates/angular/auth/index.ts.ejs +24 -0
  278. package/src/templates/angular/auth/keycloak-admin.service.ts.ejs +670 -0
  279. package/src/templates/angular/auth/landing.guard.ts.ejs +61 -0
  280. package/src/templates/angular/auth/no-auth.guard.ts.ejs +19 -0
  281. package/src/templates/angular/auth/permission-attribute.util.ts.ejs +182 -0
  282. package/src/templates/angular/auth/sign-in.ts.ejs +60 -0
  283. package/src/templates/angular/entity/_entity_.model.ts.ejs +64 -0
  284. package/src/templates/angular/entity/_entity_.routes.ts.ejs +9 -0
  285. package/src/templates/angular/entity/components/forms/form/_entity_-form.component.html.ejs +168 -0
  286. package/src/templates/angular/entity/components/forms/form/_entity_-form.component.ts.ejs +175 -0
  287. package/src/templates/angular/entity/components/forms/form-page/_entity_-form-page.component.html.ejs +10 -0
  288. package/src/templates/angular/entity/components/forms/form-page/_entity_-form-page.component.ts.ejs +86 -0
  289. package/src/templates/angular/entity/components/forms/update/_entity_-form.service.ts.ejs +136 -0
  290. package/src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.html.ejs +68 -0
  291. package/src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.ts.ejs +69 -0
  292. package/src/templates/angular/entity/components/interaction/filter/_entity_-filter.component.html.ejs +111 -0
  293. package/src/templates/angular/entity/components/interaction/filter/_entity_-filter.component.ts.ejs +102 -0
  294. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.css.ejs +33 -0
  295. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.html.ejs +29 -0
  296. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.ts.ejs +93 -0
  297. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.css.ejs +54 -0
  298. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.html.ejs +58 -0
  299. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.ts.ejs +47 -0
  300. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.html.ejs +419 -0
  301. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.scss.ejs +48 -0
  302. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs +592 -0
  303. package/src/templates/angular/entity/service/_entity_.service.ts.ejs +153 -0
  304. package/src/templates/angular/entity/settings/_entity_.settings.ts.ejs +49 -0
  305. package/src/templates/angular/enums/_enum_.model.ts.ejs +11 -0
  306. package/src/templates/angular/iam/change-password.component.html.ejs +39 -0
  307. package/src/templates/angular/iam/change-password.component.ts.ejs +44 -0
  308. package/src/templates/angular/iam/no-access.component.ts.ejs +17 -0
  309. package/src/templates/angular/iam/role-list.component.html.ejs +331 -0
  310. package/src/templates/angular/iam/role-list.component.scss.ejs +107 -0
  311. package/src/templates/angular/iam/role-list.component.ts.ejs +597 -0
  312. package/src/templates/angular/iam/role-management/role-header.component.html.ejs +51 -0
  313. package/src/templates/angular/iam/role-management/role-header.component.ts.ejs +45 -0
  314. package/src/templates/angular/iam/role-management/role-inheritance.component.html.ejs +15 -0
  315. package/src/templates/angular/iam/role-management/role-inheritance.component.ts.ejs +19 -0
  316. package/src/templates/angular/iam/role-management/role-permissions-panel.component.css.ejs +24 -0
  317. package/src/templates/angular/iam/role-management/role-permissions-panel.component.html.ejs +108 -0
  318. package/src/templates/angular/iam/role-management/role-permissions-panel.component.ts.ejs +122 -0
  319. package/src/templates/angular/iam/role-management/role-users.component.html.ejs +104 -0
  320. package/src/templates/angular/iam/role-management/role-users.component.ts.ejs +53 -0
  321. package/src/templates/angular/iam/role-management/user-select-dialog.component.html.ejs +113 -0
  322. package/src/templates/angular/iam/role-management/user-select-dialog.component.ts.ejs +157 -0
  323. package/src/templates/angular/iam/role-permission-matrix.component.html.ejs +355 -0
  324. package/src/templates/angular/iam/role-permission-matrix.component.scss.ejs +217 -0
  325. package/src/templates/angular/iam/role-permission-matrix.component.ts.ejs +800 -0
  326. package/src/templates/angular/iam/screen-catalog.ts.ejs +147 -0
  327. package/src/templates/angular/iam/shared/confirm-dialog.component.ts.ejs +83 -0
  328. package/src/templates/angular/iam/shared/matrix-hierarchy.util.ts.ejs +196 -0
  329. package/src/templates/angular/iam/shared/permission-matrix-sound.ts.ejs +59 -0
  330. package/src/templates/angular/iam/shared/role-display.util.ts.ejs +43 -0
  331. package/src/templates/angular/iam/shared/role-permission-skeleton.component.ts.ejs +54 -0
  332. package/src/templates/angular/iam/shared/screen-matrix.util.ts.ejs +554 -0
  333. package/src/templates/angular/iam/shared/screen-navigation-map.ts.ejs +187 -0
  334. package/src/templates/angular/iam/shared/screen-navigation.registry.ts.ejs +64 -0
  335. package/src/templates/angular/iam/shared/text-highlight.pipe.ts.ejs +39 -0
  336. package/src/templates/angular/iam/user-access-matrix.component.html.ejs +212 -0
  337. package/src/templates/angular/iam/user-access-matrix.component.scss.ejs +34 -0
  338. package/src/templates/angular/iam/user-access-matrix.component.ts.ejs +239 -0
  339. package/src/templates/angular/iam/user-detail.component.html.ejs +143 -0
  340. package/src/templates/angular/iam/user-detail.component.ts.ejs +108 -0
  341. package/src/templates/angular/iam/user-form.component.html.ejs +63 -0
  342. package/src/templates/angular/iam/user-form.component.ts.ejs +100 -0
  343. package/src/templates/angular/iam/user-list.component.html.ejs +369 -0
  344. package/src/templates/angular/iam/user-list.component.scss.ejs +242 -0
  345. package/src/templates/angular/iam/user-list.component.ts.ejs +446 -0
  346. package/src/templates/angular/iam/user-management.routes.ts.ejs +47 -0
  347. package/src/templates/angular/iam/user-permission-matrix.component.html.ejs +231 -0
  348. package/src/templates/angular/iam/user-permission-matrix.component.scss.ejs +216 -0
  349. package/src/templates/angular/iam/user-permission-matrix.component.ts.ejs +516 -0
  350. package/src/templates/angular/shared/base/audit-user-name.pipe.ts.ejs +69 -0
  351. package/src/templates/angular/shared/base/base-filter.component.ts.ejs +245 -0
  352. package/src/templates/angular/shared/base/base-form.component.ts.ejs +109 -0
  353. package/src/templates/angular/shared/base/base-list.component.ts.ejs +507 -0
  354. package/src/templates/angular/shared/base/filter-types.ts.ejs +55 -0
  355. package/src/templates/angular/shared/base/input-format.directive.ts.ejs +47 -0
  356. package/src/templates/angular/shared/base/input-format.ts.ejs +15 -0
  357. package/src/templates/angular/shared/confirm-with-input-dialog.component.ts.ejs +91 -0
  358. package/src/templates/angular/shared/confirmation-dialog.component.ts.ejs +80 -0
  359. package/src/templates/angular/shared/list-styles.css.ejs +103 -0
  360. package/src/templates/angular/shared/loading-overlay.component.ts.ejs +24 -0
  361. package/src/templates/angular/shared/resizable.directive.ts.ejs +115 -0
  362. package/src/templates/angular/shared/snackbar-styles.css.ejs +11 -0
  363. package/src/templates/fuse-packs/access-control-tools/access-control-sync.js +819 -0
  364. package/src/templates/fuse-packs/access-control-tools/build-rename-map.js +67 -0
  365. package/src/templates/fuse-packs/access-control-tools/inject-qa-ids.js +14 -0
  366. package/src/templates/fuse-packs/access-control-tools/lib/hierarchy-scanner.js +628 -0
  367. package/src/templates/fuse-packs/access-control-tools/lib/hierarchy-scanner.test.js +323 -0
  368. package/src/templates/fuse-packs/access-control-tools/lib/html-scanner.js +362 -0
  369. package/src/templates/fuse-packs/access-control-tools/lib/id-rules.duplicate.test.js +136 -0
  370. package/src/templates/fuse-packs/access-control-tools/lib/id-rules.js +1095 -0
  371. package/src/templates/fuse-packs/access-control-tools/lib/ts-component-index.js +482 -0
  372. package/src/templates/fuse-packs/access-control-tools/output/.gitkeep +0 -0
  373. package/src/templates/fuse-packs/access-control-tools/run.sh +223 -0
  374. package/src/templates/fuse-packs/access-control-tools/scan-access-control.js +14 -0
  375. package/src/templates/fuse-packs/iam-assets/sound/apple-play.mp3 +0 -0
  376. package/src/templates/fuse-packs/iam-assets/sound/remove.mp3 +0 -0
  377. package/src/templates/generator/README.md +12 -0
  378. package/src/templates/testing/ci.cjs +45 -0
  379. package/src/templates/testing/mock.cjs +149 -0
  380. package/src/templates/testing/playwright.config.cjs +25 -0
  381. package/src/templates/testing/serve.cjs +22 -0
  382. package/src/templates/testing/smoke.spec.cjs +180 -0
  383. package/src/utils/branding.js +39 -0
  384. package/src/utils/colors.js +18 -0
  385. package/src/utils/diff.js +109 -0
  386. package/src/utils/merger.js +115 -0
  387. package/src/validator/backend-diagnostics.js +30 -0
  388. package/src/validator/blueprint-validator.js +276 -0
  389. package/src/validator/diagnostics.js +608 -0
  390. package/src/validator/pattern.js +28 -0
  391. package/test/auth-interceptor.js +84 -0
  392. package/test/auth-provision-checks.js +297 -0
  393. package/test/field-preferences.js +145 -0
  394. package/test/fixtures/field-profile.gdl +7 -0
  395. package/test/fixtures/mock.jdl +17 -0
  396. package/test/fixtures/mock.png +0 -0
  397. package/test/fixtures/performance/config.yml +33 -0
  398. package/test/fixtures/performance/large.gdl +199 -0
  399. package/test/fixtures/performance/small.gdl +7 -0
  400. package/test/fixtures/quality/custom.yml +22 -0
  401. package/test/fixtures/quality/go-duck.yml +22 -0
  402. package/test/fixtures/quality/jhipster.yml +30 -0
  403. package/test/fixtures/quality/representative.gdl +14 -0
  404. package/test/list-state.js +138 -0
  405. package/test/micro-functions-layout.js +95 -0
  406. package/test/performance-generation.js +32 -0
  407. package/test/pmb-preset.js +127 -0
  408. package/test/project-workflow.js +111 -0
  409. package/test/quality-features.js +250 -0
  410. package/test/verify-commands.js +83 -0
  411. package/test/verify.js +959 -0
  412. package/test-files/GDL/example.gdl +15 -0
  413. package/test-files/JDL/example.jdl +15 -0
  414. package/test-files/config/crudforge.config.yml +127 -0
  415. package/test-run.sh +272 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Page ↔ embedded-screen hierarchy helpers.
3
+ *
4
+ * Source of truth at runtime: `public/assets/screen-navigation-map.json (served as assets/...)`
5
+ * regenerated by access-control-tools (`lib/hierarchy-scanner.js` via `npm run access-control:sync`).
6
+ * Do not hardcode module-specific page trees here.
7
+ */
8
+
9
+ export interface EmbeddedScreenRef {
10
+ screenId: string;
11
+ title: string;
12
+ /** Primary working surface on the page (e.g. main form). */
13
+ isPrimary?: boolean;
14
+ }
15
+
16
+ export interface PageNavigationEntry {
17
+ domain: string;
18
+ navTitle: string;
19
+ route: string;
20
+ /** Primary host SCREEN id used in the page component. */
21
+ hostScreenId: string;
22
+ /** Legacy / alternate host ids that should resolve to the same page group. */
23
+ hostScreenAliases?: string[];
24
+ embeddedScreens: EmbeddedScreenRef[];
25
+ }
26
+
27
+ export interface ScreenNavigationMapFile {
28
+ version?: number;
29
+ generatedAt?: string;
30
+ pages: PageNavigationEntry[];
31
+ unresolved?: Array<{
32
+ hostScreenId?: string | null;
33
+ selector: string;
34
+ reason: string;
35
+ file?: string | null;
36
+ }>;
37
+ stats?: Record<string, number>;
38
+ }
39
+
40
+ /** Empty until assets JSON is loaded (or sync has not been run yet). */
41
+ export const EMPTY_SCREEN_NAVIGATION_MAP: readonly PageNavigationEntry[] = [];
42
+
43
+ /** Component ids that must stay explicit — never auto-granted with screen / page essentials. */
44
+ const SENSITIVE_COMPONENT_ID_RE =
45
+ /(?:^|-)(delete|remove|approve|finalize|reject|revoke)(?:-|$)/i;
46
+
47
+ export function isSensitiveComponentId(componentId: string | null | undefined): boolean {
48
+ const id = String(componentId || '').trim().toLowerCase();
49
+ if (!id) {
50
+ return false;
51
+ }
52
+ return SENSITIVE_COMPONENT_ID_RE.test(id);
53
+ }
54
+
55
+ export function filterEssentialComponentIds(componentIds: string[]): string[] {
56
+ return componentIds.filter((id) => id && !isSensitiveComponentId(id));
57
+ }
58
+
59
+ function hostIdSet(entry: PageNavigationEntry): Set<string> {
60
+ return new Set(
61
+ [entry.hostScreenId, ...(entry.hostScreenAliases || [])]
62
+ .map((id) => String(id || '').trim().toLowerCase())
63
+ .filter(Boolean)
64
+ );
65
+ }
66
+
67
+ export function normalizeNavigationPages(
68
+ pages: PageNavigationEntry[] | null | undefined
69
+ ): PageNavigationEntry[] {
70
+ if (!Array.isArray(pages)) {
71
+ return [];
72
+ }
73
+ return pages
74
+ .filter((page) => page && page.hostScreenId)
75
+ .map((page) => ({
76
+ domain: page.domain || 'Application',
77
+ navTitle: page.navTitle || page.hostScreenId,
78
+ route: page.route || '',
79
+ hostScreenId: String(page.hostScreenId),
80
+ hostScreenAliases: Array.isArray(page.hostScreenAliases)
81
+ ? page.hostScreenAliases.map(String)
82
+ : [],
83
+ embeddedScreens: Array.isArray(page.embeddedScreens)
84
+ ? page.embeddedScreens
85
+ .filter((e) => e?.screenId)
86
+ .map((e) => ({
87
+ screenId: String(e.screenId),
88
+ title: e.title || String(e.screenId),
89
+ isPrimary: Boolean(e.isPrimary),
90
+ }))
91
+ : [],
92
+ }));
93
+ }
94
+
95
+ /** Embedded screenId → host screen ids that embed it (may be multiple routes). */
96
+ export function buildEmbeddedScreenParentMap(
97
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
98
+ ): Record<string, string[]> {
99
+ const result: Record<string, string[]> = {};
100
+ map.forEach((entry) => {
101
+ entry.embeddedScreens.forEach((embedded) => {
102
+ const child = String(embedded.screenId || '').trim().toLowerCase();
103
+ if (!child) {
104
+ return;
105
+ }
106
+ const parents = result[child] || [];
107
+ const host = String(entry.hostScreenId || '').trim().toLowerCase();
108
+ if (host && !parents.includes(host)) {
109
+ parents.push(host);
110
+ }
111
+ (entry.hostScreenAliases || []).forEach((alias) => {
112
+ const a = String(alias || '').trim().toLowerCase();
113
+ if (a && !parents.includes(a)) {
114
+ parents.push(a);
115
+ }
116
+ });
117
+ result[child] = parents;
118
+ });
119
+ });
120
+ return result;
121
+ }
122
+
123
+ /** Host screenId (or alias) → embedded child screen ids. */
124
+ export function buildHostEmbeddedChildrenMap(
125
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
126
+ ): Record<string, string[]> {
127
+ const result: Record<string, string[]> = {};
128
+ map.forEach((entry) => {
129
+ const children = entry.embeddedScreens
130
+ .map((e) => String(e.screenId || '').trim().toLowerCase())
131
+ .filter(Boolean);
132
+ hostIdSet(entry).forEach((host) => {
133
+ const existing = result[host] || [];
134
+ children.forEach((child) => {
135
+ if (!existing.includes(child)) {
136
+ existing.push(child);
137
+ }
138
+ });
139
+ result[host] = existing;
140
+ });
141
+ });
142
+ return result;
143
+ }
144
+
145
+ export function getParentHostScreenIds(
146
+ embeddedScreenId: string,
147
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
148
+ ): string[] {
149
+ const key = String(embeddedScreenId || '').trim().toLowerCase();
150
+ return buildEmbeddedScreenParentMap(map)[key] || [];
151
+ }
152
+
153
+ export function getEmbeddedChildScreenIds(
154
+ hostScreenId: string,
155
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
156
+ ): string[] {
157
+ const key = String(hostScreenId || '').trim().toLowerCase();
158
+ return buildHostEmbeddedChildrenMap(map)[key] || [];
159
+ }
160
+
161
+ export function findPageNavigationEntry(
162
+ screenId: string,
163
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
164
+ ): PageNavigationEntry | undefined {
165
+ const key = String(screenId || '').trim().toLowerCase();
166
+ if (!key) {
167
+ return undefined;
168
+ }
169
+ return map.find((entry) => hostIdSet(entry).has(key));
170
+ }
171
+
172
+ /** All screen ids claimed by the navigation map (hosts + embeds) for flat-list exclusion. */
173
+ export function getMappedScreenIds(
174
+ map: readonly PageNavigationEntry[] = EMPTY_SCREEN_NAVIGATION_MAP
175
+ ): Set<string> {
176
+ const ids = new Set<string>();
177
+ map.forEach((entry) => {
178
+ hostIdSet(entry).forEach((id) => ids.add(id));
179
+ entry.embeddedScreens.forEach((embedded) => {
180
+ const child = String(embedded.screenId || '').trim().toLowerCase();
181
+ if (child) {
182
+ ids.add(child);
183
+ }
184
+ });
185
+ });
186
+ return ids;
187
+ }
@@ -0,0 +1,64 @@
1
+ import { Injectable, inject, signal, computed } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { catchError, of } from 'rxjs';
4
+ import {
5
+ PageNavigationEntry,
6
+ ScreenNavigationMapFile,
7
+ buildEmbeddedScreenParentMap,
8
+ buildHostEmbeddedChildrenMap,
9
+ normalizeNavigationPages,
10
+ } from './screen-navigation-map';
11
+
12
+ /**
13
+ * Loads generated `assets/screen-navigation-map.json` (from access-control sync).
14
+ * Matrix + AccessControlService read host/embed relationships from here — not hardcoding.
15
+ */
16
+ @Injectable({ providedIn: 'root' })
17
+ export class ScreenNavigationRegistry {
18
+ private readonly http = inject(HttpClient);
19
+
20
+ private readonly pagesSignal = signal<PageNavigationEntry[]>([]);
21
+ private readonly loadedSignal = signal(false);
22
+ private readonly loadErrorSignal = signal<string | null>(null);
23
+
24
+ readonly pages = this.pagesSignal.asReadonly();
25
+ readonly isLoaded = this.loadedSignal.asReadonly();
26
+ readonly loadError = this.loadErrorSignal.asReadonly();
27
+
28
+ readonly parentByChild = computed(() => buildEmbeddedScreenParentMap(this.pagesSignal()));
29
+ readonly childrenByHost = computed(() => buildHostEmbeddedChildrenMap(this.pagesSignal()));
30
+
31
+ constructor() {
32
+ this.reload();
33
+ }
34
+
35
+ reload(): void {
36
+ this.http
37
+ .get<ScreenNavigationMapFile>('assets/screen-navigation-map.json')
38
+ .pipe(
39
+ catchError((err) => {
40
+ console.warn(
41
+ '[ScreenNavigationRegistry] Failed to load screen-navigation-map.json — matrix stays flat until sync generates it.',
42
+ err
43
+ );
44
+ this.loadErrorSignal.set(String(err?.message || err || 'load failed'));
45
+ this.loadedSignal.set(true);
46
+ return of({ pages: [] } as ScreenNavigationMapFile);
47
+ })
48
+ )
49
+ .subscribe((file) => {
50
+ this.pagesSignal.set(normalizeNavigationPages(file?.pages));
51
+ this.loadedSignal.set(true);
52
+ });
53
+ }
54
+
55
+ getParentHostScreenIds(embeddedScreenId: string): string[] {
56
+ const key = String(embeddedScreenId || '').trim().toLowerCase();
57
+ return this.parentByChild()[key] || [];
58
+ }
59
+
60
+ getEmbeddedChildScreenIds(hostScreenId: string): string[] {
61
+ const key = String(hostScreenId || '').trim().toLowerCase();
62
+ return this.childrenByHost()[key] || [];
63
+ }
64
+ }
@@ -0,0 +1,39 @@
1
+ import { Pipe, PipeTransform, inject } from '@angular/core';
2
+ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
3
+
4
+ function escapeHtml(value: string): string {
5
+ return value
6
+ .replace(/&/g, '&amp;')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&#39;');
11
+ }
12
+
13
+ function escapeRegExp(value: string): string {
14
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ }
16
+
17
+ @Pipe({
18
+ name: 'textHighlight',
19
+ standalone: true,
20
+ })
21
+ export class TextHighlightPipe implements PipeTransform {
22
+ private readonly sanitizer = inject(DomSanitizer);
23
+
24
+ transform(
25
+ value: string | number | null | undefined,
26
+ query: string | null | undefined,
27
+ highlightClass = 'bg-amber-200 font-semibold rounded-sm px-0.5'
28
+ ): SafeHtml | string {
29
+ const text = value == null ? '' : String(value);
30
+ const needle = String(query || '').trim();
31
+ if (!text || !needle) {
32
+ return text;
33
+ }
34
+ const escapedText = escapeHtml(text);
35
+ const regex = new RegExp(`(${escapeRegExp(needle)})`, 'gi');
36
+ const highlighted = escapedText.replace(regex, (match) => `<span class="${highlightClass}">${match}</span>`);
37
+ return this.sanitizer.bypassSecurityTrustHtml(highlighted);
38
+ }
39
+ }
@@ -0,0 +1,212 @@
1
+ <div class="flex min-h-0 flex-auto flex-col overflow-hidden min-w-0">
2
+ <!-- Header -->
3
+ <div class="flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between p-6 sm:py-8 border-b bg-card dark:bg-transparent">
4
+ <div class="flex-1 min-w-0">
5
+ <!-- Breadcrumbs -->
6
+ <div class="flex items-center gap-x-2 text-xs font-semibold uppercase tracking-wider text-slate-500">
7
+ <a *ngIf="SCREEN | accessCtrl: 'admin-user-management'" id="qa-access-matrix__admin-user-management" data-qa-id="qa-access-matrix__admin-user-management" data-component-id="admin-user-management" data-screen-id="access-matrix" routerLink="/admin/user-management" class="hover:underline text-primary-600 dark:text-primary-400">Users</a>
8
+ <span class="text-slate-400">/</span>
9
+ <span>User Access Matrix</span>
10
+ </div>
11
+ <!-- Title -->
12
+ <div class="mt-2 text-3xl font-extrabold tracking-tight leading-none text-slate-900 dark:text-white flex items-center gap-x-3">
13
+ <span>Access Matrix:</span>
14
+ @if (user()) {
15
+ <span class="text-primary-700 dark:text-primary-400 font-semibold">{{ user()?.firstName }} {{ user()?.lastName }}</span>
16
+ } @else {
17
+ <span class="text-slate-400 font-normal">Loading User...</span>
18
+ }
19
+ </div>
20
+ </div>
21
+ <!-- Actions -->
22
+ <div class="flex shrink-0 items-center gap-4 mt-6 sm:mt-0">
23
+ <a *ngIf="SCREEN | accessCtrl: 'admin-user-management-2'" id="qa-access-matrix__admin-user-management-2" data-qa-id="qa-access-matrix__admin-user-management-2" data-component-id="admin-user-management-2" data-screen-id="access-matrix" routerLink="/admin/user-management"
24
+ class="inline-flex items-center justify-center px-4 py-2 border border-slate-300 rounded-xl bg-white text-sm font-semibold text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none transition dark:bg-slate-800 dark:text-slate-200 dark:border-slate-700 dark:hover:bg-slate-700">
25
+ <mat-icon class="mr-2 text-slate-500 dark:text-slate-400" svgIcon="arrow-left"></mat-icon>
26
+ <span>Back to Users</span>
27
+ </a>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Main Content -->
32
+ @if (isLoading()) {
33
+ <div class="flex flex-auto flex-col items-center justify-center p-8 min-h-0">
34
+ <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-600"></div>
35
+ <div class="mt-4 text-sm font-medium text-slate-500">Loading user profile & roles mapping...</div>
36
+ </div>
37
+ } @else {
38
+ <div class="min-h-0 flex-auto overflow-y-auto p-6 sm:p-10">
39
+ <!-- User summary banner -->
40
+ @if (user()) {
41
+ <div class="bg-card rounded-2xl border shadow-sm p-6 mb-8 flex flex-col md:flex-row md:items-center justify-between gap-6 dark:bg-slate-900/50">
42
+ <div class="flex items-center gap-4">
43
+ <div class="size-14 rounded-full bg-primary-50 dark:bg-primary-950/20 border border-primary-100 dark:border-primary-900/40 text-primary-700 dark:text-primary-400 flex items-center justify-center text-xl font-bold uppercase">
44
+ {{ user()?.firstName?.substring(0,1) }}{{ user()?.lastName?.substring(0,1) }}
45
+ </div>
46
+ <div>
47
+ <div class="text-xl font-bold text-slate-900 dark:text-white">
48
+ {{ user()?.firstName }} {{ user()?.lastName }}
49
+ </div>
50
+ <div class="text-sm text-slate-500 flex items-center gap-x-2 mt-0.5">
51
+ <span>&#64;{{ user()?.username }}</span>
52
+ <span class="text-slate-300 dark:text-slate-700">•</span>
53
+ <span>{{ user()?.email || 'No Email' }}</span>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="flex items-center gap-x-3">
58
+ <span class="text-xs font-semibold uppercase tracking-wider text-slate-400">Account Status</span>
59
+ <span class="inline-flex items-center gap-x-1.5 px-3 py-1 rounded-full text-xs font-semibold"
60
+ [class.bg-primary-100]="user()?.enabled" [class.text-primary-800]="user()?.enabled"
61
+ [class.bg-red-100]="!user()?.enabled" [class.text-red-800]="!user()?.enabled">
62
+ <span class="size-1.5 rounded-full" [class.bg-primary-600]="user()?.enabled" [class.bg-red-600]="!user()?.enabled"></span>
63
+ <span>{{ user()?.enabled ? 'Active' : 'Disabled' }}</span>
64
+ </span>
65
+ </div>
66
+ </div>
67
+ }
68
+
69
+ <div class="w-full">
70
+ <!-- Roles Toggles Card -->
71
+ <div class="bg-card rounded-2xl border shadow-sm overflow-hidden dark:bg-slate-900/50 flex flex-col">
72
+ <div class="px-6 py-5 border-b flex flex-col md:flex-row md:items-center justify-between gap-4">
73
+ <div>
74
+ <div class="text-lg font-bold text-slate-900 dark:text-white">Identity Role Mapping</div>
75
+ </div>
76
+
77
+ <!-- Role Search Feature -->
78
+ <div class="flex items-center gap-4 flex-1 max-w-md">
79
+ <div class="relative w-full">
80
+ <mat-icon svgIcon="search" class="absolute left-3 top-1/2 -translate-y-1/2 !w-4 !h-4 text-slate-400"></mat-icon>
81
+ <input
82
+ type="text"
83
+ [value]="searchTerm()"
84
+ (input)="onSearchInput($event)"
85
+ placeholder="Search roles by name or description..."
86
+ class="w-full rounded-xl border border-slate-200 bg-slate-50/80 py-2 pl-9 pr-9 text-xs font-medium text-slate-900 placeholder:text-slate-400 focus:border-primary-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-primary-500/20 dark:border-slate-800 dark:bg-slate-900/60 dark:text-white dark:focus:border-primary-500 dark:focus:bg-slate-950"
87
+ />
88
+ @if (searchTerm()) {
89
+ <button id="qa-access-matrix__clear-search" data-qa-id="qa-access-matrix__clear-search" data-component-id="clear-search" data-screen-id="access-matrix" *ngIf="SCREEN | accessCtrl: 'clear-search'"
90
+ type="button"
91
+ (click)="clearSearch()"
92
+ title="Clear role search"
93
+ class="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 flex items-center justify-center">
94
+ <mat-icon svgIcon="x" class="!w-4 !h-4"></mat-icon>
95
+ </button>
96
+ }
97
+ </div>
98
+ </div>
99
+
100
+ <div class="flex flex-wrap items-center gap-2">
101
+ <span class="rounded-full bg-primary-100 px-3 py-1 text-[11px] font-bold uppercase tracking-wider text-primary-800 dark:bg-primary-500/15 dark:text-primary-300">
102
+ {{ assignedRoleCount() }} Assigned
103
+ </span>
104
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-[11px] font-bold uppercase tracking-wider text-slate-600 dark:bg-slate-800 dark:text-slate-300">
105
+ {{ allRoles().length }} Roles
106
+ </span>
107
+ </div>
108
+ </div>
109
+
110
+ <div class="p-6">
111
+ @if (filteredAssignedRoleList().length) {
112
+ <div class="mb-8">
113
+ <div class="mb-4">
114
+ <div class="text-sm font-bold text-primary-800 dark:text-primary-300">Assigned Roles</div>
115
+ </div>
116
+ <div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
117
+ @for (role of filteredAssignedRoleList(); track role.name) {
118
+ <ng-container *ngTemplateOutlet="roleCard; context: { $implicit: role, assigned: true }"></ng-container>
119
+ }
120
+ </div>
121
+ </div>
122
+ }
123
+
124
+ @if (filteredUnassignedRoleList().length) {
125
+ <div>
126
+ <div class="mb-4 flex items-center gap-2">
127
+ <div class="text-sm font-bold text-slate-800 dark:text-slate-200">Available Roles</div>
128
+ <span class="text-xs text-slate-500">{{ filteredUnassignedRoleList().length }} remaining</span>
129
+ </div>
130
+ <div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
131
+ @for (role of filteredUnassignedRoleList(); track role.name) {
132
+ <ng-container *ngTemplateOutlet="roleCard; context: { $implicit: role, assigned: false }"></ng-container>
133
+ }
134
+ </div>
135
+ </div>
136
+ }
137
+
138
+ @if (searchTerm() && filteredAssignedRoleList().length === 0 && filteredUnassignedRoleList().length === 0) {
139
+ <div class="text-center p-8 text-slate-500">
140
+ No roles matching "<span class="font-semibold text-slate-800 dark:text-slate-200">{{ searchTerm() }}</span>"
141
+ </div>
142
+ }
143
+
144
+ <ng-template #roleCard let-role let-assigned="assigned">
145
+ <div
146
+ class="flex h-full flex-col justify-between rounded-xl border p-4 transition duration-150"
147
+ [class.border-primary-200]="assigned"
148
+ [class.bg-primary-50/40]="assigned"
149
+ [class.dark:border-primary-900/40]="assigned"
150
+ [class.dark:bg-primary-950/10]="assigned"
151
+ [class.border-slate-100]="!assigned"
152
+ [class.hover:bg-slate-50/50]="!assigned"
153
+ [class.dark:border-slate-800/80]="!assigned"
154
+ [class.dark:hover:bg-slate-800/10]="!assigned"
155
+ >
156
+ <div class="min-w-0 flex-1">
157
+ <div class="font-bold text-slate-800 dark:text-slate-200 flex flex-wrap items-center gap-x-2 gap-y-1">
158
+ <span class="break-all" [innerHTML]="formatRoleName(role.name) | textHighlight: searchTerm(): 'bg-amber-300 font-bold text-slate-900 px-1 rounded-sm'"></span>
159
+ @if (assigned) {
160
+ <span class="text-[10px] font-bold uppercase tracking-wider text-primary-700 bg-primary-50 dark:bg-primary-950/20 dark:text-primary-400 border border-primary-100 dark:border-primary-900/40 px-2 py-0.5 rounded-md">
161
+ Assigned
162
+ </span>
163
+ }
164
+ </div>
165
+ <div class="text-xs text-slate-500 mt-2 line-clamp-3" [innerHTML]="(role.description || 'No role description.') | textHighlight: searchTerm(): 'bg-amber-300 font-bold text-slate-900 px-1 rounded-sm'"></div>
166
+ </div>
167
+
168
+ <div class="mt-4 flex items-center justify-between border-t pt-3"
169
+ [class.border-primary-100]="assigned"
170
+ [class.dark:border-primary-900/30]="assigned"
171
+ [class.border-slate-100]="!assigned"
172
+ [class.dark:border-slate-800/80]="!assigned">
173
+ <span class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Grant Access</span>
174
+ <div class="flex items-center gap-2">
175
+ @if (isProcessingRole()[role.name || '']) {
176
+ <div class="animate-spin rounded-full h-5 w-5 border-t-2 border-primary-600"></div>
177
+ }
178
+ <div id="qa-access-matrix__request-role-toggle" data-qa-id="qa-access-matrix__request-role-toggle" data-component-id="request-role-toggle" data-screen-id="access-matrix" *ngIf="SCREEN | accessCtrl: 'request-role-toggle'"
179
+ class="role-access-toggle-trigger"
180
+ [class.role-access-toggle-trigger--disabled]="isRoleToggleDisabled(role.name || '')"
181
+ role="button"
182
+ tabindex="0"
183
+ title="Toggle role access"
184
+ [attr.aria-label]="isAssigned(role.name || '') ? 'Revoke role access' : 'Grant role access'"
185
+ [attr.title]="isAssigned(role.name || '') ? 'Revoke role access' : 'Grant role access'"
186
+ (click)="requestRoleToggle(role.name || '')"
187
+ (keydown.enter)="requestRoleToggle(role.name || '')"
188
+ (keydown.space)="$event.preventDefault(); requestRoleToggle(role.name || '')">
189
+ <mat-slide-toggle id="qa-mat-slide-toggle-user-access-matrix-btn" data-qa-id="qa-mat-slide-toggle-user-access-matrix-btn" data-component-id="mat-slide-toggle-user-access-matrix-btn" data-screen-id="user-access-matrix"
190
+ class="role-access-toggle-display"
191
+ [checked]="isAssigned(role.name || '')"
192
+ [disabled]="isRoleToggleDisabled(role.name || '')"
193
+ tabindex="-1"
194
+ color="primary">
195
+ </mat-slide-toggle>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </ng-template>
201
+
202
+ @if (allRoles().length === 0) {
203
+ <div class="text-center p-8 text-slate-500">
204
+ No realm roles found in Keycloak. Go to "Role Management" to create some.
205
+ </div>
206
+ }
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ }
212
+ </div>
@@ -0,0 +1,34 @@
1
+ /* SCSS styles for User Access Matrix Component */
2
+ :host {
3
+ display: flex;
4
+ flex: 1 1 auto;
5
+ width: 100%;
6
+ }
7
+
8
+ .mat-mdc-slide-toggle {
9
+ --mdc-switch-selected-handle-color: var(--theme-color-primary-600);
10
+ --mdc-switch-selected-track-color: var(--theme-color-primary-200);
11
+ --mdc-switch-unselected-handle-color: #f1f5f9;
12
+ --mdc-switch-unselected-track-color: #cbd5e1;
13
+ }
14
+
15
+ .role-access-toggle-trigger {
16
+ display: inline-flex;
17
+ cursor: pointer;
18
+ border-radius: 9999px;
19
+ outline: none;
20
+ }
21
+
22
+ .role-access-toggle-trigger:focus-visible {
23
+ box-shadow: 0 0 0 2px var(--theme-color-primary-500);
24
+ }
25
+
26
+ .role-access-toggle-trigger--disabled {
27
+ cursor: not-allowed;
28
+ opacity: 0.6;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .role-access-toggle-display {
33
+ pointer-events: none;
34
+ }