easy-admin-rails 0.1.15 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +254 -18
  3. data/app/assets/builds/easy_admin.base.js.map +4 -4
  4. data/app/assets/builds/easy_admin.css +112 -18
  5. data/app/components/easy_admin/base_component.rb +1 -0
  6. data/app/components/easy_admin/form_tabs_component.rb +5 -2
  7. data/app/components/easy_admin/navbar_component.rb +5 -1
  8. data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
  9. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
  10. data/app/components/easy_admin/resources/index_component.rb +1 -4
  11. data/app/components/easy_admin/sidebar_component.rb +67 -2
  12. data/app/controllers/easy_admin/application_controller.rb +131 -1
  13. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  14. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  15. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  16. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  17. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  18. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  19. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  20. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  21. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  22. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  23. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  24. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  25. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  26. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  27. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  28. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  29. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  30. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  31. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  33. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  34. data/app/javascript/easy_admin/controllers.js +5 -1
  35. data/app/models/easy_admin/admin_user.rb +6 -0
  36. data/app/policies/admin_user_policy.rb +36 -0
  37. data/app/policies/application_policy.rb +83 -0
  38. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  39. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  40. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  41. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  42. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  43. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  44. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  46. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  47. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  48. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  49. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  50. data/app/views/easy_admin/resources/index.html.erb +1 -1
  51. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  52. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  53. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  54. data/config/initializers/easy_admin_permissions.rb +73 -0
  55. data/db/seeds/easy_admin_permissions.rb +121 -0
  56. data/lib/easy-admin-rails.rb +2 -0
  57. data/lib/easy_admin/permissions/component.rb +168 -0
  58. data/lib/easy_admin/permissions/configuration.rb +37 -0
  59. data/lib/easy_admin/permissions/controller.rb +164 -0
  60. data/lib/easy_admin/permissions/dsl.rb +160 -0
  61. data/lib/easy_admin/permissions/models.rb +44 -0
  62. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  63. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  64. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  65. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  66. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  67. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  68. data/lib/easy_admin/permissions.rb +113 -0
  69. data/lib/easy_admin/resource/base.rb +119 -0
  70. data/lib/easy_admin/resource/configuration.rb +148 -0
  71. data/lib/easy_admin/resource/dsl.rb +117 -0
  72. data/lib/easy_admin/resource/field_registry.rb +189 -0
  73. data/lib/easy_admin/resource/form_builder.rb +123 -0
  74. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  75. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  76. data/lib/easy_admin/resource/show_builder.rb +359 -0
  77. data/lib/easy_admin/resource.rb +8 -835
  78. data/lib/easy_admin/resource_modules.rb +11 -0
  79. data/lib/easy_admin/version.rb +1 -1
  80. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  81. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  82. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  84. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  90. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  91. metadata +62 -5
  92. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,359 @@
