Packages
phoenix_kit
1.7.131
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/activity/index.ex
defmodule PhoenixKitWeb.Live.Activity.Index do
@moduledoc """
Admin LiveView for the activity feed.
Shows a real-time stream of business-level actions across the platform
with filtering by action type, user, resource type, and date range.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Activity
alias PhoenixKit.PubSub.Manager, as: PubSubManager
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
alias PhoenixKit.Utils.Values
@impl true
def mount(_params, _session, socket) do
scope = socket.assigns[:phoenix_kit_current_scope]
if scope && Scope.has_module_access?(scope, "dashboard") do
if connected?(socket) do
PubSubManager.subscribe(Activity.pubsub_topic())
end
project_title = Settings.get_project_title()
socket =
socket
|> assign(:page_title, gettext("Activity"))
|> assign(:project_title, project_title)
|> assign(:modules, Activity.list_modules())
|> assign(:modes, Activity.list_modes())
|> assign(:action_types, Activity.list_action_types())
|> assign(:resource_types, Activity.list_resource_types())
|> assign_filter_defaults()
|> load_activities()
{:ok, socket}
else
{:ok,
socket
|> put_flash(:error, gettext("Access denied"))
|> push_navigate(to: Routes.path("/admin"))}
end
end
@impl true
def handle_params(params, url, socket) do
socket =
socket
|> assign(:url_path, URI.parse(url).path)
|> apply_params(params)
|> load_activities()
{:noreply, socket}
end
@impl true
def handle_event("filter", params, socket) do
filter_params = Map.get(params, "filter", %{})
new_params =
%{"page" => "1"}
|> maybe_put("module", filter_params["module"])
|> maybe_put("mode", filter_params["mode"])
|> maybe_put("action", filter_params["action"])
|> maybe_put("resource_type", filter_params["resource_type"])
query = URI.encode_query(new_params)
{:noreply, push_patch(socket, to: Routes.path("/admin/activity?#{query}"))}
end
@impl true
def handle_event("clear_filters", _params, socket) do
{:noreply, push_patch(socket, to: Routes.path("/admin/activity"))}
end
@impl true
def handle_info({:activity_logged, _entry}, socket) do
# Reload on new activity (real-time update)
{:noreply, load_activities(socket)}
end
## Private
defp assign_filter_defaults(socket) do
socket
|> assign(:page, 1)
|> assign(:per_page, 50)
|> assign(:filter_module, nil)
|> assign(:filter_mode, nil)
|> assign(:filter_action, nil)
|> assign(:filter_resource_type, nil)
end
defp apply_params(socket, params) do
socket
|> assign(:page, parse_int(params["page"], 1))
|> assign(:filter_module, Values.blank_to_nil(params["module"]))
|> assign(:filter_mode, Values.blank_to_nil(params["mode"]))
|> assign(:filter_action, Values.blank_to_nil(params["action"]))
|> assign(:filter_resource_type, Values.blank_to_nil(params["resource_type"]))
end
defp load_activities(socket) do
result =
Activity.list(
page: socket.assigns.page,
per_page: socket.assigns.per_page,
module: socket.assigns.filter_module,
mode: socket.assigns.filter_mode,
action: socket.assigns.filter_action,
resource_type: socket.assigns.filter_resource_type,
preload: [:actor, :target]
)
resource_users = Activity.resolve_resource_users(result.entries)
socket
|> assign(:entries, result.entries)
|> assign(:resource_users, resource_users)
|> assign(:total, result.total)
|> assign(:total_pages, result.total_pages)
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, _key, ""), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
defp parse_int(nil, default), do: default
defp parse_int(str, default) when is_binary(str) do
case Integer.parse(str) do
{n, _} -> max(n, 1)
:error -> default
end
end
defp action_badge_color(action), do: Activity.action_badge_color(action)
defp mode_badge_color(mode), do: Activity.mode_badge_color(mode)
# True when any of the four Activity filters is set. Drives both the
# toolbar Clear-filters button and the filtered empty-state message.
defp any_filter_active?(assigns) do
[
assigns.filter_module,
assigns.filter_mode,
assigns.filter_action,
assigns.filter_resource_type
]
|> Enum.any?(&(&1 not in [nil, ""]))
end
defp summarize_details(metadata) do
meta = metadata || %{}
if meta["added"] || meta["removed"] do
# For role updates, show added/removed summary
parts = []
parts = if meta["added"], do: parts ++ ["added: #{meta["added"]}"], else: parts
parts = if meta["removed"], do: parts ++ ["removed: #{meta["removed"]}"], else: parts
Enum.join(parts, ", ")
else
# For profile updates, extract field names from _from/_to pairs
changed_fields =
meta
|> Map.keys()
|> Enum.filter(&String.ends_with?(&1, "_to"))
|> Enum.map(&String.trim_trailing(&1, "_to"))
|> Enum.reject(&(&1 == ""))
if changed_fields != [] do
fields = Enum.map_join(changed_fields, ", ", &String.replace(&1, "_", " "))
"#{fields} updated"
else
summarize_remaining_meta(meta)
end
end
end
defp summarize_remaining_meta(meta) do
meta
|> Map.drop(["method", "actor_role"])
|> Enum.reject(fn {_k, v} -> v == nil or v == "" end)
|> case do
[] -> nil
entries -> Enum.map_join(entries, ", ", fn {k, v} -> "#{k}: #{v}" end)
end
end
end