open-loam 0.1.1 → 0.3.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 (287) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +148 -8
  3. data/LICENSE +1 -1
  4. data/README.md +27 -25
  5. data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
  6. data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
  7. data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
  8. data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
  9. data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
  10. data/app/models/open_loam/api_token.rb +63 -0
  11. data/app/models/{loam → open_loam}/audit_record.rb +3 -3
  12. data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
  13. data/app/models/{loam → open_loam}/business_rule.rb +10 -10
  14. data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
  15. data/app/models/{loam → open_loam}/comment.rb +9 -9
  16. data/app/models/{loam → open_loam}/config.rb +7 -7
  17. data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
  18. data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
  19. data/app/models/open_loam/dictionary.rb +20 -0
  20. data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
  21. data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
  22. data/app/models/open_loam/event_record.rb +42 -0
  23. data/app/models/{loam → open_loam}/field_definition.rb +8 -8
  24. data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
  25. data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
  26. data/app/models/{loam → open_loam}/membership.rb +6 -6
  27. data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
  28. data/app/models/{loam → open_loam}/notification.rb +10 -10
  29. data/app/models/{loam → open_loam}/pending_action.rb +70 -26
  30. data/app/models/{loam → open_loam}/perspective.rb +5 -5
  31. data/app/models/{loam → open_loam}/progress_job.rb +7 -7
  32. data/app/models/{loam → open_loam}/record_lock.rb +5 -5
  33. data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
  34. data/app/models/open_loam/search_token.rb +9 -0
  35. data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
  36. data/app/models/open_loam/sso_provider.rb +66 -0
  37. data/app/models/open_loam/tenant.rb +20 -0
  38. data/app/models/{loam → open_loam}/translation.rb +5 -5
  39. data/app/models/open_loam/webhook_endpoint.rb +39 -0
  40. data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
  41. data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
  42. data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
  43. data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
  44. data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
  45. data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
  46. data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
  47. data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
  48. data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
  49. data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
  50. data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
  51. data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
  52. data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
  53. data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
  54. data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
  55. data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
  56. data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
  57. data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
  58. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
  59. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
  60. data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
  61. data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
  62. data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
  63. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
  64. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
  65. data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
  66. data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
  67. data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
  68. data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
  69. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
  70. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
  71. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
  72. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
  73. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
  74. data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
  75. data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
  76. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
  77. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
  78. data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
  79. data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
  80. data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
  81. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
  82. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
  83. data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
  84. data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
  85. data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
  86. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
  87. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
  88. data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
  89. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
  90. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
  91. data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
  92. data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
  93. data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
  94. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
  95. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
  96. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
  97. data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
  98. data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
  99. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
  100. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
  101. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
  102. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
  103. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
  104. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
  105. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
  106. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
  107. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
  108. data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
  109. data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
  110. data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
  111. data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
  112. data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
  113. data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
  114. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
  115. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
  116. data/lib/generators/{loam/install/templates/migrations/create_loam_auth_attempts.rb → open_loam/install/templates/migrations/create_open_loam_auth_attempts.rb} +3 -3
  117. data/lib/generators/{loam/install/templates/migrations/create_loam_business_rules.rb → open_loam/install/templates/migrations/create_open_loam_business_rules.rb} +10 -10
  118. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
  119. data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
  120. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
  121. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
  122. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
  123. data/lib/generators/{loam/install/templates/migrations/create_loam_dictionary_entries.rb → open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb} +6 -6
  124. data/lib/generators/{loam/install/templates/migrations/create_loam_event_deliveries.rb → open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb} +5 -5
  125. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
  126. data/lib/generators/{loam/install/templates/migrations/create_loam_field_definitions.rb → open_loam/install/templates/migrations/create_open_loam_field_definitions.rb} +4 -4
  127. data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -10
  128. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
  129. data/lib/generators/{loam/install/templates/migrations/create_loam_mfa_credentials.rb → open_loam/install/templates/migrations/create_open_loam_mfa_credentials.rb} +4 -4
  130. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
  131. data/lib/generators/{loam/install/templates/migrations/create_loam_pending_actions.rb → open_loam/install/templates/migrations/create_open_loam_pending_actions.rb} +9 -9
  132. data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
  133. data/lib/generators/{loam/install/templates/migrations/create_loam_progress_jobs.rb → open_loam/install/templates/migrations/create_open_loam_progress_jobs.rb} +5 -5
  134. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
  135. data/lib/generators/{loam/install/templates/migrations/create_loam_scheduled_jobs.rb → open_loam/install/templates/migrations/create_open_loam_scheduled_jobs.rb} +5 -5
  136. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
  137. data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +15 -12
  138. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
  139. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
  140. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
  141. data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
  142. data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
  143. data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
  144. data/lib/open-loam.rb +2 -2
  145. data/lib/{loam → open_loam}/attachable.rb +4 -4
  146. data/lib/{loam → open_loam}/auditable.rb +23 -23
  147. data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
  148. data/lib/{loam → open_loam}/base32.rb +1 -1
  149. data/lib/{loam → open_loam}/bulk.rb +7 -7
  150. data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
  151. data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
  152. data/lib/{loam → open_loam}/business_rules.rb +19 -19
  153. data/lib/{loam → open_loam}/commentable.rb +6 -6
  154. data/lib/{loam → open_loam}/configs.rb +19 -19
  155. data/lib/{loam → open_loam}/cron.rb +2 -2
  156. data/lib/{loam → open_loam}/csv.rb +2 -2
  157. data/lib/{loam → open_loam}/current.rb +5 -5
  158. data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
  159. data/lib/{loam → open_loam}/custom_fields.rb +17 -17
  160. data/lib/{loam → open_loam}/dashboard.rb +5 -5
  161. data/lib/{loam → open_loam}/dictionaries.rb +12 -12
  162. data/lib/{loam → open_loam}/durable_events.rb +19 -19
  163. data/lib/{loam → open_loam}/encryptable.rb +39 -34
  164. data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
  165. data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
  166. data/lib/{loam → open_loam}/encryption.rb +58 -15
  167. data/lib/open_loam/engine.rb +65 -0
  168. data/lib/{loam → open_loam}/enrichers.rb +5 -5
  169. data/lib/{loam → open_loam}/errors.rb +20 -10
  170. data/lib/{loam → open_loam}/eval.rb +2 -2
  171. data/lib/open_loam/event_log.rb +77 -0
  172. data/lib/{loam → open_loam}/event_stream.rb +14 -14
  173. data/lib/{loam → open_loam}/eventful.rb +12 -12
  174. data/lib/{loam → open_loam}/events.rb +5 -5
  175. data/lib/{loam → open_loam}/export.rb +12 -12
  176. data/lib/{loam → open_loam}/features.rb +15 -15
  177. data/lib/{loam → open_loam}/generated_key.rb +6 -6
  178. data/lib/{loam → open_loam}/import.rb +12 -15
  179. data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
  180. data/lib/{loam → open_loam}/lifecycle.rb +54 -25
  181. data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
  182. data/lib/{loam → open_loam}/mcp/server.rb +9 -9
  183. data/lib/{loam → open_loam}/mcp.rb +31 -22
  184. data/lib/{loam → open_loam}/notifications.rb +5 -5
  185. data/lib/{loam → open_loam}/open_api.rb +16 -16
  186. data/lib/open_loam/outbound_url.rb +100 -0
  187. data/lib/{loam → open_loam}/overrides.rb +10 -10
  188. data/lib/{loam → open_loam}/pending_actions.rb +9 -9
  189. data/lib/{loam → open_loam}/permissions.rb +8 -8
  190. data/lib/{loam → open_loam}/perspectives.rb +9 -9
  191. data/lib/{loam → open_loam}/policy.rb +21 -8
  192. data/lib/{loam → open_loam}/progress.rb +6 -6
  193. data/lib/{loam → open_loam}/record_locks.rb +9 -9
  194. data/lib/{loam → open_loam}/scheduler.rb +20 -20
  195. data/lib/{loam → open_loam}/search/driver.rb +3 -3
  196. data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
  197. data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
  198. data/lib/{loam → open_loam}/search.rb +5 -5
  199. data/lib/{loam → open_loam}/searchable.rb +22 -22
  200. data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
  201. data/lib/{loam → open_loam}/sso/claims.rb +1 -1
  202. data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
  203. data/lib/open_loam/sso/http_client.rb +51 -0
  204. data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
  205. data/lib/{loam → open_loam}/sso.rb +39 -18
  206. data/lib/{loam → open_loam}/telemetry.rb +6 -6
  207. data/lib/{loam → open_loam}/tenant_record.rb +10 -10
  208. data/lib/{loam → open_loam}/test_helpers.rb +4 -4
  209. data/lib/{loam → open_loam}/totp.rb +3 -3
  210. data/lib/{loam → open_loam}/translatable.rb +24 -24
  211. data/lib/{loam → open_loam}/undo.rb +11 -11
  212. data/lib/open_loam/version.rb +3 -0
  213. data/lib/{loam → open_loam}/webhooks.rb +4 -4
  214. data/lib/{loam → open_loam}/widgets.rb +9 -9
  215. data/lib/{loam → open_loam}/workflow.rb +45 -45
  216. data/lib/open_loam.rb +124 -0
  217. data/lib/tasks/open_loam.rake +203 -0
  218. data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
  219. data/lib/tasks/open_loam_mcp.rake +20 -0
  220. metadata +247 -242
  221. data/app/models/loam/api_token.rb +0 -40
  222. data/app/models/loam/dictionary.rb +0 -20
  223. data/app/models/loam/search_token.rb +0 -9
  224. data/app/models/loam/sso_provider.rb +0 -35
  225. data/app/models/loam/tenant.rb +0 -20
  226. data/app/models/loam/webhook_endpoint.rb +0 -26
  227. data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
  228. data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
  229. data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
  230. data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
  231. data/lib/generators/loam/install/templates/AGENTS.md +0 -341
  232. data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
  233. data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
  234. data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
  235. data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
  236. data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
  237. data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
  238. data/lib/generators/loam/install/templates/import_job.rb +0 -25
  239. data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
  240. data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
  241. data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
  242. data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
  243. data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
  244. data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
  245. data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
  246. data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
  247. data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
  248. data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
  249. data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
  250. data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
  251. data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
  252. data/lib/loam/engine.rb +0 -57
  253. data/lib/loam/sso/http_client.rb +0 -42
  254. data/lib/loam/version.rb +0 -3
  255. data/lib/loam.rb +0 -122
  256. data/lib/tasks/loam.rake +0 -171
  257. data/lib/tasks/loam_mcp.rake +0 -20
  258. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
  259. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
  260. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
  261. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
  262. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
  263. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
  264. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
  265. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
  266. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
  267. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
  268. /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
  269. /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
  270. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
  271. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
  272. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
  273. /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
  274. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
  275. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
  276. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
  277. /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
  278. /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
  279. /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
  280. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
  281. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
  282. /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
  283. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
  284. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
  285. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
  286. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
  287. /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