1
+ module EasyAdmin
2
+ module ResourceModules
3
+ # ShowBuilder class for show layout DSL
4
+ # Handles all show page layout definitions with rows, columns, cards, and tabs
5
+ class ShowBuilder
6
+ PREDEFINED_COLORS = %w[primary secondary success danger warning info light dark white transparent].freeze
7
+ COLUMN_SIZES = [1, 2, 3, 4, 6, 8, 12].freeze
8
+ SPACING_OPTIONS = %w[none small medium large].freeze
9
+ PADDING_OPTIONS = %w[none small medium large].freeze
10
+ HEADING_SIZES = %w[small medium large].freeze
11
+ DIVIDER_STYLES = %w[default dashed dotted thick].freeze
12
+ SHADOW_OPTIONS = %w[none small medium large].freeze
13
+
14
+ def initialize(resource_class)
15
+ @resource_class = resource_class
16
+ @context_stack = []
17
+ @current_row = nil
18
+ @current_column = nil
19
+ @current_card = nil
20
+ @current_tab_container = nil
21
+ @current_tab = nil
22
+ end
23
+
24
+ # Layout methods
25
+ def row(columns: 1, spacing: "medium", &block)
26
+ validate_spacing!(spacing)
27
+
28
+ row_config = {
29
+ type: :row,
30
+ columns_count: columns,
31
+ spacing: spacing,
32
+ columns: []
33
+ }
34
+
35
+ push_context
36
+ @current_row = row_config
37
+
38
+ if @current_column
39
+ @current_column[:elements] << row_config
40
+ else
41
+ @resource_class.add_show_layout_element(row_config)
42
+ end
43
+
44
+ instance_eval(&block) if block_given?
45
+ pop_context
46
+ end
47
+
48
+ def column(size: nil, &block)
49
+ raise "column must be called within a row block" unless @current_row
50
+
51
+ calculated_size = size || (12 / [@current_row[:columns_count], 1].max)
52
+ validate_column_size!(calculated_size)
53
+
54
+ column_config = {
55
+ type: :column,
56
+ size: calculated_size,
57
+ elements: []
58
+ }
59
+
60
+ push_context
61
+ @current_column = column_config
62
+ @current_row[:columns] << column_config
63
+
64
+ instance_eval(&block) if block_given?
65
+ pop_context
66
+ end
67
+
68
+ # Card methods
69
+ def card(title: nil, color: "light", padding: "medium", shadow: "small", &block)
70
+ raise "card must be called within a column block" unless @current_column
71
+ validate_color!(color)
72
+ validate_padding!(padding)
73
+ validate_shadow!(shadow)
74
+
75
+ card_config = {
76
+ type: :card,
77
+ title: title,
78
+ color: color,
79
+ padding: padding,
80
+ shadow: shadow,
81
+ elements: []
82
+ }
83
+
84
+ push_context
85
+ @current_card = card_config
86
+ @current_column[:elements] << card_config
87
+
88
+ instance_eval(&block) if block_given?
89
+ pop_context
90
+ end
91
+
92
+ def metric_card(title:, value:, color: "primary", icon: nil, trend: nil)
93
+ raise "metric_card must be called within a column block" unless @current_column
94
+ validate_color!(color)
95
+
96
+ metric_config = {
97
+ type: :metric_card,
98
+ title: title,
99
+ value: value,
100
+ icon: icon,
101
+ color: color,
102
+ trend: trend
103
+ }
104
+
105
+ @current_column[:elements] << metric_config
106
+ end
107
+
108
+ def chart_card(title:, chart_type:, **options, &block)
109
+ raise "chart_card must be called within a column block" unless @current_column
110
+
111
+ chart_config = {
112
+ type: :chart_card,
113
+ title: title,
114
+ chart_type: chart_type,
115
+ height: options[:height] || 350,
116
+ data_source: options[:data_source],
117
+ css_classes: options[:class] || "card",
118
+ config: {}
119
+ }
120
+
121
+ if block_given?
122
+ chart_builder = ChartBuilder.new
123
+ chart_builder.instance_eval(&block)
124
+ chart_config[:config] = chart_builder.config
125
+ end
126
+
127
+ @current_column[:elements] << chart_config
128
+ end
129
+
130
+ # Tab methods
131
+ def tabs(**options, &block)
132
+ container = @current_card || @current_column
133
+ raise "tabs must be called within a card or column block" unless container
134
+
135
+ tabs_config = {
136
+ type: :tabs,
137
+ css_classes: options[:class] || "tabs-menu",
138
+ tabs: []
139
+ }
140
+
141
+ push_context
142
+ @current_tab_container = tabs_config
143
+ container[:elements] << tabs_config
144
+
145
+ instance_eval(&block) if block_given?
146
+ pop_context
147
+ end
148
+
149
+ def tab(name, **options, &block)
150
+ raise "tab must be called within a tabs block" unless @current_tab_container
151
+
152
+ tab_config = {
153
+ name: name,
154
+ label: options[:label] || name,
155
+ icon: options[:icon],
156
+ active: options[:active] || false,
157
+ elements: []
158
+ }
159
+
160
+ push_context
161
+ @current_tab = tab_config
162
+ @current_tab_container[:tabs] << tab_config
163
+
164
+ instance_eval(&block) if block_given?
165
+ pop_context
166
+ end
167
+
168
+ # Field display methods
169
+ def field(name, **options)
170
+ container = @current_tab || @current_card || @current_column
171
+ raise "field must be called within a tab, card, or column block" unless container
172
+
173
+ label_value = if options.key?(:label) && options[:label] == false
174
+ nil
175
+ else
176
+ options[:label] || name.to_s.humanize
177
+ end
178
+
179
+ field_config = {
180
+ type: :field,
181
+ name: name,
182
+ label: label_value,
183
+ field_type: options[:field_type] || :default,
184
+ format: options[:format],
185
+ css_classes: options[:class],
186
+ show_label: options.fetch(:show_label, true)
187
+ }
188
+
189
+ container[:elements] << field_config
190
+ end
191
+
192
+ def fields(*field_names, **options)
193
+ field_names.each do |name|
194
+ field(name, **options)
195
+ end
196
+ end
197
+
198
+ # Component rendering support with context
199
+ def render(component_class_or_instance, **options)
200
+ container = @current_tab || @current_card || @current_column
201
+ raise "render must be called within a tab, card, or column block" unless container
202
+
203
+ component_config = {
204
+ type: :custom_component,
205
+ component_class: component_class_or_instance.class,
206
+ component_options: options,
207
+ component_instance: component_class_or_instance
208
+ }
209
+
210
+ container[:elements] << component_config
211
+ component_class_or_instance
212
+ end
213
+
214
+ # Content methods
215
+ def content(html = nil, **options, &block)
216
+ container = @current_tab || @current_card || @current_column
217
+ raise "content must be called within a tab, card, or column block" unless container
218
+
219
+ content_config = {
220
+ type: :content,
221
+ html: html,
222
+ css_classes: options[:class],
223
+ block: block
224
+ }
225
+
226
+ container[:elements] << content_config
227
+ end
228
+
229
+ def heading(text, level: 2, size: "medium")
230
+ container = @current_tab || @current_card || @current_column
231
+ raise "heading must be called within a tab, card, or column block" unless container
232
+
233
+ validate_heading_level!(level)
234
+ validate_heading_size!(size)
235
+
236
+ heading_config = {
237
+ type: :heading,
238
+ text: text,
239
+ level: level,
240
+ size: size
241
+ }
242
+
243
+ container[:elements] << heading_config
244
+ end
245
+
246
+ def divider(style: "default")
247
+ container = @current_tab || @current_card || @current_column
248
+ raise "divider must be called within a tab, card, or column block" unless container
249
+
250
+ validate_divider_style!(style)
251
+
252
+ divider_config = {
253
+ type: :divider,
254
+ style: style
255
+ }
256
+
257
+ container[:elements] << divider_config
258
+ end
259
+
260
+ private
261
+
262
+ def validate_color!(color)
263
+ return if PREDEFINED_COLORS.include?(color.to_s)
264
+ raise ArgumentError, "Invalid color '#{color}'. Available colors: #{PREDEFINED_COLORS.join(', ')}"
265
+ end
266
+
267
+ def validate_column_size!(size)
268
+ return if COLUMN_SIZES.include?(size)
269
+ raise ArgumentError, "Invalid column size '#{size}'. Available sizes: #{COLUMN_SIZES.join(', ')}"
270
+ end
271
+
272
+ def validate_spacing!(spacing)
273
+ return if SPACING_OPTIONS.include?(spacing.to_s)
274
+ raise ArgumentError, "Invalid spacing '#{spacing}'. Available options: #{SPACING_OPTIONS.join(', ')}"
275
+ end
276
+
277
+ def validate_padding!(padding)
278
+ return if PADDING_OPTIONS.include?(padding.to_s)
279
+ raise ArgumentError, "Invalid padding '#{padding}'. Available options: #{PADDING_OPTIONS.join(', ')}"
280
+ end
281
+
282
+ def validate_shadow!(shadow)
283
+ return if SHADOW_OPTIONS.include?(shadow.to_s)
284
+ raise ArgumentError, "Invalid shadow '#{shadow}'. Available options: #{SHADOW_OPTIONS.join(', ')}"
285
+ end
286
+
287
+ def validate_heading_level!(level)
288
+ return if (1..6).include?(level)
289
+ raise ArgumentError, "Invalid heading level '#{level}'. Must be between 1 and 6"
290
+ end
291
+
292
+ def validate_heading_size!(size)
293
+ return if HEADING_SIZES.include?(size.to_s)
294
+ raise ArgumentError, "Invalid heading size '#{size}'. Available sizes: #{HEADING_SIZES.join(', ')}"
295
+ end
296
+
297
+ def validate_divider_style!(style)
298
+ return if DIVIDER_STYLES.include?(style.to_s)
299
+ raise ArgumentError, "Invalid divider style '#{style}'. Available styles: #{DIVIDER_STYLES.join(', ')}"
300
+ end
301
+
302
+ def push_context
303
+ @context_stack.push({
304
+ row: @current_row,
305
+ column: @current_column,
306
+ card: @current_card,
307
+ tab_container: @current_tab_container,
308
+ tab: @current_tab
309
+ })
310
+ end
311
+
312
+ def pop_context
313
+ if @context_stack.any?
314
+ context = @context_stack.pop
315
+ @current_row = context[:row]
316
+ @current_column = context[:column]
317
+ @current_card = context[:card]
318
+ @current_tab_container = context[:tab_container]
319
+ @current_tab = context[:tab]
320
+ else
321
+ @current_row = nil
322
+ @current_column = nil
323
+ @current_card = nil
324
+ @current_tab_container = nil
325
+ @current_tab = nil
326
+ end
327
+ end
328
+ end
329
+
330
+ # ChartBuilder class for chart configuration
331
+ class ChartBuilder
332
+ attr_reader :config
333
+
334
+ def initialize
335
+ @config = {}
336
+ end
337
+
338
+ def series(data)
339
+ @config[:series] = data
340
+ end
341
+
342
+ def categories(data)
343
+ @config[:categories] = data
344
+ end
345
+
346
+ def colors(data)
347
+ @config[:colors] = data
348
+ end
349
+
350
+ def title(text)
351
+ @config[:title] = text
352
+ end
353
+
354
+ def subtitle(text)
355
+ @config[:subtitle] = text
356
+ end
357
+ end
358
+ end
359
+ end