pgbus 0.2.4 → 0.2.6

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -1
  3. data/app/controllers/pgbus/application_controller.rb +13 -4
  4. data/app/helpers/pgbus/application_helper.rb +10 -0
  5. data/app/models/pgbus/application_record.rb +4 -1
  6. data/app/models/pgbus/batch_entry.rb +1 -1
  7. data/app/models/pgbus/blocked_execution.rb +1 -1
  8. data/app/models/pgbus/job_lock.rb +1 -1
  9. data/app/models/pgbus/job_stat.rb +1 -1
  10. data/app/models/pgbus/outbox_entry.rb +1 -1
  11. data/app/models/pgbus/process_entry.rb +1 -1
  12. data/app/models/pgbus/processed_event.rb +1 -1
  13. data/app/models/pgbus/queue_state.rb +1 -1
  14. data/app/models/pgbus/recurring_execution.rb +1 -1
  15. data/app/models/pgbus/recurring_task.rb +1 -1
  16. data/app/models/pgbus/semaphore.rb +1 -1
  17. data/app/views/layouts/pgbus/application.html.erb +104 -15
  18. data/app/views/pgbus/dashboard/_processes_table.html.erb +5 -5
  19. data/app/views/pgbus/dashboard/_queues_table.html.erb +6 -6
  20. data/app/views/pgbus/dashboard/_recent_failures.html.erb +4 -4
  21. data/app/views/pgbus/dead_letter/_messages_table.html.erb +1 -1
  22. data/app/views/pgbus/events/index.html.erb +8 -8
  23. data/app/views/pgbus/insights/show.html.erb +31 -29
  24. data/app/views/pgbus/jobs/_enqueued_table.html.erb +1 -1
  25. data/app/views/pgbus/jobs/_failed_table.html.erb +7 -7
  26. data/app/views/pgbus/locks/index.html.erb +7 -7
  27. data/app/views/pgbus/outbox/index.html.erb +7 -7
  28. data/app/views/pgbus/processes/_processes_table.html.erb +7 -7
  29. data/app/views/pgbus/queues/_queues_list.html.erb +8 -8
  30. data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +8 -8
  31. data/config/locales/da.yml +2 -0
  32. data/config/locales/de.yml +2 -0
  33. data/config/locales/en.yml +2 -0
  34. data/config/locales/es.yml +2 -0
  35. data/config/locales/fi.yml +2 -0
  36. data/config/locales/fr.yml +2 -0
  37. data/config/locales/it.yml +2 -0
  38. data/config/locales/ja.yml +2 -0
  39. data/config/locales/nb.yml +2 -0
  40. data/config/locales/nl.yml +2 -0
  41. data/config/locales/pt.yml +2 -0
  42. data/config/locales/sv.yml +2 -0
  43. data/lib/pgbus/bus_record.rb +16 -0
  44. data/lib/pgbus/configuration.rb +4 -2
  45. data/lib/pgbus/engine.rb +1 -1
  46. data/lib/pgbus/process/consumer_priority.rb +1 -1
  47. data/lib/pgbus/process/dispatcher.rb +1 -1
  48. data/lib/pgbus/process/queue_lock.rb +1 -1
  49. data/lib/pgbus/process/worker.rb +1 -1
  50. data/lib/pgbus/version.rb +1 -1
  51. data/lib/pgbus/web/data_source.rb +1 -1
  52. data/lib/pgbus.rb +4 -0
  53. data/lib/tasks/pgbus_pgmq.rake +1 -1
  54. metadata +2 -1
@@ -68,7 +68,7 @@
68
68
  <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
69
69
  <h3 class="text-sm font-medium text-gray-700 dark:text-gray-300"><%= t("pgbus.insights.show.slowest.title") %></h3>
70
70
  </div>
71
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
71
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
72
72
  <thead class="bg-gray-50 dark:bg-gray-900">
73
73
  <tr>