@@ -15,34 +15,39 @@ module Admin
15
15
  end
16
16
 
17
17
  def index
18
- @perspective = Loam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
19
- @perspectives = Loam::Perspectives.visible_to("<%= class_name %>", user: current_actor)
18
+ authorize!(policy_for(<%= class_name %>.new), :read?)
19
+ @perspective = OpenLoam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
20
+ @perspectives = OpenLoam::Perspectives.visible_to("<%= class_name %>", user: current_actor)
20
21
  @records, @page, @has_next = paginate(index_scope)
21
- @index_partial = params[:cf_field].present? && Loam::CustomFieldIndex.partial? # incomplete-index warning (L-919)
22
+ @index_partial = params[:cf_field].present? && OpenLoam::CustomFieldIndex.partial? # incomplete-index warning (L-919)
22
23
  end
23
24
 
24
25
  # CSV of the CURRENT filtered/perspective view — manager-only, policy- and
25
- # encryption-aware (Loam::Export).
26
+ # encryption-aware (OpenLoam::Export).
26
27
  def export
27
28
  require_role!(:manager)
28
- send_data Loam::Export.csv(index_scope, actor: current_actor),
29
+ send_data OpenLoam::Export.csv(index_scope, actor: current_actor),
29
30
  filename: "<%= plural_name %>-#{Date.current}.csv", type: "text/csv"
