Packages

phoenix_kit

1.7.36
1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web live users roles.ex
Raw

lib/phoenix_kit_web/live/users/roles.ex

defmodule PhoenixKitWeb.Live.Users.Roles do
@moduledoc """
Role management LiveView for PhoenixKit admin panel.
Provides interface for viewing and managing user roles and permissions.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Admin.Events
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Users.Permissions
alias PhoenixKit.Users.{Role, Roles}
def mount(_params, _session, socket) do
# Attach locale hook for automatic locale handling
# Subscribe to role events for live updates
if connected?(socket) do
Events.subscribe_to_roles()
Events.subscribe_to_stats()
Events.subscribe_to_permissions()
Events.subscribe_to_modules()
end
# Load optimized role statistics once
role_stats = load_role_statistics()
# Get project title from settings
project_title = Settings.get_project_title()
socket =
socket
|> assign(:roles, [])
|> assign(:show_create_form, false)
|> assign(:show_edit_form, false)
|> assign(:create_role_form, nil)
|> assign(:edit_role_form, nil)
|> assign(:editing_role, nil)
|> assign(:delete_confirmation, %{show: false})
|> assign(:show_permissions_editor, false)
|> assign(:permissions_role, nil)
|> assign(:permissions_role_keys, MapSet.new())
|> assign(:permissions_grantable_keys, MapSet.new())
|> assign(:permissions_preserved_keys, MapSet.new())
|> assign(:page_title, "Roles")
|> assign(:role_stats, role_stats)
|> assign(:project_title, project_title)
|> load_roles()
{:ok, socket}
end
def handle_event("show_create_form", _params, socket) do
form = to_form(Role.changeset(%Role{}, %{}))
socket =
socket
|> assign(:show_create_form, true)
|> assign(:create_role_form, form)
{:noreply, socket}
end
def handle_event("show_edit_role", %{"role_id" => role_id_str}, socket) do
role = find_role_by_id(socket.assigns.roles, role_id_str)
if role && !role.is_system_role do
form = to_form(Role.changeset(role, %{}))
socket =
socket
|> assign(:show_edit_form, true)
|> assign(:edit_role_form, form)
|> assign(:editing_role, role)
{:noreply, socket}
else
socket = put_flash(socket, :error, "System roles cannot be edited")
{:noreply, socket}
end
end
def handle_event("hide_form", _params, socket) do
socket =
socket
|> assign(:show_create_form, false)
|> assign(:show_edit_form, false)
|> assign(:create_role_form, nil)
|> assign(:edit_role_form, nil)
|> assign(:editing_role, nil)
{:noreply, socket}
end
def handle_event("create_role", %{"role" => role_params}, socket) do
case Roles.create_role(role_params) do
{:ok, role} ->
flash_msg =
if can_manage_permissions?(socket),
do: "Role \"#{role.name}\" created. Click Permissions to configure access.",
else: "Role created successfully"
socket =
socket
|> put_flash(:info, flash_msg)
|> assign(:show_create_form, false)
|> assign(:create_role_form, nil)
|> load_roles()
{:noreply, socket}
{:error, changeset} ->
{:noreply, assign(socket, :create_role_form, to_form(changeset))}
end
end
def handle_event("update_role", %{"role" => role_params}, socket) do
editing_role = socket.assigns.editing_role
case Roles.update_role(editing_role, role_params) do
{:ok, _role} ->
socket =
socket
|> put_flash(:info, "Role updated successfully")
|> assign(:show_edit_form, false)
|> assign(:edit_role_form, nil)
|> assign(:editing_role, nil)
|> load_roles()
{:noreply, socket}
{:error, changeset} ->
{:noreply, assign(socket, :edit_role_form, to_form(changeset))}
end
end
# New events for confirmation modal
def handle_event(
"request_delete_role",
%{"role_id" => role_id, "role_name" => role_name},
socket
) do
delete_confirmation = %{
show: true,
role_id: role_id,
role_name: role_name
}
{:noreply, assign(socket, :delete_confirmation, delete_confirmation)}
end
def handle_event("cancel_delete", _params, socket) do
{:noreply, assign(socket, :delete_confirmation, %{show: false})}
end
def handle_event("confirm_delete_role", _params, socket) do
role_id = socket.assigns.delete_confirmation.role_id
# Close modal first
socket = assign(socket, :delete_confirmation, %{show: false})
# Execute the deletion
handle_delete_role(role_id, socket)
end
# Keep old handler for backward compatibility
def handle_event("delete_role", %{"role_id" => role_id}, socket) do
handle_delete_role(role_id, socket)
end
# --- Permission Editor Events ---
def handle_event("show_permissions_editor", %{"role_id" => role_id_str}, socket) do
if can_manage_permissions?(socket) do
role = find_role_by_id(socket.assigns.roles, role_id_str)
if role do
grantable = grantable_keys(socket)
enabled = Permissions.enabled_module_keys()
# Only show keys that are both grantable AND enabled
displayable = MapSet.intersection(grantable, enabled)
current_keys = Permissions.get_permissions_for_role(role.uuid) |> MapSet.new()
editable_checked = MapSet.intersection(current_keys, displayable)
# Preserve: keys outside displayable set (not grantable OR disabled modules)
preserved = MapSet.difference(current_keys, displayable)
socket =
socket
|> assign(:show_permissions_editor, true)
|> assign(:permissions_role, role)
|> assign(:permissions_role_keys, editable_checked)
|> assign(:permissions_grantable_keys, displayable)
|> assign(:permissions_preserved_keys, preserved)
{:noreply, socket}
else
{:noreply, put_flash(socket, :error, "Role not found")}
end
else
{:noreply, put_flash(socket, :error, "You don't have permission to manage permissions")}
end
end
def handle_event("hide_permissions_editor", _params, socket) do
socket =
socket
|> assign(:show_permissions_editor, false)
|> assign(:permissions_role, nil)
|> assign(:permissions_role_keys, MapSet.new())
|> assign(:permissions_grantable_keys, MapSet.new())
|> assign(:permissions_preserved_keys, MapSet.new())
{:noreply, socket}
end
def handle_event("toggle_permission", %{"key" => key}, socket) do
if can_manage_permissions?(socket) do
grantable = socket.assigns.permissions_grantable_keys
if MapSet.member?(grantable, key) do
keys = socket.assigns.permissions_role_keys
new_keys =
if MapSet.member?(keys, key),
do: MapSet.delete(keys, key),
else: MapSet.put(keys, key)
{:noreply, assign(socket, :permissions_role_keys, new_keys)}
else
{:noreply, put_flash(socket, :error, "You can only manage permissions you have")}
end
else
{:noreply, put_flash(socket, :error, "You don't have permission to manage permissions")}
end
end
def handle_event("grant_all_permissions", _params, socket) do
if can_manage_permissions?(socket) do
{:noreply,
assign(socket, :permissions_role_keys, socket.assigns.permissions_grantable_keys)}
else
{:noreply, put_flash(socket, :error, "You don't have permission to manage permissions")}
end
end
def handle_event("revoke_all_permissions", _params, socket) do
if can_manage_permissions?(socket) do
{:noreply, assign(socket, :permissions_role_keys, MapSet.new())}
else
{:noreply, put_flash(socket, :error, "You don't have permission to manage permissions")}
end
end
def handle_event("save_permissions", _params, socket) do
role = socket.assigns.permissions_role
cond do
is_nil(role) ->
{:noreply, put_flash(socket, :error, "No role selected")}
!can_manage_permissions?(socket) ->
{:noreply, put_flash(socket, :error, "You don't have permission to manage permissions")}
true ->
preserved = socket.assigns.permissions_preserved_keys
editor_keys = socket.assigns.permissions_role_keys
final_keys = MapSet.union(preserved, editor_keys) |> MapSet.to_list()
scope = socket.assigns[:phoenix_kit_current_scope]
granted_by_id = if scope, do: Scope.user_id(scope), else: nil
case Permissions.set_permissions(role.id, final_keys, granted_by_id) do
:ok ->
socket =
socket
|> put_flash(:info, "Permissions updated for #{role.name}")
|> assign(:show_permissions_editor, false)
|> assign(:permissions_role, nil)
|> assign(:permissions_role_keys, MapSet.new())
|> assign(:permissions_grantable_keys, MapSet.new())
|> assign(:permissions_preserved_keys, MapSet.new())
{:noreply, socket}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, "Failed to update permissions")}
end
end
end
# Keep the old handler for backward compatibility and make it private
defp handle_delete_role(role_id, socket) when is_binary(role_id) do
role = find_role_by_id(socket.assigns.roles, role_id)
if role && !role.is_system_role do
case Roles.delete_role(role) do
{:ok, _role} ->
socket =
socket
|> put_flash(:info, "Role deleted successfully")
|> load_roles()
{:noreply, socket}
{:error, :role_in_use} ->
socket =
put_flash(socket, :error, "Cannot delete role: it is currently assigned to users")
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to delete role")
{:noreply, socket}
end
else
socket = put_flash(socket, :error, "System roles cannot be deleted")
{:noreply, socket}
end
end
defp load_roles(socket) do
roles = Roles.list_roles()
assign(socket, :roles, roles)
end
# Load role statistics using optimized extended stats
defp load_role_statistics do
stats = Roles.get_extended_stats()
# Create lookup map for fast role count access
%{
"Owner" => stats.owner_count,
"Admin" => stats.admin_count,
"User" => stats.user_count
}
end
# Optimized function using cached statistics
## Live Event Handlers
def handle_info({:role_created, _role}, socket) do
socket =
socket
|> load_roles()
|> assign(:role_stats, load_role_statistics())
{:noreply, socket}
end
def handle_info({:role_updated, _role}, socket) do
socket =
socket
|> load_roles()
|> assign(:role_stats, load_role_statistics())
{:noreply, socket}
end
def handle_info({:role_deleted, role}, socket) do
socket =
socket
|> close_modals_for_deleted_role(role)
|> load_roles()
|> assign(:role_stats, load_role_statistics())
{:noreply, socket}
end
def handle_info({:stats_updated, _stats}, socket) do
socket =
socket
|> assign(:role_stats, load_role_statistics())
{:noreply, socket}
end
def handle_info({:permission_granted, role_id, _key}, socket) do
{:noreply, refresh_permissions_if_editing(socket, role_id)}
end
def handle_info({:permission_revoked, role_id, _key}, socket) do
{:noreply, refresh_permissions_if_editing(socket, role_id)}
end
def handle_info({:permissions_synced, role_id, _keys}, socket) do
{:noreply, refresh_permissions_if_editing(socket, role_id)}
end
def handle_info({:module_enabled, _key}, socket) do
{:noreply, refresh_permissions_if_editing_any(socket)}
end
def handle_info({:module_disabled, _key}, socket) do
{:noreply, refresh_permissions_if_editing_any(socket)}
end
def handle_info(_msg, socket), do: {:noreply, socket}
# --- Helpers ---
defp can_manage_permissions?(socket) do
scope = socket.assigns[:phoenix_kit_current_scope]
scope && Scope.has_module_access?(scope, "users")
end
defp grantable_keys(socket) do
scope = socket.assigns[:phoenix_kit_current_scope]
if scope && Scope.owner?(scope) do
MapSet.new(Permissions.all_module_keys())
else
Scope.accessible_modules(scope)
end
end
defp find_role_by_id(roles, id_str) when is_binary(id_str) do
Enum.find(roles, &(to_string(&1.uuid) == id_str))
end
defp close_modals_for_deleted_role(socket, deleted_role) do
socket
|> then(fn s ->
if s.assigns.show_permissions_editor &&
s.assigns.permissions_role &&
s.assigns.permissions_role.id == deleted_role.id do
s
|> assign(:show_permissions_editor, false)
|> assign(:permissions_role, nil)
|> assign(:permissions_role_keys, MapSet.new())
|> assign(:permissions_grantable_keys, MapSet.new())
|> assign(:permissions_preserved_keys, MapSet.new())
|> put_flash(:info, "Role \"#{deleted_role.name}\" was deleted")
else
s
end
end)
|> then(fn s ->
if s.assigns.editing_role && s.assigns.editing_role.id == deleted_role.id do
s
|> assign(:show_edit_form, false)
|> assign(:edit_role_form, nil)
|> assign(:editing_role, nil)
|> put_flash(:info, "Role \"#{deleted_role.name}\" was deleted")
else
s
end
end)
end
defp refresh_permissions_if_editing(socket, role_id) do
if socket.assigns.show_permissions_editor &&
socket.assigns.permissions_role &&
socket.assigns.permissions_role.id == role_id do
reload_permission_editor_data(socket)
else
socket
end
end
defp refresh_permissions_if_editing_any(socket) do
if socket.assigns.show_permissions_editor && socket.assigns.permissions_role do
reload_permission_editor_data(socket)
else
socket
end
end
defp reload_permission_editor_data(socket) do
role = socket.assigns.permissions_role
grantable = grantable_keys(socket)
enabled = Permissions.enabled_module_keys()
displayable = MapSet.intersection(grantable, enabled)
current_keys = Permissions.get_permissions_for_role(role.id) |> MapSet.new()
editable_checked = MapSet.intersection(current_keys, displayable)
preserved = MapSet.difference(current_keys, displayable)
socket
|> assign(:permissions_grantable_keys, displayable)
|> assign(:permissions_role_keys, editable_checked)
|> assign(:permissions_preserved_keys, preserved)
end
end