easy-admin-rails 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +254 -18
  3. data/app/assets/builds/easy_admin.base.js.map +4 -4
  4. data/app/assets/builds/easy_admin.css +112 -18
  5. data/app/components/easy_admin/base_component.rb +1 -0
  6. data/app/components/easy_admin/form_tabs_component.rb +5 -2
  7. data/app/components/easy_admin/navbar_component.rb +5 -1
  8. data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
  9. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
  10. data/app/components/easy_admin/resources/index_component.rb +1 -4
  11. data/app/components/easy_admin/sidebar_component.rb +67 -2
  12. data/app/controllers/easy_admin/application_controller.rb +131 -1
  13. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  14. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  15. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  16. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  17. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  18. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  19. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  20. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  21. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  22. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  23. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  24. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  25. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  26. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  27. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  28. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  29. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  30. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  31. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  33. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  34. data/app/javascript/easy_admin/controllers.js +5 -1
  35. data/app/models/easy_admin/admin_user.rb +6 -0
  36. data/app/policies/admin_user_policy.rb +36 -0
  37. data/app/policies/application_policy.rb +83 -0
  38. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  39. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  40. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  41. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  42. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  43. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  44. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  46. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  47. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  48. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  49. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  50. data/app/views/easy_admin/resources/index.html.erb +1 -1
  51. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  52. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  53. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  54. data/config/initializers/easy_admin_permissions.rb +73 -0
  55. data/db/seeds/easy_admin_permissions.rb +121 -0
  56. data/lib/easy-admin-rails.rb +2 -0
  57. data/lib/easy_admin/permissions/component.rb +168 -0
  58. data/lib/easy_admin/permissions/configuration.rb +37 -0
  59. data/lib/easy_admin/permissions/controller.rb +164 -0
  60. data/lib/easy_admin/permissions/dsl.rb +180 -0
  61. data/lib/easy_admin/permissions/models.rb +44 -0
  62. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  63. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  64. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  65. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  66. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  67. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  68. data/lib/easy_admin/permissions.rb +113 -0
  69. data/lib/easy_admin/resource/base.rb +119 -0
  70. data/lib/easy_admin/resource/configuration.rb +148 -0
  71. data/lib/easy_admin/resource/dsl.rb +117 -0
  72. data/lib/easy_admin/resource/field_registry.rb +189 -0
  73. data/lib/easy_admin/resource/form_builder.rb +123 -0
  74. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  75. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  76. data/lib/easy_admin/resource/show_builder.rb +359 -0
  77. data/lib/easy_admin/resource.rb +8 -835
  78. data/lib/easy_admin/resource_modules.rb +11 -0
  79. data/lib/easy_admin/version.rb +1 -1
  80. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  81. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  82. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  84. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  90. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  91. metadata +62 -5
  92. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,129 @@
1
+ module EasyAdmin
2
+ module Permissions
3
+ module UserExtensions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :user_roles, class_name: 'EasyAdmin::Permissions::UserRole', foreign_key: 'user_id', dependent: :destroy
8
+ has_many :roles, through: :user_roles, class_name: 'EasyAdmin::Permissions::Role'
9
+ has_many :permissions, through: :roles, class_name: 'EasyAdmin::Permissions::Permission'
10
+
11
+ # Cache for computed permissions (JSON columns handle serialization automatically)
12
+ end
13
+
14
+ # Check if user has specific permission
15
+ def has_permission?(permission_name, context: nil)
16
+ return false unless EasyAdmin::Permissions.enabled?
17
+ return false unless respond_to?(:permissions_cache)
18
+ return false unless permissions_cache.present?
19
+
20
+ # For now, ignore context and just use flat permission structure
21
+ permission_value = permissions_cache[permission_name.to_s]
22
+
23
+ # Check if permission exists in cache (handle both string and boolean values)
24
+ return true if permission_value == "true" || permission_value == true
25
+ return false if permission_value == "false" || permission_value == false
26
+
27
+ # If not explicitly set, default to false
28
+ false
29
+ end
30
+
31
+ # Check if user has specific role
32
+ def has_role?(role_name, context: nil)
33
+ user_roles.active.includes(:role).any? do |user_role|
34
+ next false if context && user_role.context != context
35
+ user_role.role.slug == role_name.to_s || user_role.role.name == role_name.to_s
36
+ end
37
+ end
38
+
39
+ # Get all user permissions for a context
40
+ def calculate_permissions(context: nil)
41
+ return [] unless EasyAdmin::Permissions.enabled?
42
+ return [] unless respond_to?(:permissions_cache)
43
+ return [] unless permissions_cache.present?
44
+
45
+ # Return only permissions that are explicitly set to true
46
+ permissions_cache.select { |k, v| v == "true" || v == true }.keys
47
+ end
48
+
49
+ # Set user permissions directly in cache
50
+ def set_permission(permission_name, value = true, context: nil)
51
+ return false unless EasyAdmin::Permissions.enabled?
52
+ return false unless respond_to?(:permissions_cache=)
53
+
54
+ context_key = context&.cache_key || 'global'
55
+ current_cache = permissions_cache || {}
56
+
57
+ if context
58
+ current_cache[context_key] ||= {}
59
+ current_cache[context_key][permission_name.to_s] = value.to_s
60
+ else
61
+ current_cache[permission_name.to_s] = value.to_s
62
+ end
63
+
64
+ if persisted?
65
+ update_column(:permissions_cache, current_cache)
66
+ else
67
+ self.permissions_cache = current_cache
68
+ end
69
+
70
+ true
71
+ end
72
+
73
+ # Remove permission from cache
74
+ def remove_permission(permission_name, context: nil)
75
+ return false unless EasyAdmin::Permissions.enabled?
76
+ return false unless respond_to?(:permissions_cache=)
77
+
78
+ context_key = context&.cache_key || 'global'
79
+ current_cache = permissions_cache || {}
80
+
81
+ if context && current_cache[context_key]
82
+ current_cache[context_key].delete(permission_name.to_s)
83
+ else
84
+ current_cache.delete(permission_name.to_s)
85
+ end
86
+
87
+ if persisted?
88
+ update_column(:permissions_cache, current_cache)
89
+ else
90
+ self.permissions_cache = current_cache
91
+ end
92
+
93
+ true
94
+ end
95
+
96
+ # Get all roles for user in specific context
97
+ def roles_for_context(context = nil)
98
+ if context
99
+ user_roles.active.for_context(context).includes(:role).map(&:role)
100
+ else
101
+ user_roles.active.global.includes(:role).map(&:role)
102
+ end
103
+ end
104
+
105
+ # Clear permission cache
106
+ def clear_permissions_cache!
107
+ if respond_to?(:permissions_cache=)
108
+ if persisted?
109
+ update_column(:permissions_cache, {})
110
+ else
111
+ self.permissions_cache = {}
112
+ end
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def find_role(role_identifier)
119
+ if role_identifier.is_a?(String) || role_identifier.is_a?(Symbol)
120
+ EasyAdmin::Permissions::Role.find_by(slug: role_identifier.to_s) ||
121
+ EasyAdmin::Permissions::Role.find_by(name: role_identifier.to_s)
122
+ else
123
+ role_identifier
124
+ end
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -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