30
31
  end
31
32
 
32
33
  # Datatable bulk actions on the selected ids — each is policy-checked per
33
- # record and tenant-scoped (Loam::Bulk). Zero selection is a no-op.
34
+ # record and tenant-scoped (OpenLoam::Bulk). Zero selection is a no-op.
34
35
  def bulk
36
+ # OpenLoam::Bulk checks the policy PER record (destroy? for soft_delete,
37
+ # update? + writable? for set_field) and silently skips what the actor may
38
+ # not touch, so the authorization is real — it just lives there, not here.
39
+ authorized!
35
40
  ids = Array(params[:ids])
36
41
  case params[:bulk_action]
37
42
  when "soft_delete"
38
- count = Loam::Bulk.soft_delete(<%= class_name %>, ids)
43
+ count = OpenLoam::Bulk.soft_delete(<%= class_name %>, ids)
39
44
  redirect_to polymorphic_path([:admin, <%= class_name %>]), notice: "Deleted #{count} record(s)."
40
45
  when "set_field"
41
- count = Loam::Bulk.set_field(<%= class_name %>, ids, field: params[:field], value: params[:value])
46
+ count = OpenLoam::Bulk.set_field(<%= class_name %>, ids, field: params[:field], value: params[:value])
42
47
  redirect_to polymorphic_path([:admin, <%= class_name %>]), notice: "Updated #{count} record(s)."
43
48
  when "export"
44
49
  require_role!(:manager) # same gate as the dedicated export action
45
- send_data Loam::Export.csv(Loam::Bulk.selected(<%= class_name %>, ids), actor: current_actor),
50
+ send_data OpenLoam::Export.csv(OpenLoam::Bulk.selected(<%= class_name %>, ids), actor: current_actor),
46
51
  filename: "<%= plural_name %>-selected.csv", type: "text/csv"
47
52
  else
48
53
  redirect_to polymorphic_path([:admin, <%= class_name %>]), alert: "Unknown bulk action."
@@ -75,7 +80,7 @@ module Admin
75
80
  attach_files!(@record, policy)
76
81
 
77
82
  if @record.save
78
- redirect_to [:admin, @record], notice: t("loam.flash.created", name: <%= class_name %>.model_name.human)
83
+ redirect_to [:admin, @record], notice: t("open_loam.flash.created", name: <%= class_name %>.model_name.human)
79
84
  else
80
85
  render :new, status: :unprocessable_entity
81
86
  end
@@ -84,7 +89,7 @@ module Admin
84
89
  def edit
85
90
  authorize!(policy_for(@record), :update?)
86
91
  # Take the advisory lock (courtesy) or learn who holds it, for the banner.
87
- @lock = Loam::RecordLocks.acquire(@record, by: current_actor) || Loam::RecordLocks.active_lock(@record)
92
+ @lock = OpenLoam::RecordLocks.acquire(@record, by: current_actor) || OpenLoam::RecordLocks.active_lock(@record)
88
93
  end
89
94
 
90
95
  def update
@@ -94,15 +99,15 @@ module Admin
94
99
  attach_files!(@record, policy)
95
100
 
96
101
  if @record.update(permitted_params(policy))
