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.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_admin.base.js +254 -18
- data/app/assets/builds/easy_admin.base.js.map +4 -4
- data/app/assets/builds/easy_admin.css +112 -18
- data/app/components/easy_admin/base_component.rb +1 -0
- data/app/components/easy_admin/form_tabs_component.rb +5 -2
- data/app/components/easy_admin/navbar_component.rb +5 -1
- data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
- data/app/components/easy_admin/resources/index_component.rb +1 -4
- data/app/components/easy_admin/sidebar_component.rb +67 -2
- data/app/components/easy_admin/versions/diff_modal_component.rb +5 -1
- data/app/controllers/easy_admin/application_controller.rb +131 -1
- data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
- data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
- data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
- data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
- data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
- data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
- data/app/controllers/easy_admin/resources_controller.rb +13 -762
- data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
- data/app/helpers/easy_admin/fields_helper.rb +61 -9
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
- data/app/javascript/easy_admin/controllers.js +5 -1
- data/app/models/easy_admin/admin_user.rb +6 -0
- data/app/policies/admin_user_policy.rb +36 -0
- data/app/policies/application_policy.rb +83 -0
- data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
- data/app/views/easy_admin/dashboards/card.html.erb +5 -0
- data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
- data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
- data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
- data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
- data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
- data/app/views/easy_admin/resources/edit.html.erb +1 -1
- data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
- data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/index.html.erb +1 -1
- data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
- data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
- data/app/views/layouts/easy_admin/application.html.erb +15 -2
- data/config/initializers/easy_admin_permissions.rb +73 -0
- data/db/seeds/easy_admin_permissions.rb +121 -0
- data/lib/easy-admin-rails.rb +2 -0
- data/lib/easy_admin/permissions/component.rb +168 -0
- data/lib/easy_admin/permissions/configuration.rb +37 -0
- data/lib/easy_admin/permissions/controller.rb +164 -0
- data/lib/easy_admin/permissions/dsl.rb +180 -0
- data/lib/easy_admin/permissions/models.rb +44 -0
- data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
- data/lib/easy_admin/permissions/role_definition.rb +45 -0
- data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
- data/lib/easy_admin/permissions/role_dsl.rb +73 -0
- data/lib/easy_admin/permissions/user_extensions.rb +129 -0
- data/lib/easy_admin/permissions.rb +113 -0
- data/lib/easy_admin/resource/base.rb +119 -0
- data/lib/easy_admin/resource/configuration.rb +148 -0
- data/lib/easy_admin/resource/dsl.rb +117 -0
- data/lib/easy_admin/resource/field_registry.rb +189 -0
- data/lib/easy_admin/resource/form_builder.rb +123 -0
- data/lib/easy_admin/resource/layout_builder.rb +249 -0
- data/lib/easy_admin/resource/scope_manager.rb +252 -0
- data/lib/easy_admin/resource/show_builder.rb +359 -0
- data/lib/easy_admin/resource.rb +8 -835
- data/lib/easy_admin/resource_modules.rb +11 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
- data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
- data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
- data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
- data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
- metadata +62 -5
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -3418,10 +3418,6 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
3418
3418
|
left: 50%;
|
3419
3419
|
}
|
3420
3420
|
|
3421
|
-
.left-10 {
|
3422
|
-
left: 2.5rem;
|
3423
|
-
}
|
3424
|
-
|
3425
3421
|
.left-2 {
|
3426
3422
|
left: 0.5rem;
|
3427
3423
|
}
|
@@ -3430,10 +3426,6 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
3430
3426
|
left: 1.5rem;
|
3431
3427
|
}
|
3432
3428
|
|
3433
|
-
.left-8 {
|
3434
|
-
left: 2rem;
|
3435
|
-
}
|
3436
|
-
|
3437
3429
|
.right-0 {
|
3438
3430
|
right: 0px;
|
3439
3431
|
}
|
@@ -3616,6 +3608,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
3616
3608
|
margin-inline-start: auto;
|
3617
3609
|
}
|
3618
3610
|
|
3611
|
+
.mt-0\.5 {
|
3612
|
+
margin-top: 0.125rem;
|
3613
|
+
}
|
3614
|
+
|
3619
3615
|
.mt-1 {
|
3620
3616
|
margin-top: 0.25rem;
|
3621
3617
|
}
|
@@ -3712,6 +3708,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
3712
3708
|
height: 0.5rem;
|
3713
3709
|
}
|
3714
3710
|
|
3711
|
+
.h-20 {
|
3712
|
+
height: 5rem;
|
3713
|
+
}
|
3714
|
+
|
3715
3715
|
.h-3 {
|
3716
3716
|
height: 0.75rem;
|
3717
3717
|
}
|
@@ -3764,6 +3764,14 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
3764
3764
|
min-height: 2.5rem;
|
3765
3765
|
}
|
3766
3766
|
|
3767
|
+
.min-h-96 {
|
3768
|
+
min-height: 24rem;
|
3769
|
+
}
|
3770
|
+
|
3771
|
+
.min-h-\[2rem\] {
|
3772
|
+
min-height: 2rem;
|
3773
|
+
}
|
3774
|
+
|
3767
3775
|
.min-h-\[40px\] {
|
3768
3776
|
min-height: 40px;
|
3769
3777
|
}
|
@@ -4063,6 +4071,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4063
4071
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
4064
4072
|
}
|
4065
4073
|
|
4074
|
+
.grid-cols-3 {
|
4075
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
4076
|
+
}
|
4077
|
+
|
4066
4078
|
.grid-cols-4 {
|
4067
4079
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
4068
4080
|
}
|
@@ -4355,6 +4367,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4355
4367
|
border-style: none;
|
4356
4368
|
}
|
4357
4369
|
|
4370
|
+
.border-amber-200 {
|
4371
|
+
--tw-border-opacity: 1;
|
4372
|
+
border-color: rgb(253 230 138 / var(--tw-border-opacity, 1));
|
4373
|
+
}
|
4374
|
+
|
4358
4375
|
.border-blue-100 {
|
4359
4376
|
--tw-border-opacity: 1;
|
4360
4377
|
border-color: rgb(219 234 254 / var(--tw-border-opacity, 1));
|
@@ -4458,6 +4475,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4458
4475
|
border-color: transparent;
|
4459
4476
|
}
|
4460
4477
|
|
4478
|
+
.border-white {
|
4479
|
+
--tw-border-opacity: 1;
|
4480
|
+
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
4481
|
+
}
|
4482
|
+
|
4461
4483
|
.border-white\/20 {
|
4462
4484
|
border-color: rgb(255 255 255 / 0.2);
|
4463
4485
|
}
|
@@ -4472,6 +4494,16 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4472
4494
|
border-color: rgb(254 240 138 / var(--tw-border-opacity, 1));
|
4473
4495
|
}
|
4474
4496
|
|
4497
|
+
.bg-amber-50 {
|
4498
|
+
--tw-bg-opacity: 1;
|
4499
|
+
background-color: rgb(255 251 235 / var(--tw-bg-opacity, 1));
|
4500
|
+
}
|
4501
|
+
|
4502
|
+
.bg-amber-600 {
|
4503
|
+
--tw-bg-opacity: 1;
|
4504
|
+
background-color: rgb(217 119 6 / var(--tw-bg-opacity, 1));
|
4505
|
+
}
|
4506
|
+
|
4475
4507
|
.bg-black {
|
4476
4508
|
--tw-bg-opacity: 1;
|
4477
4509
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
|
@@ -4822,6 +4854,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4822
4854
|
padding-right: 0px;
|
4823
4855
|
}
|
4824
4856
|
|
4857
|
+
.px-1\.5 {
|
4858
|
+
padding-left: 0.375rem;
|
4859
|
+
padding-right: 0.375rem;
|
4860
|
+
}
|
4861
|
+
|
4825
4862
|
.px-2 {
|
4826
4863
|
padding-left: 0.5rem;
|
4827
4864
|
padding-right: 0.5rem;
|
@@ -4916,6 +4953,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4916
4953
|
padding-bottom: 1rem;
|
4917
4954
|
}
|
4918
4955
|
|
4956
|
+
.pb-6 {
|
4957
|
+
padding-bottom: 1.5rem;
|
4958
|
+
}
|
4959
|
+
|
4919
4960
|
.pl-3 {
|
4920
4961
|
padding-left: 0.75rem;
|
4921
4962
|
}
|
@@ -4952,6 +4993,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4952
4993
|
padding-top: 5rem;
|
4953
4994
|
}
|
4954
4995
|
|
4996
|
+
.pt-3 {
|
4997
|
+
padding-top: 0.75rem;
|
4998
|
+
}
|
4999
|
+
|
4955
5000
|
.pt-4 {
|
4956
5001
|
padding-top: 1rem;
|
4957
5002
|
}
|
@@ -4994,6 +5039,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
4994
5039
|
line-height: 2.25rem;
|
4995
5040
|
}
|
4996
5041
|
|
5042
|
+
.text-6xl {
|
5043
|
+
font-size: 3.75rem;
|
5044
|
+
line-height: 1;
|
5045
|
+
}
|
5046
|
+
|
4997
5047
|
.text-base {
|
4998
5048
|
font-size: 1rem;
|
4999
5049
|
line-height: 1.5rem;
|
@@ -5051,6 +5101,10 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5051
5101
|
line-height: 1.5rem;
|
5052
5102
|
}
|
5053
5103
|
|
5104
|
+
.leading-relaxed {
|
5105
|
+
line-height: 1.625;
|
5106
|
+
}
|
5107
|
+
|
5054
5108
|
.tracking-wide {
|
5055
5109
|
letter-spacing: 0.025em;
|
5056
5110
|
}
|
@@ -5059,6 +5113,21 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5059
5113
|
letter-spacing: 0.05em;
|
5060
5114
|
}
|
5061
5115
|
|
5116
|
+
.text-amber-400 {
|
5117
|
+
--tw-text-opacity: 1;
|
5118
|
+
color: rgb(251 191 36 / var(--tw-text-opacity, 1));
|
5119
|
+
}
|
5120
|
+
|
5121
|
+
.text-amber-600 {
|
5122
|
+
--tw-text-opacity: 1;
|
5123
|
+
color: rgb(217 119 6 / var(--tw-text-opacity, 1));
|
5124
|
+
}
|
5125
|
+
|
5126
|
+
.text-amber-700 {
|
5127
|
+
--tw-text-opacity: 1;
|
5128
|
+
color: rgb(180 83 9 / var(--tw-text-opacity, 1));
|
5129
|
+
}
|
5130
|
+
|
5062
5131
|
.text-blue-400 {
|
5063
5132
|
--tw-text-opacity: 1;
|
5064
5133
|
color: rgb(96 165 250 / var(--tw-text-opacity, 1));
|
@@ -5129,6 +5198,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5129
5198
|
color: rgb(74 222 128 / var(--tw-text-opacity, 1));
|
5130
5199
|
}
|
5131
5200
|
|
5201
|
+
.text-green-500 {
|
5202
|
+
--tw-text-opacity: 1;
|
5203
|
+
color: rgb(34 197 94 / var(--tw-text-opacity, 1));
|
5204
|
+
}
|
5205
|
+
|
5132
5206
|
.text-green-600 {
|
5133
5207
|
--tw-text-opacity: 1;
|
5134
5208
|
color: rgb(22 163 74 / var(--tw-text-opacity, 1));
|
@@ -5144,6 +5218,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5144
5218
|
color: rgb(22 101 52 / var(--tw-text-opacity, 1));
|
5145
5219
|
}
|
5146
5220
|
|
5221
|
+
.text-green-900 {
|
5222
|
+
--tw-text-opacity: 1;
|
5223
|
+
color: rgb(20 83 45 / var(--tw-text-opacity, 1));
|
5224
|
+
}
|
5225
|
+
|
5147
5226
|
.text-red-400 {
|
5148
5227
|
--tw-text-opacity: 1;
|
5149
5228
|
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
|
@@ -5417,12 +5496,6 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5417
5496
|
transition-duration: 150ms;
|
5418
5497
|
}
|
5419
5498
|
|
5420
|
-
.transition-shadow {
|
5421
|
-
transition-property: box-shadow;
|
5422
|
-
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
5423
|
-
transition-duration: 150ms;
|
5424
|
-
}
|
5425
|
-
|
5426
5499
|
.transition-transform {
|
5427
5500
|
transition-property: transform;
|
5428
5501
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
@@ -5624,6 +5697,21 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5624
5697
|
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
5625
5698
|
}
|
5626
5699
|
|
5700
|
+
.hover\:border-green-300:hover {
|
5701
|
+
--tw-border-opacity: 1;
|
5702
|
+
border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
|
5703
|
+
}
|
5704
|
+
|
5705
|
+
.hover\:border-red-300:hover {
|
5706
|
+
--tw-border-opacity: 1;
|
5707
|
+
border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));
|
5708
|
+
}
|
5709
|
+
|
5710
|
+
.hover\:bg-amber-700:hover {
|
5711
|
+
--tw-bg-opacity: 1;
|
5712
|
+
background-color: rgb(180 83 9 / var(--tw-bg-opacity, 1));
|
5713
|
+
}
|
5714
|
+
|
5627
5715
|
.hover\:bg-blue-100:hover {
|
5628
5716
|
--tw-bg-opacity: 1;
|
5629
5717
|
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
|
@@ -5684,6 +5772,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5684
5772
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity, 1));
|
5685
5773
|
}
|
5686
5774
|
|
5775
|
+
.hover\:bg-green-100:hover {
|
5776
|
+
--tw-bg-opacity: 1;
|
5777
|
+
background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
|
5778
|
+
}
|
5779
|
+
|
5687
5780
|
.hover\:bg-green-50:hover {
|
5688
5781
|
--tw-bg-opacity: 1;
|
5689
5782
|
background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
|
@@ -5748,6 +5841,11 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
5748
5841
|
--tw-gradient-to: #1d4ed8 var(--tw-gradient-to-position);
|
5749
5842
|
}
|
5750
5843
|
|
5844
|
+
.hover\:text-amber-800:hover {
|
5845
|
+
--tw-text-opacity: 1;
|
5846
|
+
color: rgb(146 64 14 / var(--tw-text-opacity, 1));
|
5847
|
+
}
|
5848
|
+
|
5751
5849
|
.hover\:text-blue-600:hover {
|
5752
5850
|
--tw-text-opacity: 1;
|
5753
5851
|
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
@@ -6065,10 +6163,6 @@ input:checked + .toggle-switch .toggle-slider:before {
|
|
6065
6163
|
margin-bottom: 1.5rem;
|
6066
6164
|
}
|
6067
6165
|
|
6068
|
-
.sm\:mb-8 {
|
6069
|
-
margin-bottom: 2rem;
|
6070
|
-
}
|
6071
|
-
|
6072
6166
|
.sm\:ml-16 {
|
6073
6167
|
margin-left: 4rem;
|
6074
6168
|
}
|
@@ -12,6 +12,7 @@ module EasyAdmin
|
|
12
12
|
include ActionView::Helpers::NumberHelper
|
13
13
|
include EasyAdmin::DashboardsHelper
|
14
14
|
include EasyAdmin::FieldsHelper
|
15
|
+
include EasyAdmin::Permissions::Component if defined?(EasyAdmin::Permissions::Component)
|
15
16
|
|
16
17
|
# Add method to access all Rails helpers if needed
|
17
18
|
def helpers
|
@@ -2,9 +2,10 @@ module EasyAdmin
|
|
2
2
|
class FormTabsComponent < Phlex::HTML
|
3
3
|
include EasyAdmin::FieldsHelper
|
4
4
|
|
5
|
-
def initialize(resource_class:, form:)
|
5
|
+
def initialize(resource_class:, form:, record: nil)
|
6
6
|
@resource_class = resource_class
|
7
7
|
@form = form
|
8
|
+
@record = record
|
8
9
|
end
|
9
10
|
|
10
11
|
def view_template
|
@@ -120,7 +121,7 @@ module EasyAdmin
|
|
120
121
|
div(class: "grid grid-cols-1 gap-6") do
|
121
122
|
tab[:fields].each do |field|
|
122
123
|
div do
|
123
|
-
unsafe_raw render_field(field, action: :form, form: @form)
|
124
|
+
unsafe_raw render_field(field, action: :form, form: @form, record: @record)
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
@@ -158,6 +159,8 @@ module EasyAdmin
|
|
158
159
|
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>'
|
159
160
|
when :info
|
160
161
|
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
|
162
|
+
when :shield
|
163
|
+
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>'
|
161
164
|
else
|
162
165
|
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>'
|
163
166
|
end
|
@@ -191,7 +191,11 @@ module EasyAdmin
|
|
191
191
|
end
|
192
192
|
|
193
193
|
def user_role
|
194
|
-
@current_user&.respond_to?(:role)
|
194
|
+
if @current_user&.respond_to?(:role) && @current_user.role
|
195
|
+
@current_user.role.name || @current_user.role.slug || "Administrator"
|
196
|
+
else
|
197
|
+
"No Role Assigned"
|
198
|
+
end
|
195
199
|
end
|
196
200
|
|
197
201
|
# SVG Icons
|
@@ -0,0 +1,254 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Permissions
|
3
|
+
class UserRoleAssignmentComponent < EasyAdmin::BaseComponent
|
4
|
+
def initialize(user:, form: nil)
|
5
|
+
@user = user
|
6
|
+
@form = form
|
7
|
+
@current_role = user&.respond_to?(:role) ? user.role : nil
|
8
|
+
|
9
|
+
# Get actual permissions from permissions_cache
|
10
|
+
@user_permissions_cache = get_user_permissions_from_cache(user)
|
11
|
+
|
12
|
+
# Get all available resources and generate permissions dynamically
|
13
|
+
@available_resources = EasyAdmin::Permissions.available_resources
|
14
|
+
@all_permissions = generate_permissions_from_resources
|
15
|
+
end
|
16
|
+
|
17
|
+
def view_template
|
18
|
+
div(class: "space-y-6") do
|
19
|
+
# Current Role Section
|
20
|
+
render_current_role_section
|
21
|
+
|
22
|
+
# Permissions Section
|
23
|
+
if @form
|
24
|
+
render_permissions_form_section
|
25
|
+
else
|
26
|
+
render_permissions_display_section
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def render_current_role_section
|
34
|
+
div(class: "bg-white border border-gray-200 rounded-lg p-6") do
|
35
|
+
div(class: "flex items-center justify-between mb-4") do
|
36
|
+
h3(class: "text-lg font-medium text-gray-900") { "Role Assignment" }
|
37
|
+
|
38
|
+
if @current_role
|
39
|
+
span(class: "inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800") do
|
40
|
+
@current_role.name
|
41
|
+
end
|
42
|
+
else
|
43
|
+
span(class: "inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800") do
|
44
|
+
"No role assigned"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if @current_role&.description.present?
|
50
|
+
p(class: "text-gray-600") { @current_role.description }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Show enabled permissions (read-only view)
|
56
|
+
def render_permissions_display_section
|
57
|
+
div(class: "bg-white border border-gray-200 rounded-lg p-6") do
|
58
|
+
h3(class: "text-lg font-medium text-gray-900 mb-4") { "Current Permissions" }
|
59
|
+
|
60
|
+
enabled_permissions = @user_permissions_cache.select { |k, v| v == "true" }
|
61
|
+
|
62
|
+
if enabled_permissions.any?
|
63
|
+
div(class: "grid grid-cols-1 md:grid-cols-2 gap-4") do
|
64
|
+
@available_resources.each do |resource|
|
65
|
+
plural_resource = resource.pluralize
|
66
|
+
resource_permissions = enabled_permissions.select { |k, _| k.start_with?("#{plural_resource}:") }
|
67
|
+
next if resource_permissions.empty?
|
68
|
+
|
69
|
+
div(class: "border border-green-200 bg-green-50 rounded-lg p-4") do
|
70
|
+
h4(class: "font-medium text-green-900 mb-2") { plural_resource.humanize.titleize }
|
71
|
+
div(class: "space-y-1") do
|
72
|
+
resource_permissions.each do |permission_name, _|
|
73
|
+
action = permission_name.split(':').last
|
74
|
+
span(class: "inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 mr-2 mb-1") do
|
75
|
+
action.humanize
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
else
|
83
|
+
p(class: "text-gray-500 italic") { "No permissions currently assigned." }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Show permission form (editable)
|
89
|
+
def render_permissions_form_section
|
90
|
+
div(class: "bg-white border border-gray-200 rounded-lg p-6") do
|
91
|
+
h3(class: "text-lg font-medium text-gray-900 mb-4") { "Manage Permissions" }
|
92
|
+
p(class: "text-sm text-gray-600 mb-6") { "Click to toggle permissions for each resource:" }
|
93
|
+
|
94
|
+
div(class: "space-y-4", data: { controller: "permission-toggle", permission_toggle_user_id_value: @user&.id }) do
|
95
|
+
@available_resources.each do |resource|
|
96
|
+
plural_resource = resource.pluralize
|
97
|
+
render_resource_permission_group(plural_resource)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def render_resource_permission_group(resource)
|
104
|
+
div(class: "border border-gray-200 bg-white rounded-lg p-4") do
|
105
|
+
div(class: "flex items-center justify-between mb-4") do
|
106
|
+
div(class: "flex items-center") do
|
107
|
+
h4(class: "text-md font-medium text-gray-900 capitalize") { resource.humanize }
|
108
|
+
end
|
109
|
+
|
110
|
+
div(class: "flex items-center space-x-2") do
|
111
|
+
span(class: "inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-700") do
|
112
|
+
"4 permissions"
|
113
|
+
end
|
114
|
+
# Toggle all button for this resource
|
115
|
+
button(
|
116
|
+
type: "button",
|
117
|
+
class: "text-xs px-2 py-1 bg-gray-200 text-gray-700 hover:bg-gray-300 rounded transition-colors",
|
118
|
+
data: {
|
119
|
+
action: "click->permission-toggle#toggleAllForResource",
|
120
|
+
resource_type: resource
|
121
|
+
}
|
122
|
+
) do
|
123
|
+
"Toggle All"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Permissions grid with old card style (2 per row)
|
129
|
+
div(class: "grid grid-cols-1 md:grid-cols-2 gap-3") do
|
130
|
+
%w[read create update delete].each do |action|
|
131
|
+
permission_name = "#{resource}:#{action}"
|
132
|
+
is_granted = permission_granted?(permission_name)
|
133
|
+
|
134
|
+
render_permission_toggle_card(permission_name, action, is_granted)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def render_permission_toggle_card(permission_name, action, is_granted)
|
141
|
+
div(
|
142
|
+
class: "permission-card cursor-pointer transition-all duration-200 #{permission_card_classes(is_granted)}",
|
143
|
+
data: {
|
144
|
+
action: "click->permission-toggle#togglePermission",
|
145
|
+
permission_toggle_target: "permissionCard",
|
146
|
+
permission_name: permission_name,
|
147
|
+
granted: is_granted.to_s,
|
148
|
+
resource_type: permission_name.split(':').first
|
149
|
+
}
|
150
|
+
) do
|
151
|
+
div(class: "flex items-start p-3") do
|
152
|
+
# Permission icon
|
153
|
+
div(class: "flex-shrink-0 mr-3") do
|
154
|
+
unsafe_raw permission_icon_svg(is_granted)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Permission details
|
158
|
+
div(class: "flex-1 min-w-0") do
|
159
|
+
div(class: "mb-1") do
|
160
|
+
span(class: "text-sm font-medium #{is_granted ? 'text-green-900' : 'text-gray-900'} capitalize") do
|
161
|
+
action.humanize
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Permission description
|
166
|
+
p(class: "text-xs #{is_granted ? 'text-green-700' : 'text-gray-600'} leading-relaxed") do
|
167
|
+
permission_description(action)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Permission name (technical)
|
171
|
+
p(class: "text-xs #{is_granted ? 'text-green-600' : 'text-gray-500'} font-mono mt-1") do
|
172
|
+
permission_name
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Hidden input for form submission
|
178
|
+
if @form
|
179
|
+
model_name = @form.object.class.name.underscore
|
180
|
+
field_name = "#{model_name}[permissions_cache][#{permission_name}]"
|
181
|
+
input(
|
182
|
+
type: "hidden",
|
183
|
+
name: field_name,
|
184
|
+
value: is_granted.to_s,
|
185
|
+
data: { permission_toggle_target: "hiddenInput" }
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def generate_permissions_from_resources
|
192
|
+
permissions = {}
|
193
|
+
@available_resources.each do |resource|
|
194
|
+
# Pluralize resource name to match permissions_cache format
|
195
|
+
plural_resource = resource.pluralize
|
196
|
+
%w[read create update delete].each do |action|
|
197
|
+
permission_name = "#{plural_resource}:#{action}"
|
198
|
+
permissions[permission_name] = {
|
199
|
+
name: permission_name,
|
200
|
+
resource: plural_resource,
|
201
|
+
action: action
|
202
|
+
}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
permissions
|
206
|
+
end
|
207
|
+
|
208
|
+
def get_user_permissions_from_cache(user)
|
209
|
+
return {} unless user&.permissions_cache.present?
|
210
|
+
|
211
|
+
case user.permissions_cache
|
212
|
+
when Hash
|
213
|
+
user.permissions_cache
|
214
|
+
when String
|
215
|
+
JSON.parse(user.permissions_cache) rescue {}
|
216
|
+
else
|
217
|
+
{}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def permission_card_classes(is_granted)
|
222
|
+
if is_granted
|
223
|
+
"bg-green-50 border border-green-200 hover:border-green-300 hover:bg-green-100"
|
224
|
+
else
|
225
|
+
"bg-red-50 border border-red-200 hover:border-red-300 hover:bg-red-100"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def permission_granted?(permission_name)
|
230
|
+
permission_value = @user_permissions_cache[permission_name]
|
231
|
+
# Handle string "true", boolean true, and string "1"
|
232
|
+
permission_value == "true" || permission_value == true || permission_value == "1"
|
233
|
+
end
|
234
|
+
|
235
|
+
def permission_description(action)
|
236
|
+
descriptions = {
|
237
|
+
'read' => 'View and list records',
|
238
|
+
'create' => 'Create new records',
|
239
|
+
'update' => 'Edit existing records',
|
240
|
+
'delete' => 'Remove records'
|
241
|
+
}
|
242
|
+
descriptions[action] || action.humanize
|
243
|
+
end
|
244
|
+
|
245
|
+
def permission_icon_svg(is_granted)
|
246
|
+
if is_granted
|
247
|
+
'<svg class="w-5 h-5 text-green-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>'
|
248
|
+
else
|
249
|
+
'<svg class="w-5 h-5 text-red-500 mt-0.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|