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,174 @@
1
+ module EasyAdmin
2
+ class SidebarComponent < Phlex::HTML
3
+ def initialize(items: nil, current_path: nil)
4
+ @items = items || EasyAdmin.configuration.sidebar_items
5
+ @current_path = current_path
6
+ end
7
+
8
+ def view_template
9
+ div(data: { controller: "sidebar-mobile" }) do
10
+ # Sidebar overlay for mobile
11
+ div(
12
+ class: "fixed inset-0 z-40 lg:hidden bg-gray-600 bg-opacity-75 transition-opacity duration-300 opacity-0 pointer-events-none sidebar-overlay",
13
+ data: {
14
+ sidebar_target: "overlay",
15
+ sidebar_mobile_target: "overlay",
16
+ action: "click->sidebar-mobile#close click->sidebar#close"
17
+ }
18
+ )
19
+
20
+ # Sidebar container
21
+ aside(
22
+ class: "fixed inset-y-0 left-0 z-50 w-64 bg-white border-r border-gray-200 transform transition-transform duration-300 -translate-x-full lg:translate-x-0 shadow-lg sidebar",
23
+ data: {
24
+ sidebar_target: "container",
25
+ sidebar_mobile_target: "container",
26
+ controller: "sidebar-nav"
27
+ }
28
+ ) do
29
+ # Header with logo and brand
30
+ div(class: "flex items-center justify-between px-6 py-4 border-b border-gray-200 bg-gradient-to-r from-blue-600 to-blue-700 transition-all duration-300") do
31
+ # Logo container - centers when collapsed
32
+ div(class: "flex items-center space-x-3 transition-all duration-300 sidebar-logo-container") do
33
+ div(class: "min-w-[2.5rem] w-10 h-10 bg-white rounded-lg flex items-center justify-center flex-shrink-0 sidebar-logo") do
34
+ span(class: "text-blue-600 font-bold text-xl") { "EA" }
35
+ end
36
+ # App name - hidden when collapsed
37
+ h2(class: "text-white font-semibold text-lg hidden lg:block transition-opacity duration-300 sidebar-app-name") do
38
+ EasyAdmin.configuration.app_name
39
+ end
40
+ end
41
+
42
+ # Mobile close button
43
+ button(
44
+ class: "lg:hidden p-1 rounded-md text-white hover:bg-blue-800 transition-colors",
45
+ data: { action: "click->sidebar#toggle" }
46
+ ) do
47
+ unsafe_raw('<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>')
48
+ end
49
+ end
50
+
51
+ # Navigation menu
52
+ nav(class: "flex-1 px-4 py-6 overflow-y-auto") do
53
+ ul(class: "space-y-2") do
54
+ @items.each do |item|
55
+ render_sidebar_item(item)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def render_sidebar_item(item)
66
+ li do
67
+ if item[:children]&.any?
68
+ render_expandable_item(item)
69
+ else
70
+ render_simple_item(item)
71
+ end
72
+ end
73
+ end
74
+
75
+ def render_simple_item(item)
76
+ active_classes = is_active?(item) ? "bg-blue-50 text-blue-700 border-r-2 border-blue-600" : "text-gray-700 hover:bg-gray-50 hover:text-gray-900"
77
+
78
+ a(
79
+ href: item[:path],
80
+ class: "group flex items-center px-3 py-2.5 text-sm font-medium rounded-md transition-colors duration-200 #{active_classes}"
81
+ ) do
82
+ # Icon
83
+ if item[:icon]
84
+ span(class: "flex-shrink-0 min-w-[1.5rem] w-6 mr-3 text-lg flex items-center justify-center") { item[:icon] }
85
+ end
86
+ # Label
87
+ span(class: "truncate") { item[:label] }
88
+ end
89
+ end
90
+
91
+ def render_expandable_item(item)
92
+ has_active_child = has_active_child?(item)
93
+ active_classes = has_active_child ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-gray-50 hover:text-gray-900"
94
+
95
+ div(class: "space-y-1") do
96
+ # Expandable button
97
+ button(
98
+ class: "group w-full flex items-center justify-between px-3 py-2.5 text-sm font-medium rounded-md transition-colors duration-200 #{active_classes}",
99
+ data: {
100
+ action: "click->sidebar-nav#toggle",
101
+ sidebar_nav_target: "toggle"
102
+ }
103
+ ) do
104
+ # Icon and Label container
105
+ div(class: "flex items-center") do
106
+ # Icon
107
+ if item[:icon]
108
+ span(class: "flex-shrink-0 min-w-[1.5rem] w-6 mr-3 text-lg flex items-center justify-center") { item[:icon] }
109
+ end
110
+ # Label
111
+ span(class: "text-left truncate") { item[:label] }
112
+ end
113
+
114
+ # Chevron
115
+ unsafe_raw(%(<svg class="h-5 w-5 transform transition-transform duration-200 #{'rotate-90' if has_active_child}" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-sidebar-nav-target="chevron"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>))
116
+ end
117
+
118
+ # Submenu
119
+ div(
120
+ class: "space-y-1 #{'block' if has_active_child} #{'hidden' unless has_active_child}",
121
+ data: { sidebar_nav_target: "submenu" }
122
+ ) do
123
+ item[:children].each do |child|
124
+ render_submenu_item(child)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def render_submenu_item(item)
131
+ active_classes = is_active?(item) ? "bg-blue-50 text-blue-700 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
132
+
133
+ a(
134
+ href: item[:path],
135
+ class: "group flex items-center px-6 py-2 text-sm font-medium rounded-md transition-colors duration-200 #{active_classes}"
136
+ ) do
137
+ # Icon (smaller for submenu items)
138
+ if item[:icon]
139
+ span(class: "flex-shrink-0 min-w-[1.25rem] w-5 mr-2 text-sm flex items-center justify-center") { item[:icon] }
140
+ end
141
+ # Label
142
+ span(class: "truncate") { item[:label] }
143
+ end
144
+ end
145
+
146
+ # Helper methods for determining active state
147
+ def is_active?(item)
148
+ return false unless @current_path && item[:path]
149
+
150
+ # Exact match
151
+ return true if @current_path == item[:path]
152
+
153
+ # Check if current path starts with item path (for nested routes)
154
+ # But avoid matching root path "/" with everything
155
+ if item[:path] != "/" && @current_path.start_with?(item[:path])
156
+ return true
157
+ end
158
+
159
+ false
160
+ end
161
+
162
+ def has_active_child?(item)
163
+ return false unless item[:children]
164
+
165
+ item[:children].any? do |child|
166
+ if child[:children]
167
+ has_active_child?(child)
168
+ else
169
+ is_active?(child)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,40 @@
1
+ module EasyAdmin
2
+ module Turbo
3
+ class ResponseComponent < BaseComponent
4
+ def initialize(&block)
5
+ @builder = block
6
+ end
7
+
8
+ def view_template
9
+ # The builder receives a small DSL via `yield self`
10
+ @builder&.call(self)
11
+ end
12
+
13
+ # --- Actions API (return component instances, not html_safe strings) ---
14
+ def replace(target, **attrs, &block)
15
+ render StreamComponent.new(action: "replace", target: target, **attrs, &block)
16
+ end
17
+
18
+ def append(target, **attrs, &block)
19
+ render StreamComponent.new(action: "append", target: target, **attrs, &block)
20
+ end
21
+
22
+ def prepend(target, **attrs, &block)
23
+ render StreamComponent.new(action: "prepend", target: target, **attrs, &block)
24
+ end
25
+
26
+ def remove(target, **attrs)
27
+ render StreamComponent.new(action: "remove", target: target, **attrs)
28
+ end
29
+
30
+ def update(target, **attrs, &block)
31
+ render StreamComponent.new(action: "update", target: target, **attrs, &block)
32
+ end
33
+
34
+ # Custom action: requires Turbo.StreamActions.redirect in JS
35
+ def redirect_to(url)
36
+ render StreamComponent.new(action: "redirect", url: url)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ module EasyAdmin
2
+ module Turbo
3
+ class StreamComponent < BaseComponent
4
+ def initialize(action:, target: nil, targets: nil, **attrs, &block)
5
+ @action = action
6
+ @target = target
7
+ @targets = targets
8
+ @attrs = attrs
9
+ @block = block
10
+ end
11
+
12
+ def view_template
13
+ attrs = { action: @action }.merge(@attrs)
14
+ attrs[:target] = @target if @target
15
+ attrs[:targets] = @targets if @targets
16
+
17
+ turbo_stream(**attrs) do
18
+ if @block
19
+ template do
20
+ # Yield the block so callers can `render` nested components
21
+ @block.call(self)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ module EasyAdmin
2
+ class ApplicationController < ActionController::Base
3
+ include Pagy::Backend
4
+
5
+ helper EasyAdmin::FieldsHelper
6
+ helper EasyAdmin::PagyHelper
7
+
8
+ before_action :authenticate_easy_admin_admin_user!
9
+ before_action :set_active_storage_url_options
10
+ before_action :set_feature_toggles
11
+
12
+ protected
13
+
14
+ def authenticate_easy_admin_admin_user!
15
+ authenticate_admin_user! || redirect_to_sign_in
16
+ end
17
+
18
+ private
19
+
20
+ def authenticate_admin_user!
21
+ return true if admin_user_signed_in?
22
+ false
23
+ end
24
+
25
+ def admin_user_signed_in?
26
+ !!current_admin_user
27
+ end
28
+
29
+ def current_admin_user
30
+ @current_admin_user ||= warden.authenticate(scope: :admin_user)
31
+ end
32
+
33
+ def redirect_to_sign_in
34
+ redirect_to new_admin_user_session_path, alert: 'You need to sign in to access this page.'
35
+ end
36
+
37
+ def set_active_storage_url_options
38
+ ActiveStorage::Current.url_options = { host: request.base_url } if defined?(ActiveStorage::Current)
39
+ end
40
+
41
+ def set_feature_toggles
42
+ return unless EasyAdmin.configuration.global_settings && defined?(Flipper)
43
+
44
+ @feature_toggles = Flipper.features.map do |feature|
45
+ {
46
+ name: feature.name,
47
+ enabled: feature.enabled?,
48
+ description: feature_description(feature.name)
49
+ }
50
+ end
51
+ end
52
+
53
+ def feature_description(feature_name)
54
+ descriptions = {
55
+ "new_ui" => "Enable the new user interface design",
56
+ "advanced_search" => "Enable advanced search functionality",
57
+ "dark_mode" => "Enable dark mode theme",
58
+ "beta_features" => "Enable beta features for testing"
59
+ }
60
+
61
+ descriptions[feature_name.to_s] || "Toggle #{feature_name.humanize.downcase}"
62
+ end
63
+
64
+ helper_method :current_admin_user, :admin_user_signed_in?
65
+ end
66
+ end
@@ -0,0 +1,166 @@
1
+ module EasyAdmin
2
+ class BatchActionsController < ApplicationController
3
+ before_action :set_resource_class
4
+ before_action :set_selected_records, only: [:execute, :form]
5
+
6
+ def execute
7
+ action_class_name = params[:action_class]
8
+ execution_mode = params[:execution_mode]
9
+
10
+ begin
11
+ action_class = action_class_name.constantize
12
+ action_instance = action_class.new
13
+
14
+ # Set attributes for modal actions
15
+ if execution_mode == 'modal'
16
+ action_instance.assign_attributes(batch_action_params(action_class))
17
+
18
+ unless action_instance.valid?
19
+ respond_to do |format|
20
+ format.turbo_stream do
21
+ render turbo_stream: [
22
+ turbo_stream.update("notifications",
23
+ EasyAdmin::NotificationComponent.new(
24
+ type: :error,
25
+ message: action_instance.errors.full_messages.join(', '),
26
+ title: "Validation Error"
27
+ ).call
28
+ ),
29
+ ]
30
+ end
31
+ end
32
+ return
33
+ end
34
+ end
35
+
36
+ # Set up the action instance with selected records and current user
37
+ action_instance.selected_ids = @selected_records.pluck(:id)
38
+ action_instance.current_user = current_admin_user
39
+ action_instance.resource_class = @resource_class
40
+ action_instance.params = batch_action_params(action_class).to_h
41
+ # Execute the batch action
42
+ result = action_instance.perform
43
+
44
+ respond_to do |format|
45
+ format.turbo_stream do
46
+ if result[:success]
47
+ # Update each affected row individually
48
+ streams = [
49
+ turbo_stream.update("notifications",
50
+ EasyAdmin::NotificationComponent.new(
51
+ type: :success,
52
+ message: result[:message] || 'Action completed successfully',
53
+ title: "Success"
54
+ ).call
55
+ )
56
+ ]
57
+
58
+ # Add individual row updates
59
+ @selected_records.each do |record|
60
+ # Reload record to get updated data
61
+ record.reload
62
+ streams << turbo_stream.replace(
63
+ helpers.dom_id(record),
64
+ EasyAdmin::Resources::TableRowComponent.new(
65
+ record: record,
66
+ resource_class: @resource_class
67
+ ).call
68
+ )
69
+ end
70
+
71
+ render turbo_stream: streams
72
+ else
73
+ render turbo_stream: [
74
+ turbo_stream.update("notifications",
75
+ EasyAdmin::NotificationComponent.new(
76
+ type: :error,
77
+ message: result[:message] || 'Action failed',
78
+ title: "Error"
79
+ ).call
80
+ ),
81
+ ]
82
+ end
83
+ end
84
+ end
85
+
86
+ rescue NameError => e
87
+ Rails.logger.error e.backtrace
88
+ respond_to do |format|
89
+ format.turbo_stream do
90
+ render turbo_stream: turbo_stream.update("notifications",
91
+ EasyAdmin::NotificationComponent.new(
92
+ type: :error,
93
+ message: 'Invalid action class',
94
+ title: "Error"
95
+ ).call
96
+ )
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def form
103
+ action_class_name = params[:action_class]
104
+ selected_ids = params[:selected_ids]&.split(',') || []
105
+
106
+ begin
107
+ action_class = action_class_name.constantize
108
+
109
+ respond_to do |format|
110
+ format.turbo_stream do
111
+ render turbo_stream: turbo_stream.update(
112
+ 'modal',
113
+ render_batch_action_form(action_class, selected_ids).call
114
+ )
115
+ end
116
+ end
117
+
118
+ rescue NameError
119
+ respond_to do |format|
120
+ format.turbo_stream do
121
+ render turbo_stream: turbo_stream.update("notifications",
122
+ EasyAdmin::NotificationComponent.new(
123
+ type: :error,
124
+ message: 'Invalid action class',
125
+ title: "Error"
126
+ ).call
127
+ )
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def set_resource_class
136
+ @resource_class = EasyAdmin::ResourceRegistry.find_resource(params[:resource_name])
137
+ end
138
+
139
+ def set_selected_records
140
+ selected_ids = params[:selected_ids]
141
+ selected_ids = selected_ids.split(',') if selected_ids.is_a?(String)
142
+ selected_ids = selected_ids || []
143
+
144
+ @selected_records = @resource_class.model_class.where(id: selected_ids)
145
+ end
146
+
147
+ def batch_action_params(action_class)
148
+ return {} unless params[:batch_action].present?
149
+
150
+ # Get the allowed parameters from the action class
151
+ allowed_params = action_class.attribute_names
152
+ params.require(:batch_action).permit(allowed_params)
153
+ end
154
+
155
+ def render_batch_action_form(action_class, selected_ids)
156
+ submit_url = "/admin/#{@resource_class.route_key}/batch_action"
157
+ EasyAdmin::BatchActionFormComponent.new(
158
+ resource_class: @resource_class,
159
+ action_class: action_class,
160
+ selected_records: @selected_records,
161
+ selected_ids: selected_ids,
162
+ submit_url: submit_url
163
+ )
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,20 @@
1
+ module EasyAdmin
2
+ class ConfirmationModalController < ApplicationController
3
+ def show
4
+ respond_to do |format|
5
+ format.turbo_stream do
6
+ render turbo_stream: turbo_stream.update(
7
+ "modal",
8
+ EasyAdmin::ConfirmationModalComponent.new(
9
+ title: params[:title] || "Confirm",
10
+ message: params[:message] || "Are you sure?",
11
+ confirm_text: params[:confirm_text] || "Confirm",
12
+ cancel_text: params[:cancel_text] || "Cancel",
13
+ danger: params[:danger] == "true"
14
+ ).call
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module EasyAdmin
2
+ class DashboardController < ApplicationController
3
+ def index
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,123 @@
1
+ module EasyAdmin
2
+ class DashboardsController < ApplicationController
3
+ before_action :find_dashboard_class
4
+
5
+ def show
6
+ @dashboard_instance = @dashboard_class.new(dashboard_params)
7
+
8
+ respond_to do |format|
9
+ format.html
10
+ format.turbo_stream do
11
+ render turbo_stream: turbo_stream.replace(
12
+ "dashboard-grid",
13
+ EasyAdmin::Dashboards::ShowComponent.new(
14
+ dashboard_class: @dashboard_class,
15
+ params: dashboard_params,
16
+ request_path: request.path
17
+ ).call
18
+ )
19
+ end
20
+ format.json { render json: dashboard_data }
21
+ end
22
+ end
23
+
24
+ def card
25
+ @dashboard_instance = @dashboard_class.new(dashboard_params)
26
+ @card = @dashboard_class.find_card(params[:card_name])
27
+
28
+ unless @card
29
+ render json: { error: "Card not found" }, status: :not_found
30
+ return
31
+ end
32
+
33
+ @card_data = @dashboard_instance.card_data(@card[:name])
34
+
35
+ respond_to do |format|
36
+ format.html do
37
+ render plain: EasyAdmin::Dashboards::CardStreamComponent.new(
38
+ card: @card,
39
+ card_data: @card_data,
40
+ dashboard_class: @dashboard_class
41
+ ).call
42
+ end
43
+ format.turbo_stream do
44
+ render turbo_stream: turbo_stream.replace(
45
+ "card_#{@card[:name]}",
46
+ EasyAdmin::Dashboards::CardStreamComponent.new(
47
+ card: @card,
48
+ card_data: @card_data,
49
+ dashboard_class: @dashboard_class
50
+ ).call
51
+ )
52
+ end
53
+ format.json { render json: @card_data }
54
+ end
55
+ rescue => e
56
+ Rails.logger.error "Dashboard card error: #{e.message}"
57
+
58
+ respond_to do |format|
59
+ format.html do
60
+ render plain: view_context.turbo_frame_tag("card_#{params[:card_name]}") do
61
+ EasyAdmin::Dashboards::CardErrorComponent.new(error: e.message).call
62
+ end
63
+ end
64
+ format.turbo_stream do
65
+ render turbo_stream: turbo_stream.replace(
66
+ "card_#{params[:card_name]}",
67
+ view_context.turbo_frame_tag("card_#{params[:card_name]}") do
68
+ EasyAdmin::Dashboards::CardErrorComponent.new(error: e.message).call
69
+ end
70
+ )
71
+ end
72
+ format.json { render json: { error: e.message }, status: :server_error }
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def find_dashboard_class
79
+ @dashboard_class = EasyAdmin::DashboardRegistry.find(params[:dashboard_name])
80
+
81
+ unless @dashboard_class
82
+ raise "Dashboard '#{params[:dashboard_name]}' not found"
83
+ end
84
+ end
85
+
86
+ def dashboard_params
87
+ params.permit(:date_from, :date_to, :segment, :search, :period, :refresh)
88
+ end
89
+
90
+ def dashboard_data
91
+ {
92
+ title: @dashboard_class.title,
93
+ columns: @dashboard_class.columns,
94
+ cards: @dashboard_class.visible_cards.map do |card|
95
+ card.merge(
96
+ data: @dashboard_instance.card_data(card[:name]),
97
+ url: card_url(card[:name])
98
+ )
99
+ end
100
+ }
101
+ end
102
+
103
+ def card_partial
104
+ partial_name = @card[:partial] || "easy_admin/dashboards/cards/#{@card[:type]}"
105
+
106
+ # Check if custom partial exists
107
+ if lookup_context.exists?(partial_name, [], true)
108
+ partial_name
109
+ else
110
+ "easy_admin/dashboards/cards/#{@card[:type]}"
111
+ end
112
+ end
113
+
114
+
115
+ def card_url(card_name)
116
+ easy_admin.dashboard_card_path(
117
+ dashboard_name: params[:dashboard_name],
118
+ card_name: card_name,
119
+ **dashboard_params.to_h.symbolize_keys
120
+ )
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,15 @@
1
+ module EasyAdmin
2
+ class PasswordsController < Devise::PasswordsController
3
+ layout 'easy_admin/auth'
4
+
5
+ protected
6
+
7
+ def after_resetting_password_path_for(resource)
8
+ easy_admin.root_path
9
+ end
10
+
11
+ def after_sending_reset_password_instructions_path_for(resource_name)
12
+ new_easy_admin_admin_user_session_path
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module EasyAdmin
2
+ class RegistrationsController < Devise::RegistrationsController
3
+ layout 'easy_admin/auth'
4
+
5
+ # GET /easy_admin/sign_up
6
+ def new
7
+ super
8
+ end
9
+
10
+ # POST /easy_admin/sign_up
11
+ def create
12
+ super
13
+ end
14
+
15
+ # GET /easy_admin/edit
16
+ def edit
17
+ super
18
+ end
19
+
20
+ # PATCH/PUT /easy_admin/
21
+ def update
22
+ super
23
+ end
24
+
25
+ # DELETE /easy_admin/
26
+ def destroy
27
+ super
28
+ end
29
+
30
+ protected
31
+
32
+ # If you have extra params to permit, append them to the sanitizer.
33
+ def configure_sign_up_params
34
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name])
35
+ end
36
+
37
+ # If you have extra params to permit, append them to the sanitizer.
38
+ def configure_account_update_params
39
+ devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name])
40
+ end
41
+
42
+ # The path used after signing up
43
+ def after_sign_up_path_for(resource)
44
+ easy_admin_root_path
45
+ end
46
+
47
+ # The path used after account update
48
+ def after_update_path_for(resource)
49
+ easy_admin_root_path
50
+ end
51
+ end
52
+ end