97
- Loam::RecordLocks.release(@record, by: current_actor)
98
- redirect_to [:admin, @record], notice: t("loam.flash.updated", name: <%= class_name %>.model_name.human)
102
+ OpenLoam::RecordLocks.release(@record, by: current_actor)
103
+ redirect_to [:admin, @record], notice: t("open_loam.flash.updated", name: <%= class_name %>.model_name.human)
99
104
  else
100
105
  render :edit, status: :unprocessable_entity
101
106
  end
102
107
  rescue ActiveRecord::StaleObjectError
103
108
  # Someone saved between open and submit — show the diff, reload fresh, retry.
104
109
  stale_conflict!(@record, FIELDS)
105
- @lock = Loam::RecordLocks.acquire(@record, by: current_actor) || Loam::RecordLocks.active_lock(@record)
110
+ @lock = OpenLoam::RecordLocks.acquire(@record, by: current_actor) || OpenLoam::RecordLocks.active_lock(@record)
106
111
  render :edit, status: :conflict
107
112
  end
108
113
 
@@ -111,8 +116,8 @@ module Admin
111
116
  def destroy
112
117
  authorize!(policy_for(@record), :destroy?)
113
118
  @record.soft_delete!
114
- Loam::RecordLocks.release(@record, by: current_actor)
115
- redirect_to [:admin, <%= class_name %>], notice: t("loam.flash.destroyed", name: <%= class_name %>.model_name.human)
119
+ OpenLoam::RecordLocks.release(@record, by: current_actor)
120
+ redirect_to [:admin, <%= class_name %>], notice: t("open_loam.flash.destroyed", name: <%= class_name %>.model_name.human)
116
121
  end
117
122
 
118
123
  # Restore looks through the deleted rows — the default scope hides them, so a
@@ -121,7 +126,7 @@ module Admin
121
126
  @record = <%= class_name %>.with_deleted.find(params[:id])
122
127
  authorize!(policy_for(@record), :update?)
123
128
  @record.restore!
124
- redirect_to [:deleted, :admin, <%= class_name %>], notice: t("loam.flash.restored", name: <%= class_name %>.model_name.human)
129
+ redirect_to [:deleted, :admin, <%= class_name %>], notice: t("open_loam.flash.restored", name: <%= class_name %>.model_name.human)
125
130
  end
126
131
 
127
132
  private
@@ -134,7 +139,7 @@ module Admin
134
139
  # export so the CSV matches what the manager is looking at.
135
140
  def index_scope
136
141
  scope = <%= class_name %>.all
137
- perspective = Loam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
142
+ perspective = OpenLoam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
138
143
  scope = perspective.apply(scope) if perspective
139
144
  <% if searchable_attributes.any? -%>
140
145
  scope = scope.search(params[:q])
@@ -147,9 +152,14 @@ module Admin
147
152
  # against real columns (never interpolated from params — that would be SQL
148
153
  # injection), and the direction is constrained to asc/desc. With no valid
149
154
  # sort param, a perspective's own order is kept, else a stable default.
155
+ #
156
+ # It must also be a column the role may READ: ordering by a hidden field
157
+ # leaks its ordering, which is an inference oracle on a value the role
158
+ # cannot see. Same reasoning as OpenLoam::FieldAccessError on custom fields —
159
+ # ignored rather than raised, since the header is not rendered either.
150
160
  def apply_sort(scope)
151
161
  column = params[:sort].to_s
152
- if <%= class_name %>.column_names.include?(column)
162
+ if <%= class_name %>.column_names.include?(column) && policy_for(<%= class_name %>.new).readable?(column)
153
163
  dir = params[:dir].to_s.casecmp("asc").zero? ? :asc : :desc
154
164
  scope.reorder(column => dir).order(id: dir)
155
165
  elsif scope.order_values.empty?
@@ -160,13 +170,13 @@ module Admin
160
170
  end
161
171
 
162
172
  # A custom-field filter routed through the read-model index
163
- # (Loam::CustomFieldIndex) — index-backed, not a JSON scan. An unknown field
173
+ # (OpenLoam::CustomFieldIndex) — index-backed, not a JSON scan. An unknown field
164
174
  # is ignored rather than raising.
165
175
  def apply_custom_field_filter(scope)
166
176
  return scope if params[:cf_field].blank?
167
177
 
168
- scope.merge(Loam::CustomFieldIndex.filter(<%= class_name %>, params[:cf_field], params[:cf_op].presence || "eq", params[:cf_value]))
169
- rescue Loam::Error
178
+ scope.merge(OpenLoam::CustomFieldIndex.filter(<%= class_name %>, params[:cf_field], params[:cf_op].presence || "eq", params[:cf_value]))
179
+ rescue OpenLoam::Error
170
180
  scope
171
181
  end
172
182
 
@@ -1,19 +1,19 @@
1
1
  require "test_helper"
2
2
 
3
- # Generated guardrail tests for <%= class_name %>. They prove the Loam
3
+ # Generated guardrail tests for <%= class_name %>. They prove the OpenLoam
4
4
  # invariants hold for THIS entity: tenant isolation, loud failure without
5
5
  # context, audit-by-default, lifecycle events, membership-gated policies.
6
6
  # Extend freely; never delete.
7
- class <%= class_name %>LoamTest < ActiveSupport::TestCase
7
+ class <%= class_name %>OpenLoamTest < ActiveSupport::TestCase
8
8
  setup do
