easy-admin-rails 0.1.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 (203) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/easy_admin.base.js +43505 -0
  6. data/app/assets/builds/easy_admin.base.js.map +7 -0
  7. data/app/assets/builds/easy_admin.css +6141 -0
  8. data/app/assets/config/easy_admin_manifest.js +1 -0
  9. data/app/assets/images/jsoneditor-icons.svg +749 -0
  10. data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
  11. data/app/components/easy_admin/base_component.rb +35 -0
  12. data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
  13. data/app/components/easy_admin/batch_action_form_component.rb +124 -0
  14. data/app/components/easy_admin/combined_filters_component.rb +232 -0
  15. data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
  16. data/app/components/easy_admin/context_menu_component.rb +161 -0
  17. data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
  18. data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
  19. data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
  20. data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
  21. data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
  22. data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
  23. data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
  24. data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
  25. data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
  26. data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
  27. data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
  28. data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
  29. data/app/components/easy_admin/dashboards/show_component.rb +163 -0
  30. data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
  31. data/app/components/easy_admin/date_picker_component.rb +188 -0
  32. data/app/components/easy_admin/fields/base_component.rb +101 -0
  33. data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
  34. data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
  35. data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
  36. data/app/components/easy_admin/fields/form/date_component.rb +55 -0
  37. data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
  38. data/app/components/easy_admin/fields/form/email_component.rb +55 -0
  39. data/app/components/easy_admin/fields/form/file_component.rb +190 -0
  40. data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
  41. data/app/components/easy_admin/fields/form/json_component.rb +81 -0
  42. data/app/components/easy_admin/fields/form/number_component.rb +55 -0
  43. data/app/components/easy_admin/fields/form/select_component.rb +326 -0
  44. data/app/components/easy_admin/fields/form/text_component.rb +55 -0
  45. data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
  46. data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
  47. data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
  48. data/app/components/easy_admin/fields/index/date_component.rb +13 -0
  49. data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
  50. data/app/components/easy_admin/fields/index/email_component.rb +24 -0
  51. data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
  52. data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
  53. data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
  54. data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
  55. data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
  56. data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
  57. data/app/components/easy_admin/fields/index/json_component.rb +23 -0
  58. data/app/components/easy_admin/fields/index/number_component.rb +20 -0
  59. data/app/components/easy_admin/fields/index/select_component.rb +25 -0
  60. data/app/components/easy_admin/fields/index/text_component.rb +20 -0
  61. data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
  62. data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
  63. data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
  64. data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
  65. data/app/components/easy_admin/fields/show/date_component.rb +13 -0
  66. data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
  67. data/app/components/easy_admin/fields/show/email_component.rb +19 -0
  68. data/app/components/easy_admin/fields/show/file_component.rb +304 -0
  69. data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
  70. data/app/components/easy_admin/fields/show/json_component.rb +45 -0
  71. data/app/components/easy_admin/fields/show/number_component.rb +20 -0
  72. data/app/components/easy_admin/fields/show/select_component.rb +25 -0
  73. data/app/components/easy_admin/fields/show/text_component.rb +17 -0
  74. data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
  75. data/app/components/easy_admin/filters_component.rb +120 -0
  76. data/app/components/easy_admin/form_tabs_component.rb +166 -0
  77. data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
  78. data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
  79. data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
  80. data/app/components/easy_admin/modal_frame_component.rb +26 -0
  81. data/app/components/easy_admin/navbar_component.rb +226 -0
  82. data/app/components/easy_admin/notification_component.rb +83 -0
  83. data/app/components/easy_admin/pagination_component.rb +188 -0
  84. data/app/components/easy_admin/quick_filters_component.rb +65 -0
  85. data/app/components/easy_admin/resource_pagination_component.rb +14 -0
  86. data/app/components/easy_admin/resources/index_component.rb +211 -0
  87. data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
  88. data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
  89. data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
  90. data/app/components/easy_admin/resources/table_component.rb +206 -0
  91. data/app/components/easy_admin/resources/table_row_component.rb +160 -0
  92. data/app/components/easy_admin/row_action_form_component.rb +127 -0
  93. data/app/components/easy_admin/scopes_component.rb +224 -0
  94. data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
  95. data/app/components/easy_admin/show_layout_component.rb +600 -0
  96. data/app/components/easy_admin/sidebar_component.rb +174 -0
  97. data/app/components/easy_admin/turbo/response_component.rb +40 -0
  98. data/app/components/easy_admin/turbo/stream_component.rb +28 -0
  99. data/app/controllers/easy_admin/application_controller.rb +66 -0
  100. data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
  101. data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
  102. data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
  103. data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
  104. data/app/controllers/easy_admin/passwords_controller.rb +15 -0
  105. data/app/controllers/easy_admin/registrations_controller.rb +52 -0
  106. data/app/controllers/easy_admin/resources_controller.rb +907 -0
  107. data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
  108. data/app/controllers/easy_admin/sessions_controller.rb +32 -0
  109. data/app/controllers/easy_admin/settings_controller.rb +94 -0
  110. data/app/helpers/easy_admin/application_helper.rb +4 -0
  111. data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
  112. data/app/helpers/easy_admin/fields_helper.rb +27 -0
  113. data/app/helpers/easy_admin/pagy_helper.rb +30 -0
  114. data/app/helpers/easy_admin/resources_helper.rb +39 -0
  115. data/app/javascript/easy_admin/application.js +12 -0
  116. data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
  117. data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
  118. data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
  119. data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
  120. data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
  121. data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
  122. data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
  123. data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
  124. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
  125. data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
  126. data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
  127. data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
  128. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
  129. data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
  130. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
  131. data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
  132. data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
  133. data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
  134. data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
  135. data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
  136. data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
  137. data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
  138. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
  139. data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
  140. data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
  141. data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
  142. data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
  143. data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
  144. data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
  145. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
  146. data/app/javascript/easy_admin/controllers.js +54 -0
  147. data/app/javascript/easy_admin.base.js +4 -0
  148. data/app/models/easy_admin/admin_user.rb +53 -0
  149. data/app/models/easy_admin/application_record.rb +5 -0
  150. data/app/views/easy_admin/dashboard/index.html.erb +3 -0
  151. data/app/views/easy_admin/dashboards/show.html.erb +7 -0
  152. data/app/views/easy_admin/passwords/edit.html.erb +42 -0
  153. data/app/views/easy_admin/passwords/new.html.erb +41 -0
  154. data/app/views/easy_admin/registrations/new.html.erb +65 -0
  155. data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
  156. data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
  157. data/app/views/easy_admin/resources/edit.html.erb +151 -0
  158. data/app/views/easy_admin/resources/index.html.erb +12 -0
  159. data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
  160. data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
  161. data/app/views/easy_admin/resources/new.html.erb +100 -0
  162. data/app/views/easy_admin/resources/show.html.erb +31 -0
  163. data/app/views/easy_admin/sessions/new.html.erb +55 -0
  164. data/app/views/easy_admin/settings/_form.html.erb +51 -0
  165. data/app/views/easy_admin/settings/index.html.erb +53 -0
  166. data/app/views/layouts/easy_admin/application.html.erb +48 -0
  167. data/app/views/layouts/easy_admin/auth.html.erb +34 -0
  168. data/config/initializers/easy_admin_card_factory.rb +27 -0
  169. data/config/initializers/pagy.rb +15 -0
  170. data/config/initializers/rack_mini_profiler.rb +67 -0
  171. data/config/routes.rb +70 -0
  172. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
  173. data/lib/easy-admin.rb +32 -0
  174. data/lib/easy_admin/action.rb +159 -0
  175. data/lib/easy_admin/batch_action.rb +134 -0
  176. data/lib/easy_admin/configuration.rb +75 -0
  177. data/lib/easy_admin/dashboard.rb +110 -0
  178. data/lib/easy_admin/dashboard_registry.rb +30 -0
  179. data/lib/easy_admin/delete_action.rb +22 -0
  180. data/lib/easy_admin/engine.rb +54 -0
  181. data/lib/easy_admin/field.rb +118 -0
  182. data/lib/easy_admin/resource.rb +806 -0
  183. data/lib/easy_admin/resource_registry.rb +22 -0
  184. data/lib/easy_admin/types/json_type.rb +25 -0
  185. data/lib/easy_admin/version.rb +3 -0
  186. data/lib/generators/easy_admin/auth_generator.rb +69 -0
  187. data/lib/generators/easy_admin/card/card_generator.rb +94 -0
  188. data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
  189. data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
  190. data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
  191. data/lib/generators/easy_admin/install_generator.rb +25 -0
  192. data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
  193. data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
  194. data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
  195. data/lib/generators/easy_admin/resource_generator.rb +43 -0
  196. data/lib/generators/easy_admin/templates/AUTH_README +35 -0
  197. data/lib/generators/easy_admin/templates/README +27 -0
  198. data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
  199. data/lib/generators/easy_admin/templates/devise.rb +267 -0
  200. data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
  201. data/lib/generators/easy_admin/templates/resource.rb +29 -0
  202. data/lib/tasks/easy_admin_tasks.rake +4 -0
  203. metadata +445 -0
