Packages

phoenix_kit

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

lib/phoenix_kit_web/live/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}
>
<div class="container flex-col mx-auto px-4 py-6">
<!-- Header Section -->
<header class="w-full relative mb-6">
<!-- Back Button -->
<.link
navigate="/phoenix_kit/admin/dashboard"
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 19l-7-7m0 0l7-7m-7 7h18"
>
</path>
</svg>
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">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm3.88 9.88l-4.24 4.24c-.78.78-2.05.78-2.83 0-.78-.78-.78-2.05 0-2.83L12.05 10l-3.24-3.29c-.78-.78-.78-2.05 0-2.83.78-.78 2.05-.78 2.83 0l4.24 4.24c.39.39.39 1.02 0 1.41z" />
</svg>
</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">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path 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 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">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" />
</svg>
</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">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
</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">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
<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"
>
<svg class="w-4 h-4 mr-2" 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>
<!-- 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">
{UtilsDate.format_datetime_with_user_format(session.created_at)}
</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">
{UtilsDate.format_datetime_with_user_format(session.expires_at)}
</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"
>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</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"
>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</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>