9
- @tenant_a = Loam::Tenant.create!(name: "Tenant A", slug: "a-<%= file_name %>")
10
- @tenant_b = Loam::Tenant.create!(name: "Tenant B", slug: "b-<%= file_name %>")
9
+ @tenant_a = OpenLoam::Tenant.create!(name: "Tenant A", slug: "a-<%= file_name %>")
10
+ @tenant_b = OpenLoam::Tenant.create!(name: "Tenant B", slug: "b-<%= file_name %>")
11
11
  @manager = User.create!(name: "Manager", email: "manager@example.test", password: "password")
12
12
  @employee = User.create!(name: "Employee", email: "employee@example.test", password: "password")
13
13
 
14
14
  with_tenant(@tenant_a) do
15
- Loam::Membership.create!(user: @manager, role: "manager")
16
- Loam::Membership.create!(user: @employee, role: "employee")
15
+ OpenLoam::Membership.create!(user: @manager, role: "manager")
16
+ OpenLoam::Membership.create!(user: @employee, role: "employee")
17
17
  end
18
18
  end
19
19
 
@@ -29,15 +29,15 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
29
29
  end
30
30
 
31
31
  test "guardrail: touching the model with no tenant context raises" do
32
- assert_raises(Loam::MissingTenantError) { <%= class_name %>.count }
33
- assert_raises(Loam::MissingTenantError) { <%= class_name %>.new }
32
+ assert_raises(OpenLoam::MissingTenantError) { <%= class_name %>.count }
33
+ assert_raises(OpenLoam::MissingTenantError) { <%= class_name %>.new }
34
34
  end
35
35
 
36
36
  test "guardrail: a record cannot be written into a foreign tenant" do
37
37
  record = with_tenant(@tenant_a) { <%= class_name %>.create!(<%= sample_attributes %>) }
38
38
 
39
39
  with_tenant(@tenant_b) do
40
- assert_raises(Loam::MissingTenantError) { record.update!(<%= sample_attributes(1) %>) }
40
+ assert_raises(OpenLoam::MissingTenantError) { record.update!(<%= sample_attributes(1) %>) }
41
41
  end
42
42
  end
43
43
 
@@ -45,7 +45,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
45
45
  with_tenant(@tenant_a, actor: @manager) do
46
46
  record = <%= class_name %>.create!(<%= sample_attributes %>)
47
47
 
48
- audit = Loam::AuditRecord.find_by(
48
+ audit = OpenLoam::AuditRecord.find_by(
49
49
  auditable_type: "<%= class_name %>", auditable_id: record.id, action: "create"
50
50
  )
51
51
  assert audit, "expected an audit record for the create"
@@ -56,7 +56,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
56
56
 
57
57
  test "lifecycle events are published with the tenant stamped" do
58
58
  received = []
59
- subscription = Loam::Events.subscribe("<%= domain %>.<%= singular_name %>.created") do |_name, payload|
59
+ subscription = OpenLoam::Events.subscribe("<%= domain %>.<%= singular_name %>.created") do |_name, payload|
60
60
  received << payload
61
61
  end
62
62
 
@@ -78,7 +78,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
78
78
  with_tenant(@tenant_a) do
79
79
  assert_equal 0, <%= class_name %>.count, "a soft-deleted record is gone from ordinary queries"
80
80
  assert_equal 1, <%= class_name %>.with_deleted.count, "but the row still exists"
81
- assert Loam::AuditRecord.exists?(auditable_type: "<%= class_name %>", auditable_id: record_id, action: "soft_delete")
81
+ assert OpenLoam::AuditRecord.exists?(auditable_type: "<%= class_name %>", auditable_id: record_id, action: "soft_delete")
82
82
  end
83
83
 
84
84
  # with_deleted lifts the deleted_at filter but NOT the tenant filter.
@@ -1,10 +1,10 @@
1
1
  class Create<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::VERSION::STRING.to_f %>]
2
2
  def change
3
- create_table :<%= table_name %><%= loam_id_option %> do |t|
4
- t.references :tenant, null: false, foreign_key: { to_table: :loam_tenants }<%= loam_type_option %>
3
+ create_table :<%= table_name %><%= open_loam_id_option %> do |t|
4
+ t.references :tenant, null: false, foreign_key: { to_table: :open_loam_tenants }<%= open_loam_type_option %>
5
5
  <% attributes.each do |attribute| -%>
6
6
  <% if encrypted?(attribute) -%>
7
- t.text :<%= attribute.name %> # encrypted at rest (Loam::Encryptable) — text, since ciphertext outgrows varchar
7
+ t.text :<%= attribute.name %> # encrypted at rest (OpenLoam::Encryptable) — text, since ciphertext outgrows varchar
8
8
  <% else -%>
9
9
  t.<%= attribute.type %> :<%= attribute.name %>
10
10
  <% end -%>
@@ -14,10 +14,10 @@ class Create<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecor
14
14
  <% end -%>
15
15
  t.json :custom_fields, null: false, default: {}
16
16
  t.datetime :deleted_at
17
- t.integer :lock_version, null: false, default: 0 # optimistic locking (Loam::RecordLock)
17
+ t.integer :lock_version, null: false, default: 0 # optimistic locking (OpenLoam::RecordLock)
18
18
  t.timestamps
