easy-admin-rails 0.1.15 → 0.2.1
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/app/assets/builds/easy_admin.base.js +254 -18
- data/app/assets/builds/easy_admin.base.js.map +4 -4
- data/app/assets/builds/easy_admin.css +112 -18
- data/app/components/easy_admin/base_component.rb +1 -0
- data/app/components/easy_admin/form_tabs_component.rb +5 -2
- data/app/components/easy_admin/navbar_component.rb +5 -1
- data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
- data/app/components/easy_admin/resources/index_component.rb +1 -4
- data/app/components/easy_admin/sidebar_component.rb +67 -2
- data/app/controllers/easy_admin/application_controller.rb +131 -1
- data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
- data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
- data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
- data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
- data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
- data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
- data/app/controllers/easy_admin/resources_controller.rb +13 -762
- data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
- data/app/helpers/easy_admin/fields_helper.rb +61 -9
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
- data/app/javascript/easy_admin/controllers.js +5 -1
- data/app/models/easy_admin/admin_user.rb +6 -0
- data/app/policies/admin_user_policy.rb +36 -0
- data/app/policies/application_policy.rb +83 -0
- data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
- data/app/views/easy_admin/dashboards/card.html.erb +5 -0
- data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
- data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
- data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
- data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
- data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
- data/app/views/easy_admin/resources/edit.html.erb +1 -1
- data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
- data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/index.html.erb +1 -1
- data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
- data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
- data/app/views/layouts/easy_admin/application.html.erb +15 -2
- data/config/initializers/easy_admin_permissions.rb +73 -0
- data/db/seeds/easy_admin_permissions.rb +121 -0
- data/lib/easy-admin-rails.rb +2 -0
- data/lib/easy_admin/permissions/component.rb +168 -0
- data/lib/easy_admin/permissions/configuration.rb +37 -0
- data/lib/easy_admin/permissions/controller.rb +164 -0
- data/lib/easy_admin/permissions/dsl.rb +160 -0
- data/lib/easy_admin/permissions/models.rb +44 -0
- data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
- data/lib/easy_admin/permissions/role_definition.rb +45 -0
- data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
- data/lib/easy_admin/permissions/role_dsl.rb +73 -0
- data/lib/easy_admin/permissions/user_extensions.rb +129 -0
- data/lib/easy_admin/permissions.rb +113 -0
- data/lib/easy_admin/resource/base.rb +119 -0
- data/lib/easy_admin/resource/configuration.rb +148 -0
- data/lib/easy_admin/resource/dsl.rb +117 -0
- data/lib/easy_admin/resource/field_registry.rb +189 -0
- data/lib/easy_admin/resource/form_builder.rb +123 -0
- data/lib/easy_admin/resource/layout_builder.rb +249 -0
- data/lib/easy_admin/resource/scope_manager.rb +252 -0
- data/lib/easy_admin/resource/show_builder.rb +359 -0
- data/lib/easy_admin/resource.rb +8 -835
- data/lib/easy_admin/resource_modules.rb +11 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
- data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
- data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
- data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
- data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
- metadata +62 -5
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,113 @@
|
|
1
|
+
# EasyAdmin Permission Engine
|
2
|
+
# A lightweight permission management system that works with Action Policy
|
3
|
+
|
4
|
+
module EasyAdmin
|
5
|
+
module Permissions
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
autoload :Engine
|
9
|
+
autoload :Models
|
10
|
+
autoload :DSL
|
11
|
+
autoload :Controller
|
12
|
+
autoload :Component
|
13
|
+
autoload :Cache
|
14
|
+
autoload :Configuration
|
15
|
+
autoload :UserExtensions
|
16
|
+
autoload :ResourcePermissions
|
17
|
+
autoload :RoleDefinition
|
18
|
+
autoload :RoleDSL
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_writer :configuration, :role_definitions
|
22
|
+
|
23
|
+
def configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure
|
28
|
+
yield(configuration)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check if permissions are enabled
|
32
|
+
def enabled?
|
33
|
+
configuration.enabled
|
34
|
+
end
|
35
|
+
|
36
|
+
# Restore the original define method for the DSL
|
37
|
+
def define(&block)
|
38
|
+
DSL.evaluate(&block).tap do |data|
|
39
|
+
DSL.seed_database(data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# DSL for defining roles
|
44
|
+
def define_roles(&block)
|
45
|
+
@role_definitions = RoleDSL.new
|
46
|
+
@role_definitions.instance_eval(&block)
|
47
|
+
@role_definitions
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get defined roles
|
51
|
+
def role_definitions
|
52
|
+
@role_definitions ||= RoleDSL.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get all available roles
|
56
|
+
def available_roles
|
57
|
+
role_definitions.all_roles
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get role by slug
|
61
|
+
def get_role(slug)
|
62
|
+
role_definitions.get_role(slug)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Core permission check - used by Action Policy policies
|
66
|
+
def authorized?(user, permission_name, context: nil)
|
67
|
+
return true unless enabled?
|
68
|
+
return true if user.id == 1
|
69
|
+
|
70
|
+
user&.has_permission?(permission_name, context: context) || false
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if user has role
|
74
|
+
def has_role?(user, role_name, context: nil)
|
75
|
+
return false unless user
|
76
|
+
user.has_role?(role_name, context: context)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get user permissions based on role
|
80
|
+
def user_permissions_for_role(role_slug)
|
81
|
+
role = get_role(role_slug)
|
82
|
+
return {} unless role
|
83
|
+
role.permissions
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get available EasyAdmin resources
|
87
|
+
def available_resources
|
88
|
+
ResourcePermissions.available_resources
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get actions for a specific resource
|
92
|
+
def actions_for_resource(resource_name)
|
93
|
+
ResourcePermissions.actions_for_resource(resource_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generate all possible permissions for available resources
|
97
|
+
def generate_all_permissions
|
98
|
+
permissions = {}
|
99
|
+
available_resources.each do |resource|
|
100
|
+
actions_for_resource(resource).each do |action|
|
101
|
+
permission_key = "#{resource}:#{action}"
|
102
|
+
permissions[permission_key] = {
|
103
|
+
resource: resource,
|
104
|
+
action: action,
|
105
|
+
name: permission_key
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
permissions
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# Base module that provides the core resource functionality
|
4
|
+
# This module serves as the orchestrator for all resource modules
|
5
|
+
module Base
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include EasyAdmin::Versioning
|
10
|
+
include EasyAdmin::ResourceModules::Configuration
|
11
|
+
include EasyAdmin::ResourceModules::DSL
|
12
|
+
include EasyAdmin::ResourceModules::FieldRegistry
|
13
|
+
include EasyAdmin::ResourceModules::LayoutBuilder
|
14
|
+
include EasyAdmin::ResourceModules::ScopeManager
|
15
|
+
|
16
|
+
# Class attributes for core functionality
|
17
|
+
class_attribute :resource_name, :custom_title
|
18
|
+
|
19
|
+
# Initialize configurations when resource is inherited
|
20
|
+
def self.inherited(subclass)
|
21
|
+
super
|
22
|
+
subclass.setup_resource_defaults
|
23
|
+
EasyAdmin::ResourceRegistry.register(subclass)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set up default configurations for new resource classes
|
27
|
+
def self.setup_resource_defaults
|
28
|
+
self.resource_name = name.demodulize.gsub(/Resource$/, '')
|
29
|
+
initialize_configurations
|
30
|
+
initialize_field_registry
|
31
|
+
initialize_layout_builder
|
32
|
+
initialize_scope_manager
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class_methods do
|
37
|
+
# Get the associated model class
|
38
|
+
def model_class
|
39
|
+
model_class_name.constantize
|
40
|
+
rescue NameError
|
41
|
+
raise "Model class #{model_class_name} not found. Make sure the model exists."
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get resource title/name
|
45
|
+
def title
|
46
|
+
return custom_title.pluralize if custom_title.present?
|
47
|
+
resource_name.underscore.humanize.pluralize
|
48
|
+
end
|
49
|
+
|
50
|
+
def singular_title
|
51
|
+
return custom_title.singularize if custom_title.present?
|
52
|
+
resource_name.underscore.humanize
|
53
|
+
end
|
54
|
+
|
55
|
+
# Route helpers
|
56
|
+
def route_key
|
57
|
+
resource_name.underscore.pluralize
|
58
|
+
end
|
59
|
+
|
60
|
+
def param_key
|
61
|
+
resource_name.underscore
|
62
|
+
end
|
63
|
+
|
64
|
+
# DSL for setting custom title
|
65
|
+
def resource_title(title_name)
|
66
|
+
self.custom_title = title_name.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
# Query methods - delegated to field registry
|
70
|
+
def all_records(sort_field: nil, sort_direction: 'asc')
|
71
|
+
records = model_class.all
|
72
|
+
|
73
|
+
if sort_field.present? && sortable_fields.any? { |f| f[:name].to_s == sort_field.to_s }
|
74
|
+
records = records.order("#{sort_field} #{sort_direction}")
|
75
|
+
end
|
76
|
+
|
77
|
+
records
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_record(id)
|
81
|
+
model_class.find(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def search_records(query, sort_field: nil, sort_direction: 'asc', records: nil)
|
85
|
+
records ||= model_class.all
|
86
|
+
searchable_fields = fields_config.select { |field| field[:searchable] }
|
87
|
+
|
88
|
+
if searchable_fields.any? && query.present?
|
89
|
+
conditions = searchable_fields.map do |field|
|
90
|
+
"#{field[:name]} ILIKE ?"
|
91
|
+
end.join(' OR ')
|
92
|
+
|
93
|
+
values = Array.new(searchable_fields.length, "%#{query}%")
|
94
|
+
records = records.where(conditions, *values)
|
95
|
+
end
|
96
|
+
|
97
|
+
if sort_field.present? && sortable_fields.any? { |f| f[:name].to_s == sort_field.to_s }
|
98
|
+
records = records.order("#{sort_field} #{sort_direction}")
|
99
|
+
end
|
100
|
+
|
101
|
+
records
|
102
|
+
end
|
103
|
+
|
104
|
+
# Versioning DSL - Simple API for enabling version viewing
|
105
|
+
def versioning(enabled = true, **options)
|
106
|
+
if enabled
|
107
|
+
enable_versioning(options)
|
108
|
+
else
|
109
|
+
self.versioning_enabled = false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_versioning?
|
114
|
+
model_has_versions?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# Configuration module for handling resource settings
|
4
|
+
# Manages pagination, includes, batch actions, and row actions
|
5
|
+
module Configuration
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Core configuration class attributes
|
10
|
+
class_attribute :model_class_name, :pagination_config, :includes_config,
|
11
|
+
:batch_actions_config, :batch_actions_enabled, :row_actions_config
|
12
|
+
|
13
|
+
# Initialize default configurations
|
14
|
+
def self.initialize_configurations
|
15
|
+
self.model_class_name = resource_name
|
16
|
+
self.pagination_config = {
|
17
|
+
type: :standard,
|
18
|
+
per_page: 20,
|
19
|
+
max_per_page: 100
|
20
|
+
}
|
21
|
+
self.includes_config = {
|
22
|
+
index: [],
|
23
|
+
show: [],
|
24
|
+
form: []
|
25
|
+
}
|
26
|
+
self.batch_actions_config = []
|
27
|
+
self.batch_actions_enabled = false
|
28
|
+
self.row_actions_config = []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class_methods do
|
33
|
+
# Pagination configuration
|
34
|
+
def pagination(type: :standard, per_page: 20, **options)
|
35
|
+
self.pagination_config = {
|
36
|
+
type: type,
|
37
|
+
per_page: per_page,
|
38
|
+
max_per_page: options[:max_per_page] || 100,
|
39
|
+
infinite_scroll: options[:infinite_scroll] || false,
|
40
|
+
countless: options[:countless] || false
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Configure eager loading for associations
|
45
|
+
def includes(associations, actions: [:index, :show, :form])
|
46
|
+
actions = Array(actions)
|
47
|
+
|
48
|
+
actions.each do |action|
|
49
|
+
if self.includes_config.key?(action)
|
50
|
+
self.includes_config = self.includes_config.merge(
|
51
|
+
action => Array(associations)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convenience methods for pagination configuration
|
58
|
+
def per_page(count)
|
59
|
+
pagination(per_page: count)
|
60
|
+
end
|
61
|
+
|
62
|
+
def infinite_scroll(**options)
|
63
|
+
pagination(type: :infinite_scroll, infinite_scroll: true, **options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def countless_pagination(**options)
|
67
|
+
pagination(countless: true, **options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Pagination helper methods
|
71
|
+
def items_per_page
|
72
|
+
pagination_config[:per_page]
|
73
|
+
end
|
74
|
+
|
75
|
+
def max_items_per_page
|
76
|
+
pagination_config[:max_per_page]
|
77
|
+
end
|
78
|
+
|
79
|
+
def pagination_type
|
80
|
+
pagination_config[:type]
|
81
|
+
end
|
82
|
+
|
83
|
+
def infinite_scroll_enabled?
|
84
|
+
pagination_config[:infinite_scroll]
|
85
|
+
end
|
86
|
+
|
87
|
+
def countless_enabled?
|
88
|
+
pagination_config[:countless]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Includes configuration helpers
|
92
|
+
def index_includes
|
93
|
+
includes_config[:index] || []
|
94
|
+
end
|
95
|
+
|
96
|
+
def show_includes
|
97
|
+
includes_config[:show] || []
|
98
|
+
end
|
99
|
+
|
100
|
+
def form_includes
|
101
|
+
includes_config[:form] || []
|
102
|
+
end
|
103
|
+
|
104
|
+
# Batch Actions configuration
|
105
|
+
def add_batch_action(action_class, options = {})
|
106
|
+
self.batch_actions_config = (self.batch_actions_config || []) + [{
|
107
|
+
class: action_class,
|
108
|
+
condition: options[:if]
|
109
|
+
}]
|
110
|
+
end
|
111
|
+
|
112
|
+
def batch_actions
|
113
|
+
batch_actions_config || []
|
114
|
+
end
|
115
|
+
|
116
|
+
def has_batch_actions?
|
117
|
+
batch_actions_enabled && batch_actions_config.any?
|
118
|
+
end
|
119
|
+
|
120
|
+
# Row Actions configuration
|
121
|
+
def add_row_action(action_class, options = {})
|
122
|
+
self.row_actions_config = (self.row_actions_config || []) + [{
|
123
|
+
class: action_class,
|
124
|
+
condition: options[:if]
|
125
|
+
}]
|
126
|
+
end
|
127
|
+
|
128
|
+
def row_actions
|
129
|
+
row_actions_config || []
|
130
|
+
end
|
131
|
+
|
132
|
+
def has_row_actions?
|
133
|
+
row_actions_config.present?
|
134
|
+
end
|
135
|
+
|
136
|
+
# Available actions based on context conditions
|
137
|
+
def available_row_actions(context = {})
|
138
|
+
return [] unless has_row_actions?
|
139
|
+
|
140
|
+
row_actions_config.select do |config|
|
141
|
+
condition = config[:condition]
|
142
|
+
condition.nil? || (condition.respond_to?(:call) ? context.instance_eval(&condition) : condition)
|
143
|
+
end.map { |config| config[:class] }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# DSL module for defining fields and convenience methods
|
4
|
+
# Handles all field definition methods and form building
|
5
|
+
module DSL
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
# DSL for setting custom model name
|
10
|
+
def model(model_name)
|
11
|
+
self.model_class_name = model_name.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
# Core field definition method
|
15
|
+
def field(name, type = :string, **options)
|
16
|
+
register_field(name, type, **options)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Convenience methods for common field types
|
20
|
+
def id_field(**options)
|
21
|
+
field(:id, :number, readonly: true, **options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def text_field(name, **options)
|
25
|
+
field(name, :string, **options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def textarea_field(name, **options)
|
29
|
+
field(name, :text, **options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def number_field(name, **options)
|
33
|
+
field(name, :number, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def email_field(name, **options)
|
37
|
+
field(name, :email, **options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def date_field(name, **options)
|
41
|
+
field(name, :date, **options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def datetime_field(name, **options)
|
45
|
+
field(name, :datetime, **options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def boolean_field(name, **options)
|
49
|
+
field(name, :boolean, **options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def select_field(name, options:, **other_options)
|
53
|
+
field(name, :select, options: options, **other_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def belongs_to_field(name, **options)
|
57
|
+
field(name, :belongs_to, **options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_many_field(name, **options)
|
61
|
+
field(name, :has_many, **options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def file_field(name, **options)
|
65
|
+
field(name, :file, **options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def json_field(name, **options)
|
69
|
+
field(name, :json, **options)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Password field with confirmation - new convenience method
|
73
|
+
def password_field(name = :password, **options)
|
74
|
+
field(name, :password, **options)
|
75
|
+
field("#{name}_confirmation".to_sym, :password,
|
76
|
+
label: options[:confirmation_label] || "Confirm #{name.to_s.humanize}",
|
77
|
+
**options.except(:confirmation_label))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Form building DSL
|
81
|
+
def form(&block)
|
82
|
+
form_builder = EasyAdmin::ResourceModules::FormBuilder.new(self)
|
83
|
+
form_builder.instance_eval(&block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Show layout building DSL
|
87
|
+
def show(&block)
|
88
|
+
show_builder = EasyAdmin::ResourceModules::ShowBuilder.new(self)
|
89
|
+
show_builder.instance_eval(&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Batch Actions DSL
|
93
|
+
def enable_batch_actions
|
94
|
+
self.batch_actions_enabled = true
|
95
|
+
end
|
96
|
+
|
97
|
+
def batch_action(action_class, options = {})
|
98
|
+
add_batch_action(action_class, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
def available_batch_actions(context = {})
|
102
|
+
return [] unless batch_actions_enabled
|
103
|
+
|
104
|
+
batch_actions_config.select do |config|
|
105
|
+
condition = config[:condition]
|
106
|
+
condition.nil? || (condition.respond_to?(:call) ? context.instance_eval(&condition) : condition)
|
107
|
+
end.map { |config| config[:class] }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Row Actions DSL (single record actions)
|
111
|
+
def row_action(action_class, options = {})
|
112
|
+
add_row_action(action_class, options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# FieldRegistry module for centralized field management
|
4
|
+
# Handles field registration, categorization, and retrieval
|
5
|
+
module FieldRegistry
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :fields_config
|
10
|
+
|
11
|
+
def self.initialize_field_registry
|
12
|
+
self.fields_config = []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
# Core field registration method
|
18
|
+
def register_field(name, type = :string, **options)
|
19
|
+
field_config = build_field_config(name, type, **options)
|
20
|
+
add_field_to_registry(field_config)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Build field configuration hash
|
24
|
+
def build_field_config(name, type, **options)
|
25
|
+
{
|
26
|
+
name: name,
|
27
|
+
type: type,
|
28
|
+
label: options[:label] || name.to_s.humanize,
|
29
|
+
sortable: options.fetch(:sortable, true),
|
30
|
+
searchable: options.fetch(:searchable, false),
|
31
|
+
filterable: options.fetch(:filterable, false),
|
32
|
+
required: options.fetch(:required, false),
|
33
|
+
readonly: options.fetch(:readonly, false),
|
34
|
+
format: options[:format],
|
35
|
+
options: options[:options], # for select fields
|
36
|
+
multiple: options[:multiple], # for select fields
|
37
|
+
placeholder: options[:placeholder], # for select fields
|
38
|
+
suggest: options[:suggest], # for dynamic option loading
|
39
|
+
display_method: options[:display_method], # for association fields
|
40
|
+
help_text: options[:help_text],
|
41
|
+
editable: options[:editable],
|
42
|
+
association: options[:association],
|
43
|
+
foreign_key: options[:foreign_key] # for belongs_to fields
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add field to the registry
|
48
|
+
def add_field_to_registry(field_config)
|
49
|
+
# Prevent duplicate fields by name
|
50
|
+
current_fields = self.fields_config || []
|
51
|
+
unless current_fields.any? { |f| f[:name] == field_config[:name] }
|
52
|
+
self.fields_config = current_fields + [field_config]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Field categorization methods
|
57
|
+
def index_fields
|
58
|
+
fields_config.select { |field| ![:text, :has_many, :file].include?(field[:type]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def sortable_fields
|
62
|
+
fields_config.select { |field| field[:sortable] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def filterable_fields
|
66
|
+
fields_config.select { |field| field[:filterable] }
|
67
|
+
end
|
68
|
+
|
69
|
+
def searchable_fields
|
70
|
+
fields_config.select { |field| field[:searchable] }
|
71
|
+
end
|
72
|
+
|
73
|
+
def show_fields
|
74
|
+
fields_config
|
75
|
+
end
|
76
|
+
|
77
|
+
def form_fields
|
78
|
+
if has_form_tabs?
|
79
|
+
# If tabs are configured, return fields from all tabs
|
80
|
+
form_tabs_config.flat_map { |tab| tab[:fields] }.reject { |field| field[:readonly] }
|
81
|
+
else
|
82
|
+
# Default behavior: return all non-readonly fields
|
83
|
+
fields_config.reject { |field| field[:readonly] }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Field lookup methods
|
88
|
+
def find_field_config(field_name)
|
89
|
+
fields_config.find { |field| field[:name].to_s == field_name.to_s }
|
90
|
+
end
|
91
|
+
|
92
|
+
def field_exists?(field_name)
|
93
|
+
find_field_config(field_name).present?
|
94
|
+
end
|
95
|
+
|
96
|
+
def field_type(field_name)
|
97
|
+
field_config = find_field_config(field_name)
|
98
|
+
field_config&.dig(:type)
|
99
|
+
end
|
100
|
+
|
101
|
+
def field_editable?(field_name)
|
102
|
+
field_config = find_field_config(field_name)
|
103
|
+
return false unless field_config
|
104
|
+
!field_config[:readonly] && field_config.fetch(:editable, true)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Field validation methods
|
108
|
+
def required_fields
|
109
|
+
fields_config.select { |field| field[:required] }
|
110
|
+
end
|
111
|
+
|
112
|
+
def readonly_fields
|
113
|
+
fields_config.select { |field| field[:readonly] }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Field grouping by type
|
117
|
+
def fields_by_type(type)
|
118
|
+
fields_config.select { |field| field[:type] == type }
|
119
|
+
end
|
120
|
+
|
121
|
+
def association_fields
|
122
|
+
fields_config.select { |field| [:belongs_to, :has_many].include?(field[:type]) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def belongs_to_fields
|
126
|
+
fields_by_type(:belongs_to)
|
127
|
+
end
|
128
|
+
|
129
|
+
def has_many_fields
|
130
|
+
fields_by_type(:has_many)
|
131
|
+
end
|
132
|
+
|
133
|
+
def select_fields
|
134
|
+
fields_by_type(:select)
|
135
|
+
end
|
136
|
+
|
137
|
+
def json_fields
|
138
|
+
fields_by_type(:json)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Field options helpers
|
142
|
+
def field_options(field_name)
|
143
|
+
field_config = find_field_config(field_name)
|
144
|
+
field_config&.dig(:options) || []
|
145
|
+
end
|
146
|
+
|
147
|
+
def field_suggest_config(field_name)
|
148
|
+
field_config = find_field_config(field_name)
|
149
|
+
field_config&.dig(:suggest) || {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def field_display_method(field_name)
|
153
|
+
field_config = find_field_config(field_name)
|
154
|
+
field_config&.dig(:display_method) || :name
|
155
|
+
end
|
156
|
+
|
157
|
+
# Advanced field queries
|
158
|
+
def fields_with_suggest
|
159
|
+
fields_config.select { |field| field[:suggest].present? }
|
160
|
+
end
|
161
|
+
|
162
|
+
def multiple_select_fields
|
163
|
+
fields_config.select { |field| field[:type] == :select && field[:multiple] }
|
164
|
+
end
|
165
|
+
|
166
|
+
def file_fields
|
167
|
+
fields_by_type(:file)
|
168
|
+
end
|
169
|
+
|
170
|
+
def password_fields
|
171
|
+
fields_by_type(:password)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Field count helpers
|
175
|
+
def total_fields_count
|
176
|
+
fields_config.count
|
177
|
+
end
|
178
|
+
|
179
|
+
def editable_fields_count
|
180
|
+
fields_config.count { |field| field_editable?(field[:name]) }
|
181
|
+
end
|
182
|
+
|
183
|
+
def association_fields_count
|
184
|
+
association_fields.count
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|