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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/builds/easy_admin.base.js +43505 -0
- data/app/assets/builds/easy_admin.base.js.map +7 -0
- data/app/assets/builds/easy_admin.css +6141 -0
- data/app/assets/config/easy_admin_manifest.js +1 -0
- data/app/assets/images/jsoneditor-icons.svg +749 -0
- data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
- data/app/components/easy_admin/base_component.rb +35 -0
- data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
- data/app/components/easy_admin/batch_action_form_component.rb +124 -0
- data/app/components/easy_admin/combined_filters_component.rb +232 -0
- data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
- data/app/components/easy_admin/context_menu_component.rb +161 -0
- data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
- data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
- data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
- data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
- data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
- data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
- data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
- data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
- data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
- data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
- data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
- data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
- data/app/components/easy_admin/dashboards/show_component.rb +163 -0
- data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
- data/app/components/easy_admin/date_picker_component.rb +188 -0
- data/app/components/easy_admin/fields/base_component.rb +101 -0
- data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
- data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
- data/app/components/easy_admin/fields/form/date_component.rb +55 -0
- data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
- data/app/components/easy_admin/fields/form/email_component.rb +55 -0
- data/app/components/easy_admin/fields/form/file_component.rb +190 -0
- data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
- data/app/components/easy_admin/fields/form/json_component.rb +81 -0
- data/app/components/easy_admin/fields/form/number_component.rb +55 -0
- data/app/components/easy_admin/fields/form/select_component.rb +326 -0
- data/app/components/easy_admin/fields/form/text_component.rb +55 -0
- data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
- data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
- data/app/components/easy_admin/fields/index/date_component.rb +13 -0
- data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/index/email_component.rb +24 -0
- data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
- data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
- data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
- data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
- data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
- data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
- data/app/components/easy_admin/fields/index/json_component.rb +23 -0
- data/app/components/easy_admin/fields/index/number_component.rb +20 -0
- data/app/components/easy_admin/fields/index/select_component.rb +25 -0
- data/app/components/easy_admin/fields/index/text_component.rb +20 -0
- data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
- data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
- data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
- data/app/components/easy_admin/fields/show/date_component.rb +13 -0
- data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/show/email_component.rb +19 -0
- data/app/components/easy_admin/fields/show/file_component.rb +304 -0
- data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
- data/app/components/easy_admin/fields/show/json_component.rb +45 -0
- data/app/components/easy_admin/fields/show/number_component.rb +20 -0
- data/app/components/easy_admin/fields/show/select_component.rb +25 -0
- data/app/components/easy_admin/fields/show/text_component.rb +17 -0
- data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
- data/app/components/easy_admin/filters_component.rb +120 -0
- data/app/components/easy_admin/form_tabs_component.rb +166 -0
- data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
- data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
- data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
- data/app/components/easy_admin/modal_frame_component.rb +26 -0
- data/app/components/easy_admin/navbar_component.rb +226 -0
- data/app/components/easy_admin/notification_component.rb +83 -0
- data/app/components/easy_admin/pagination_component.rb +188 -0
- data/app/components/easy_admin/quick_filters_component.rb +65 -0
- data/app/components/easy_admin/resource_pagination_component.rb +14 -0
- data/app/components/easy_admin/resources/index_component.rb +211 -0
- data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
- data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
- data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
- data/app/components/easy_admin/resources/table_component.rb +206 -0
- data/app/components/easy_admin/resources/table_row_component.rb +160 -0
- data/app/components/easy_admin/row_action_form_component.rb +127 -0
- data/app/components/easy_admin/scopes_component.rb +224 -0
- data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
- data/app/components/easy_admin/show_layout_component.rb +600 -0
- data/app/components/easy_admin/sidebar_component.rb +174 -0
- data/app/components/easy_admin/turbo/response_component.rb +40 -0
- data/app/components/easy_admin/turbo/stream_component.rb +28 -0
- data/app/controllers/easy_admin/application_controller.rb +66 -0
- data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
- data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
- data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
- data/app/controllers/easy_admin/passwords_controller.rb +15 -0
- data/app/controllers/easy_admin/registrations_controller.rb +52 -0
- data/app/controllers/easy_admin/resources_controller.rb +907 -0
- data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
- data/app/controllers/easy_admin/sessions_controller.rb +32 -0
- data/app/controllers/easy_admin/settings_controller.rb +94 -0
- data/app/helpers/easy_admin/application_helper.rb +4 -0
- data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
- data/app/helpers/easy_admin/fields_helper.rb +27 -0
- data/app/helpers/easy_admin/pagy_helper.rb +30 -0
- data/app/helpers/easy_admin/resources_helper.rb +39 -0
- data/app/javascript/easy_admin/application.js +12 -0
- data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
- data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
- data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
- data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
- data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
- data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
- data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
- data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
- data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
- data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
- data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
- data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
- data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
- data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
- data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
- data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
- data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
- data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
- data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
- data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
- data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
- data/app/javascript/easy_admin/controllers.js +54 -0
- data/app/javascript/easy_admin.base.js +4 -0
- data/app/models/easy_admin/admin_user.rb +53 -0
- data/app/models/easy_admin/application_record.rb +5 -0
- data/app/views/easy_admin/dashboard/index.html.erb +3 -0
- data/app/views/easy_admin/dashboards/show.html.erb +7 -0
- data/app/views/easy_admin/passwords/edit.html.erb +42 -0
- data/app/views/easy_admin/passwords/new.html.erb +41 -0
- data/app/views/easy_admin/registrations/new.html.erb +65 -0
- data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
- data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
- data/app/views/easy_admin/resources/edit.html.erb +151 -0
- data/app/views/easy_admin/resources/index.html.erb +12 -0
- data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
- data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
- data/app/views/easy_admin/resources/new.html.erb +100 -0
- data/app/views/easy_admin/resources/show.html.erb +31 -0
- data/app/views/easy_admin/sessions/new.html.erb +55 -0
- data/app/views/easy_admin/settings/_form.html.erb +51 -0
- data/app/views/easy_admin/settings/index.html.erb +53 -0
- data/app/views/layouts/easy_admin/application.html.erb +48 -0
- data/app/views/layouts/easy_admin/auth.html.erb +34 -0
- data/config/initializers/easy_admin_card_factory.rb +27 -0
- data/config/initializers/pagy.rb +15 -0
- data/config/initializers/rack_mini_profiler.rb +67 -0
- data/config/routes.rb +70 -0
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
- data/lib/easy-admin.rb +32 -0
- data/lib/easy_admin/action.rb +159 -0
- data/lib/easy_admin/batch_action.rb +134 -0
- data/lib/easy_admin/configuration.rb +75 -0
- data/lib/easy_admin/dashboard.rb +110 -0
- data/lib/easy_admin/dashboard_registry.rb +30 -0
- data/lib/easy_admin/delete_action.rb +22 -0
- data/lib/easy_admin/engine.rb +54 -0
- data/lib/easy_admin/field.rb +118 -0
- data/lib/easy_admin/resource.rb +806 -0
- data/lib/easy_admin/resource_registry.rb +22 -0
- data/lib/easy_admin/types/json_type.rb +25 -0
- data/lib/easy_admin/version.rb +3 -0
- data/lib/generators/easy_admin/auth_generator.rb +69 -0
- data/lib/generators/easy_admin/card/card_generator.rb +94 -0
- data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
- data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
- data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
- data/lib/generators/easy_admin/install_generator.rb +25 -0
- data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
- data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
- data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
- data/lib/generators/easy_admin/resource_generator.rb +43 -0
- data/lib/generators/easy_admin/templates/AUTH_README +35 -0
- data/lib/generators/easy_admin/templates/README +27 -0
- data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
- data/lib/generators/easy_admin/templates/devise.rb +267 -0
- data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
- data/lib/generators/easy_admin/templates/resource.rb +29 -0
- data/lib/tasks/easy_admin_tasks.rake +4 -0
- 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,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
|