19
19
  end
20
- # Loam::SoftDeletable filters `deleted_at IS NULL` on every query.
20
+ # OpenLoam::SoftDeletable filters `deleted_at IS NULL` on every query.
21
21
  add_index :<%= table_name %>, :deleted_at
22
22
  <% encrypt_searchable_names.each do |field| -%>
23
23
  add_index :<%= table_name %>, :<%= field %>_hash
@@ -1,14 +1,14 @@
1
1
  # Tenant-scoped, audited, evented — by inheritance, not by remembering.
2
- class <%= class_name %> < Loam::TenantRecord
3
- include Loam::Auditable
4
- include Loam::Eventful
5
- include Loam::CustomFields
6
- include Loam::Commentable
7
- include Loam::Attachable
8
- include Loam::Searchable
9
- include Loam::SoftDeletable
2
+ class <%= class_name %> < OpenLoam::TenantRecord
3
+ include OpenLoam::Auditable
4
+ include OpenLoam::Eventful
5
+ include OpenLoam::CustomFields
6
+ include OpenLoam::Commentable
7
+ include OpenLoam::Attachable
8
+ include OpenLoam::Searchable
9
+ include OpenLoam::SoftDeletable
10
10
  <% if encrypted_field_names.any? -%>
11
- include Loam::Encryptable
11
+ include OpenLoam::Encryptable
12
12
  <% end -%>
13
13
 
14
14
  event_domain :<%= domain %>
@@ -23,5 +23,5 @@ class <%= class_name %> < Loam::TenantRecord
23
23
  <% end -%>
24
24
 
25
25
  # Business logic goes here. Publish business events explicitly:
26
- # Loam::Events.publish("<%= domain %>.something.happened", id: id)
26
+ # OpenLoam::Events.publish("<%= domain %>.something.happened", id: id)
27
27
  end
@@ -1,4 +1,4 @@
1
- class <%= class_name %>Policy < Loam::Policy
1
+ class <%= class_name %>Policy < OpenLoam::Policy
2
2
  # Action defaults: any member of the current tenant may read/create/update/
3
3
  # destroy. Tighten by overriding, e.g.:
4
4
  #
@@ -1,4 +1,4 @@
1
- <%% policy = Loam::Policy.for(@record) %>
1
+ <%% policy = OpenLoam::Policy.for(@record) %>
2
2
 
3
3
  <%%# Optimistic-locking conflict: what the user typed vs what is now saved. %>
4
4
  <%% if @conflict.present? %>
@@ -36,25 +36,28 @@
36
36
  </ul>
37
37
  <%% end %>
38
38
 
39
+ <%%# Three states per field: writable (input), readable-only (shown, labelled
40
+ read-only), or neither (absent). There is no branch for the third case on
41
+ purpose — a field the role may not read is not rendered at all. %>
39
42
  <% attributes.each do |attribute| -%>
40
43
  <%% if policy.writable?(:<%= attribute.name %>) %>
41
44
  <p>
42
45
  <%%= f.label :<%= attribute.name %> %><br>
43
46
  <%%= f.<%= form_field_helper(attribute) %> :<%= attribute.name %> %>
44
47
  </p>
45
- <%% else %>
48
+ <%% elsif policy.readable?(:<%= attribute.name %>) %>
46
49
  <p>
47
50
  <%%= f.label :<%= attribute.name %> %>: <%%= @record.<%= attribute.name %> %>
48
- <em><%%= t("loam.form.read_only") %></em>
51
+ <em><%%= t("open_loam.form.read_only") %></em>
49
52
  </p>
50
53
  <%% end %>
51
54
  <% end -%>
52
55
 
53
- <%%= render "loam/custom_fields/fields", record: @record, policy: policy, mode: :edit %>
56
+ <%%= render "open_loam/custom_fields/fields", record: @record, policy: policy, mode: :edit %>
54
57
 
55
58
  <%% if policy.update? %>
56
59
  <p>
57
- <%%= f.label :files, t("loam.form.attach_files") %><br>
60
+ <%%= f.label :files, t("open_loam.form.attach_files") %><br>
58
61
  <%%= f.file_field :files, multiple: true %>
59
62
  </p>
60
63
  <%% end %>
@@ -1,13 +1,13 @@
1
- <h1><%%= t("loam.actions.deleted_title", name: <%= class_name %>.model_name.human(count: 2)) %></h1>
1
+ <h1><%%= t("open_loam.actions.deleted_title", name: <%= class_name %>.model_name.human(count: 2)) %></h1>
2
2
 
3
- <p><%%= link_to t("loam.actions.back"), [:admin, <%= class_name %>] %></p>
3
+ <p><%%= link_to t("open_loam.actions.back"), [:admin, <%= class_name %>] %></p>
4
4
 
5
5
  <%%# No Show/Edit here: these rows are hidden by the default scope, so a plain
6
6
  find would 404. The one action a deleted record offers is Restore. %>
7
7
  <table>
8
8
  <thead>
9
9
  <tr>
10
- <th><%%= t("loam.labels.id") %></th>
10
+ <th><%%= t("open_loam.labels.id") %></th>
11
11
  <% attributes.each do |attribute| -%>
