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,159 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class Action
|
3
|
+
include ActiveModel::Model
|
4
|
+
include ActiveModel::Attributes
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
# Execution modes
|
8
|
+
INSTANT = :instant # Execute immediately with optional confirmation
|
9
|
+
MODAL = :modal # Show form first, then execute
|
10
|
+
|
11
|
+
# Action types
|
12
|
+
SINGLE = :single # Single row action
|
13
|
+
BATCH = :batch # Batch action (for compatibility)
|
14
|
+
|
15
|
+
class_attribute :execution_mode, :label, :icon, :style, :confirm_message
|
16
|
+
class_attribute :modal_title, :modal_description, :modal_form_fields
|
17
|
+
class_attribute :action_type, :permissions, :visibility_conditions
|
18
|
+
|
19
|
+
# Set default execution mode
|
20
|
+
self.execution_mode = INSTANT
|
21
|
+
self.action_type = SINGLE
|
22
|
+
|
23
|
+
# Attributes for action context
|
24
|
+
attribute :current_user
|
25
|
+
attribute :record # For single actions
|
26
|
+
attribute :selected_records # For batch actions (compatibility)
|
27
|
+
attribute :resource_class
|
28
|
+
attr_accessor :params # Use attr_accessor instead of attribute for hash
|
29
|
+
|
30
|
+
def initialize(attributes = {})
|
31
|
+
super
|
32
|
+
@params ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def instant(label:, icon: nil, style: :primary, confirm: nil, permissions: nil)
|
37
|
+
self.execution_mode = INSTANT
|
38
|
+
self.label = label
|
39
|
+
self.icon = icon
|
40
|
+
self.style = style
|
41
|
+
self.confirm_message = confirm
|
42
|
+
self.permissions = permissions
|
43
|
+
end
|
44
|
+
|
45
|
+
def modal(label:, icon: nil, style: :primary, title: nil, description: nil, permissions: nil)
|
46
|
+
self.execution_mode = MODAL
|
47
|
+
self.label = label
|
48
|
+
self.icon = icon
|
49
|
+
self.style = style
|
50
|
+
self.modal_title = title || label
|
51
|
+
self.modal_description = description
|
52
|
+
self.permissions = permissions
|
53
|
+
self.modal_form_fields = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def form_field(name, type, options = {})
|
57
|
+
self.modal_form_fields ||= []
|
58
|
+
self.modal_form_fields << {
|
59
|
+
name: name,
|
60
|
+
type: type,
|
61
|
+
options: options
|
62
|
+
}
|
63
|
+
|
64
|
+
# Add attribute and validation dynamically
|
65
|
+
attribute name, get_attribute_type(type), **options.slice(:default)
|
66
|
+
|
67
|
+
# Add validations if specified
|
68
|
+
if options[:required]
|
69
|
+
validates name, presence: true
|
70
|
+
end
|
71
|
+
|
72
|
+
if options[:format]
|
73
|
+
validates name, format: options[:format]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def get_attribute_type(type)
|
80
|
+
case type
|
81
|
+
when :string, :text, :email, :url
|
82
|
+
:string
|
83
|
+
when :integer, :number
|
84
|
+
:integer
|
85
|
+
when :boolean
|
86
|
+
:boolean
|
87
|
+
when :date
|
88
|
+
:date
|
89
|
+
when :datetime
|
90
|
+
:datetime
|
91
|
+
when :json
|
92
|
+
:json
|
93
|
+
when :belongs_to
|
94
|
+
:string # Will store the ID
|
95
|
+
else
|
96
|
+
:string
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def single_action
|
101
|
+
self.action_type = SINGLE
|
102
|
+
end
|
103
|
+
|
104
|
+
def batch_action
|
105
|
+
self.action_type = BATCH
|
106
|
+
end
|
107
|
+
|
108
|
+
def visible_if(&block)
|
109
|
+
self.visibility_conditions = block
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Main execution method - must be implemented by subclasses
|
114
|
+
def perform
|
115
|
+
raise NotImplementedError, "Subclasses must implement #perform method"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Visibility check
|
119
|
+
def visible?
|
120
|
+
return true unless self.class.visibility_conditions
|
121
|
+
instance_eval(&self.class.visibility_conditions)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Permission check
|
125
|
+
def permitted?
|
126
|
+
return true unless self.class.permissions
|
127
|
+
# Integration with Permisi gem would go here
|
128
|
+
# For now, always return true
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Turbo response strategy for UI updates
|
133
|
+
def turbo_response_strategy
|
134
|
+
:replace # Default strategy, can be overridden
|
135
|
+
end
|
136
|
+
|
137
|
+
# Helper methods
|
138
|
+
class << self
|
139
|
+
# Helper method to get field configuration
|
140
|
+
def field_config(field_name)
|
141
|
+
modal_form_fields&.find { |field| field[:name] == field_name } || {}
|
142
|
+
end
|
143
|
+
|
144
|
+
# Helper method to check if action is instant or modal
|
145
|
+
def instant?
|
146
|
+
execution_mode == INSTANT
|
147
|
+
end
|
148
|
+
|
149
|
+
def modal?
|
150
|
+
execution_mode == MODAL
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Helper method to access form parameters safely
|
155
|
+
def form_param(key)
|
156
|
+
params[key.to_s] || params[key.to_sym]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class BatchAction
|
3
|
+
include ActiveModel::Model
|
4
|
+
include ActiveModel::Attributes
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
attr_accessor :selected_ids, :params, :current_user, :resource_class
|
8
|
+
|
9
|
+
# Class-level configuration
|
10
|
+
class_attribute :label, :icon, :style, :confirm_message, :execution_mode
|
11
|
+
class_attribute :modal_form_fields, :modal_title, :modal_description
|
12
|
+
|
13
|
+
# Execution modes
|
14
|
+
INSTANT = :instant # Execute immediately (toggle, delete, etc.)
|
15
|
+
MODAL = :modal # Show form first, then execute
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
super(attributes)
|
19
|
+
@records = nil
|
20
|
+
@selected_ids ||= []
|
21
|
+
@params ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Main execution method - must be implemented by subclasses
|
25
|
+
def perform
|
26
|
+
raise NotImplementedError, "Subclasses must implement #perform"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validation before execution
|
30
|
+
def valid_for_execution?
|
31
|
+
selected_ids.present? && records.exists?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the selected records
|
35
|
+
def records
|
36
|
+
@records ||= resource_class.model_class.where(id: selected_ids)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override for custom authorization
|
40
|
+
def authorized?
|
41
|
+
true # Override in subclasses
|
42
|
+
end
|
43
|
+
|
44
|
+
# Class methods for configuration
|
45
|
+
class << self
|
46
|
+
def instant(label:, icon: nil, style: :primary, confirm: nil)
|
47
|
+
self.execution_mode = INSTANT
|
48
|
+
self.label = label
|
49
|
+
self.icon = icon
|
50
|
+
self.style = style
|
51
|
+
self.confirm_message = confirm
|
52
|
+
end
|
53
|
+
|
54
|
+
def modal(label:, icon: nil, style: :primary, title: nil, description: nil)
|
55
|
+
self.execution_mode = MODAL
|
56
|
+
self.label = label
|
57
|
+
self.icon = icon
|
58
|
+
self.style = style
|
59
|
+
self.modal_title = title || label
|
60
|
+
self.modal_description = description
|
61
|
+
self.modal_form_fields = []
|
62
|
+
end
|
63
|
+
|
64
|
+
def form_field(name, type, options = {})
|
65
|
+
self.modal_form_fields ||= []
|
66
|
+
self.modal_form_fields << {
|
67
|
+
name: name,
|
68
|
+
type: type,
|
69
|
+
options: options
|
70
|
+
}
|
71
|
+
|
72
|
+
# Add attribute and validation dynamically
|
73
|
+
attribute name, get_attribute_type(type), **options.slice(:default)
|
74
|
+
|
75
|
+
# Add validations if specified
|
76
|
+
if options[:required]
|
77
|
+
validates name, presence: true
|
78
|
+
end
|
79
|
+
|
80
|
+
if options[:format]
|
81
|
+
validates name, format: options[:format]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def get_attribute_type(type)
|
88
|
+
case type
|
89
|
+
when :string, :text, :email, :url
|
90
|
+
:string
|
91
|
+
when :integer, :number
|
92
|
+
:integer
|
93
|
+
when :boolean
|
94
|
+
:boolean
|
95
|
+
when :date
|
96
|
+
:date
|
97
|
+
when :datetime
|
98
|
+
:datetime
|
99
|
+
when :json
|
100
|
+
:json
|
101
|
+
when :belongs_to
|
102
|
+
:string # Will store the ID
|
103
|
+
else
|
104
|
+
:string
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Helper method to get field configuration
|
110
|
+
def self.field_config(field_name)
|
111
|
+
modal_form_fields&.find { |field| field[:name] == field_name } || {}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Helper method to check if action is instant or modal
|
115
|
+
def self.instant?
|
116
|
+
execution_mode == INSTANT
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.modal?
|
120
|
+
execution_mode == MODAL
|
121
|
+
end
|
122
|
+
|
123
|
+
# Default success message
|
124
|
+
def success_message(count = nil)
|
125
|
+
count ||= records.count
|
126
|
+
"#{self.class.label} completed for #{count} item#{'s' if count != 1}"
|
127
|
+
end
|
128
|
+
|
129
|
+
# Helper method to access form parameters safely
|
130
|
+
def form_param(key)
|
131
|
+
params[key.to_s] || params[key.to_sym]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :app_name, :sidebar_items, :global_settings, :enable_profiler
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@app_name = "EasyAdmin"
|
7
|
+
@sidebar_items = []
|
8
|
+
@global_settings = false
|
9
|
+
@enable_profiler = Rails.env.development?
|
10
|
+
end
|
11
|
+
|
12
|
+
def sidebar(&block)
|
13
|
+
sidebar_builder = SidebarBuilder.new
|
14
|
+
sidebar_builder.instance_eval(&block) if block_given?
|
15
|
+
@sidebar_items = sidebar_builder.items
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class SidebarBuilder
|
20
|
+
attr_reader :items
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@items = []
|
24
|
+
@current_group = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def item(label, path = nil, icon: nil, active: false, &block)
|
28
|
+
item_config = {
|
29
|
+
label: label,
|
30
|
+
path: path,
|
31
|
+
icon: icon,
|
32
|
+
active: active
|
33
|
+
}
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
# This is a parent item with children
|
37
|
+
old_items = @items
|
38
|
+
@items = []
|
39
|
+
@current_group = item_config
|
40
|
+
|
41
|
+
instance_eval(&block)
|
42
|
+
|
43
|
+
item_config[:children] = @items
|
44
|
+
item_config[:expanded] = item_config[:children].any? { |child| child[:active] }
|
45
|
+
|
46
|
+
@items = old_items
|
47
|
+
end
|
48
|
+
|
49
|
+
@items << item_config
|
50
|
+
end
|
51
|
+
|
52
|
+
def group(label, icon: nil, expanded: false, &block)
|
53
|
+
group_config = {
|
54
|
+
label: label,
|
55
|
+
icon: icon,
|
56
|
+
expanded: expanded,
|
57
|
+
children: []
|
58
|
+
}
|
59
|
+
|
60
|
+
if block_given?
|
61
|
+
old_items = @items
|
62
|
+
@items = []
|
63
|
+
|
64
|
+
instance_eval(&block)
|
65
|
+
|
66
|
+
group_config[:children] = @items
|
67
|
+
group_config[:expanded] = expanded || @items.any? { |child| child[:active] }
|
68
|
+
|
69
|
+
@items = old_items
|
70
|
+
end
|
71
|
+
|
72
|
+
@items << group_config
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class Dashboard
|
3
|
+
class << self
|
4
|
+
attr_accessor :cards_config, :dashboard_name, :grid_columns
|
5
|
+
|
6
|
+
def inherited(subclass)
|
7
|
+
subclass.cards_config = []
|
8
|
+
subclass.grid_columns = 4
|
9
|
+
super
|
10
|
+
# Register will happen after class is fully defined
|
11
|
+
end
|
12
|
+
|
13
|
+
# DSL methods for dashboard configuration
|
14
|
+
def dashboard(name, **options, &block)
|
15
|
+
self.dashboard_name = name
|
16
|
+
self.grid_columns = options[:columns] || 4
|
17
|
+
instance_eval(&block) if block_given?
|
18
|
+
EasyAdmin::DashboardRegistry.register(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def metric_card(name, **options)
|
22
|
+
card(name, :metric, **options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def chart_card(name, **options)
|
26
|
+
card(name, :chart, **options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def table_card(name, **options)
|
30
|
+
card(name, :table, **options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def custom_card(name, **options)
|
34
|
+
card(name, :custom, **options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def card(name, type, **options)
|
38
|
+
self.cards_config += [{
|
39
|
+
name: name,
|
40
|
+
type: type,
|
41
|
+
title: options[:title] || name.to_s.humanize,
|
42
|
+
cols: options[:cols] || 1,
|
43
|
+
rows: options[:rows] || 1,
|
44
|
+
refresh_every: options[:refresh_every],
|
45
|
+
partial: options[:partial],
|
46
|
+
data_method: options[:data_method] || "#{name}_data",
|
47
|
+
visible: options.fetch(:visible, true),
|
48
|
+
**options
|
49
|
+
}]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Helper methods
|
53
|
+
def title
|
54
|
+
dashboard_name.to_s.humanize
|
55
|
+
end
|
56
|
+
|
57
|
+
def route_key
|
58
|
+
dashboard_name.to_s.underscore
|
59
|
+
end
|
60
|
+
|
61
|
+
def visible_cards
|
62
|
+
cards_config.select { |card| card[:visible] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_card(name)
|
66
|
+
cards_config.find { |card| card[:name].to_s == name.to_s }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Grid configuration
|
70
|
+
def columns
|
71
|
+
grid_columns
|
72
|
+
end
|
73
|
+
|
74
|
+
def columns_class
|
75
|
+
"cols-#{grid_columns}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Data methods - override in subclasses
|
79
|
+
def method_missing(method_name, *args, &block)
|
80
|
+
if method_name.to_s.end_with?('_data')
|
81
|
+
{}
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def respond_to_missing?(method_name, include_private = false)
|
88
|
+
method_name.to_s.end_with?('_data') || super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Instance methods for data fetching
|
93
|
+
def initialize(params = {})
|
94
|
+
@params = params
|
95
|
+
end
|
96
|
+
|
97
|
+
def card_data(card_name)
|
98
|
+
method_name = "#{card_name}_data"
|
99
|
+
if self.class.respond_to?(method_name)
|
100
|
+
self.class.send(method_name, @params)
|
101
|
+
else
|
102
|
+
{}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
attr_reader :params
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class DashboardRegistry
|
3
|
+
class << self
|
4
|
+
def dashboards
|
5
|
+
@dashboards ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def register(dashboard_class)
|
9
|
+
route_key = dashboard_class.route_key
|
10
|
+
dashboards[route_key] = dashboard_class
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(route_key)
|
14
|
+
dashboards[route_key.to_s]
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
dashboards.values
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?(route_key)
|
22
|
+
dashboards.key?(route_key.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
@dashboards = {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class DeleteAction < Action
|
3
|
+
# Configure as instant action with confirmation
|
4
|
+
instant(
|
5
|
+
label: "Delete",
|
6
|
+
icon: "🗑️",
|
7
|
+
style: :danger,
|
8
|
+
confirm: "Are you sure you want to delete this record? This action cannot be undone."
|
9
|
+
)
|
10
|
+
|
11
|
+
def perform
|
12
|
+
# Delete the record
|
13
|
+
record.destroy!
|
14
|
+
|
15
|
+
{
|
16
|
+
success: true,
|
17
|
+
message: "Record deleted successfully",
|
18
|
+
strategy: :remove # Remove the row from the table
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "turbo-rails"
|
2
|
+
require "devise"
|
3
|
+
require_relative "types/json_type"
|
4
|
+
|
5
|
+
module EasyAdmin
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace EasyAdmin
|
8
|
+
|
9
|
+
initializer "easy_admin.assets.precompile" do |app|
|
10
|
+
app.config.assets.precompile += %w[
|
11
|
+
easy_admin/application.css
|
12
|
+
easy_admin/dashboards.css
|
13
|
+
easy_admin/date_picker.css
|
14
|
+
easy_admin.base.js
|
15
|
+
jsoneditor-icons.svg
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer "easy_admin.phlex" do |app|
|
20
|
+
app.config.autoload_paths = app.config.autoload_paths + [root.join("app", "components").to_s]
|
21
|
+
end
|
22
|
+
|
23
|
+
initializer "easy_admin.register_types" do |app|
|
24
|
+
ActiveModel::Type.register(:json, EasyAdmin::Types::JsonType)
|
25
|
+
end
|
26
|
+
|
27
|
+
initializer "easy_admin.devise" do |app|
|
28
|
+
config.to_prepare do
|
29
|
+
Devise.setup do |config|
|
30
|
+
config.router_name = :easy_admin
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Load rack-mini-profiler if available and enabled
|
36
|
+
initializer 'easy_admin.rack_mini_profiler', after: :load_config_initializers do |app|
|
37
|
+
begin
|
38
|
+
require 'rack-mini-profiler'
|
39
|
+
# Add middleware to the stack if profiler should be enabled
|
40
|
+
profiler_enabled = begin
|
41
|
+
EasyAdmin.configuration.enable_profiler
|
42
|
+
rescue
|
43
|
+
Rails.env.development?
|
44
|
+
end
|
45
|
+
|
46
|
+
if profiler_enabled && defined?(Rack::MiniProfiler)
|
47
|
+
app.middleware.use Rack::MiniProfiler
|
48
|
+
end
|
49
|
+
rescue LoadError
|
50
|
+
# rack-mini-profiler not available, skip silently
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class Field
|
3
|
+
attr_accessor :name, :type, :label, :editable, :options_adapter, :permitted_attached_attributes, :attached_param_key
|
4
|
+
|
5
|
+
def initialize(name, type, options = {})
|
6
|
+
@name = name
|
7
|
+
@type = type
|
8
|
+
@label = options[:label] || name.to_s.humanize
|
9
|
+
@editable = options[:editable]
|
10
|
+
@options_adapter = options[:options_adapter]
|
11
|
+
@permitted_attached_attributes = options[:permitted_attached_attributes] || []
|
12
|
+
@attached_param_key = options[:attached_param_key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def editable?
|
16
|
+
editable.present?
|
17
|
+
end
|
18
|
+
|
19
|
+
def editable_via_menu?
|
20
|
+
type == :belongs_to && editable&.dig(:via) == :menu_or_modal
|
21
|
+
end
|
22
|
+
|
23
|
+
def editable_via_modal?
|
24
|
+
editable? && !editable_via_menu?
|
25
|
+
end
|
26
|
+
|
27
|
+
def permitted_param_key
|
28
|
+
case type
|
29
|
+
when :belongs_to
|
30
|
+
foreign_key || :"#{name}_id"
|
31
|
+
else
|
32
|
+
name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def foreign_key
|
37
|
+
@foreign_key ||= :"#{name}_id"
|
38
|
+
end
|
39
|
+
|
40
|
+
def normalize_input!(attrs)
|
41
|
+
key = permitted_param_key
|
42
|
+
value = attrs[key]
|
43
|
+
|
44
|
+
attrs[key] = case type
|
45
|
+
when :boolean
|
46
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
47
|
+
when :date
|
48
|
+
if value.present?
|
49
|
+
Date.parse(value) rescue value
|
50
|
+
else
|
51
|
+
value
|
52
|
+
end
|
53
|
+
when :datetime
|
54
|
+
if value.present?
|
55
|
+
Time.zone.parse(value) rescue value
|
56
|
+
else
|
57
|
+
value
|
58
|
+
end
|
59
|
+
when :enum
|
60
|
+
# Validate against allowed values if needed
|
61
|
+
validate_enum_value(value)
|
62
|
+
when :json
|
63
|
+
# Parse JSON string to ensure valid JSON
|
64
|
+
if value.present? && value.is_a?(String)
|
65
|
+
begin
|
66
|
+
JSON.parse(value)
|
67
|
+
value
|
68
|
+
rescue JSON::ParserError => e
|
69
|
+
raise "Invalid JSON: #{e.message}"
|
70
|
+
end
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
else
|
75
|
+
value # string, email, number, etc.
|
76
|
+
end
|
77
|
+
|
78
|
+
attrs
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
def render(field_type, action:, **options)
|
83
|
+
component_class = find_component_class(field_type, action)
|
84
|
+
component_class.new(**options)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def find_component_class(field_type, action)
|
90
|
+
# Try to find action-specific component first, then fall back to base
|
91
|
+
action_component_name = "EasyAdmin::Fields::#{action.to_s.classify}::#{field_type.to_s.classify}Component"
|
92
|
+
|
93
|
+
begin
|
94
|
+
action_component_name.constantize
|
95
|
+
rescue NameError
|
96
|
+
# Fallback to base text component for the action
|
97
|
+
case action
|
98
|
+
when :index
|
99
|
+
EasyAdmin::Fields::Index::TextComponent
|
100
|
+
when :show
|
101
|
+
EasyAdmin::Fields::Show::TextComponent
|
102
|
+
when :form
|
103
|
+
EasyAdmin::Fields::Form::TextComponent
|
104
|
+
else
|
105
|
+
EasyAdmin::Fields::Index::TextComponent
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def validate_enum_value(value)
|
114
|
+
return value unless respond_to?(:values) && values.present?
|
115
|
+
values.include?(value) ? value : raise("Invalid enum value: #{value}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|