Packages

phoenix_kit

1.2.4
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 live_sessions_live.html.heex
Raw

lib/phoenix_kit_web/live/live_sessions_live.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Live Sessions - {@project_title} Admin"
current_path={@current_path}
project_title={@project_title}
>
<div class="container mx-auto px-4 py-8">
<!-- Header with Live Indicator -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<h1 class="text-3xl font-bold text-base-content">
Live Sessions
</h1>
<!-- Live Indicator -->
<div class="flex items-center gap-2 px-3 py-1 bg-success/10 text-success rounded-full">
<div class="w-2 h-2 bg-success rounded-full animate-pulse"></div>
<span class="text-xs font-medium">LIVE</span>
</div>
</div>
<!-- Controls -->
<div class="flex items-center gap-2">
<!-- Auto-refresh toggle -->
<button
phx-click="toggle_auto_refresh"
class={[
"btn btn-sm",
if(@auto_refresh, do: "btn-success", else: "btn-outline")
]}
>
<%= if @auto_refresh do %>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 9v6l3-3m6-5a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Auto-refresh ON
<% else %>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 0l-3-3m-6 0l3 3"
/>
</svg>
Auto-refresh OFF
<% end %>
</button>
<!-- Manual refresh -->
<button
phx-click="refresh_now"
class="btn btn-sm btn-outline"
>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Refresh
</button>
</div>
</div>
<p class="text-base-content/70 mt-2">
Real-time monitoring of all active sessions with live updates.
Last updated: {Calendar.strftime(@last_updated, "%H:%M:%S")}
</p>
</div>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<!-- Total Sessions -->
<div class="card bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">
{@presence_stats.total_sessions}
</div>
<div class="text-blue-100 text-sm">Total Sessions</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
</svg>
</div>
</div>
</div>
</div>
<!-- Anonymous Sessions -->
<div class="card bg-gradient-to-br from-orange-500 to-orange-600 text-white shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">
{@presence_stats.anonymous_sessions}
</div>
<div class="text-orange-100 text-sm">Anonymous</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</div>
</div>
</div>
</div>
<!-- Authenticated Sessions -->
<div class="card bg-gradient-to-br from-green-500 to-green-600 text-white shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">
{@presence_stats.authenticated_sessions}
</div>
<div class="text-green-100 text-sm">Authenticated</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
</div>
</div>
</div>
</div>
<!-- Unique Visitors -->
<div class="card bg-gradient-to-br from-purple-500 to-purple-600 text-white shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">
{@presence_stats.unique_anonymous_visitors}
</div>
<div class="text-purple-100 text-sm">Unique Visitors</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
<!-- Filters and Search -->
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body p-4">
<div class="flex flex-col md:flex-row gap-4 items-center justify-between">
<!-- Search -->
<div class="flex-1 max-w-md">
<form phx-submit="search" phx-change="search" class="form-control">
<div class="input-group">
<input
type="text"
name="search"
value={@search_query}
placeholder="Search by IP, User-Agent, email..."
class="input input-bordered flex-1"
/>
<button type="submit" class="btn btn-square">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</button>
</div>
</form>
</div>
<!-- Filter Tabs -->
<div class="tabs tabs-boxed">
<button
phx-click="filter_by"
phx-value-type="all"
class={["tab", if(@filter_type == "all", do: "tab-active", else: "")]}
>
All ({@total_sessions})
</button>
<button
phx-click="filter_by"
phx-value-type="anonymous"
class={["tab", if(@filter_type == "anonymous", do: "tab-active", else: "")]}
>
Anonymous ({@presence_stats.anonymous_sessions})
</button>
<button
phx-click="filter_by"
phx-value-type="authenticated"
class={["tab", if(@filter_type == "authenticated", do: "tab-active", else: "")]}
>
Authenticated ({@presence_stats.authenticated_sessions})
</button>
</div>
</div>
</div>
</div>
<!-- Sessions Table -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-0">
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>
<button
phx-click="sort_by"
phx-value-field="type"
class="flex items-center gap-1 hover:text-primary"
>
Type
<%= if @sort_by == :type do %>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<%= if @sort_order == :asc do %>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 15l7-7 7 7"
/>
<% else %>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
<% end %>
</svg>
<% end %>
</button>
</th>
<th>User/Session</th>
<th>IP Address</th>
<th>Current Page</th>
<th>
<button
phx-click="sort_by"
phx-value-field="connected_at"
class="flex items-center gap-1 hover:text-primary"
>
Connected
<%= if @sort_by == :connected_at do %>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<%= if @sort_order == :asc do %>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 15l7-7 7 7"
/>
<% else %>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
<% end %>
</svg>
<% end %>
</button>
</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<%= for session <- @sessions do %>
<tr>
<td>
<div class="flex items-center gap-2">
<%= if session.type == :authenticated do %>
<div class="badge badge-success badge-sm">
<svg
class="w-3 h-3 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
Auth
</div>
<% else %>
<div class="badge badge-warning badge-sm">
<svg
class="w-3 h-3 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
Anon
</div>
<% end %>
</div>
</td>
<td>
<%= if session.type == :authenticated do %>
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-success rounded-full flex items-center justify-center text-success-content text-xs font-bold">
{String.first(session.user_email || "?") |> String.upcase()}
</div>
<div>
<div class="font-medium text-sm">
{session.user_email}
</div>
<div class="text-xs text-base-content/70">
ID: {session.user_id}
</div>
</div>
</div>
<% else %>
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-warning rounded-full flex items-center justify-center text-warning-content text-xs font-bold">
?
</div>
<div>
<div class="font-medium text-sm">
Anonymous User
</div>
<div class="text-xs text-base-content/70 font-mono">
{session.session_id}
</div>
</div>
</div>
<% end %>
</td>
<td>
<code class="text-xs bg-base-200 px-2 py-1 rounded">
{session.ip_address || "Unknown"}
</code>
</td>
<td>
<%= if session.current_page do %>
<code class="text-xs bg-base-200 px-2 py-1 rounded">
{session.current_page}
</code>
<% else %>
<span class="text-base-content/50"></span>
<% end %>
</td>
<td>
<div class="text-xs">
{format_time_ago(session.connected_at)}
</div>
</td>
<td>
<div class="flex items-center gap-1">
<div class="w-2 h-2 bg-success rounded-full animate-pulse"></div>
<span class="text-xs text-success">Active</span>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= if Enum.empty?(@sessions) do %>
<div class="text-center py-12">
<div class="text-base-content/50 mb-4">
<svg
class="w-16 h-16 mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 009.586 13H7"
/>
</svg>
</div>
<h3 class="text-lg font-medium text-base-content mb-2">No active sessions</h3>
<p class="text-base-content/70">
There are currently no active sessions to display.
</p>
</div>
<% end %>
</div>
</div>
</div>
<!-- Pagination -->
<%= if @total_pages > 1 do %>
<div class="flex justify-center mt-6">
<div class="btn-group">
<%= for page_num <- 1..@total_pages do %>
<button
phx-click="goto_page"
phx-value-page={page_num}
class={[
"btn btn-sm",
if(page_num == @page, do: "btn-active", else: "")
]}
>
{page_num}
</button>
<% end %>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>