Packages
phoenix_kit
1.3.1
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/modules/emails/queue.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Email Queue"
current_path={@url_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 (Left aligned) --%>
<.link
navigate={Routes.path("/admin/emails")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<.icon_arrow_left /> Back to Emails
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">Email Queue</h1>
<p class="text-lg text-base-content">Real-time monitoring and queue management</p>
</div>
</header>
<%!-- Action Buttons --%>
<div class="flex justify-between items-center gap-2 mb-6">
<div class="text-sm text-base-content/70">
Last updated: {UtilsDate.format_time_with_user_format(@last_updated)}
</div>
<div class="flex gap-2">
<.button phx-click="refresh" class="btn btn-outline btn-sm">
<.icon name="hero-arrow-path" class="w-4 h-4 mr-1" /> Refresh
</.button>
</div>
</div>
<%!-- System Status Cards --%>
<div class="stats shadow mb-6 w-full">
<div class="stat">
<div class="stat-figure text-primary">
<.icon name="hero-server" class="w-8 h-8" />
</div>
<div class="stat-title">System Status</div>
<div class={[
"stat-value",
(@system_status[:system_enabled] && "text-success") || "text-error"
]}>
{(@system_status[:system_enabled] && "Online") || "Offline"}
</div>
<div class="stat-desc">
Email system is {(@system_status[:system_enabled] && "operational") || "disabled"}
</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<.icon name="hero-envelope" class="w-8 h-8" />
</div>
<div class="stat-title">Sent Today</div>
<div class="stat-value text-secondary">
{format_number(@system_status[:total_sent_today] || 0)}
</div>
<div class="stat-desc">Total emails sent since midnight</div>
</div>
<div class="stat">
<div class="stat-figure text-warning">
<.icon name="hero-exclamation-triangle" class="w-8 h-8" />
</div>
<div class="stat-title">Failed (24h)</div>
<div class={[
"stat-value",
(length(@failed_emails) > 0 && "text-error") || "text-success"
]}>
{length(@failed_emails)}
</div>
<div class="stat-desc">Failed emails in last 24 hours</div>
</div>
<div class="stat">
<div class="stat-figure text-info">
<.icon name="hero-clock" class="w-8 h-8" />
</div>
<div class="stat-title">Retention</div>
<div class="stat-value text-info">{@system_status[:retention_days] || 90}</div>
<div class="stat-desc">Days of data retention</div>
</div>
</div>
<%!-- Rate Limit Status --%>
<div class="card bg-base-100 shadow-sm mb-6">
<div class="card-body">
<h2 class="card-title text-lg mb-4">Rate Limit Status</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<%!-- Global Rate Limit --%>
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Global Hourly Limit</div>
<div class="stat-value text-sm">
{format_number(@rate_limit_status[:global][:count] || 0)} / {format_number(
@rate_limit_status[:global][:limit] || 0
)}
</div>
<div class="stat-desc">
<div class="mt-2">
<progress
class={[
"progress w-full",
((@rate_limit_status[:global][:percentage] || 0) > 80 && "progress-error") ||
((@rate_limit_status[:global][:percentage] || 0) > 60 && "progress-warning") ||
"progress-success"
]}
value={@rate_limit_status[:global][:percentage] || 0}
max="100"
>
</progress>
<span class="text-xs">{@rate_limit_status[:global][:percentage] || 0}% used</span>
</div>
</div>
</div>
<%!-- Recipients --%>
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Recipient Limits</div>
<div class="stat-value text-sm">
{@rate_limit_status[:recipients][:active_limits] || 0}
</div>
<div class="stat-desc">
Active recipient rate limits ({@rate_limit_status[:recipients][:total_emails] || 0} emails)
</div>
</div>
<%!-- Senders --%>
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Sender Limits</div>
<div class="stat-value text-sm">
{@rate_limit_status[:senders][:active_limits] || 0}
</div>
<div class="stat-desc">
Active sender rate limits ({@rate_limit_status[:senders][:total_emails] || 0} emails)
</div>
</div>
<%!-- Blocklist --%>
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Blocklist</div>
<div class="stat-value text-sm">
{@rate_limit_status[:blocklist][:active_blocks] || 0}
</div>
<div class="stat-desc">
Active blocks ({@rate_limit_status[:blocklist][:expired_today] || 0} expired today)
</div>
</div>
</div>
</div>
</div>
<%!-- Failed Emails Section --%>
<%= if length(@failed_emails) > 0 do %>
<div class="card bg-base-100 shadow-sm mb-6">
<div class="card-body">
<div class="flex justify-between items-center mb-4">
<h2 class="card-title text-lg">Failed Emails (Last 24 Hours)</h2>
<%= if length(@selected_emails) > 0 do %>
<div class="flex gap-2">
<span class="badge badge-primary">{length(@selected_emails)} selected</span>
<button phx-click="clear_selection" class="btn btn-xs btn-ghost">
Clear
</button>
<button
phx-click="set_bulk_action"
phx-value-action="retry"
class="btn btn-xs btn-success"
>
Retry Selected
</button>
</div>
<% else %>
<button phx-click="select_all_failed" class="btn btn-xs btn-outline">
Select All
</button>
<% end %>
</div>
<%= if @bulk_action do %>
<div class="alert alert-warning mb-4">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<p class="font-semibold">
Confirm bulk action: {String.upcase(@bulk_action)}
</p>
<p class="text-sm">
This will {@bulk_action} {length(@selected_emails)} emails
</p>
</div>
<div class="flex gap-2">
<button phx-click="execute_bulk_action" class="btn btn-sm btn-warning">
Confirm
</button>
<button phx-click="clear_selection" class="btn btn-sm btn-ghost">
Cancel
</button>
</div>
</div>
<% end %>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th><input type="checkbox" class="checkbox checkbox-sm" disabled /></th>
<th>Recipient</th>
<th>Subject</th>
<th>Error</th>
<th>Time</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<%= for email <- @failed_emails do %>
<tr>
<td>
<input
type="checkbox"
class="checkbox checkbox-sm"
checked={email.id in @selected_emails}
phx-click="toggle_email_selection"
phx-value-email_id={email.id}
/>
</td>
<td class="font-mono text-xs">{email.to}</td>
<td class="truncate max-w-xs">{email.subject}</td>
<td class="text-error text-xs truncate max-w-xs">
{email.error_message || "Unknown error"}
</td>
<td class="text-xs">
{UtilsDate.format_datetime_with_user_format(email.sent_at)}
</td>
<td>
<button
phx-click="retry_email"
phx-value-email_id={email.id}
class="btn btn-xs btn-outline btn-success"
>
<.icon name="hero-arrow-path" class="w-3 h-3" /> Retry
</button>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<% end %>
<%!-- Recent Activity Section --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">Recent Activity (Last 20 Emails)</h2>
<%= if @loading do %>
<div class="flex justify-center items-center h-32">
<span class="loading loading-spinner loading-md"></span>
<span class="ml-2">Loading activity...</span>
</div>
<% else %>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Status</th>
<th>Recipient</th>
<th>Subject</th>
<th>Sent At</th>
<th>Events</th>
</tr>
</thead>
<tbody>
<%= for email <- @recent_activity do %>
<tr>
<td>
<div class={[
"badge badge-sm",
(email.status == "sent" && "badge-success") ||
(email.status == "failed" &&
"badge-error") || "badge-warning"
]}>
{String.capitalize(email.status || "unknown")}
</div>
</td>
<td class="font-mono text-xs">{email.to}</td>
<td class="truncate max-w-xs">{email.subject}</td>
<td class="text-xs">
{UtilsDate.format_datetime_with_user_format(email.sent_at)}
</td>
<td>
<div class="flex gap-1">
<%= if email.delivered_at do %>
<div class="badge badge-xs badge-success" title="Delivered">
<.icon name="hero-check" class="w-3 h-3" />
</div>
<% end %>
<%= if email.opened_at do %>
<div class="badge badge-xs badge-info" title="Opened">
<.icon name="hero-envelope-open" class="w-3 h-3" />
</div>
<% end %>
<%= if email.clicked_at do %>
<div class="badge badge-xs badge-secondary" title="Clicked">
<.icon name="hero-cursor-arrow-rays" class="w-3 h-3" />
</div>
<% end %>
</div>
</td>
</tr>
<% end %>
<%= if length(@recent_activity) == 0 do %>
<tr>
<td colspan="5" class="text-center py-8 text-base-content/60">
No recent email activity
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>