Packages
phoenix_kit
1.7.53
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
Current section
Files
lib/phoenix_kit_web/live/users/permissions_matrix.ex
defmodule PhoenixKitWeb.Live.Users.PermissionsMatrix do
@moduledoc """
Interactive permissions matrix view for PhoenixKit admin panel.
Displays a matrix of roles vs module permission keys, showing which
roles have access to which sections. Owner column shows "always" badge.
Cells are clickable to toggle permissions directly.
"""
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.Roles
def mount(_params, _session, socket) do
if connected?(socket) do
Events.subscribe_to_roles()
Events.subscribe_to_permissions()
Events.subscribe_to_modules()
end
project_title = Settings.get_project_title()
socket =
socket
|> assign(:page_title, gettext("Permissions"))
|> assign(:project_title, project_title)
|> load_matrix()
{:ok, socket}
end
# --- PubSub Handlers ---
def handle_info({:role_created, _role}, socket) do
{:noreply, load_matrix(socket)}
end
def handle_info({:role_updated, _role}, socket) do
{:noreply, load_matrix(socket)}
end
def handle_info({:role_deleted, _role}, socket) do
{:noreply, load_matrix(socket)}
end
def handle_info({:permission_granted, _role_uuid, _key}, socket) do
{:noreply, refresh_matrix(socket)}
end
def handle_info({:permission_revoked, _role_uuid, _key}, socket) do
{:noreply, refresh_matrix(socket)}
end
def handle_info({:permissions_synced, _role_uuid, _keys}, socket) do
{:noreply, refresh_matrix(socket)}
end
def handle_info({:module_enabled, _key}, socket) do
{:noreply, load_matrix(socket)}
end
def handle_info({:module_disabled, _key}, socket) do
{:noreply, load_matrix(socket)}
end
def handle_info(_msg, socket), do: {:noreply, socket}
# --- Events ---
def handle_event("toggle_permission", %{"role_uuid" => role_uuid, "key" => key}, socket) do
scope = socket.assigns[:phoenix_kit_current_scope]
with role when not is_nil(role) <-
Enum.find(socket.assigns.roles, &(to_string(&1.uuid) == role_uuid)),
:ok <- Permissions.can_edit_role_permissions?(scope, role),
true <- scope != nil && Scope.has_module_access?(scope, "users") do
grantable =
if Scope.owner?(scope),
do: MapSet.new(Permissions.all_module_keys()),
else: Scope.accessible_modules(scope)
if MapSet.member?(grantable, key) do
toggle_role_permission(socket, role, key, scope)
else
{:noreply, put_flash(socket, :error, gettext("You can only manage permissions you have"))}
end
else
{:error, reason} ->
{:noreply, put_flash(socket, :error, permission_error_message(reason))}
nil ->
{:noreply, put_flash(socket, :error, gettext("Role not found"))}
false ->
{:noreply,
put_flash(socket, :error, gettext("You don't have permission to manage permissions"))}
end
end
# --- Helpers ---
defp toggle_role_permission(socket, role, key, scope) do
granted_by_uuid = Scope.user_uuid(scope)
role_uuid = to_string(role.uuid)
role_keys = Map.get(socket.assigns.matrix, role_uuid, MapSet.new())
label = Permissions.module_label(key)
if MapSet.member?(role_keys, key) do
case Permissions.revoke_permission(role_uuid, key) do
:ok ->
{:noreply,
socket
|> put_flash(
:info,
gettext("Revoked %{label} from %{role_name}",
label: label,
role_name: role.name
)
)
|> refresh_matrix()}
{:error, _reason} ->
{:noreply,
socket
|> put_flash(
:error,
gettext("Failed to revoke %{label} from %{role_name}",
label: label,
role_name: role.name
)
)
|> refresh_matrix()}
end
else
case Permissions.grant_permission(role_uuid, key, granted_by_uuid) do
{:ok, _} ->
{:noreply,
socket
|> put_flash(
:info,
gettext("Granted %{label} to %{role_name}",
label: label,
role_name: role.name
)
)
|> refresh_matrix()}
{:error, _reason} ->
{:noreply,
socket
|> put_flash(
:error,
gettext("Failed to grant %{label} to %{role_name}",
label: label,
role_name: role.name
)
)
|> refresh_matrix()}
end
end
end
defp load_matrix(socket) do
roles = Roles.list_roles()
matrix = Permissions.get_permissions_matrix()
all_count = length(Permissions.all_module_keys())
# Sort: Owner first, then by permission count descending, then name
sorted_roles =
Enum.sort_by(roles, fn role ->
count =
if role.name == "Owner",
do: all_count,
else: Map.get(matrix, to_string(role.uuid), MapSet.new()) |> MapSet.size()
{role.name != "Owner", -count, role.name}
end)
enabled = Permissions.enabled_module_keys()
enabled_feature_keys =
Enum.filter(Permissions.feature_module_keys(), &MapSet.member?(enabled, &1))
core_keys = Permissions.core_section_keys()
custom_keys = Permissions.custom_keys()
visible_keys = MapSet.new(core_keys ++ enabled_feature_keys ++ custom_keys)
# Determine which roles can't be edited by the current user
scope = socket.assigns[:phoenix_kit_current_scope]
uneditable_role_uuids =
sorted_roles
|> Enum.filter(fn role ->
role.name == "Owner" or Permissions.can_edit_role_permissions?(scope, role) != :ok
end)
|> MapSet.new(fn role -> to_string(role.uuid) end)
socket
|> assign(:roles, sorted_roles)
|> assign(:matrix, matrix)
|> assign(:core_keys, core_keys)
|> assign(:feature_keys, enabled_feature_keys)
|> assign(:custom_keys, custom_keys)
|> assign(:visible_keys, visible_keys)
|> assign(:uneditable_role_uuids, uneditable_role_uuids)
end
# Refresh matrix data only, keep existing role order stable
defp refresh_matrix(socket) do
matrix = Permissions.get_permissions_matrix()
assign(socket, :matrix, matrix)
end
defp permission_error_message(:not_authenticated), do: gettext("Not authenticated")
defp permission_error_message(:owner_immutable),
do: gettext("Owner role always has full access and cannot be modified")
defp permission_error_message(:self_role),
do: gettext("You cannot edit permissions for your own role")
defp permission_error_message(:admin_owner_only),
do: gettext("Only the Owner can edit Admin permissions")
defp permission_error_message(_), do: gettext("Permission denied")
end