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,100 @@
1
+ <% content_for :title, "New #{@resource_class.singular_title}" %>
2
+
3
+ <!-- Page Header -->
4
+ <div class="mb-6">
5
+ <div class="flex items-center justify-between">
6
+ <nav class="flex" aria-label="Breadcrumb">
7
+ <ol class="inline-flex items-center space-x-1 md:space-x-3">
8
+ <li>
9
+ <%= link_to easy_admin.resources_path(@resource_class.route_key),
10
+ class: "text-gray-500 hover:text-gray-700 text-sm font-medium" do %>
11
+ <%= @resource_class.title %>
12
+ <% end %>
13
+ </li>
14
+ <li>
15
+ <div class="flex items-center">
16
+ <svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
17
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
18
+ </svg>
19
+ <span class="ml-1 text-sm font-medium text-gray-900">New</span>
20
+ </div>
21
+ </li>
22
+ </ol>
23
+ </nav>
24
+
25
+ <div>
26
+ <%= link_to easy_admin.resources_path(@resource_class.route_key),
27
+ class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
28
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m0 7h18"/>
30
+ </svg>
31
+ Back to <%= @resource_class.title %>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Form Container -->
38
+ <div class="max-w-4xl mx-auto">
39
+ <%= form_with model: [@resource_class.model_class.new], url: easy_admin.resources_path(@resource_class.route_key),
40
+ local: true, multipart: true, class: "space-y-6" do |form| %>
41
+
42
+ <!-- Error Messages -->
43
+ <% if @record && @record.errors.any? %>
44
+ <div class="bg-red-50 border border-red-200 rounded-md p-4">
45
+ <div class="flex">
46
+ <div class="flex-shrink-0">
47
+ <svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
48
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
49
+ </svg>
50
+ </div>
51
+ <div class="ml-3">
52
+ <h3 class="text-sm font-medium text-red-800">
53
+ <%= pluralize(@record.errors.count, "error") %> prohibited this <%= @resource_class.singular_title.downcase %> from being saved:
54
+ </h3>
55
+ <div class="mt-2 text-sm text-red-700">
56
+ <ul class="list-disc pl-5 space-y-1">
57
+ <% @record.errors.full_messages.each do |message| %>
58
+ <li><%= message %></li>
59
+ <% end %>
60
+ </ul>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <% end %>
66
+
67
+ <!-- Form Fields -->
68
+ <% if @resource_class.has_form_tabs? %>
69
+ <%= EasyAdmin::FormTabsComponent.new(resource_class: @resource_class, form: form).call.html_safe %>
70
+ <% else %>
71
+ <!-- Default Single Card Layout -->
72
+ <div class="bg-white shadow-sm rounded-lg border border-gray-200">
73
+ <div class="px-4 py-5 sm:p-6">
74
+ <h3 class="text-lg font-medium leading-6 text-gray-900 mb-6">
75
+ <%= @resource_class.singular_title %> Information
76
+ </h3>
77
+
78
+ <div class="grid grid-cols-1 gap-6">
79
+ <% @resource_class.form_fields.each do |field| %>
80
+ <div>
81
+ <%= render_field(field, action: :form, form: form) %>
82
+ </div>
83
+ <% end %>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <% end %>
88
+
89
+ <!-- Action Buttons -->
90
+ <div class="flex items-center justify-end space-x-3 pt-6">
91
+ <%= link_to easy_admin.resources_path(@resource_class.route_key),
92
+ class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
93
+ Cancel
94
+ <% end %>
95
+
96
+ <%= form.submit "Create #{@resource_class.singular_title}",
97
+ class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
98
+ </div>
99
+ <% end %>
100
+ </div>
@@ -0,0 +1,31 @@
1
+ <% content_for :title, "#{@resource_class.singular_title} ##{@record.id}" %>
2
+
3
+ <!-- Page Header -->
4
+ <div class="mb-6">
5
+ <div class="sm:flex sm:items-center">
6
+ <div class="sm:flex-auto">
7
+ <div class="flex items-center space-x-3 mb-2">
8
+ <%= link_to easy_admin.resources_path(@resource_class.route_key),
9
+ class: "inline-flex items-center text-sm text-gray-500 hover:text-gray-700 transition-colors duration-150" do %>
10
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
12
+ </svg>
13
+ Back to <%= @resource_class.title %>
14
+ <% end %>
15
+ </div>
16
+ <h1 class="text-2xl font-bold leading-6 text-gray-900">
17
+ <%= @resource_class.singular_title %> #<%= @record.id %>
18
+ </h1>
19
+ </div>
20
+ <%== EasyAdmin::Resources::ShowPageActionsComponent.new(
21
+ record: @record,
22
+ resource_class: @resource_class,
23
+ current_user: current_admin_user
24
+ ).call %>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Show Layout Content -->
29
+ <div class="show-content">
30
+ <%== EasyAdmin::ShowLayoutComponent.new(resource_class: @resource_class, record: @record).call %>
31
+ </div>
@@ -0,0 +1,55 @@
1
+ <!-- Form -->
2
+ <div class="p-10">
3
+ <%= form_for(resource, as: resource_name, url: session_path(resource_name), local: true, html: { class: "space-y-8" }) do |f| %>
4
+
5
+ <!-- Input Fields Container -->
6
+ <div class="space-y-4">
7
+ <!-- Email Field -->
8
+ <div class="relative">
9
+ <%= f.email_field :email, autofocus: true, autocomplete: "email",
10
+ class: "w-full px-6 py-5 bg-gray-50/80 border-0 text-gray-900 placeholder-gray-400 focus:bg-gray-100/80 focus:ring-0 focus:outline-none text-base rounded-2xl transition-colors duration-200 font-medium",
11
+ placeholder: "Email address" %>
12
+ </div>
13
+
14
+ <!-- Password Field -->
15
+ <div class="relative">
16
+ <%= f.password_field :password, autocomplete: "current-password",
17
+ class: "w-full px-6 py-5 bg-gray-50/80 border-0 text-gray-900 placeholder-gray-400 focus:bg-gray-100/80 focus:ring-0 focus:outline-none text-base rounded-2xl transition-colors duration-200 font-medium",
18
+ placeholder: "Password" %>
19
+ </div>
20
+ </div>
21
+
22
+ <!-- Remember Me -->
23
+ <% if devise_mapping.rememberable? %>
24
+ <div class="flex items-center justify-between">
25
+ <label class="flex items-center">
26
+ <%= f.check_box :remember_me, class: "h-5 w-5 text-blue-500 focus:ring-blue-500/30 focus:ring-4 border-gray-300 rounded-lg transition-all duration-200" %>
27
+ <span class="ml-3 text-base text-gray-700 font-medium">Remember me</span>
28
+ </label>
29
+
30
+ <% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
31
+ <%= link_to "Forgot?", new_password_path(resource_name),
32
+ class: "text-base text-blue-500 hover:text-blue-600 font-semibold transition-colors duration-200" %>
33
+ <% end %>
34
+ </div>
35
+ <% end %>
36
+
37
+ <!-- Submit Button -->
38
+ <div class="pt-4">
39
+ <%= f.submit "Sign In",
40
+ class: "w-full bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-bold py-5 px-6 rounded-2xl transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-blue-500/30 text-base shadow-xl shadow-blue-500/20 active:scale-[0.98]" %>
41
+ </div>
42
+
43
+ <% end %>
44
+
45
+ <!-- Links -->
46
+ <div class="text-center pt-6">
47
+ <% if devise_mapping.registerable? && controller_name != 'registrations' %>
48
+ <div class="text-base text-gray-600">
49
+ New to EasyAdmin?
50
+ <%= link_to "Create account", new_registration_path(resource_name),
51
+ class: "text-blue-500 hover:text-blue-600 font-bold ml-2 transition-colors duration-200" %>
52
+ </div>
53
+ <% end %>
54
+ </div>
55
+ </div>
@@ -0,0 +1,51 @@
1
+ <%= form_with url: settings_path, method: :patch, class: "space-y-6" do |form| %>
2
+ <!-- Feature Toggles Section -->
3
+ <div>
4
+ <h3 class="text-md font-medium text-gray-900 mb-4">Feature Toggles</h3>
5
+
6
+ <div class="space-y-4">
7
+ <% @feature_toggles.each do |feature| %>
8
+ <div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg border border-gray-200">
9
+ <div class="flex-1">
10
+ <%= form.label "features_#{feature[:name]}", feature[:name].humanize,
11
+ class: "block text-sm font-medium text-gray-900 cursor-pointer" %>
12
+
13
+ <% if feature[:description].present? %>
14
+ <p class="text-xs text-gray-600 mt-1"><%= feature[:description] %></p>
15
+ <% end %>
16
+ </div>
17
+
18
+ <!-- Toggle switch -->
19
+ <div class="ml-4">
20
+ <%= form.hidden_field "features[#{feature[:name]}]", value: "0" %>
21
+
22
+ <%= form.check_box "features[#{feature[:name]}]",
23
+ {
24
+ checked: feature[:enabled],
25
+ class: "sr-only",
26
+ id: "features_#{feature[:name]}",
27
+ value: "1",
28
+ data: { action: "change->settings-sidebar#toggleFeature" }
29
+ } %>
30
+
31
+ <%= form.label "features_#{feature[:name]}",
32
+ class: "relative inline-flex items-center h-6 rounded-full w-11 cursor-pointer transition-colors ease-in-out duration-200 #{'bg-blue-600' if feature[:enabled]} #{'bg-gray-300' unless feature[:enabled]}" do %>
33
+ <span class="inline-block w-4 h-4 transform bg-white rounded-full transition ease-in-out duration-200 <%= 'translate-x-6' if feature[:enabled] %> <%= 'translate-x-1' unless feature[:enabled] %>"></span>
34
+ <% end %>
35
+ </div>
36
+ </div>
37
+ <% end %>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Save button -->
42
+ <div class="pt-6 border-t border-gray-200">
43
+ <%= form.submit "Save Settings",
44
+ class: "w-full inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors" do %>
45
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
46
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
47
+ </svg>
48
+ <span>Save Settings</span>
49
+ <% end %>
50
+ </div>
51
+ <% end %>
@@ -0,0 +1,53 @@
1
+ <turbo-frame id="settings-form" class="space-y-6">
2
+ <%= form_with url: settings_path, method: :patch, class: "space-y-6" do |form| %>
3
+ <!-- Feature Toggles Section -->
4
+ <div>
5
+ <h3 class="text-md font-medium text-gray-900 mb-4">Feature Toggles</h3>
6
+
7
+ <div class="space-y-4">
8
+ <% @feature_toggles.each do |feature| %>
9
+ <div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg border border-gray-200">
10
+ <div class="flex-1">
11
+ <%= form.label "features_#{feature[:name]}", feature[:name].humanize,
12
+ class: "block text-sm font-medium text-gray-900 cursor-pointer" %>
13
+
14
+ <% if feature[:description].present? %>
15
+ <p class="text-xs text-gray-600 mt-1"><%= feature[:description] %></p>
16
+ <% end %>
17
+ </div>
18
+
19
+ <!-- Toggle switch -->
20
+ <div class="ml-4">
21
+ <%= form.hidden_field "features[#{feature[:name]}]", value: "0" %>
22
+
23
+ <%= form.check_box "features[#{feature[:name]}]",
24
+ {
25
+ checked: feature[:enabled],
26
+ class: "sr-only",
27
+ id: "features_#{feature[:name]}",
28
+ value: "1",
29
+ data: { action: "change->settings-sidebar#toggleFeature" }
30
+ } %>
31
+
32
+ <%= form.label "features_#{feature[:name]}",
33
+ class: "relative inline-flex items-center h-6 rounded-full w-11 cursor-pointer transition-colors ease-in-out duration-200 #{'bg-blue-600' if feature[:enabled]} #{'bg-gray-300' unless feature[:enabled]}" do %>
34
+ <span class="inline-block w-4 h-4 transform bg-white rounded-full transition ease-in-out duration-200 <%= 'translate-x-6' if feature[:enabled] %> <%= 'translate-x-1' unless feature[:enabled] %>"></span>
35
+ <% end %>
36
+ </div>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- Save button -->
43
+ <div class="pt-6 border-t border-gray-200">
44
+ <%= form.submit "Save Settings",
45
+ class: "w-full inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors" do %>
46
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
47
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
48
+ </svg>
49
+ <span>Save Settings</span>
50
+ <% end %>
51
+ </div>
52
+ <% end %>
53
+ </turbo-frame>
@@ -0,0 +1,48 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Easy admin</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= yield :head %>
10
+
11
+ <%= stylesheet_link_tag "easy_admin", media: "all" %>
12
+ <%= javascript_include_tag "easy_admin.base", "data-turbo-track": "reload", defer: true %>
13
+ </head>
14
+ <body class="bg-gray-50 font-sans text-gray-900 min-h-screen pt-20" data-controller="sidebar navbar-scroll settings-sidebar" data-navbar-scroll-target="body">
15
+
16
+ <!-- Mobile toggle button -->
17
+ <button class="lg:hidden fixed top-6 left-6 z-50 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors" data-action="click->sidebar#toggle" title="Toggle sidebar">
18
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
20
+ </svg>
21
+ </button>
22
+
23
+ <%== EasyAdmin::SidebarComponent.new(current_path: request.path).call %>
24
+
25
+ <%== EasyAdmin::NavbarComponent.new(
26
+ page_title: content_for?(:page_title) ? yield(:page_title) : "Dashboard",
27
+ breadcrumbs: content_for?(:breadcrumbs) ? yield(:breadcrumbs) : nil
28
+ ).call %>
29
+
30
+ <main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-50 lg:ml-64 transition-all duration-300" data-sidebar-target="content">
31
+ <div class="container mx-auto px-6 py-8">
32
+ <%= yield %>
33
+ </div>
34
+ </main>
35
+
36
+ <!-- Notifications Turbo Frame -->
37
+ <turbo-frame id="notifications"></turbo-frame>
38
+
39
+ <!-- Modal Container -->
40
+ <turbo-frame id="modal"></turbo-frame>
41
+
42
+ <!-- Settings Sidebar - only if global_settings enabled and Flipper available -->
43
+ <% if EasyAdmin.configuration.global_settings && defined?(Flipper) %>
44
+ <%== EasyAdmin::SettingsSidebarComponent.new(feature_toggles: @feature_toggles || []).call %>
45
+ <% end %>
46
+
47
+ </body>
48
+ </html>
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>EasyAdmin - Authentication</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= yield :head %>
10
+
11
+ <%= stylesheet_link_tag "easy_admin", media: "all" %>
12
+ <%= javascript_include_tag "easy_admin.base", "data-turbo-track": "reload", defer: true %>
13
+ </head>
14
+ <body class="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 flex items-center justify-center p-4 font-sans antialiased">
15
+
16
+ <div class="w-full max-w-sm">
17
+ <!-- Auth Card -->
18
+ <div class="bg-white/90 backdrop-blur-2xl rounded-[28px] shadow-2xl shadow-black/5 border border-white/20 overflow-hidden">
19
+ <%= yield %>
20
+ </div>
21
+
22
+ <!-- Footer -->
23
+ <div class="text-center mt-12">
24
+ <p class="text-gray-400 text-sm">
25
+ Powered by <span class="font-semibold text-gray-500">EasyAdmin</span>
26
+ </p>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- Notifications Turbo Frame -->
31
+ <turbo-frame id="notifications"></turbo-frame>
32
+
33
+ </body>
34
+ </html>
@@ -0,0 +1,27 @@
1
+ # EasyAdmin Card Factory Configuration
2
+ #
3
+ # This file demonstrates how to extend the card factory with custom components.
4
+ # You can register custom card components or override existing ones.
5
+
6
+ # Example: Register a custom card component
7
+ # EasyAdmin::Dashboards::CardFactory.register(:kpi, MyApp::KpiCardComponent)
8
+ # EasyAdmin::Dashboards::CardFactory.register(:analytics, MyApp::AnalyticsCardComponent)
9
+
10
+ # Example: Register a third-party integration
11
+ # EasyAdmin::Dashboards::CardFactory.register(:stripe_metrics, StripeIntegration::MetricsCardComponent)
12
+
13
+ # The factory will automatically try to resolve components in this order:
14
+ # 1. Registered custom components (via CardFactory.register)
15
+ # 2. Component class specified in card configuration (component_class: "MyComponent")
16
+ # 3. Naming convention in gem namespace (EasyAdmin::Dashboards::Cards::TypeCardComponent)
17
+ # 4. Naming convention in app namespace (App::Dashboards::Cards::TypeCardComponent)
18
+ # 5. Fallback to CustomCardComponent
19
+
20
+ # Example card configuration using custom component class:
21
+ #
22
+ # dashboard "analytics" do
23
+ # card :special_metric,
24
+ # type: :custom,
25
+ # component_class: "MyApp::SpecialMetricCardComponent",
26
+ # title: "Special Metric"
27
+ # end
@@ -0,0 +1,15 @@
1
+ require 'pagy'
2
+ require 'pagy/extras/countless'
3
+
4
+ # Pagy configuration for EasyAdmin
5
+ Pagy::DEFAULT[:items] = 10 # items per page - changed from 20 to debug override issues
6
+ Pagy::DEFAULT[:size] = 7 # number of page links to show (was [1, 4, 4, 1] in older versions)
7
+ Pagy::DEFAULT[:page_param] = :page # page parameter name
8
+
9
+ # Performance optimization: Use countless pagination for large datasets
10
+ # This avoids expensive COUNT queries but shows "Page X" instead of "Page X of Y"
11
+ Pagy::DEFAULT[:countless_minimal] = false
12
+
13
+ # You can also configure other Pagy variables here if needed
14
+ # Pagy::DEFAULT[:max_pages] = 100 # max pages
15
+ # Pagy::DEFAULT[:overflow] = :empty_page # :last_page, :empty_page
@@ -0,0 +1,67 @@
1
+ # Rack Mini Profiler configuration for EasyAdmin
2
+ if defined?(Rack::MiniProfiler)
3
+ # Check if profiler is enabled in configuration
4
+ profiler_enabled = begin
5
+ EasyAdmin.configuration.enable_profiler
6
+ rescue => e
7
+ # Fallback if configuration not yet loaded
8
+ Rails.env.development?
9
+ end
10
+
11
+ if profiler_enabled
12
+ # Configure profiler settings
13
+ Rack::MiniProfiler.config.tap do |config|
14
+ # Position the profiler widget
15
+ config.position = 'bottom-left'
16
+
17
+ # Only show for admin users (if available)
18
+ config.authorization_mode = :allow_all
19
+
20
+ # Skip profiling for certain paths
21
+ config.skip_paths = [
22
+ '/assets/',
23
+ '/favicon.ico',
24
+ '/admin/assets/',
25
+ %r{^/admin.*\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$}
26
+ ]
27
+
28
+ # Enable backtrace profiling for slow queries
29
+ config.backtrace_includes = [/easy_admin/, /app\//]
30
+ config.backtrace_ignores = []
31
+
32
+ # Show SQL queries by default
33
+ config.enable_advanced_debugging_tools = true
34
+
35
+ # Storage (use file storage in development)
36
+ config.storage = Rack::MiniProfiler::FileStore
37
+ config.storage_options = { path: Rails.root.join('tmp', 'miniprofiler') }
38
+
39
+ # Only profile requests that take longer than this (in milliseconds)
40
+ config.flamegraph_sample_rate = 0.5
41
+
42
+ # Custom colors for EasyAdmin
43
+ config.backtrace_threshold_ms = 0 # Show all backtraces
44
+ end
45
+
46
+ # Custom authorization for production-like environments
47
+ if Rails.env.staging?
48
+ Rack::MiniProfiler.config.authorization_mode = :whitelist
49
+ # You can customize this to check for admin users
50
+ # Rack::MiniProfiler.config.pre_authorize_cb = lambda { |env|
51
+ # # Check if current user is admin
52
+ # false # Disable by default in staging
53
+ # }
54
+ end
55
+
56
+ # Add custom timing for EasyAdmin components
57
+ if defined?(EasyAdmin)
58
+ # Hook into Rails to add custom profiling points
59
+ ActiveSupport::Notifications.subscribe("render_component.phlex") do |name, start, finish, id, payload|
60
+ if defined?(Rack::MiniProfiler) && Rack::MiniProfiler.current
61
+ duration_ms = (finish - start) * 1000
62
+ Rack::MiniProfiler.current.custom_timing("phlex", "#{payload[:component]}", duration_ms)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,70 @@
1
+ EasyAdmin::Engine.routes.draw do
2
+ # Authentication routes
3
+ devise_for :admin_users,
4
+ class_name: 'EasyAdmin::AdminUser',
5
+ controllers: {
6
+ sessions: 'easy_admin/sessions',
7
+ registrations: 'easy_admin/registrations',
8
+ passwords: 'easy_admin/passwords'
9
+ },
10
+ path: '',
11
+ path_names: {
12
+ sign_in: 'sign_in',
13
+ sign_out: 'sign_out',
14
+ sign_up: 'sign_up'
15
+ }
16
+
17
+ root 'dashboard#index'
18
+
19
+ # Settings routes
20
+ get 'settings', to: 'settings#index'
21
+ patch 'settings', to: 'settings#update'
22
+
23
+ # Confirmation modal
24
+ get 'confirmation_modal', to: 'confirmation_modal#show'
25
+
26
+ # Dashboard routes
27
+ scope 'dashboards', as: 'dashboards' do
28
+ get ':dashboard_name', to: 'dashboards#show', as: 'dashboard'
29
+ get ':dashboard_name/cards/:card_name', to: 'dashboards#card', as: 'dashboard_card'
30
+ end
31
+
32
+ # Generic resource routes - will be resolved dynamically in controller
33
+ scope ':resource_name', constraints: { resource_name: /[a-z_]+/ } do
34
+ get '/', to: 'resources#index', as: 'resources'
35
+ get '/new', to: 'resources#new', as: 'new_resource'
36
+ post '/', to: 'resources#create'
37
+ get '/:id', to: 'resources#show', as: 'resource'
38
+ get '/:id/edit', to: 'resources#edit', as: 'edit_resource'
39
+ patch '/:id', to: 'resources#update'
40
+ put '/:id', to: 'resources#update'
41
+ delete '/:id', to: 'resources#destroy'
42
+
43
+ # Inline field editing routes
44
+ get '/:id/fields/:field/edit', to: 'resources#edit_field', as: 'edit_field_resource'
45
+ patch '/:id/fields/:field', to: 'resources#update_field', as: 'update_field_resource'
46
+
47
+ # Suggest search route for dynamic option loading
48
+ get '/suggest/:field', to: 'resources#suggest', as: 'suggest_resource'
49
+
50
+ # Belongs_to field specific routes
51
+ get '/:id/fields/:field/reattach', to: 'resources#belongs_to_reattach', as: 'belongs_to_reattach_resource'
52
+ patch '/:id/fields/:field/reattach', to: 'resources#update_field'
53
+ get '/:id/fields/:field/edit_attached', to: 'resources#belongs_to_edit_attached', as: 'belongs_to_edit_attached_resource'
54
+ patch '/:id/fields/:field/attached/:attached_id', to: 'resources#update_belongs_to_attached', as: 'update_belongs_to_attached_resource'
55
+
56
+ # Batch action routes
57
+ post '/batch_action', to: 'batch_actions#execute', as: 'batch_action'
58
+ get '/batch_action/:action_class/form', to: 'batch_actions#form', as: 'batch_action_form'
59
+
60
+ # Row action routes (single record actions)
61
+ get '/:id/context_menu', to: 'row_actions#context_menu', as: 'context_menu'
62
+ post '/:id/actions/:action_class', to: 'row_actions#execute', as: 'row_action'
63
+ get '/:id/actions/:action_class/form', to: 'row_actions#form', as: 'row_action_form'
64
+
65
+ # Lazy loading routes for show page components
66
+ get '/:id/metrics/:metric_id', to: 'resources#metric', as: 'resource_metric'
67
+ get '/:id/charts/:chart_id', to: 'resources#chart', as: 'resource_chart'
68
+ get '/:id/tabs/:tab_name', to: 'resources#tab_content', as: 'resource_tab_content'
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ class CreateEasyAdminAdminUsers < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :easy_admin_admin_users do |t|
4
+ ## Database authenticatable
5
+ t.string :email, null: false, default: ""
6
+ t.string :encrypted_password, null: false, default: ""
7
+
8
+ ## Personal information
9
+ t.string :first_name, null: false
10
+ t.string :last_name, null: false
11
+
12
+ ## Recoverable
13
+ t.string :reset_password_token
14
+ t.datetime :reset_password_sent_at
15
+
16
+ ## Rememberable
17
+ t.datetime :remember_created_at
18
+
19
+ ## Trackable
20
+ t.integer :sign_in_count, default: 0, null: false
21
+ t.datetime :current_sign_in_at
22
+ t.datetime :last_sign_in_at
23
+ t.string :current_sign_in_ip
24
+ t.string :last_sign_in_ip
25
+
26
+ ## Confirmable
27
+ t.string :confirmation_token
28
+ t.datetime :confirmed_at
29
+ t.datetime :confirmation_sent_at
30
+ t.string :unconfirmed_email # Only if using reconfirmable
31
+
32
+ ## Lockable
33
+ t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
34
+ t.string :unlock_token # Only if unlock strategy is :email or :both
35
+ t.datetime :locked_at
36
+
37
+ t.timestamps null: false
38
+ end
39
+
40
+ add_index :easy_admin_admin_users, :email, unique: true
41
+ add_index :easy_admin_admin_users, :reset_password_token, unique: true
42
+ add_index :easy_admin_admin_users, :confirmation_token, unique: true
43
+ add_index :easy_admin_admin_users, :unlock_token, unique: true
44
+ end
45
+ end
data/lib/easy-admin.rb ADDED
@@ -0,0 +1,32 @@
1
+ require "phlex"
2
+ require "dry-monads"
3
+ require "ransack"
4
+ require "easy_admin/version"
5
+ require "easy_admin/configuration"
6
+ require "easy_admin/resource_registry"
7
+ require "easy_admin/resource"
8
+ require "easy_admin/dashboard_registry"
9
+ require "easy_admin/dashboard"
10
+ require "easy_admin/field"
11
+ require "easy_admin/action"
12
+ require "easy_admin/delete_action"
13
+ require "easy_admin/batch_action"
14
+ require "easy_admin/engine"
15
+
16
+ module EasyAdmin
17
+ class << self
18
+ attr_writer :configuration
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ def configure
25
+ yield(configuration)
26
+ end
27
+
28
+ def reset_configuration!
29
+ @configuration = Configuration.new
30
+ end
31
+ end
32
+ end