easy-admin-rails 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) 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/components/easy_admin/versions/diff_modal_component.rb +5 -1
  13. data/app/controllers/easy_admin/application_controller.rb +131 -1
  14. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  15. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  16. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  17. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  18. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  19. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  20. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  21. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  22. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  23. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  24. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  25. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  26. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  27. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  28. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  29. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  30. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  31. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  33. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  34. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  35. data/app/javascript/easy_admin/controllers.js +5 -1
  36. data/app/models/easy_admin/admin_user.rb +6 -0
  37. data/app/policies/admin_user_policy.rb +36 -0
  38. data/app/policies/application_policy.rb +83 -0
  39. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  40. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  41. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  42. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  43. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  44. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  46. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  47. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  48. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  49. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  50. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  51. data/app/views/easy_admin/resources/index.html.erb +1 -1
  52. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  53. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  54. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  55. data/config/initializers/easy_admin_permissions.rb +73 -0
  56. data/db/seeds/easy_admin_permissions.rb +121 -0
  57. data/lib/easy-admin-rails.rb +2 -0
  58. data/lib/easy_admin/permissions/component.rb +168 -0
  59. data/lib/easy_admin/permissions/configuration.rb +37 -0
  60. data/lib/easy_admin/permissions/controller.rb +164 -0
  61. data/lib/easy_admin/permissions/dsl.rb +180 -0
  62. data/lib/easy_admin/permissions/models.rb +44 -0
  63. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  64. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  65. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  66. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  67. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  68. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  69. data/lib/easy_admin/permissions.rb +113 -0
  70. data/lib/easy_admin/resource/base.rb +119 -0
  71. data/lib/easy_admin/resource/configuration.rb +148 -0
  72. data/lib/easy_admin/resource/dsl.rb +117 -0
  73. data/lib/easy_admin/resource/field_registry.rb +189 -0
  74. data/lib/easy_admin/resource/form_builder.rb +123 -0
  75. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  76. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  77. data/lib/easy_admin/resource/show_builder.rb +359 -0
  78. data/lib/easy_admin/resource.rb +8 -835
  79. data/lib/easy_admin/resource_modules.rb +11 -0
  80. data/lib/easy_admin/version.rb +1 -1
  81. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  82. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  84. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  90. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  91. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  92. metadata +62 -5
  93. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,83 @@
