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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +148 -8
- data/LICENSE +1 -1
- data/README.md +27 -25
- data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
- data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
- data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
- data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
- data/app/models/open_loam/api_token.rb +63 -0
- data/app/models/{loam → open_loam}/audit_record.rb +3 -3
- data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
- data/app/models/{loam → open_loam}/business_rule.rb +10 -10
- data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
- data/app/models/{loam → open_loam}/comment.rb +9 -9
- data/app/models/{loam → open_loam}/config.rb +7 -7
- data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
- data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
- data/app/models/open_loam/dictionary.rb +20 -0
- data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
- data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
- data/app/models/open_loam/event_record.rb +42 -0
- data/app/models/{loam → open_loam}/field_definition.rb +8 -8
- data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
- data/app/models/{loam → open_loam}/membership.rb +6 -6
- data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
- data/app/models/{loam → open_loam}/notification.rb +10 -10
- data/app/models/{loam → open_loam}/pending_action.rb +70 -26
- data/app/models/{loam → open_loam}/perspective.rb +5 -5
- data/app/models/{loam → open_loam}/progress_job.rb +7 -7
- data/app/models/{loam → open_loam}/record_lock.rb +5 -5
- data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
- data/app/models/open_loam/search_token.rb +9 -0
- data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
- data/app/models/open_loam/sso_provider.rb +66 -0
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/open_loam/webhook_endpoint.rb +39 -0
- data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
- data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
- data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
- data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
- data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
- data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
- data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
- data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
- data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
- data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
- data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
- data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
- data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
- data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
- 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
- data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
- data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
- data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
- data/lib/open-loam.rb +2 -2
- data/lib/{loam → open_loam}/attachable.rb +4 -4
- data/lib/{loam → open_loam}/auditable.rb +23 -23
- data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
- data/lib/{loam → open_loam}/base32.rb +1 -1
- data/lib/{loam → open_loam}/bulk.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
- data/lib/{loam → open_loam}/business_rules.rb +19 -19
- data/lib/{loam → open_loam}/commentable.rb +6 -6
- data/lib/{loam → open_loam}/configs.rb +19 -19
- data/lib/{loam → open_loam}/cron.rb +2 -2
- data/lib/{loam → open_loam}/csv.rb +2 -2
- data/lib/{loam → open_loam}/current.rb +5 -5
- data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
- data/lib/{loam → open_loam}/custom_fields.rb +17 -17
- data/lib/{loam → open_loam}/dashboard.rb +5 -5
- data/lib/{loam → open_loam}/dictionaries.rb +12 -12
- data/lib/{loam → open_loam}/durable_events.rb +19 -19
- data/lib/{loam → open_loam}/encryptable.rb +39 -34
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
- data/lib/{loam → open_loam}/encryption.rb +58 -15
- data/lib/open_loam/engine.rb +65 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +20 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- data/lib/open_loam/event_log.rb +77 -0
- data/lib/{loam → open_loam}/event_stream.rb +14 -14
- data/lib/{loam → open_loam}/eventful.rb +12 -12
- data/lib/{loam → open_loam}/events.rb +5 -5
- data/lib/{loam → open_loam}/export.rb +12 -12
- data/lib/{loam → open_loam}/features.rb +15 -15
- data/lib/{loam → open_loam}/generated_key.rb +6 -6
- data/lib/{loam → open_loam}/import.rb +12 -15
- data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
- data/lib/{loam → open_loam}/lifecycle.rb +54 -25
- data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
- data/lib/{loam → open_loam}/mcp/server.rb +9 -9
- data/lib/{loam → open_loam}/mcp.rb +31 -22
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- data/lib/open_loam/outbound_url.rb +100 -0
- data/lib/{loam → open_loam}/overrides.rb +10 -10
- data/lib/{loam → open_loam}/pending_actions.rb +9 -9
- data/lib/{loam → open_loam}/permissions.rb +8 -8
- data/lib/{loam → open_loam}/perspectives.rb +9 -9
- data/lib/{loam → open_loam}/policy.rb +21 -8
- data/lib/{loam → open_loam}/progress.rb +6 -6
- data/lib/{loam → open_loam}/record_locks.rb +9 -9
- data/lib/{loam → open_loam}/scheduler.rb +20 -20
- data/lib/{loam → open_loam}/search/driver.rb +3 -3
- data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
- data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
- data/lib/{loam → open_loam}/search.rb +5 -5
- data/lib/{loam → open_loam}/searchable.rb +22 -22
- data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
- data/lib/{loam → open_loam}/sso/claims.rb +1 -1
- data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
- data/lib/open_loam/sso/http_client.rb +51 -0
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +39 -18
- data/lib/{loam → open_loam}/telemetry.rb +6 -6
- data/lib/{loam → open_loam}/tenant_record.rb +10 -10
- data/lib/{loam → open_loam}/test_helpers.rb +4 -4
- data/lib/{loam → open_loam}/totp.rb +3 -3
- data/lib/{loam → open_loam}/translatable.rb +24 -24
- data/lib/{loam → open_loam}/undo.rb +11 -11
- data/lib/open_loam/version.rb +3 -0
- data/lib/{loam → open_loam}/webhooks.rb +4 -4
- data/lib/{loam → open_loam}/widgets.rb +9 -9
- data/lib/{loam → open_loam}/workflow.rb +45 -45
- data/lib/open_loam.rb +124 -0
- data/lib/tasks/open_loam.rake +203 -0
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +247 -242
- data/app/models/loam/api_token.rb +0 -40
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/sso_provider.rb +0 -35
- data/app/models/loam/tenant.rb +0 -20
- data/app/models/loam/webhook_endpoint.rb +0 -26
- data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
- data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
- data/lib/generators/loam/install/templates/AGENTS.md +0 -341
- data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
- data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
- data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
- data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
- data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
- data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
- data/lib/generators/loam/install/templates/import_job.rb +0 -25
- data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
- data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
- data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
- data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
- data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
- data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
- data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
- data/lib/loam/engine.rb +0 -57
- data/lib/loam/sso/http_client.rb +0 -42
- data/lib/loam/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam.rake +0 -171
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
|
@@ -37,3 +37,26 @@ audit trail. Send it as <code>Authorization: Bearer <token></code>.</p>
|
|
|
37
37
|
<%% end %>
|
|
38
38
|
</tbody>
|
|
39
39
|
</table>
|
|
40
|
+
|
|
41
|
+
<%% if @tenant_records.any? %>
|
|
42
|
+
<h2>Everyone else's tokens</h2>
|
|
43
|
+
<p>Manager view. Removing someone's membership already stops their tokens
|
|
44
|
+
working; revoking is the clean end of it.</p>
|
|
45
|
+
|
|
46
|
+
<table>
|
|
47
|
+
<thead>
|
|
48
|
+
<tr><th>Owner</th><th>Label</th><th>Created</th><th>Last used</th><th></th></tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
<%% @tenant_records.each do |record| %>
|
|
52
|
+
<tr>
|
|
53
|
+
<td><%%= record.user.name %></td>
|
|
54
|
+
<td><%%= record.label %></td>
|
|
55
|
+
<td><%%= record.created_at.to_fs(:short) %></td>
|
|
56
|
+
<td><%%= record.last_used_at&.to_fs(:short) || "never" %></td>
|
|
57
|
+
<td><%%= button_to "Revoke", admin_api_token_path(record), method: :delete, form: { style: "display:inline" } %></td>
|
|
58
|
+
</tr>
|
|
59
|
+
<%% end %>
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
<%% end %>
|
|
@@ -4,22 +4,42 @@ module Admin
|
|
|
4
4
|
|
|
5
5
|
layout "admin"
|
|
6
6
|
|
|
7
|
-
before_action :
|
|
7
|
+
before_action :set_open_loam_context
|
|
8
|
+
before_action :require_mfa_enrollment!
|
|
8
9
|
before_action :set_locale
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
# Fails an action that never authorized anything. Forgetting `authorize!` is
|
|
12
|
+
# otherwise silent — the screen renders, and nothing says the policy was
|
|
13
|
+
# never consulted.
|
|
14
|
+
#
|
|
15
|
+
# It runs AFTER the action, so it catches the omission in development and in
|
|
16
|
+
# tests; it is not a runtime access-control layer. A `destroy` that forgot to
|
|
17
|
+
# authorize has already destroyed the record by the time this raises.
|
|
18
|
+
after_action :verify_authorized!
|
|
19
|
+
|
|
20
|
+
# For screens that are authorized STRUCTURALLY rather than by a policy call —
|
|
21
|
+
# a list scoped to current_actor, a pre-auth screen. The reason is required
|
|
22
|
+
# and shows up in the source, so every exemption is a documented claim rather
|
|
23
|
+
# than a silent opt-out.
|
|
24
|
+
def self.skip_authorization!(reason, **options)
|
|
25
|
+
raise ArgumentError, "skip_authorization! needs a reason" if reason.to_s.strip.empty?
|
|
26
|
+
|
|
27
|
+
skip_after_action :verify_authorized!, **options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rescue_from OpenLoam::NotAuthorizedError do
|
|
11
31
|
render plain: "403 Forbidden — your role does not permit this action.", status: :forbidden
|
|
12
32
|
end
|
|
13
33
|
|
|
14
34
|
# A disabled feature is "not here" for this tenant, so it 404s — unlike a
|
|
15
35
|
# policy refusal (403), which is "you may not". Capability vs. person.
|
|
16
|
-
rescue_from
|
|
36
|
+
rescue_from OpenLoam::FeatureDisabledError do
|
|
17
37
|
render plain: "404 Not Found — this feature is not enabled for your tenant.", status: :not_found
|
|
18
38
|
end
|
|
19
39
|
|
|
20
40
|
# A business rule (or any code) can veto a workflow transition from
|
|
21
|
-
# `
|
|
22
|
-
rescue_from
|
|
41
|
+
# `open_loam_perform_transition!`. Surface it as a clean 422, never a 500.
|
|
42
|
+
rescue_from OpenLoam::TransitionVetoedError do |error|
|
|
23
43
|
render plain: "422 — #{error.message}", status: :unprocessable_entity
|
|
24
44
|
end
|
|
25
45
|
|
|
@@ -41,49 +61,65 @@ module Admin
|
|
|
41
61
|
end
|
|
42
62
|
|
|
43
63
|
# Locale is request state like the tenant: from the switcher (param),
|
|
44
|
-
# remembered in the session, defaulting to
|
|
45
|
-
# translations (
|
|
64
|
+
# remembered in the session, defaulting to OpenLoam.default_locale. Content
|
|
65
|
+
# translations (OpenLoam::Translatable) overlay onto it.
|
|
46
66
|
def set_locale
|
|
47
|
-
session[:locale] = params[:locale] if params[:locale].present? &&
|
|
48
|
-
|
|
67
|
+
session[:locale] = params[:locale] if params[:locale].present? && OpenLoam.locales.include?(params[:locale])
|
|
68
|
+
OpenLoam::Current.locale = session[:locale] || OpenLoam.default_locale
|
|
49
69
|
# Drive Rails i18n (UI strings) off the same choice, so the switcher moves
|
|
50
70
|
# both the chrome and the content — but only to a locale that actually has
|
|
51
71
|
# translations, so an unlisted locale keeps English UI (data still overlays).
|
|
52
|
-
chosen =
|
|
72
|
+
chosen = OpenLoam::Current.locale.to_s
|
|
53
73
|
I18n.locale = chosen if I18n.available_locales.map(&:to_s).include?(chosen)
|
|
54
74
|
end
|
|
55
75
|
|
|
56
|
-
def current_locale =
|
|
76
|
+
def current_locale = OpenLoam::Current.locale
|
|
57
77
|
|
|
58
|
-
def current_tenant =
|
|
59
|
-
def current_actor =
|
|
78
|
+
def current_tenant = OpenLoam::Current.tenant
|
|
79
|
+
def current_actor = OpenLoam::Current.actor
|
|
60
80
|
|
|
61
|
-
# Establishes
|
|
81
|
+
# Establishes OpenLoam::Current from the session, then proves the actor is
|
|
62
82
|
# allowed to be here.
|
|
63
83
|
#
|
|
64
|
-
# The order is load-bearing.
|
|
84
|
+
# The order is load-bearing. OpenLoam::Membership is itself tenant-scoped, so
|
|
65
85
|
# "is this actor a member?" is a question you can only ask from inside a
|
|
66
|
-
# tenant — the tenant has to be in
|
|
67
|
-
#
|
|
86
|
+
# tenant — the tenant has to be in OpenLoam::Current first, or the check raises
|
|
87
|
+
# OpenLoam::MissingTenantError instead of answering. The `&&` chain below
|
|
68
88
|
# guarantees that: membership is only consulted once both are set.
|
|
69
89
|
#
|
|
70
90
|
# If anything fails, BOTH halves of the context are cleared before the
|
|
71
91
|
# redirect, so a half-established context can never leak into the next
|
|
72
92
|
# request or into a job enqueued from it.
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
def set_open_loam_context
|
|
94
|
+
OpenLoam::Current.tenant = OpenLoam::Tenant.find_by(id: session[:tenant_id])
|
|
95
|
+
OpenLoam::Current.actor = User.find_by(id: session[:user_id])
|
|
76
96
|
|
|
77
97
|
return if current_tenant && current_actor && member_of_current_tenant?
|
|
78
98
|
|
|
79
|
-
|
|
99
|
+
OpenLoam::Current.reset
|
|
80
100
|
redirect_to new_admin_session_path
|
|
81
101
|
end
|
|
82
102
|
|
|
83
|
-
# Scoped to the current tenant by
|
|
103
|
+
# Scoped to the current tenant by OpenLoam::TenantRecord, which is the whole
|
|
84
104
|
# point: a membership in some other tenant is not membership here.
|
|
85
105
|
def member_of_current_tenant?
|
|
86
|
-
|
|
106
|
+
OpenLoam::Membership.exists?(user_id: current_actor.id)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# security.mfa_required_roles has to hold on EVERY request, not just as a
|
|
110
|
+
# redirect at the end of login — otherwise typing any other admin URL walks
|
|
111
|
+
# straight past enrollment and the requirement means nothing.
|
|
112
|
+
def require_mfa_enrollment!
|
|
113
|
+
return if OpenLoam::MfaCredential.active_for(current_actor)
|
|
114
|
+
return unless mfa_required_role?
|
|
115
|
+
|
|
116
|
+
redirect_to new_admin_mfa_path,
|
|
117
|
+
alert: "Your role requires two-factor authentication — set it up to continue."
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def mfa_required_role?
|
|
121
|
+
required = Array(OpenLoam::Configs.get("security.mfa_required_roles", default: []))
|
|
122
|
+
required.map(&:to_s).include?(current_role.to_s)
|
|
87
123
|
end
|
|
88
124
|
|
|
89
125
|
# Drives the bell in the admin layout. One COUNT per admin page render,
|
|
@@ -91,54 +127,77 @@ module Admin
|
|
|
91
127
|
def unread_notification_count
|
|
92
128
|
return 0 unless current_actor
|
|
93
129
|
|
|
94
|
-
|
|
130
|
+
OpenLoam::Notification.unread.where(user_id: current_actor.id).count
|
|
95
131
|
end
|
|
96
132
|
|
|
97
133
|
# Staged changes awaiting review, for the "Approvals (N)" nav badge.
|
|
98
134
|
def pending_approval_count
|
|
99
135
|
return 0 unless current_tenant
|
|
100
136
|
|
|
101
|
-
|
|
137
|
+
OpenLoam::PendingAction.pending.count
|
|
102
138
|
end
|
|
103
139
|
|
|
104
140
|
def policy_for(record)
|
|
105
|
-
|
|
141
|
+
OpenLoam::Policy.for(record)
|
|
106
142
|
end
|
|
107
143
|
|
|
108
144
|
def authorize!(policy, action)
|
|
109
|
-
|
|
145
|
+
authorized!
|
|
146
|
+
raise OpenLoam::NotAuthorizedError unless policy.public_send(action)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Records that this action asked a person-authorization question — marked
|
|
150
|
+
# whether the answer was yes or no, since a refusal is still a check.
|
|
151
|
+
#
|
|
152
|
+
# Call it directly only where the check is an inline `raise` rather than one
|
|
153
|
+
# of the helpers below (see Admin::PerspectivesController). Deliberately NOT
|
|
154
|
+
# called by require_feature! (a capability gate, not a person gate) or
|
|
155
|
+
# require_sudo! (step-up, orthogonal to role) — either marking the action
|
|
156
|
+
# would let a feature-gated screen skip authorization silently.
|
|
157
|
+
def authorized!
|
|
158
|
+
@open_loam_authorized = true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def verify_authorized!
|
|
162
|
+
return if @open_loam_authorized
|
|
163
|
+
|
|
164
|
+
raise OpenLoam::AuthorizationNotPerformedError,
|
|
165
|
+
"#{self.class.name}##{action_name} finished without authorizing. Call authorize!/require_role!/" \
|
|
166
|
+
"require_permission!, or declare `skip_authorization! \"<why>\"` if it is authorized structurally."
|
|
110
167
|
end
|
|
111
168
|
|
|
112
169
|
# For admin screens with no per-record policy (e.g. field definitions,
|
|
113
170
|
# which apply to a whole entity_type rather than one record).
|
|
114
171
|
def current_role
|
|
115
|
-
|
|
172
|
+
OpenLoam::Membership.find_by(user_id: current_actor&.id)&.role&.to_sym
|
|
116
173
|
end
|
|
117
174
|
|
|
118
175
|
def require_role!(*roles)
|
|
119
|
-
|
|
176
|
+
authorized!
|
|
177
|
+
raise OpenLoam::NotAuthorizedError unless roles.include?(current_role)
|
|
120
178
|
end
|
|
121
179
|
|
|
122
|
-
# Feature-string permission gate (
|
|
180
|
+
# Feature-string permission gate (OpenLoam::Permissions) — a finer capability
|
|
123
181
|
# check than require_role!. `can?` is a helper_method so views hide UI a role
|
|
124
182
|
# lacks; require_permission! enforces it (403) server-side.
|
|
125
183
|
def can?(permission)
|
|
126
|
-
|
|
184
|
+
OpenLoam.can?(permission)
|
|
127
185
|
end
|
|
128
186
|
|
|
129
187
|
def require_permission!(permission)
|
|
130
|
-
|
|
188
|
+
authorized!
|
|
189
|
+
raise OpenLoam::NotAuthorizedError unless OpenLoam.can?(permission)
|
|
131
190
|
end
|
|
132
191
|
|
|
133
192
|
# Feature guards. These gate a CAPABILITY (is the feature on for this
|
|
134
193
|
# tenant), orthogonal to require_role! / policies, which gate a PERSON.
|
|
135
194
|
# `feature_on?` is a helper_method, so views can hide UI a flag turns off.
|
|
136
195
|
def feature_on?(name)
|
|
137
|
-
|
|
196
|
+
OpenLoam::Features.on?(name)
|
|
138
197
|
end
|
|
139
198
|
|
|
140
199
|
def require_feature!(name)
|
|
141
|
-
raise
|
|
200
|
+
raise OpenLoam::FeatureDisabledError unless OpenLoam::Features.on?(name)
|
|
142
201
|
end
|
|
143
202
|
|
|
144
203
|
# Optimistic-locking conflict handling: someone saved this record between the
|
|
@@ -146,13 +205,17 @@ module Admin
|
|
|
146
205
|
# StaleObjectError). Build a field-level diff of what the user tried to write
|
|
147
206
|
# vs the current saved values, then RELOAD to fresh data so the retry carries
|
|
148
207
|
# the new lock_version — never a 500, never a silent clobber. lock_version is
|
|
149
|
-
# the guarantee; the advisory
|
|
208
|
+
# the guarantee; the advisory OpenLoam::RecordLock is only a courtesy warning.
|
|
150
209
|
#
|
|
151
210
|
# Encrypted fields are compared and shown via the reader (decrypted); the raw
|
|
152
211
|
# column is ciphertext that never matches (random IV), so a byte compare would
|
|
153
212
|
# be a false conflict every time.
|
|
154
213
|
def stale_conflict!(record, fields)
|
|
155
|
-
encrypted = record.class.respond_to?(:
|
|
214
|
+
encrypted = record.class.respond_to?(:open_loam_encrypted_attributes) ? record.class.open_loam_encrypted_attributes.map(&:to_s) : []
|
|
215
|
+
# The diff shows the OTHER person's saved values, so it obeys the same field
|
|
216
|
+
# rules as any other read. Anything not writable was never submitted anyway.
|
|
217
|
+
policy = policy_for(record)
|
|
218
|
+
fields = fields.select { |field| policy.writable?(field) && policy.readable?(field) }
|
|
156
219
|
attempted = fields.map(&:to_s).index_with { |field| conflict_value(record, field, encrypted) }
|
|
157
220
|
|
|
158
221
|
record.reload
|
|
@@ -199,12 +262,12 @@ module Admin
|
|
|
199
262
|
submitted = Array(params.dig(record.model_name.param_key, :files)).reject(&:blank?)
|
|
200
263
|
return if submitted.empty?
|
|
201
264
|
|
|
202
|
-
raise
|
|
265
|
+
raise OpenLoam::NotAuthorizedError unless policy.update?
|
|
203
266
|
|
|
204
267
|
record.files.attach(submitted)
|
|
205
268
|
end
|
|
206
269
|
|
|
207
|
-
# Runtime custom fields (see
|
|
270
|
+
# Runtime custom fields (see OpenLoam::CustomFields) go through the same
|
|
208
271
|
# field-level enforcement as real columns: only a writable definition's
|
|
209
272
|
# value is ever assigned.
|
|
210
273
|
def assign_custom_fields!(record, params, policy)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Managing business rules (
|
|
2
|
+
# Managing business rules (OpenLoam::BusinessRule) — manager-only, since a rule can
|
|
3
3
|
# notify, emit events, and set fields across the tenant. The condition and
|
|
4
4
|
# actions are edited as JSON for the prototype (a visual builder is a future
|
|
5
5
|
# increment). The recent execution log shows WHY rules fired.
|
|
@@ -9,21 +9,21 @@ module Admin
|
|
|
9
9
|
|
|
10
10
|
rescue_from JSON::ParserError do |error|
|
|
11
11
|
flash.now[:alert] = "Condition and actions must be valid JSON: #{error.message}"
|
|
12
|
-
@rule ||=
|
|
12
|
+
@rule ||= OpenLoam::BusinessRule.new
|
|
13
13
|
render(@rule.persisted? ? :edit : :new, status: :unprocessable_entity)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def index
|
|
17
|
-
@rules =
|
|
18
|
-
@recent_runs =
|
|
17
|
+
@rules = OpenLoam::BusinessRule.by_priority
|
|
18
|
+
@recent_runs = OpenLoam::BusinessRuleRun.recent.limit(20)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def new
|
|
22
|
-
@rule =
|
|
22
|
+
@rule = OpenLoam::BusinessRule.new(active: true, condition: {}, actions: [])
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def create
|
|
26
|
-
@rule =
|
|
26
|
+
@rule = OpenLoam::BusinessRule.new(rule_params)
|
|
27
27
|
if @rule.save
|
|
28
28
|
redirect_to admin_business_rules_path, notice: "Rule created."
|
|
29
29
|
else
|
|
@@ -49,7 +49,7 @@ module Admin
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
51
|
def set_rule
|
|
52
|
-
@rule =
|
|
52
|
+
@rule = OpenLoam::BusinessRule.find(params[:id])
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Parse the JSON textareas here; a malformed value raises JSON::ParserError,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<p style="color:#b00"><%%= @error || flash[:alert] %></p>
|
|
3
3
|
<%% end %>
|
|
4
4
|
|
|
5
|
-
<%# The model is namespaced (
|
|
6
|
-
would be
|
|
5
|
+
<%# The model is namespaced (OpenLoam::BusinessRule), so an inferred URL/param-scope
|
|
6
|
+
would be admin_open_loam_business_rule_path / open_loam_business_rule[...]. Pin both to
|
|
7
7
|
the un-namespaced route and the "business_rule" key the controller permits. %>
|
|
8
8
|
<%%= form_with model: @rule, scope: :business_rule,
|
|
9
9
|
url: (@rule.persisted? ? admin_business_rule_path(@rule) : admin_business_rules_path) do |f| %>
|
|
@@ -14,7 +14,7 @@ module Admin
|
|
|
14
14
|
# discussion too.
|
|
15
15
|
authorize!(policy_for(record), :read?)
|
|
16
16
|
|
|
17
|
-
comment = record.
|
|
17
|
+
comment = record.open_loam_comments.new(body: params[:body], author: current_actor)
|
|
18
18
|
flash[:alert] = comment.errors.full_messages.to_sentence unless comment.save
|
|
19
19
|
|
|
20
20
|
redirect_back fallback_location: [ :admin, record ]
|
|
@@ -24,13 +24,13 @@ module Admin
|
|
|
24
24
|
|
|
25
25
|
# The type arrives from the browser, so it is never constantized blindly.
|
|
26
26
|
# `safe_constantize` returns nil for anything that is not a real constant,
|
|
27
|
-
# and the class then has to prove it is a
|
|
27
|
+
# and the class then has to prove it is a OpenLoam entity that opted into
|
|
28
28
|
# comments — the allowlist is the contract, not a hand-kept list of names.
|
|
29
29
|
def commentable
|
|
30
30
|
klass = params[:commentable_type].to_s.safe_constantize
|
|
31
31
|
|
|
32
|
-
unless klass.is_a?(Class) && klass <
|
|
33
|
-
raise
|
|
32
|
+
unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord && klass.include?(OpenLoam::Commentable)
|
|
33
|
+
raise OpenLoam::NotAuthorizedError, "#{params[:commentable_type].inspect} is not a commentable OpenLoam entity"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
klass.find(params[:commentable_id])
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Per-tenant settings (
|
|
2
|
+
# Per-tenant settings (OpenLoam::Configs). Structural, not per-record, so it is
|
|
3
3
|
# gated on the manager role rather than an entity policy — settings are
|
|
4
|
-
# privileged. Everything routes through
|
|
4
|
+
# privileged. Everything routes through OpenLoam::Configs, never OpenLoam::Config
|
|
5
5
|
# directly, so the cross-level resolution stays in vetted gem code.
|
|
6
6
|
class ConfigsController < BaseController
|
|
7
7
|
before_action { require_role!(:manager) }
|
|
8
8
|
|
|
9
9
|
def index
|
|
10
|
-
# Feature flags share the
|
|
10
|
+
# Feature flags share the open_loam_configs store but have their own screen
|
|
11
11
|
# (/admin/features), so hide their reserved-prefix keys from Settings.
|
|
12
|
-
keys =
|
|
12
|
+
keys = OpenLoam::Configs.defined_keys.reject { |key| key.start_with?(OpenLoam::Features::PREFIX) }
|
|
13
13
|
@settings = keys.map do |key|
|
|
14
|
-
{ key: key, value:
|
|
14
|
+
{ key: key, value: OpenLoam::Configs.get(key), overridden: OpenLoam::Configs.overridden?(key) }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def edit
|
|
19
19
|
@key = params[:key]
|
|
20
|
-
@value =
|
|
21
|
-
@overridden =
|
|
20
|
+
@value = OpenLoam::Configs.get(@key)
|
|
21
|
+
@overridden = OpenLoam::Configs.overridden?(@key)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def update
|
|
25
|
-
|
|
25
|
+
OpenLoam::Configs.set(params[:key], coerce(params[:value]))
|
|
26
26
|
redirect_to admin_configs_path, notice: "#{params[:key]} set for this tenant."
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def reset
|
|
30
|
-
|
|
30
|
+
OpenLoam::Configs.reset(params[:key])
|
|
31
31
|
redirect_to admin_configs_path, notice: "#{params[:key]} reset to the default."
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class DashboardController < BaseController
|
|
3
|
+
skip_authorization! "Widgets are filtered by role in OpenLoam::Dashboard.for; there is no per-record subject."
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
# Configurable, role-visible widgets (OpenLoam::Dashboard) — a raising widget
|
|
7
|
+
# is isolated into an error tile, never breaking the page. Register your
|
|
8
|
+
# own with OpenLoam::Widgets.register; arrange them under Dashboard settings.
|
|
9
|
+
@widgets = OpenLoam::Dashboard.for(actor: current_actor, role: current_role)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb
RENAMED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Dashboard settings (
|
|
2
|
+
# Dashboard settings (OpenLoam::DashboardWidget) — manager-only. Choose which
|
|
3
3
|
# registered widgets appear and in what order for this tenant.
|
|
4
4
|
class DashboardWidgetsController < BaseController
|
|
5
5
|
before_action { require_role!(:manager) }
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
|
-
@registered =
|
|
9
|
-
@config =
|
|
8
|
+
@registered = OpenLoam::Widgets.registered
|
|
9
|
+
@config = OpenLoam::DashboardWidget.all.index_by(&:widget_key)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def update
|
|
13
|
-
|
|
13
|
+
OpenLoam::Widgets.keys.each do |key|
|
|
14
14
|
attrs = params.dig(:widgets, key) || {}
|
|
15
|
-
row =
|
|
15
|
+
row = OpenLoam::DashboardWidget.find_or_initialize_by(widget_key: key)
|
|
16
16
|
row.active = attrs[:active] == "1"
|
|
17
17
|
row.position = attrs[:position].to_i
|
|
18
18
|
row.save!
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Managed lookup lists (
|
|
2
|
+
# Managed lookup lists (OpenLoam::Dictionary) — manager-only. The index lists a
|
|
3
3
|
# tenant's dictionaries; the edit screen curates a dictionary's entries
|
|
4
4
|
# (Admin::DictionaryEntriesController). Tenant-scoped by the model's default
|
|
5
5
|
# scope, so a manager only ever sees their own tenant's lists.
|
|
@@ -8,15 +8,15 @@ module Admin
|
|
|
8
8
|
before_action :set_dictionary, only: %i[edit update destroy]
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
|
-
@dictionaries =
|
|
11
|
+
@dictionaries = OpenLoam::Dictionary.order(:key)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def new
|
|
15
|
-
@dictionary =
|
|
15
|
+
@dictionary = OpenLoam::Dictionary.new
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def create
|
|
19
|
-
@dictionary =
|
|
19
|
+
@dictionary = OpenLoam::Dictionary.new(dictionary_params)
|
|
20
20
|
if @dictionary.save
|
|
21
21
|
redirect_to edit_admin_dictionary_path(@dictionary), notice: "Dictionary created — add its entries below."
|
|
22
22
|
else
|
|
@@ -45,7 +45,7 @@ module Admin
|
|
|
45
45
|
private
|
|
46
46
|
|
|
47
47
|
def set_dictionary
|
|
48
|
-
@dictionary =
|
|
48
|
+
@dictionary = OpenLoam::Dictionary.find(params[:id])
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def dictionary_params
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%%#
|
|
1
|
+
<%%# OpenLoam::Dictionary is namespaced, so pin the URL + param scope. %>
|
|
2
2
|
<%%= form_with model: @dictionary, scope: :dictionary,
|
|
3
3
|
url: (@dictionary.persisted? ? admin_dictionary_path(@dictionary) : admin_dictionaries_path) do |f| %>
|
|
4
4
|
<%% if @dictionary.errors.any? %>
|
data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Entries of a
|
|
2
|
+
# Entries of a OpenLoam::Dictionary — manager-only, nested under a dictionary.
|
|
3
3
|
# Add / edit (value, label, color, icon, position, default, active) / delete;
|
|
4
4
|
# reordering is by editing `position`.
|
|
5
5
|
class DictionaryEntriesController < BaseController
|
|
@@ -33,7 +33,7 @@ module Admin
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
35
|
def set_dictionary
|
|
36
|
-
@dictionary =
|
|
36
|
+
@dictionary = OpenLoam::Dictionary.find(params[:dictionary_id])
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def set_entry
|
data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Durable event deliveries (
|
|
2
|
+
# Durable event deliveries (OpenLoam::EventDelivery) — the dead-letter view.
|
|
3
3
|
# Manager-only. Lists deliveries parked as `dead` (handler removed, or retries
|
|
4
4
|
# exhausted) and lets an operator requeue one after fixing the handler. Pending
|
|
5
5
|
# deliveries drain on their own; a small sample is shown for visibility.
|
|
@@ -7,16 +7,16 @@ module Admin
|
|
|
7
7
|
before_action { require_role!(:manager) }
|
|
8
8
|
|
|
9
9
|
def index
|
|
10
|
-
@dead =
|
|
11
|
-
@pending =
|
|
10
|
+
@dead = OpenLoam::EventDelivery.dead.order(updated_at: :desc).limit(200)
|
|
11
|
+
@pending = OpenLoam::EventDelivery.pending.order(Arel.sql("next_attempt_at IS NULL DESC, next_attempt_at ASC")).limit(50)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# Re-arm one delivery: back to pending with a clean slate, and nudge a job.
|
|
15
15
|
# The sweep would eventually pick it up anyway; this is the "try now" button.
|
|
16
16
|
def redeliver
|
|
17
|
-
delivery =
|
|
17
|
+
delivery = OpenLoam::EventDelivery.find(params[:id])
|
|
18
18
|
delivery.update!(status: "pending", attempts: 0, next_attempt_at: nil, last_error: nil)
|
|
19
|
-
|
|
19
|
+
OpenLoam::EventDeliveryJob.perform_later(delivery.tenant_id, delivery.id)
|
|
20
20
|
redirect_to admin_event_deliveries_path, notice: "Delivery requeued."
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1>Event deliveries</h1>
|
|
2
2
|
|
|
3
|
-
<p>Durable subscribers (<code>
|
|
3
|
+
<p>Durable subscribers (<code>OpenLoam::DurableEvents</code>). Delivery is at-least-once
|
|
4
4
|
with retry; a delivery that exhausts its retries — or whose subscriber was removed —
|
|
5
5
|
is parked here as <strong>dead</strong> for you to fix and requeue.</p>
|
|
6
6
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Server-Sent Events: a per-tenant push stream (
|
|
2
|
+
# Server-Sent Events: a per-tenant push stream (OpenLoam::EventStream) so the admin
|
|
3
3
|
# updates live instead of polling. Authentication runs in the normal
|
|
4
|
-
# before_action chain (
|
|
4
|
+
# before_action chain (set_open_loam_context) BEFORE any stream write — so an
|
|
5
5
|
# unauthenticated request redirects to login the ordinary way, since no headers
|
|
6
6
|
# have been committed yet. Once the loop starts writing, the response is
|
|
7
7
|
# committed and there is no going back; that ordering is the whole safety of a
|
|
8
8
|
# Live action.
|
|
9
9
|
class EventsController < BaseController
|
|
10
|
+
skip_authorization! "OpenLoam::EventStream gates every message: declared pattern, matching tenant, and audience."
|
|
11
|
+
|
|
10
12
|
include ActionController::Live
|
|
11
13
|
|
|
12
14
|
HEARTBEAT_SECONDS = 20
|
|
@@ -20,7 +22,7 @@ module Admin
|
|
|
20
22
|
# Subscribe as the FIRST thing in the begin whose ensure unsubscribes: a
|
|
21
23
|
# leaked AS::Notifications subscriber would fire on every future event in
|
|
22
24
|
# the process, holding this dead stream's queue forever.
|
|
23
|
-
handle =
|
|
25
|
+
handle = OpenLoam::EventStream.broadcaster.subscribe(tenant: current_tenant, actor: current_actor) do |sse|
|
|
24
26
|
queue << sse
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -34,9 +36,9 @@ module Admin
|
|
|
34
36
|
rescue IOError, ActionController::Live::ClientDisconnected
|
|
35
37
|
# The client went away — nothing to do but clean up in `ensure`.
|
|
36
38
|
ensure
|
|
37
|
-
|
|
39
|
+
OpenLoam::EventStream.broadcaster.unsubscribe(handle)
|
|
38
40
|
response.stream.close
|
|
39
|
-
|
|
41
|
+
OpenLoam::Current.reset
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
end
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Feature flags (
|
|
3
|
-
# like Settings. Everything routes through
|
|
2
|
+
# Feature flags (OpenLoam::Features). Structural and privileged, so manager-only,
|
|
3
|
+
# like Settings. Everything routes through OpenLoam::Features; the admin toggles
|
|
4
4
|
# the CURRENT tenant's override, and Reset drops it back to the declared /
|
|
5
5
|
# global state. Global (app-wide) flips are a deploy concern, not this screen.
|
|
6
6
|
class FeaturesController < BaseController
|
|
7
7
|
before_action { require_role!(:manager) }
|
|
8
8
|
|
|
9
9
|
def index
|
|
10
|
-
@flags =
|
|
10
|
+
@flags = OpenLoam::Features.declared.map do |name|
|
|
11
11
|
{
|
|
12
12
|
name: name,
|
|
13
|
-
on:
|
|
14
|
-
overridden:
|
|
15
|
-
description:
|
|
13
|
+
on: OpenLoam::Features.on?(name),
|
|
14
|
+
overridden: OpenLoam::Features.overridden?(name),
|
|
15
|
+
description: OpenLoam::Features.description(name)
|
|
16
16
|
}
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def enable
|
|
21
|
-
|
|
21
|
+
OpenLoam::Features.enable(params[:name])
|
|
22
22
|
redirect_to admin_features_path, notice: "#{params[:name]} enabled for this tenant."
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def disable
|
|
26
|
-
|
|
26
|
+
OpenLoam::Features.disable(params[:name])
|
|
27
27
|
redirect_to admin_features_path, notice: "#{params[:name]} disabled for this tenant."
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def reset
|
|
31
|
-
|
|
31
|
+
OpenLoam::Features.reset(params[:name])
|
|
32
32
|
redirect_to admin_features_path, notice: "#{params[:name]} reset to the default."
|
|
33
33
|
end
|
|
34
34
|
end
|