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

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,107 @@
1
+ :host {
2
+ display: flex;
3
+ flex: 1 1 auto;
4
+ width: 100%;
5
+ }
6
+
7
+ .mat-mdc-table {
8
+ background: transparent !important;
9
+ }
10
+
11
+ .mat-mdc-header-cell {
12
+ font-size: 0.75rem !important;
13
+ text-transform: uppercase !important;
14
+ letter-spacing: 0.05em !important;
15
+ }
16
+
17
+ .size-4 {
18
+ width: 16px !important;
19
+ height: 16px !important;
20
+ min-width: 16px !important;
21
+ min-height: 16px !important;
22
+ }
23
+
24
+ .size-5 {
25
+ width: 20px !important;
26
+ height: 20px !important;
27
+ min-width: 20px !important;
28
+ min-height: 20px !important;
29
+ }
30
+
31
+ .size-6 {
32
+ width: 24px !important;
33
+ height: 24px !important;
34
+ min-width: 24px !important;
35
+ min-height: 24px !important;
36
+ }
37
+
38
+ .size-8 {
39
+ width: 32px !important;
40
+ height: 32px !important;
41
+ min-width: 32px !important;
42
+ min-height: 32px !important;
43
+ }
44
+
45
+ .size-16 {
46
+ width: 64px !important;
47
+ height: 64px !important;
48
+ min-width: 64px !important;
49
+ min-height: 64px !important;
50
+ }
51
+
52
+ .main-content-layout {
53
+ height: calc(100vh - 185px) !important;
54
+ overflow: hidden !important;
55
+ }
56
+
57
+ @media (max-width: 1023px) {
58
+ .main-content-layout {
59
+ height: auto !important;
60
+ overflow: visible !important;
61
+ }
62
+ }
63
+
64
+ .no-scrollbar::-webkit-scrollbar {
65
+ display: none !important;
66
+ }
67
+
68
+ .no-scrollbar {
69
+ -ms-overflow-style: none !important;
70
+ scrollbar-width: none !important;
71
+ }
72
+
73
+ .details-container {
74
+ container-type: inline-size;
75
+ container-name: details;
76
+ }
77
+
78
+ .bento-grid {
79
+ display: flex;
80
+ flex-direction: column;
81
+ }
82
+
83
+ .bento-col-side {
84
+ display: grid;
85
+ grid-template-columns: 1fr;
86
+ gap: 1.5rem;
87
+ }
88
+
89
+ @container details (min-width: 600px) {
90
+ .bento-col-side {
91
+ grid-template-columns: repeat(2, 1fr);
92
+ }
93
+ }
94
+
95
+ @container details (min-width: 1024px) {
96
+ .bento-grid {
97
+ display: grid !important;
98
+ grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
99
+ }
100
+ .bento-col-main {
101
+ grid-column: span 2 / span 2 !important;
102
+ }
103
+ .bento-col-side {
104
+ grid-column: span 1 / span 1 !important;
105
+ grid-template-columns: 1fr !important;
106
+ }
107
+ }
@@ -0,0 +1,597 @@
1
+ import { Component, OnInit, inject, signal, computed } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
7
+ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
8
+ import { MatTooltipModule } from '@angular/material/tooltip';
9
+ import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
10
+ import { Router, RouterLink } from '@angular/router';
11
+ import { HttpClient } from '@angular/common/http';
12
+ import { of } from 'rxjs';
13
+ import { catchError } from 'rxjs/operators';
14
+ import {
15
+ AccessCtrlPipe,
16
+ KeycloakAdminService,
17
+ RoleRepresentation,
18
+ UserRepresentation,
19
+ } from '@/app/core/auth';
20
+ import {
21
+ ConfirmDialogComponent,
22
+ ConfirmDialogData,
23
+ } from '@/app/core/user-management/shared/confirm-dialog.component';
24
+ import { formatRoleDisplayName } from '@/app/core/user-management/shared/role-display.util';
25
+ import {
26
+ buildScreenComponentClusters,
27
+ getCleanScreenName,
28
+ GroupedScreenCluster,
29
+ IScreen,
30
+ parseCsvComponentToScreenMap,
31
+ parseCsvRegistry,
32
+ } from '@/app/core/user-management/shared/screen-matrix.util';
33
+ import { RoleHeaderComponent } from './components/role-header/role-header.component';
34
+ import { RoleUsersComponent } from './components/role-users/role-users.component';
35
+ <% if (iam.inheritance !== false) { %>
36
+ import { RoleInheritanceComponent } from './components/role-inheritance/role-inheritance.component';
37
+ <% } %>
38
+ <% if (iam.rolePermissions !== false) { %>
39
+ import { RolePermissionsPanelComponent } from './components/role-permissions-panel/role-permissions-panel.component';
40
+ <% } %>
41
+ import { UserSelectDialogComponent } from './components/user-select-dialog/user-select-dialog.component';
42
+
43
+ @Component({
44
+ selector: 'app-role-list',
45
+ standalone: true,
46
+ imports: [
47
+ AccessCtrlPipe,
48
+ CommonModule,
49
+ ReactiveFormsModule,
50
+ MatButtonModule,
51
+ MatIconModule,
52
+ MatSnackBarModule,
53
+ MatDialogModule,
54
+ MatTooltipModule,
55
+ RouterLink,
56
+ RoleHeaderComponent,
57
+ RoleUsersComponent,
58
+ <% if (iam.inheritance !== false) { %>
59
+ RoleInheritanceComponent,
60
+ <% } %>
61
+ <% if (iam.rolePermissions !== false) { %>
62
+ RolePermissionsPanelComponent,
63
+ <% } %>
64
+ MatPaginatorModule,
65
+ ],
66
+ templateUrl: './role-list.component.html',
67
+ styleUrls: ['./role-list.component.scss'],
68
+ host: { class: 'flex h-full min-h-0 w-full min-w-0 flex-auto' },
69
+ })
70
+ export class RoleListComponent implements OnInit {
71
+ readonly SCREEN = 'role-management';
72
+ private fb = inject(FormBuilder);
73
+ private keycloakAdminService = inject(KeycloakAdminService);
74
+ private snackBar = inject(MatSnackBar);
75
+ private dialog = inject(MatDialog);
76
+ private router = inject(Router);
77
+ private http = inject(HttpClient);
78
+
79
+ roles = signal<RoleRepresentation[]>([]);
80
+ screenClusters = signal<GroupedScreenCluster[]>([]);
81
+ searchQuery = signal<string>('');
82
+ roleForm: FormGroup;
83
+ showCreateForm = signal<boolean>(false);
84
+ isSubmitting = signal<boolean>(false);
85
+
86
+ selectedRole = signal<RoleRepresentation | null>(null);
87
+ selectedRoleUsers = signal<UserRepresentation[]>([]);
88
+ selectedRoleUserCount = signal<number>(0);
89
+ selectedRolePermissions = signal<{ name: string; components: string[] }[]>([]);
90
+ isLoadingDetails = signal<boolean>(false);
91
+ isSyncingHolders = signal<boolean>(false);
92
+ isMobile = signal<boolean>(false);
93
+ showMobileDetails = signal<boolean>(false);
94
+
95
+ rolesPage = signal<number>(0);
96
+ rolesPageSize = 8;
97
+
98
+ selectedRoleUsersSearchQuery = signal<string>('');
99
+ selectedRoleUsersPage = signal<number>(0);
100
+ readonly userPageSize = 5;
101
+
102
+ readonly IGNORED_REALM_ROLES = new Set(
103
+ ['offline_access', 'default-roles-jhipster', 'uma_authorization', 'default-roles-siis-dev', 'role_user', 'role_admin', 'ghost_account'].map(
104
+ (s) => s.toLowerCase()
105
+ )
106
+ );
107
+
108
+ filteredRoles = computed(() => {
109
+ const query = this.searchQuery().toLowerCase().trim();
110
+ const filtered = this.roles().filter(
111
+ (role) => role.name && !this.IGNORED_REALM_ROLES.has(role.name.toLowerCase())
112
+ );
113
+ if (!query) return filtered;
114
+ return filtered.filter(
115
+ (role) =>
116
+ role.name?.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)
117
+ );
118
+ });
119
+
120
+ paginatedRoles = computed(() => {
121
+ const roles = this.filteredRoles();
122
+ const page = this.rolesPage();
123
+ const start = page * this.rolesPageSize;
124
+ return roles.slice(start, start + this.rolesPageSize);
125
+ });
126
+
127
+ filteredSelectedRoleUsers = computed(() => {
128
+ const query = this.selectedRoleUsersSearchQuery().toLowerCase().trim();
129
+ const users = this.selectedRoleUsers();
130
+ if (!query) return users;
131
+ return users.filter(
132
+ (u) =>
133
+ u.username?.toLowerCase().includes(query) ||
134
+ u.firstName?.toLowerCase().includes(query) ||
135
+ u.lastName?.toLowerCase().includes(query)
136
+ );
137
+ });
138
+
139
+ paginatedSelectedRoleUsers = computed(() => {
140
+ const users = this.filteredSelectedRoleUsers();
141
+ const page = this.selectedRoleUsersPage();
142
+ const start = page * this.userPageSize;
143
+ return users.slice(start, start + this.userPageSize);
144
+ });
145
+
146
+ totalSelectedRoleUsersPages = computed(() => {
147
+ const count = this.filteredSelectedRoleUsers().length;
148
+ return Math.ceil(count / this.userPageSize);
149
+ });
150
+
151
+ constructor() {
152
+ this.roleForm = this.fb.group({
153
+ name: ['', [Validators.required, Validators.pattern(/^[A-Z0-9_]+$/)]],
154
+ description: ['', Validators.required],
155
+ });
156
+ this.roleForm.get('name')?.valueChanges.subscribe((value) => {
157
+ if (typeof value !== 'string') {
158
+ return;
159
+ }
160
+ const normalized = value.toUpperCase().replace(/[^A-Z0-9_]/g, '');
161
+ if (normalized !== value) {
162
+ this.roleForm.get('name')?.setValue(normalized, { emitEvent: false });
163
+ }
164
+ });
165
+ this.checkScreenSize();
166
+ if (typeof window !== 'undefined') {
167
+ window.addEventListener('resize', () => this.checkScreenSize());
168
+ }
169
+ }
170
+
171
+ private checkScreenSize(): void {
172
+ if (typeof window !== 'undefined') {
173
+ this.isMobile.set(window.innerWidth < 1024);
174
+ }
175
+ }
176
+
177
+ ngOnInit(): void {
178
+ this.loadRoles();
179
+ this.loadScreensFromCsv();
180
+ }
181
+
182
+ onRolesPageChange(event: PageEvent): void {
183
+ this.rolesPage.set(event.pageIndex);
184
+ if (event.pageSize !== this.rolesPageSize) {
185
+ this.rolesPageSize = event.pageSize;
186
+ }
187
+ }
188
+
189
+ loadScreensFromCsv(): void {
190
+ this.http
191
+ .get('assets/screen-components.csv', { responseType: 'text' })
192
+ .pipe(catchError(() => of('')))
193
+ .subscribe({
194
+ next: (csv) => {
195
+ const csvRows = parseCsvRegistry(csv);
196
+ const csvMap = parseCsvComponentToScreenMap(csv);
197
+ this.screenClusters.set(buildScreenComponentClusters([], [], csvMap, csvRows));
198
+ const filtered = this.filteredRoles();
199
+ if (filtered.length > 0 && !this.isMobile() && !this.selectedRole()) {
200
+ this.selectRole(filtered[0]);
201
+ }
202
+ },
203
+ error: (err) => {
204
+ console.error('Failed to load screen catalog', err);
205
+ },
206
+ });
207
+ }
208
+
209
+ loadRoles(): void {
210
+ this.keycloakAdminService.getRealmRoles().subscribe({
211
+ next: (roles) => {
212
+ this.roles.set(roles || []);
213
+ const filtered = this.filteredRoles();
214
+ if (this.screenClusters().length > 0 && filtered.length > 0 && !this.isMobile() && !this.selectedRole()) {
215
+ this.selectRole(filtered[0]);
216
+ }
217
+ },
218
+ error: (err) => {
219
+ console.error(err);
220
+ this.snackBar.open('Failed to load roles from Keycloak', 'Close', { duration: 3000 });
221
+ },
222
+ });
223
+ }
224
+
225
+ toggleCreateForm(): void {
226
+ this.showCreateForm.update((val) => !val);
227
+ if (this.showCreateForm()) {
228
+ this.selectedRole.set(null);
229
+ this.showMobileDetails.set(false);
230
+ } else {
231
+ this.roleForm.reset();
232
+ const filtered = this.filteredRoles();
233
+ if (filtered.length > 0 && !this.isMobile()) {
234
+ this.selectRole(filtered[0]);
235
+ }
236
+ }
237
+ }
238
+
239
+ onSubmit(): void {
240
+ if (this.roleForm.invalid) return;
241
+
242
+ this.isSubmitting.set(true);
243
+ const { name, description } = this.roleForm.value;
244
+ const normalizedName = String(name || '')
245
+ .trim()
246
+ .toUpperCase()
247
+ .replace(/[^A-Z0-9_]/g, '');
248
+
249
+ this.keycloakAdminService.createRealmRoleOnly(normalizedName, description.trim()).subscribe({
250
+ next: () => {
251
+ this.snackBar.open(`Role "${normalizedName}" created successfully.`, 'Close', { duration: 3000 });
252
+ this.loadRoles();
253
+ this.toggleCreateForm();
254
+ this.isSubmitting.set(false);
255
+ },
256
+ error: (err) => {
257
+ console.error(err);
258
+ this.snackBar.open('Failed to create role. It may already exist.', 'Close', { duration: 3000 });
259
+ this.isSubmitting.set(false);
260
+ },
261
+ });
262
+ }
263
+
264
+ duplicateRole(role: RoleRepresentation): void {
265
+ const displayRoleName = this.cleanRoleName(role.name);
266
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
267
+ width: '400px',
268
+ data: {
269
+ title: 'Duplicate Role',
270
+ message: `Enter the name of the new role to duplicate "${displayRoleName}" settings and permissions:`,
271
+ confirmLabel: 'Duplicate',
272
+ cancelLabel: 'Cancel',
273
+ showInput: true,
274
+ inputValue: `${displayRoleName.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_COPY`,
275
+ inputPlaceholder: 'ROLE_NAME',
276
+ },
277
+ });
278
+
279
+ dialogRef.afterClosed().subscribe((newName) => {
280
+ if (newName && typeof newName === 'string' && newName.trim()) {
281
+ const trimmedName = newName.trim().toUpperCase().replace(/[\s-]+/g, '_');
282
+ this.isSubmitting.set(true);
283
+ this.keycloakAdminService
284
+ .duplicateRole(role.name!, trimmedName, `Copy of ${displayRoleName}`)
285
+ .subscribe({
286
+ next: () => {
287
+ this.snackBar.open(
288
+ `Role duplicated to "${this.cleanRoleName(trimmedName)}" successfully.`,
289
+ 'Close',
290
+ { duration: 3000 }
291
+ );
292
+ this.loadRoles();
293
+ this.isSubmitting.set(false);
294
+ },
295
+ error: (err) => {
296
+ console.error(err);
297
+ this.snackBar.open('Failed to duplicate role.', 'Close', { duration: 3000 });
298
+ this.isSubmitting.set(false);
299
+ },
300
+ });
301
+ }
302
+ });
303
+ }
304
+
305
+ editPermissions(role: RoleRepresentation): void {
306
+ <% if (iam.rolePermissions === false) { %>
307
+ return;
308
+ <% } else { %>
309
+ const displayRoleName = this.cleanRoleName(role.name);
310
+ const dialogData: ConfirmDialogData = {
311
+ title: 'Configure Permissions',
312
+ message: `Are you sure you want to configure access capabilities for the role "${displayRoleName}"?`,
313
+ confirmLabel: 'Proceed',
314
+ cancelLabel: 'Cancel',
315
+ };
316
+
317
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
318
+ width: '400px',
319
+ data: dialogData,
320
+ });
321
+
322
+ dialogRef.afterClosed().subscribe((confirmed) => {
323
+ if (confirmed) {
324
+ this.router.navigate(['/admin/user-management/role-permissions'], {
325
+ queryParams: { role: role.name },
326
+ });
327
+ }
328
+ });
329
+ <% } %>
330
+ }
331
+
332
+ syncSelectedRoleHolders(): void {
333
+ const role = this.selectedRole();
334
+ if (!role?.name || this.isSyncingHolders()) {
335
+ return;
336
+ }
337
+
338
+ const displayRoleName = this.cleanRoleName(role.name);
339
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
340
+ width: '420px',
341
+ data: {
342
+ title: 'Sync role holders',
343
+ message: `Rebuild screen/button attributes for every user holding "${displayRoleName}" from their assigned Keycloak roles? Protected keys are kept. Affected users should refresh or re-login afterward.`,
344
+ confirmLabel: 'Sync holders',
345
+ cancelLabel: 'Cancel',
346
+ } as ConfirmDialogData,
347
+ });
348
+
349
+ dialogRef.afterClosed().subscribe((confirmed) => {
350
+ if (!confirmed || !role.name) {
351
+ return;
352
+ }
353
+ this.isSyncingHolders.set(true);
354
+ this.keycloakAdminService.syncAllRoleHoldersPermissions(role.name).subscribe({
355
+ next: (result) => {
356
+ this.isSyncingHolders.set(false);
357
+ if (result.total === 0) {
358
+ this.snackBar.open(`No holders to sync for "${displayRoleName}".`, 'Close', {
359
+ duration: 3000,
360
+ });
361
+ return;
362
+ }
363
+ const summary =
364
+ result.failed > 0
365
+ ? `Synced ${result.synced}/${result.total} holders (${result.failed} failed).`
366
+ : `Synced ${result.synced} holder${result.synced === 1 ? '' : 's'} for "${displayRoleName}".`;
367
+ this.snackBar.open(summary, 'Close', { duration: 3500 });
368
+ },
369
+ error: (err) => {
370
+ console.error('Failed to sync role holders', err);
371
+ this.isSyncingHolders.set(false);
372
+ this.snackBar.open(`Failed to sync holders for "${displayRoleName}".`, 'Close', {
373
+ duration: 3500,
374
+ });
375
+ },
376
+ });
377
+ });
378
+ }
379
+
380
+ selectRole(role: RoleRepresentation): void {
381
+ this.selectedRole.set(role);
382
+ this.showCreateForm.set(false);
383
+ this.isLoadingDetails.set(true);
384
+
385
+ this.selectedRoleUsersSearchQuery.set('');
386
+ this.selectedRoleUsersPage.set(0);
387
+
388
+ if (this.isMobile()) {
389
+ this.showMobileDetails.set(true);
390
+ }
391
+
392
+ this.keycloakAdminService.getUsersByRealmRole(role.name!, 0, 500).subscribe({
393
+ next: (page) => {
394
+ const ghostUsername = role.name!.toLowerCase();
395
+ const activeUsers = (page.users || []).filter((u) => u.username !== ghostUsername);
396
+ this.selectedRoleUsers.set(activeUsers);
397
+ this.selectedRoleUserCount.set(activeUsers.length);
398
+ },
399
+ error: () => {
400
+ this.selectedRoleUsers.set([]);
401
+ this.selectedRoleUserCount.set(0);
402
+ },
403
+ });
404
+
405
+ this.keycloakAdminService.resolveRoleAttributesRecursive(role.name!).subscribe({
406
+ next: (attrs) => {
407
+ const attrsMap = attrs as Record<string, string[]>;
408
+ const clusters = this.screenClusters();
409
+
410
+ const mappedData = clusters
411
+ .filter((group) => {
412
+ const ids = [String(group.screen.screenId || ''), ...(group.screenIds || [])].filter(Boolean);
413
+ return ids.some(
414
+ (sId) =>
415
+ attrsMap[sId] !== undefined || (attrsMap['screens'] || []).map(String).includes(sId)
416
+ );
417
+ })
418
+ .map((group) => {
419
+ const matchedComps = group.components
420
+ .filter((comp) => {
421
+ const compId = String(comp.componentId || '');
422
+ const compType = String(comp.componentType || '').toUpperCase();
423
+ const compName = String(comp.componentName || '').toLowerCase();
424
+ if (compType === 'SCREEN' || compName.includes('screen access')) return false;
425
+ return attrsMap[compId] !== undefined && attrsMap[compId].includes('true');
426
+ })
427
+ .map((comp) => String(comp.componentName || comp.componentId || ''));
428
+
429
+ return {
430
+ name: this.getCleanDisplayScreenName(group.screen),
431
+ components: matchedComps,
432
+ };
433
+ });
434
+
435
+ this.selectedRolePermissions.set(mappedData);
436
+ this.isLoadingDetails.set(false);
437
+ },
438
+ error: () => {
439
+ this.selectedRolePermissions.set([]);
440
+ this.isLoadingDetails.set(false);
441
+ },
442
+ });
443
+ }
444
+
445
+ openAssignUserDialog(): void {
446
+ const role = this.selectedRole();
447
+ if (!role || !role.name) return;
448
+
449
+ const assignedUserIds = new Set<string>(
450
+ this.selectedRoleUsers()
451
+ .map((u) => u.id)
452
+ .filter((id): id is string => !!id)
453
+ );
454
+
455
+ const dialogRef = this.dialog.open(UserSelectDialogComponent, {
456
+ width: '100%',
457
+ maxWidth: '900px',
458
+ position: { bottom: '0px' },
459
+ panelClass: 'bottom-sheet-dialog',
460
+ disableClose: true,
461
+ data: {
462
+ roleName: role.name,
463
+ alreadyAssignedUserIds: assignedUserIds,
464
+ },
465
+ });
466
+
467
+ dialogRef.afterClosed().subscribe((changed) => {
468
+ if (changed) {
469
+ this.selectRole(role);
470
+ }
471
+ });
472
+ }
473
+
474
+ unassignUser(user: UserRepresentation): void {
475
+ const role = this.selectedRole();
476
+ if (!role || !role.name || !user.id) return;
477
+
478
+ const dialogData: ConfirmDialogData = {
479
+ title: 'Remove User Assignment',
480
+ message: `Are you sure you want to revoke the role "${this.cleanRoleName(role.name)}" from user "${user.username}"?`,
481
+ confirmLabel: 'Revoke',
482
+ cancelLabel: 'Cancel',
483
+ };
484
+
485
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
486
+ width: '400px',
487
+ data: dialogData,
488
+ });
489
+
490
+ dialogRef.afterClosed().subscribe((confirmed) => {
491
+ if (confirmed) {
492
+ this.isSubmitting.set(true);
493
+ this.keycloakAdminService.removeRealmRoleFromUser(user.id!, role.name!).subscribe({
494
+ next: () => {
495
+ this.snackBar.open(`Role assignment revoked from "${user.username}".`, 'Close', {
496
+ duration: 3000,
497
+ });
498
+ this.selectRole(role);
499
+ this.isSubmitting.set(false);
500
+ },
501
+ error: (err) => {
502
+ console.error(err);
503
+ this.snackBar.open(`Failed to revoke role assignment.`, 'Close', { duration: 3000 });
504
+ this.isSubmitting.set(false);
505
+ },
506
+ });
507
+ }
508
+ });
509
+ }
510
+
511
+ cleanRoleName(name: string | undefined): string {
512
+ return formatRoleDisplayName(name);
513
+ }
514
+
515
+ getRoleIcon(roleName?: string): string {
516
+ if (!roleName) return 'shield';
517
+ const name = roleName.toUpperCase();
518
+ if (name.includes('SUPER_ADMIN') || name.includes('ADMIN')) return 'shield-check';
519
+ if (name.includes('KEYCLOAK_ADMIN') || name.includes('KEYCLOAK')) return 'key';
520
+ if (name.includes('DEFAULT') || name.includes('USER')) return 'users';
521
+ if (name.includes('MILLER')) return 'factory';
522
+ if (name.includes('PMB') || name.includes('STAFF')) return 'briefcase';
523
+ return 'shield';
524
+ }
525
+
526
+ getRoleBadgeClass(roleName?: string): string {
527
+ if (!roleName) {
528
+ return 'bg-indigo-50 text-indigo-900 border-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-400 dark:border-indigo-900/40 font-bold';
529
+ }
530
+ const name = roleName.toUpperCase();
531
+ if (name.includes('SUPER_ADMIN')) {
532
+ return 'bg-amber-50 text-amber-900 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900/50 font-bold';
533
+ }
534
+ if (name.includes('KEYCLOAK_ADMIN')) {
535
+ return 'bg-sky-50 text-sky-900 border-sky-200 dark:bg-sky-950/40 dark:text-sky-300 dark:border-sky-900/50 font-bold';
536
+ }
537
+ if (name.includes('DEFAULT')) {
538
+ return 'bg-primary-50 text-primary-900 border-primary-200 dark:bg-primary-950/40 dark:text-primary-300 dark:border-primary-900/50 font-bold';
539
+ }
540
+ return 'bg-indigo-50 text-indigo-900 border-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-400 dark:border-indigo-900/40 font-bold';
541
+ }
542
+
543
+ getRoleTooltip(roleName?: string): string {
544
+ if (!roleName) return 'Application Role';
545
+ const name = roleName.toUpperCase();
546
+ if (name.includes('SUPER_ADMIN')) {
547
+ return 'Industry-Standard Super Administrator Role — Unrestricted system access.';
548
+ }
549
+ if (name.includes('KEYCLOAK_ADMIN')) {
550
+ return 'Keycloak REST Admin API Role — Grants Keycloak user & realm administration API authority.';
551
+ }
552
+ if (name.includes('DEFAULT')) {
553
+ return 'Keycloak Composite Realm Role — Auto-assigned to every registered user upon account creation.';
554
+ }
555
+ return `Custom application role: ${this.cleanRoleName(roleName)}`;
556
+ }
557
+
558
+ isCoreSystemRole(roleName?: string): boolean {
559
+ if (!roleName) return false;
560
+ const name = roleName.toLowerCase().trim();
561
+ return (
562
+ name === 'default-roles-pmb' ||
563
+ name === 'keycloak_admin_access' ||
564
+ name === 'role_super_admin' ||
565
+ name === 'role_super_admin_pmb'
566
+ );
567
+ }
568
+
569
+ getCleanDisplayScreenName(screen: IScreen): string {
570
+ const screenId = screen.screenId ? String(screen.screenId) : '';
571
+ const screenName = screen.screenName ? String(screen.screenName) : '';
572
+
573
+ if (!screenId) return getCleanScreenName(screenName) || screenName;
574
+
575
+ const cleanScreenName = getCleanScreenName(screenName);
576
+ const suffix = '-' + cleanScreenName.toLowerCase().replace(/\s+/g, '-');
577
+
578
+ let baseId = screenId;
579
+ if (baseId.endsWith(suffix) && baseId !== suffix.substring(1)) {
580
+ baseId = baseId.slice(0, -suffix.length);
581
+ }
582
+
583
+ return baseId
584
+ .split(/[-_]+/)
585
+ .map((word) => {
586
+ if (word.toLowerCase() === 'caa') return 'CAA';
587
+ if (word.toLowerCase() === 'pmb') return 'PMB';
588
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
589
+ })
590
+ .join(' ');
591
+ }
592
+
593
+ goBackToList(): void {
594
+ this.showMobileDetails.set(false);
595
+ this.showCreateForm.set(false);
596
+ }
597
+ }