1
+ class ApplicationPolicy < ActionPolicy::Base
2
+ # Configure authorization context
3
+ authorize :user, allow_nil: true
4
+
5
+ # Make this the default policy for EasyAdmin models
6
+ self.identifier = :application
7
+
8
+ # Basic authorization rules using EasyAdmin permissions
9
+ def index?
10
+ user_has_permission?("#{resource_name}:read")
11
+ end
12
+
13
+ def show?
14
+ user_has_permission?("#{resource_name}:read")
15
+ end
16
+
17
+ def create?
18
+ user_has_permission?("#{resource_name}:create")
19
+ end
20
+
21
+ def update?
22
+ user_has_permission?("#{resource_name}:update")
23
+ end
24
+
25
+ def destroy?
26
+ user_has_permission?("#{resource_name}:delete")
27
+ end
28
+
29
+ # Versioning permissions (requires update permission + additional versioning permission)
30
+ def manage_versions?
31
+ user_has_permission?("#{resource_name}:update") && user_has_permission?("#{resource_name}:manage_versions")
32
+ end
33
+
34
+ # Batch actions permission (requires update permission + batch action permission)
35
+ def batch_action?
36
+ user_has_permission?("#{resource_name}:update") && user_has_permission?("#{resource_name}:batch_actions")
37
+ end
38
+
39
+ # Row actions permission (requires read permission + row action permission)
40
+ def row_action?
41
+ user_has_permission?("#{resource_name}:read") && user_has_permission?("#{resource_name}:row_actions")
42
+ end
43
+
44
+
45
+ private
46
+
47
+ def user_has_permission?(permission_name, context: nil)
48
+ EasyAdmin::Permissions.authorized?(user, permission_name, context: context)
49
+ end
50
+
51
+ def admin_user?
52
+ # Check if user has admin privileges through EasyAdmin permissions
53
+ return false unless user
54
+
55
+ # Check if user has a super_admin or admin role
56
+ if user.respond_to?(:role) && user.role
57
+ return true if %w[super_admin admin].include?(user.role.slug)
58
+ end
59
+
60
+ # Fallback: check if user has manage permissions for admin_users
61
+ user_has_permission?("admin_users:manage") || user_has_permission?("admin_users:create")
62
+ end
63
+
64
+ def resource_name
65
+ if record.is_a?(Class)
66
+ # For class-level authorization
67
+ model_class = record
68
+ else
69
+ # For instance-level authorization
70
+ model_class = record.class
71
+ end
72
+
73
+ # Handle EasyAdmin namespaced models
74
+ class_name = model_class.name
75
+ if class_name.start_with?('EasyAdmin::')
76
+ # Extract the model name after the namespace
77
+ class_name.split('::').last.underscore
78
+ else
79
+ # Regular models
80
+ class_name.underscore
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,8 @@
1
+ <%= turbo_stream.update "notifications" do %>
2
+ <%== EasyAdmin::NotificationComponent.new(
3
+ type: :error,
4
+ title: "Access Denied",
5
+ message: @error_message,
6
+ duration: 6000
7
+ ).call %>
8
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%== EasyAdmin::Dashboards::CardStreamComponent.new(
2
+ card: @card,
3
+ card_data: @card_data,
4
+ dashboard_class: @dashboard_class
5
+ ).call %>
@@ -0,0 +1,7 @@
1
+ <%= turbo_stream.replace "card_#{@card[:name]}" do %>
2
+ <%== EasyAdmin::Dashboards::CardStreamComponent.new(
3
+ card: @card,
4
+ card_data: @card_data,
5
+ dashboard_class: @dashboard_class
6
+ ).call %>
7
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="card_<%= params[:card_name] %>">
2
+ <%== EasyAdmin::Dashboards::CardErrorComponent.new(error: @error_message).call %>
3
+ </turbo-frame>
@@ -0,0 +1,5 @@
1
+ <%= turbo_stream.replace "card_#{params[:card_name]}" do %>
2
+ <turbo-frame id="card_<%= params[:card_name] %>">
3
+ <%== EasyAdmin::Dashboards::CardErrorComponent.new(error: @error_message).call %>
4
+ </turbo-frame>
5
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <%= turbo_stream.replace "dashboard-grid" do %>
2
+ <%== EasyAdmin::Dashboards::ShowComponent.new(
3
+ dashboard_class: @dashboard_class,
4
+ params: dashboard_params,
5
+ request_path: request.path
6
+ ).call %>
7
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%== EasyAdmin::Fields::BelongsToEditModalComponent.new(
2
+ record: @associated_record,
3
+ resource_class: @associated_resource_class,
4
+ parent_record: @record,
5
+ parent_field: @field_config
6
+ ).call %>
@@ -0,0 +1,8 @@
1
+ <%= turbo_stream.update "modal" do %>
2
+ <%== EasyAdmin::Fields::BelongsToEditModalComponent.new(
3
+ record: @associated_record,
4
+ resource_class: @associated_resource_class,
5
+ parent_record: @record,
6
+ parent_field: @field_config
7
+ ).call %>
8
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%== EasyAdmin::Fields::InlineEditModalComponent.new(
2
+ record: @record,
3
+ field_config: @select_field_config,
4
+ resource_class: @resource_class
5
+ ).call %>
@@ -117,7 +117,7 @@
117
117
 
118
118
  <!-- Form Fields -->
119
119
  <% if @resource_class.has_form_tabs? %>
120
- <%= EasyAdmin::FormTabsComponent.new(resource_class: @resource_class, form: form).call.html_safe %>
120
+ <%= EasyAdmin::FormTabsComponent.new(resource_class: @resource_class, form: form, record: @record).call.html_safe %>
121
121
  <% else %>
122
122
  <!-- Default Single Card Layout -->
123
123
  <div class="bg-white shadow-sm rounded-lg border border-gray-200">
@@ -0,0 +1,5 @@
1
+ <%== EasyAdmin::Fields::InlineEditModalComponent.new(
2
+ record: @record,
3
+ field_config: @field_config,
4
+ resource_class: @resource_class
5
+ ).call %>
@@ -0,0 +1,7 @@
1
+ <%= turbo_stream.update "modal" do %>
2
+ <%== EasyAdmin::Fields::InlineEditModalComponent.new(
3
+ record: @record,
4
+ field_config: @field_config,
5
+ resource_class: @resource_class
6
+ ).call %>
7
+ <% end %>
@@ -1,4 +1,4 @@
1
- <% content_for :title, @resource_class.title %>
1
+ <% content_for :page_title, @resource_class.title %>
2
2
 