74
74
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.insights.show.slowest.headers.job_class") %></th>
@@ -80,10 +80,10 @@
80
80
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
81
81
  <% @slowest.each do |row| %>
82
82
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
83
- <td class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300"><%= row[:job_class] %></td>
84
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_number(row[:count]) %></td>
85
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:avg_ms]) %></td>
86
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:max_ms]) %></td>
83
+ <td data-label="Job Class" class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300"><%= row[:job_class] %></td>
84
+ <td data-label="Count" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_number(row[:count]) %></td>
85
+ <td data-label="Avg" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:avg_ms]) %></td>
86
+ <td data-label="Max" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:max_ms]) %></td>
87
87
  </tr>
88
88
  <% end %>
89
89
  <% if @slowest.empty? %>
@@ -95,12 +95,14 @@
95
95
 
96
96
  <script src="https://cdn.jsdelivr.net/npm/apexcharts@4"></script>
97
97
  <script>
98
- let throughputChart, statusChart;
98
+ (function() {
99
+ // IIFE prevents "redeclaration of let" when Turbo re-executes on navigation
100
+ var throughputChart, statusChart;
99
101
 
100
102
  function getThemeColors() {
101
- const isDark = document.documentElement.classList.contains('dark');
103
+ var isDark = document.documentElement.classList.contains('dark');
102
104
  return {
103
- isDark,
105
+ isDark: isDark,
104
106
  text: isDark ? '#9ca3af' : '#6b7280',
105
107
  grid: isDark ? '#374151' : '#e5e7eb',
106
108
  tooltip: isDark ? 'dark' : 'light',
@@ -109,16 +111,14 @@
109
111
  }
110
112
 
111
113
  function renderCharts(data) {
112
- const t = getThemeColors();
114
+ var t = getThemeColors();
113
115
 
114
- // Destroy existing charts before re-rendering
115
116
  if (throughputChart) throughputChart.destroy();
116
117
  if (statusChart) statusChart.destroy();
117
118
 
118
- // Throughput chart
119
- const throughputData = data.throughput.map(p => ({
120
- x: new Date(p.time).getTime(), y: p.count
121
- }));
119
+ var throughputData = data.throughput.map(function(p) {
120
+ return { x: new Date(p.time).getTime(), y: p.count };
121
+ });
122
122
 
123
123
  throughputChart = new ApexCharts(document.querySelector('#throughput-chart'), {
124
124
  series: [{ name: '<%= j(t("pgbus.insights.show.charts.series_name")) %>', data: throughputData }],
@@ -134,10 +134,9 @@
134
134
  });
135
135
  throughputChart.render();
136
136
 
137
- // Status distribution chart
138
- const statusLabels = Object.keys(data.status_counts);
139
- const statusValues = Object.values(data.status_counts);
140
- const statusColors = statusLabels.map(s => {
137
+ var statusLabels = Object.keys(data.status_counts);
138
+ var statusValues = Object.values(data.status_counts);
139
+ var statusColors = statusLabels.map(function(s) {
141
140
  if (s === 'success') return '#10b981';
142
141
  if (s === 'failed') return '#ef4444';
143
142
  if (s === 'dead_lettered') return '#f97316';
@@ -161,21 +160,24 @@
161
160
  }
162
161
  }
163
162
 
164
- // Fetch data and render
165
- let chartData = null;
163
+ var chartData = null;
166
164
  fetch('<%= pgbus.api_insights_path(minutes: @minutes) %>')
167
- .then(r => r.json())
168
- .then(data => { chartData = data; renderCharts(data); })
169
- .catch(() => {
170
- const msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24"><%= j(t("pgbus.insights.show.charts.failed_to_load")) %></p>';
171
- document.querySelector('#throughput-chart').innerHTML = msg;
172
- document.querySelector('#status-chart').innerHTML = msg;
165
+ .then(function(r) {
166
+ if (!r.ok) throw new Error('HTTP ' + r.status);
167
+ return r.json();
168
+ })
169
+ .then(function(data) { chartData = data; renderCharts(data); })
170
+ .catch(function(err) {
171
+ if (err.name === 'AbortError') return;
172
+ var msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24"><%= j(t("pgbus.insights.show.charts.failed_to_load")) %></p>';
173
+ var el1 = document.querySelector('#throughput-chart');
174
+ var el2 = document.querySelector('#status-chart');
175
+ if (el1) el1.innerHTML = msg;
176
+ if (el2) el2.innerHTML = msg;
173
177
  });
174
178
 
175
- // Re-render charts when dark mode toggles.
176
- // Listen for class changes on <html> instead of wrapping the toggle function,
177
- // so it works regardless of script loading order.
178
179
  new MutationObserver(function() {
179
180
  if (chartData) renderCharts(chartData);
180
181
  }).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
182
+ })();
181
183
  </script>
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.jobs.enqueued_table.title") %></h2>
4
4
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
5
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
5
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
6
6
  <thead class="bg-gray-50 dark:bg-gray-900">
7
7
  <tr>
8
8
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.jobs.enqueued_table.headers.id") %></th>
@@ -2,7 +2,7 @@
2
2
  <div class="mb-8">
3
3
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.jobs.failed_table.title") %></h2>
4
4
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
5
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
5
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
6
6
  <thead class="bg-gray-50 dark:bg-gray-900">
7
7
  <tr>
8
8
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.jobs.failed_table.headers.id") %></th>
@@ -16,16 +16,16 @@
16
16
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
17
17
  <% @failed.each do |f| %>
18
18
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
19
- <td class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
19
+ <td data-label="ID" class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
20
20
  <%= link_to f["id"], pgbus.job_path(f["id"]), class: "text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
21
21
  </td>
22
- <td class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
23
- <td class="px-4 py-3 text-sm text-red-600 max-w-sm truncate">
22
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
23
+ <td data-label="Error" class="px-4 py-3 text-sm text-red-600 max-w-sm truncate">
24
24
  <span class="font-medium"><%= f["error_class"] %></span>: <%= truncate(f["error_message"].to_s, length: 80) %>
25
25
  </td>
26
- <td class="px-4 py-3 text-sm text-gray-500"><%= f["retry_count"] %></td>
27
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
28
- <td class="px-4 py-3 text-sm text-right space-x-2">
26
+ <td data-label="Retries" class="px-4 py-3 text-sm text-gray-500"><%= f["retry_count"] %></td>
27
+ <td data-label="Failed" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
28
+ <td data-label="Actions" class="px-4 py-3 text-sm text-right space-x-2">
29
29
  <%= button_to t("pgbus.jobs.failed_table.retry"), pgbus.retry_job_path(f["id"]), method: :post,
30
30
  class: "text-xs text-indigo-600 hover:text-indigo-800 font-medium",
31
31
  data: { turbo_frame: "_top" } %>
@@ -4,7 +4,7 @@
4
4
  </div>
5
5
 
6
6
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
7
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
7
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
8
8
  <thead class="bg-gray-50 dark:bg-gray-900">
9
9
  <tr>
10
10
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.lock_key") %></th>
@@ -18,16 +18,16 @@
18
18
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
19
19
  <% @locks.each do |lock| %>
20
20
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
21
- <td class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
22
- <td class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:job_class] %></td>
23
- <td class="px-4 py-3 text-sm">
21
+ <td data-label="Lock Key" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
22
+ <td data-label="Job Class" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:job_class] %></td>
23
+ <td data-label="State" class="px-4 py-3 text-sm">
24
24
  <% if lock[:state] == "executing" %>
25
25
  <span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:text-blue-300"><%= t("pgbus.locks.index.executing") %></span>
26
26
  <% else %>
27
27
  <span class="inline-flex items-center rounded-full bg-yellow-100 dark:bg-yellow-900/50 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:text-yellow-300"><%= t("pgbus.locks.index.queued") %></span>
28
28
  <% end %>
29
29
  </td>
30
- <td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
30
+ <td data-label="Owner" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
31
31
  <% if lock[:owner_pid] %>
32
32
  <span class="font-mono"><%= lock[:owner_pid] %></span>
33
33
  <% if lock[:owner_hostname] %>
@@ -37,12 +37,12 @@
37
37
  <span class="text-gray-400 dark:text-gray-500">—</span>
38
38
  <% end %>
39
39
  </td>
40
- <td class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
40
+ <td data-label="Age" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
41
41
  <% if lock[:age_seconds] %>
42
42
  <%= pgbus_duration(lock[:age_seconds]) %>
43
43
  <% end %>
44
44
  </td>
45
- <td class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_time_ago_future(lock[:expires_at]) %></td>
45
+ <td data-label="Expires" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_time_ago_future(lock[:expires_at]) %></td>
46
46
  </tr>
47
47
  <% end %>
48
48
  <% if @locks.empty? %>
@@ -19,7 +19,7 @@
19
19
  </div>
20
20
 
21
21
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
22
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
22
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
23
23
  <thead class="bg-gray-50 dark:bg-gray-900">
24
24
  <tr>
25
25
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.id") %></th>
@@ -33,18 +33,18 @@
33
33
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
34
34
  <% @entries.each do |entry| %>
35
35
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
36
- <td class="px-4 py-3 text-sm font-mono text-gray-700"><%= entry.id %></td>
37
- <td class="px-4 py-3 text-sm text-gray-700"><%= entry.routing_key || entry.queue_name %></td>
38
- <td class="px-4 py-3 text-sm text-gray-500 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= entry.priority %></td>
40
- <td class="px-4 py-3 text-sm text-right">
36
+ <td data-label="ID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= entry.id %></td>
37
+ <td data-label="Routing Key" class="px-4 py-3 text-sm text-gray-700"><%= entry.routing_key || entry.queue_name %></td>
38
+ <td data-label="Payload" class="px-4 py-3 text-sm text-gray-500 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
+ <td data-label="Priority" class="px-4 py-3 text-sm text-right text-gray-500"><%= entry.priority %></td>
40
+ <td data-label="Status" class="px-4 py-3 text-sm text-right">
41
41
  <% if entry.published_at %>
42
42
  <span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"><%= t("pgbus.outbox.index.published") %></span>
43
43
  <% else %>
44
44
  <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.outbox.index.pending") %></span>
45
45
  <% end %>
46
46
  </td>
47
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_time_ago(entry.created_at) %></td>
47
+ <td data-label="Created" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_time_ago(entry.created_at) %></td>
48
48
  </tr>
49
49
  <% end %>
50
50
  <% if @entries.empty? %>
@@ -1,6 +1,6 @@
1
1
  <turbo-frame id="processes-list" data-auto-refresh data-src="<%= pgbus.processes_path(frame: 'list') %>">
2
2
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
3
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
3
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
6
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.kind") %></th>
@@ -14,12 +14,12 @@
14
14
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
15
15
  <% @processes.each do |p| %>
16
16
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
17
- <td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
- <td class="px-4 py-3 text-sm text-gray-700"><%= p[:hostname] %></td>
19
- <td class="px-4 py-3 text-sm font-mono text-gray-700"><%= p[:pid] %></td>
20
- <td class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
21
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(p[:last_heartbeat_at]) %></td>
22
- <td class="px-4 py-3 text-sm text-gray-500 font-mono text-xs max-w-xs truncate">
17
+ <td data-label="Kind" class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
+ <td data-label="Host" class="px-4 py-3 text-sm text-gray-700"><%= p[:hostname] %></td>
19
+ <td data-label="PID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= p[:pid] %></td>
20
+ <td data-label="Status" class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
21
+ <td data-label="Last Heartbeat" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(p[:last_heartbeat_at]) %></td>
22
+ <td data-label="Metadata" class="px-4 py-3 text-sm text-gray-500 font-mono text-xs max-w-xs truncate">
23
23
  <% if p[:metadata].is_a?(Hash) %>
24
24
  <% p[:metadata].each do |k, v| %>
25
25
  <span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-xs mr-1"><%= k %>: <%= v %></span>
@@ -1,6 +1,6 @@
1
1
  <turbo-frame id="queues-list" data-auto-refresh data-src="<%= pgbus.queues_path(frame: 'list') %>">
2
2
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
3
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
3
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
6
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.queue") %></th>
@@ -15,19 +15,19 @@
15
15
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
16
16
  <% @queues.each do |q| %>
17
17
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
18
- <td class="px-4 py-3 text-sm">
18
+ <td data-label="Queue" class="px-4 py-3 text-sm">
19
19
  <%= link_to q[:name], pgbus.queue_path(name: q[:name]), class: "font-medium text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
20
20
  <%= pgbus_queue_badge(q[:name]) %>
21
21
  <% if q[:paused] %>
22
22
  <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.queues.queues_list.paused") %></span>
23
23
  <% end %>
24
24
  </td>
25
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
26
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
27
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
28
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:newest_msg_age_sec] || "—" %></td>
29
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
30
- <td class="px-4 py-3 text-sm text-right space-x-2">
25
+ <td data-label="Depth" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
26
+ <td data-label="Visible" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
27
+ <td data-label="Oldest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
28
+ <td data-label="Newest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:newest_msg_age_sec] || "—" %></td>
29
+ <td data-label="Total" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
30
+ <td data-label="Actions" class="px-4 py-3 text-sm text-right space-x-2">
31
31
  <% if q[:paused] %>
32
32
  <%= button_to t("pgbus.queues.queues_list.resume"), pgbus.resume_queue_path(name: q[:name]),
33
33
  method: :post,
@@ -6,7 +6,7 @@
6
6
  <p class="mt-1 text-sm"><%= t("pgbus.recurring_tasks.tasks_table.empty_hint") %></p>
7
7
  </div>
8
8
  <% else %>
9
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
9
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
10
10
  <thead class="bg-gray-50 dark:bg-gray-900">
11
11
  <tr>
12
12
  <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"><%= t("pgbus.recurring_tasks.tasks_table.headers.task") %></th>
@@ -21,7 +21,7 @@
21
21
  <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
22
22
  <% @recurring_tasks.each do |task| %>
23
23
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
24
- <td class="px-4 py-3">
24
+ <td data-label="Task" class="px-4 py-3">
25
25
  <div>
26
26
  <%= link_to task[:key], pgbus.recurring_task_path(task[:id]),
27
27
  class: "text-sm font-medium text-blue-600 hover:text-blue-800" %>
@@ -31,26 +31,26 @@
31
31
  <div class="text-xs text-gray-400 mt-0.5"><%= task[:description] %></div>
32
32
  <% end %>
33
33
  </td>
34
- <td class="px-4 py-3">
34
+ <td data-label="Schedule" class="px-4 py-3">
35
35
  <div class="text-sm text-gray-900 dark:text-white"><%= task[:schedule] %></div>
36
36
  <% if task[:human_schedule] && task[:human_schedule] != task[:schedule] %>
37
37
  <div class="text-xs text-gray-400"><%= task[:human_schedule] %></div>
38
38
  <% end %>
39
39
  </td>
40
- <td class="px-4 py-3 text-sm text-gray-600">
40
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-600">
41
41
  <%= task[:queue_name] || t("pgbus.recurring_tasks.tasks_table.default_queue") %>
42
42
  </td>
43
- <td class="px-4 py-3 text-sm text-gray-600">
43
+ <td data-label="Last Run" class="px-4 py-3 text-sm text-gray-600">
44
44
  <%= task[:last_run_at] ? pgbus_time_ago(task[:last_run_at]) : t("pgbus.recurring_tasks.tasks_table.never") %>
45
45
  </td>
46
- <td class="px-4 py-3 text-sm text-gray-600">
46
+ <td data-label="Next Run" class="px-4 py-3 text-sm text-gray-600">
47
47
  <% if task[:enabled] && task[:next_run_at] %>
48
48
  <%= pgbus_time_ago_future(task[:next_run_at]) %>
49
49
  <% else %>
50
50
  <span class="text-gray-400">—</span>
51
51
  <% end %>
52
52
  </td>
53
- <td class="px-4 py-3">
53
+ <td data-label="Status" class="px-4 py-3">
54
54
  <% if task[:enabled] %>
55
55
  <%= pgbus_recurring_health_badge(task) %>
56
56
  <% else %>
@@ -59,7 +59,7 @@
59
59
  </span>
60
60
  <% end %>
61
61
  </td>
62
- <td class="px-4 py-3 text-right space-x-1">
62
+ <td data-label="Actions" class="px-4 py-3 text-right space-x-1">
63
63
  <%= button_to task[:enabled] ? t("pgbus.recurring_tasks.tasks_table.disable") : t("pgbus.recurring_tasks.tasks_table.enable"),
64
64
  pgbus.toggle_recurring_task_path(task[:id]),
65
65
  class: "inline-flex items-center rounded px-2 py-1 text-xs font-medium " \
@@ -217,8 +217,10 @@ da:
217
217
  processes: Processer
218
218
  queues: Køer
219
219
  recurring: Gentagende
220
+ return_to_app: Tilbage til app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Skift til mørk tilstand
223
+ toggle_menu: Skift menu
222
224
  locks:
223
225
  index:
224
226
  description: Aktive unikke låse forhindrer duplikeret jobudførelse
@@ -217,8 +217,10 @@ de:
217
217
  processes: Prozesse
218
218
  queues: Warteschlangen
219
219
  recurring: Wiederkehrend
220
+ return_to_app: Zurück zur App
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Dunkelmodus umschalten
223
+ toggle_menu: Menü umschalten
222
224
  locks:
223
225
  index:
224
226
  description: Aktive Einzigartigkeitssperren verhindern doppelte Auftragserstellung
@@ -217,8 +217,10 @@ en:
217
217
  processes: Processes
218
218
  queues: Queues
219
219
  recurring: Recurring
220
+ return_to_app: Back to app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Toggle dark mode
223
+ toggle_menu: Toggle menu
222
224
  locks:
223
225
  index:
224
226
  description: Active uniqueness locks preventing duplicate job execution
@@ -217,8 +217,10 @@ es:
217
217
  processes: Procesos
218
218
  queues: Colas
219
219
  recurring: Recurrente
220
+ return_to_app: Volver a la app
220
221
  title: Panel de Pgbus
221
222
  toggle_dark_mode: Alternar modo oscuro
223
+ toggle_menu: Alternar menú
222
224
  locks:
223
225
  index:
224
226
  description: Bloqueos de unicidad activos que impiden la ejecución duplicada del trabajo
@@ -217,8 +217,10 @@ fi:
217
217
  processes: Prosessit
218
218
  queues: Jonot
219
219
  recurring: Toistuva
220
+ return_to_app: Takaisin sovellukseen
220
221
  title: Pgbus-hallintapaneeli
221
222
  toggle_dark_mode: Vaihda tumma tila
223
+ toggle_menu: Vaihda valikko
222
224
  locks:
223
225
  index:
224
226
  description: Aktiiviset ainutlaatuisuuden lukot estävät päällekkäisen työn suorittamisen
@@ -217,8 +217,10 @@ fr:
217
217
  processes: Processus
218
218
  queues: Files d'attente
219
219
  recurring: Récurrent
220
+ return_to_app: Retour à l'application
220
221
  title: Tableau de bord Pgbus
221
222
  toggle_dark_mode: Basculer en mode sombre
223
+ toggle_menu: Basculer le menu
222
224
  locks:
223
225
  index:
224
226
  description: Verrous d'unicité actifs empêchant l'exécution de travaux en double
@@ -217,8 +217,10 @@ it:
217
217
  processes: Processi
218
218
  queues: Code
219
219
  recurring: Ricorrente
220
+ return_to_app: Torna all'app
220
221
  title: Cruscotto Pgbus
221
222
  toggle_dark_mode: Attiva/disattiva modalità scura
223
+ toggle_menu: Attiva/disattiva menu
222
224
  locks:
223
225
  index:
224
226
  description: Blocchi di unicità attivi che impediscono l'esecuzione duplicata del lavoro
@@ -217,8 +217,10 @@ ja:
217
217
  processes: プロセス
218
218
  queues: キュー
219
219
  recurring: 定期実行
220
+ return_to_app: アプリに戻る
220
221
  title: Pgbus ダッシュボード
221
222
  toggle_dark_mode: ダークモード切替
223
+ toggle_menu: メニュー切替
222
224
  locks:
223
225
  index:
224
226
  description: 重複ジョブ実行を防ぐアクティブなユニークロック
@@ -217,8 +217,10 @@ nb:
217
217
  processes: Prosesser
218
218
  queues: Køer
219
219
  recurring: Gjentakende
220
+ return_to_app: Tilbake til appen
220
221
  title: Pgbus-dashbord
221
222
  toggle_dark_mode: Bytt til mørk modus
223
+ toggle_menu: Veksle meny
222
224
  locks:
223
225
  index:
224
226
  description: Aktive unike låser som forhindrer duplisert jobbkjøring
@@ -217,8 +217,10 @@ nl:
217
217
  processes: Processen
218
218
  queues: Wachtrijen
219
219
  recurring: Terugkerend
220
+ return_to_app: Terug naar app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Donkere modus schakelen
223
+ toggle_menu: Menu wisselen
222
224
  locks:
223
225
  index:
224
226
  description: Actieve uniekheidsvergrendelingen voorkomen dubbele taakuitvoering
@@ -217,8 +217,10 @@ pt:
217
217
  processes: Processos
218
218
  queues: Filas
219
219
  recurring: Recorrente
220
+ return_to_app: Voltar ao aplicativo
220
221
  title: Painel Pgbus
221
222
  toggle_dark_mode: Alternar modo escuro
223
+ toggle_menu: Alternar menu
222
224
  locks:
223
225
  index:
224
226
  description: Bloqueios de exclusividade ativos impedindo a execução duplicada do trabalho
@@ -217,8 +217,10 @@ sv:
217
217
  processes: Processer
218
218
  queues: Köer
219
219
  recurring: Återkommande
220
+ return_to_app: Tillbaka till appen
220
221
  title: Pgbus-instrumentpanel
221
222
  toggle_dark_mode: Växla mörkt läge
223
+ toggle_menu: Växla meny
222
224
  locks:
223
225
  index:
224
226
  description: Aktiva unika lås som förhindrar duplicerad jobbexekvering
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ module Pgbus
6
+ # Base class for all Pgbus ActiveRecord models.
7
+ #
8
+ # Lives in lib/pgbus/ so the main Zeitwerk gem loader picks it up
9
+ # regardless of Rails engine boot order. This avoids the NameError
10
+ # that occurs when a host app uses selective railtie requires
11
+ # (require "rails" + individual railties instead of require "rails/all")
12
+ # and the engine's app/models path isn't registered yet.
13
+ class BusRecord < ActiveRecord::Base
14
+ self.abstract_class = true
15
+ end
16
+ end
@@ -64,7 +64,7 @@ module Pgbus
64
64
 
65
65
  # Web dashboard
66
66
  attr_accessor :web_auth, :web_refresh_interval, :web_per_page, :web_live_updates, :web_data_source,
67
- :insights_default_minutes
67
+ :insights_default_minutes, :base_controller_class, :return_to_app_url
68
68
 
69
69
  def initialize
70
70
  @database_url = nil
@@ -136,6 +136,8 @@ module Pgbus
136
136
  @web_live_updates = true
137
137
  @web_data_source = nil
138
138
  @insights_default_minutes = 30 * 24 * 60 # 30 days
139
+ @base_controller_class = "::ActionController::Base"
140
+ @return_to_app_url = nil
139
141
  end
140
142
 
141
143
  def queue_name(name)
@@ -201,7 +203,7 @@ module Pgbus
201
203
  connection_params
202
204
  elsif defined?(ActiveRecord::Base)
203
205
  if connects_to
204
- -> { Pgbus::ApplicationRecord.connection.raw_connection }
206
+ -> { Pgbus::BusRecord.connection.raw_connection }
205
207
  else
206
208
  -> { ActiveRecord::Base.connection.raw_connection }
207
209
  end
data/lib/pgbus/engine.rb CHANGED
@@ -27,7 +27,7 @@ module Pgbus
27
27
 
28
28
  initializer "pgbus.db" do
29
29
  ActiveSupport.on_load(:active_record) do
30
- Pgbus::ApplicationRecord.connects_to(**Pgbus.configuration.connects_to) if Pgbus.configuration.connects_to
30
+ Pgbus::BusRecord.connects_to(**Pgbus.configuration.connects_to) if Pgbus.configuration.connects_to
31
31
  end
32
32
  end
33
33
 
@@ -27,7 +27,7 @@ module Pgbus
27
27
  # that share at least one queue with the given queue list,
28
28
  # excluding the current worker (by PID).
29
29
  def self.max_active_priority(queues, my_pid)
30
- conn = Pgbus.configuration.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
30
+ conn = Pgbus.configuration.connects_to ? Pgbus::BusRecord.connection : ActiveRecord::Base.connection
31
31
  rows = conn.select_all(
32
32
  "SELECT metadata FROM pgbus_processes WHERE kind = 'worker' AND pid != $1 AND last_heartbeat_at > $2",
33
33
  "Pgbus ConsumerPriority",
@@ -180,7 +180,7 @@ module Pgbus
180
180
  batch_size = config.archive_compaction_batch_size || 1000
181
181
  prefix = config.queue_prefix
182
182
 
183
- conn = config.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
183
+ conn = config.connects_to ? Pgbus::BusRecord.connection : ActiveRecord::Base.connection
184
184
  queue_names = conn.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
185
185
 
186
186
  queue_names.each do |full_name|
@@ -77,7 +77,7 @@ module Pgbus
77
77
 
78
78
  def connection
79
79
  if Pgbus.configuration.connects_to
80
- Pgbus::ApplicationRecord.connection
80
+ Pgbus::BusRecord.connection
81
81
  else
82
82
  ActiveRecord::Base.connection
83
83
  end
@@ -191,7 +191,7 @@ module Pgbus
191
191
  dlq_suffix = config.dead_letter_queue_suffix
192
192
  prefix = "#{config.queue_prefix}_"
193
193
 
194
- conn = Pgbus.configuration.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
194
+ conn = Pgbus.configuration.connects_to ? Pgbus::BusRecord.connection : ActiveRecord::Base.connection
195
195
  all_queues = conn.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
196
196
  resolved = all_queues
197
197
  .reject { |q| q.end_with?(dlq_suffix) }
data/lib/pgbus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgbus
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.6"
5
5
  end
@@ -561,7 +561,7 @@ module Pgbus
561
561
  private
562
562
 
563
563
  def connection
564
- Pgbus::ApplicationRecord.connection
564
+ Pgbus::BusRecord.connection
565
565
  end
566
566
 
567
567
  # name is the full PGMQ queue name (already prefixed)