pgbus 0.17.1 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +48 -13
- data/app/controllers/concerns/pgbus/job_listing.rb +37 -0
- data/app/controllers/concerns/pgbus/process_listing.rb +17 -0
- data/app/controllers/pgbus/application_controller.rb +5 -2
- data/app/controllers/pgbus/batches_controller.rb +4 -1
- data/app/controllers/pgbus/dashboard_controller.rb +20 -6
- data/app/controllers/pgbus/dead_letter_controller.rb +27 -3
- data/app/controllers/pgbus/events_controller.rb +52 -3
- data/app/controllers/pgbus/insights_controller.rb +19 -1
- data/app/controllers/pgbus/jobs_controller.rb +45 -26
- data/app/controllers/pgbus/locks_controller.rb +7 -2
- data/app/controllers/pgbus/outbox_controller.rb +4 -1
- data/app/controllers/pgbus/processes_controller.rb +3 -1
- data/app/controllers/pgbus/queues_controller.rb +16 -4
- data/app/controllers/pgbus/recurring_tasks_controller.rb +5 -7
- data/app/frontend/pgbus/modules/charts.js +119 -77
- data/app/frontend/pgbus/style.css +1 -1
- data/app/frontend/pgbus/tailwind.css +18 -0
- data/app/helpers/pgbus/application_helper.rb +155 -113
- data/app/helpers/pgbus/button_helper.rb +88 -0
- data/app/helpers/pgbus/dashboard_helper.rb +157 -0
- data/app/helpers/pgbus/events_helper.rb +40 -0
- data/app/helpers/pgbus/insights_helper.rb +39 -0
- data/app/helpers/pgbus/processes_helper.rb +79 -0
- data/app/views/pgbus/batches/_batches_table.html.erb +16 -13
- data/app/views/pgbus/batches/show.html.erb +3 -4
- data/app/views/pgbus/dashboard/_processes_table.html.erb +11 -11
- data/app/views/pgbus/dashboard/_queue_health.html.erb +42 -51
- data/app/views/pgbus/dashboard/_queues_table.html.erb +18 -16
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +16 -12
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +70 -36
- data/app/views/pgbus/dashboard/show.html.erb +1 -1
- data/app/views/pgbus/dead_letter/_error.html.erb +13 -0
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +37 -96
- data/app/views/pgbus/dead_letter/_row.html.erb +75 -0
- data/app/views/pgbus/dead_letter/index.html.erb +6 -9
- data/app/views/pgbus/dead_letter/show.html.erb +37 -21
- data/app/views/pgbus/events/_list.html.erb +55 -0
- data/app/views/pgbus/events/_processed_table.html.erb +45 -0
- data/app/views/pgbus/events/_replay.html.erb +10 -0
- data/app/views/pgbus/events/_row.html.erb +162 -0
- data/app/views/pgbus/events/_tabs.html.erb +13 -0
- data/app/views/pgbus/events/index.html.erb +27 -48
- data/app/views/pgbus/events/show.html.erb +25 -7
- data/app/views/pgbus/insights/_chart_card.html.erb +21 -0
- data/app/views/pgbus/insights/_latency_table.html.erb +20 -0
- data/app/views/pgbus/insights/_status_table.html.erb +19 -0
- data/app/views/pgbus/insights/_throughput_table.html.erb +18 -0
- data/app/views/pgbus/insights/show.html.erb +83 -100
- data/app/views/pgbus/jobs/_list.html.erb +64 -0
- data/app/views/pgbus/jobs/_row.html.erb +107 -0
- data/app/views/pgbus/jobs/_tabs.html.erb +14 -0
- data/app/views/pgbus/jobs/index.html.erb +10 -10
- data/app/views/pgbus/jobs/show.html.erb +18 -20
- data/app/views/pgbus/locks/_concurrency.html.erb +25 -23
- data/app/views/pgbus/locks/_uniqueness.html.erb +20 -22
- data/app/views/pgbus/outbox/index.html.erb +30 -27
- data/app/views/pgbus/processes/_processes_table.html.erb +6 -25
- data/app/views/pgbus/processes/_row.html.erb +48 -0
- data/app/views/pgbus/processes/_status.html.erb +4 -0
- data/app/views/pgbus/queues/_queues_list.html.erb +28 -32
- data/app/views/pgbus/queues/show.html.erb +69 -118
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +75 -76
- data/app/views/pgbus/recurring_tasks/index.html.erb +1 -1
- data/app/views/pgbus/recurring_tasks/show.html.erb +45 -51
- data/app/views/pgbus/shared/_empty_row.html.erb +2 -0
- data/app/views/pgbus/shared/_pager.html.erb +34 -0
- data/config/i18n-tasks.yml +11 -2
- data/config/locales/da.yml +405 -113
- data/config/locales/de.yml +405 -113
- data/config/locales/en.yml +405 -113
- data/config/locales/es.yml +404 -112
- data/config/locales/fi.yml +404 -112
- data/config/locales/fr.yml +406 -114
- data/config/locales/it.yml +405 -113
- data/config/locales/ja.yml +405 -113
- data/config/locales/nb.yml +405 -113
- data/config/locales/nl.yml +405 -113
- data/config/locales/pt.yml +406 -114
- data/config/locales/sv.yml +404 -112
- data/lib/generators/pgbus/templates/initializer.rb.erb +4 -0
- data/lib/pgbus/active_job/executor.rb +16 -6
- data/lib/pgbus/cli/dlq.rb +32 -5
- data/lib/pgbus/client.rb +4 -2
- data/lib/pgbus/configuration.rb +47 -2
- data/lib/pgbus/dead_letter_header.rb +129 -0
- data/lib/pgbus/doctor.rb +8 -4
- data/lib/pgbus/event_bus/handler.rb +18 -11
- data/lib/pgbus/event_bus/registry.rb +6 -0
- data/lib/pgbus/failed_event_recorder.rb +34 -0
- data/lib/pgbus/instrumentation.rb +2 -1
- data/lib/pgbus/mcp/base_tool.rb +25 -0
- data/lib/pgbus/mcp/tools/concurrency_tool.rb +23 -4
- data/lib/pgbus/mcp/tools/dlq_detail_tool.rb +4 -2
- data/lib/pgbus/mcp/tools/dlq_tool.rb +10 -4
- data/lib/pgbus/mcp/tools/locks_tool.rb +23 -5
- data/lib/pgbus/mcp/tools/recurring_tool.rb +33 -4
- data/lib/pgbus/outbox/poller.rb +1 -1
- data/lib/pgbus/process/consumer.rb +53 -3
- data/lib/pgbus/process/dispatcher.rb +1 -1
- data/lib/pgbus/process/supervisor.rb +32 -11
- data/lib/pgbus/process/worker.rb +45 -8
- data/lib/pgbus/recurring/scheduler.rb +2 -1
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source/dashboard_signals.rb +170 -0
- data/lib/pgbus/web/data_source/dead_letter.rb +142 -0
- data/lib/pgbus/web/data_source/event_list.rb +161 -0
- data/lib/pgbus/web/data_source/job_list.rb +323 -0
- data/lib/pgbus/web/data_source/list_counts.rb +80 -0
- data/lib/pgbus/web/data_source/process_list.rb +69 -0
- data/lib/pgbus/web/data_source/queue_summary.rb +122 -0
- data/lib/pgbus/web/data_source.rb +132 -254
- data/lib/pgbus/web/dead_letter_reason.rb +81 -0
- data/lib/pgbus/web/event_state.rb +71 -0
- data/lib/pgbus/web/health_app.rb +7 -3
- data/lib/pgbus/web/health_verdict.rb +78 -0
- data/lib/pgbus/web/insights_charts.rb +84 -0
- data/lib/pgbus/web/job_state.rb +111 -0
- data/lib/pgbus/web/process_activity.rb +97 -0
- data/lib/pgbus/web/process_state.rb +187 -0
- data/lib/pgbus/web/queue_summary.rb +104 -0
- data/lib/pgbus/web/time_format.rb +128 -0
- metadata +43 -4
- data/app/views/pgbus/events/_pending_table.html.erb +0 -169
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +0 -118
- data/app/views/pgbus/jobs/_failed_table.html.erb +0 -76
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%# The recorded error of a dead message: the full error line, which attempt
|
|
2
|
+
it came from when later ones recorded nothing, and the backtrace head.
|
|
3
|
+
Shared by the list expansion and the show page. Local: reason. %>
|
|
4
|
+
<% if reason.error_class %>
|
|
5
|
+
<p class="mb-2 text-sm text-red-700 dark:text-red-300 break-words"><span class="font-medium"><%= reason.error_class %></span>: <%= reason.error_message %></p>
|
|
6
|
+
<% if (later = reason.later_attempts) %>
|
|
7
|
+
<p class="mb-2 text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.dead_letter.detail.error_from_attempt", attempt: reason.error_attempt, from: later.first, to: later.last) %></p>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if reason.backtrace.present? %>
|
|
10
|
+
<span class="text-xs font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.detail.backtrace") %></span>
|
|
11
|
+
<pre tabindex="0" class="mb-3 mt-1 max-h-40 overflow-auto rounded bg-white dark:bg-gray-800 p-2 text-xs text-gray-700 dark:text-gray-200"><%= reason.backtrace %></pre>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -1,4 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
<%# Dead-letter list (issue #495). Every row says why the message died, read
|
|
2
|
+
from the pgbus_dead_letter header block by DeadLetterReason; a row's
|
|
3
|
+
expansion is the <tr> after it, shown by CSS (.pgbus-job-detail in
|
|
4
|
+
tailwind.css) while the ID cell's <details> is open, like the Jobs list.
|
|
5
|
+
@list_path (->(extra) { url }) carries the dlq/error_class filters into
|
|
6
|
+
the frame source, the chips and the pager. %>
|
|
7
|
+
<% columns = @messages.any? ? 8 : 7 %>
|
|
8
|
+
<turbo-frame id="dlq-messages" data-auto-refresh data-src="<%= @list_path.call(frame: "list", page: (@page if @page > 1)) %>">
|
|
9
|
+
<nav class="mb-4 flex flex-wrap items-center gap-2" aria-label="<%= t("pgbus.dead_letter.filters.label") %>" data-testid="dlq-filters">
|
|
10
|
+
<% [nil, *@dlq_counts.keys].each do |name| %>
|
|
11
|
+
<% active = name == @dlq %>
|
|
12
|
+
<a href="<%= @list_path.call(dlq: name, page: nil) %>" data-turbo-frame="_top"
|
|
13
|
+
aria-current="<%= active ? "page" : "false" %>"
|
|
14
|
+
class="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium <%= active ? 'bg-gray-800 text-white dark:bg-gray-700' : 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white' %>">
|
|
15
|
+
<span><%= name || t("pgbus.dead_letter.filters.all") %></span>
|
|
16
|
+
<span class="rounded-full px-2 py-0.5 text-xs <%= active ? 'bg-gray-600 text-white' : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-200' %>"><%= number_with_delimiter(name ? @dlq_counts[name] : @dlq_counts.values.sum) %></span>
|
|
17
|
+
</a>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if @error_class %>
|
|
20
|
+
<span class="inline-flex items-center gap-2 rounded-md bg-red-50 dark:bg-red-900/30 px-3 py-1 text-sm text-red-800 dark:text-red-200" data-testid="dlq-error-filter">
|
|
21
|
+
<%= t("pgbus.dead_letter.filters.error_class", error_class: @error_class) %>
|
|
22
|
+
<%= pgbus_link_to t("pgbus.dead_letter.filters.clear_error_class"), @list_path.call(error_class: nil, page: nil),
|
|
23
|
+
size: :sm, tone: :red, data: { turbo_frame: "_top" } %>
|
|
24
|
+
</span>
|
|
25
|
+
<% end %>
|
|
26
|
+
</nav>
|
|
2
27
|
<%# Count badge lives inside the frame so it refreshes in lockstep with the table. %>
|
|
3
28
|
<% if @total_count && @total_count > 0 %>
|
|
4
29
|
<p class="mb-3 text-sm text-gray-500 dark:text-gray-400">
|
|
@@ -18,110 +43,26 @@
|
|
|
18
43
|
<th class="w-10 px-4 py-3">
|
|
19
44
|
<input type="checkbox" data-bulk-select-all
|
|
20
45
|
aria-label="<%= t("pgbus.helpers.bulk_select_all") %>"
|
|
21
|
-
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600">
|
|
46
|
+
class="h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-600">
|
|
22
47
|
</th>
|
|
23
48
|
<% end %>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<th class="px-4 py-3 text-
|
|
28
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dead_letter.messages_table.headers.reads") %></th>
|
|
49
|
+
<% %w[id job source_queue died attempts reason].each do |header| %>
|
|
50
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.messages_table.headers.#{header}") %></th>
|
|
51
|
+
<% end %>
|
|
52
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.messages_table.headers.actions") %></th>
|
|
29
53
|
</tr>
|
|
30
54
|
</thead>
|
|
31
55
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
32
|
-
<% @
|
|
33
|
-
|
|
34
|
-
<% source_queue = m[:queue_name].to_s.delete_suffix(Pgbus::DEAD_LETTER_SUFFIX) %>
|
|
35
|
-
<tr>
|
|
36
|
-
<td class="w-10 px-4 py-3 align-top">
|
|
37
|
-
<input type="checkbox" data-bulk-item data-pgbus-action="bulk-row-toggle"
|
|
38
|
-
aria-label="<%= t("pgbus.helpers.bulk_select_row", id: m[:msg_id]) %>"
|
|
39
|
-
data-queue-name="<%= m[:queue_name] %>" data-msg-id="<%= m[:msg_id] %>"
|
|
40
|
-
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 mt-1">
|
|
41
|
-
<input type="hidden" name="messages[][queue_name]" value="<%= m[:queue_name] %>" form="bulk-discard-dlq-form" disabled>
|
|
42
|
-
<input type="hidden" name="messages[][msg_id]" value="<%= m[:msg_id] %>" form="bulk-discard-dlq-form" disabled>
|
|
43
|
-
</td>
|
|
44
|
-
<td colspan="5" class="p-0">
|
|
45
|
-
<details class="group">
|
|
46
|
-
<summary class="flex cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900 list-none">
|
|
47
|
-
<span class="w-16 shrink-0 px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= m[:msg_id] %></span>
|
|
48
|
-
<span class="flex-1 px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= payload["job_class"] || "—" %></span>
|
|
49
|
-
<span class="w-40 shrink-0 px-4 py-3 text-sm text-gray-700"><%= source_queue %></span>
|
|
50
|
-
<span class="w-28 shrink-0 px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:enqueued_at]) %></span>
|
|
51
|
-
<span class="w-16 shrink-0 px-4 py-3 text-sm text-gray-500"><%= m[:read_ct] %></span>
|
|
52
|
-
</summary>
|
|
53
|
-
<div class="px-4 pb-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100">
|
|
54
|
-
<div class="flex items-center justify-between mt-3 mb-3">
|
|
55
|
-
<span class="text-xs font-mono text-gray-400"><%= t("pgbus.dead_letter.messages_table.job_id") %> <%= payload["job_id"] %></span>
|
|
56
|
-
<div class="flex space-x-2">
|
|
57
|
-
<%= button_to t("pgbus.dead_letter.messages_table.retry"), pgbus.retry_dead_letter_path(m[:msg_id], queue_name: m[:queue_name]), method: :post,
|
|
58
|
-
class: "rounded-md bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-indigo-500",
|
|
59
|
-
data: { turbo_frame: "_top" } %>
|
|
60
|
-
<%= button_to t("pgbus.dead_letter.messages_table.discard"), pgbus.discard_dead_letter_path(m[:msg_id], queue_name: m[:queue_name]), method: :post,
|
|
61
|
-
class: "rounded-md bg-red-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-red-500",
|
|
62
|
-
data: { turbo_confirm: t("pgbus.dead_letter.messages_table.discard_confirm"), turbo_frame: "_top" } %>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
<div class="grid grid-cols-2 gap-4 mb-3">
|
|
66
|
-
<div>
|
|
67
|
-
<span class="text-xs font-medium text-gray-500"><%= t("pgbus.dead_letter.messages_table.arguments") %></span>
|
|
68
|
-
<pre class="text-xs text-gray-700 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-40"><%= JSON.pretty_generate(payload["arguments"] || []) rescue "—" %></pre>
|
|
69
|
-
</div>
|
|
70
|
-
<div>
|
|
71
|
-
<span class="text-xs font-medium text-gray-500"><%= t("pgbus.dead_letter.messages_table.metadata") %></span>
|
|
72
|
-
<div class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 space-y-1">
|
|
73
|
-
<% if payload["queue_name"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.queue") %></strong> <%= payload["queue_name"] %></p><% end %>
|
|
74
|
-
<% if payload["priority"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.priority") %></strong> <%= payload["priority"] %></p><% end %>
|
|
75
|
-
<% if payload["executions"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.executions") %></strong> <%= payload["executions"] %></p><% end %>
|
|
76
|
-
<% if m[:vt] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.visible_at") %></strong> <%= m[:vt] %></p><% end %>
|
|
77
|
-
<% if m[:last_read_at] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.last_read") %></strong> <%= m[:last_read_at] %></p><% end %>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
<details class="mt-2">
|
|
82
|
-
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700"><%= t("pgbus.dead_letter.messages_table.full_json_payload") %></summary>
|
|
83
|
-
<pre class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-96"><%= JSON.pretty_generate(payload) rescue m[:message] %></pre>
|
|
84
|
-
</details>
|
|
85
|
-
<% if m[:headers] %>
|
|
86
|
-
<details class="mt-2">
|
|
87
|
-
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700"><%= t("pgbus.dead_letter.messages_table.headers_section") %></summary>
|
|
88
|
-
<pre class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(m[:headers])) rescue m[:headers] %></pre>
|
|
89
|
-
</details>
|
|
90
|
-
<% end %>
|
|
91
|
-
</div>
|
|
92
|
-
</details>
|
|
93
|
-
</td>
|
|
94
|
-
</tr>
|
|
56
|
+
<% @dlq_rows.each do |m, reason| %>
|
|
57
|
+
<%= render "pgbus/dead_letter/row", m: m, reason: reason, columns: columns %>
|
|
95
58
|
<% end %>
|
|
96
59
|
<% if @messages.empty? %>
|
|
97
|
-
|
|
60
|
+
<%# No rows means no checkbox column. %>
|
|
61
|
+
<%= render "pgbus/shared/empty_row", colspan: columns, message: t("pgbus.dead_letter.messages_table.empty") %>
|
|
98
62
|
<% end %>
|
|
99
63
|
</tbody>
|
|
100
64
|
</table>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
104
|
-
<% from = (@page - 1) * @per_page + 1 %>
|
|
105
|
-
<% to = [from + @messages.size - 1, @total_count].min %>
|
|
106
|
-
<%= t("pgbus.helpers.pagination.showing", from: from, to: to, total: @total_count) %>
|
|
107
|
-
</p>
|
|
108
|
-
<% if @total_pages > 1 %>
|
|
109
|
-
<nav class="inline-flex rounded-lg shadow-sm" role="navigation" aria-label="Pagination">
|
|
110
|
-
<% if @page > 1 %>
|
|
111
|
-
<%= link_to t("pgbus.helpers.pagination.previous"), pgbus.dead_letter_index_path(page: @page - 1),
|
|
112
|
-
class: "px-3 py-1.5 text-xs font-medium border border-gray-300 dark:border-gray-600 rounded-l-lg bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700" %>
|
|
113
|
-
<% else %>
|
|
114
|
-
<span class="px-3 py-1.5 text-xs font-medium border border-gray-300 dark:border-gray-600 rounded-l-lg bg-gray-100 dark:bg-gray-900 text-gray-400 dark:text-gray-600 cursor-not-allowed"><%= t("pgbus.helpers.pagination.previous") %></span>
|
|
115
|
-
<% end %>
|
|
116
|
-
<% if @page < @total_pages %>
|
|
117
|
-
<%= link_to t("pgbus.helpers.pagination.next"), pgbus.dead_letter_index_path(page: @page + 1),
|
|
118
|
-
class: "-ml-px px-3 py-1.5 text-xs font-medium border border-gray-300 dark:border-gray-600 rounded-r-lg bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700" %>
|
|
119
|
-
<% else %>
|
|
120
|
-
<span class="-ml-px px-3 py-1.5 text-xs font-medium border border-gray-300 dark:border-gray-600 rounded-r-lg bg-gray-100 dark:bg-gray-900 text-gray-400 dark:text-gray-600 cursor-not-allowed"><%= t("pgbus.helpers.pagination.next") %></span>
|
|
121
|
-
<% end %>
|
|
122
|
-
</nav>
|
|
123
|
-
<% end %>
|
|
124
|
-
</div>
|
|
125
|
-
<% end %>
|
|
65
|
+
<%= render "pgbus/shared/pager", page: @page, per_page: @per_page, shown: @messages.size, total_count: @total_count.to_i,
|
|
66
|
+
page_path: ->(n) { @list_path.call(page: n) } %>
|
|
126
67
|
</div>
|
|
127
68
|
</turbo-frame>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<%# One dead-letter message plus its expansion row. Locals: m (a
|
|
2
|
+
DataSource#dlq_messages hash), reason (its DeadLetterReason::Result),
|
|
3
|
+
columns. %>
|
|
4
|
+
<% payload = pgbus_parse_message(m[:message]) %>
|
|
5
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50" data-dlq-row="<%= m[:msg_id] %>">
|
|
6
|
+
<td class="w-10 px-4 py-3 align-top">
|
|
7
|
+
<input type="checkbox" data-bulk-item data-pgbus-action="bulk-row-toggle"
|
|
8
|
+
aria-label="<%= t("pgbus.helpers.bulk_select_row", id: m[:msg_id]) %>"
|
|
9
|
+
data-queue-name="<%= m[:queue_name] %>" data-msg-id="<%= m[:msg_id] %>"
|
|
10
|
+
class="h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-600 mt-1">
|
|
11
|
+
<input type="hidden" name="messages[][queue_name]" value="<%= m[:queue_name] %>" form="bulk-discard-dlq-form" disabled>
|
|
12
|
+
<input type="hidden" name="messages[][msg_id]" value="<%= m[:msg_id] %>" form="bulk-discard-dlq-form" disabled>
|
|
13
|
+
</td>
|
|
14
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.id") %>" class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white align-top">
|
|
15
|
+
<details data-job-toggle class="group">
|
|
16
|
+
<summary class="cursor-pointer list-none text-indigo-600 hover:text-indigo-800 dark:hover:text-indigo-300 dark:text-indigo-400"><%= m[:msg_id] %></summary>
|
|
17
|
+
</details>
|
|
18
|
+
</td>
|
|
19
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.job") %>" class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white align-top"><%= pgbus_dead_letter_job(m[:message]) %></td>
|
|
20
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.source_queue") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200 align-top"><%= reason.source_queue %></td>
|
|
21
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.died") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 align-top"><%= pgbus_time(reason.died_at) %></td>
|
|
22
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.attempts") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 align-top"><%= pgbus_dead_letter_attempts(reason) %></td>
|
|
23
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.reason") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200 align-top" data-testid="dlq-reason">
|
|
24
|
+
<%= pgbus_dead_letter_reason(reason, filter_path: @list_path) %>
|
|
25
|
+
</td>
|
|
26
|
+
<td data-label="<%= t("pgbus.dead_letter.messages_table.headers.actions") %>" class="px-4 py-3 text-sm text-right align-top">
|
|
27
|
+
<div class="flex justify-end space-x-2">
|
|
28
|
+
<%= pgbus_button_to t("pgbus.dead_letter.messages_table.retry"), pgbus.retry_dead_letter_path(m[:msg_id], queue_name: m[:queue_name]), method: :post,
|
|
29
|
+
variant: :link, size: :sm,
|
|
30
|
+
data: { turbo_frame: "_top" } %>
|
|
31
|
+
<%= pgbus_button_to t("pgbus.dead_letter.messages_table.discard"), pgbus.discard_dead_letter_path(m[:msg_id], queue_name: m[:queue_name]), method: :post,
|
|
32
|
+
variant: :link, size: :sm, tone: :red,
|
|
33
|
+
data: { turbo_confirm: t("pgbus.dead_letter.messages_table.discard_confirm"), turbo_frame: "_top" } %>
|
|
34
|
+
</div>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr class="pgbus-job-detail" data-job-detail data-dlq-detail="<%= m[:msg_id] %>">
|
|
38
|
+
<td colspan="<%= columns %>" class="px-4 pb-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100 dark:border-gray-700">
|
|
39
|
+
<div class="flex items-center justify-between mt-3 mb-3">
|
|
40
|
+
<span class="text-xs font-mono text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.messages_table.job_id") %> <%= payload["job_id"] || payload["event_id"] || "—" %></span>
|
|
41
|
+
<%= pgbus_link_to t("pgbus.dead_letter.messages_table.view"), pgbus.dead_letter_path(m[:msg_id], queue_name: m[:queue_name]),
|
|
42
|
+
size: :sm, data: { turbo_frame: "_top" } %>
|
|
43
|
+
</div>
|
|
44
|
+
<%= render "pgbus/dead_letter/error", reason: reason %>
|
|
45
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-3">
|
|
46
|
+
<div>
|
|
47
|
+
<span class="text-xs font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.messages_table.arguments") %></span>
|
|
48
|
+
<pre class="text-xs text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-40"><%= JSON.pretty_generate(payload["arguments"] || payload["payload"] || []) rescue "—" %></pre>
|
|
49
|
+
</div>
|
|
50
|
+
<div>
|
|
51
|
+
<span class="text-xs font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.messages_table.metadata") %></span>
|
|
52
|
+
<div class="text-xs text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800 rounded p-2 mt-1 space-y-1">
|
|
53
|
+
<p><strong><%= t("pgbus.dead_letter.detail.dlq") %></strong> <%= m[:queue_name] %></p>
|
|
54
|
+
<% if reason.source %><p><strong><%= t("pgbus.dead_letter.detail.source") %></strong> <%= t("pgbus.dead_letter.detail.sources.#{reason.source}", default: reason.source) %></p><% end %>
|
|
55
|
+
<p><strong><%= t("pgbus.dead_letter.detail.died") %></strong> <%= pgbus_timestamp(reason.died_at) %></p>
|
|
56
|
+
<% if payload["queue_name"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.queue") %></strong> <%= payload["queue_name"] %></p><% end %>
|
|
57
|
+
<% if payload["priority"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.priority") %></strong> <%= payload["priority"] %></p><% end %>
|
|
58
|
+
<% if payload["executions"] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.executions") %></strong> <%= payload["executions"] %></p><% end %>
|
|
59
|
+
<% if m[:vt] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.visible_at") %></strong> <%= pgbus_timestamp(m[:vt]) %></p><% end %>
|
|
60
|
+
<% if m[:last_read_at] %><p><strong><%= t("pgbus.dead_letter.messages_table.metadata_labels.last_read") %></strong> <%= pgbus_timestamp(m[:last_read_at]) %></p><% end %>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<details class="mt-2">
|
|
65
|
+
<summary class="text-xs font-medium text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200"><%= t("pgbus.dead_letter.messages_table.full_json_payload") %></summary>
|
|
66
|
+
<pre class="text-xs text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-96"><%= JSON.pretty_generate(payload) rescue m[:message] %></pre>
|
|
67
|
+
</details>
|
|
68
|
+
<% if m[:headers] %>
|
|
69
|
+
<details class="mt-2">
|
|
70
|
+
<summary class="text-xs font-medium text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200"><%= t("pgbus.dead_letter.messages_table.headers_section") %></summary>
|
|
71
|
+
<pre class="text-xs text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(m[:headers])) rescue m[:headers] %></pre>
|
|
72
|
+
</details>
|
|
73
|
+
<% end %>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
@@ -4,17 +4,14 @@
|
|
|
4
4
|
<div class="flex items-center space-x-2">
|
|
5
5
|
<div data-bulk-actions class="hidden flex items-center space-x-2">
|
|
6
6
|
<span class="text-sm text-gray-500 dark:text-gray-400"><span data-bulk-count>0</span> <%= t("pgbus.helpers.bulk_selected") %></span>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
data-turbo-confirm="<%= t("pgbus.dead_letter.index.discard_selected_confirm") %>">
|
|
10
|
-
<%= t("pgbus.dead_letter.index.discard_selected") %>
|
|
11
|
-
</button>
|
|
7
|
+
<%= pgbus_button_tag t("pgbus.dead_letter.index.discard_selected"), variant: :danger, form: "bulk-discard-dlq-form",
|
|
8
|
+
confirm: t("pgbus.dead_letter.index.discard_selected_confirm") %>
|
|
12
9
|
</div>
|
|
13
|
-
<%=
|
|
14
|
-
|
|
10
|
+
<%= pgbus_button_to t("pgbus.dead_letter.index.retry_all"), pgbus.retry_all_dead_letter_index_path, method: :post,
|
|
11
|
+
variant: :primary,
|
|
15
12
|
data: { turbo_confirm: t("pgbus.dead_letter.index.retry_all_confirm") } %>
|
|
16
|
-
<%=
|
|
17
|
-
|
|
13
|
+
<%= pgbus_button_to t("pgbus.dead_letter.index.discard_all"), pgbus.discard_all_dead_letter_index_path, method: :post,
|
|
14
|
+
variant: :danger,
|
|
18
15
|
data: { turbo_confirm: t("pgbus.dead_letter.index.discard_all_confirm") } %>
|
|
19
16
|
</div>
|
|
20
17
|
<% end %>
|
|
@@ -1,40 +1,56 @@
|
|
|
1
1
|
<div class="mb-6">
|
|
2
|
-
<%=
|
|
2
|
+
<%= pgbus_link_to "\u2190 #{t("pgbus.dead_letter.show.back")}", pgbus.dead_letter_index_path %>
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<% if @message %>
|
|
6
6
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-6"><%= t("pgbus.dead_letter.show.title", id: @message[:msg_id]) %></h1>
|
|
7
7
|
|
|
8
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6" data-testid="dead-letter-reason">
|
|
9
|
+
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.dead_letter.detail.why") %></h2>
|
|
10
|
+
<p class="text-sm text-gray-900 dark:text-white mb-3"><%= pgbus_dead_letter_reason(@reason) %></p>
|
|
11
|
+
<%= render "pgbus/dead_letter/error", reason: @reason %>
|
|
12
|
+
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
13
|
+
<div>
|
|
14
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.source_queue") %></dt>
|
|
15
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @reason.source_queue %></dd>
|
|
16
|
+
</div>
|
|
17
|
+
<div>
|
|
18
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.attempts") %></dt>
|
|
19
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= pgbus_dead_letter_attempts(@reason) %></dd>
|
|
20
|
+
</div>
|
|
21
|
+
<div>
|
|
22
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.died") %></dt>
|
|
23
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= pgbus_timestamp(@reason.died_at) %></dd>
|
|
24
|
+
</div>
|
|
25
|
+
</dl>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
8
28
|
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
9
29
|
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
10
30
|
<div>
|
|
11
|
-
<dt class="text-sm font-medium text-gray-500"><%= t("pgbus.dead_letter.show.labels.
|
|
31
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.dlq") %></dt>
|
|
12
32
|
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:queue_name] %></dd>
|
|
13
33
|
</div>
|
|
14
34
|
<div>
|
|
15
|
-
<dt class="text-sm font-medium text-gray-500"><%= t("pgbus.dead_letter.show.labels.enqueued_at") %></dt>
|
|
16
|
-
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:enqueued_at] %></dd>
|
|
17
|
-
</div>
|
|
18
|
-
<div>
|
|
19
|
-
<dt class="text-sm font-medium text-gray-500"><%= t("pgbus.dead_letter.show.labels.read_count") %></dt>
|
|
20
|
-
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:read_ct] %></dd>
|
|
35
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.enqueued_at") %></dt>
|
|
36
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= pgbus_timestamp(@message[:enqueued_at]) %></dd>
|
|
21
37
|
</div>
|
|
22
38
|
<div>
|
|
23
|
-
<dt class="text-sm font-medium text-gray-500"><%= t("pgbus.dead_letter.show.labels.visibility_timeout") %></dt>
|
|
24
|
-
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:vt] %></dd>
|
|
39
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.labels.visibility_timeout") %></dt>
|
|
40
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= pgbus_timestamp(@message[:vt]) %></dd>
|
|
25
41
|
</div>
|
|
26
42
|
</dl>
|
|
27
43
|
</div>
|
|
28
44
|
|
|
29
45
|
<% if (context = pgbus_job_context(@message[:message])) %>
|
|
30
46
|
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6" data-testid="job-context">
|
|
31
|
-
<h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.dead_letter.show.context") %></h2>
|
|
47
|
+
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.dead_letter.show.context") %></h2>
|
|
32
48
|
<% context.each do |klass_name, attrs| %>
|
|
33
49
|
<h3 class="text-xs font-semibold text-gray-700 dark:text-gray-300 mt-3 mb-1 font-mono"><%= klass_name %></h3>
|
|
34
50
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-1 sm:grid-cols-2">
|
|
35
51
|
<% attrs.each do |name, value| %>
|
|
36
52
|
<div class="flex gap-2 text-xs">
|
|
37
|
-
<dt class="text-gray-500 font-mono"><%= name %></dt>
|
|
53
|
+
<dt class="text-gray-500 dark:text-gray-400 font-mono"><%= name %></dt>
|
|
38
54
|
<dd class="text-gray-900 dark:text-white font-mono break-all"><%= value %></dd>
|
|
39
55
|
</div>
|
|
40
56
|
<% end %>
|
|
@@ -44,26 +60,26 @@
|
|
|
44
60
|
<% end %>
|
|
45
61
|
|
|
46
62
|
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
47
|
-
<h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.dead_letter.show.payload") %></h2>
|
|
48
|
-
<pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:message])) rescue @message[:message] %></pre>
|
|
63
|
+
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.dead_letter.show.payload") %></h2>
|
|
64
|
+
<pre tabindex="0" class="text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:message])) rescue @message[:message] %></pre>
|
|
49
65
|
</div>
|
|
50
66
|
|
|
51
67
|
<% if @message[:headers] %>
|
|
52
68
|
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
53
|
-
<h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.dead_letter.show.headers") %></h2>
|
|
54
|
-
<pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:headers])) rescue @message[:headers] %></pre>
|
|
69
|
+
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.dead_letter.show.headers") %></h2>
|
|
70
|
+
<pre tabindex="0" class="text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:headers])) rescue @message[:headers] %></pre>
|
|
55
71
|
</div>
|
|
56
72
|
<% end %>
|
|
57
73
|
|
|
58
74
|
<div class="flex space-x-2">
|
|
59
|
-
<%=
|
|
60
|
-
|
|
61
|
-
<%=
|
|
62
|
-
|
|
75
|
+
<%= pgbus_button_to t("pgbus.dead_letter.show.retry"), pgbus.retry_dead_letter_path(@message[:msg_id], queue_name: @message[:queue_name]), method: :post,
|
|
76
|
+
variant: :primary %>
|
|
77
|
+
<%= pgbus_button_to t("pgbus.dead_letter.show.discard"), pgbus.discard_dead_letter_path(@message[:msg_id], queue_name: @message[:queue_name]), method: :post,
|
|
78
|
+
variant: :danger,
|
|
63
79
|
data: { turbo_confirm: t("pgbus.dead_letter.show.discard_confirm") } %>
|
|
64
80
|
</div>
|
|
65
81
|
<% else %>
|
|
66
82
|
<div class="text-center py-12">
|
|
67
|
-
<p class="text-gray-400"><%= t("pgbus.dead_letter.show.not_found") %></p>
|
|
83
|
+
<p class="text-gray-500 dark:text-gray-400"><%= t("pgbus.dead_letter.show.not_found") %></p>
|
|
68
84
|
</div>
|
|
69
85
|
<% end %>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<%# Pending events (issue #494): the unified job list scoped to the EventBus
|
|
2
|
+
handler queues. Each row's state comes from SQL, EventState adds the
|
|
3
|
+
reason; a row's expansion is the <tr> after it (.pgbus-job-detail in
|
|
4
|
+
tailwind.css), so the auto-refresh pause on `details[open]` keeps working.
|
|
5
|
+
Orphaned failed rows (message gone) are discarded through their failed
|
|
6
|
+
event and take no part in the bulk selection. %>
|
|
7
|
+
<% selectable = @rows.any? { |r| r[:source] == "queue" } %>
|
|
8
|
+
<% columns = selectable ? 8 : 7 %>
|
|
9
|
+
<turbo-frame id="events-list" data-auto-refresh data-turbo-action="advance"
|
|
10
|
+
data-src="<%= events_list_path(frame: "list") %>">
|
|
11
|
+
<div data-bulk-page>
|
|
12
|
+
<div class="flex items-center justify-end mb-3 space-x-2">
|
|
13
|
+
<div data-bulk-actions class="hidden flex items-center space-x-2">
|
|
14
|
+
<span class="text-sm text-gray-500 dark:text-gray-400"><span data-bulk-count>0</span> <%= t("pgbus.helpers.bulk_selected") %></span>
|
|
15
|
+
<%= pgbus_button_tag t("pgbus.events.list.discard_selected"), variant: :danger, form: "bulk-discard-events-form",
|
|
16
|
+
confirm: t("pgbus.events.list.discard_selected_confirm") %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<%# Bulk form outside the table to avoid nested forms. %>
|
|
20
|
+
<form id="bulk-discard-events-form" action="<%= pgbus.discard_selected_events_path %>" method="post" data-turbo-frame="_top" class="hidden">
|
|
21
|
+
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
|
|
22
|
+
</form>
|
|
23
|
+
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700" data-bulk-scope="events">
|
|
24
|
+
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
25
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
26
|
+
<tr>
|
|
27
|
+
<% if selectable %>
|
|
28
|
+
<th class="w-10 px-4 py-3">
|
|
29
|
+
<input type="checkbox" data-bulk-select-all
|
|
30
|
+
aria-label="<%= t("pgbus.helpers.bulk_select_all") %>"
|
|
31
|
+
class="h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-600">
|
|
32
|
+
</th>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% %w[id event handler state attempts reason].each do |header| %>
|
|
35
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.events.list.headers.#{header}") %></th>
|
|
36
|
+
<% end %>
|
|
37
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.events.list.headers.actions") %></th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
41
|
+
<% @rows.each do |row| %>
|
|
42
|
+
<% event = Pgbus::Web::EventState.present(row, @state_context, ahead: @ahead[[row[:queue_name], row[:msg_id]]]) %>
|
|
43
|
+
<%= render "pgbus/events/row", row: row, event: event, selectable: selectable, columns: columns %>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% if @rows.empty? %>
|
|
46
|
+
<%= render "pgbus/shared/empty_row", colspan: columns, message: t("pgbus.events.list.empty.#{@state}") %>
|
|
47
|
+
<% end %>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
<%= render "pgbus/shared/pager", page: @page, per_page: @per_page, shown: @rows.size,
|
|
51
|
+
total_count: @counts[@state], has_more: @counts.capped?(@state) && @rows.size == @per_page,
|
|
52
|
+
page_path: ->(n) { events_list_path(page: n) } %>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</turbo-frame>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<%# Processed events (issue #494): the pgbus_processed_events audit, each
|
|
2
|
+
claim in words (completed, being handled, went silent) and a Replay where
|
|
3
|
+
the handler is still registered and the archive still holds the event.
|
|
4
|
+
Pages on processed_page, so the pending list keeps its own page. %>
|
|
5
|
+
<turbo-frame id="processed-events" data-auto-refresh data-turbo-action="advance"
|
|
6
|
+
data-src="<%= events_list_path(frame: "processed") %>">
|
|
7
|
+
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
8
|
+
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
9
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
10
|
+
<tr>
|
|
11
|
+
<% %w[event_id handler state reason].each do |header| %>
|
|
12
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.events.processed.headers.#{header}") %></th>
|
|
13
|
+
<% end %>
|
|
14
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.events.processed.headers.actions") %></th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
18
|
+
<% now = Time.now %>
|
|
19
|
+
<% @events.each do |e| %>
|
|
20
|
+
<% claim = Pgbus::Web::EventState.processed(e, now: now, claim_window: @claim_window) %>
|
|
21
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50" data-processed-event data-state="<%= claim.state %>">
|
|
22
|
+
<td data-label="<%= t("pgbus.events.processed.headers.event_id") %>" class="px-4 py-3 text-sm font-mono">
|
|
23
|
+
<%= pgbus_link_to e["event_id"], pgbus.event_path(e["id"]), variant: :link, size: :sm, data: { turbo_frame: "_top" } %>
|
|
24
|
+
</td>
|
|
25
|
+
<td data-label="<%= t("pgbus.events.processed.headers.handler") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200"><%= e["handler_class"] %></td>
|
|
26
|
+
<td data-label="<%= t("pgbus.events.processed.headers.state") %>" class="px-4 py-3 text-sm"><%= pgbus_processed_event_badge(claim) %></td>
|
|
27
|
+
<td data-label="<%= t("pgbus.events.processed.headers.reason") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200"><%= pgbus_processed_event_reason(claim) %></td>
|
|
28
|
+
<td data-label="<%= t("pgbus.events.processed.headers.actions") %>" class="px-4 py-3 text-sm text-right">
|
|
29
|
+
<%# An unfinished claim's message is still in its queue: nothing to replay yet. %>
|
|
30
|
+
<% if claim.state == "completed" %>
|
|
31
|
+
<%= render "pgbus/events/replay", event: e, replay_state: @replay_states[e["id"]] %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% if @events.empty? %>
|
|
37
|
+
<%= render "pgbus/shared/empty_row", colspan: 5, message: t("pgbus.events.processed.empty") %>
|
|
38
|
+
<% end %>
|
|
39
|
+
</tbody>
|
|
40
|
+
</table>
|
|
41
|
+
<%= render "pgbus/shared/pager", page: @processed_page, per_page: @per_page, shown: @events.size,
|
|
42
|
+
total_count: @processed_count.total, has_more: @processed_count.capped? && @events.size == @per_page,
|
|
43
|
+
page_path: ->(n) { events_list_path(processed_page: n) } %>
|
|
44
|
+
</div>
|
|
45
|
+
</turbo-frame>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# Replay for one processed event, or in words why there is none. Locals:
|
|
2
|
+
event (a pgbus_processed_events row), replay_state (:replayable,
|
|
3
|
+
:not_archived or :no_handler, from DataSource#event_replay_states). %>
|
|
4
|
+
<% if replay_state == :replayable %>
|
|
5
|
+
<%= pgbus_button_to t("pgbus.events.processed.replay"), pgbus.replay_event_path(event["id"]),
|
|
6
|
+
method: :post, variant: :link, size: :sm,
|
|
7
|
+
data: { turbo_confirm: t("pgbus.events.processed.replay_confirm"), turbo_frame: "_top" } %>
|
|
8
|
+
<% else %>
|
|
9
|
+
<span class="text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.events.processed.replay_unavailable.#{replay_state || :not_archived}") %></span>
|
|
10
|
+
<% end %>
|