3
3
  <%== EasyAdmin::Resources::IndexComponent.new(
4
4
  resource_class: @resource_class,
@@ -1,142 +1,8 @@
1
- <turbo-frame id="table-frame">
2
- <!-- Striped Table -->
3
- <div class="mt-8 flow-root">
4
- <div class="mobile-table-wrapper -mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
5
- <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
6
- <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
7
- <table class="table-striped min-w-full divide-y divide-gray-300">
8
- <thead class="bg-gray-50">
9
- <tr>
10
- <% @resource_class.index_fields.each_with_index do |field, index| %>
11
- <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide <%= 'mobile-hide' if index > 2 %>">
12
- <% if field[:sortable] %>
13
- <div class="flex items-center justify-between">
14
- <%
15
- # Determine next direction: if currently sorting by this field and direction is 'asc', toggle to 'desc'
16
- # Otherwise default to 'asc' for new sort or when toggling from 'desc'
17
- next_direction = (params[:sort] == field[:name].to_s && params[:direction] == 'asc') ? 'desc' : 'asc'
18
- %>
19
- <%= link_to url_for(request.params.merge(sort: field[:name], direction: next_direction)),
20
- class: "group hover:text-gray-900 transition-colors duration-150" do %>
21
- <%= field[:label] %>
22
- <% end %>
23
- <span class="ml-2 flex-none text-gray-400 hover:text-gray-600">
24
- <% if params[:sort] == field[:name].to_s %>
25
- <% if params[:direction] == 'asc' %>
26
- <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
27
- <path fill-rule="evenodd" d="M14.77 12.79a.75.75 0 01-1.06-.02L10 8.832 6.29 12.77a.75.75 0 11-1.08-1.04l4.25-4.5a.75.75 0 011.08 0l4.25 4.5a.75.75 0 01-.02 1.06z" clip-rule="evenodd"/>
28
- </svg>
29
- <% else %>
30
- <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
31
- <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd"/>
32
- </svg>
33
- <% end %>
34
- <% else %>
35
- <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
36
- <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd"/>
37
- </svg>
38
- <% end %>
39
- </span>
40
- </div>
41
- <% else %>
42
- <%= field[:label] %>
43
- <% end %>
44
- </th>
45
- <% end %>
46
- <th scope="col" class="relative px-6 py-3">
47
- <span class="sr-only">Actions</span>
48
- </th>
49
- </tr>
50
- </thead>
51
- <tbody id="records-container" class="divide-y divide-gray-200 bg-white">
52
- <% @records.each do |record| %>
53
- <tr class="hover:bg-gray-100 cursor-pointer transition-colors duration-150"
54
- onclick="window.location='<%= easy_admin.resource_path(@resource_class.route_key, record) %>'">
55
- <% @resource_class.index_fields.each_with_index do |field, index| %>
56
- <td class="whitespace-nowrap px-6 py-4 text-sm <%= 'mobile-hide' if index > 2 %>">
57
- <div class="text-gray-900 font-medium">
58
- <%== render_field(field, action: :index, value: record.public_send(field[:name]), record: record) %>
59
- </div>
60
- </td>
61
- <% end %>
62
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
63
- <div class="flex items-center justify-end space-x-2 sm:mobile-actions">
64
- <%= link_to easy_admin.resource_path(@resource_class.route_key, record),
65
- class: "inline-flex items-center justify-center w-8 h-8 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-lg transition-colors duration-150",
66
- onclick: "event.stopPropagation()",
67
- title: "View" do %>
68
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
69
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
70
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
71
- </svg>
72
- <% end %>
73
- <%= link_to easy_admin.edit_resource_path(@resource_class.route_key, record),
74
- class: "inline-flex items-center justify-center w-8 h-8 text-green-600 hover:text-green-800 hover:bg-green-50 rounded-lg transition-colors duration-150",
75
- onclick: "event.stopPropagation()",
76
- title: "Edit" do %>
77
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
78
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
79
- </svg>
80
- <% end %>
81
- <%= link_to easy_admin.resource_path(@resource_class.route_key, record),
82
- method: :delete,
83
- class: "inline-flex items-center justify-center w-8 h-8 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-lg transition-colors duration-150",
84
- onclick: "event.stopPropagation()",
85
- title: "Delete",
86
- data: {
87
- confirm: "Are you sure you want to delete this #{@resource_class.singular_title.downcase}?",
88
- method: :delete
89
- } do %>
90
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
91
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
92
- </svg>
93
- <% end %>
94
- </div>
95
- </td>
96
- </tr>
97
- <% end %>
98
- </tbody>
99
- </table>
100
- </div>
101
- </div>
102
- </div>
103
- </div>
104
-
105
- <!-- Pagination within the turbo frame -->
106
- <div id="infinite-scroll-container">
107
- <% if @resource_class.respond_to?(:infinite_scroll_enabled?) && @resource_class.infinite_scroll_enabled? %>
108
- <%== EasyAdmin::InfiniteScrollComponent.new(
109
- pagy: @pagy,
110
- resource_class: @resource_class,
111
- current_params: params.permit(:search, :scope, :sort, :direction, :period, :page, q: {}),
112
- current_path: request.path
113
- ).call %>
114
- <% else %>
115
- <%== EasyAdmin::ResourcePaginationComponent.new(
116
- pagy: @pagy,
117
- resource_class: @resource_class,
118
- request_params: params
119
- ).call %>
120
- <% end %>
121
- </div>
122
-
123
- <!-- Empty state within the turbo frame -->
124
- <% if @records.empty? && @pagy.page == 1 %>
125
- <div class="text-center py-12">
126
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
127
- <path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
128
- </svg>
129
- <h3 class="mt-2 text-sm font-semibold text-gray-900">No <%= @resource_class.title.downcase %></h3>
130
- <p class="mt-1 text-sm text-gray-500">Get started by creating a new <%= @resource_class.singular_title.downcase %>.</p>
131
- <div class="mt-6">
132
- <%= link_to easy_admin.new_resource_path(@resource_class.route_key),
133
- class: "inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600" do %>
134
- <svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
135
- <path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/>
136
- </svg>
137
- New <%= @resource_class.singular_title %>
138
- <% end %>
139
- </div>
140
- </div>
141
- <% end %>
142
- </turbo-frame>
1
+ <%== EasyAdmin::Resources::IndexFrameComponent.new(
2
+ resource_class: @resource_class,
3
+ records: @records,
4
+ pagy: @pagy,
5
+ current_params: @current_params,
6
+ current_path: request.path,
7
+ current_user: current_admin_user
8
+ ).call %>
@@ -0,0 +1,25 @@
1
+ <% if @success %>
2
+ <%= turbo_stream.replace "#{dom_id(@record)}_#{@association_name}" do %>
3
+ <%== EasyAdmin::Resources::TableCellComponent.new(
4
+ record: @record.reload,
5
+ field_config: @field_config,
6
+ resource_class: @resource_class
7
+ ).call %>
8
+ <% end %>
9
+
10
+ <%= turbo_stream.update "notifications" do %>
11
+ <%== EasyAdmin::NotificationComponent.new(
12
+ type: :success,
13
+ message: "#{@field_config[:label]} updated successfully!",
14
+ title: "Success"
15
+ ).call %>
16
+ <% end %>
17
+ <% else %>
18
+ <%= turbo_stream.update "notifications" do %>
19
+ <%== EasyAdmin::NotificationComponent.new(
20
+ type: :error,
21
+ message: @attached_record.errors.full_messages.join(', '),
22
+ title: "Error"
23
+ ).call %>
24
+ <% end %>
25
+ <% end %>
@@ -20,11 +20,12 @@
20
20
  </svg>
21
21
  </button>
22
22
 
23
- <%== EasyAdmin::SidebarComponent.new(current_path: request.path).call %>
23
+ <%== EasyAdmin::SidebarComponent.new(current_path: request.path, current_user: current_admin_user).call %>
24
24
 
25
25
  <%== EasyAdmin::NavbarComponent.new(
26
26
  page_title: content_for?(:page_title) ? yield(:page_title) : "Dashboard",
27
- breadcrumbs: content_for?(:breadcrumbs) ? yield(:breadcrumbs) : nil
27
+ breadcrumbs: content_for?(:breadcrumbs) ? yield(:breadcrumbs) : nil,
28
+ current_user: current_admin_user
28
29
  ).call %>
29
30
 
30
31
  <main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-50 lg:ml-64 transition-all duration-300" data-sidebar-target="content">
@@ -33,6 +34,18 @@
33
34
  </div>
34
35
  </main>
35
36
 
37
+ <!-- Flash Messages -->
38
+ <% if flash.any? %>
39
+ <% flash.each do |type, message| %>
40
+ <% next if message.blank? %>
41
+ <%== EasyAdmin::NotificationComponent.new(
42
+ type: flash_type_to_notification_type(type),
43
+ message: message,
44
+ duration: 6000
45
+ ).call %>
46
+ <% end %>
47
+ <% end %>
48
+
36
49
  <!-- Notifications Turbo Frame -->
37
50
  <turbo-frame id="notifications"></turbo-frame>
38
51
 
@@ -0,0 +1,73 @@
1
+ EasyAdmin::Permissions.configure do |config|
2
+ # Enable the permissions system
3
+ config.enabled = true
4
+
5
+ # Cache duration for permission lookups (default: 1 hour)
6
+ config.cache_duration = 1.hour
7
+
8
+ # Allow admin users to bypass permissions (default: true)
9
+ config.admin_bypass = true
10
+
11
+ # Set the user model class name (default: 'User')
12
+ config.user_class 'EasyAdmin::AdminUser'
13
+
14
+ # Define permission contexts (optional - for scoped permissions)
15
+
16
+ # config.contexts :organization, :project
17
+
18
+ end
19
+
20
+ # Define your permissions here
21
+ # Note: This will only run if the database tables exist
22
+ if ActiveRecord::Base.connection.table_exists?(:easy_admin_permissions)
23
+ EasyAdmin::Permissions.define do
24
+ # Dashboard permissions
25
+ permission :dashboard, :view, description: "Access admin dashboard"
26
+
27
+ # Admin user management permissions
28
+ resource :admin_users, actions: [:read, :create, :update, :delete]
29
+ permission :admin_users, :manage_roles, description: "Assign and remove user roles"
30
+
31
+ # Reports permissions
32
+ permission :reports, :view, description: "View reports"
33
+ permission :reports, :export, description: "Export reports"
34
+
35
+ # Settings permissions
36
+ permission :settings, :read, description: "View system settings"
37
+ permission :settings, :update, description: "Update system settings"
38
+
39
+ # Role definitions
40
+ role :super_admin,
41
+ description: "Full system access",
42
+ permissions: %w[
43
+ dashboard:view admin_users:read admin_users:create admin_users:update
44
+ admin_users:delete admin_users:manage_roles reports:view reports:export
45
+ settings:read settings:update
46
+ ]
47
+
48
+ role :admin,
49
+ description: "Administrative access with user management",
50
+ permissions: %w[
51
+ dashboard:view admin_users:read admin_users:create admin_users:update
52
+ admin_users:manage_roles reports:view reports:export settings:read
53
+ ]
54
+
55
+ role :manager,
56
+ description: "Management access with limited user permissions",
57
+ permissions: %w[
58
+ dashboard:view admin_users:read admin_users:update reports:view reports:export
59
+ ]
60
+
61
+ role :editor,
62
+ description: "Content editing permissions",
63
+ permissions: %w[
64
+ dashboard:view admin_users:read
65
+ ]
66
+
67
+ role :viewer,
68
+ description: "Read-only access",
69
+ permissions: %w[
70
+ dashboard:view admin_users:read reports:view
71
+ ]
72
+ end
73
+ end
@@ -0,0 +1,121 @@
1
+ # EasyAdmin Permission System Seeds
2
+ # Load these seeds after running: rails easy_admin:permissions:install
3
+
4
+ puts "🔐 Setting up EasyAdmin Permission System..."
5
+
6
+ # Create basic permissions
7
+ permissions_data = [
8
+ # Dashboard permissions
9
+ { name: "dashboard:view", resource_type: "dashboard", action: "view", description: "Access admin dashboard" },
10
+
11
+ # User management permissions
12
+ { name: "admin_users:read", resource_type: "admin_users", action: "read", description: "View admin users" },
13
+ { name: "admin_users:create", resource_type: "admin_users", action: "create", description: "Create new admin users" },
14
+ { name: "admin_users:update", resource_type: "admin_users", action: "update", description: "Edit existing admin users" },
15
+ { name: "admin_users:delete", resource_type: "admin_users", action: "delete", description: "Delete admin users" },
16
+ { name: "admin_users:manage_roles", resource_type: "admin_users", action: "manage_roles", description: "Assign and remove user roles" },
17
+
18
+ # Reports permissions
19
+ { name: "reports:view", resource_type: "reports", action: "view", description: "View reports" },
20
+ { name: "reports:export", resource_type: "reports", action: "export", description: "Export reports" },
21
+
22
+ # Settings permissions
23
+ { name: "settings:read", resource_type: "settings", action: "read", description: "View system settings" },
24
+ { name: "settings:update", resource_type: "settings", action: "update", description: "Update system settings" }
25
+ ]
26
+
27
+ puts " Creating permissions..."
28
+ permissions = {}
29
+ permissions_data.each do |perm_data|
30
+ permission = EasyAdmin::Permissions::Permission.find_or_create_by(name: perm_data[:name]) do |p|
31
+ p.resource_type = perm_data[:resource_type]
32
+ p.action = perm_data[:action]
33
+ p.description = perm_data[:description]
34
+ end
35
+ permissions[perm_data[:name]] = permission
36
+ puts " ✓ #{perm_data[:name]}"
37
+ end
38
+
39
+ # Create roles
40
+ roles_data = [
41
+ {
42
+ name: "Super Admin",
43
+ slug: "super_admin",
44
+ description: "Full system access - all permissions",
45
+ permissions: permissions.keys
46
+ },
47
+ {
48
+ name: "Admin",
49
+ slug: "admin",
50
+ description: "Administrative access with user management",
51
+ permissions: [
52
+ "dashboard:view", "admin_users:read", "admin_users:create", "admin_users:update", "admin_users:manage_roles",
53
+ "reports:view", "reports:export", "settings:read"
54
+ ]
55
+ },
56
+ {
57
+ name: "Manager",
58
+ slug: "manager",
59
+ description: "Management access with limited user permissions",
60
+ permissions: [
61
+ "dashboard:view", "admin_users:read", "admin_users:update", "reports:view", "reports:export"
62
+ ]
63
+ },
64
+ {
65
+ name: "Editor",
66
+ slug: "editor",
67
+ description: "Content editing permissions",
68
+ permissions: [
69
+ "dashboard:view", "admin_users:read"
70
+ ]
71
+ },
72
+ {
73
+ name: "Viewer",
74
+ slug: "viewer",
75
+ description: "Read-only access",
76
+ permissions: [
77
+ "dashboard:view", "admin_users:read", "reports:view"
78
+ ]
79
+ }
80
+ ]
81
+
82
+ puts " Creating roles..."
83
+ roles_data.each do |role_data|
84
+ role = EasyAdmin::Permissions::Role.find_or_create_by(slug: role_data[:slug]) do |r|
85
+ r.name = role_data[:name]
86
+ r.description = role_data[:description]
87
+ r.active = true
88
+ end
89
+
90
+ # Assign permissions to role
91
+ role_data[:permissions].each do |permission_name|
92
+ permission = permissions[permission_name]
93
+ if permission
94
+ EasyAdmin::Permissions::RolePermission.find_or_create_by(
95
+ role: role,
96
+ permission: permission
97
+ )
98
+ end
99
+ end
100
+
101
+ puts " ✓ #{role_data[:name]} (#{role.permissions.count} permissions)"
102
+ end
103
+
104
+ # Assign super admin role to the first admin user
105
+ if EasyAdmin::AdminUser.exists?
106
+ first_admin = EasyAdmin::AdminUser.first
107
+ super_admin_role = EasyAdmin::Permissions::Role.find_by(slug: "super_admin")
108
+
109
+ if first_admin && super_admin_role
110
+ first_admin.assign_role(super_admin_role)
111
+ puts " ✓ Assigned Super Admin role to #{first_admin.email}"
112
+ end
113
+ end
114
+
115
+ puts "✅ EasyAdmin Permission System setup complete!"
116
+ puts ""
117
+ puts "Next steps:"
118
+ puts "1. Configure permissions in config/initializers/easy_admin_permissions.rb"
119
+ puts "2. Assign roles to users: admin_user.assign_role(:admin)"
120
+ puts "3. Check permissions: admin_user.has_permission?('admin_users:read')"
121
+ puts ""
@@ -5,6 +5,7 @@ require "easy_admin/version"
5
5
  require "easy_admin/configuration"
6
6
  require "easy_admin/resource_registry"
7
7
  require "easy_admin/versioning"
8
+ require "easy_admin/resource_modules"
8
9
  require "easy_admin/resource"
9
10
  require "easy_admin/dashboard_registry"
10
11
  require "easy_admin/dashboard"
@@ -12,6 +13,7 @@ require "easy_admin/field"
12
13
  require "easy_admin/action"
13
14
  require "easy_admin/delete_action"
14
15
  require "easy_admin/batch_action"
16
+ require "easy_admin/permissions"
15
17
  require "easy_admin/engine"
16
18
 
17
19
  module EasyAdmin