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
data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Manages
|
|
2
|
+
# Manages OpenLoam::FieldDefinition records: the "migration-free field" screen.
|
|
3
3
|
# Structural, not per-entity — gated on the manager role rather than a
|
|
4
4
|
# generated entity policy, since a field definition applies to a whole
|
|
5
5
|
# entity_type, not one record.
|
|
@@ -9,16 +9,16 @@ module Admin
|
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
11
|
@entity_type = params[:entity_type]
|
|
12
|
-
@records =
|
|
12
|
+
@records = OpenLoam::FieldDefinition.order(:entity_type, :name)
|
|
13
13
|
@records = @records.where(entity_type: @entity_type) if @entity_type.present?
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def new
|
|
17
|
-
@record =
|
|
17
|
+
@record = OpenLoam::FieldDefinition.new(entity_type: params[:entity_type])
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def create
|
|
21
|
-
@record =
|
|
21
|
+
@record = OpenLoam::FieldDefinition.new(permitted_params)
|
|
22
22
|
@record.writable_roles = parse_roles(params.dig(:field_definition, :writable_roles))
|
|
23
23
|
@record.readable_roles = parse_roles(params.dig(:field_definition, :readable_roles))
|
|
24
24
|
|
|
@@ -38,7 +38,7 @@ module Admin
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
40
|
def set_record
|
|
41
|
-
@record =
|
|
41
|
+
@record = OpenLoam::FieldDefinition.find(params[:id])
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def permitted_params
|
data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb
RENAMED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
</p>
|
|
20
20
|
<p>
|
|
21
21
|
<%%= f.label :field_type %><br>
|
|
22
|
-
<%%= f.select :field_type,
|
|
22
|
+
<%%= f.select :field_type, OpenLoam::FieldDefinition::FIELD_TYPES %>
|
|
23
23
|
</p>
|
|
24
24
|
<p>
|
|
25
25
|
<%%= f.label :dictionary_key, "Dictionary key (only for a 'dictionary' field type)" %><br>
|
|
26
|
-
<%%= f.select :dictionary_key,
|
|
26
|
+
<%%= f.select :dictionary_key, OpenLoam::Dictionary.order(:key).pluck(:key), include_blank: true %>
|
|
27
27
|
</p>
|
|
28
28
|
<p>
|
|
29
29
|
<%%= f.label :writable_roles, "Writable by (comma-separated roles, blank = any member)" %><br>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
# A record's change history (OpenLoam::AuditRecord) with one-click Undo (L-704).
|
|
3
|
+
# Undo applies the inverse of a change and records ITSELF as an audit, so
|
|
4
|
+
# undoing an "undo" entry is redo — no separate redo action. Reading needs read
|
|
5
|
+
# on the record; undoing needs update (the same gate as editing it).
|
|
6
|
+
class HistoryController < BaseController
|
|
7
|
+
before_action :set_target
|
|
8
|
+
|
|
9
|
+
helper_method :visible_changeset
|
|
10
|
+
|
|
11
|
+
def index
|
|
12
|
+
authorize!(policy_for(@record), :read?)
|
|
13
|
+
@audits = OpenLoam::AuditRecord
|
|
14
|
+
.where(auditable_type: @record.class.name, auditable_id: @record.id)
|
|
15
|
+
.order(id: :desc).limit(100)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def undo
|
|
19
|
+
authorize!(policy_for(@record), :update?)
|
|
20
|
+
audit = OpenLoam::AuditRecord.find(params[:id])
|
|
21
|
+
OpenLoam::Undo.undo(audit, policy: policy_for(@record))
|
|
22
|
+
redirect_to admin_history_path(type: @record.class.name, record_id: @record.id), notice: "Change undone."
|
|
23
|
+
rescue OpenLoam::Undo::NotUndoableError => error
|
|
24
|
+
redirect_to admin_history_path(type: @record.class.name, record_id: @record.id), alert: error.message
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# History is a read path like any other: a field the role may not read must
|
|
30
|
+
# not come back here as an old → new pair.
|
|
31
|
+
def visible_changeset(audit)
|
|
32
|
+
policy = (@history_policy ||= policy_for(@record))
|
|
33
|
+
|
|
34
|
+
(audit.changeset || {}).each_with_object({}) do |(field, values), visible|
|
|
35
|
+
if field == "custom_fields"
|
|
36
|
+
readable = readable_custom_fields(values, policy)
|
|
37
|
+
visible[field] = readable if readable
|
|
38
|
+
elsif policy.readable?(field)
|
|
39
|
+
visible[field] = values
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Runtime fields are audited as one json blob, so the filter goes inside the
|
|
45
|
+
# old/new pair rather than dropping the whole entry.
|
|
46
|
+
def readable_custom_fields(values, policy)
|
|
47
|
+
return values unless values.is_a?(Array)
|
|
48
|
+
|
|
49
|
+
sides = values.map { |side| side.to_h.select { |name, _| policy.custom_field_readable?(name) } }
|
|
50
|
+
sides.any?(&:present?) ? sides : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The target record, looked up through its OWN default scope (tenancy holds;
|
|
54
|
+
# a soft-deleted row is still reachable to restore). The type is whitelisted
|
|
55
|
+
# to a OpenLoam::TenantRecord — never a bare constantize of a param.
|
|
56
|
+
def set_target
|
|
57
|
+
klass = params[:type].to_s.safe_constantize
|
|
58
|
+
raise OpenLoam::NotAuthorizedError unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
59
|
+
|
|
60
|
+
scope = klass.respond_to?(:with_deleted) ? klass.with_deleted : klass.all
|
|
61
|
+
@record = scope.find(params[:record_id])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -18,13 +18,13 @@ never reverted here.</p>
|
|
|
18
18
|
<td><code><%%= audit.action %></code></td>
|
|
19
19
|
<td><%%= audit.actor&.name || "—" %></td>
|
|
20
20
|
<td>
|
|
21
|
-
<%% (audit
|
|
21
|
+
<%% visible_changeset(audit).each do |field, values| %>
|
|
22
22
|
<div><code><%%= field %></code>:
|
|
23
23
|
<%%= values.is_a?(Array) ? "#{values.first.inspect} → #{values.last.inspect}" : values %></div>
|
|
24
24
|
<%% end %>
|
|
25
25
|
</td>
|
|
26
26
|
<td>
|
|
27
|
-
<%% if
|
|
27
|
+
<%% if OpenLoam::Undo.undoable?(audit) %>
|
|
28
28
|
<%%= button_to "Undo", admin_undo_history_path(id: audit.id, type: @record.class.name, record_id: @record.id),
|
|
29
29
|
form: { style: "display:inline" } %>
|
|
30
30
|
<%% end %>
|
|
@@ -2,7 +2,7 @@ module Admin
|
|
|
2
2
|
# Generic CSV importer, scoped by entity_type — manager-only (a privileged bulk
|
|
3
3
|
# mutation). Flow: new (upload) → preview (map columns) → create (dry-run
|
|
4
4
|
# summary, or a backgrounded run reporting live progress). The mapping only
|
|
5
|
-
# offers fields the manager may write (
|
|
5
|
+
# offers fields the manager may write (OpenLoam::Import.allowed_targets); a crafted
|
|
6
6
|
# target is refused by the engine.
|
|
7
7
|
class ImportsController < BaseController
|
|
8
8
|
before_action { require_role!(:manager) }
|
|
@@ -15,11 +15,11 @@ module Admin
|
|
|
15
15
|
# Parse the uploaded file, show the mapping form + a few sample rows.
|
|
16
16
|
def preview
|
|
17
17
|
@csv = read_upload
|
|
18
|
-
preview =
|
|
18
|
+
preview = OpenLoam::Import.preview(@csv)
|
|
19
19
|
@headers = preview[:headers]
|
|
20
20
|
@sample = preview[:rows]
|
|
21
|
-
@targets =
|
|
22
|
-
rescue
|
|
21
|
+
@targets = OpenLoam::Import.allowed_targets(@model, current_actor).to_a.sort
|
|
22
|
+
rescue OpenLoam::Error => error
|
|
23
23
|
redirect_to new_admin_import_path(entity_type: @entity_type), alert: error.message
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -31,23 +31,23 @@ module Admin
|
|
|
31
31
|
if params[:commit] == "Dry run"
|
|
32
32
|
run_dry(mapping, match_key)
|
|
33
33
|
else
|
|
34
|
-
ImportJob.perform_later(entity_type: @entity_type,
|
|
34
|
+
ImportJob.perform_later(entity_type: @entity_type, blob_id: staged_csv_blob(@csv).id, mapping: mapping,
|
|
35
35
|
match_key: match_key, tenant_id: current_tenant.id, actor_id: current_actor.id)
|
|
36
36
|
redirect_to admin_progress_jobs_path, notice: "Import started — watch it progress under Tasks."
|
|
37
37
|
end
|
|
38
|
-
rescue
|
|
38
|
+
rescue OpenLoam::Error => error
|
|
39
39
|
redirect_to new_admin_import_path(entity_type: @entity_type), alert: error.message
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# The failed rows as a fix-and-re-upload CSV.
|
|
43
43
|
def download_errors
|
|
44
44
|
@entity_type = params[:entity_type]
|
|
45
|
-
model =
|
|
45
|
+
model = OpenLoam::Import.allowed_model(@entity_type)
|
|
46
46
|
csv = params[:csv].to_s
|
|
47
47
|
mapping = params[:mapping].to_unsafe_h.reject { |_h, target| target.blank? }
|
|
48
|
-
result =
|
|
48
|
+
result = OpenLoam::Import.run(csv, model: model, mapping: mapping, actor: current_actor,
|
|
49
49
|
match_key: params[:match_key].presence, dry_run: true)
|
|
50
|
-
send_data
|
|
50
|
+
send_data OpenLoam::Import.error_csv(result, csv),
|
|
51
51
|
filename: "#{@entity_type.underscore}-import-errors.csv", type: "text/csv"
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -55,21 +55,33 @@ module Admin
|
|
|
55
55
|
|
|
56
56
|
def set_model
|
|
57
57
|
@entity_type = params[:entity_type]
|
|
58
|
-
@model =
|
|
58
|
+
@model = OpenLoam::Import.allowed_model(@entity_type)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def run_dry(mapping, match_key)
|
|
62
|
-
@result =
|
|
62
|
+
@result = OpenLoam::Import.run(@csv, model: @model, mapping: mapping, actor: current_actor,
|
|
63
63
|
match_key: match_key, dry_run: true)
|
|
64
|
-
@headers =
|
|
64
|
+
@headers = OpenLoam::Import.preview(@csv)[:headers]
|
|
65
65
|
render :summary
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def read_upload
|
|
69
69
|
file = params[:file]
|
|
70
|
-
raise
|
|
70
|
+
raise OpenLoam::Error, "choose a CSV file to upload" if file.blank?
|
|
71
71
|
|
|
72
72
|
file.read.force_encoding("UTF-8")
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
# The CSV goes to blob storage and the JOB gets an id. ActiveJob arguments
|
|
76
|
+
# are serialized into the queue backend and echoed in the job log, so
|
|
77
|
+
# passing the file itself would leave every row sitting in the clear —
|
|
78
|
+
# including the columns headed for encrypted fields. ImportJob purges it.
|
|
79
|
+
def staged_csv_blob(csv)
|
|
80
|
+
ActiveStorage::Blob.create_and_upload!(
|
|
81
|
+
io: StringIO.new(csv),
|
|
82
|
+
filename: "#{@entity_type.underscore}-import-#{SecureRandom.hex(8)}.csv",
|
|
83
|
+
content_type: "text/csv"
|
|
84
|
+
)
|
|
85
|
+
end
|
|
74
86
|
end
|
|
75
87
|
end
|
data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# Manage the external systems allowed to POST webhooks into this tenant
|
|
3
|
-
# (
|
|
3
|
+
# (OpenLoam::InboundWebhookSource) — manager-only. Create a source (token + secret
|
|
4
4
|
# are generated), copy the receive URL and secret to the sender, rotate either,
|
|
5
5
|
# toggle active, and see the most recent received deliveries.
|
|
6
6
|
class InboundWebhookSourcesController < BaseController
|
|
@@ -8,16 +8,16 @@ module Admin
|
|
|
8
8
|
before_action :set_source, only: %i[destroy rotate_secret rotate_token toggle]
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
|
-
@sources =
|
|
12
|
-
@recent =
|
|
11
|
+
@sources = OpenLoam::InboundWebhookSource.order(:name)
|
|
12
|
+
@recent = OpenLoam::InboundWebhookDelivery.order(received_at: :desc).limit(20)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def new
|
|
16
|
-
@source =
|
|
16
|
+
@source = OpenLoam::InboundWebhookSource.new(event_name: "inbound.webhook.received")
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def create
|
|
20
|
-
@source =
|
|
20
|
+
@source = OpenLoam::InboundWebhookSource.new(source_params)
|
|
21
21
|
if @source.save
|
|
22
22
|
redirect_to admin_inbound_webhook_sources_path, notice: "Source created. Copy the secret now — it signs every call."
|
|
23
23
|
else
|
|
@@ -48,12 +48,12 @@ module Admin
|
|
|
48
48
|
private
|
|
49
49
|
|
|
50
50
|
def set_source
|
|
51
|
-
@source =
|
|
51
|
+
@source = OpenLoam::InboundWebhookSource.find(params[:id])
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def source_params
|
|
55
55
|
params.require(:inbound_webhook_source).permit(
|
|
56
|
-
:name, :event_name, :signature_header,
|
|
56
|
+
:name, :event_name, :signature_header,
|
|
57
57
|
:timestamp_header, :timestamp_tolerance
|
|
58
58
|
)
|
|
59
59
|
end
|
data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb
RENAMED
|
@@ -14,13 +14,11 @@
|
|
|
14
14
|
<%%= f.text_field :event_name %>
|
|
15
15
|
</p>
|
|
16
16
|
<p>
|
|
17
|
-
<%%= f.label :signature_header, "Signature header (default X-
|
|
18
|
-
<%%= f.text_field :signature_header, placeholder: "X-
|
|
17
|
+
<%%= f.label :signature_header, "Signature header (default X-OpenLoam-Signature)" %><br>
|
|
18
|
+
<%%= f.text_field :signature_header, placeholder: "X-OpenLoam-Signature" %>
|
|
19
19
|
<small>e.g. X-Hub-Signature-256 for GitHub.</small>
|
|
20
20
|
</p>
|
|
21
21
|
<p>
|
|
22
|
-
<%%= f.label :delivery_id_header, "Delivery-id header (optional, for dedupe)" %><br>
|
|
23
|
-
<%%= f.text_field :delivery_id_header %>
|
|
24
22
|
<small>Blank = dedupe on a hash of the body.</small>
|
|
25
23
|
</p>
|
|
26
24
|
<p>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Admin</title>
|
|
5
|
+
<%%= csrf_meta_tags %>
|
|
6
|
+
<%%= stylesheet_link_tag "admin", media: "all" %>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<%% flash.each do |type, message| %>
|
|
10
|
+
<div class="flash flash-<%%= type.to_s == "alert" ? "alert" : "notice" %>"><%%= message %></div>
|
|
11
|
+
<%% end %>
|
|
12
|
+
<%% if defined?(current_tenant) && current_tenant %>
|
|
13
|
+
<div class="context">
|
|
14
|
+
<%%= t("open_loam.chrome.tenant") %>: <strong><%%= current_tenant.name %></strong> ·
|
|
15
|
+
<%%= t("open_loam.chrome.user") %>: <strong><%%= current_actor&.name %></strong> ·
|
|
16
|
+
<%%= button_to t("open_loam.chrome.sign_out"), admin_session_path, method: :delete, form: { style: "display:inline" } %>
|
|
17
|
+
</div>
|
|
18
|
+
<nav>
|
|
19
|
+
<%%= link_to t("open_loam.nav.dashboard"), admin_root_path %>
|
|
20
|
+
<%%= link_to admin_notifications_path do %><%%= t("open_loam.nav.notifications") %> (<span id="open-loam-notification-count"><%%= unread_notification_count %></span>)<%% end %>
|
|
21
|
+
<%%= link_to t("open_loam.nav.api_tokens"), admin_api_tokens_path %>
|
|
22
|
+
<%%= link_to t("open_loam.nav.security"), admin_mfa_path %>
|
|
23
|
+
<%%= link_to "#{t('open_loam.nav.approvals')} (#{pending_approval_count})", admin_pending_actions_path %>
|
|
24
|
+
<%%= link_to t("open_loam.nav.settings"), admin_configs_path %>
|
|
25
|
+
<%%= link_to t("open_loam.nav.features"), admin_features_path %>
|
|
26
|
+
<%%= link_to t("open_loam.nav.rules"), admin_business_rules_path %>
|
|
27
|
+
<%%= link_to t("open_loam.nav.sso"), admin_sso_providers_path %>
|
|
28
|
+
<%%= link_to t("open_loam.nav.dictionaries"), admin_dictionaries_path %>
|
|
29
|
+
<%%= link_to t("open_loam.nav.tasks"), admin_progress_jobs_path %>
|
|
30
|
+
<%%= link_to t("open_loam.nav.schedules"), admin_scheduled_jobs_path %>
|
|
31
|
+
<%%= link_to t("open_loam.nav.deliveries"), admin_event_deliveries_path %>
|
|
32
|
+
<%%= link_to t("open_loam.nav.inbound"), admin_inbound_webhook_sources_path %>
|
|
33
|
+
<%%= link_to t("open_loam.nav.api_docs"), admin_api_docs_path %>
|
|
34
|
+
<%%= link_to t("open_loam.nav.overrides"), admin_overrides_path %>
|
|
35
|
+
<%%= form_with url: admin_search_path, method: :get, class: "search" do %>
|
|
36
|
+
<%%= text_field_tag :q, params[:q], placeholder: t("open_loam.chrome.search") %>
|
|
37
|
+
<%% end %>
|
|
38
|
+
<%%# Locale switcher (OpenLoam::Translatable): overlays content onto the picked language. %>
|
|
39
|
+
<%% if OpenLoam.locales.size > 1 %>
|
|
40
|
+
<span class="locale">
|
|
41
|
+
<%% OpenLoam.locales.each do |loc| %><%%= link_to loc.upcase, "#{request.path}?locale=#{loc}", style: (loc == current_locale ? "font-weight:bold" : "") %> <%% end %>
|
|
42
|
+
</span>
|
|
43
|
+
<%% end %>
|
|
44
|
+
</nav>
|
|
45
|
+
|
|
46
|
+
<%%# Live updates over SSE (OpenLoam::EventStream). Degrades gracefully: if
|
|
47
|
+
EventSource is unavailable or the stream drops, the per-page-load bell
|
|
48
|
+
count above still works. %>
|
|
49
|
+
<script>
|
|
50
|
+
(function () {
|
|
51
|
+
if (typeof EventSource === "undefined") return;
|
|
52
|
+
var source = new EventSource("<%%= admin_events_stream_path %>");
|
|
53
|
+
source.addEventListener("open_loam.notification.created", function () {
|
|
54
|
+
var badge = document.getElementById("open-loam-notification-count");
|
|
55
|
+
if (badge) badge.textContent = (parseInt(badge.textContent, 10) || 0) + 1;
|
|
56
|
+
});
|
|
57
|
+
// Live task progress (OpenLoam::ProgressJob): move the matching bar. The
|
|
58
|
+
// frame carries only id/percent/status.
|
|
59
|
+
source.addEventListener("open_loam.progress.updated", function (e) {
|
|
60
|
+
var d = JSON.parse(e.data);
|
|
61
|
+
var fill = document.getElementById("progress-fill-" + d.id);
|
|
62
|
+
if (fill) fill.style.width = d.percent + "%";
|
|
63
|
+
var pct = document.getElementById("progress-percent-" + d.id);
|
|
64
|
+
if (pct) pct.textContent = d.percent + "%";
|
|
65
|
+
var st = document.getElementById("progress-status-" + d.id);
|
|
66
|
+
if (st) st.textContent = d.status;
|
|
67
|
+
});
|
|
68
|
+
source.onerror = function () {
|
|
69
|
+
if (source.readyState === EventSource.CLOSED) source.close();
|
|
70
|
+
};
|
|
71
|
+
})();
|
|
72
|
+
</script>
|
|
73
|
+
<%% end %>
|
|
74
|
+
<%%= yield %>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -6,6 +6,11 @@ module Admin
|
|
|
6
6
|
# old secret stays valid until the new one is proven — so neither a cross-site
|
|
7
7
|
# GET nor an abandoned re-enrollment can downgrade an active credential.
|
|
8
8
|
class MfaController < BaseController
|
|
9
|
+
skip_authorization! "Your own MFA credentials; sensitive changes go through require_sudo! instead."
|
|
10
|
+
|
|
11
|
+
# This IS the enrollment screen the requirement sends people to.
|
|
12
|
+
skip_before_action :require_mfa_enrollment!
|
|
13
|
+
|
|
9
14
|
# Shown in authenticator apps as the account issuer; defaults to the app name.
|
|
10
15
|
ISSUER = Rails.application.class.module_parent_name.freeze
|
|
11
16
|
|
|
@@ -15,7 +20,7 @@ module Admin
|
|
|
15
20
|
before_action :require_sudo!, only: %i[create destroy], if: :active_mfa?
|
|
16
21
|
|
|
17
22
|
def show
|
|
18
|
-
@credential =
|
|
23
|
+
@credential = OpenLoam::MfaCredential.active_for(current_actor)
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
# READ-ONLY: mint a candidate secret into the (encrypted cookie) session and
|
|
@@ -28,7 +33,7 @@ module Admin
|
|
|
28
33
|
# recovery codes once. The active secret is replaced only on success.
|
|
29
34
|
def create
|
|
30
35
|
secret = session[:mfa_enrollment_secret]
|
|
31
|
-
credential =
|
|
36
|
+
credential = OpenLoam::MfaCredential.find_or_initialize_by(user_id: current_actor.id)
|
|
32
37
|
@recovery_codes = secret && credential.activate_with!(secret, params[:code])
|
|
33
38
|
|
|
34
39
|
if @recovery_codes
|
|
@@ -42,7 +47,7 @@ module Admin
|
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def destroy
|
|
45
|
-
|
|
50
|
+
OpenLoam::MfaCredential.where(user_id: current_actor.id).delete_all
|
|
46
51
|
session.delete(:mfa_enrollment_secret)
|
|
47
52
|
redirect_to admin_mfa_path, notice: "Two-factor authentication disabled."
|
|
48
53
|
end
|
|
@@ -50,12 +55,12 @@ module Admin
|
|
|
50
55
|
private
|
|
51
56
|
|
|
52
57
|
def set_enrollment_secret
|
|
53
|
-
@secret = (session[:mfa_enrollment_secret] ||=
|
|
54
|
-
@provisioning_uri =
|
|
58
|
+
@secret = (session[:mfa_enrollment_secret] ||= OpenLoam::Totp.generate_secret)
|
|
59
|
+
@provisioning_uri = OpenLoam::Totp.provisioning_uri(@secret, account: current_actor.email, issuer: ISSUER)
|
|
55
60
|
end
|
|
56
61
|
|
|
57
62
|
def active_mfa?
|
|
58
|
-
|
|
63
|
+
OpenLoam::MfaCredential.active_for(current_actor).present?
|
|
59
64
|
end
|
|
60
65
|
end
|
|
61
66
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# The bell. Every query here is scoped to the current actor, so there is no
|
|
3
3
|
# "read someone else's notifications" path to authorize and no per-record
|
|
4
|
-
# policy to write: tenancy is structural (
|
|
5
|
-
#
|
|
4
|
+
# policy to write: tenancy is structural (OpenLoam::Notification is a
|
|
5
|
+
# OpenLoam::TenantRecord) and BaseController has already established that this
|
|
6
6
|
# actor is signed in to this tenant. Recipient scoping is the whole rule.
|
|
7
7
|
class NotificationsController < BaseController
|
|
8
|
+
skip_authorization! "Scoped to current_actor — there is no path to another user's notifications."
|
|
9
|
+
|
|
8
10
|
def index
|
|
9
11
|
@records = notifications.order(created_at: :desc).limit(100)
|
|
10
12
|
end
|
|
@@ -17,7 +19,7 @@ module Admin
|
|
|
17
19
|
private
|
|
18
20
|
|
|
19
21
|
def notifications
|
|
20
|
-
|
|
22
|
+
OpenLoam::Notification.where(user_id: current_actor.id)
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Read-only view of the app's
|
|
2
|
+
# Read-only view of the app's OpenLoam::Overrides — which registry entries are
|
|
3
3
|
# disabled or replaced, plus any STALE overrides (a key that no longer exists).
|
|
4
4
|
# Manager-only, for legibility: it's config declared in an initializer, shown
|
|
5
5
|
# here so an operator can see what's been customized without reading code.
|
|
@@ -7,8 +7,8 @@ module Admin
|
|
|
7
7
|
before_action { require_role!(:manager) }
|
|
8
8
|
|
|
9
9
|
def index
|
|
10
|
-
@overrides =
|
|
11
|
-
@stale =
|
|
10
|
+
@overrides = OpenLoam::Overrides.all
|
|
11
|
+
@stale = OpenLoam::Overrides.stale
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1>Overrides</h1>
|
|
2
2
|
|
|
3
3
|
<p>Registry entries this app disables or replaces (declared in
|
|
4
|
-
<code>config/initializers/
|
|
4
|
+
<code>config/initializers/open_loam.rb</code> via <code>OpenLoam::Overrides</code>) —
|
|
5
5
|
customization without forking. Structural pieces (views, controllers, routes)
|
|
6
6
|
are overridden the Rails way (path shadowing), not here.</p>
|
|
7
7
|
|
|
@@ -18,7 +18,7 @@ are overridden the Rails way (path shadowing), not here.</p>
|
|
|
18
18
|
<tr>
|
|
19
19
|
<td><code><%%= registry %></code></td>
|
|
20
20
|
<td><code><%%= key %></code></td>
|
|
21
|
-
<td><%%= value ==
|
|
21
|
+
<td><%%= value == OpenLoam::Overrides::DISABLED ? "disabled" : "replaced" %></td>
|
|
22
22
|
</tr>
|
|
23
23
|
<%% end %>
|
|
24
24
|
<%% end %>
|
data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# The approval queue: staged mutations (
|
|
2
|
+
# The approval queue: staged mutations (OpenLoam::PendingAction) waiting on a
|
|
3
3
|
# human. Manager-only — approving EXECUTES the change. The workflow's role gate
|
|
4
4
|
# is the deeper enforcement (approve!/reject! raise for a non-manager); this
|
|
5
5
|
# screen is a manager screen on top of it.
|
|
@@ -8,7 +8,7 @@ module Admin
|
|
|
8
8
|
before_action :set_pending_action, only: %i[approve reject]
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
|
-
@pending =
|
|
11
|
+
@pending = OpenLoam::PendingAction.pending.order(created_at: :desc)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def approve
|
|
@@ -24,7 +24,7 @@ module Admin
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
26
|
def set_pending_action
|
|
27
|
-
@pending_action =
|
|
27
|
+
@pending_action = OpenLoam::PendingAction.find(params[:id])
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def approval_notice(pending)
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Managing saved views (
|
|
2
|
+
# Managing saved views (OpenLoam::Perspective) for an entity. A view is CREATED by
|
|
3
3
|
# "save current view" on the entity index (capturing the live filters/sort); it
|
|
4
4
|
# starts private, owned by you. Here you rename it, widen its audience, make it
|
|
5
5
|
# the default, or delete it.
|
|
6
6
|
#
|
|
7
7
|
# Who may manage a view: its owner always; a manager for role/tenant-visibility
|
|
8
8
|
# ones. A private view of someone else's is invisible here by construction
|
|
9
|
-
# (
|
|
9
|
+
# (OpenLoam::Perspectives.visible_to never returns it).
|
|
10
10
|
class PerspectivesController < BaseController
|
|
11
|
+
# update/destroy/set_default authorize via authorize_manage!. index and create
|
|
12
|
+
# act only on the actor's own private views, so there is no subject to gate.
|
|
13
|
+
skip_authorization! "index lists views visible to the actor; create only ever makes a private one, owned by them.",
|
|
14
|
+
only: %i[index create]
|
|
15
|
+
|
|
11
16
|
before_action :set_perspective, only: %i[update destroy set_default]
|
|
12
17
|
before_action :authorize_manage!, only: %i[update destroy set_default]
|
|
13
18
|
|
|
@@ -18,12 +23,12 @@ module Admin
|
|
|
18
23
|
|
|
19
24
|
def index
|
|
20
25
|
@entity_type = params[:entity_type].to_s
|
|
21
|
-
@perspectives =
|
|
26
|
+
@perspectives = OpenLoam::Perspectives.visible_to(@entity_type, user: current_actor)
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
# From the entity index: save the CURRENT filters/sort as a new private view.
|
|
25
30
|
def create
|
|
26
|
-
perspective =
|
|
31
|
+
perspective = OpenLoam::Perspective.create!(
|
|
27
32
|
entity_type: params[:entity_type].to_s,
|
|
28
33
|
name: params[:name].presence || "Saved view",
|
|
29
34
|
visibility: "private",
|
|
@@ -38,7 +43,7 @@ module Admin
|
|
|
38
43
|
# the audience clears the default flag so it is re-chosen deliberately.
|
|
39
44
|
attributes = perspective_params
|
|
40
45
|
if attributes[:visibility].present? && attributes[:visibility] != @perspective.visibility
|
|
41
|
-
raise
|
|
46
|
+
raise OpenLoam::NotAuthorizedError if attributes[:visibility] != "private" && current_role != :manager
|
|
42
47
|
|
|
43
48
|
attributes[:is_default] = false
|
|
44
49
|
end
|
|
@@ -64,11 +69,12 @@ module Admin
|
|
|
64
69
|
private
|
|
65
70
|
|
|
66
71
|
def set_perspective
|
|
67
|
-
@perspective =
|
|
72
|
+
@perspective = OpenLoam::Perspective.find(params[:id])
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
def authorize_manage!
|
|
71
|
-
|
|
76
|
+
authorized!
|
|
77
|
+
raise OpenLoam::NotAuthorizedError unless can_manage?(@perspective)
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
# Owner always; a manager for shared (role/tenant) views. A private view of
|
|
@@ -24,7 +24,7 @@ or delete it.</p>
|
|
|
24
24
|
<%% if perspective.owner_id == current_actor.id || (perspective.visibility != "private" && current_role == :manager) %>
|
|
25
25
|
<%%= form_with url: admin_perspective_path(perspective, entity_type: @entity_type), method: :patch, class: "inline" do %>
|
|
26
26
|
<%%= text_field_tag :"perspective[name]", perspective.name, size: 16 %>
|
|
27
|
-
<%%= select_tag :"perspective[visibility]", options_for_select(
|
|
27
|
+
<%%= select_tag :"perspective[visibility]", options_for_select(OpenLoam::Perspective::VISIBILITIES, perspective.visibility) %>
|
|
28
28
|
<%%= text_field_tag :"perspective[role]", perspective.role, placeholder: "role", size: 8 %>
|
|
29
29
|
<%%= submit_tag "Save" %>
|
|
30
30
|
<%% end %>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
# Long-running task progress (OpenLoam::ProgressJob) — a tenant-scoped list of
|
|
3
|
+
# recent jobs with a live bar (the open_loam.progress.updated SSE event updates it).
|
|
4
|
+
# Your app starts jobs with OpenLoam::Progress.start in its own background jobs;
|
|
5
|
+
# a running one can be cancelled cooperatively here.
|
|
6
|
+
class ProgressJobsController < BaseController
|
|
7
|
+
skip_authorization! "A tenant-scoped list of the tenant's own jobs; cancel is the gated action.",
|
|
8
|
+
only: :index
|
|
9
|
+
|
|
10
|
+
def index
|
|
11
|
+
@jobs = OpenLoam::ProgressJob.recent.limit(20)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def cancel
|
|
15
|
+
job = OpenLoam::ProgressJob.find(params[:id])
|
|
16
|
+
# Manager-or-owner: a member must not cancel another user's job.
|
|
17
|
+
authorized!
|
|
18
|
+
raise OpenLoam::NotAuthorizedError unless current_role == :manager || job.actor_id == current_actor&.id
|
|
19
|
+
job.cancel! if job.running?
|
|
20
|
+
redirect_to admin_progress_jobs_path, notice: "Job cancelled."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1>Tasks</h1>
|
|
2
2
|
|
|
3
3
|
<p>Long-running jobs report progress here live (no reload — pushed over SSE).
|
|
4
|
-
Start one from your own code with <code>
|
|
4
|
+
Start one from your own code with <code>OpenLoam::Progress.start(name:, total:)</code>.</p>
|
|
5
5
|
|
|
6
6
|
<table>
|
|
7
7
|
<thead>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Manager take-over of an advisory edit lock (
|
|
2
|
+
# Manager take-over of an advisory edit lock (OpenLoam::RecordLocks). Only a
|
|
3
3
|
# manager may force-release someone else's lock; the record it points at is
|
|
4
|
-
# resolved through a whitelist (a
|
|
4
|
+
# resolved through a whitelist (a OpenLoam::TenantRecord subclass), never a raw
|
|
5
5
|
# constantize of user input.
|
|
6
6
|
class RecordLocksController < BaseController
|
|
7
7
|
before_action { require_role!(:manager) }
|
|
8
8
|
|
|
9
9
|
def destroy
|
|
10
10
|
klass = params[:lockable_type].to_s.safe_constantize
|
|
11
|
-
raise
|
|
11
|
+
raise OpenLoam::NotAuthorizedError unless klass && klass < OpenLoam::TenantRecord
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
OpenLoam::RecordLocks.force_release(klass.find(params[:lockable_id]))
|
|
14
14
|
redirect_back fallback_location: admin_root_path, notice: "Lock released — you can edit now."
|
|
15
15
|
end
|
|
16
16
|
end
|