12
12
  <th><%%= <%= class_name %>.human_attribute_name("<%= attribute.name %>") %></th>
13
13
  <% end -%>
@@ -23,7 +23,7 @@
23
23
  <td><%%= record.<%= attribute.name %> %></td>
24
24
  <% end -%>
25
25
  <td><%%= record.deleted_at %></td>
26
- <td><%%= button_to t("loam.actions.restore"), [:restore, :admin, record], method: :patch %></td>
26
+ <td><%%= button_to t("open_loam.actions.restore"), [:restore, :admin, record], method: :patch %></td>
27
27
  </tr>
28
28
  <%% end %>
29
29
  </tbody>
@@ -32,10 +32,10 @@
32
32
  <%%# Page links carry nothing but the page: the recycle bin has no filter box. %>
33
33
  <p>
34
34
  <%% if @page > 1 %>
35
- <%%= link_to t("loam.pagination.previous"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page - 1) %>
35
+ <%%= link_to t("open_loam.pagination.previous"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page - 1) %>
36
36
  <%% end %>
37
- <%%= t("loam.pagination.page", page: @page) %>
37
+ <%%= t("open_loam.pagination.page", page: @page) %>
38
38
  <%% if @has_next %>
39
- <%%= link_to t("loam.pagination.next"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page + 1) %>
39
+ <%%= link_to t("open_loam.pagination.next"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page + 1) %>
40
40
  <%% end %>
41
41
  </p>
