permission_forge 0.14.1 → 0.15.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 +2372 -0
- data/README.md +76 -25
- data/app/controllers/concerns/permission_forge/api/crud_actions.rb +3 -2
- data/app/controllers/concerns/permission_forge/api/input_shapes.rb +200 -0
- data/app/controllers/concerns/permission_forge/api/pagination.rb +12 -5
- data/app/controllers/concerns/permission_forge/api/response_renderer.rb +37 -1
- data/app/controllers/concerns/permission_forge/api_authentication.rb +13 -2
- data/app/controllers/concerns/permission_forge/cache_confirmation.rb +42 -11
- data/app/controllers/concerns/permission_forge/change_review.rb +42 -0
- data/app/controllers/concerns/permission_forge/copy_refusal.rb +39 -0
- data/app/controllers/concerns/permission_forge/param_shapes.rb +207 -7
- data/app/controllers/concerns/permission_forge/save_notice.rb +69 -0
- data/app/controllers/permission_forge/api/audit_events_controller.rb +1 -1
- data/app/controllers/permission_forge/api/base_controller.rb +163 -12
- data/app/controllers/permission_forge/api/dashboard_controller.rb +29 -4
- data/app/controllers/permission_forge/api/permission_set_group_users_controller.rb +5 -2
- data/app/controllers/permission_forge/api/permission_set_groups_controller.rb +5 -4
- data/app/controllers/permission_forge/api/permission_set_rules_controller.rb +7 -5
- data/app/controllers/permission_forge/api/permission_set_users_controller.rb +80 -19
- data/app/controllers/permission_forge/api/permission_sets_controller.rb +12 -6
- data/app/controllers/permission_forge/api/resource_actions_controller.rb +1 -1
- data/app/controllers/permission_forge/api/resource_fields_controller.rb +3 -4
- data/app/controllers/permission_forge/api/resources_controller.rb +26 -12
- data/app/controllers/permission_forge/api/unmatched_routes_controller.rb +20 -0
- data/app/controllers/permission_forge/application_controller.rb +39 -5
- data/app/controllers/permission_forge/assignments_controller.rb +36 -34
- data/app/controllers/permission_forge/bulk_assignments_controller.rb +8 -4
- data/app/controllers/permission_forge/field_lookups_controller.rb +1 -1
- data/app/controllers/permission_forge/maintenance_controller.rb +6 -2
- data/app/controllers/permission_forge/people_controller.rb +14 -14
- data/app/controllers/permission_forge/permission_set_editor.rb +20 -6
- data/app/controllers/permission_forge/permission_set_group_users_controller.rb +3 -1
- data/app/controllers/permission_forge/permission_set_groups_controller.rb +23 -9
- data/app/controllers/permission_forge/permission_set_rules_controller.rb +20 -10
- data/app/controllers/permission_forge/permission_sets_controller.rb +23 -24
- data/app/controllers/permission_forge/resource_actions_controller.rb +11 -6
- data/app/controllers/permission_forge/resource_fields_controller.rb +37 -19
- data/app/controllers/permission_forge/resources_controller.rb +12 -11
- data/app/frontend/permission_forge/pf_admin.css +99 -17
- data/app/frontend/permission_forge/pf_admin.js +36 -0
- data/app/helpers/permission_forge/application_helper.rb +22 -72
- data/app/helpers/permission_forge/audit_attribution_helper.rb +77 -0
- data/app/helpers/permission_forge/audit_helper.rb +57 -119
- data/app/helpers/permission_forge/audit_records_helper.rb +103 -0
- data/app/helpers/permission_forge/audit_sentences_helper.rb +56 -2
- data/app/helpers/permission_forge/badges_helper.rb +74 -17
- data/app/helpers/permission_forge/changes_helper.rb +24 -6
- data/app/helpers/permission_forge/conditions_helper.rb +119 -0
- data/app/helpers/permission_forge/labels_helper.rb +134 -0
- data/app/helpers/permission_forge/people_helper.rb +11 -19
- data/app/helpers/permission_forge/rule_problems_helper.rb +73 -0
- data/app/helpers/permission_forge/sentences_helper.rb +119 -98
- data/app/helpers/permission_forge/settings_helper.rb +2 -2
- data/app/helpers/permission_forge/ui_helper.rb +65 -4
- data/app/models/permission_forge/audit_event.rb +5 -1
- data/app/models/permission_forge/permission_set.rb +208 -18
- data/app/models/permission_forge/permission_set_group.rb +10 -4
- data/app/models/permission_forge/permission_set_group_user.rb +3 -1
- data/app/models/permission_forge/permission_set_rule.rb +254 -17
- data/app/models/permission_forge/permission_set_user.rb +38 -3
- data/app/models/permission_forge/resource.rb +18 -8
- data/app/models/permission_forge/resource_action.rb +15 -6
- data/app/models/permission_forge/resource_field.rb +97 -11
- data/app/views/permission_forge/assignments/confirm_destroy.html.erb +4 -4
- data/app/views/permission_forge/assignments/new.html.erb +10 -6
- data/app/views/permission_forge/audit_events/index.html.erb +1 -1
- data/app/views/permission_forge/audit_events/show.html.erb +16 -9
- data/app/views/permission_forge/bulk_assignments/confirm_revoke.html.erb +3 -3
- data/app/views/permission_forge/bulk_assignments/new.html.erb +3 -3
- data/app/views/permission_forge/field_lookups/show.html.erb +2 -2
- data/app/views/permission_forge/home/_broken_rules_card.html.erb +35 -0
- data/app/views/permission_forge/home/_conditions_card.html.erb +28 -0
- data/app/views/permission_forge/home/_ending_card.html.erb +11 -5
- data/app/views/permission_forge/home/_expired_card.html.erb +1 -1
- data/app/views/permission_forge/home/_mask_strategies_card.html.erb +27 -0
- data/app/views/permission_forge/home/_privacy_card.html.erb +21 -7
- data/app/views/permission_forge/home/_rules_card.html.erb +1 -1
- data/app/views/permission_forge/home/_secret_rules_card.html.erb +33 -0
- data/app/views/permission_forge/home/_stale_card.html.erb +7 -5
- data/app/views/permission_forge/home/_tidy_card.html.erb +1 -1
- data/app/views/permission_forge/home/index.html.erb +5 -6
- data/app/views/permission_forge/people/_access_tab.html.erb +26 -19
- data/app/views/permission_forge/people/_audit_tab.html.erb +8 -8
- data/app/views/permission_forge/people/_effective_field_source.html.erb +13 -7
- data/app/views/permission_forge/people/_effective_tab.html.erb +16 -15
- data/app/views/permission_forge/people/_effective_why.html.erb +20 -16
- data/app/views/permission_forge/people/_why_item.html.erb +1 -1
- data/app/views/permission_forge/people/confirm_remove_all.html.erb +3 -3
- data/app/views/permission_forge/people/erasure_preview.html.erb +1 -1
- data/app/views/permission_forge/permission_set_groups/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/permission_set_groups/index.html.erb +3 -3
- data/app/views/permission_forge/permission_set_groups/show.html.erb +28 -19
- data/app/views/permission_forge/permission_set_rules/_builder.html.erb +35 -15
- data/app/views/permission_forge/permission_set_rules/_preview.html.erb +17 -0
- data/app/views/permission_forge/permission_set_rules/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/permission_set_rules/preview.html.erb +12 -0
- data/app/views/permission_forge/permission_sets/_assignments_tab.html.erb +37 -22
- data/app/views/permission_forge/permission_sets/_audit_tab.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/_condition_problems.html.erb +15 -0
- data/app/views/permission_forge/permission_sets/_conditions_tab.html.erb +14 -8
- data/app/views/permission_forge/permission_sets/_header.html.erb +6 -5
- data/app/views/permission_forge/permission_sets/_records_summary.html.erb +2 -2
- data/app/views/permission_forge/permission_sets/_resources_tab.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/confirm_destroy.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/confirm_switch_mode.html.erb +3 -3
- data/app/views/permission_forge/permission_sets/index.html.erb +6 -6
- data/app/views/permission_forge/permission_sets/new.html.erb +2 -2
- data/app/views/permission_forge/permission_sets/review.html.erb +13 -3
- data/app/views/permission_forge/permission_sets/show.html.erb +7 -5
- data/app/views/permission_forge/resource_actions/_across_sets.html.erb +7 -10
- data/app/views/permission_forge/resource_actions/_effect.html.erb +5 -5
- data/app/views/permission_forge/resource_actions/_form.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/_managed_note.html.erb +5 -3
- data/app/views/permission_forge/resource_actions/confirm_destroy.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/new.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/show.html.erb +2 -2
- data/app/views/permission_forge/resource_fields/_explain.html.erb +19 -15
- data/app/views/permission_forge/resource_fields/show.html.erb +21 -12
- data/app/views/permission_forge/resources/_actions_tab.html.erb +4 -4
- data/app/views/permission_forge/resources/_fields_tab.html.erb +17 -9
- data/app/views/permission_forge/resources/_rules_tab.html.erb +9 -6
- data/app/views/permission_forge/resources/_sync.html.erb +8 -5
- data/app/views/permission_forge/resources/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/resources/new.html.erb +3 -2
- data/app/views/permission_forge/resources/show.html.erb +6 -6
- data/app/views/permission_forge/settings/_row.html.erb +2 -2
- data/app/views/permission_forge/settings/show.html.erb +10 -1
- data/app/views/permission_forge/shared/_across_sets.html.erb +8 -9
- data/app/views/permission_forge/shared/_audit_events.html.erb +2 -2
- data/app/views/permission_forge/shared/_audit_footer.html.erb +13 -2
- data/app/views/permission_forge/shared/_crud_cell.html.erb +6 -6
- data/app/views/permission_forge/shared/_mode_banners.html.erb +1 -1
- data/app/views/permission_forge/shared/_savebar.html.erb +4 -2
- data/config/locales/permission_forge.en.yml +480 -69
- data/config/locales/permission_forge.mk.yml +499 -79
- data/config/locales/permission_forge.sq.yml +509 -80
- data/config/routes.rb +38 -23
- data/lib/generators/permission_forge/install/install_generator.rb +21 -0
- data/lib/generators/permission_forge/install/templates/initializer.rb.tt +35 -34
- data/lib/generators/permission_forge/install/templates/migration.rb.tt +5 -5
- data/lib/permission_forge/activation_conditions.rb +200 -0
- data/lib/permission_forge/admin/action_label.rb +33 -0
- data/lib/permission_forge/admin/action_rows.rb +3 -1
- data/lib/permission_forge/admin/actor_label.rb +52 -0
- data/lib/permission_forge/admin/attention_items.rb +152 -31
- data/lib/permission_forge/admin/audit_log.rb +4 -7
- data/lib/permission_forge/admin/audit_trail.rb +33 -17
- data/lib/permission_forge/admin/bulk_people_matcher.rb +32 -14
- data/lib/permission_forge/admin/chain_entries.rb +42 -0
- data/lib/permission_forge/admin/change_set.rb +12 -1
- data/lib/permission_forge/admin/change_set_parts/base.rb +5 -4
- data/lib/permission_forge/admin/change_set_parts/context.rb +3 -1
- data/lib/permission_forge/admin/change_set_parts/field_edit.rb +4 -1
- data/lib/permission_forge/admin/change_set_parts/field_levels.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/new_actions.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/new_fields.rb +38 -11
- data/lib/permission_forge/admin/change_set_parts/resource_edit.rb +17 -1
- data/lib/permission_forge/admin/change_set_parts/resource_flags.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/set_attrs.rb +4 -0
- data/lib/permission_forge/admin/change_set_parts/values.rb +10 -4
- data/lib/permission_forge/admin/column_fields.rb +22 -21
- data/lib/permission_forge/admin/conditions_form.rb +47 -15
- data/lib/permission_forge/admin/controller_catalog.rb +42 -14
- data/lib/permission_forge/admin/editable_lock.rb +83 -3
- data/lib/permission_forge/admin/effective_access.rb +147 -48
- data/lib/permission_forge/admin/effective_fields.rb +75 -67
- data/lib/permission_forge/admin/error_sentence.rb +47 -0
- data/lib/permission_forge/admin/expiry_presets.rb +17 -0
- data/lib/permission_forge/admin/exports/holdings.rb +6 -3
- data/lib/permission_forge/admin/field_across_sets.rb +7 -4
- data/lib/permission_forge/admin/field_chain.rb +109 -0
- data/lib/permission_forge/admin/field_detail.rb +24 -11
- data/lib/permission_forge/admin/field_label.rb +109 -0
- data/lib/permission_forge/admin/field_level.rb +4 -68
- data/lib/permission_forge/admin/field_provenance.rb +132 -0
- data/lib/permission_forge/admin/field_rows.rb +26 -11
- data/lib/permission_forge/admin/last_changes.rb +14 -0
- data/lib/permission_forge/admin/list_sentence.rb +32 -0
- data/lib/permission_forge/admin/mask_strategy_choices.rb +57 -0
- data/lib/permission_forge/admin/model_catalog.rb +83 -39
- data/lib/permission_forge/admin/model_label.rb +66 -0
- data/lib/permission_forge/admin/people_candidates.rb +21 -9
- data/lib/permission_forge/admin/people_directory.rb +23 -5
- data/lib/permission_forge/admin/permission_set_factory.rb +6 -15
- data/lib/permission_forge/admin/person_label.rb +105 -16
- data/lib/permission_forge/admin/prefill.rb +92 -0
- data/lib/permission_forge/admin/record_name.rb +45 -0
- data/lib/permission_forge/admin/rule_builder.rb +77 -3
- data/lib/permission_forge/admin/rule_form.rb +44 -10
- data/lib/permission_forge/admin/rule_form_input.rb +27 -11
- data/lib/permission_forge/admin/rule_notes.rb +43 -0
- data/lib/permission_forge/admin/rule_preview.rb +96 -0
- data/lib/permission_forge/admin/sensitive_columns.rb +10 -62
- data/lib/permission_forge/admin/settings_catalog.rb +64 -12
- data/lib/permission_forge/admin/state_label.rb +97 -0
- data/lib/permission_forge/admin_method_override.rb +44 -0
- data/lib/permission_forge/api/schema.rb +322 -0
- data/lib/permission_forge/api/serializers/base_serializer.rb +87 -1
- data/lib/permission_forge/api/serializers/permission_set_group_user_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/permission_set_rule_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/permission_set_serializer.rb +5 -2
- data/lib/permission_forge/api/serializers/permission_set_user_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_action_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_field_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_serializer.rb +1 -1
- data/lib/permission_forge/api_authorizations.rb +59 -0
- data/lib/permission_forge/api_only_admin.rb +81 -0
- data/lib/permission_forge/audit/actor_codes.rb +32 -0
- data/lib/permission_forge/audit/actor_resolver.rb +1 -2
- data/lib/permission_forge/audit/boot_warnings.rb +29 -9
- data/lib/permission_forge/audit/event_catalog.rb +16 -6
- data/lib/permission_forge/audit/event_query.rb +3 -3
- data/lib/permission_forge/audit/reason_codes.rb +25 -0
- data/lib/permission_forge/audit/runtime.rb +9 -2
- data/lib/permission_forge/audit/settings_observer.rb +2 -2
- data/lib/permission_forge/audit/unmasked_without_user.rb +169 -0
- data/lib/permission_forge/audit.rb +24 -10
- data/lib/permission_forge/boot_checks.rb +180 -63
- data/lib/permission_forge/cache/base.rb +10 -0
- data/lib/permission_forge/cache/null_cache.rb +5 -0
- data/lib/permission_forge/cache/outcome.rb +119 -0
- data/lib/permission_forge/cache/rails_cache.rb +6 -0
- data/lib/permission_forge/cache/report.rb +150 -0
- data/lib/permission_forge/cache/user_keys.rb +8 -4
- data/lib/permission_forge/cache_warnings.rb +140 -0
- data/lib/permission_forge/concerns/auto_permission_scoping.rb +19 -2
- data/lib/permission_forge/concerns/has_tenant_scoping.rb +1 -1
- data/lib/permission_forge/concerns/has_uuid.rb +1 -1
- data/lib/permission_forge/concerns/keeps_copied_values.rb +48 -0
- data/lib/permission_forge/concerns/masked_attributes/alias_access.rb +12 -2
- data/lib/permission_forge/concerns/masked_attributes/virtual_readers.rb +6 -4
- data/lib/permission_forge/concerns/masked_attributes.rb +150 -34
- data/lib/permission_forge/concerns/normalizes_label.rb +78 -0
- data/lib/permission_forge/concerns/permission_cache_accessor.rb +3 -3
- data/lib/permission_forge/concerns/permission_scoped_findable.rb +121 -29
- data/lib/permission_forge/concerns/permissioned_user.rb +1 -1
- data/lib/permission_forge/concerns/records_audit_events.rb +13 -6
- data/lib/permission_forge/concerns/requires_name.rb +37 -0
- data/lib/permission_forge/concerns/validates_column_lengths.rb +24 -0
- data/lib/permission_forge/concerns/validates_dependency.rb +5 -2
- data/lib/permission_forge/concerns/validates_required_columns.rb +74 -0
- data/lib/permission_forge/conditional_sets.rb +13 -6
- data/lib/permission_forge/configuration/admin_ui_settings.rb +18 -4
- data/lib/permission_forge/configuration/audit_settings.rb +2 -1
- data/lib/permission_forge/configuration/controller_action_settings.rb +5 -4
- data/lib/permission_forge/configuration/masked_attributes_settings.rb +73 -19
- data/lib/permission_forge/configuration/presets.rb +20 -11
- data/lib/permission_forge/configuration/query_scoping_settings.rb +55 -0
- data/lib/permission_forge/configuration/strategy_values.rb +2 -1
- data/lib/permission_forge/configuration.rb +41 -18
- data/lib/permission_forge/controller_actions/managed_index.rb +8 -7
- data/lib/permission_forge/database_reach.rb +93 -0
- data/lib/permission_forge/deprecation.rb +1 -1
- data/lib/permission_forge/docs/api_samples.rb +250 -0
- data/lib/permission_forge/docs/configuration_reference.rb +172 -0
- data/lib/permission_forge/encrypted_attributes.rb +150 -0
- data/lib/permission_forge/engine.rb +46 -14
- data/lib/permission_forge/execution_state/restore.rb +74 -0
- data/lib/permission_forge/execution_state.rb +8 -1
- data/lib/permission_forge/field_level.rb +123 -0
- data/lib/permission_forge/field_names.rb +145 -0
- data/lib/permission_forge/field_variants.rb +96 -0
- data/lib/permission_forge/generated_text.rb +95 -0
- data/lib/permission_forge/inspection_filters.rb +83 -0
- data/lib/permission_forge/managed_fields.rb +66 -0
- data/lib/permission_forge/masked_models.rb +25 -19
- data/lib/permission_forge/masking_sync.rb +100 -25
- data/lib/permission_forge/model_bindings.rb +30 -19
- data/lib/permission_forge/model_lookup.rb +42 -0
- data/lib/permission_forge/operators/encrypted_alias.rb +67 -0
- data/lib/permission_forge/operators/registry.rb +6 -0
- data/lib/permission_forge/operators/rule_column.rb +242 -27
- data/lib/permission_forge/operators/rule_path.rb +179 -0
- data/lib/permission_forge/operators/rule_problem.rb +309 -0
- data/lib/permission_forge/operators/rule_sources.rb +96 -0
- data/lib/permission_forge/operators/value_casters/postgresql_types.rb +237 -0
- data/lib/permission_forge/operators/value_casters/temporal.rb +9 -20
- data/lib/permission_forge/operators/value_casters.rb +61 -13
- data/lib/permission_forge/operators/value_typing.rb +83 -7
- data/lib/permission_forge/query_scoping_warnings.rb +22 -17
- data/lib/permission_forge/rake_audit.rb +5 -4
- data/lib/permission_forge/rake_dry_run.rb +32 -0
- data/lib/permission_forge/rake_output.rb +189 -0
- data/lib/permission_forge/record_read_access.rb +51 -0
- data/lib/permission_forge/resource_chain.rb +14 -2
- data/lib/permission_forge/schema_read/ordered_types.rb +72 -0
- data/lib/permission_forge/schema_read.rb +335 -0
- data/lib/permission_forge/secret_columns.rb +49 -0
- data/lib/permission_forge/sensitive_columns.rb +114 -0
- data/lib/permission_forge/services/attribute_access_guard.rb +22 -14
- data/lib/permission_forge/services/bulk_permission_set_assignment.rb +16 -8
- data/lib/permission_forge/services/concerns/clonable.rb +9 -0
- data/lib/permission_forge/services/concerns/settles_cache.rb +58 -0
- data/lib/permission_forge/services/consent_withdrawal_handler.rb +37 -19
- data/lib/permission_forge/services/data_subject_erasure.rb +83 -45
- data/lib/permission_forge/services/expired_assignment_cleaner.rb +62 -25
- data/lib/permission_forge/services/extra_field_defaults.rb +1 -1
- data/lib/permission_forge/services/field_permission_merger.rb +44 -17
- data/lib/permission_forge/services/filters_response_attributes.rb +20 -5
- data/lib/permission_forge/services/gdpr_field_auditor/text_report.rb +33 -5
- data/lib/permission_forge/services/gdpr_field_auditor.rb +68 -48
- data/lib/permission_forge/services/host_key_guard.rb +8 -5
- data/lib/permission_forge/services/mask_value.rb +72 -11
- data/lib/permission_forge/services/permission_cache_clearer.rb +36 -15
- data/lib/permission_forge/services/permission_hash_builder.rb +48 -19
- data/lib/permission_forge/services/permission_rule_applier.rb +131 -68
- data/lib/permission_forge/services/permission_set_cloner.rb +32 -8
- data/lib/permission_forge/services/permission_set_condition_evaluator.rb +38 -36
- data/lib/permission_forge/services/permission_set_group_cloner.rb +2 -1
- data/lib/permission_forge/services/permits_attributes/result.rb +31 -0
- data/lib/permission_forge/services/permits_attributes.rb +48 -27
- data/lib/permission_forge/services/resource_field_generator/row_builder.rb +96 -0
- data/lib/permission_forge/services/resource_field_generator.rb +71 -42
- data/lib/permission_forge/services/resource_with_fields_builder.rb +2 -1
- data/lib/permission_forge/services/rls_operator_sql.rb +83 -24
- data/lib/permission_forge/services/rls_policy_generator.rb +83 -39
- data/lib/permission_forge/services/rls_read_access_sql.rb +131 -0
- data/lib/permission_forge/services/security_audit/action_check.rb +49 -5
- data/lib/permission_forge/services/security_audit/assignment_check.rb +2 -1
- data/lib/permission_forge/services/security_audit/audit_trail_check.rb +1 -3
- data/lib/permission_forge/services/security_audit/conditions_check.rb +92 -0
- data/lib/permission_forge/services/security_audit/config_check.rb +41 -18
- data/lib/permission_forge/services/security_audit/integrity_check.rb +71 -4
- data/lib/permission_forge/services/security_audit/masking_without_user.rb +66 -0
- data/lib/permission_forge/services/security_audit/personal_data_check.rb +7 -11
- data/lib/permission_forge/services/security_audit/rule_check.rb +180 -50
- data/lib/permission_forge/services/security_audit/text_report.rb +14 -2
- data/lib/permission_forge/services/security_audit/unmanaged_actions.rb +145 -0
- data/lib/permission_forge/services/security_audit/user_model_check.rb +4 -3
- data/lib/permission_forge/services/security_audit.rb +16 -4
- data/lib/permission_forge/services/stale_assignment_auditor.rb +8 -5
- data/lib/permission_forge/timestamp_fields.rb +60 -0
- data/lib/permission_forge/unique_conflict.rb +117 -0
- data/lib/permission_forge/version.rb +1 -1
- data/lib/permission_forge/virtual_fields/discovery.rb +2 -1
- data/lib/permission_forge/virtual_fields.rb +15 -5
- data/lib/permission_forge/warnings.rb +263 -0
- data/lib/permission_forge/web_request.rb +248 -0
- data/lib/permission_forge.rb +54 -0
- data/lib/tasks/permission_forge.rake +26 -6
- data/lib/tasks/permission_forge_audit_events.rake +8 -4
- data/lib/tasks/permission_forge_cache.rake +12 -12
- data/lib/tasks/permission_forge_docs.rake +48 -0
- data/lib/tasks/permission_forge_expiration.rake +11 -5
- data/lib/tasks/permission_forge_gdpr.rake +36 -20
- metadata +84 -1
|
@@ -1,138 +1,153 @@
|
|
|
1
1
|
module PermissionForge
|
|
2
|
-
# Plain-language sentences for
|
|
3
|
-
#
|
|
4
|
-
# descriptions: ChangesHelper).
|
|
2
|
+
# Plain-language sentences for record rules and resources (activation
|
|
3
|
+
# conditions: ConditionsHelper; audit events: AuditSentencesHelper;
|
|
4
|
+
# "Review and save" change descriptions: ChangesHelper).
|
|
5
5
|
#
|
|
6
6
|
# @api private
|
|
7
7
|
module SentencesHelper
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return if
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
8
|
+
# Resource flags a Muting Permission Set can take away, in the order the page shows them.
|
|
9
|
+
MUTED_FLAGS = %i[read_access create_access edit_access delete_access].freeze
|
|
10
|
+
|
|
11
|
+
# --- resources ------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
# The toast after "Add resource": how many fields it got. In a Muting
|
|
14
|
+
# Permission Set "with 0 fields" reads like a gap, so there it says what
|
|
15
|
+
# the resource takes away, or that it takes nothing away yet.
|
|
16
|
+
def pf_added_resource_notice(resource, muting:)
|
|
17
|
+
label = pf_resource_label(resource)
|
|
18
|
+
count = resource.resource_fields.size
|
|
19
|
+
return t('permission_forge.flash.resources.added', resource: label, count: count) unless muting
|
|
20
|
+
|
|
21
|
+
taken = pf_muted_parts(resource, count)
|
|
22
|
+
return t('permission_forge.flash.resources.added_muting_nothing', resource: label) if taken.empty?
|
|
23
|
+
|
|
24
|
+
t('permission_forge.flash.resources.added_muting', resource: label, what: pf_to_sentence(taken))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# What a muting resource takes away: its muted flags, then its field rows.
|
|
28
|
+
def pf_muted_parts(resource, field_count)
|
|
29
|
+
parts = MUTED_FLAGS.select { |flag| resource.public_send(flag) }.map { |flag| pf_ui("flags_short.#{flag}") }
|
|
30
|
+
parts << t('permission_forge.flash.resources.muted_fields', count: field_count) if field_count.positive?
|
|
31
|
+
parts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# --- field levels ---------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
# What one Permission Set does to a field, by its level (an
|
|
37
|
+
# Admin::EffectiveAccess::FieldSource): "Sales rep shows this field masked (Email)."
|
|
38
|
+
# `set_name`: how the set is named (LabelsHelper#pf_chain_set_name on a
|
|
39
|
+
# person's effective access); the set's name by default.
|
|
40
|
+
def pf_field_source_sentence(source, set_name: pf_record_name(source.permission_set))
|
|
41
|
+
strategy = pf_mask_strategy_label(source.mask_strategy)
|
|
42
|
+
pf_t_markup(set: set_name) { |held| pf_ui("level_sentences.#{source.level}", **held, strategy: strategy) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A person's Effective access row for a field a Muting Permission Set
|
|
46
|
+
# decides (FieldOutcome#reason :muted): the level the deciding mutes
|
|
47
|
+
# give (FieldOutcome#deciding_mutes: hidden, masked or read-only) and
|
|
48
|
+
# their sets, each named with the Resource whose row mutes
|
|
49
|
+
# (#pf_field_source_set_name; B#38).
|
|
50
|
+
def pf_field_muted_by(field, leaf_name)
|
|
51
|
+
pf_t_markup(set: pf_deciding_mute_names(field, leaf_name)) do |held|
|
|
52
|
+
case field.blocked_level
|
|
53
|
+
when 'mask' then t('permission_forge.people.effective_field_source.masked_by', **held)
|
|
54
|
+
when 'no_edit' then t('permission_forge.people.effective_field_source.read_only_by', **held)
|
|
55
|
+
else t('permission_forge.people.effective_field_source.muted_by', **held)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
51
58
|
end
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
# The field drawer's "Explain for" for the same decision: the level and
|
|
61
|
+
# the same sets as the row (#pf_field_muted_by), counted for the verb.
|
|
62
|
+
def pf_field_muted_explain(field, leaf_name, person_name)
|
|
63
|
+
count = field.deciding_mutes.size
|
|
64
|
+
pf_t_markup(sets: pf_deciding_mute_names(field, leaf_name)) do |held|
|
|
65
|
+
case field.blocked_level
|
|
66
|
+
when 'mask' then t('permission_forge.resource_fields.explain.masked_by', name: person_name, count: count, **held)
|
|
67
|
+
when 'no_edit' then t('permission_forge.resource_fields.explain.read_only_by', name: person_name, count: count, **held)
|
|
68
|
+
else t('permission_forge.resource_fields.explain.hidden_by', name: person_name, count: count, **held)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
57
71
|
end
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
# The sets of FieldOutcome#deciding_mutes as one list, each named with its Resource.
|
|
74
|
+
def pf_deciding_mute_names(field, leaf_name)
|
|
75
|
+
pf_markup_sentence(field.deciding_mutes.map { |source| pf_field_source_set_name(source, leaf_name) })
|
|
62
76
|
end
|
|
63
77
|
|
|
64
78
|
# --- record rules ---------------------------------------------------------
|
|
65
79
|
|
|
66
80
|
def pf_operator_label(operator)
|
|
67
|
-
t("permission_forge.ui.operators.#{operator}", default: operator.to_s
|
|
81
|
+
t("permission_forge.ui.operators.#{operator}", default: operator.to_s)
|
|
68
82
|
end
|
|
69
83
|
|
|
70
84
|
# Labels along a rule's relationship path and the final field, e.g.
|
|
71
|
-
# ["Organization", "Members", "User"].
|
|
85
|
+
# ["Organization", "Members", "User"]. The path is walked by
|
|
86
|
+
# Operators::RulePath (the one walk of a path); one it cannot follow is
|
|
87
|
+
# labelled from its names (a rule that matches nothing is drawn by
|
|
88
|
+
# RuleProblemsHelper first).
|
|
72
89
|
def pf_rule_field_labels(model_name, rule)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
label
|
|
78
|
-
end
|
|
79
|
-
labels << pf_attribute_label(klass, rule.field_name)
|
|
90
|
+
segments = PermissionForge::Operators::RuleColumn.path_segments(rule.field_accessor_path)
|
|
91
|
+
models = pf_rule_path_models(PermissionForge::ModelLookup.model_for(model_name), segments)
|
|
92
|
+
labels = segments.each_with_index.map { |assoc, index| pf_attribute_label(models[index], assoc) }
|
|
93
|
+
labels << pf_attribute_label(models[segments.size], rule.field_name)
|
|
80
94
|
end
|
|
81
95
|
|
|
96
|
+
# The label of a stored attribute or association name (Admin::FieldLabel),
|
|
97
|
+
# as text: the host's translation, else the name as stored.
|
|
82
98
|
def pf_attribute_label(klass, attribute)
|
|
83
|
-
klass
|
|
99
|
+
pf_field_label_text(klass, attribute)
|
|
84
100
|
end
|
|
85
101
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
# The model, then the model of each association on the path; the model
|
|
103
|
+
# alone when the path cannot be followed (or nothing, without a model).
|
|
104
|
+
def pf_rule_path_models(klass, segments)
|
|
105
|
+
return [] unless klass.is_a?(Class) && klass < ActiveRecord::Base
|
|
106
|
+
|
|
107
|
+
PermissionForge::Operators::RulePath.follow(klass, segments).models.presence || [klass]
|
|
108
|
+
rescue *PermissionForge::Operators::RuleColumn::UNTELLABLE
|
|
109
|
+
[klass]
|
|
94
110
|
end
|
|
95
111
|
|
|
112
|
+
# The value as the rule compares it: "the current user" only where
|
|
113
|
+
# ValueTyping substitutes the user's id (one value compared:
|
|
114
|
+
# ValueTyping.current_user?); in a list the word is an element as written.
|
|
96
115
|
def pf_rule_value_text(rule)
|
|
97
116
|
value = rule.field_value.to_s
|
|
98
117
|
return '…' if value.strip.empty? # builder preview before a value is entered
|
|
99
|
-
return pf_ui('rules.current_user') if value
|
|
118
|
+
return pf_ui('rules.current_user') if PermissionForge::Operators::ValueTyping.current_user?(rule.operator, value)
|
|
100
119
|
|
|
101
120
|
pf_rule_value_parts(rule.operator, value)
|
|
102
121
|
end
|
|
103
122
|
|
|
104
|
-
#
|
|
123
|
+
# A list's values as "a, b"; a range's ends as "a and b" — split as
|
|
124
|
+
# ValueTyping splits them (its SHAPES: :list, :pair); anything else verbatim.
|
|
105
125
|
def pf_rule_value_parts(operator, value)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
parts = PermissionForge::Operators::ValueTyping.split(value)
|
|
127
|
+
case PermissionForge::Operators::ValueTyping::SHAPES[operator]
|
|
128
|
+
when :list then parts.join(', ')
|
|
129
|
+
when :pair then parts.join(" #{pf_ui('rules.and')} ")
|
|
130
|
+
else value
|
|
131
|
+
end
|
|
110
132
|
end
|
|
111
133
|
|
|
134
|
+
# Whether the sentence adds "or … is empty": the rule includes the NULL
|
|
135
|
+
# rows and its operator's strategy adds them (Operators::Registry::NULL_INCLUDING).
|
|
112
136
|
def pf_rule_nullable?(rule)
|
|
113
|
-
rule.include_nulls &&
|
|
137
|
+
rule.include_nulls && PermissionForge::Operators::Registry::NULL_INCLUDING.include?(rule.operator)
|
|
114
138
|
end
|
|
115
139
|
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
klass = name.to_s.safe_constantize
|
|
120
|
-
return name.to_s.demodulize.pluralize unless klass.respond_to?(:model_name)
|
|
121
|
-
|
|
122
|
-
plural = klass.model_name.human(count: 2)
|
|
123
|
-
plural == klass.model_name.human && I18n.locale.to_s == 'en' ? plural.pluralize : plural
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def pf_resource_label(resource_or_name)
|
|
127
|
-
name = resource_or_name.respond_to?(:name) ? resource_or_name.name : resource_or_name.to_s
|
|
128
|
-
return resource_or_name.label if resource_or_name.respond_to?(:label) && resource_or_name.label.present?
|
|
129
|
-
|
|
130
|
-
klass = name.safe_constantize
|
|
131
|
-
klass.respond_to?(:model_name) ? klass.model_name.human : name.demodulize.titleize
|
|
140
|
+
# Whether the operator takes no value (Operators::Validations::VALUELESS: the null checks).
|
|
141
|
+
def pf_rule_valueless?(rule)
|
|
142
|
+
PermissionForge::Operators::Validations.valueless?(rule.operator)
|
|
132
143
|
end
|
|
133
144
|
|
|
134
|
-
# Token sentence: Only show [Customers] where [Owner] [is] [the current user], or Owner is empty
|
|
145
|
+
# Token sentence: Only show [Customers] where [Owner] [is] [the current user], or Owner is empty.
|
|
146
|
+
# A rule that matches nothing says so, and what it names as written.
|
|
135
147
|
def pf_rule_sentence(rule, model_name = rule.resource&.name)
|
|
148
|
+
problem = pf_rule_problem(rule)
|
|
149
|
+
return pf_rule_problem_sentence(rule, problem) if problem
|
|
150
|
+
|
|
136
151
|
field = pf_rule_field_labels(model_name, rule).join(' › ')
|
|
137
152
|
sentence = safe_join(pf_rule_tokens(rule, model_name, field), ' ')
|
|
138
153
|
sentence = safe_join([sentence, content_tag(:span, ", #{pf_ui('rules.or_empty', field: field)}")]) if pf_rule_nullable?(rule)
|
|
@@ -151,24 +166,30 @@ module PermissionForge
|
|
|
151
166
|
end
|
|
152
167
|
|
|
153
168
|
def pf_rule_value_token(rule)
|
|
154
|
-
return if
|
|
169
|
+
return if pf_rule_valueless?(rule)
|
|
155
170
|
|
|
156
171
|
content_tag(:span, pf_rule_value_text(rule), class: 'pf-token pf-token--value')
|
|
157
172
|
end
|
|
158
173
|
|
|
159
174
|
def pf_rule_text(rule, model_name = rule.resource&.name)
|
|
175
|
+
problem = pf_rule_problem(rule)
|
|
176
|
+
return pf_rule_problem_line(problem) if problem
|
|
177
|
+
|
|
160
178
|
field = pf_rule_field_labels(model_name, rule).join(' › ')
|
|
161
179
|
text = "#{pf_ui('rules.only_show')} #{pf_resource_plural(model_name)} #{pf_ui('rules.where')} #{field} " \
|
|
162
180
|
"#{pf_operator_label(rule.operator)}"
|
|
163
|
-
text += " #{pf_rule_value_text(rule)}" unless
|
|
181
|
+
text += " #{pf_rule_value_text(rule)}" unless pf_rule_valueless?(rule)
|
|
164
182
|
text += ", #{pf_ui('rules.or_empty', field: field)}" if pf_rule_nullable?(rule)
|
|
165
183
|
text
|
|
166
184
|
end
|
|
167
185
|
|
|
168
186
|
# Short form used in matrix cells: "Owner is the current user".
|
|
169
187
|
def pf_rule_summary(rule, model_name = rule.resource&.name)
|
|
188
|
+
problem = pf_rule_problem(rule)
|
|
189
|
+
return safe_join(['⊘ ', pf_rule_problem_line(problem)]) if problem
|
|
190
|
+
|
|
170
191
|
field = pf_rule_field_labels(model_name, rule).join(' › ')
|
|
171
|
-
value =
|
|
192
|
+
value = pf_rule_valueless?(rule) ? nil : pf_rule_value_text(rule)
|
|
172
193
|
[field, pf_operator_label(rule.operator), value].compact.join(' ')
|
|
173
194
|
end
|
|
174
195
|
end
|
|
@@ -54,7 +54,7 @@ module PermissionForge
|
|
|
54
54
|
options << row.value unless options.include?(row.value.to_s)
|
|
55
55
|
pills = options.map do |opt|
|
|
56
56
|
current = opt == row.value.to_s
|
|
57
|
-
content_tag(:
|
|
57
|
+
content_tag(:code, opt, class: "pf-pill pf-pill--opt#{' pf-pill--current' if current}", 'aria-current': (current ? 'true' : nil))
|
|
58
58
|
end
|
|
59
59
|
content_tag(:span, safe_join(pills, ' '), class: 'pf-row pf-row--tight')
|
|
60
60
|
end
|
|
@@ -88,7 +88,7 @@ module PermissionForge
|
|
|
88
88
|
value = row.value
|
|
89
89
|
return content_tag(:span, t('permission_forge.settings.values.empty'), class: 'pf-text-3') if value.blank?
|
|
90
90
|
|
|
91
|
-
content_tag(:span, safe_join(value.map { |k, v| content_tag(:
|
|
91
|
+
content_tag(:span, safe_join(value.map { |k, v| content_tag(:code, "#{k} → #{v}") }), class: 'pf-map')
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def pf_setting_secret(row)
|
|
@@ -6,8 +6,6 @@ module PermissionForge
|
|
|
6
6
|
#
|
|
7
7
|
# @api private
|
|
8
8
|
module UiHelper
|
|
9
|
-
STATE_GLYPHS = { granted: '✓', blocked: '⊘', none: '–' }.freeze
|
|
10
|
-
|
|
11
9
|
# --- assets ---------------------------------------------------------------
|
|
12
10
|
|
|
13
11
|
def pf_asset_url(file)
|
|
@@ -41,6 +39,40 @@ module PermissionForge
|
|
|
41
39
|
t("permission_forge.ui.#{key}", **)
|
|
42
40
|
end
|
|
43
41
|
|
|
42
|
+
# A translation whose interpolated values may be markup (a SafeBuffer:
|
|
43
|
+
# a name marked as host data) although its key does not end in `_html`:
|
|
44
|
+
# the block translates with a marker in place of each value, and the
|
|
45
|
+
# pieces are joined with safe_join — the translation's own text escaped,
|
|
46
|
+
# the markup kept, nothing marked safe by hand. The `t(...)` stays in the
|
|
47
|
+
# caller (i18n-tasks sees the key).
|
|
48
|
+
#
|
|
49
|
+
# pf_t_markup(set: pf_host_data(name)) { |held| t('.flag_granted', **held, flag: word) }
|
|
50
|
+
#
|
|
51
|
+
# A marker no value was given for (text that happens to hold one, such
|
|
52
|
+
# as a stored value interpolated as text) is left as written, escaped.
|
|
53
|
+
#
|
|
54
|
+
# @yieldparam held [Hash{Symbol => String}] the markers, by value name
|
|
55
|
+
# @return [ActiveSupport::SafeBuffer]
|
|
56
|
+
def pf_t_markup(**values)
|
|
57
|
+
held = values.to_h { |name, _| [name, "\u0001#{name}\u0001"] }
|
|
58
|
+
by_name = values.transform_keys(&:to_s)
|
|
59
|
+
pieces = yield(held).to_s.split(/\u0001(\w+)\u0001/)
|
|
60
|
+
safe_join(pieces.each_with_index.map { |piece, index| index.odd? ? by_name.fetch(piece) { "\u0001#{piece}\u0001" } : piece })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# "Bob, Carol and Dan" in the page's language (Admin::ListSentence): never
|
|
64
|
+
# a bare to_sentence, whose connectors stay English without rails-i18n.
|
|
65
|
+
def pf_to_sentence(items, **)
|
|
66
|
+
PermissionForge::Admin::ListSentence.call(items, **)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# #pf_to_sentence for items that carry markup (a set named with its
|
|
70
|
+
# Resource as host data): each item is kept as it is (#pf_t_markup).
|
|
71
|
+
def pf_markup_sentence(items)
|
|
72
|
+
values = items.each_with_index.to_h { |item, index| [:"item#{index}", item] }
|
|
73
|
+
pf_t_markup(**values) { |held| pf_to_sentence(held.values) }
|
|
74
|
+
end
|
|
75
|
+
|
|
44
76
|
def pf_people_text(count)
|
|
45
77
|
pf_ui('people_count', count: count)
|
|
46
78
|
end
|
|
@@ -79,11 +111,28 @@ module PermissionForge
|
|
|
79
111
|
format('%02d:00', hour.to_i)
|
|
80
112
|
end
|
|
81
113
|
|
|
114
|
+
# --- states (Admin::StateLabel: one registry of glyphs and words) -----------
|
|
115
|
+
|
|
116
|
+
# A state (Admin::StateLabel::Label): `.word`, `.glyph`, and `.to_s` for
|
|
117
|
+
# both — pf_state(:field_level, :none).word → "Not configured",
|
|
118
|
+
# pf_state(:effective, :blocked).to_s → "⊘ Blocked".
|
|
119
|
+
def pf_state(kind, state)
|
|
120
|
+
PermissionForge::Admin::StateLabel.for(kind, state)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The glyph (hidden from assistive technology) and the word of a state
|
|
124
|
+
# (pf_state), for a cell (`<span class="pf-cell__glyph">`) or a legend
|
|
125
|
+
# (`glyph_tag: :b`, one space between) that styles the glyph itself.
|
|
126
|
+
def pf_state_parts(label, glyph_tag: :span, separator: '')
|
|
127
|
+
glyph = content_tag(glyph_tag, label.glyph, class: ('pf-cell__glyph' if glyph_tag == :span), 'aria-hidden': true)
|
|
128
|
+
safe_join([glyph, label.word], separator)
|
|
129
|
+
end
|
|
130
|
+
|
|
82
131
|
# Read-only state cell (effective access grid, across-sets lists).
|
|
83
132
|
def pf_state_cell(state, aria:, href: nil, why: nil, current: false)
|
|
84
133
|
state = state.to_sym
|
|
85
|
-
label =
|
|
86
|
-
content = safe_join([content_tag(:span,
|
|
134
|
+
label = pf_state(:effective, state).word
|
|
135
|
+
content = safe_join([content_tag(:span, pf_state(:effective, state).glyph, class: 'pf-cell__glyph', 'aria-hidden': true),
|
|
87
136
|
content_tag(:span, label)], ' ')
|
|
88
137
|
opts = { class: "pf-state pf-state--#{state}", 'aria-label': "#{aria}: #{label}" }
|
|
89
138
|
opts[:'aria-current'] = 'true' if current
|
|
@@ -135,6 +184,18 @@ module PermissionForge
|
|
|
135
184
|
content_tag(pf_fragment? ? :h3 : :h2, text, class: 'pf-h3', id: id)
|
|
136
185
|
end
|
|
137
186
|
|
|
187
|
+
# A frame that scrolls sideways when its content (a wide grid) is wider than the page: a
|
|
188
|
+
# tab stop with a name (aria-label, or aria-labelledby on a section), so the keyboard
|
|
189
|
+
# reaches the region and assistive technology says what scrolls; the stylesheet paints the
|
|
190
|
+
# cue at its edges and clips what it contains. Every grid 480px or wider goes in one
|
|
191
|
+
# (html/layout_lint_test.rb).
|
|
192
|
+
# <%= pf_scroll_frame('aria-label': t('.label'), class: 'pf-card') do %> … <% end %>
|
|
193
|
+
# <%= pf_scroll_frame(tag: :section, 'aria-labelledby': 'pf-changes', class: 'pf-card') do %> … <% end %>
|
|
194
|
+
def pf_scroll_frame(tag: :div, **options, &)
|
|
195
|
+
classes = ['pf-scroll-x', options.delete(:class)].compact.join(' ')
|
|
196
|
+
content_tag(tag, class: classes, role: (tag == :section ? nil : 'region'), tabindex: '0', **options, &)
|
|
197
|
+
end
|
|
198
|
+
|
|
138
199
|
# Breadcrumb from [[label, path], ..., label]
|
|
139
200
|
def pf_breadcrumb(*items)
|
|
140
201
|
parts = items.map do |item|
|
|
@@ -25,6 +25,10 @@ module PermissionForge
|
|
|
25
25
|
ACTOR_TYPES = PermissionForge::Audit::Actor::TYPES
|
|
26
26
|
SOURCES = PermissionForge::Audit::Source::SOURCES
|
|
27
27
|
|
|
28
|
+
# actor_label and record_label name people: #inspect hides them whenever
|
|
29
|
+
# the application's filters hide a `name` (InspectionFilters).
|
|
30
|
+
PermissionForge::InspectionFilters.hide_people_labels(self)
|
|
31
|
+
|
|
28
32
|
# JSON text that works on SQLite, PostgreSQL and MySQL alike; times are
|
|
29
33
|
# written in ISO 8601 (ActiveSupport::JSON), nothing reads as nil.
|
|
30
34
|
#
|
|
@@ -68,7 +72,7 @@ module PermissionForge
|
|
|
68
72
|
# @return [Symbol, nil]
|
|
69
73
|
def self.tenant_column
|
|
70
74
|
column = PermissionForge.configuration.resolved_tenant_column
|
|
71
|
-
column if column &&
|
|
75
|
+
column if column && PermissionForge::SchemaRead.column?(self, column.to_s, ignored: false)
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
# The events of one tenant, on the relation it is called on; every event
|