@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,153 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { HttpClient, HttpResponse, HttpParams, HttpHeaders } from '@angular/common/http';
3
+ import { Observable, of } from 'rxjs';
4
+ import { map, delay } from 'rxjs/operators';
5
+ import dayjs from 'dayjs/esm';
6
+ import { normalizeInputFields } from '<%= preferences.fuse?.enabled ? '@/app/domains/admin/modules/apps/shared/base' : preferences.architecture?.centralizedSharedFolder ? '../../../shared/base' : '../shared/base' %>/input-format';
7
+
8
+ import { <%= toCamelCase(entity.name) %>Settings } from '../settings/settings.config';
9
+ <% if (preferences.mock?.enabled) { %>
10
+ import { mockData } from '../mock/data';
11
+ <% } %>
12
+
13
+ import { <%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>, New<%= entity.name %> } from '../<%= toKebabCase(entity.name) %>.model';
14
+
15
+ <%
16
+ const dateFields = entity.fields.filter(f => ['LocalDate', 'Instant', 'ZonedDateTime'].includes(f.fieldType));
17
+ const hasDateFields = dateFields.length > 0;
18
+ %>
19
+
20
+ export type PartialUpdate<%= entity.name %> = Partial<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>> & Pick<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>, 'id'>;
21
+
22
+ // --- Define REST-safe types by converting Dayjs objects to strings ---
23
+ type RestOf<T extends <%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %> | New<%= entity.name %> | PartialUpdate<%= entity.name %>> = Omit<T, <%- hasDateFields ? dateFields.map(f => `'${f.fieldName}'`).join(' | ') : 'never' %>> & {
24
+ <% dateFields.forEach(field => { %>
25
+ <%= field.fieldName %>?: string | null;
26
+ <% }); %>
27
+ };
28
+
29
+ export type Rest<%= entity.name %> = RestOf<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>>;
30
+ export type NewRest<%= entity.name %> = RestOf<New<%= entity.name %>>;
31
+ export type PartialUpdateRest<%= entity.name %> = RestOf<PartialUpdate<%= entity.name %>>;
32
+
33
+ export type EntityResponseType = HttpResponse<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>>;
34
+ export type EntityArrayResponseType = HttpResponse<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>[]>;
35
+
36
+ @Injectable({ providedIn: 'root' })
37
+ export class <%= entity.name %>Service {
38
+ protected readonly http = inject(HttpClient);
39
+ protected resourceUrl = `<%= (entity.microservice || config.api?.microservice) ? '/services/' + (entity.microservice || config.api.microservice) : '' %><%= (config.api?.baseUrl || '/api').replace(/\/$/, '') %>/<%= toKebabCase(entity.name) %>s`;
40
+
41
+ create(payload: New<%= entity.name %>): Observable<EntityResponseType> {
42
+ const copy = this.convertDateFromClient(payload);
43
+ <% if (preferences.mock?.enabled) { %>
44
+ if (<%= toCamelCase(entity.name) %>Settings.useMockData) {
45
+ return of(new HttpResponse({ status: 201, body: { ...copy, id: Math.floor(Math.random() * 1000) } as any })).pipe(delay(500), map(res => this.convertResponseFromServer(res)));
46
+ }
47
+ <% } %>
48
+ return this.http.post<Rest<%= entity.name %>>(this.resourceUrl, copy, { observe: 'response' }).pipe(map(res => this.convertResponseFromServer(res)));
49
+ }
50
+
51
+ update(payload: <%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>): Observable<EntityResponseType> {
52
+ const copy = this.convertDateFromClient(payload);
53
+ <% if (preferences.mock?.enabled) { %>
54
+ if (<%= toCamelCase(entity.name) %>Settings.useMockData) {
55
+ return of(new HttpResponse({ status: 200, body: copy as any })).pipe(delay(500), map(res => this.convertResponseFromServer(res)));
56
+ }
57
+ <% } %>
58
+ return this.http.put<Rest<%= entity.name %>>(`${this.resourceUrl}/${payload.id}`, copy, { observe: 'response' }).pipe(map(res => this.convertResponseFromServer(res)));
59
+ }
60
+
61
+ find(id: number): Observable<EntityResponseType> {
62
+ <% if (preferences.mock?.enabled) { %>
63
+ if (<%= toCamelCase(entity.name) %>Settings.useMockData) {
64
+ const found = mockData.find((item: any) => item.id === id) || mockData[0];
65
+ return of(new HttpResponse({ status: 200, body: found as any })).pipe(delay(500), map(res => this.convertResponseFromServer(res)));
66
+ }
67
+ <% } %>
68
+ return this.http.get<Rest<%= entity.name %>>(`${this.resourceUrl}/${id}`, { observe: 'response' }).pipe(map(res => this.convertResponseFromServer(res)));
69
+ }
70
+
71
+ query(req?: any): Observable<EntityArrayResponseType> {
72
+ <% if (preferences.mock?.enabled) { %>
73
+ if (<%= toCamelCase(entity.name) %>Settings.useMockData) {
74
+ const page = req?.page ?? 0;
75
+ const size = req?.size ?? 20;
76
+ let filtered = [...mockData];
77
+ if (req?.query) {
78
+ const query = String(req.query).toLowerCase();
79
+ filtered = filtered.filter(item =>
80
+ Object.values(item).some(value => String(value ?? '').toLowerCase().includes(query))
81
+ );
82
+ }
83
+ Object.keys(req || {}).forEach(key => {
84
+ if (!key.endsWith('.contains')) {
85
+ return;
86
+ }
87
+ const field = key.replace(/\.contains$/, '');
88
+ const query = String(req[key]).toLowerCase();
89
+ filtered = filtered.filter(item => String((item as any)[field] ?? '').toLowerCase().includes(query));
90
+ });
91
+ const start = page * size;
92
+ const paginatedData = filtered.slice(start, start + size);
93
+ return of(new HttpResponse({
94
+ status: 200,
95
+ body: paginatedData as any,
96
+ headers: new HttpHeaders({ 'X-Total-Count': filtered.length.toString() })
97
+ })).pipe(delay(500), map(res => this.convertResponseArrayFromServer(res)));
98
+ }
99
+ <% } %>
100
+ const options = this.createRequestOption(req);
101
+ return this.http.get<Rest<%= entity.name %>[]>(this.resourceUrl, { params: options, observe: 'response' }).pipe(map(res => this.convertResponseArrayFromServer(res)));
102
+ }
103
+
104
+ delete(id: number): Observable<HttpResponse<{}>> {
105
+ <% if (preferences.mock?.enabled) { %>
106
+ if (<%= toCamelCase(entity.name) %>Settings.useMockData) {
107
+ return of(new HttpResponse<{}>({ status: 204 })).pipe(delay(500));
108
+ }
109
+ <% } %>
110
+ return this.http.delete(`${this.resourceUrl}/${id}`, { observe: 'response' });
111
+ }
112
+
113
+ protected createRequestOption(req?: any): HttpParams {
114
+ let options: HttpParams = new HttpParams();
115
+ if (req) {
116
+ Object.keys(req).forEach(key => {
117
+ if (req[key] !== null && req[key] !== undefined) {
118
+ options = options.set(key, req[key]);
119
+ }
120
+ });
121
+ }
122
+ return options;
123
+ }
124
+
125
+ // --- Date Conversion Helpers ---
126
+ protected convertDateFromClient<T extends <%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %> | New<%= entity.name %> | PartialUpdate<%= entity.name %>>(entity: T): RestOf<T> {
127
+ const copy: any = normalizeInputFields(entity, <%= toCamelCase(entity.name) %>Settings.fields);
128
+ <% dateFields.forEach(field => { %>
129
+ if (dayjs.isDayjs(entity.<%= field.fieldName %>)) {
130
+ copy.<%= field.fieldName %> = entity.<%= field.fieldName %>.toJSON();
131
+ }
132
+ <% }); %>
133
+ return copy;
134
+ }
135
+
136
+ protected convertDateFromServer(restEntity: Rest<%= entity.name %>): <%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %> {
137
+ const entity: any = { ...restEntity };
138
+ <% dateFields.forEach(field => { %>
139
+ if (entity.<%= field.fieldName %>) {
140
+ entity.<%= field.fieldName %> = dayjs(entity.<%= field.fieldName %>);
141
+ }
142
+ <% }); %>
143
+ return entity;
144
+ }
145
+
146
+ protected convertResponseFromServer(res: HttpResponse<Rest<%= entity.name %>>): HttpResponse<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>> {
147
+ return res.clone({ body: res.body ? this.convertDateFromServer(res.body) : null });
148
+ }
149
+
150
+ protected convertResponseArrayFromServer(res: HttpResponse<Rest<%= entity.name %>[]>): HttpResponse<<%= preferences.namingConventions?.modelInterfacePrefix || 'I' %><%= entity.name %>[]> {
151
+ return res.clone({ body: res.body ? res.body.map(item => this.convertDateFromServer(item)) : null });
152
+ }
153
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * settings.config.ts — runtime UI settings for <%= entity.name %>
3
+ *
4
+ * Edit these values to change list/form behavior without re-running the generator.
5
+ * Prefer changing `config/config.yml` (or `fields.entities`) and regenerating when
6
+ * you want the defaults to stick for the whole project.
7
+ *
8
+ * ---------------------------------------------------------------------------
9
+ * Keywords (accepted values)
10
+ * ---------------------------------------------------------------------------
11
+ * fields.<fieldName>.inputFormat → 'uppercase' | 'lowercase' | 'none'
12
+ * viewMode → 'table' | 'card'
13
+ * formMode → 'sidebar' | 'page' (new + edit share one mode)
14
+ * searchMode → 'field' | 'global' (global only if API accepts `query`)
15
+ * useMockData → true | false
16
+ * snackbarPositionTop → true | false
17
+ * snackbarSuccessColor / Error → CSS color string, e.g. '#16a34a'
18
+ * searchable → true | false (from GDL/JDL @Searchable)
19
+ * activeStatus → true | false (from GDL/JDL @ActiveStatus)
20
+ *
21
+ * ---------------------------------------------------------------------------
22
+ * Samples
23
+ * ---------------------------------------------------------------------------
24
+ * fields: {
25
+ * regionCode: { inputFormat: 'uppercase' },
26
+ * email: { inputFormat: 'lowercase' },
27
+ * notes: { inputFormat: 'none' },
28
+ * }
29
+ * viewMode: 'card'
30
+ * formMode: 'page'
31
+ * searchMode: 'field'
32
+ * useMockData: true
33
+ * snackbarPositionTop: false
34
+ * snackbarSuccessColor: '#16a34a'
35
+ * snackbarErrorColor: '#dc2626'
36
+ */
37
+
38
+ export const <%= toCamelCase(entity.name) %>Settings = {
39
+ fields: <%- JSON.stringify(typeof fieldSettings === 'undefined' ? {} : fieldSettings, null, 2) %> as const,
40
+ useMockData: <%= preferences.mock?.enabled === true %>,
41
+ snackbarPositionTop: <%= preferences.ui?.snackbarPositionTop === true %>,
42
+ snackbarSuccessColor: '<%= preferences.ui?.styling?.snackbarSuccessColor || "#16a34a" %>',
43
+ snackbarErrorColor: '<%= preferences.ui?.styling?.snackbarErrorColor || "#dc2626" %>',
44
+ viewMode: '<%= preferences.ui?.viewMode || "table" %>',
45
+ formMode: '<%= preferences.ui?.formMode || "sidebar" %>',
46
+ searchMode: '<%= preferences.ui?.searchMode || "field" %>',
47
+ searchable: <%= entity.searchable === true %>,
48
+ activeStatus: <%= entity.activeStatus === true %>
49
+ };
@@ -0,0 +1,11 @@
1
+ // This is a generated enum file.
2
+ export enum <%= enumDef.name %> {
3
+ <% enumDef.values.forEach((value, index) => { %>
4
+ <%
5
+ let valName = value.name;
6
+ if (preferences.namingConventions?.enumValueCasing === 'uppercase') valName = valName.toUpperCase();
7
+ if (preferences.namingConventions?.enumValueCasing === 'lowercase') valName = valName.toLowerCase();
8
+ %>
9
+ <%= value.name.toUpperCase() %> = '<%= valName %>'<%= index < enumDef.values.length - 1 ? ',' : '' %>
10
+ <% }); %>
11
+ }
@@ -0,0 +1,39 @@
1
+ <div class="overflow-hidden">
2
+ <div class="border-b border-slate-200 px-6 py-5 dark:border-slate-800">
3
+ <div class="flex items-start gap-4">
4
+ <div class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary">
5
+ <mat-icon svgIcon="key"></mat-icon>
6
+ </div>
7
+ <div class="min-w-0 flex-1">
8
+ <h2 mat-dialog-title class="!p-0 text-xl font-semibold tracking-tight">Change password</h2>
9
+ <p class="mt-2 text-sm leading-6 text-slate-600 dark:text-slate-300">
10
+ Set a new password for this user. They can sign in immediately with the updated credentials.
11
+ </p>
12
+ </div>
13
+ </div>
14
+ </div>
15
+
16
+ <form [formGroup]="form" (ngSubmit)="save()" class="px-6 py-5">
17
+ <mat-form-field appearance="outline" subscriptSizing="dynamic" class="w-full">
18
+ <mat-label>New password</mat-label>
19
+ <mat-icon matPrefix svgIcon="lock-keyhole" class="mr-2 text-slate-500"></mat-icon>
20
+ <input matInput formControlName="password" [type]="hidePassword ? 'password' : 'text'" autocomplete="new-password" />
21
+ <button mat-icon-button matSuffix type="button" (click)="hidePassword = !hidePassword" [attr.aria-label]="hidePassword ? 'Show password' : 'Hide password'">
22
+ <mat-icon [svgIcon]="hidePassword ? 'eye-off' : 'eye'"></mat-icon>
23
+ </button>
24
+ </mat-form-field>
25
+
26
+ <div class="mt-4 flex items-center justify-between rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 dark:border-slate-800 dark:bg-slate-900">
27
+ <div>
28
+ <div class="text-sm font-semibold">Temporary password</div>
29
+ <div class="text-xs text-slate-500">Require the user to reset this password on next login</div>
30
+ </div>
31
+ <mat-slide-toggle formControlName="temporary" color="primary"></mat-slide-toggle>
32
+ </div>
33
+
34
+ <div class="mt-5 flex items-center justify-end gap-3">
35
+ <button mat-stroked-button type="button" (click)="dialogRef.close(false)">Cancel</button>
36
+ <button mat-flat-button color="primary" type="submit" [disabled]="form.invalid">Save</button>
37
+ </div>
38
+ </form>
39
+ </div>
@@ -0,0 +1,44 @@
1
+ import { Component, inject } from '@angular/core';
2
+ import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3
+ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatFormFieldModule } from '@angular/material/form-field';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import { MatInputModule } from '@angular/material/input';
8
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
9
+ import { KeycloakAdminService } from '@/app/core/auth';
10
+
11
+ @Component({
12
+ selector: 'app-change-password',
13
+ standalone: true,
14
+ imports: [
15
+ ReactiveFormsModule,
16
+ MatDialogModule,
17
+ MatButtonModule,
18
+ MatFormFieldModule,
19
+ MatIconModule,
20
+ MatInputModule,
21
+ MatSlideToggleModule,
22
+ ],
23
+ templateUrl: './change-password.component.html',
24
+ })
25
+ export class ChangePasswordComponent {
26
+ private readonly fb = inject(FormBuilder);
27
+ private readonly keycloak = inject(KeycloakAdminService);
28
+ readonly dialogRef = inject(MatDialogRef<ChangePasswordComponent, boolean>);
29
+ readonly userId = inject(MAT_DIALOG_DATA) as string;
30
+ hidePassword = true;
31
+ readonly form = this.fb.nonNullable.group({
32
+ password: ['', Validators.required],
33
+ temporary: [true],
34
+ });
35
+
36
+ save(): void {
37
+ const value = this.form.getRawValue();
38
+ this.keycloak.resetPassword(this.userId, {
39
+ type: 'password',
40
+ value: value.password,
41
+ temporary: value.temporary,
42
+ }).subscribe(() => this.dialogRef.close(true));
43
+ }
44
+ }
@@ -0,0 +1,17 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'app-no-access',
5
+ standalone: true,
6
+ template: `
7
+ <div class="flex flex-col flex-auto items-center justify-center p-6 sm:p-10">
8
+ <div class="text-4xl font-extrabold tracking-tight text-center sm:text-5xl">
9
+ No screens assigned
10
+ </div>
11
+ <div class="mt-4 max-w-lg text-lg text-center text-secondary">
12
+ Your account is signed in, but no screens are available yet. Ask an administrator to grant access.
13
+ </div>
14
+ </div>
15
+ `,
16
+ })
17
+ export default class NoAccessComponent {}
@@ -0,0 +1,331 @@
1
+ <div class="flex h-full min-w-0 flex-auto flex-col overflow-hidden bg-slate-50 dark:bg-slate-900">
2
+ <div class="z-10 flex flex-0 flex-col border-b bg-white p-6 shadow-sm sm:flex-row sm:items-center sm:justify-between sm:py-8 dark:bg-slate-900">
3
+ <div class="min-w-0 flex-1">
4
+ <div class="flex items-center gap-x-2 text-xs font-semibold uppercase tracking-wider text-slate-500">
5
+ <a
6
+ routerLink=".."
7
+ class="text-primary-600 hover:underline dark:text-primary-400"
8
+ >Users</a>
9
+ <span class="text-slate-400">/</span>
10
+ <span>Role Management</span>
11
+ </div>
12
+ <div class="mt-2 text-3xl font-extrabold leading-none tracking-tight text-slate-900 dark:text-white">
13
+ Roles & Access Management
14
+ </div>
15
+ </div>
16
+ <div class="mt-6 flex shrink-0 items-center gap-4 sm:mt-0">
17
+ <button
18
+ *ngIf="selectedRole()?.name && (SCREEN | accessCtrl: 'sync-holders')"
19
+ id="qa-role-management__sync-holders"
20
+ data-qa-id="qa-role-management__sync-holders"
21
+ data-component-id="sync-holders"
22
+ data-screen-id="role-management"
23
+ type="button"
24
+ (click)="syncSelectedRoleHolders()"
25
+ [disabled]="isSyncingHolders()"
26
+ class="inline-flex items-center justify-center rounded-xl border border-amber-200 bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-900 shadow-sm transition hover:bg-amber-100 focus:outline-none disabled:opacity-50 dark:border-amber-900/50 dark:bg-amber-950/30 dark:text-amber-200 dark:hover:bg-amber-950/50"
27
+ title="Rebuild user attributes from assigned roles for everyone holding this role"
28
+ >
29
+ <mat-icon class="mr-2" [class.animate-spin]="isSyncingHolders()" svgIcon="refresh-cw"></mat-icon>
30
+ <span>{{ isSyncingHolders() ? 'Syncing…' : 'Sync holders' }}</span>
31
+ </button>
32
+ <% if (iam.rolePermissions !== false) { %>
33
+ <a
34
+ *ngIf="SCREEN | accessCtrl: 'role-permissions'"
35
+ id="qa-role-management__role-permissions"
36
+ data-qa-id="qa-role-management__role-permissions"
37
+ data-component-id="role-permissions"
38
+ data-screen-id="role-management"
39
+ routerLink="../role-permissions"
40
+ class="inline-flex items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:bg-slate-50 focus:outline-none dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700"
41
+ >
42
+ <mat-icon class="mr-2 text-slate-500 dark:text-slate-400" svgIcon="layers"></mat-icon>
43
+ <span>Permission Matrix</span>
44
+ </a>
45
+ <% } %>
46
+ <button
47
+ id="qa-role-management__toggle-create-form"
48
+ data-qa-id="qa-role-management__toggle-create-form"
49
+ data-component-id="toggle-create-form"
50
+ data-screen-id="role-management"
51
+ *ngIf="SCREEN | accessCtrl: 'toggle-create-form'"
52
+ type="button"
53
+ (click)="toggleCreateForm()"
54
+ class="inline-flex items-center justify-center rounded-xl bg-primary-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-primary-700 focus:outline-none"
55
+ >
56
+ <mat-icon class="mr-2" [svgIcon]="showCreateForm() ? 'x' : 'plus'"></mat-icon>
57
+ <span>{{ showCreateForm() ? 'Cancel' : 'Create Role' }}</span>
58
+ </button>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="main-content-layout relative flex min-h-0 flex-auto flex-row overflow-hidden">
63
+ <div
64
+ class="flex min-w-0 flex-auto flex-col border-r bg-white transition-all duration-300 md:w-80 md:flex-initial lg:w-96 dark:bg-slate-900"
65
+ [class.hidden]="isMobile() && showMobileDetails() && selectedRole()"
66
+ >
67
+ <div class="flex flex-col gap-3 border-b p-4">
68
+ <div class="text-sm font-bold uppercase tracking-wide text-slate-900 dark:text-white">Roles</div>
69
+ <div class="relative flex items-center">
70
+ <mat-icon svgIcon="search" class="pointer-events-none absolute left-3 size-4 text-slate-400"></mat-icon>
71
+ <input
72
+ type="text"
73
+ [value]="searchQuery()"
74
+ (input)="searchQuery.set($any($event.target).value); rolesPage.set(0)"
75
+ placeholder="Search roles..."
76
+ class="w-full rounded-xl border border-slate-200 bg-white py-2 pl-9 pr-9 text-sm 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"
77
+ />
78
+ @if (searchQuery()) {
79
+ <button
80
+ id="qa-role-management__search-clear"
81
+ data-qa-id="qa-role-management__search-clear"
82
+ data-component-id="search-clear"
83
+ data-screen-id="role-management"
84
+ *ngIf="SCREEN | accessCtrl: 'search-clear'"
85
+ type="button"
86
+ (click)="searchQuery.set(''); rolesPage.set(0)"
87
+ title="Clear role search"
88
+ class="absolute right-3 flex items-center justify-center text-slate-400 hover:text-slate-600 focus:outline-none"
89
+ >
90
+ <mat-icon svgIcon="x" class="size-4"></mat-icon>
91
+ </button>
92
+ }
93
+ </div>
94
+ </div>
95
+
96
+ <div class="no-scrollbar flex-auto space-y-3 overflow-y-auto p-4">
97
+ <div
98
+ id="qa-role-management__select-role"
99
+ data-qa-id="qa-role-management__select-role"
100
+ data-component-id="select-role"
101
+ data-screen-id="role-management"
102
+ *ngFor="let role of paginatedRoles()"
103
+ (click)="selectRole(role)"
104
+ [matTooltip]="getRoleTooltip(role.name)"
105
+ matTooltipPosition="above"
106
+ [class.border-primary-500]="selectedRole()?.name === role.name"
107
+ [class.bg-primary-50]="selectedRole()?.name === role.name"
108
+ [class.dark:bg-primary-900/20]="selectedRole()?.name === role.name"
109
+ [class.bg-white]="selectedRole()?.name !== role.name"
110
+ [class.dark:bg-slate-900]="selectedRole()?.name !== role.name"
111
+ [class.border-slate-200]="selectedRole()?.name !== role.name"
112
+ [class.dark:border-slate-800]="selectedRole()?.name !== role.name"
113
+ class="group relative flex cursor-pointer flex-col overflow-hidden rounded-xl border p-4 shadow-sm transition hover:bg-slate-50 dark:hover:bg-slate-800/40"
114
+ >
115
+ <div class="flex items-start justify-between gap-2">
116
+ <div class="flex items-center gap-2.5">
117
+ <div class="flex size-8 shrink-0 items-center justify-center rounded-lg shadow-xs" [ngClass]="getRoleBadgeClass(role.name)">
118
+ <mat-icon [svgIcon]="getRoleIcon(role.name)" class="size-4"></mat-icon>
119
+ </div>
120
+ <div class="text-sm font-bold text-slate-900 dark:text-white">
121
+ {{ cleanRoleName(role.name || '') }}
122
+ </div>
123
+ </div>
124
+ <mat-icon
125
+ *ngIf="selectedRole()?.name === role.name"
126
+ svgIcon="circle-check"
127
+ class="size-5 shrink-0 text-primary-600 dark:text-primary-400"
128
+ ></mat-icon>
129
+ </div>
130
+
131
+ <div class="mt-2 truncate pr-24 text-xs text-slate-500 dark:text-slate-400">
132
+ {{ role.description || 'No description provided.' }}
133
+ </div>
134
+
135
+ <div class="mt-3 flex flex-wrap items-center gap-2">
136
+ <span [ngClass]="getRoleBadgeClass(role.name)" class="rounded-lg border px-2.5 py-0.5 font-mono text-xs font-bold shadow-2xs">
137
+ {{ cleanRoleName(role.name || '') }}
138
+ </span>
139
+ <% if (iam.inheritance !== false) { %>
140
+ <span
141
+ *ngIf="role.attributes?.['parentRole']?.[0]"
142
+ class="rounded-lg border border-indigo-200 bg-indigo-50 px-2.5 py-0.5 text-xs font-bold text-indigo-900 dark:border-indigo-900/40 dark:bg-indigo-950/30 dark:text-indigo-400"
143
+ >
144
+ Inherits: {{ cleanRoleName(role.attributes?.['parentRole']?.[0]) }}
145
+ </span>
146
+ <% } %>
147
+ </div>
148
+
149
+ <div
150
+ *ngIf="isCoreSystemRole(role.name)"
151
+ class="absolute bottom-0 right-0 z-10 inline-flex items-center gap-1.5 rounded-tl-xl border-l border-t border-rose-200 bg-rose-50 px-3 py-1 text-xs font-extrabold text-rose-900 shadow-xs dark:border-rose-900/60 dark:bg-rose-950/80 dark:text-rose-300"
152
+ matTooltip="Core Platform Role — Mandatory for system operation & security"
153
+ matTooltipPosition="above"
154
+ >
155
+ <mat-icon svgIcon="lock" class="size-4 animate-pulse text-rose-600 dark:text-rose-400"></mat-icon>
156
+ <span>Core Required</span>
157
+ </div>
158
+ </div>
159
+
160
+ <div *ngIf="paginatedRoles().length === 0" class="p-8 text-center text-sm text-slate-500 dark:text-slate-400">
161
+ No matching custom roles found.
162
+ </div>
163
+ </div>
164
+
165
+ <div class="flex h-14 flex-0 shrink-0 items-center border-t border-neutral-200 bg-white px-4 dark:border-slate-800 dark:bg-slate-900">
166
+ <mat-paginator
167
+ class="w-full bg-transparent [&_.mat-mdc-paginator-container]:justify-between [&_.mat-mdc-paginator-page-size]:hidden"
168
+ [length]="filteredRoles().length"
169
+ [pageSize]="rolesPageSize"
170
+ [pageIndex]="rolesPage()"
171
+ [hidePageSize]="true"
172
+ [showFirstLastButtons]="false"
173
+ (page)="onRolesPageChange($event)"
174
+ >
175
+ </mat-paginator>
176
+ </div>
177
+ </div>
178
+
179
+ <div
180
+ class="flex min-w-0 flex-auto flex-col overflow-y-auto bg-slate-50 dark:bg-slate-950"
181
+ [class.hidden]="isMobile() && !showMobileDetails() && !showCreateForm()"
182
+ >
183
+ <div
184
+ *ngIf="isMobile() && (showMobileDetails() || showCreateForm())"
185
+ class="z-10 flex items-center justify-between border-b bg-white p-4 shadow-sm dark:bg-slate-900"
186
+ >
187
+ <button
188
+ id="qa-role-management__go-back-to-list"
189
+ data-qa-id="qa-role-management__go-back-to-list"
190
+ data-component-id="go-back-to-list"
191
+ data-screen-id="role-management"
192
+ *ngIf="SCREEN | accessCtrl: 'go-back-to-list'"
193
+ type="button"
194
+ (click)="goBackToList()"
195
+ class="inline-flex items-center text-sm font-semibold text-primary-600 hover:text-primary-700 focus:outline-none"
196
+ >
197
+ <mat-icon svgIcon="arrow-left" class="mr-1 size-5"></mat-icon>
198
+ <span>Back to Roles</span>
199
+ </button>
200
+ </div>
201
+
202
+ <div *ngIf="showCreateForm()" class="mx-auto w-full max-w-2xl p-6 md:p-10">
203
+ <div class="flex flex-col rounded-2xl border bg-white p-6 shadow-sm dark:bg-slate-900">
204
+ <div class="mb-2 flex items-center gap-2 text-xl font-extrabold text-slate-900 dark:text-white">
205
+ <mat-icon svgIcon="plus" class="size-5 text-primary-600"></mat-icon>
206
+ <span>New Identity Role</span>
207
+ </div>
208
+ <p class="mb-6 text-sm text-slate-500 dark:text-slate-400">
209
+ Create a standalone role with its own permissions. Configure screens and components in the Permission Matrix after creation.
210
+ </p>
211
+
212
+ <form [formGroup]="roleForm" (ngSubmit)="onSubmit()" class="flex flex-col gap-y-5">
213
+ <div>
214
+ <label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-700 dark:text-slate-300">
215
+ Role Name
216
+ </label>
217
+ <input
218
+ type="text"
219
+ formControlName="name"
220
+ placeholder="e.g. CAA_STAFF"
221
+ class="w-full rounded-xl border bg-white px-4 py-2.5 text-sm uppercase shadow-sm outline-none transition focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-slate-700 dark:bg-slate-800"
222
+ />
223
+ @if (roleForm.get('name')?.invalid && roleForm.get('name')?.touched) {
224
+ <div class="mt-1 text-xs text-red-500">
225
+ @if (roleForm.get('name')?.hasError('required')) {
226
+ Role name is required.
227
+ } @else {
228
+ Use only letters, numbers, and underscores (e.g. CAA_STAFF).
229
+ }
230
+ </div>
231
+ }
232
+ </div>
233
+
234
+ <div>
235
+ <label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-700 dark:text-slate-300">
236
+ Description
237
+ </label>
238
+ <textarea
239
+ formControlName="description"
240
+ rows="3"
241
+ placeholder="Define the role purpose..."
242
+ class="w-full rounded-xl border bg-white px-4 py-2.5 text-sm shadow-sm outline-none transition focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-slate-700 dark:bg-slate-800"
243
+ ></textarea>
244
+ @if (roleForm.get('description')?.invalid && roleForm.get('description')?.touched) {
245
+ <div class="mt-1 text-xs text-red-500">
246
+ Description is required.
247
+ </div>
248
+ }
249
+ </div>
250
+
251
+ <div class="mt-4 flex items-center justify-end gap-x-3 border-t pt-4">
252
+ <button
253
+ id="qa-role-management__toggle-create-form-2"
254
+ data-qa-id="qa-role-management__toggle-create-form-2"
255
+ data-component-id="toggle-create-form-2"
256
+ data-screen-id="role-management"
257
+ *ngIf="SCREEN | accessCtrl: 'toggle-create-form-2'"
258
+ type="button"
259
+ (click)="toggleCreateForm()"
260
+ class="rounded-xl px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
261
+ >
262
+ Cancel
263
+ </button>
264
+ <button
265
+ id="qa-role-management__submit"
266
+ data-qa-id="qa-role-management__submit"
267
+ data-component-id="submit"
268
+ data-screen-id="role-management"
269
+ *ngIf="SCREEN | accessCtrl: 'submit'"
270
+ type="submit"
271
+ [disabled]="roleForm.invalid || isSubmitting()"
272
+ class="rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-primary-700 disabled:opacity-50"
273
+ >
274
+ {{ isSubmitting() ? 'Saving...' : 'Create' }}
275
+ </button>
276
+ </div>
277
+ </form>
278
+ </div>
279
+ </div>
280
+
281
+ <div *ngIf="selectedRole() && !showCreateForm()" class="details-container w-full max-w-full space-y-6 p-6 md:p-8">
282
+ <app-role-header
283
+ [selectedRole]="selectedRole()"
284
+ [showEditPermissions]="<%= iam.rolePermissions !== false %>"
285
+ (editPermissions)="editPermissions($event)"
286
+ (duplicate)="duplicateRole($event)"
287
+ >
288
+ </app-role-header>
289
+
290
+ <div class="bento-grid mt-10 gap-6">
291
+ <div class="bento-col-main space-y-6">
292
+ <app-role-users
293
+ [searchQuery]="selectedRoleUsersSearchQuery()"
294
+ [filteredUsersCount]="filteredSelectedRoleUsers().length"
295
+ [paginatedUsers]="paginatedSelectedRoleUsers()"
296
+ [currentPage]="selectedRoleUsersPage()"
297
+ [totalPages]="totalSelectedRoleUsersPages()"
298
+ (searchQueryChange)="selectedRoleUsersSearchQuery.set($event); selectedRoleUsersPage.set(0)"
299
+ (pageChange)="selectedRoleUsersPage.set($event)"
300
+ (openAssignUser)="openAssignUserDialog()"
301
+ (unassignUser)="unassignUser($event)"
302
+ >
303
+ </app-role-users>
304
+ </div>
305
+
306
+ <div class="bento-col-side gap-6">
307
+ <% if (iam.inheritance !== false) { %>
308
+ <app-role-inheritance [selectedRole]="selectedRole()"></app-role-inheritance>
309
+ <% } %>
310
+ <% if (iam.rolePermissions !== false) { %>
311
+ <app-role-permissions-panel
312
+ [selectedRolePermissions]="selectedRolePermissions()"
313
+ [roleName]="selectedRole()?.name || ''"
314
+ (configureMatrix)="editPermissions(selectedRole()!)"
315
+ >
316
+ </app-role-permissions-panel>
317
+ <% } %>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <div *ngIf="!selectedRole() && !showCreateForm()" class="flex flex-auto flex-col items-center justify-center p-6 text-center">
323
+ <mat-icon svgIcon="shield" class="mb-4 size-16 text-slate-300 dark:text-slate-700"></mat-icon>
324
+ <div class="text-lg font-bold text-slate-700 dark:text-slate-300">Select a Role</div>
325
+ <div class="mt-1 max-w-xs text-sm text-slate-500">
326
+ Choose a role from the left list to review its inheritance details, screen permissions, and active users.
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </div>