@@ -0,0 +1,3 @@
1
+ <h1><%%= t("open_loam.actions.edit_title", name: <%= class_name %>.model_name.human, id: @record.id) %></h1>
2
+
3
+ <%%= render "form" %>
@@ -0,0 +1,94 @@
1
+ <%%# One policy for the whole table: readable? keys off the actor's ROLE, not the
2
+ row, so building it per record would only add a membership query per row. %>
3
+ <%% policy = OpenLoam::Policy.for_model(<%= class_name %>, current_actor) %>
4
+
5
+ <h1><%%= <%= class_name %>.model_name.human(count: 2) %></h1>
6
+
7
+ <%% if @index_partial %>
8
+ <p style="color:#b00"><%%= t("open_loam.index.incomplete") %></p>
9
+ <%% end %>
10
+
11
+ <p>
12
+ <%%= link_to t("open_loam.actions.new", name: <%= class_name %>.model_name.human), new_polymorphic_path([:admin, <%= class_name %>]) %>
13
+ <%%= link_to t("open_loam.actions.deleted", name: <%= class_name %>.model_name.human(count: 2)), polymorphic_path([:deleted, :admin, <%= class_name %>]) %>
14
+ <%% if current_role == :manager %>
15
+ <%%= link_to t("open_loam.actions.export_csv"), url_for(action: :export, **current_index_params) %>
16
+ <%%= link_to t("open_loam.actions.import_csv"), new_admin_import_path(entity_type: "<%= class_name %>") %>
17
+ <%% end %>
18
+ </p>
19
+
20
+ <%%# Saved views (OpenLoam::Perspectives): pick one to apply, or save the current one. %>
21
+ <%% if @perspectives.any? %>
22
+ <%%= form_with url: [:admin, <%= class_name %>], method: :get do %>
23
+ <%%= select_tag :perspective_id,
24
+ options_for_select([[t("open_loam.views.all", name: <%= class_name %>.model_name.human(count: 2)), "none"]] + @perspectives.map { |p| [p.name, p.id] }, @perspective&.id),
25
+ onchange: "this.form.submit()" %>
26
+ <%%= link_to t("open_loam.views.manage"), admin_perspectives_path(entity_type: "<%= class_name %>") %>
27
+ <%% end %>
28
+ <%% else %>
29
+ <p><%%= link_to t("open_loam.views.manage_saved"), admin_perspectives_path(entity_type: "<%= class_name %>") %></p>
30
+ <%% end %>
31
+
32
+ <%%= form_with url: admin_perspectives_path, method: :post do %>
33
+ <%%= hidden_field_tag :entity_type, "<%= class_name %>" %>
34
+ <%%= hidden_field_tag :q, params[:q] %>
35
+ <%%= text_field_tag :name, nil, placeholder: t("open_loam.views.name_placeholder") %>
36
+ <%%= submit_tag t("open_loam.views.save_current") %>
37
+ <%% end %>
38
+ <% if searchable_attributes.any? -%>
39
+
40
+ <%%= form_with url: [:admin, <%= class_name %>], method: :get do |f| %>
41
+ <%%= text_field_tag :q, params[:q], placeholder: t("open_loam.views.filter_placeholder", name: <%= class_name %>.model_name.human(count: 2)) %>
42
+ <%%= f.submit t("open_loam.actions.filter") %>
43
+ <%% end %>
44
+ <% end -%>
45
+
46
+ <%%# Bulk actions on selected rows (OpenLoam::Bulk). Zero selection = no-op; each
47
+ action is policy-checked per record server-side. %>
48
+ <%%= form_with url: url_for(action: :bulk), method: :post do %>
49
+ <p>
50
+ <%%= select_tag :bulk_action, options_for_select([[t("open_loam.bulk.delete_selected"), "soft_delete"], [t("open_loam.bulk.set_field"), "set_field"], [t("open_loam.bulk.export_selected"), "export"]]) %>
51
+ <%%= select_tag :field, options_for_select(%i[<%= field_names.join(" ") %>]) %>
52
+ <%%= text_field_tag :value, nil, placeholder: t("open_loam.bulk.new_value") %>
53
+ <%%= submit_tag t("open_loam.bulk.apply"), data: { turbo_confirm: t("open_loam.bulk.confirm") } %>
54
+ </p>
55
+
56
+ <table>
57
+ <thead>
58
+ <tr>
59
+ <th><input type="checkbox" onclick="document.querySelectorAll('.bulk-select').forEach(function(c){c.checked=this.checked}.bind(this))"></th>
60
+ <th><%%= t("open_loam.labels.id") %></th>
61
+ <% attributes.each do |attribute| -%>
62
+ <%% if policy.readable?(:<%= attribute.name %>) %><th><%%= sort_link("<%= attribute.name %>", <%= class_name %>.human_attribute_name("<%= attribute.name %>")) %></th><%% end %>
63
+ <% end -%>
64
+ <th></th>
65
+ </tr>
66
+ </thead>
67
+ <tbody>
68
+ <%% @records.each do |record| %>
69
+ <tr>
70
+ <td><%%= check_box_tag "ids[]", record.id, false, class: "bulk-select" %></td>
71
+ <td><%%= record.id %></td>
72
+ <% attributes.each do |attribute| -%>
73
+ <%% if policy.readable?(:<%= attribute.name %>) %><td><%%= record.<%= attribute.name %> %></td><%% end %>
74
+ <% end -%>
75
+ <td>
76
+ <%%= link_to t("open_loam.actions.show"), [:admin, record] %>
77
+ <%%= link_to t("open_loam.actions.edit"), edit_polymorphic_path([:admin, record]) %>
78
+ </td>
79
+ </tr>
80
+ <%% end %>
81
+ </tbody>
82
+ </table>
83
+ <%% end %>
84
+
85
+ <%%# Page links carry the full filter state, or page 2 would quietly drop it. %>
86
+ <p>
87
+ <%% if @page > 1 %>
88
+ <%%= link_to t("open_loam.pagination.previous"), polymorphic_path([:admin, <%= class_name %>], **current_index_params, page: @page - 1) %>
89
+ <%% end %>
90
+ <%%= t("open_loam.pagination.page", page: @page) %>
91
+ <%% if @has_next %>
92
+ <%%= link_to t("open_loam.pagination.next"), polymorphic_path([:admin, <%= class_name %>], **current_index_params, page: @page + 1) %>
93
+ <%% end %>
94
+ </p>
@@ -0,0 +1,3 @@
1
+ <h1><%%= t("open_loam.actions.new_title", name: <%= class_name %>.model_name.human) %></h1>
2
+
3
+ <%%= render "form" %>
@@ -0,0 +1,38 @@
1
+ <%% policy = OpenLoam::Policy.for(@record) %>
2
+
3
+ <h1><%%= t("open_loam.actions.show_title", name: <%= class_name %>.model_name.human, id: @record.id) %></h1>
4
+
5
+ <% attributes.each do |attribute| -%>
6
+ <%% if policy.readable?(:<%= attribute.name %>) %>
7
+ <p><strong><%%= <%= class_name %>.human_attribute_name("<%= attribute.name %>") %>:</strong> <%%= @record.<%= attribute.name %> %></p>
8
+ <%% end %>
9
+ <% end -%>
10
+
11
+ <%%= render "open_loam/custom_fields/fields", record: @record, policy: policy, mode: :show %>
12
+
13
+ <%%# Computed cross-module blocks (OpenLoam::Enrichers) — no section when none registered. %>
14
+ <%% enrichments = OpenLoam::Enrichers.enrich(@record) %>
15
+ <%% if enrichments.any? %>
16
+ <h2><%%= t("open_loam.labels.enrichments") %></h2>
17
+ <dl>
18
+ <%% enrichments.each do |key, value| %>
19
+ <dt><strong><%%= key.humanize %></strong></dt>
20
+ <dd><%%= value %></dd>
21
+ <%% end %>
22
+ </dl>
23
+ <%% end %>
24
+
25
+ <p>
26
+ <%%= link_to t("open_loam.actions.edit"), edit_polymorphic_path([:admin, @record]) %>
27
+ <%%= link_to "History", admin_history_path(type: @record.class.name, record_id: @record.id) %>
28
+ <%% if @record.class.respond_to?(:open_loam_translatable_attributes) && @record.class.open_loam_translatable_attributes.any? && current_role == :manager %>
29
+ <%%= link_to t("open_loam.actions.translations"), admin_translations_path(translatable_type: @record.class.name, translatable_id: @record.id) %>
30
+ <%% end %>
31
+ <%%# Delete soft-deletes: the record moves to the recycle bin, not oblivion. %>
32
+ <%%= button_to t("open_loam.actions.destroy"), [:admin, @record], method: :delete if policy.destroy? %>
33
+ <%%= link_to t("open_loam.actions.back"), [:admin, <%= class_name %>] %>
34
+ </p>
35
+
36
+ <%%= render "open_loam/attachments/attachments", record: @record %>
37
+
38
+ <%%= render "open_loam/comments/comments", record: @record %>