Packages
phoenix_kit
1.2.7
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/live_sessions_live.ex
defmodule PhoenixKitWeb.Live.LiveSessionsLive do
@moduledoc """
Real-time session monitoring dashboard for the PhoenixKit admin panel.
This module provides comprehensive real-time monitoring of:
- All active sessions (anonymous + authenticated)
- Live session statistics and metrics
- Real-time connection/disconnection events
- Session geographic distribution
- Page activity heatmaps
- User behavior patterns
Features real-time updates via Phoenix LiveView and WebSocket connectivity.
Only accessible to users with Owner or Admin roles.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Utils.Routes
alias PhoenixKit.Admin.{Events, Presence}
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
# Refresh every 5 seconds
@refresh_interval 5_000
@per_page 20
def mount(_params, session, socket) do
# Subscribe to presence events for real-time updates
if connected?(socket) do
Events.subscribe_to_presence()
Events.subscribe_to_stats()
# Start auto-refresh timer
schedule_refresh()
# Track authenticated user session if logged in
track_authenticated_session(socket, session)
end
# Get current path for navigation
current_path = get_current_path(socket, session)
# Get project title from settings
project_title = Settings.get_setting("project_title", "PhoenixKit")
socket =
socket
|> assign(:page, 1)
|> assign(:per_page, @per_page)
|> assign(:search_query, "")
# all, anonymous, authenticated
|> assign(:filter_type, "all")
|> assign(:current_path, current_path)
|> assign(:page_title, "Live Sessions")
|> assign(:project_title, project_title)
|> assign(:sort_by, :connected_at)
|> assign(:sort_order, :desc)
|> assign(:auto_refresh, true)
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:ok, socket}
end
def handle_params(_params, _url, socket) do
{:noreply, socket}
end
def handle_event("search", %{"search" => search_query}, socket) do
socket =
socket
|> assign(:search_query, search_query)
|> assign(:page, 1)
|> load_sessions()
{:noreply, socket}
end
def handle_event("filter_by", %{"type" => filter_type}, socket) do
socket =
socket
|> assign(:filter_type, filter_type)
|> assign(:page, 1)
|> load_sessions()
{:noreply, socket}
end
def handle_event("sort_by", %{"field" => field}, socket) do
field_atom = String.to_existing_atom(field)
{sort_by, sort_order} =
if socket.assigns.sort_by == field_atom do
{field_atom, toggle_sort_order(socket.assigns.sort_order)}
else
{field_atom, :desc}
end
socket =
socket
|> assign(:sort_by, sort_by)
|> assign(:sort_order, sort_order)
|> load_sessions()
{:noreply, socket}
end
def handle_event("toggle_auto_refresh", _params, socket) do
auto_refresh = !socket.assigns.auto_refresh
if auto_refresh do
schedule_refresh()
end
socket = assign(socket, :auto_refresh, auto_refresh)
{:noreply, socket}
end
def handle_event("refresh_now", _params, socket) do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:noreply, socket}
end
def handle_info(:refresh, socket) do
if socket.assigns.auto_refresh do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
schedule_refresh()
{:noreply, socket}
else
{:noreply, socket}
end
end
# Handle real-time presence events
def handle_info({:anonymous_session_connected, _session_id, _metadata}, socket) do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:noreply, socket}
end
def handle_info({:anonymous_session_disconnected, _session_id}, socket) do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_session_connected, _user_id, _metadata}, socket) do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_session_disconnected, _user_id, _session_id}, socket) do
socket =
socket
|> assign(:last_updated, DateTime.utc_now())
|> load_sessions()
|> load_stats()
{:noreply, socket}
end
def handle_info({:presence_stats_updated, stats}, socket) do
socket =
socket
|> assign(:presence_stats, stats)
|> assign(:last_updated, DateTime.utc_now())
{:noreply, socket}
end
# Ignore other messages
def handle_info(_msg, socket), do: {:noreply, socket}
defp load_sessions(socket) do
sessions = Presence.list_active_sessions()
# Apply filters
filtered_sessions =
sessions
|> filter_by_search(socket.assigns.search_query)
|> filter_by_type(socket.assigns.filter_type)
|> sort_sessions(socket.assigns.sort_by, socket.assigns.sort_order)
# Calculate pagination
total_count = length(filtered_sessions)
total_pages = max(1, ceil(total_count / socket.assigns.per_page))
page = min(socket.assigns.page, total_pages)
# Get page sessions
page_sessions =
filtered_sessions
|> Enum.drop((page - 1) * socket.assigns.per_page)
|> Enum.take(socket.assigns.per_page)
socket
|> assign(:sessions, page_sessions)
|> assign(:total_sessions, total_count)
|> assign(:total_pages, total_pages)
|> assign(:page, page)
end
defp load_stats(socket) do
stats = Presence.get_presence_stats()
assign(socket, :presence_stats, stats)
end
defp filter_by_search(sessions, "") do
sessions
end
defp filter_by_search(sessions, search_query) do
query = String.downcase(search_query)
Enum.filter(sessions, fn session ->
String.contains?(String.downcase(session.ip_address || ""), query) ||
String.contains?(String.downcase(session.user_agent || ""), query) ||
String.contains?(String.downcase(session.current_page || ""), query) ||
(session.user_email && String.contains?(String.downcase(session.user_email), query))
end)
end
defp filter_by_type(sessions, "all"), do: sessions
defp filter_by_type(sessions, "anonymous"), do: Enum.filter(sessions, &(&1.type == :anonymous))
defp filter_by_type(sessions, "authenticated"),
do: Enum.filter(sessions, &(&1.type == :authenticated))
defp sort_sessions(sessions, sort_by, sort_order) do
sessions
|> Enum.sort_by(fn session -> Map.get(session, sort_by) end, sort_order)
end
defp toggle_sort_order(:asc), do: :desc
defp toggle_sort_order(:desc), do: :asc
defp schedule_refresh do
Process.send_after(self(), :refresh, @refresh_interval)
end
defp get_current_path(socket, session) do
case get_connect_info(socket, :uri) do
%URI{path: path} ->
path
_ ->
case session["current_path"] do
nil -> Routes.path("/admin/live_sessions")
path -> path
end
end
end
defp format_time_ago(datetime) when is_struct(datetime, DateTime) do
now = DateTime.utc_now()
diff_seconds = DateTime.diff(now, datetime, :second)
cond do
diff_seconds < 60 -> "#{diff_seconds}s ago"
diff_seconds < 3_600 -> "#{div(diff_seconds, 60)}m ago"
diff_seconds < 86_400 -> "#{div(diff_seconds, 3_600)}h ago"
true -> "#{div(diff_seconds, 86_400)}d ago"
end
end
defp format_time_ago(_), do: "Unknown"
defp track_authenticated_session(socket, session) do
scope = socket.assigns[:phoenix_kit_current_scope]
if scope && Scope.authenticated?(scope) do
user_id = Scope.user_id(scope)
user_email = Scope.user_email(scope)
# Create a user struct for tracking
user = %{id: user_id, email: user_email}
session_id = session["live_socket_id"] || generate_session_id()
Presence.track_user(user, %{
connected_at: DateTime.utc_now(),
session_id: session_id,
ip_address: get_connect_info(socket, :peer_data) |> extract_ip_address(),
user_agent: get_connect_info(socket, :user_agent),
current_page: Routes.path("/admin/live_sessions")
})
end
end
defp generate_session_id do
:crypto.strong_rand_bytes(16) |> Base.encode64()
end
defp extract_ip_address(nil), do: "unknown"
defp extract_ip_address(%{address: {a, b, c, d}}), do: "#{a}.#{b}.#{c}.#{d}"
defp extract_ip_address(%{address: address}), do: to_string(address)
defp extract_ip_address(_), do: "unknown"
end