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