cccux 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/CHANGELOG.md +67 -0
- data/MIT-LICENSE +20 -0
- data/README.md +382 -0
- data/Rakefile +8 -0
- data/app/assets/config/cccux_manifest.js +1 -0
- data/app/assets/stylesheets/cccux/application.css +102 -0
- data/app/controllers/cccux/ability_permissions_controller.rb +271 -0
- data/app/controllers/cccux/application_controller.rb +37 -0
- data/app/controllers/cccux/authorization_controller.rb +10 -0
- data/app/controllers/cccux/cccux_controller.rb +64 -0
- data/app/controllers/cccux/dashboard_controller.rb +172 -0
- data/app/controllers/cccux/home_controller.rb +19 -0
- data/app/controllers/cccux/roles_controller.rb +290 -0
- data/app/controllers/cccux/simple_controller.rb +7 -0
- data/app/controllers/cccux/users_controller.rb +112 -0
- data/app/controllers/concerns/cccux/application_controller_concern.rb +32 -0
- data/app/helpers/cccux/application_helper.rb +4 -0
- data/app/helpers/cccux/authorization_helper.rb +228 -0
- data/app/jobs/cccux/application_job.rb +4 -0
- data/app/mailers/cccux/application_mailer.rb +6 -0
- data/app/models/cccux/ability.rb +142 -0
- data/app/models/cccux/ability_permission.rb +61 -0
- data/app/models/cccux/application_record.rb +5 -0
- data/app/models/cccux/role.rb +90 -0
- data/app/models/cccux/role_ability.rb +49 -0
- data/app/models/cccux/user_role.rb +42 -0
- data/app/models/concerns/cccux/authorizable.rb +25 -0
- data/app/models/concerns/cccux/scoped_ownership.rb +183 -0
- data/app/models/concerns/cccux/user_concern.rb +87 -0
- data/app/views/cccux/ability_permissions/edit.html.erb +58 -0
- data/app/views/cccux/ability_permissions/index.html.erb +108 -0
- data/app/views/cccux/ability_permissions/new.html.erb +308 -0
- data/app/views/cccux/dashboard/index.html.erb +69 -0
- data/app/views/cccux/dashboard/model_discovery.html.erb +148 -0
- data/app/views/cccux/home/index.html.erb +42 -0
- data/app/views/cccux/roles/_flash.html.erb +10 -0
- data/app/views/cccux/roles/_form.html.erb +78 -0
- data/app/views/cccux/roles/_role.html.erb +67 -0
- data/app/views/cccux/roles/edit.html.erb +317 -0
- data/app/views/cccux/roles/index.html.erb +51 -0
- data/app/views/cccux/roles/new.html.erb +3 -0
- data/app/views/cccux/roles/show.html.erb +99 -0
- data/app/views/cccux/users/edit.html.erb +117 -0
- data/app/views/cccux/users/index.html.erb +99 -0
- data/app/views/cccux/users/new.html.erb +94 -0
- data/app/views/cccux/users/show.html.erb +138 -0
- data/app/views/layouts/cccux/admin.html.erb +168 -0
- data/app/views/layouts/cccux/application.html.erb +17 -0
- data/app/views/shared/_footer.html.erb +101 -0
- data/config/routes.rb +63 -0
- data/db/migrate/20250626194001_create_cccux_roles.rb +15 -0
- data/db/migrate/20250626194007_create_cccux_ability_permissions.rb +18 -0
- data/db/migrate/20250626194011_create_cccux_user_roles.rb +13 -0
- data/db/migrate/20250626194016_create_cccux_role_abilities.rb +10 -0
- data/db/migrate/20250627170611_add_owned_to_cccux_role_abilities.rb +9 -0
- data/db/migrate/20250705193709_add_context_to_cccux_role_abilities.rb +9 -0
- data/db/migrate/20250706214415_add_ownership_configuration_to_role_abilities.rb +21 -0
- data/db/seeds.rb +136 -0
- data/lib/cccux/engine.rb +50 -0
- data/lib/cccux/version.rb +3 -0
- data/lib/cccux.rb +7 -0
- data/lib/tasks/cccux.rake +703 -0
- data/lib/tasks/view_helpers.rake +274 -0
- metadata +188 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
|
2
|
+
<h1>User Management</h1>
|
|
3
|
+
<%= link_to "New User", cccux.new_user_path,
|
|
4
|
+
style: "background-color: #007bff; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div style="background-color: #e7f3ff; border: 1px solid #b8daff; padding: 1rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
8
|
+
<h4 style="margin-top: 0; color: #004085;">👥 User Management</h4>
|
|
9
|
+
<p style="color: #004085; font-size: 0.9rem; margin-bottom: 0;">
|
|
10
|
+
Manage users and their role assignments. Users automatically get the "Basic User" role when created.
|
|
11
|
+
You can assign additional roles or modify existing assignments.
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<% if @users.any? %>
|
|
16
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; overflow: hidden;">
|
|
17
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
18
|
+
<thead style="background-color: #f8f9fa;">
|
|
19
|
+
<tr>
|
|
20
|
+
<th style="padding: 1rem; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: bold; color: #495057;">ID</th>
|
|
21
|
+
<th style="padding: 1rem; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: bold; color: #495057;">Email</th>
|
|
22
|
+
<th style="padding: 1rem; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: bold; color: #495057;">Roles</th>
|
|
23
|
+
<th style="padding: 1rem; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: bold; color: #495057;">Created</th>
|
|
24
|
+
<th style="padding: 1rem; text-align: center; border-bottom: 1px solid #dee2e6; font-weight: bold; color: #495057;">Actions</th>
|
|
25
|
+
</tr>
|
|
26
|
+
</thead>
|
|
27
|
+
<tbody>
|
|
28
|
+
<% @users.each do |user| %>
|
|
29
|
+
<tr style="border-bottom: 1px solid #f8f9fa;">
|
|
30
|
+
<td style="padding: 1rem; color: #6c757d; font-size: 0.9rem; font-family: monospace;">
|
|
31
|
+
#<%= user.id %>
|
|
32
|
+
</td>
|
|
33
|
+
<td style="padding: 1rem; color: #495057;">
|
|
34
|
+
<strong><%= user.email %></strong>
|
|
35
|
+
<% if user.created_at > 24.hours.ago %>
|
|
36
|
+
<span style="background-color: #d4edda; color: #155724; padding: 0.25rem 0.5rem; border-radius: 12px; font-size: 0.75rem; margin-left: 0.5rem;">NEW</span>
|
|
37
|
+
<% end %>
|
|
38
|
+
</td>
|
|
39
|
+
<td style="padding: 1rem;">
|
|
40
|
+
<% if user.cccux_roles.any? %>
|
|
41
|
+
<% user.cccux_roles.each do |role| %>
|
|
42
|
+
<span style="background-color: #e7f3ff; color: #0066cc; padding: 0.25rem 0.5rem; border-radius: 12px; font-size: 0.75rem; margin-right: 0.25rem; margin-bottom: 0.25rem; display: inline-block;">
|
|
43
|
+
<%= role.name %>
|
|
44
|
+
</span>
|
|
45
|
+
<% end %>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span style="color: #6c757d; font-style: italic; font-size: 0.9rem;">No roles assigned</span>
|
|
48
|
+
<% end %>
|
|
49
|
+
</td>
|
|
50
|
+
<td style="padding: 1rem; color: #6c757d; font-size: 0.9rem;">
|
|
51
|
+
<%= user.created_at.strftime('%b %d, %Y') %>
|
|
52
|
+
</td>
|
|
53
|
+
<td style="padding: 1rem; text-align: center;">
|
|
54
|
+
<div style="display: flex; gap: 0.5rem; justify-content: center;">
|
|
55
|
+
<%= link_to "View", cccux.user_path(user),
|
|
56
|
+
style: "color: #007bff; text-decoration: none; font-size: 0.9rem;" %>
|
|
57
|
+
<%= link_to "Edit", cccux.edit_user_path(user),
|
|
58
|
+
style: "color: #28a745; text-decoration: none; font-size: 0.9rem;" %>
|
|
59
|
+
<%= link_to "Delete", cccux.user_path(user),
|
|
60
|
+
data: { "turbo-method": :delete, "turbo-confirm": "Are you sure? This will permanently delete the user." },
|
|
61
|
+
style: "color: #dc3545; text-decoration: none; font-size: 0.9rem;" %>
|
|
62
|
+
</div>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<% end %>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div style="margin-top: 1rem; color: #6c757d; font-size: 0.9rem; text-align: center;">
|
|
71
|
+
Showing <%= @users.count %> users
|
|
72
|
+
</div>
|
|
73
|
+
<% else %>
|
|
74
|
+
<div style="text-align: center; padding: 3rem; background-color: #f8f9fa; border-radius: 8px;">
|
|
75
|
+
<h3 style="color: #495057; margin-bottom: 1rem;">No users found</h3>
|
|
76
|
+
<p style="color: #6c757d; margin-bottom: 2rem;">Get started by creating your first user.</p>
|
|
77
|
+
<%= link_to "Create First User", cccux.new_user_path,
|
|
78
|
+
style: "background-color: #007bff; color: white; padding: 0.75rem 1.5rem; text-decoration: none; border-radius: 4px; font-weight: bold;" %>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
82
|
+
<!-- Quick Stats -->
|
|
83
|
+
<div style="margin-top: 2rem; padding: 1rem; background-color: #f8f9fa; border-radius: 8px;">
|
|
84
|
+
<h4 style="margin-top: 0; color: #495057;">Quick Stats</h4>
|
|
85
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
|
86
|
+
<div style="text-align: center;">
|
|
87
|
+
<div style="font-size: 1.5rem; font-weight: bold; color: #007bff;"><%= @users.count %></div>
|
|
88
|
+
<div style="color: #6c757d; font-size: 0.9rem;">Total Users</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div style="text-align: center;">
|
|
91
|
+
<div style="font-size: 1.5rem; font-weight: bold; color: #28a745;"><%= @users.joins(:cccux_roles).distinct.count %></div>
|
|
92
|
+
<div style="color: #6c757d; font-size: 0.9rem;">Users with Roles</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div style="text-align: center;">
|
|
95
|
+
<div style="font-size: 1.5rem; font-weight: bold; color: #ffc107;"><%= @roles.count %></div>
|
|
96
|
+
<div style="color: #6c757d; font-size: 0.9rem;">Available Roles</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
|
2
|
+
<h1>Create New User</h1>
|
|
3
|
+
<%= link_to "← Back to Users", cccux.users_path,
|
|
4
|
+
style: "background-color: #6c757d; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<%= form_with model: [@user], url: cccux.users_path, local: true do |form| %>
|
|
8
|
+
<% if @user.errors.any? %>
|
|
9
|
+
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 1rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
10
|
+
<h4 style="margin-top: 0;">Please fix the following errors:</h4>
|
|
11
|
+
<ul style="margin-bottom: 0;">
|
|
12
|
+
<% @user.errors.full_messages.each do |message| %>
|
|
13
|
+
<li><%= message %></li>
|
|
14
|
+
<% end %>
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
18
|
+
|
|
19
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
|
|
20
|
+
<!-- User Information -->
|
|
21
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem;">
|
|
22
|
+
<h3 style="margin-top: 0; color: #495057;">User Information</h3>
|
|
23
|
+
|
|
24
|
+
<div style="margin-bottom: 1.5rem;">
|
|
25
|
+
<%= form.label :email, style: "display: block; margin-bottom: 0.5rem; font-weight: bold; color: #495057;" %>
|
|
26
|
+
<%= form.email_field :email,
|
|
27
|
+
style: "width: 100%; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 1rem;" %>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div style="margin-bottom: 1.5rem;">
|
|
31
|
+
<%= form.label :password, style: "display: block; margin-bottom: 0.5rem; font-weight: bold; color: #495057;" %>
|
|
32
|
+
<%= form.password_field :password,
|
|
33
|
+
style: "width: 100%; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 1rem;" %>
|
|
34
|
+
<small style="color: #6c757d; font-size: 0.875rem;">Minimum 6 characters</small>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div style="margin-bottom: 1.5rem;">
|
|
38
|
+
<%= form.label :password_confirmation, style: "display: block; margin-bottom: 0.5rem; font-weight: bold; color: #495057;" %>
|
|
39
|
+
<%= form.password_field :password_confirmation,
|
|
40
|
+
style: "width: 100%; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 1rem;" %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Role Assignment -->
|
|
45
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem;">
|
|
46
|
+
<h3 style="margin-top: 0; color: #495057;">Role Assignment</h3>
|
|
47
|
+
|
|
48
|
+
<div style="background-color: #e7f3ff; border: 1px solid #b8daff; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
|
|
49
|
+
<p style="margin: 0; color: #004085; font-size: 0.9rem;">
|
|
50
|
+
<strong>Note:</strong> Users automatically receive the "Basic User" role when created.
|
|
51
|
+
You can assign additional roles here.
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<% if @roles.any? %>
|
|
56
|
+
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #e9ecef; border-radius: 4px; padding: 0.5rem;">
|
|
57
|
+
<% @roles.each do |role| %>
|
|
58
|
+
<div style="padding: 0.5rem; border-bottom: 1px solid #f8f9fa; display: flex; align-items: center;">
|
|
59
|
+
<%= check_box_tag "user[role_ids][]", role.id, false,
|
|
60
|
+
id: "role_#{role.id}",
|
|
61
|
+
style: "margin-right: 0.5rem;" %>
|
|
62
|
+
<%= label_tag "role_#{role.id}", style: "margin: 0; cursor: pointer; flex: 1;" do %>
|
|
63
|
+
<strong style="color: #495057;"><%= role.name %></strong>
|
|
64
|
+
<% if role.description.present? %>
|
|
65
|
+
<br><small style="color: #6c757d;"><%= role.description %></small>
|
|
66
|
+
<% end %>
|
|
67
|
+
<% end %>
|
|
68
|
+
</div>
|
|
69
|
+
<% end %>
|
|
70
|
+
</div>
|
|
71
|
+
<% else %>
|
|
72
|
+
<div style="text-align: center; padding: 2rem; color: #6c757d; font-style: italic;">
|
|
73
|
+
No roles available. Create roles first.
|
|
74
|
+
</div>
|
|
75
|
+
<% end %>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Form Actions -->
|
|
80
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem;">
|
|
81
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
82
|
+
<div>
|
|
83
|
+
<%= form.submit "Create User",
|
|
84
|
+
style: "background-color: #28a745; color: white; padding: 0.75rem 1.5rem; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; margin-right: 1rem;" %>
|
|
85
|
+
<%= link_to "Cancel", cccux.users_path,
|
|
86
|
+
style: "color: #6c757d; text-decoration: none; padding: 0.75rem 1rem; font-size: 1rem;" %>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div style="color: #6c757d; font-size: 0.9rem;">
|
|
90
|
+
<em>User will receive login credentials and can change password after first login</em>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<% end %>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
|
2
|
+
<h1>User: <%= @user.email %></h1>
|
|
3
|
+
<div>
|
|
4
|
+
<%= link_to "Edit", cccux.edit_user_path(@user),
|
|
5
|
+
style: "background-color: #28a745; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
6
|
+
<%= link_to "← Back to Users", cccux.users_path,
|
|
7
|
+
style: "background-color: #6c757d; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
|
12
|
+
<!-- User Details -->
|
|
13
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem;">
|
|
14
|
+
<h3 style="margin-top: 0; color: #495057;">User Details</h3>
|
|
15
|
+
|
|
16
|
+
<div style="margin-bottom: 1rem;">
|
|
17
|
+
<strong style="color: #495057;">Email:</strong>
|
|
18
|
+
<span style="display: block; margin-top: 0.25rem; color: #6c757d;"><%= @user.email %></span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div style="margin-bottom: 1rem;">
|
|
22
|
+
<strong style="color: #495057;">Account Created:</strong>
|
|
23
|
+
<span style="display: block; margin-top: 0.25rem; color: #6c757d;">
|
|
24
|
+
<%= @user.created_at.strftime('%B %d, %Y at %I:%M %p') %>
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div style="margin-bottom: 1rem;">
|
|
29
|
+
<strong style="color: #495057;">Last Updated:</strong>
|
|
30
|
+
<span style="display: block; margin-top: 0.25rem; color: #6c757d;">
|
|
31
|
+
<%= @user.updated_at.strftime('%B %d, %Y at %I:%M %p') %>
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div>
|
|
36
|
+
<strong style="color: #495057;">Total Roles:</strong>
|
|
37
|
+
<span style="display: block; margin-top: 0.25rem; color: #6c757d;">
|
|
38
|
+
<%= pluralize(@user_roles.count, 'role') %> assigned
|
|
39
|
+
</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Current Roles -->
|
|
44
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem;">
|
|
45
|
+
<h3 style="margin-top: 0; color: #495057;">Current Roles (<%= @user_roles.count %>)</h3>
|
|
46
|
+
|
|
47
|
+
<% if @user_roles.any? %>
|
|
48
|
+
<div style="margin-bottom: 1.5rem;">
|
|
49
|
+
<% @user_roles.each do |role| %>
|
|
50
|
+
<div style="display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; border: 1px solid #e9ecef; border-radius: 4px; margin-bottom: 0.5rem;">
|
|
51
|
+
<div>
|
|
52
|
+
<strong style="color: #495057;"><%= role.name %></strong>
|
|
53
|
+
<% if role.description.present? %>
|
|
54
|
+
<br><small style="color: #6c757d;"><%= role.description %></small>
|
|
55
|
+
<% end %>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<span style="background-color: #e7f3ff; color: #0066cc; padding: 0.25rem 0.5rem; border-radius: 12px; font-size: 0.75rem;">
|
|
59
|
+
<%= pluralize(role.ability_permissions.count, 'permission') %>
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
<% else %>
|
|
66
|
+
<div style="text-align: center; padding: 2rem; color: #6c757d; font-style: italic; border: 2px dashed #dee2e6; border-radius: 4px;">
|
|
67
|
+
No roles assigned to this user
|
|
68
|
+
</div>
|
|
69
|
+
<% end %>
|
|
70
|
+
|
|
71
|
+
<!-- Quick Role Assignment -->
|
|
72
|
+
<% if @available_roles.any? %>
|
|
73
|
+
<div style="border-top: 1px solid #dee2e6; padding-top: 1rem; margin-top: 1rem;">
|
|
74
|
+
<h5 style="margin-bottom: 0.5rem; color: #495057;">Quick Assign Role:</h5>
|
|
75
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
|
76
|
+
<% @available_roles.limit(3).each do |role| %>
|
|
77
|
+
<%= link_to "+ #{role.name}", cccux.assign_role_user_path(@user, role_id: role.id),
|
|
78
|
+
method: :post,
|
|
79
|
+
style: "background-color: #28a745; color: white; padding: 0.25rem 0.5rem; text-decoration: none; border-radius: 12px; font-size: 0.8rem;" %>
|
|
80
|
+
<% end %>
|
|
81
|
+
<% if @available_roles.count > 3 %>
|
|
82
|
+
<span style="color: #6c757d; font-size: 0.8rem; padding: 0.25rem;">
|
|
83
|
+
+ <%= @available_roles.count - 3 %> more (use Edit)
|
|
84
|
+
</span>
|
|
85
|
+
<% end %>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<% end %>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Role Permissions Matrix -->
|
|
93
|
+
<% if @user_roles.any? %>
|
|
94
|
+
<div style="background-color: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem; margin-top: 2rem;">
|
|
95
|
+
<h3 style="margin-top: 0; color: #495057;">Effective Permissions</h3>
|
|
96
|
+
<p style="color: #6c757d; font-size: 0.9rem; margin-bottom: 1rem;">
|
|
97
|
+
All permissions this user has through their assigned roles:
|
|
98
|
+
</p>
|
|
99
|
+
|
|
100
|
+
<% permissions_by_subject = @user_roles.flat_map(&:ability_permissions).group_by(&:subject) %>
|
|
101
|
+
<% if permissions_by_subject.any? %>
|
|
102
|
+
<div style="max-height: 400px; overflow-y: auto; border: 1px solid #e9ecef; border-radius: 4px;">
|
|
103
|
+
<% permissions_by_subject.each do |subject, permissions| %>
|
|
104
|
+
<div style="padding: 1rem; border-bottom: 1px solid #f8f9fa;">
|
|
105
|
+
<h5 style="margin: 0 0 0.5rem 0; color: #495057;"><%= subject %></h5>
|
|
106
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
|
|
107
|
+
<% permissions.uniq.each do |permission| %>
|
|
108
|
+
<span style="background-color: #e7f3ff; color: #0066cc; padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.75rem;">
|
|
109
|
+
<%= permission.action %>
|
|
110
|
+
</span>
|
|
111
|
+
<% end %>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<% end %>
|
|
115
|
+
</div>
|
|
116
|
+
<% else %>
|
|
117
|
+
<div style="text-align: center; padding: 2rem; color: #6c757d; font-style: italic;">
|
|
118
|
+
No permissions available (roles may not have permissions assigned)
|
|
119
|
+
</div>
|
|
120
|
+
<% end %>
|
|
121
|
+
</div>
|
|
122
|
+
<% end %>
|
|
123
|
+
|
|
124
|
+
<!-- Actions -->
|
|
125
|
+
<div style="margin-top: 2rem; padding: 1rem; background-color: #f8f9fa; border-radius: 8px;">
|
|
126
|
+
<h4 style="margin-top: 0; color: #495057;">Actions</h4>
|
|
127
|
+
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
128
|
+
<%= link_to "Edit User & Roles", cccux.edit_user_path(@user),
|
|
129
|
+
style: "background-color: #007bff; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
130
|
+
<%= link_to "View All Users", cccux.users_path,
|
|
131
|
+
style: "background-color: #6f42c1; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
132
|
+
<%= link_to "Manage Roles", cccux.roles_path,
|
|
133
|
+
style: "background-color: #fd7e14; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
134
|
+
<%= link_to "Delete User", cccux.user_path(@user),
|
|
135
|
+
data: { "turbo-method": :delete, "turbo-confirm": "Are you sure? This will permanently delete the user and all their role assignments." },
|
|
136
|
+
style: "background-color: #dc3545; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 4px;" %>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>CCCUX Authorization Admin</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<%= csp_meta_tag %>
|
|
8
|
+
|
|
9
|
+
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
|
10
|
+
<%= stylesheet_link_tag "stylesheets/cccux/application", "data-turbo-track": "reload" %>
|
|
11
|
+
<%= javascript_importmap_tags %>
|
|
12
|
+
|
|
13
|
+
<!-- Sortable.js for drag and drop -->
|
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
|
15
|
+
|
|
16
|
+
<!-- Drag and drop styles -->
|
|
17
|
+
<style>
|
|
18
|
+
.sortable-ghost {
|
|
19
|
+
opacity: 0.4;
|
|
20
|
+
background-color: #f8f9fa !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sortable-chosen {
|
|
24
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sortable-drag {
|
|
28
|
+
opacity: 0.8;
|
|
29
|
+
transform: rotate(5deg);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.role-card:hover {
|
|
33
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
34
|
+
border-color: #007bff;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.drag-handle:hover {
|
|
38
|
+
color: #007bff !important;
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
|
|
43
|
+
<body>
|
|
44
|
+
<!-- Turbo Loading Indicator -->
|
|
45
|
+
<div id="turbo-indicator" style="position: fixed; top: 10px; right: 10px; background: #28a745; color: white; padding: 0.5rem; border-radius: 4px; display: none; z-index: 9999;">
|
|
46
|
+
🚀 Turbo Loading...
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
// Show loading indicator during Turbo navigation
|
|
51
|
+
document.addEventListener('turbo:before-visit', () => {
|
|
52
|
+
const indicator = document.getElementById('turbo-indicator');
|
|
53
|
+
if (indicator) indicator.style.display = 'block';
|
|
54
|
+
console.log('🚀 TURBO: Starting navigation...');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
document.addEventListener('turbo:load', () => {
|
|
58
|
+
const indicator = document.getElementById('turbo-indicator');
|
|
59
|
+
if (indicator) indicator.style.display = 'none';
|
|
60
|
+
console.log('✅ TURBO: Page loaded!');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// For comparison - this fires on traditional page loads
|
|
64
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
65
|
+
console.log('📄 TRADITIONAL: Full page load');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Initialize role drag and drop
|
|
69
|
+
function initializeRoleSorting() {
|
|
70
|
+
const rolesList = document.getElementById('roles_list');
|
|
71
|
+
if (rolesList && window.Sortable) {
|
|
72
|
+
new Sortable(rolesList, {
|
|
73
|
+
animation: 150,
|
|
74
|
+
ghostClass: 'sortable-ghost',
|
|
75
|
+
chosenClass: 'sortable-chosen',
|
|
76
|
+
dragClass: 'sortable-drag',
|
|
77
|
+
handle: '.role-card',
|
|
78
|
+
onEnd: function(evt) {
|
|
79
|
+
// Get the new order of role IDs
|
|
80
|
+
const roleIds = Array.from(rolesList.children).map(card =>
|
|
81
|
+
card.dataset.roleId
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Send AJAX request to update priorities
|
|
85
|
+
const reorderUrl = rolesList.dataset.reorderUrl;
|
|
86
|
+
fetch(reorderUrl, {
|
|
87
|
+
method: 'PATCH',
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/json',
|
|
90
|
+
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({ role_ids: roleIds })
|
|
93
|
+
})
|
|
94
|
+
.then(response => response.json())
|
|
95
|
+
.then(data => {
|
|
96
|
+
if (data.success) {
|
|
97
|
+
console.log('✅ Role priorities updated successfully');
|
|
98
|
+
// Update priority badges
|
|
99
|
+
updatePriorityBadges(roleIds);
|
|
100
|
+
} else {
|
|
101
|
+
console.error('❌ Failed to update role priorities:', data.error);
|
|
102
|
+
// Revert the visual change
|
|
103
|
+
location.reload();
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.catch(error => {
|
|
107
|
+
console.error('❌ Error updating role priorities:', error);
|
|
108
|
+
location.reload();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update priority badges after reordering
|
|
116
|
+
function updatePriorityBadges(roleIds) {
|
|
117
|
+
roleIds.forEach((roleId, index) => {
|
|
118
|
+
const card = document.querySelector(`[data-role-id="${roleId}"]`);
|
|
119
|
+
const priorityBadge = card.querySelector('.priority-badge');
|
|
120
|
+
if (priorityBadge) {
|
|
121
|
+
const newPriority = (index + 1) * 10;
|
|
122
|
+
priorityBadge.textContent = `Priority: ${newPriority}`;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Initialize on both DOMContentLoaded and turbo:load
|
|
128
|
+
document.addEventListener('DOMContentLoaded', initializeRoleSorting);
|
|
129
|
+
document.addEventListener('turbo:load', initializeRoleSorting);
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<nav class="navbar" style="background-color: #f8f9fa; padding: 1rem; border-bottom: 1px solid #dee2e6;">
|
|
133
|
+
<div class="container-fluid" style="display: flex; justify-content: space-between; align-items: center;">
|
|
134
|
+
<div>
|
|
135
|
+
<%= link_to "CCCUX Authorization", cccux.root_path, style: "font-weight: bold; text-decoration: none; font-size: 1.2rem; color: #007bff;" %>
|
|
136
|
+
<span style="margin-left: 1rem;">
|
|
137
|
+
<%= link_to "Dashboard", cccux.root_path, style: "margin-right: 1rem; text-decoration: none;" %>
|
|
138
|
+
<%= link_to "Users", cccux.users_path, style: "margin-right: 1rem; text-decoration: none;" %>
|
|
139
|
+
<%= link_to "Roles", cccux.roles_path, style: "margin-right: 1rem; text-decoration: none;" %>
|
|
140
|
+
<%= link_to "Permissions", cccux.ability_permissions_path, style: "margin-right: 1rem; text-decoration: none;" %>
|
|
141
|
+
<%= link_to "← Home", main_app.root_path, style: "text-decoration: none; color: #28a745; font-weight: bold;" %>
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div>
|
|
145
|
+
<span style="margin-right: 1rem; color: #666;">
|
|
146
|
+
Admin Panel
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</nav>
|
|
151
|
+
|
|
152
|
+
<div class="container" style="max-width: 1200px; margin: 0 auto; padding: 2rem;">
|
|
153
|
+
<% if notice %>
|
|
154
|
+
<div class="alert alert-success" style="background-color: #d1edff; border: 1px solid #0c5460; padding: 1rem; margin-bottom: 1rem; border-radius: 4px;">
|
|
155
|
+
<%= notice %>
|
|
156
|
+
</div>
|
|
157
|
+
<% end %>
|
|
158
|
+
|
|
159
|
+
<% if alert %>
|
|
160
|
+
<div class="alert alert-danger" style="background-color: #f8d7da; border: 1px solid #721c24; padding: 1rem; margin-bottom: 1rem; border-radius: 4px;">
|
|
161
|
+
<%= alert %>
|
|
162
|
+
</div>
|
|
163
|
+
<% end %>
|
|
164
|
+
|
|
165
|
+
<%= yield %>
|
|
166
|
+
</div>
|
|
167
|
+
</body>
|
|
168
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Cccux</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
|
|
8
|
+
<%= stylesheet_link_tag "cccux/application", media: "all" %>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
|
|
12
|
+
<%= yield %>
|
|
13
|
+
|
|
14
|
+
<%= render 'shared/footer' %>
|
|
15
|
+
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<footer class="footer">
|
|
2
|
+
<div class="footer-content">
|
|
3
|
+
<div class="footer-nav">
|
|
4
|
+
<% if defined?(current_user) && current_user %>
|
|
5
|
+
<%= link_to "Home", main_app.root_path, class: "footer-link" %>
|
|
6
|
+
|
|
7
|
+
<%= link_to "Dashboard", cccux.root_path, class: "footer-link" %>
|
|
8
|
+
<%= link_to "Orders", "/orders", class: "footer-link" %>
|
|
9
|
+
<% if respond_to?(:roles_path) %>
|
|
10
|
+
<%= link_to "Roles", cccux.roles_path, class: "footer-link" %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% else %>
|
|
13
|
+
<%= link_to "Home", main_app.root_path, class: "footer-link" %>
|
|
14
|
+
<%= link_to "Dashboard", cccux.root_path, class: "footer-link" %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="footer-auth">
|
|
19
|
+
<% if defined?(current_user) && current_user %>
|
|
20
|
+
<span class="user-info">
|
|
21
|
+
Welcome, <%= current_user.respond_to?(:first_name) ? current_user.first_name : current_user.email %>
|
|
22
|
+
</span>
|
|
23
|
+
<%= link_to "Logout", main_app.destroy_user_session_path, method: :delete, class: "footer-link auth-link", data: { turbo_method: :delete } %>
|
|
24
|
+
<% else %>
|
|
25
|
+
<%= link_to "Login", main_app.new_user_session_path, class: "footer-link auth-link" %>
|
|
26
|
+
<%= link_to "Sign Up", main_app.new_user_registration_path, class: "footer-link auth-link" %>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</footer>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.footer {
|
|
34
|
+
background-color: #f8f9fa;
|
|
35
|
+
border-top: 1px solid #dee2e6;
|
|
36
|
+
padding: 15px 0;
|
|
37
|
+
margin-top: 40px;
|
|
38
|
+
position: relative;
|
|
39
|
+
bottom: 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.footer-content {
|
|
44
|
+
max-width: 1200px;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 0 20px;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.footer-nav, .footer-auth {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 20px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.footer-link {
|
|
60
|
+
color: #6c757d;
|
|
61
|
+
text-decoration: none;
|
|
62
|
+
padding: 5px 10px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
transition: all 0.2s ease;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.footer-link:hover {
|
|
68
|
+
color: #495057;
|
|
69
|
+
background-color: #e9ecef;
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.footer-link.auth-link {
|
|
74
|
+
color: #007bff;
|
|
75
|
+
font-weight: 500;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.footer-link.auth-link:hover {
|
|
79
|
+
color: #0056b3;
|
|
80
|
+
background-color: #e7f3ff;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.user-info {
|
|
84
|
+
color: #6c757d;
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
margin-right: 10px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@media (max-width: 768px) {
|
|
90
|
+
.footer-content {
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
gap: 15px;
|
|
93
|
+
text-align: center;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.footer-nav, .footer-auth {
|
|
97
|
+
flex-wrap: wrap;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
</style>
|