@@ -0,0 +1,159 @@
1
+ module EasyAdmin
2
+ class Action
3
+ include ActiveModel::Model
4
+ include ActiveModel::Attributes
5
+ include ActiveModel::Validations
6
+
7
+ # Execution modes
8
+ INSTANT = :instant # Execute immediately with optional confirmation
9
+ MODAL = :modal # Show form first, then execute
10
+
11
+ # Action types
12
+ SINGLE = :single # Single row action
13
+ BATCH = :batch # Batch action (for compatibility)
14
+
15
+ class_attribute :execution_mode, :label, :icon, :style, :confirm_message
16
+ class_attribute :modal_title, :modal_description, :modal_form_fields
17
+ class_attribute :action_type, :permissions, :visibility_conditions
18
+
19
+ # Set default execution mode
20
+ self.execution_mode = INSTANT
21
+ self.action_type = SINGLE
22
+
23
+ # Attributes for action context
24
+ attribute :current_user
25
+ attribute :record # For single actions
26
+ attribute :selected_records # For batch actions (compatibility)
27
+ attribute :resource_class
28
+ attr_accessor :params # Use attr_accessor instead of attribute for hash
29
+
30
+ def initialize(attributes = {})
31
+ super
32
+ @params ||= {}
33
+ end
34
+
35
+ class << self
36
+ def instant(label:, icon: nil, style: :primary, confirm: nil, permissions: nil)
37
+ self.execution_mode = INSTANT
38
+ self.label = label
39
+ self.icon = icon
40
+ self.style = style
41
+ self.confirm_message = confirm
42
+ self.permissions = permissions
43
+ end
44
+
45
+ def modal(label:, icon: nil, style: :primary, title: nil, description: nil, permissions: nil)
46
+ self.execution_mode = MODAL
47
+ self.label = label
48
+ self.icon = icon
49
+ self.style = style
50
+ self.modal_title = title || label
51
+ self.modal_description = description
52
+ self.permissions = permissions
53
+ self.modal_form_fields = []
54
+ end
55
+
56
+ def form_field(name, type, options = {})
57
+ self.modal_form_fields ||= []
58
+ self.modal_form_fields << {
59
+ name: name,
60
+ type: type,
61
+ options: options
62
+ }
63
+
64
+ # Add attribute and validation dynamically
65
+ attribute name, get_attribute_type(type), **options.slice(:default)
66
+
67
+ # Add validations if specified
68
+ if options[:required]
69
+ validates name, presence: true
70
+ end
71
+
72
+ if options[:format]
73
+ validates name, format: options[:format]
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def get_attribute_type(type)
80
+ case type
81
+ when :string, :text, :email, :url
82
+ :string
83
+ when :integer, :number
84
+ :integer
85
+ when :boolean
86
+ :boolean
87
+ when :date
88
+ :date
89
+ when :datetime
90
+ :datetime
91
+ when :json
92
+ :json
93
+ when :belongs_to
94
+ :string # Will store the ID
95
+ else
96
+ :string
97
+ end
98
+ end
99
+
100
+ def single_action
101
+ self.action_type = SINGLE
102
+ end
103
+
104
+ def batch_action
105
+ self.action_type = BATCH
106
+ end
107
+
108
+ def visible_if(&block)
109
+ self.visibility_conditions = block
110
+ end
111
+ end
112
+
113
+ # Main execution method - must be implemented by subclasses
114
+ def perform
115
+ raise NotImplementedError, "Subclasses must implement #perform method"
116
+ end
117
+
118
+ # Visibility check
119
+ def visible?
120
+ return true unless self.class.visibility_conditions
121
+ instance_eval(&self.class.visibility_conditions)
122
+ end
123
+
124
+ # Permission check
125
+ def permitted?
126
+ return true unless self.class.permissions
127
+ # Integration with Permisi gem would go here
128
+ # For now, always return true
129
+ true
130
+ end
131
+
132
+ # Turbo response strategy for UI updates
133
+ def turbo_response_strategy
134
+ :replace # Default strategy, can be overridden
135
+ end
136
+
137
+ # Helper methods
138
+ class << self
139
+ # Helper method to get field configuration
140
+ def field_config(field_name)
141
+ modal_form_fields&.find { |field| field[:name] == field_name } || {}
142
+ end
143
+
144
+ # Helper method to check if action is instant or modal
145
+ def instant?
146
+ execution_mode == INSTANT
147
+ end
148
+
149
+ def modal?
150
+ execution_mode == MODAL
151
+ end
152
+ end
153
+
154
+ # Helper method to access form parameters safely
155
+ def form_param(key)
156
+ params[key.to_s] || params[key.to_sym]
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,134 @@
1
+ module EasyAdmin
2
+ class BatchAction
3
+ include ActiveModel::Model
4
+ include ActiveModel::Attributes
5
+ include ActiveModel::Validations
6
+
7
+ attr_accessor :selected_ids, :params, :current_user, :resource_class
8
+
9
+ # Class-level configuration
10
+ class_attribute :label, :icon, :style, :confirm_message, :execution_mode
11
+ class_attribute :modal_form_fields, :modal_title, :modal_description
12
+
13
+ # Execution modes
14
+ INSTANT = :instant # Execute immediately (toggle, delete, etc.)
15
+ MODAL = :modal # Show form first, then execute
16
+
17
+ def initialize(attributes = {})
18
+ super(attributes)
19
+ @records = nil
20
+ @selected_ids ||= []
21
+ @params ||= {}
22
+ end
23
+
24
+ # Main execution method - must be implemented by subclasses
25
+ def perform
26
+ raise NotImplementedError, "Subclasses must implement #perform"
27
+ end
28
+
29
+ # Validation before execution
30
+ def valid_for_execution?
31
+ selected_ids.present? && records.exists?
32
+ end
33
+
34
+ # Get the selected records
35
+ def records
36
+ @records ||= resource_class.model_class.where(id: selected_ids)
37
+ end
38
+
39
+ # Override for custom authorization
40
+ def authorized?
41
+ true # Override in subclasses
42
+ end
43
+
44
+ # Class methods for configuration
45
+ class << self
46
+ def instant(label:, icon: nil, style: :primary, confirm: nil)
47
+ self.execution_mode = INSTANT
48
+ self.label = label
49
+ self.icon = icon
50
+ self.style = style
51
+ self.confirm_message = confirm
52
+ end
53
+
54
+ def modal(label:, icon: nil, style: :primary, title: nil, description: nil)
55
+ self.execution_mode = MODAL
56
+ self.label = label
57
+ self.icon = icon
58
+ self.style = style
59
+ self.modal_title = title || label
60
+ self.modal_description = description
61
+ self.modal_form_fields = []
62
+ end
63
+
64
+ def form_field(name, type, options = {})
65
+ self.modal_form_fields ||= []
66
+ self.modal_form_fields << {
67
+ name: name,
68
+ type: type,
69
+ options: options
70
+ }
71
+
72
+ # Add attribute and validation dynamically
73
+ attribute name, get_attribute_type(type), **options.slice(:default)
74
+
75
+ # Add validations if specified
76
+ if options[:required]
77
+ validates name, presence: true
78
+ end
79
+
80
+ if options[:format]
81
+ validates name, format: options[:format]
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def get_attribute_type(type)
88
+ case type
89
+ when :string, :text, :email, :url
90
+ :string
91
+ when :integer, :number
92
+ :integer
93
+ when :boolean
94
+ :boolean
95
+ when :date
96
+ :date
97
+ when :datetime
98
+ :datetime
99
+ when :json
100
+ :json
101
+ when :belongs_to
102
+ :string # Will store the ID
103
+ else
104
+ :string
105
+ end
106
+ end
107
+ end
108
+
109
+ # Helper method to get field configuration
110
+ def self.field_config(field_name)
111
+ modal_form_fields&.find { |field| field[:name] == field_name } || {}
112
+ end
113
+
114
+ # Helper method to check if action is instant or modal
115
+ def self.instant?
116
+ execution_mode == INSTANT
117
+ end
118
+
119
+ def self.modal?
120
+ execution_mode == MODAL
121
+ end
122
+
123
+ # Default success message
124
+ def success_message(count = nil)
125
+ count ||= records.count
126
+ "#{self.class.label} completed for #{count} item#{'s' if count != 1}"
127
+ end
128
+
129
+ # Helper method to access form parameters safely
130
+ def form_param(key)
131
+ params[key.to_s] || params[key.to_sym]
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,75 @@
1
+ module EasyAdmin
2
+ class Configuration
3
+ attr_accessor :app_name, :sidebar_items, :global_settings, :enable_profiler
4
+
5
+ def initialize
6
+ @app_name = "EasyAdmin"
7
+ @sidebar_items = []
8
+ @global_settings = false
9
+ @enable_profiler = Rails.env.development?
10
+ end
11
+
12
+ def sidebar(&block)
13
+ sidebar_builder = SidebarBuilder.new
14
+ sidebar_builder.instance_eval(&block) if block_given?
15
+ @sidebar_items = sidebar_builder.items
16
+ end
17
+ end
18
+
19
+ class SidebarBuilder
20
+ attr_reader :items
21
+
22
+ def initialize
23
+ @items = []
24
+ @current_group = nil
25
+ end
26
+
27
+ def item(label, path = nil, icon: nil, active: false, &block)
28
+ item_config = {
29
+ label: label,
30
+ path: path,
31
+ icon: icon,
32
+ active: active
33
+ }
34
+
35
+ if block_given?
36
+ # This is a parent item with children
37
+ old_items = @items
38
+ @items = []
39
+ @current_group = item_config
40
+
41
+ instance_eval(&block)
42
+
43
+ item_config[:children] = @items
44
+ item_config[:expanded] = item_config[:children].any? { |child| child[:active] }
45
+
46
+ @items = old_items
47
+ end
48
+
49
+ @items << item_config
50
+ end
51
+
52
+ def group(label, icon: nil, expanded: false, &block)
53
+ group_config = {
54
+ label: label,
55
+ icon: icon,
56
+ expanded: expanded,
57
+ children: []
58
+ }
59
+
60
+ if block_given?
61
+ old_items = @items
62
+ @items = []
63
+
64
+ instance_eval(&block)
65
+
66
+ group_config[:children] = @items
67
+ group_config[:expanded] = expanded || @items.any? { |child| child[:active] }
68
+
69
+ @items = old_items
70
+ end
71
+
72
+ @items << group_config
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,110 @@
1
+ module EasyAdmin
2
+ class Dashboard
3
+ class << self
4
+ attr_accessor :cards_config, :dashboard_name, :grid_columns
5
+
6
+ def inherited(subclass)
7
+ subclass.cards_config = []
8
+ subclass.grid_columns = 4
9
+ super
10
+ # Register will happen after class is fully defined
11
+ end
12
+
13
+ # DSL methods for dashboard configuration
14
+ def dashboard(name, **options, &block)
15
+ self.dashboard_name = name
16
+ self.grid_columns = options[:columns] || 4
17
+ instance_eval(&block) if block_given?
18
+ EasyAdmin::DashboardRegistry.register(self)
19
+ end
20
+
21
+ def metric_card(name, **options)
22
+ card(name, :metric, **options)
23
+ end
24
+
25
+ def chart_card(name, **options)
26
+ card(name, :chart, **options)
27
+ end
28
+
29
+ def table_card(name, **options)
30
+ card(name, :table, **options)
31
+ end
32
+
33
+ def custom_card(name, **options)
34
+ card(name, :custom, **options)
35
+ end
36
+
37
+ def card(name, type, **options)
38
+ self.cards_config += [{
39
+ name: name,
40
+ type: type,
41
+ title: options[:title] || name.to_s.humanize,
42
+ cols: options[:cols] || 1,
43
+ rows: options[:rows] || 1,
44
+ refresh_every: options[:refresh_every],
45
+ partial: options[:partial],
46
+ data_method: options[:data_method] || "#{name}_data",
47
+ visible: options.fetch(:visible, true),
48
+ **options
49
+ }]
50
+ end
51
+
52
+ # Helper methods
53
+ def title
54
+ dashboard_name.to_s.humanize
55
+ end
56
+
57
+ def route_key
58
+ dashboard_name.to_s.underscore
59
+ end
60
+
61
+ def visible_cards
62
+ cards_config.select { |card| card[:visible] }
63
+ end
64
+
65
+ def find_card(name)
66
+ cards_config.find { |card| card[:name].to_s == name.to_s }
67
+ end
68
+
69
+ # Grid configuration
70
+ def columns
71
+ grid_columns
72
+ end
73
+
74
+ def columns_class
75
+ "cols-#{grid_columns}"
76
+ end
77
+
78
+ # Data methods - override in subclasses
79
+ def method_missing(method_name, *args, &block)
80
+ if method_name.to_s.end_with?('_data')
81
+ {}
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def respond_to_missing?(method_name, include_private = false)
88
+ method_name.to_s.end_with?('_data') || super
89
+ end
90
+ end
91
+
92
+ # Instance methods for data fetching
93
+ def initialize(params = {})
94
+ @params = params
95
+ end
96
+
97
+ def card_data(card_name)
98
+ method_name = "#{card_name}_data"
99
+ if self.class.respond_to?(method_name)
100
+ self.class.send(method_name, @params)
101
+ else
102
+ {}
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ attr_reader :params
109
+ end
110
+ end
@@ -0,0 +1,30 @@
1
+ module EasyAdmin
2
+ class DashboardRegistry
3
+ class << self
4
+ def dashboards
5
+ @dashboards ||= {}
6
+ end
7
+
8
+ def register(dashboard_class)
9
+ route_key = dashboard_class.route_key
10
+ dashboards[route_key] = dashboard_class
11
+ end
12
+
13
+ def find(route_key)
14
+ dashboards[route_key.to_s]
15
+ end
16
+
17
+ def all
18
+ dashboards.values
19
+ end
20
+
21
+ def exists?(route_key)
22
+ dashboards.key?(route_key.to_s)
23
+ end
24
+
25
+ def clear!
26
+ @dashboards = {}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ module EasyAdmin
2
+ class DeleteAction < Action
3
+ # Configure as instant action with confirmation
4
+ instant(
5
+ label: "Delete",
6
+ icon: "🗑️",
7
+ style: :danger,
8
+ confirm: "Are you sure you want to delete this record? This action cannot be undone."
9
+ )
10
+
11
+ def perform
12
+ # Delete the record
13
+ record.destroy!
14
+
15
+ {
16
+ success: true,
17
+ message: "Record deleted successfully",
18
+ strategy: :remove # Remove the row from the table
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ require "turbo-rails"
2
+ require "devise"
3
+ require_relative "types/json_type"
4
+
5
+ module EasyAdmin
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace EasyAdmin
8
+
9
+ initializer "easy_admin.assets.precompile" do |app|
10
+ app.config.assets.precompile += %w[
11
+ easy_admin/application.css
12
+ easy_admin/dashboards.css
13
+ easy_admin/date_picker.css
14
+ easy_admin.base.js
15
+ jsoneditor-icons.svg
16
+ ]
17
+ end
18
+
19
+ initializer "easy_admin.phlex" do |app|
20
+ app.config.autoload_paths = app.config.autoload_paths + [root.join("app", "components").to_s]
21
+ end
22
+
23
+ initializer "easy_admin.register_types" do |app|
24
+ ActiveModel::Type.register(:json, EasyAdmin::Types::JsonType)
25
+ end
26
+
27
+ initializer "easy_admin.devise" do |app|
28
+ config.to_prepare do
29
+ Devise.setup do |config|
30
+ config.router_name = :easy_admin
31
+ end
32
+ end
33
+ end
34
+
35
+ # Load rack-mini-profiler if available and enabled
36
+ initializer 'easy_admin.rack_mini_profiler', after: :load_config_initializers do |app|
37
+ begin
38
+ require 'rack-mini-profiler'
39
+ # Add middleware to the stack if profiler should be enabled
40
+ profiler_enabled = begin
41
+ EasyAdmin.configuration.enable_profiler
42
+ rescue
43
+ Rails.env.development?
44
+ end
45
+
46
+ if profiler_enabled && defined?(Rack::MiniProfiler)
47
+ app.middleware.use Rack::MiniProfiler
48
+ end
49
+ rescue LoadError
50
+ # rack-mini-profiler not available, skip silently
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,118 @@
1
+ module EasyAdmin
2
+ class Field
3
+ attr_accessor :name, :type, :label, :editable, :options_adapter, :permitted_attached_attributes, :attached_param_key
4
+
5
+ def initialize(name, type, options = {})
6
+ @name = name
7
+ @type = type
8
+ @label = options[:label] || name.to_s.humanize
9
+ @editable = options[:editable]
10
+ @options_adapter = options[:options_adapter]
11
+ @permitted_attached_attributes = options[:permitted_attached_attributes] || []
12
+ @attached_param_key = options[:attached_param_key]
13
+ end
14
+
15
+ def editable?
16
+ editable.present?
17
+ end
18
+
19
+ def editable_via_menu?
20
+ type == :belongs_to && editable&.dig(:via) == :menu_or_modal
21
+ end
22
+
23
+ def editable_via_modal?
24
+ editable? && !editable_via_menu?
25
+ end
26
+
27
+ def permitted_param_key
28
+ case type
29
+ when :belongs_to
30
+ foreign_key || :"#{name}_id"
31
+ else
32
+ name
33
+ end
34
+ end
35
+
36
+ def foreign_key
37
+ @foreign_key ||= :"#{name}_id"
38
+ end
39
+
40
+ def normalize_input!(attrs)
41
+ key = permitted_param_key
42
+ value = attrs[key]
43
+
44
+ attrs[key] = case type
45
+ when :boolean
46
+ ActiveModel::Type::Boolean.new.cast(value)
47
+ when :date
48
+ if value.present?
49
+ Date.parse(value) rescue value
50
+ else
51
+ value
52
+ end
53
+ when :datetime
54
+ if value.present?
55
+ Time.zone.parse(value) rescue value
56
+ else
57
+ value
58
+ end
59
+ when :enum
60
+ # Validate against allowed values if needed
61
+ validate_enum_value(value)
62
+ when :json
63
+ # Parse JSON string to ensure valid JSON
64
+ if value.present? && value.is_a?(String)
65
+ begin
66
+ JSON.parse(value)
67
+ value
68
+ rescue JSON::ParserError => e
69
+ raise "Invalid JSON: #{e.message}"
70
+ end
71
+ else
72
+ value
73
+ end
74
+ else
75
+ value # string, email, number, etc.
76
+ end
77
+
78
+ attrs
79
+ end
80
+
81
+ class << self
82
+ def render(field_type, action:, **options)
83
+ component_class = find_component_class(field_type, action)
84
+ component_class.new(**options)
85
+ end
86
+
87
+ private
88
+
89
+ def find_component_class(field_type, action)
90
+ # Try to find action-specific component first, then fall back to base
91
+ action_component_name = "EasyAdmin::Fields::#{action.to_s.classify}::#{field_type.to_s.classify}Component"
92
+
93
+ begin
94
+ action_component_name.constantize
95
+ rescue NameError
96
+ # Fallback to base text component for the action
97
+ case action
98
+ when :index
99
+ EasyAdmin::Fields::Index::TextComponent
100
+ when :show
101
+ EasyAdmin::Fields::Show::TextComponent
102
+ when :form
103
+ EasyAdmin::Fields::Form::TextComponent
104
+ else
105
+ EasyAdmin::Fields::Index::TextComponent
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def validate_enum_value(value)
114
+ return value unless respond_to?(:values) && values.present?
115
+ values.include?(value) ? value : raise("Invalid enum value: #{value}")
116
+ end
117
+ end
118
+ end