Packages

phoenix_kit

1.2.10
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 sessions_live.html.heex
Raw

lib/phoenix_kit_web/live/users/sessions_live.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Session Management"
current_path={@current_path}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<header class="w-full relative mb-6">
<%!-- Back Button --%>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/dashboard")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<PhoenixKitWeb.Components.Core.Icons.icon_arrow_left /> Back to Dashboard
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">Session Management</h1>
<p class="text-lg text-base-content">Monitor and manage active user sessions</p>
</div>
</header>
<%!-- Session Statistics --%>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="bg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-lg p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">{@stats.total_active}</div>
<div class="text-blue-100">Active Sessions</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<PhoenixKitWeb.Components.Core.Icons.icon_activity class="w-6 h-6" />
</div>
</div>
</div>
<div class="bg-gradient-to-br from-green-500 to-green-600 text-white rounded-lg p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">{@stats.unique_users}</div>
<div class="text-green-100">Unique Users</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<PhoenixKitWeb.Components.Core.Icons.icon_user_profile class="w-6 h-6" />
</div>
</div>
</div>
<div class="bg-gradient-to-br from-yellow-500 to-orange-500 text-white rounded-lg p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">{@stats.sessions_today}</div>
<div class="text-yellow-100">Today's Sessions</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<PhoenixKitWeb.Components.Core.Icons.icon_clock class="w-6 h-6" />
</div>
</div>
</div>
<div class="bg-gradient-to-br from-red-500 to-red-600 text-white rounded-lg p-4">
<div class="flex items-center justify-between">
<div>
<div class="text-2xl font-bold">{@stats.expired_sessions}</div>
<div class="text-red-100">Expired Sessions</div>
</div>
<div class="p-2 bg-white/20 rounded-lg">
<PhoenixKitWeb.Components.Core.Icons.icon_check_circle_filled class="w-6 h-6" />
</div>
</div>
</div>
</div>
<%!-- Revoke Session Modal --%>
<%= if @show_revoke_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<%= if @revoke_type == :single and @selected_session do %>
<h3 class="font-bold text-lg mb-4">Revoke Session</h3>
<p class="mb-4">
Are you sure you want to revoke this session for <strong>{@selected_session.user_email}</strong>?
</p>
<div class="bg-base-200 rounded p-3 mb-4">
<div class="text-sm">
<div><strong>Token:</strong> {@selected_session.token_preview}...</div>
<div>
<strong>Created:</strong> {UtilsDate.format_datetime_with_user_format(
@selected_session.created_at
)}
</div>
<div><strong>Age:</strong> {@selected_session.age_in_days} days</div>
</div>
</div>
<% end %>
<%= if @revoke_type == :user_all and @selected_user do %>
<h3 class="font-bold text-lg mb-4">Revoke All User Sessions</h3>
<p class="mb-4">
Are you sure you want to revoke
<strong>all {@user_sessions_count} session(s)</strong>
for user <strong>{@selected_user.email}</strong>?
</p>
<div class="alert alert-warning">
<PhoenixKitWeb.Components.Core.Icons.icon_warning class="stroke-current shrink-0 h-6 w-6" />
<span>This will log out the user from all devices and sessions.</span>
</div>
<% end %>
<div class="modal-action">
<button type="button" phx-click="hide_revoke_modal" class="btn btn-outline">
Cancel
</button>
<button type="button" phx-click="confirm_revoke_session" class="btn btn-error">
Revoke Session{if @revoke_type == :user_all, do: "s", else: ""}
</button>
</div>
</div>
</div>
<% end %>
<%!-- Controls --%>
<div class="bg-base-200 rounded-lg p-6 mb-6">
<div class="flex flex-col lg:flex-row gap-4 items-end">
<%!-- Search --%>
<div class="flex-1">
<label class="label">
<span class="label-text">Search by email or token</span>
</label>
<input
type="text"
value={@search_query}
phx-change="search"
phx-debounce="300"
name="search"
placeholder="Enter email address or token prefix..."
class="input input-bordered w-full"
/>
</div>
<%!-- Filter by User Status --%>
<div>
<label class="label">
<span class="label-text">Filter by User Status</span>
</label>
<select
phx-change="filter_by_user_status"
name="status"
class="select select-bordered w-full max-w-xs"
>
<option value="all" selected={@filter_user_status == "all"}>All Users</option>
<option value="active" selected={@filter_user_status == "active"}>
Active Users
</option>
<option value="inactive" selected={@filter_user_status == "inactive"}>
Inactive Users
</option>
<option value="confirmed" selected={@filter_user_status == "confirmed"}>
Confirmed Email
</option>
<option value="pending" selected={@filter_user_status == "pending"}>
Pending Email
</option>
</select>
</div>
<%!-- Refresh Button --%>
<button
phx-click="refresh_sessions"
class="btn btn-primary"
>
<PhoenixKitWeb.Components.Core.Icons.icon_refresh class="w-4 h-4 mr-2" /> Refresh
</button>
</div>
</div>
<%!-- Sessions Table --%>
<div class="bg-base-100 rounded-lg shadow-xl overflow-hidden mb-6">
<div class="overflow-x-auto">
<table class="table table-zebra w-full">
<thead class="bg-primary text-primary-content">
<tr>
<th>User</th>
<th>Token</th>
<th>Status</th>
<th>Created</th>
<th>Age</th>
<th>Expires</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<%= if Enum.empty?(@sessions) do %>
<tr>
<td colspan="7" class="text-center py-8 text-base-content/70">
<%= if @total_count == 0 do %>
No active sessions found.
<% else %>
No sessions match your current filters.
<% end %>
</td>
</tr>
<% else %>
<%= for session <- @sessions do %>
<tr class="hover:bg-base-200/50">
<%!-- User --%>
<td>
<div class="flex flex-col">
<span class="font-medium">{session.user_email}</span>
<span class="text-xs text-base-content/70">ID: {session.user_id}</span>
</div>
</td>
<%!-- Token --%>
<td>
<code class="bg-base-200 px-2 py-1 rounded text-sm">
{session.token_preview}...
</code>
</td>
<%!-- User Status --%>
<td>
<% {badge_class, badge_text} =
user_status_badge(session.user_is_active, session.user_confirmed_at) %>
<span class={"badge #{badge_class} badge-sm"}>
{badge_text}
</span>
</td>
<%!-- Created --%>
<td class="text-sm">
<div class="flex flex-col gap-1">
<div>{UtilsDate.format_date_with_user_format(session.created_at)}</div>
<div class="text-xs text-base-content/70">
{UtilsDate.format_time_with_user_format(session.created_at)}
</div>
</div>
</td>
<%!-- Age --%>
<td>
<% {age_class, age_text} = format_age_badge(session.age_in_days) %>
<span class={"badge #{age_class} badge-sm"}>
{age_text}
</span>
</td>
<%!-- Expires --%>
<td class="text-sm">
<div class="flex flex-col gap-1">
<div>{UtilsDate.format_date_with_user_format(session.expires_at)}</div>
<div class="text-xs text-base-content/70">
{UtilsDate.format_time_with_user_format(session.expires_at)}
</div>
</div>
</td>
<%!-- Actions --%>
<td>
<div class="flex gap-2">
<%!-- Revoke Single Session --%>
<button
phx-click="show_revoke_session"
phx-value-token_id={session.token_id}
class="btn btn-error btn-xs"
title="Revoke this session"
>
<PhoenixKitWeb.Components.Core.Icons.icon_x class="w-3 h-3" />
</button>
<%!-- Revoke All User Sessions --%>
<button
phx-click="show_revoke_user_sessions"
phx-value-user_id={session.user_id}
class="btn btn-warning btn-xs"
title="Revoke all sessions for this user"
>
<PhoenixKitWeb.Components.Core.Icons.icon_warning class="w-3 h-3" />
</button>
</div>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<%!-- Pagination --%>
<%= if @total_pages > 1 do %>
<div class="flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="text-sm text-base-content/70">
Showing {(@page - 1) * @per_page + 1} to {min(@page * @per_page, @total_count)} of {@total_count} sessions
</div>
<div class="join">
<%= if @page > 1 do %>
<button
phx-click="change_page"
phx-value-page={@page - 1}
class="join-item btn btn-outline"
>
« Previous
</button>
<% end %>
<%= for page_num <- max(1, @page - 2)..min(@total_pages, @page + 2) do %>
<button
phx-click="change_page"
phx-value-page={page_num}
class={"join-item btn #{if page_num == @page, do: "btn-primary", else: "btn-outline"}"}
>
{page_num}
</button>
<% end %>
<%= if @page < @total_pages do %>
<button
phx-click="change_page"
phx-value-page={@page + 1}
class="join-item btn btn-outline"
>
Next »
</button>
<% end %>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>