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,216 @@
1
+ module EasyAdmin
2
+ class RowActionsController < ApplicationController
3
+ before_action :load_resource_class
4
+ before_action :load_record
5
+ before_action :load_action_class, only: [:execute, :form]
6
+
7
+ # Render context menu with available actions
8
+ def context_menu
9
+ available_actions = @resource_class.row_actions.map do |action_config|
10
+ action_instance = action_config[:class].new(
11
+ record: @record,
12
+ current_user: current_admin_user,
13
+ resource_class: @resource_class
14
+ )
15
+
16
+ next unless action_instance.visible? && action_instance.permitted?
17
+
18
+ {
19
+ class: action_config[:class],
20
+ instance: action_instance
21
+ }
22
+ end.compact
23
+
24
+ respond_to do |format|
25
+ format.turbo_stream do
26
+ render turbo_stream: turbo_stream.update(
27
+ "context-menu-#{@record.id}",
28
+ EasyAdmin::ContextMenuComponent.new(
29
+ record: @record,
30
+ resource_class: @resource_class,
31
+ actions: available_actions
32
+ ).call
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ # Execute an action
39
+ def execute
40
+ action_instance = @action_class.new(
41
+ record: @record,
42
+ current_user: current_admin_user,
43
+ resource_class: @resource_class,
44
+ params: row_action_params.to_h
45
+ )
46
+
47
+ unless action_instance.permitted?
48
+ head :forbidden
49
+ return
50
+ end
51
+
52
+ result = action_instance.perform
53
+
54
+ respond_to do |format|
55
+ format.turbo_stream do
56
+ streams = build_turbo_response(action_instance, result)
57
+
58
+ # Add notification if message provided
59
+ if result[:message]
60
+ streams << turbo_stream.update(
61
+ "notifications",
62
+ EasyAdmin::NotificationComponent.new(
63
+ message: result[:message],
64
+ type: result[:success] ? :success : :error
65
+ ).call
66
+ )
67
+ end
68
+
69
+ render turbo_stream: streams
70
+ end
71
+ end
72
+ rescue => e
73
+ Rails.logger.error "Row action execution failed: #{e.message}"
74
+ Rails.logger.error e.backtrace.join("\n")
75
+
76
+ respond_to do |format|
77
+ format.turbo_stream do
78
+ render turbo_stream: turbo_stream.update(
79
+ "notifications",
80
+ EasyAdmin::NotificationComponent.new(
81
+ message: "Action failed: #{e.message}",
82
+ type: :error
83
+ ).call
84
+ )
85
+ end
86
+ end
87
+ end
88
+
89
+ # Render form for modal actions
90
+ def form
91
+ action_instance = @action_class.new(
92
+ record: @record,
93
+ current_user: current_admin_user,
94
+ resource_class: @resource_class
95
+ )
96
+
97
+ unless action_instance.permitted?
98
+ head :forbidden
99
+ return
100
+ end
101
+
102
+ respond_to do |format|
103
+ format.turbo_stream do
104
+ render turbo_stream: turbo_stream.update(
105
+ "modal",
106
+ EasyAdmin::RowActionFormComponent.new(
107
+ record: @record,
108
+ action_class: @action_class,
109
+ resource_class: @resource_class,
110
+ submit_url: easy_admin.row_action_path(
111
+ @resource_class.route_key,
112
+ @record,
113
+ action_class: params[:action_class]
114
+ )
115
+ ).call
116
+ )
117
+ end
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def load_resource_class
124
+ resource_name = params[:resource_name]
125
+ @resource_class = EasyAdmin::ResourceRegistry.find_resource(resource_name)
126
+
127
+ unless @resource_class
128
+ raise ActiveRecord::RecordNotFound, "Resource #{resource_name} not found"
129
+ end
130
+ end
131
+
132
+ def load_record
133
+ @record = @resource_class.model_class.find(params[:id])
134
+ end
135
+
136
+ def load_action_class
137
+ action_class_name = params[:action_class]
138
+
139
+ # Check if it's a built-in EasyAdmin action
140
+ if action_class_name.start_with?('EasyAdmin::')
141
+ begin
142
+ @action_class = action_class_name.constantize
143
+ return
144
+ rescue NameError
145
+ # Fall through to resource-specific actions
146
+ end
147
+ end
148
+
149
+ # Find action in resource configuration
150
+ action_config = @resource_class.row_actions.find do |config|
151
+ config[:class].name == action_class_name
152
+ end
153
+
154
+ unless action_config
155
+ raise ActiveRecord::RecordNotFound, "Action #{action_class_name} not found"
156
+ end
157
+
158
+ @action_class = action_config[:class]
159
+ end
160
+
161
+ def row_action_params
162
+ # Return empty hash if no row_action params (for instant actions)
163
+ return {} unless params[:row_action].present?
164
+
165
+ # Allow action-specific parameters based on action class attributes
166
+ if @action_class.respond_to?(:attribute_types)
167
+ permitted_attrs = @action_class.attribute_types.keys.map(&:to_sym) - [:record, :current_user, :resource_class, :params]
168
+ params.require(:row_action).permit(*permitted_attrs)
169
+ else
170
+ params.fetch(:row_action, {}).permit!
171
+ end
172
+ end
173
+
174
+ def build_turbo_response(action_instance, result)
175
+ strategy = result[:strategy] || action_instance.turbo_response_strategy
176
+
177
+ streams = []
178
+
179
+ case strategy
180
+ when :replace
181
+ streams << turbo_stream.replace(
182
+ helpers.dom_id(@record),
183
+ EasyAdmin::Resources::TableRowComponent.new(
184
+ record: @record.reload,
185
+ resource_class: @resource_class
186
+ ).call
187
+ )
188
+ when :remove
189
+ streams << turbo_stream.remove(helpers.dom_id(@record))
190
+ when :update
191
+ # Update with visual changes
192
+ updated_row = EasyAdmin::Resources::TableRowComponent.new(
193
+ record: @record.reload,
194
+ resource_class: @resource_class
195
+ )
196
+
197
+ streams << turbo_stream.replace(helpers.dom_id(@record), updated_row.call)
198
+ when :refresh
199
+ # Refresh the entire table
200
+ streams << turbo_stream.replace(
201
+ "table-frame",
202
+ turbo_frame_tag("table-frame", src: easy_admin.resources_path(@resource_class.route_key))
203
+ )
204
+ end
205
+
206
+ # Handle redirect if specified
207
+ if result[:redirect_to]
208
+ streams << turbo_stream.append("body",
209
+ "<script>window.location.href = '#{result[:redirect_to]}';</script>".html_safe
210
+ )
211
+ end
212
+
213
+ streams
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,32 @@
1
+ module EasyAdmin
2
+ class SessionsController < Devise::SessionsController
3
+ layout 'easy_admin/auth'
4
+
5
+ # GET /easy_admin/sign_in
6
+ def new
7
+ super
8
+ end
9
+
10
+ # POST /easy_admin/sign_in
11
+ def create
12
+ super
13
+ end
14
+
15
+ # DELETE /easy_admin/sign_out
16
+ def destroy
17
+ super
18
+ end
19
+
20
+ protected
21
+
22
+ # The path used after signing in
23
+ def after_sign_in_path_for(resource)
24
+ easy_admin.root_path
25
+ end
26
+
27
+ # The path used after signing out
28
+ def after_sign_out_path_for(resource_or_scope)
29
+ new_admin_user_session_path
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,94 @@
1
+ module EasyAdmin
2
+ class SettingsController < ApplicationController
3
+ before_action :ensure_settings_enabled
4
+
5
+ def index
6
+ @feature_toggles = get_feature_toggles
7
+ end
8
+
9
+ def update
10
+ params[:features]&.each do |feature_name, enabled|
11
+ if enabled == "1"
12
+ Flipper.enable(feature_name)
13
+ else
14
+ Flipper.disable(feature_name)
15
+ end
16
+ end
17
+
18
+ # Refresh the feature toggles for the turbo frame response
19
+ @feature_toggles = get_feature_toggles
20
+
21
+ respond_to do |format|
22
+ format.html { redirect_to settings_path, notice: "Feature toggles updated successfully" }
23
+ format.turbo_stream {
24
+ render turbo_stream: turbo_stream.replace(
25
+ "settings-sidebar",
26
+ EasyAdmin::SettingsSidebarComponent.new(feature_toggles: @feature_toggles, expanded: true).call
27
+ )
28
+ }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def get_feature_toggles
35
+ # Return all registered features with their current state
36
+ Flipper.features.map do |feature|
37
+ {
38
+ name: feature.name,
39
+ enabled: feature.enabled?,
40
+ description: feature_description(feature.name)
41
+ }
42
+ end
43
+ end
44
+
45
+ def ensure_settings_enabled
46
+ unless EasyAdmin.configuration.global_settings && defined?(Flipper)
47
+ redirect_to root_path, alert: "Settings are not available"
48
+ end
49
+ end
50
+
51
+ def feature_description(feature_name)
52
+ # You can customize descriptions for your features here
53
+ descriptions = {
54
+ # UI & Design Features
55
+ "new_ui" => "Enable the new user interface design",
56
+ "dark_mode" => "Switch to dark theme across the admin panel",
57
+ "compact_layout" => "Use a more compact layout to fit more content",
58
+
59
+ # Search & Filtering Features
60
+ "advanced_search" => "Enable advanced search with multiple filters",
61
+ "fuzzy_search" => "Allow approximate text matching in search",
62
+ "search_suggestions" => "Show suggested search terms as you type",
63
+
64
+ # Performance Features
65
+ "infinite_scroll" => "Load more content automatically when scrolling",
66
+ "lazy_loading" => "Load images and content only when needed",
67
+ "cache_optimization" => "Improve page load times with advanced caching",
68
+
69
+ # Beta & Experimental Features
70
+ "beta_features" => "Access to experimental features and improvements",
71
+ "ai_suggestions" => "Get AI-powered suggestions and recommendations",
72
+ "real_time_updates" => "See live updates without refreshing the page",
73
+
74
+ # Analytics & Monitoring
75
+ "detailed_analytics" => "View comprehensive analytics and reports",
76
+ "performance_monitoring" => "Monitor system performance and metrics",
77
+ "user_tracking" => "Track user behavior and interaction patterns",
78
+
79
+ # Export & Integration Features
80
+ "csv_export" => "Export data to CSV format for external use",
81
+ "pdf_export" => "Generate PDF reports and documents",
82
+ "api_access" => "Enable REST API access for integrations",
83
+ "webhook_notifications" => "Send real-time notifications via webhooks",
84
+
85
+ # Admin Features
86
+ "admin_logs" => "Keep detailed logs of all admin activities",
87
+ "audit_trail" => "Maintain a complete audit trail of changes",
88
+ "bulk_operations" => "Perform operations on multiple items at once"
89
+ }
90
+
91
+ descriptions[feature_name.to_s] || "Toggle #{feature_name.humanize.downcase}"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,4 @@
1
+ module EasyAdmin
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,121 @@
1
+ module EasyAdmin
2
+ module DashboardsHelper
3
+ def render_card(card, dashboard_class, request_params = nil)
4
+ component_class = card_component_class(card[:type])
5
+
6
+ # Use provided params or try to access from context
7
+ permitted_params = if request_params
8
+ request_params.permit(:date_from, :date_to, :segment, :search, :period, :refresh)
9
+ elsif respond_to?(:params)
10
+ params.permit(:date_from, :date_to, :segment, :search, :period, :refresh)
11
+ else
12
+ {}
13
+ end
14
+
15
+ component = component_class.new(
16
+ card: card,
17
+ dashboard_class: dashboard_class,
18
+ request_params: permitted_params
19
+ )
20
+ end
21
+
22
+ # Metric card helper methods
23
+ def metric_value_classes
24
+ base_classes = "text-3xl font-bold mb-1"
25
+ if @card_data[:delta]
26
+ case trend_direction
27
+ when :positive
28
+ "#{base_classes} text-green-600"
29
+ when :negative
30
+ "#{base_classes} text-red-600"
31
+ else
32
+ "#{base_classes} text-gray-900"
33
+ end
34
+ else
35
+ "#{base_classes} text-gray-900"
36
+ end
37
+ end
38
+
39
+ def trend_indicator_classes
40
+ base_classes = "flex items-center justify-center w-10 h-10 rounded-full"
41
+ case trend_direction
42
+ when :positive
43
+ "#{base_classes} bg-green-100 text-green-600"
44
+ when :negative
45
+ "#{base_classes} bg-red-100 text-red-600"
46
+ else
47
+ "#{base_classes} bg-gray-100 text-gray-600"
48
+ end
49
+ end
50
+
51
+ def delta_badge_classes
52
+ base_classes = "inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
53
+ case trend_direction
54
+ when :positive
55
+ "#{base_classes} bg-green-100 text-green-800"
56
+ when :negative
57
+ "#{base_classes} bg-red-100 text-red-800"
58
+ else
59
+ "#{base_classes} bg-gray-100 text-gray-600"
60
+ end
61
+ end
62
+
63
+ def trend_direction
64
+ return :neutral unless @card_data[:delta]
65
+ @card_data[:delta].to_f > 0 ? :positive : (@card_data[:delta].to_f < 0 ? :negative : :neutral)
66
+ end
67
+
68
+ def trend_icon
69
+ case trend_direction
70
+ when :positive
71
+ "↗"
72
+ when :negative
73
+ "↘"
74
+ else
75
+ "→"
76
+ end
77
+ end
78
+
79
+ def sparkline_color
80
+ case trend_direction
81
+ when :positive
82
+ "#059669" # green-600
83
+ when :negative
84
+ "#dc2626" # red-600
85
+ else
86
+ "#4f46e5" # indigo-600
87
+ end
88
+ end
89
+
90
+ def sparkline_points
91
+ return "" unless @card_data[:sparkline_data]&.any?
92
+
93
+ data = @card_data[:sparkline_data]
94
+ max_val = data.map { |d| d[:value] }.max
95
+ min_val = data.map { |d| d[:value] }.min
96
+ range = max_val - min_val
97
+ range = 1 if range == 0
98
+
99
+ data.map.with_index do |point, i|
100
+ x = i * (100.0 / (data.length - 1))
101
+ y = 25 - ((point[:value] - min_val) / range.to_f * 20)
102
+ "#{x},#{y}"
103
+ end.join(' ')
104
+ end
105
+
106
+ private
107
+
108
+ def card_component_class(card_type)
109
+ case card_type.to_sym
110
+ when :metric
111
+ EasyAdmin::Dashboards::MetricCardComponent
112
+ when :chart
113
+ EasyAdmin::Dashboards::ChartCardComponent
114
+ when :table
115
+ EasyAdmin::Dashboards::TableCardComponent
116
+ else
117
+ EasyAdmin::Dashboards::BaseCardComponent
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,27 @@
1
+ module EasyAdmin
2
+ module FieldsHelper
3
+ def render_field(field, action:, value: nil, record: nil, form: nil)
4
+ component = EasyAdmin::Field.render(
5
+ field[:type],
6
+ action: action,
7
+ field: field,
8
+ value: value,
9
+ record: record,
10
+ form: form
11
+ )
12
+
13
+ component.call.html_safe
14
+ end
15
+
16
+ def field_component(field, action:, value: nil, record: nil, form: nil)
17
+ EasyAdmin::Field.render(
18
+ field[:type],
19
+ action: action,
20
+ field: field,
21
+ value: value,
22
+ record: record,
23
+ form: form
24
+ )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module EasyAdmin
2
+ module PagyHelper
3
+ include Pagy::Frontend
4
+
5
+ # Override Pagy's default nav method to use our custom component
6
+ def pagy_nav(pagy, pagy_id: nil, link_extra: '', **vars)
7
+ EasyAdmin::PaginationComponent.new(pagy: pagy).call.html_safe
8
+ end
9
+
10
+ # iOS-style pagination info
11
+ def pagy_info(pagy, **vars)
12
+ return '' if pagy.count == 0
13
+
14
+ from, to, count = pagy.from, pagy.to, pagy.count
15
+
16
+ if count == 1
17
+ "Showing 1 result"
18
+ elsif from == to
19
+ "Showing result #{from} of #{count}"
20
+ else
21
+ "Showing #{from} to #{to} of #{count} results"
22
+ end
23
+ end
24
+
25
+ # Compact pagination info for mobile
26
+ def pagy_info_compact(pagy, **vars)
27
+ "#{pagy.page} of #{pagy.pages}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ module EasyAdmin
2
+ module ResourcesHelper
3
+ def sort_link(field, label = nil)
4
+ label ||= field[:label]
5
+ field_name = field[:name]
6
+
7
+ # Determine current sort direction and next direction
8
+ is_active = @current_sort_field == field_name.to_s
9
+ current_direction = @current_sort_direction if is_active
10
+ next_direction = is_active && current_direction == 'asc' ? 'desc' : 'asc'
11
+
12
+ # Build URL parameters
13
+ params_hash = params.permit(:search).to_h
14
+ params_hash[:sort] = field_name
15
+ params_hash[:direction] = next_direction
16
+
17
+ css_class = "ea-sort-pill"
18
+ css_class += " ea-sort-pill--active" if is_active
19
+
20
+ link_to easy_admin.resources_path(@resource_class.route_key, params_hash),
21
+ class: css_class do
22
+ content = label.dup
23
+ if is_active
24
+ content += " #{current_direction == 'asc' ? '↑' : '↓'}"
25
+ end
26
+ content.html_safe
27
+ end
28
+ end
29
+
30
+ def sort_arrow_svg
31
+ <<~SVG.html_safe
32
+ <svg width="8" height="12" viewBox="0 0 8 12" fill="none" class="ea-sort-arrow-icon">
33
+ <path d="M4 1L1 4H7L4 1Z" fill="currentColor" class="ea-sort-up"/>
34
+ <path d="M4 11L7 8H1L4 11Z" fill="currentColor" class="ea-sort-down"/>
35
+ </svg>
36
+ SVG
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,12 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ // Start the Stimulus application
4
+ const application = Application.start()
5
+
6
+ // Configure debug mode
7
+ application.debug = window?.localStorage.getItem('easy_admin.debug') === 'true'
8
+
9
+ // Set global Stimulus reference
10
+ window.Stimulus = application
11
+
12
+ export { application }
@@ -0,0 +1,66 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { post } from "@rails/request.js"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["form"]
6
+ static values = {
7
+ submitUrl: String,
8
+ selectedIds: String,
9
+ actionClass: String
10
+ }
11
+
12
+ close() {
13
+ this.element.remove()
14
+ }
15
+
16
+ submit() {
17
+ const formData = this.collectFormData()
18
+
19
+ post(this.submitUrlValue, {
20
+ body: formData,
21
+ responseKind: 'turbo-stream'
22
+ }).then(response => {
23
+ if (response.ok) {
24
+ // Dispatch event to clear selection
25
+ window.dispatchEvent(new CustomEvent('batch-action:completed'))
26
+ this.close()
27
+ }
28
+ }).catch(error => {
29
+ console.error('Batch action submission failed:', error)
30
+ })
31
+ }
32
+
33
+ collectFormData() {
34
+ const formData = new FormData()
35
+
36
+ // Add CSRF token
37
+ const csrfToken = document.querySelector('[name="csrf-token"]').content
38
+ formData.append('authenticity_token', csrfToken)
39
+
40
+ // Add action class and execution mode
41
+ formData.append('action_class', this.actionClassValue)
42
+ formData.append('execution_mode', 'modal')
43
+
44
+ // Add selected IDs
45
+ this.selectedIdsValue.split(',').forEach(id => {
46
+ formData.append('selected_ids[]', id)
47
+ })
48
+
49
+ // Collect form field data from the rendered form components
50
+ const formElement = this.formTarget
51
+ const inputs = formElement.querySelectorAll('input, select, textarea')
52
+
53
+ inputs.forEach(input => {
54
+ const name = input.name
55
+ if (name && name.startsWith('batch_action[')) {
56
+ if (input.type === 'checkbox') {
57
+ formData.append(name, input.checked ? '1' : '0')
58
+ } else {
59
+ formData.append(name, input.value || '')
60
+ }
61
+ }
62
+ })
63
+
64
+ return formData
65
+ }
66
+ }