@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,51 @@
1
+ <div *ngIf="selectedRole" class="relative rounded-2xl border bg-white p-6 shadow-sm dark:bg-slate-900">
2
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
3
+ <div>
4
+ <div class="flex flex-wrap items-center gap-2.5">
5
+ <h2 class="text-2xl font-extrabold text-slate-900 dark:text-white">
6
+ {{ cleanRoleName(selectedRole.name || '') }}
7
+ </h2>
8
+ <span class="rounded-full border border-primary-100 bg-primary-50 px-2.5 py-1 text-xs font-semibold text-primary-800 dark:border-primary-900/40 dark:bg-primary-950/30 dark:text-primary-400">
9
+ Active
10
+ </span>
11
+ <span
12
+ *ngIf="isCoreSystemRole(selectedRole.name)"
13
+ class="inline-flex items-center gap-1.5 rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-xs font-extrabold text-rose-700 shadow-xs dark:border-rose-900/50 dark:bg-rose-950/40 dark:text-rose-300"
14
+ >
15
+ <mat-icon svgIcon="lock" class="size-3.5 animate-pulse text-rose-600 dark:text-rose-400"></mat-icon>
16
+ <span>Core Platform Role Required</span>
17
+ </span>
18
+ </div>
19
+ <p class="mt-1 text-sm text-slate-500">{{ selectedRole.description || 'No description provided.' }}</p>
20
+ </div>
21
+
22
+ <div class="flex items-center gap-2">
23
+ <button
24
+ *ngIf="showEditPermissions"
25
+ type="button"
26
+ (click)="onEditPermissions()"
27
+ class="inline-flex items-center justify-center rounded-xl border bg-primary-50 px-4 py-2 text-sm font-semibold text-primary-700 transition hover:bg-primary-100 dark:bg-primary-950/20 dark:text-primary-400"
28
+ >
29
+ <mat-icon svgIcon="square-pen" class="mr-1.5 size-4"></mat-icon>
30
+ <span>Edit Permissions</span>
31
+ </button>
32
+ <button
33
+ type="button"
34
+ (click)="onDuplicate()"
35
+ class="inline-flex items-center justify-center rounded-xl border bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-700 transition hover:bg-amber-100 dark:bg-amber-950/20 dark:text-amber-400"
36
+ >
37
+ <mat-icon svgIcon="copy" class="mr-1.5 size-4"></mat-icon>
38
+ <span>Duplicate</span>
39
+ </button>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="mt-4 flex flex-col gap-4 border-t pt-4 text-xs text-slate-500 dark:text-slate-400 sm:flex-row">
44
+ <div>
45
+ <span class="font-semibold text-slate-700 dark:text-slate-300">Role Label:</span>
46
+ <span class="ml-1 inline-flex items-center rounded-md bg-slate-100 px-2 py-0.5 text-indigo-700 dark:bg-slate-800 dark:text-indigo-300">
47
+ {{ cleanRoleName(selectedRole.name || '') }}
48
+ </span>
49
+ </div>
50
+ </div>
51
+ </div>
@@ -0,0 +1,45 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatIconModule } from '@angular/material/icon';
4
+ import { RoleRepresentation } from '@/app/core/auth';
5
+ import { formatRoleDisplayName } from '@/app/core/user-management/shared/role-display.util';
6
+
7
+ @Component({
8
+ selector: 'app-role-header',
9
+ standalone: true,
10
+ imports: [CommonModule, MatIconModule],
11
+ templateUrl: './role-header.component.html',
12
+ })
13
+ export class RoleHeaderComponent {
14
+ @Input() selectedRole: RoleRepresentation | null = null;
15
+ @Input() showEditPermissions = true;
16
+ @Output() editPermissions = new EventEmitter<RoleRepresentation>();
17
+ @Output() duplicate = new EventEmitter<RoleRepresentation>();
18
+
19
+ cleanRoleName(name: string | undefined): string {
20
+ return formatRoleDisplayName(name);
21
+ }
22
+
23
+ isCoreSystemRole(roleName?: string): boolean {
24
+ if (!roleName) return false;
25
+ const name = roleName.toLowerCase().trim();
26
+ return (
27
+ name === 'default-roles-pmb' ||
28
+ name === 'keycloak_admin_access' ||
29
+ name === 'role_super_admin' ||
30
+ name === 'role_super_admin_pmb'
31
+ );
32
+ }
33
+
34
+ onEditPermissions(): void {
35
+ if (this.selectedRole) {
36
+ this.editPermissions.emit(this.selectedRole);
37
+ }
38
+ }
39
+
40
+ onDuplicate(): void {
41
+ if (this.selectedRole) {
42
+ this.duplicate.emit(this.selectedRole);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ <div *ngIf="selectedRole" class="flex flex-col rounded-2xl border bg-white p-6 shadow-sm dark:bg-slate-900">
2
+ <h3 class="mb-4 text-sm font-bold uppercase tracking-wider text-slate-900 dark:text-white">Inheritance Flow</h3>
3
+
4
+ <div class="flex items-center gap-3">
5
+ <div *ngIf="selectedRole.attributes?.['parentRole']?.[0]" class="flex items-center gap-3">
6
+ <div class="rounded-xl border bg-slate-50 px-4 py-2 font-semibold text-slate-700 dark:bg-slate-800 dark:text-slate-300">
7
+ {{ cleanRoleName(selectedRole.attributes?.['parentRole']?.[0]) }}
8
+ </div>
9
+ <mat-icon svgIcon="arrow-right" class="size-5"></mat-icon>
10
+ </div>
11
+ <div class="rounded-xl border border-primary-200 bg-primary-50/50 px-4 py-2 font-bold text-primary-800 dark:border-primary-900/40 dark:bg-primary-950/20 dark:text-primary-400">
12
+ {{ cleanRoleName(selectedRole.name || '') }}
13
+ </div>
14
+ </div>
15
+ </div>
@@ -0,0 +1,19 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatIconModule } from '@angular/material/icon';
4
+ import { RoleRepresentation } from '@/app/core/auth';
5
+ import { formatRoleDisplayName } from '@/app/core/user-management/shared/role-display.util';
6
+
7
+ @Component({
8
+ selector: 'app-role-inheritance',
9
+ standalone: true,
10
+ imports: [CommonModule, MatIconModule],
11
+ templateUrl: './role-inheritance.component.html',
12
+ })
13
+ export class RoleInheritanceComponent {
14
+ @Input() selectedRole: RoleRepresentation | null = null;
15
+
16
+ cleanRoleName(name: string | undefined): string {
17
+ return formatRoleDisplayName(name);
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ .expandable-content {
2
+ max-height: 0;
3
+ opacity: 0;
4
+ overflow: hidden;
5
+ transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease-in-out, margin-top 0.25s ease;
6
+ margin-top: 0;
7
+ }
8
+
9
+ .expandable-content.expanded {
10
+ max-height: 500px;
11
+ opacity: 1;
12
+ margin-top: 0.625rem;
13
+ }
14
+
15
+ .scroll-container::-webkit-scrollbar {
16
+ display: none !important;
17
+ }
18
+
19
+ .scroll-container {
20
+ -ms-overflow-style: none !important;
21
+ scrollbar-width: none !important;
22
+ overflow-x: hidden !important;
23
+ overflow-y: auto !important;
24
+ }
@@ -0,0 +1,108 @@
1
+ <div class="flex h-full select-none flex-col rounded-2xl border bg-white p-5 shadow-sm dark:bg-slate-900">
2
+ <div class="mb-4 flex items-center justify-between">
3
+ <div>
4
+ <h3 class="text-[12px] font-bold uppercase tracking-wider text-slate-900 dark:text-white">Screen Permissions</h3>
5
+ <p class="mt-0.5 text-[10px] text-slate-500">Assigned screens & action capabilities</p>
6
+ </div>
7
+ <button
8
+ type="button"
9
+ (click)="onConfigureMatrix()"
10
+ class="flex size-7 cursor-pointer items-center justify-center rounded-lg border border-primary-100/40 bg-primary-50 text-primary-700 shadow-sm transition hover:bg-primary-600 hover:text-white dark:border-primary-900/30 dark:bg-primary-950/30 dark:text-primary-400 dark:hover:bg-primary-600 dark:hover:text-white"
11
+ title="Configure Matrix"
12
+ >
13
+ <mat-icon svgIcon="layers" class="size-3.5"></mat-icon>
14
+ </button>
15
+ </div>
16
+
17
+ <div class="scroll-container max-h-[300px] flex-auto space-y-3 overflow-y-auto overflow-x-hidden pr-0.5">
18
+ <div
19
+ *ngFor="let screen of paginatedPermissions"
20
+ class="flex flex-col border-b border-slate-100 pb-2.5 last:border-0 last:pb-0 dark:border-slate-800/80"
21
+ >
22
+ <div
23
+ [class.cursor-pointer]="screen.components && screen.components.length > 0"
24
+ (click)="screen.components && screen.components.length > 0 ? toggleScreen(screen.name) : null"
25
+ class="-mx-1.5 flex items-center justify-between rounded-lg px-1.5 py-1 transition hover:bg-slate-50/40 dark:hover:bg-slate-800/20"
26
+ >
27
+ <div class="flex items-center gap-2">
28
+ <div class="flex size-7 items-center justify-center rounded-lg border border-indigo-100/40 bg-indigo-50 dark:border-indigo-900/30 dark:bg-indigo-950/30">
29
+ <mat-icon svgIcon="monitor" class="size-3.5 text-indigo-600 dark:text-indigo-400"></mat-icon>
30
+ </div>
31
+ <div class="flex flex-col">
32
+ <span class="text-xs font-bold leading-tight text-slate-900 dark:text-slate-100">{{ screen.name }}</span>
33
+ <span class="mt-0.5 text-[9px] text-slate-500">
34
+ {{
35
+ screen.components && screen.components.length > 0
36
+ ? screen.components.length + (screen.components.length === 1 ? ' action' : ' actions') + ' enabled'
37
+ : 'Access only (no custom actions)'
38
+ }}
39
+ </span>
40
+ </div>
41
+ </div>
42
+ <div class="flex items-center gap-1.5">
43
+ <span class="inline-flex items-center gap-1 text-[8px] font-bold text-primary-600 dark:text-primary-400">
44
+ <span class="size-1 animate-pulse rounded-full bg-primary-500"></span>
45
+ <span>Active</span>
46
+ </span>
47
+ <mat-icon
48
+ *ngIf="screen.components && screen.components.length > 0"
49
+ [svgIcon]="isExpanded(screen.name) ? 'chevron-down' : 'chevron-right'"
50
+ class="size-3.5 text-slate-400 transition-transform duration-200 dark:text-slate-500"
51
+ >
52
+ </mat-icon>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="expandable-content" [class.expanded]="isExpanded(screen.name)">
57
+ <div *ngIf="screen.components && screen.components.length > 0" class="flex flex-wrap items-center gap-1 pb-1 pl-9">
58
+ <span
59
+ *ngFor="let comp of screen.components"
60
+ class="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-slate-50 px-2 py-0.5 text-[9px] font-semibold text-slate-650 transition hover:bg-slate-100/50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300"
61
+ >
62
+ <span class="h-1 w-1 rounded-full bg-indigo-500"></span>
63
+ <span>{{ comp }}</span>
64
+ </span>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <div
70
+ *ngIf="selectedRolePermissions.length === 0"
71
+ class="flex flex-col items-center justify-center py-10 text-center text-slate-450 dark:text-slate-500"
72
+ >
73
+ <mat-icon svgIcon="shield" class="mb-2 size-8 text-slate-300 dark:text-slate-700"></mat-icon>
74
+ <div class="text-[11px] font-bold">No Screen Mappings</div>
75
+ <p class="mt-0.5 max-w-[180px] text-[10px]">Configure the permission matrix to assign capabilities.</p>
76
+ </div>
77
+ </div>
78
+
79
+ <div
80
+ *ngIf="totalPages > 1"
81
+ class="mt-2.5 flex items-center justify-between border-t border-slate-100 pt-2.5 dark:border-slate-800/80"
82
+ >
83
+ <span class="text-[9px] font-semibold text-slate-500">
84
+ {{ displayRangeStart }}-{{ displayRangeEnd }} of {{ selectedRolePermissions.length }}
85
+ </span>
86
+ <div class="flex items-center gap-1">
87
+ <button
88
+ type="button"
89
+ (click)="prevPage()"
90
+ [disabled]="currentPage === 0"
91
+ class="flex size-5 cursor-pointer items-center justify-center rounded-md border border-slate-200 text-slate-500 transition hover:bg-slate-50 focus:outline-none disabled:opacity-30 disabled:hover:bg-transparent dark:border-slate-800 dark:hover:bg-slate-800"
92
+ >
93
+ <mat-icon svgIcon="arrow-left" class="size-3"></mat-icon>
94
+ </button>
95
+ <span class="min-w-[24px] text-center text-[9px] font-bold text-slate-700 dark:text-slate-300">
96
+ {{ currentPage + 1 }} / {{ totalPages }}
97
+ </span>
98
+ <button
99
+ type="button"
100
+ (click)="nextPage()"
101
+ [disabled]="currentPage === totalPages - 1"
102
+ class="flex size-5 cursor-pointer items-center justify-center rounded-md border border-slate-200 text-slate-500 transition hover:bg-slate-50 focus:outline-none disabled:opacity-30 disabled:hover:bg-transparent dark:border-slate-800 dark:hover:bg-slate-800"
103
+ >
104
+ <mat-icon svgIcon="arrow-right" class="size-3"></mat-icon>
105
+ </button>
106
+ </div>
107
+ </div>
108
+ </div>
@@ -0,0 +1,122 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatIconModule } from '@angular/material/icon';
4
+
5
+ export type RolePermissionScreenSummary = {
6
+ name: string;
7
+ components: string[];
8
+ };
9
+
10
+ @Component({
11
+ selector: 'app-role-permissions-panel',
12
+ standalone: true,
13
+ imports: [CommonModule, MatIconModule],
14
+ templateUrl: './role-permissions-panel.component.html',
15
+ styleUrl: './role-permissions-panel.component.css',
16
+ })
17
+ export class RolePermissionsPanelComponent {
18
+ private _selectedRolePermissions: RolePermissionScreenSummary[] = [];
19
+ private _roleName = '';
20
+ collapsedScreens = new Set<string>();
21
+
22
+ currentPage = 0;
23
+ pageSize = 5;
24
+
25
+ @Input()
26
+ set roleName(val: string) {
27
+ this._roleName = val || '';
28
+ this.loadCollapsedState();
29
+ }
30
+ get roleName(): string {
31
+ return this._roleName;
32
+ }
33
+
34
+ @Input()
35
+ set selectedRolePermissions(val: RolePermissionScreenSummary[]) {
36
+ this._selectedRolePermissions = val || [];
37
+ this.currentPage = 0;
38
+ this.loadCollapsedState();
39
+ }
40
+
41
+ get selectedRolePermissions(): RolePermissionScreenSummary[] {
42
+ return this._selectedRolePermissions;
43
+ }
44
+
45
+ @Output() configureMatrix = new EventEmitter<void>();
46
+
47
+ onConfigureMatrix(): void {
48
+ this.configureMatrix.emit();
49
+ }
50
+
51
+ private getStorageKey(): string {
52
+ return `role-permissions-collapsed_${this.roleName}`;
53
+ }
54
+
55
+ private loadCollapsedState(): void {
56
+ this.collapsedScreens.clear();
57
+ if (!this.roleName) return;
58
+ try {
59
+ const raw = localStorage.getItem(this.getStorageKey());
60
+ if (raw) {
61
+ const arr = JSON.parse(raw);
62
+ if (Array.isArray(arr)) {
63
+ this.collapsedScreens = new Set(arr);
64
+ }
65
+ }
66
+ } catch (e) {
67
+ console.error('Failed to load collapsed state', e);
68
+ }
69
+ }
70
+
71
+ private saveCollapsedState(): void {
72
+ if (!this.roleName) return;
73
+ try {
74
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(Array.from(this.collapsedScreens)));
75
+ } catch (e) {
76
+ console.error('Failed to save collapsed state', e);
77
+ }
78
+ }
79
+
80
+ toggleScreen(screenName: string): void {
81
+ if (this.collapsedScreens.has(screenName)) {
82
+ this.collapsedScreens.delete(screenName);
83
+ } else {
84
+ this.collapsedScreens.add(screenName);
85
+ }
86
+ this.saveCollapsedState();
87
+ }
88
+
89
+ isExpanded(screenName: string): boolean {
90
+ return !this.collapsedScreens.has(screenName);
91
+ }
92
+
93
+ get paginatedPermissions(): RolePermissionScreenSummary[] {
94
+ const start = this.currentPage * this.pageSize;
95
+ return this.selectedRolePermissions.slice(start, start + this.pageSize);
96
+ }
97
+
98
+ get totalPages(): number {
99
+ return Math.ceil(this.selectedRolePermissions.length / this.pageSize);
100
+ }
101
+
102
+ get displayRangeStart(): number {
103
+ if (this.selectedRolePermissions.length === 0) return 0;
104
+ return this.currentPage * this.pageSize + 1;
105
+ }
106
+
107
+ get displayRangeEnd(): number {
108
+ return Math.min((this.currentPage + 1) * this.pageSize, this.selectedRolePermissions.length);
109
+ }
110
+
111
+ nextPage(): void {
112
+ if (this.currentPage < this.totalPages - 1) {
113
+ this.currentPage++;
114
+ }
115
+ }
116
+
117
+ prevPage(): void {
118
+ if (this.currentPage > 0) {
119
+ this.currentPage--;
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,104 @@
1
+ <div class="flex flex-col gap-6 rounded-2xl border bg-white p-6 shadow-sm dark:bg-slate-900">
2
+ <div class="flex items-center justify-between gap-4">
3
+ <div>
4
+ <h3 class="text-base font-bold uppercase tracking-wider text-slate-900 dark:text-white">
5
+ Assigned Users
6
+ </h3>
7
+ </div>
8
+
9
+ <button
10
+ type="button"
11
+ (click)="onOpenAssign()"
12
+ class="inline-flex items-center justify-center rounded-xl bg-primary-600 px-4 py-2 text-xs font-semibold text-white shadow-sm transition hover:bg-primary-700"
13
+ >
14
+ <mat-icon svgIcon="plus" class="mr-1.5 size-4"></mat-icon>
15
+ <span>Assign User</span>
16
+ </button>
17
+ </div>
18
+
19
+ <div class="flex flex-col justify-between gap-4 pt-2 sm:flex-row sm:items-center">
20
+ <div class="relative flex max-w-md flex-1 items-center">
21
+ <mat-icon svgIcon="search" class="pointer-events-none absolute left-3 size-4 text-slate-400"></mat-icon>
22
+ <input
23
+ type="text"
24
+ [value]="searchQuery"
25
+ (input)="onSearchChange($event)"
26
+ placeholder="Filter assigned users..."
27
+ class="w-full rounded-xl border border-slate-200 bg-white py-1.5 pl-9 pr-9 text-xs shadow-sm outline-none transition focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-slate-800 dark:bg-slate-900"
28
+ />
29
+ <button
30
+ *ngIf="searchQuery"
31
+ type="button"
32
+ (click)="clearSearch()"
33
+ class="absolute right-3 flex items-center justify-center text-slate-400 hover:text-slate-600 focus:outline-none"
34
+ >
35
+ <mat-icon svgIcon="x" class="size-4"></mat-icon>
36
+ </button>
37
+ </div>
38
+
39
+ <div class="flex shrink-0 items-center gap-1.5 text-xs font-semibold text-slate-500">
40
+ <span class="inline-block size-2 rounded-full bg-primary-500"></span>
41
+ <span>Total: {{ filteredUsersCount }}</span>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="overflow-hidden rounded-xl border border-slate-100 dark:border-slate-800">
46
+ <div class="divide-y divide-slate-100 bg-slate-50/50 dark:divide-slate-800 dark:bg-slate-900/30">
47
+ <div
48
+ *ngFor="let user of paginatedUsers"
49
+ class="flex items-center justify-between bg-white p-3 transition hover:bg-slate-50 dark:bg-slate-900 dark:hover:bg-slate-800/40"
50
+ >
51
+ <div class="flex items-center gap-3">
52
+ <div class="flex size-8 items-center justify-center rounded-full bg-slate-100 text-xs font-bold uppercase text-slate-600 dark:bg-slate-800 dark:text-slate-300">
53
+ {{ (user.username || '?').slice(0, 2) }}
54
+ </div>
55
+ <div>
56
+ <div class="text-sm font-bold text-slate-800 dark:text-slate-200">{{ user.username }}</div>
57
+ <div class="text-xs text-slate-400 dark:text-slate-500">
58
+ {{ user.firstName || '' }} {{ user.lastName || '' }}
59
+ <span *ngIf="user.email" class="mx-1">•</span> {{ user.email }}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ <button
64
+ type="button"
65
+ (click)="onUnassign(user)"
66
+ matTooltip="Revoke Role Assignment"
67
+ class="flex items-center justify-center rounded-lg p-1.5 text-slate-400 transition hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-950/20 dark:hover:text-red-400"
68
+ >
69
+ <mat-icon svgIcon="trash" class="size-4"></mat-icon>
70
+ </button>
71
+ </div>
72
+ <div *ngIf="paginatedUsers.length === 0" class="bg-white py-8 text-center text-xs text-slate-400 dark:bg-slate-900 dark:text-slate-500">
73
+ No users matching filter criteria.
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <div
79
+ *ngIf="totalPages > 1"
80
+ class="flex items-center justify-between border-t border-slate-100 pt-2 text-xs dark:border-slate-800"
81
+ >
82
+ <button
83
+ type="button"
84
+ [disabled]="currentPage === 0"
85
+ (click)="onPreviousPage()"
86
+ class="inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 font-semibold text-slate-600 transition hover:bg-slate-50 disabled:opacity-40 dark:border-slate-800 dark:text-slate-300 dark:hover:bg-slate-800"
87
+ >
88
+ <mat-icon svgIcon="arrow-left" class="size-3.5"></mat-icon>
89
+ <span>Previous</span>
90
+ </button>
91
+ <span class="font-medium text-slate-500">
92
+ Page {{ currentPage + 1 }} of {{ totalPages }}
93
+ </span>
94
+ <button
95
+ type="button"
96
+ [disabled]="currentPage >= totalPages - 1"
97
+ (click)="onNextPage()"
98
+ class="inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 font-semibold text-slate-600 transition hover:bg-slate-50 disabled:opacity-40 dark:border-slate-800 dark:text-slate-300 dark:hover:bg-slate-800"
99
+ >
100
+ <span>Next</span>
101
+ <mat-icon svgIcon="arrow-right" class="size-3.5"></mat-icon>
102
+ </button>
103
+ </div>
104
+ </div>
@@ -0,0 +1,53 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatIconModule } from '@angular/material/icon';
4
+ import { MatTooltipModule } from '@angular/material/tooltip';
5
+ import { UserRepresentation } from '@/app/core/auth';
6
+
7
+ @Component({
8
+ selector: 'app-role-users',
9
+ standalone: true,
10
+ imports: [CommonModule, MatIconModule, MatTooltipModule],
11
+ templateUrl: './role-users.component.html',
12
+ })
13
+ export class RoleUsersComponent {
14
+ @Input() searchQuery = '';
15
+ @Input() filteredUsersCount = 0;
16
+ @Input() paginatedUsers: UserRepresentation[] = [];
17
+ @Input() currentPage = 0;
18
+ @Input() totalPages = 0;
19
+
20
+ @Output() searchQueryChange = new EventEmitter<string>();
21
+ @Output() pageChange = new EventEmitter<number>();
22
+ @Output() openAssignUser = new EventEmitter<void>();
23
+ @Output() unassignUser = new EventEmitter<UserRepresentation>();
24
+
25
+ onSearchChange(event: Event): void {
26
+ const value = (event.target as HTMLInputElement).value;
27
+ this.searchQueryChange.emit(value);
28
+ }
29
+
30
+ clearSearch(): void {
31
+ this.searchQueryChange.emit('');
32
+ }
33
+
34
+ onPreviousPage(): void {
35
+ if (this.currentPage > 0) {
36
+ this.pageChange.emit(this.currentPage - 1);
37
+ }
38
+ }
39
+
40
+ onNextPage(): void {
41
+ if (this.currentPage < this.totalPages - 1) {
42
+ this.pageChange.emit(this.currentPage + 1);
43
+ }
44
+ }
45
+
46
+ onOpenAssign(): void {
47
+ this.openAssignUser.emit();
48
+ }
49
+
50
+ onUnassign(user: UserRepresentation): void {
51
+ this.unassignUser.emit(user);
52
+ }
53
+ }
@@ -0,0 +1,113 @@
1
+ <div
2
+ [class.slide-down-anim]="isClosing()"
3
+ class="flex h-[750px] w-full max-w-full select-none flex-col overflow-hidden rounded-b-none rounded-t-3xl bg-white p-6 dark:bg-slate-900"
4
+ >
5
+ <div class="mx-auto mb-3 h-1.5 w-12 shrink-0 cursor-pointer rounded-full bg-slate-200 dark:bg-slate-700"></div>
6
+
7
+ <div class="flex shrink-0 items-center justify-between border-b border-slate-100 pb-4 dark:border-slate-800">
8
+ <div>
9
+ <h3 class="flex items-center gap-2 text-xl font-bold text-slate-900 dark:text-white">
10
+ <mat-icon svgIcon="users" class="size-6 text-primary-600 dark:text-primary-400"></mat-icon>
11
+ <span>Assign Users to Role</span>
12
+ </h3>
13
+ <p class="mt-1 text-xs text-slate-500">Search, browse, and add active users to this identity role</p>
14
+ </div>
15
+ <button
16
+ *ngIf="SCREEN | accessCtrl: 'on-close'"
17
+ id="qa-user-select-dialog__on-close"
18
+ data-qa-id="qa-user-select-dialog__on-close"
19
+ data-component-id="on-close"
20
+ data-screen-id="user-select-dialog"
21
+ type="button"
22
+ (click)="onClose()"
23
+ class="flex items-center justify-center rounded-xl p-2 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 dark:hover:bg-slate-800 dark:hover:text-slate-200"
24
+ >
25
+ <mat-icon svgIcon="x" class="size-5"></mat-icon>
26
+ </button>
27
+ </div>
28
+
29
+ <div class="relative flex shrink-0 items-center py-4">
30
+ <mat-icon svgIcon="search" class="pointer-events-none absolute left-3.5 size-5 text-slate-400"></mat-icon>
31
+ <input
32
+ type="text"
33
+ [(ngModel)]="searchQuery"
34
+ (ngModelChange)="onSearchChange($event)"
35
+ placeholder="Search users by username, first/last name, or email address..."
36
+ class="w-full rounded-xl border border-slate-200 bg-white py-3 pl-10 pr-10 text-sm shadow-sm outline-none transition placeholder:text-slate-400 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-slate-800 dark:bg-slate-900 dark:text-white"
37
+ />
38
+ <button
39
+ id="qa-user-select-dialog__clear-search"
40
+ data-qa-id="qa-user-select-dialog__clear-search"
41
+ data-component-id="clear-search"
42
+ data-screen-id="user-select-dialog"
43
+ *ngIf="searchQuery && (SCREEN | accessCtrl: 'clear-search')"
44
+ type="button"
45
+ (click)="clearSearch()"
46
+ class="absolute right-3.5 flex items-center justify-center rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 focus:outline-none dark:hover:bg-slate-800 dark:hover:text-slate-200"
47
+ >
48
+ <mat-icon svgIcon="x" class="size-4"></mat-icon>
49
+ </button>
50
+ </div>
51
+
52
+ <div class="no-scrollbar min-h-0 flex-auto overflow-y-auto pr-1.5">
53
+ <div *ngIf="isLoading()" class="flex flex-col items-center justify-center gap-3 py-24 text-sm text-slate-400">
54
+ <span class="size-8 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"></span>
55
+ <span class="font-medium text-slate-500 dark:text-slate-400">Searching user directory...</span>
56
+ </div>
57
+
58
+ <ng-container *ngIf="!isLoading()">
59
+ <div *ngIf="users().length > 0" class="grid grid-cols-1 gap-4 pb-4 md:grid-cols-2">
60
+ <div
61
+ *ngFor="let user of users()"
62
+ class="flex items-center justify-between rounded-2xl border border-slate-150/60 bg-slate-50/40 p-4 transition duration-200 hover:bg-slate-50 dark:border-slate-800/60 dark:bg-slate-900/20 dark:hover:bg-slate-800/40"
63
+ >
64
+ <div class="flex min-w-0 items-center gap-4">
65
+ <div class="flex size-11 shrink-0 items-center justify-center rounded-xl border border-primary-100/50 bg-primary-50 text-sm font-bold uppercase text-primary-700 dark:border-primary-900/30 dark:bg-primary-950/30 dark:text-primary-400">
66
+ <mat-icon svgIcon="circle-user" class="size-6 text-primary-600 dark:text-primary-400"></mat-icon>
67
+ </div>
68
+ <div class="min-w-0">
69
+ <div class="truncate text-sm font-bold text-slate-800 dark:text-slate-100">
70
+ {{ user.username }}
71
+ </div>
72
+ <div class="mt-0.5 truncate text-xs text-slate-500 dark:text-slate-400">
73
+ <span class="font-medium text-slate-700 dark:text-slate-300">
74
+ {{ user.firstName || '' }} {{ user.lastName || '' }}
75
+ </span>
76
+ <span *ngIf="user.email" class="mx-1 text-slate-300 dark:text-slate-700">•</span>
77
+ <span>{{ user.email }}</span>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <button
83
+ id="qa-user-select-dialog__assign-user"
84
+ data-qa-id="qa-user-select-dialog__assign-user"
85
+ data-component-id="assign-user"
86
+ data-screen-id="user-select-dialog"
87
+ *ngIf="!isAssigned(user) && (SCREEN | accessCtrl: 'assign-user')"
88
+ type="button"
89
+ (click)="assignUser(user)"
90
+ class="ml-3 shrink-0 rounded-xl bg-primary-600 px-4 py-2 text-xs font-bold text-white shadow-sm transition duration-150 hover:bg-primary-700 hover:shadow"
91
+ >
92
+ Assign
93
+ </button>
94
+ <span
95
+ *ngIf="isAssigned(user)"
96
+ class="ml-3 flex shrink-0 select-none items-center gap-1.5 rounded-xl border border-slate-200/50 bg-slate-100 px-3 py-1.5 text-[11px] font-semibold text-slate-500 dark:border-slate-700/50 dark:bg-slate-800 dark:text-slate-400"
97
+ >
98
+ <mat-icon svgIcon="circle-check" class="size-4 text-primary-600 dark:text-primary-400"></mat-icon>
99
+ <span>Assigned</span>
100
+ </span>
101
+ </div>
102
+ </div>
103
+
104
+ <div *ngIf="users().length === 0" class="flex flex-col items-center justify-center py-32 text-center">
105
+ <mat-icon svgIcon="users" class="mb-3 size-16 text-slate-300 dark:text-slate-700"></mat-icon>
106
+ <div class="text-sm font-bold text-slate-700 dark:text-slate-300">No users found</div>
107
+ <div class="mx-auto mt-1 max-w-xs text-xs text-slate-500">
108
+ We couldn't find any matching profiles. Try checking the username spelling or using another query.
109
+ </div>
110
+ </div>
111
+ </ng-container>
112
+ </div>
113
+ </div>