@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,157 @@
1
+ import { Component, inject, signal, computed, OnInit, ViewEncapsulation } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import { AccessCtrlPipe, KeycloakAdminService, UserRepresentation } from '@/app/core/auth';
8
+
9
+ export interface UserSelectDialogData {
10
+ roleName: string;
11
+ alreadyAssignedUserIds: Set<string>;
12
+ }
13
+
14
+ @Component({
15
+ selector: 'app-user-select-dialog',
16
+ standalone: true,
17
+ imports: [AccessCtrlPipe, CommonModule, FormsModule, MatDialogModule, MatButtonModule, MatIconModule],
18
+ encapsulation: ViewEncapsulation.None,
19
+ styles: [
20
+ `
21
+ @keyframes slideUp {
22
+ from {
23
+ transform: translateY(100%);
24
+ }
25
+ to {
26
+ transform: translateY(0);
27
+ }
28
+ }
29
+
30
+ @keyframes slideDown {
31
+ from {
32
+ transform: translateY(0);
33
+ }
34
+ to {
35
+ transform: translateY(100%);
36
+ }
37
+ }
38
+
39
+ .bottom-sheet-dialog {
40
+ animation: slideUp 280ms cubic-bezier(0, 0, 0.2, 1) forwards !important;
41
+ margin-bottom: 0 !important;
42
+ vertical-align: bottom !important;
43
+ }
44
+
45
+ .bottom-sheet-dialog .mat-mdc-dialog-container {
46
+ border-bottom-left-radius: 0 !important;
47
+ border-bottom-right-radius: 0 !important;
48
+ border-top-left-radius: 24px !important;
49
+ border-top-right-radius: 24px !important;
50
+ padding: 0 !important;
51
+ overflow: hidden !important;
52
+ }
53
+
54
+ .bottom-sheet-dialog .mdc-dialog__surface {
55
+ border-bottom-left-radius: 0 !important;
56
+ border-bottom-right-radius: 0 !important;
57
+ border-top-left-radius: 24px !important;
58
+ border-top-right-radius: 24px !important;
59
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1) !important;
60
+ }
61
+
62
+ .slide-down-anim {
63
+ animation: slideDown 240ms cubic-bezier(0.4, 0, 1, 1) forwards !important;
64
+ }
65
+
66
+ .no-scrollbar::-webkit-scrollbar {
67
+ display: none !important;
68
+ }
69
+
70
+ .no-scrollbar {
71
+ -ms-overflow-style: none !important;
72
+ scrollbar-width: none !important;
73
+ }
74
+ `,
75
+ ],
76
+ templateUrl: './user-select-dialog.component.html',
77
+ })
78
+ export class UserSelectDialogComponent implements OnInit {
79
+ readonly SCREEN = 'user-select-dialog';
80
+ private dialogRef = inject(MatDialogRef<UserSelectDialogComponent>);
81
+ private data = inject<UserSelectDialogData>(MAT_DIALOG_DATA);
82
+ private keycloakAdminService = inject(KeycloakAdminService);
83
+
84
+ searchQuery = '';
85
+ searchQuerySignal = signal<string>('');
86
+ allUsers = signal<UserRepresentation[]>([]);
87
+ isLoading = signal<boolean>(false);
88
+ isClosing = signal<boolean>(false);
89
+
90
+ users = computed(() => {
91
+ const query = this.searchQuerySignal().trim().toLowerCase();
92
+ const list = this.allUsers();
93
+ if (!query) {
94
+ return list;
95
+ }
96
+ return list.filter((user) => {
97
+ const username = (user.username || '').toLowerCase();
98
+ const email = (user.email || '').toLowerCase();
99
+ const firstName = (user.firstName || '').toLowerCase();
100
+ const lastName = (user.lastName || '').toLowerCase();
101
+ const fullName = `${firstName} ${lastName}`.trim();
102
+
103
+ return (
104
+ username.includes(query) || email.includes(query) || fullName.includes(query)
105
+ );
106
+ });
107
+ });
108
+
109
+ ngOnInit(): void {
110
+ this.isLoading.set(true);
111
+ this.keycloakAdminService.searchUsers('', 0, 1000).subscribe({
112
+ next: (res) => {
113
+ this.allUsers.set(res.users || []);
114
+ this.isLoading.set(false);
115
+ },
116
+ error: (err) => {
117
+ console.error('Failed loading users for bottom-sheet select', err);
118
+ this.isLoading.set(false);
119
+ },
120
+ });
121
+ }
122
+
123
+ onSearchChange(val: string): void {
124
+ this.searchQuerySignal.set(val);
125
+ }
126
+
127
+ clearSearch(): void {
128
+ this.searchQuery = '';
129
+ this.searchQuerySignal.set('');
130
+ }
131
+
132
+ isAssigned(user: UserRepresentation): boolean {
133
+ return !!user.id && this.data.alreadyAssignedUserIds.has(user.id);
134
+ }
135
+
136
+ assignUser(user: UserRepresentation): void {
137
+ if (!user.id) return;
138
+ this.isLoading.set(true);
139
+ this.keycloakAdminService.assignRealmRoleToUser(user.id, this.data.roleName).subscribe({
140
+ next: () => {
141
+ this.data.alreadyAssignedUserIds.add(user.id!);
142
+ this.isLoading.set(false);
143
+ },
144
+ error: (err) => {
145
+ console.error(err);
146
+ this.isLoading.set(false);
147
+ },
148
+ });
149
+ }
150
+
151
+ onClose(): void {
152
+ this.isClosing.set(true);
153
+ setTimeout(() => {
154
+ this.dialogRef.close(true);
155
+ }, 240);
156
+ }
157
+ }
@@ -0,0 +1,355 @@
1
+ <div class="flex flex-col flex-1 min-h-0 min-w-0 h-full w-full overflow-hidden bg-slate-50 dark:bg-[#0D1E3A]">
2
+ <!-- Header -->
3
+ <div class="flex flex-col sm:flex-row flex-0 shrink-0 sm:items-center sm:justify-between p-6 sm:py-6 border-b bg-card dark:bg-[#0D1E3A] z-10">
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-role-permission-matrix__admin-user-management" data-qa-id="qa-role-permission-matrix__admin-user-management" data-component-id="admin-user-management" data-screen-id="role-permission-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
+ <a *ngIf="SCREEN | accessCtrl: 'admin-user-management-roles'" id="qa-role-permission-matrix__admin-user-management-roles" data-qa-id="qa-role-permission-matrix__admin-user-management-roles" data-component-id="admin-user-management-roles" data-screen-id="role-permission-matrix" routerLink="/admin/user-management/roles" class="hover:underline text-primary-600 dark:text-primary-400">Role Management</a>
10
+ <span class="text-slate-400">/</span>
11
+ <span>Permission Matrix</span>
12
+ </div>
13
+ <!-- Title -->
14
+ <div class="mt-2 text-3xl font-extrabold tracking-tight leading-none text-slate-900 dark:text-white flex items-center gap-x-3">
15
+ <span>Role Permission Matrix</span>
16
+ @if (isAnyToggleProcessing()) {
17
+ <span class="inline-flex items-center gap-x-1.5 rounded-lg bg-amber-50 dark:bg-amber-950/30 px-2.5 py-1 text-xs font-semibold text-amber-800 dark:text-amber-400 ring-1 ring-inset ring-amber-600/20 dark:ring-amber-500/20 animate-pulse">
18
+ <mat-icon svgIcon="refresh-cw" class="size-3.5 animate-spin text-amber-600 dark:text-amber-400"></mat-icon>
19
+ Syncing Permissions...
20
+ </span>
21
+ }
22
+ </div>
23
+ @if (filteredRoleName()) {
24
+ <div class="mt-1 text-sm text-slate-500 flex flex-wrap items-center gap-x-3 gap-y-2">
25
+ <span class="inline-flex items-center gap-x-1.5 rounded-lg bg-primary-50 dark:bg-primary-950/30 px-2.5 py-1 text-xs font-semibold text-primary-800 dark:text-primary-400 ring-1 ring-inset ring-primary-600/20 dark:ring-primary-500/20">
26
+ <span class="size-1.5 rounded-full bg-primary-500"></span>
27
+ Filtering Role: {{ formatRoleName(filteredRoleName()) }}
28
+ </span>
29
+ <button id="qa-role-permission-matrix__clear-role-filter" data-qa-id="qa-role-permission-matrix__clear-role-filter" data-component-id="clear-role-filter" data-screen-id="role-permission-matrix" *ngIf="SCREEN | accessCtrl: 'clear-role-filter'" (click)="clearRoleFilter()" [disabled]="isUiLocked()" class="inline-flex items-center text-xs text-primary-600 hover:text-primary-800 dark:text-primary-400 dark:hover:text-primary-200 underline font-semibold focus:outline-none transition disabled:opacity-50 disabled:cursor-not-allowed">
30
+ Show All Roles
31
+ </button>
32
+ </div>
33
+ }
34
+ </div>
35
+ <!-- Actions & Search -->
36
+ <div class="flex flex-col sm:flex-row items-center gap-3 mt-6 sm:mt-0">
37
+ <!-- Search Group -->
38
+ <div class="flex items-center w-full sm:w-[460px] rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-[#0D1E3A] shadow-sm transition-all focus-within:ring-1 focus-within:ring-primary-500 focus-within:border-primary-500 overflow-hidden h-10">
39
+ <!-- Dropdown -->
40
+ <div class="relative flex items-center border-r border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800 h-full">
41
+ <select [value]="searchScope()" (change)="onSearchScopeChange($event, searchInput)"
42
+ class="h-full py-0 pl-3 pr-7 bg-transparent border-0 text-sm font-medium text-slate-600 dark:text-slate-300 focus:ring-0 cursor-pointer appearance-none outline-none">
43
+ <option value="ALL">All Levels</option>
44
+ <option value="SCREEN">Screens Only</option>
45
+ </select>
46
+ <mat-icon class="absolute right-1.5 size-4 text-slate-400 pointer-events-none" svgIcon="chevron-down"></mat-icon>
47
+ </div>
48
+ <!-- Input -->
49
+ <div class="relative flex-1 flex items-center h-full">
50
+ <mat-icon class="absolute left-3 size-4.5 text-slate-400 pointer-events-none" svgIcon="search"></mat-icon>
51
+ <input #searchInput [value]="searchTerm()" (input)="onSearchTermChange($event)" type="text"
52
+ class="w-full h-full pl-9 pr-10 bg-transparent border-0 text-sm text-slate-900 dark:text-white placeholder-slate-400 focus:ring-0 outline-none"
53
+ [placeholder]="searchScope() === 'ALL' ? 'Search screens, components, descriptions…' : 'Search screens only…'">
54
+ @if (searchTerm()) {
55
+ <button id="qa-role-permission-matrix__clear-search" data-qa-id="qa-role-permission-matrix__clear-search" data-component-id="clear-search" data-screen-id="role-permission-matrix" *ngIf="SCREEN | accessCtrl: 'clear-search'" (click)="clearSearch()" class="absolute right-2 p-1 rounded-full text-slate-400 hover:text-slate-600 hover:bg-slate-200 dark:hover:text-slate-200 dark:hover:bg-slate-700 transition-colors focus:outline-none flex items-center justify-center">
56
+ <mat-icon class="size-4" svgIcon="x"></mat-icon>
57
+ </button>
58
+ }
59
+ </div>
60
+ </div>
61
+
62
+ <button *ngIf="SCREEN | accessCtrl: 'load-data'" id="qa-role-permission-matrix__load-data" data-qa-id="qa-role-permission-matrix__load-data" data-component-id="load-data" data-screen-id="role-permission-matrix" (click)="loadRolesAndMappings()" [disabled]="isUiLocked()"
63
+ class="inline-flex items-center justify-center px-4 h-10 bg-white dark:bg-[#0D1E3A] border border-slate-300 dark:border-slate-800 rounded-xl text-sm font-semibold text-slate-700 dark:text-slate-200 shadow-sm hover:bg-slate-50 dark:hover:bg-slate-800 transition disabled:opacity-50 disabled:cursor-not-allowed">
64
+ <mat-icon class="mr-2 text-slate-500 dark:text-slate-400 size-5" [class.animate-spin]="isUiLocked()" svgIcon="refresh-cw"></mat-icon>
65
+ <span>Refresh Grid</span>
66
+ </button>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Main Content -->
71
+ <div class="flex flex-col flex-1 min-h-0 min-w-0 overflow-hidden">
72
+ <!-- Matrix Table -->
73
+ <div class="bg-card dark:bg-[#0D1E3A]/50 flex flex-col flex-1 min-h-0 min-w-0 overflow-hidden border-t">
74
+ <!-- Scrollable Grid -->
75
+ <div class="matrix-scroll overflow-auto flex-1 min-h-0 min-w-0 w-full bg-white dark:bg-[#0D1E3A]">
76
+ <table class="matrix-table border-separate border-spacing-0">
77
+ <thead>
78
+ <tr class="border-b bg-slate-50 dark:bg-slate-800/20">
79
+ <!-- Sticky Top-Left Corner Header -->
80
+ <th class="sticky left-0 top-0 z-30 px-6 py-4 text-left text-xs font-semibold uppercase tracking-wider text-slate-500 bg-slate-50 dark:bg-slate-800/60 border-r border-b border-slate-200 dark:border-slate-800 min-w-[380px]">
81
+ System Screens & Components
82
+ </th>
83
+ <!-- Sticky Role Columns -->
84
+ @if (isLoading()) {
85
+ @for (i of [1,2,3,4,5,6]; track i) {
86
+ <th class="sticky top-0 z-20 px-6 py-4 text-center text-xs font-semibold uppercase tracking-wider text-slate-600 dark:text-slate-300 min-w-[170px] bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-800">
87
+ <div class="flex flex-col items-center">
88
+ <div class="h-6 w-24 animate-pulse rounded bg-slate-200 dark:bg-slate-700"></div>
89
+ </div>
90
+ </th>
91
+ }
92
+ } @else {
93
+ @for (role of roles(); track role.name) {
94
+ <th class="sticky top-0 z-20 px-6 py-4 text-center text-xs font-semibold uppercase tracking-wider text-slate-600 dark:text-slate-300 min-w-[170px] bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-800">
95
+ <div class="flex flex-col items-center">
96
+ <span class="text-blue-700 dark:text-blue-400 font-bold bg-blue-50/80 dark:bg-blue-950/20 px-2.5 py-1 rounded-lg border border-blue-100 dark:border-blue-900/40" [title]="role.name">
97
+ {{ formatRoleName(role.name) }}
98
+ </span>
99
+ </div>
100
+ </th>
101
+ }
102
+ }
103
+ </tr>
104
+ </thead>
105
+ @if (isLoading()) {
106
+ <tbody app-role-permission-skeleton [rolesCount]="roles().length || 6"></tbody>
107
+ } @else {
108
+ <tbody>
109
+ @for (item of paginatedHierarchyData(); track trackHierarchyItem(item)) {
110
+ @if (item.kind === 'page') {
111
+ <tr class="bg-teal-50/90 dark:bg-teal-950/40 font-bold transition-colors select-none">
112
+ <td *ngIf="SCREEN | accessCtrl: 'toggle-screen-collapse'"
113
+ id="qa-role-permission-matrix__toggle-page-collapse"
114
+ data-qa-id="qa-role-permission-matrix__toggle-page-collapse"
115
+ data-component-id="toggle-screen-collapse"
116
+ data-screen-id="role-permission-matrix"
117
+ (click)="togglePageCollapse(item)"
118
+ class="sticky left-0 z-10 px-6 py-3.5 border-r border-b border-teal-200/80 dark:border-teal-900/50 bg-teal-50 dark:bg-teal-950/50 cursor-pointer hover:bg-teal-100/80 dark:hover:bg-teal-900/40">
119
+ <div class="flex items-start gap-x-2 min-w-0">
120
+ <mat-icon svgIcon="chevron-right" class="text-teal-700 dark:text-teal-300 size-4 mt-0.5 shrink-0 transition-transform duration-300 ease-in-out" [class.rotate-90]="!isPageCollapsed(item)"></mat-icon>
121
+ <mat-icon svgIcon="layout-dashboard" class="text-teal-700 dark:text-teal-300 size-4 mt-0.5 shrink-0"></mat-icon>
122
+ <div class="min-w-0 flex flex-col gap-1">
123
+ <div class="flex flex-wrap items-center gap-2">
124
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide bg-teal-700 text-white dark:bg-teal-500 dark:text-teal-950">Page</span>
125
+ <span class="text-[10px] font-semibold uppercase tracking-wider text-teal-800/80 dark:text-teal-200/80">{{ item.page.domain }}</span>
126
+ </div>
127
+ <span class="text-sm font-bold text-slate-900 dark:text-white" [innerHTML]="highlightText(item.page.navTitle)"></span>
128
+ <p class="text-[11px] font-mono text-teal-900/70 dark:text-teal-100/70" [innerHTML]="highlightText(item.page.route)"></p>
129
+ <p class="text-[10px] font-normal text-slate-500 dark:text-slate-400">
130
+ Includes {{ item.embeds.length }} embedded screen{{ item.embeds.length === 1 ? '' : 's' }} · host
131
+ <span class="font-mono" [innerHTML]="highlightText(item.page.hostScreenId)"></span>
132
+ </p>
133
+ </div>
134
+ </div>
135
+ </td>
136
+ @for (role of roles(); track role.name) {
137
+ <td (click)="$event.stopPropagation()" class="px-4 py-3 border-b border-teal-200/80 dark:border-teal-900/50 text-center bg-teal-50/60 dark:bg-teal-950/30">
138
+ <div class="flex flex-col items-center gap-2">
139
+ @if (isPageEssentialsProcessing(role.name || '', item)) {
140
+ <div class="animate-spin rounded-full h-5 w-5 border-t-2 border-teal-600"></div>
141
+ }
142
+ <button type="button"
143
+ *ngIf="SCREEN | accessCtrl: 'toggle-screen-permission'"
144
+ id="qa-role-permission-matrix__grant-page-essentials"
145
+ data-qa-id="qa-role-permission-matrix__grant-page-essentials"
146
+ data-component-id="toggle-screen-permission"
147
+ data-screen-id="role-permission-matrix"
148
+ class="inline-flex items-center justify-center rounded-lg border border-teal-600/40 bg-white dark:bg-slate-900 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-teal-800 dark:text-teal-200 hover:bg-teal-100 dark:hover:bg-teal-900/40 disabled:opacity-50 disabled:cursor-not-allowed"
149
+ [disabled]="isUiLocked() || isPageEssentialsProcessing(role.name || '', item)"
150
+ [attr.title]="'Grant ' + item.page.navTitle + ' essentials for ' + formatRoleName(role.name) + ' (excludes Delete/Approve/Reject/Finalize)'"
151
+ (click)="grantPageEssentials(role.name || '', item)">
152
+ Grant essentials
153
+ </button>
154
+ <div id="qa-role-permission-matrix__toggle-screen-permission"
155
+ data-qa-id="qa-role-permission-matrix__toggle-screen-permission"
156
+ data-component-id="toggle-screen-permission"
157
+ data-screen-id="role-permission-matrix"
158
+ *ngIf="SCREEN | accessCtrl: 'toggle-screen-permission'"
159
+ class="role-access-toggle-trigger"
160
+ [class.role-access-toggle-trigger--disabled]="isScreenToggleDisabled(role.name || '', item.host)"
161
+ role="button"
162
+ [attr.tabindex]="isScreenToggleDisabled(role.name || '', item.host) ? -1 : 0"
163
+ (click)="toggleScreenPermission(role.name || '', item.host)"
164
+ (keydown.enter)="toggleScreenPermission(role.name || '', item.host)"
165
+ (keydown.space)="$event.preventDefault(); toggleScreenPermission(role.name || '', item.host)">
166
+ <div class="permission-toggle-visual"
167
+ [class.permission-toggle-visual--checked]="isClusterScreenPermissionMapped(role.name || '', item.host)">
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </td>
172
+ }
173
+ </tr>
174
+
175
+ @if (!isPageCollapsed(item)) {
176
+ <ng-container *ngTemplateOutlet="screenBlock; context: { $implicit: item.host }"></ng-container>
177
+ @for (embed of item.embeds; track embed.cleanScreenId) {
178
+ <tr class="bg-slate-100/70 dark:bg-slate-800/70 font-bold transition-colors select-none">
179
+ <td *ngIf="SCREEN | accessCtrl: 'toggle-screen-collapse'"
180
+ (click)="toggleEmbedCollapse(item, embed)"
181
+ class="sticky left-0 z-10 pl-10 pr-6 py-3 border-r border-b border-slate-200 dark:border-slate-800 bg-slate-100 dark:bg-slate-800 cursor-pointer hover:bg-slate-200/50 dark:hover:bg-slate-700/50">
182
+ <div class="flex items-start gap-x-2 min-w-0">
183
+ <mat-icon svgIcon="chevron-right" class="text-slate-500 size-4 mt-0.5 shrink-0 transition-transform duration-300 ease-in-out" [class.rotate-90]="!isEmbedCollapsed(item, embed)"></mat-icon>
184
+ <mat-icon svgIcon="layers" class="text-violet-600 dark:text-violet-400 size-4 mt-0.5 shrink-0"></mat-icon>
185
+ <div class="min-w-0 flex flex-col gap-1">
186
+ <div class="flex flex-wrap items-center gap-2">
187
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide bg-violet-100 text-violet-800 dark:bg-violet-950/40 dark:text-violet-300">Embedded</span>
188
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-violet-50 text-violet-700 dark:bg-violet-950/30 dark:text-violet-400 border border-violet-200/50 dark:border-violet-900/40" [innerHTML]="highlightText(embed.cleanScreenId)"></span>
189
+ <span class="text-sm font-semibold text-slate-800 dark:text-slate-100" [innerHTML]="highlightText(getCleanScreenName(embed.screen.screenName))"></span>
190
+ </div>
191
+ <p class="text-[10px] font-normal text-slate-400 dark:text-slate-500">
192
+ {{ embed.components.length }} component{{ embed.components.length === 1 ? '' : 's' }}
193
+ </p>
194
+ </div>
195
+ </div>
196
+ </td>
197
+ @for (role of roles(); track role.name) {
198
+ <td (click)="$event.stopPropagation()" class="px-6 py-3 border-b border-slate-200 dark:border-slate-800 text-center bg-slate-100/40 dark:bg-slate-800/40">
199
+ <div class="flex items-center justify-center gap-2">
200
+ <div *ngIf="SCREEN | accessCtrl: 'toggle-screen-permission'"
201
+ class="role-access-toggle-trigger"
202
+ [class.role-access-toggle-trigger--disabled]="isScreenToggleDisabled(role.name || '', embed)"
203
+ role="button"
204
+ (click)="toggleScreenPermission(role.name || '', embed)">
205
+ <div class="permission-toggle-visual"
206
+ [class.permission-toggle-visual--checked]="isClusterScreenPermissionMapped(role.name || '', embed)">
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </td>
211
+ }
212
+ </tr>
213
+ @if (!isEmbedCollapsed(item, embed)) {
214
+ <ng-container *ngTemplateOutlet="componentRows; context: { $implicit: embed }"></ng-container>
215
+ }
216
+ }
217
+ }
218
+ } @else {
219
+ <ng-container *ngTemplateOutlet="screenBlock; context: { $implicit: item.group }"></ng-container>
220
+ }
221
+ }
222
+ </tbody>
223
+
224
+ }
225
+ </table>
226
+ </div>
227
+ </div>
228
+
229
+ <!-- Paginator & Footer Info Hint — pinned at bottom of viewport -->
230
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between border-t bg-card dark:bg-[#0D1E3A] shrink-0 px-6 z-20">
231
+ <mat-paginator
232
+ class="bg-transparent [&_.mat-mdc-paginator-container]:justify-start"
233
+ [length]="isLoading() ? 0 : filteredHierarchyData().length"
234
+ [pageSize]="pageSize()"
235
+ [pageIndex]="currentPage()"
236
+ [pageSizeOptions]="[10, 25, 50, 100]"
237
+ [disabled]="isUiLocked()"
238
+ (page)="onPageChange($event)">
239
+ </mat-paginator>
240
+
241
+ <div class="py-3 text-xs text-slate-400 flex items-center gap-x-2">
242
+ <mat-icon svgIcon="info" class="size-4 shrink-0"></mat-icon>
243
+ <span>Any change will apply immediately. Permissions are stored on Keycloak role attributes and synced to assigned users.</span>
244
+ </div>
245
+ </div>
246
+ </div>
247
+
248
+
249
+ <ng-template #screenBlock let-group>
250
+ <tr class="bg-slate-100/80 dark:bg-slate-800/80 font-bold transition-colors select-none">
251
+ <td *ngIf="SCREEN | accessCtrl: 'toggle-screen-collapse'" (click)="toggleScreenCollapse(group.cleanScreenId)"
252
+ class="sticky left-0 z-10 px-6 py-3 border-r border-b border-slate-200 dark:border-slate-800 bg-slate-100 dark:bg-slate-800 cursor-pointer hover:bg-slate-200/50 dark:hover:bg-slate-700/50">
253
+ <div class="flex items-start gap-x-2 min-w-0">
254
+ <mat-icon svgIcon="chevron-right" class="text-slate-500 size-4 mt-0.5 shrink-0 transition-transform duration-300 ease-in-out" [class.rotate-90]="!isScreenCollapsed(group.cleanScreenId)"></mat-icon>
255
+ <mat-icon svgIcon="monitor" class="text-indigo-600 dark:text-indigo-400 size-4 mt-0.5 shrink-0"></mat-icon>
256
+ <div class="min-w-0 flex flex-col gap-1">
257
+ <div class="flex flex-wrap items-center gap-2">
258
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-indigo-50 text-indigo-700 dark:bg-indigo-950/30 dark:text-indigo-400 border border-indigo-200/50 dark:border-indigo-900/40" [innerHTML]="highlightText(group.cleanScreenId)"></span>
259
+ <span class="text-sm font-semibold text-slate-800 dark:text-slate-100" [innerHTML]="highlightText(getCleanScreenName(group.screen.screenName))"></span>
260
+ </div>
261
+ @if (group.screen.description) {
262
+ <p class="text-xs font-normal text-slate-500 dark:text-slate-400 leading-relaxed" [innerHTML]="highlightText(group.screen.description)"></p>
263
+ }
264
+ <p class="text-[10px] font-normal text-slate-400 dark:text-slate-500">
265
+ {{ group.components.length }} component{{ group.components.length === 1 ? '' : 's' }}
266
+ </p>
267
+ </div>
268
+ </div>
269
+ </td>
270
+ @for (role of roles(); track role.name) {
271
+ <td (click)="$event.stopPropagation()" class="px-6 py-3 border-b border-slate-200 dark:border-slate-800 text-center bg-slate-100/40 dark:bg-slate-800/40">
272
+ <div class="flex items-center justify-center gap-2">
273
+ <div *ngIf="SCREEN | accessCtrl: 'toggle-screen-permission'" class="role-access-toggle-trigger"
274
+ [class.role-access-toggle-trigger--disabled]="isScreenToggleDisabled(role.name || '', group)"
275
+ role="button"
276
+ (click)="toggleScreenPermission(role.name || '', group)">
277
+ <div class="permission-toggle-visual"
278
+ [class.permission-toggle-visual--checked]="isClusterScreenPermissionMapped(role.name || '', group)">
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </td>
283
+ }
284
+ </tr>
285
+ @if (!isScreenCollapsed(group.cleanScreenId)) {
286
+ <ng-container *ngTemplateOutlet="componentRows; context: { $implicit: group }"></ng-container>
287
+ }
288
+ </ng-template>
289
+
290
+ <ng-template #componentRows let-group>
291
+ @for (comp of group.components; track comp.componentId) {
292
+ <tr class="hover:bg-slate-50/30 dark:hover:bg-slate-800/10 transition-colors">
293
+ <td class="sticky left-0 z-10 px-8 py-3.5 border-r border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-[#0D1E3A]">
294
+ <div class="flex items-center gap-x-2 flex-wrap">
295
+ <span class="text-xs font-semibold px-2.5 py-0.5 rounded-full bg-blue-50 text-blue-700 dark:bg-blue-950/30 dark:text-blue-400 border border-blue-100 dark:border-blue-900/30">
296
+ {{ comp.componentType }}
297
+ </span>
298
+ @if (isSensitiveComponent(comp.componentId)) {
299
+ <span class="text-[10px] font-bold uppercase tracking-wide text-rose-700 dark:text-rose-300 bg-rose-50 dark:bg-rose-950/40 px-2 py-0.5 rounded border border-rose-200/60 dark:border-rose-900/40">Sensitive</span>
300
+ }
301
+ <div class="font-semibold text-slate-850 dark:text-slate-200 text-sm" [innerHTML]="highlightText(comp.componentName)"></div>
302
+ </div>
303
+ @if (comp.description) {
304
+ <div class="text-xs text-slate-500 dark:text-slate-400 mt-1 pl-2 leading-relaxed" [innerHTML]="highlightText(comp.description)"></div>
305
+ }
306
+ <div class="text-xs text-slate-400 dark:text-slate-500 mt-1 pl-2 font-mono" [innerHTML]="highlightText(comp.componentId)"></div>
307
+ </td>
308
+ @for (role of roles(); track role.name) {
309
+ <td class="px-6 py-3.5 text-center border-b border-slate-200 dark:border-slate-800">
310
+ <div class="flex items-center justify-center gap-2">
311
+ <div *ngIf="SCREEN | accessCtrl: 'toggle-permission'" class="role-access-toggle-trigger"
312
+ [class.role-access-toggle-trigger--disabled]="isComponentToggleDisabled(role.name || '', comp.componentId || '', group)"
313
+ role="button"
314
+ [attr.title]="(isSensitiveComponent(comp.componentId) ? 'Sensitive action — ' : '') + 'Toggle component access'"
315
+ (click)="togglePermission(role.name || '', comp.componentId || '', group)">
316
+ <div class="permission-toggle-visual"
317
+ [class.permission-toggle-visual--checked]="isClusterComponentPermissionMapped(role.name || '', group, comp.componentId || '')">
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </td>
322
+ }
323
+ </tr>
324
+ }
325
+ @if (group.components.length === 0) {
326
+ <tr class="bg-slate-50/50 dark:bg-slate-800/30">
327
+ <td [attr.colspan]="roles().length + 1" class="px-8 py-6 border-b border-slate-200 dark:border-slate-800 text-sm text-slate-500 dark:text-slate-400 italic text-center">
328
+ No sub-components found for this screen.
329
+ </td>
330
+ </tr>
331
+ }
332
+ </ng-template>
333
+
334
+ <!-- Floating: show only enabled screens for this role filter -->
335
+ <button *ngIf="SCREEN | accessCtrl: 'show-enabled-only'"
336
+ type="button"
337
+ id="qa-role-permission-matrix__show-enabled-only"
338
+ data-qa-id="qa-role-permission-matrix__show-enabled-only"
339
+ data-component-id="show-enabled-only"
340
+ data-screen-id="role-permission-matrix"
341
+ class="matrix-enabled-fab"
342
+ [class.matrix-enabled-fab--active]="showEnabledOnly()"
343
+ [disabled]="isLoading()"
344
+ (click)="toggleShowEnabledOnly()"
345
+ [attr.aria-pressed]="showEnabledOnly()"
346
+ title="Show only screens already enabled for the visible role(s)">
347
+ <div class="matrix-enabled-fab__text">
348
+ <span class="matrix-enabled-fab__label">{{ showEnabledOnly() ? 'Enabled only' : 'Show enabled' }}</span>
349
+ <span class="matrix-enabled-fab__count">{{ enabledScreenCount() }} enabled</span>
350
+ </div>
351
+ <div class="permission-toggle-visual"
352
+ [class.permission-toggle-visual--checked]="showEnabledOnly()"
353
+ aria-hidden="true"></div>
354
+ </button>
355
+ </div>