pgbus 0.2.5 → 0.2.7

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -3
  3. data/app/controllers/pgbus/application_controller.rb +8 -2
  4. data/app/controllers/pgbus/queues_controller.rb +6 -0
  5. data/app/helpers/pgbus/application_helper.rb +10 -0
  6. data/app/views/layouts/pgbus/application.html.erb +212 -25
  7. data/app/views/pgbus/dashboard/_processes_table.html.erb +5 -5
  8. data/app/views/pgbus/dashboard/_queues_table.html.erb +6 -6
  9. data/app/views/pgbus/dashboard/_recent_failures.html.erb +4 -4
  10. data/app/views/pgbus/dead_letter/_messages_table.html.erb +1 -1
  11. data/app/views/pgbus/events/index.html.erb +8 -8
  12. data/app/views/pgbus/insights/show.html.erb +31 -29
  13. data/app/views/pgbus/jobs/_enqueued_table.html.erb +1 -1
  14. data/app/views/pgbus/jobs/_failed_table.html.erb +7 -7
  15. data/app/views/pgbus/locks/index.html.erb +7 -7
  16. data/app/views/pgbus/outbox/index.html.erb +7 -7
  17. data/app/views/pgbus/processes/_processes_table.html.erb +7 -7
  18. data/app/views/pgbus/queues/_queues_list.html.erb +12 -8
  19. data/app/views/pgbus/queues/show.html.erb +20 -1
  20. data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +8 -8
  21. data/config/locales/da.yml +18 -0
  22. data/config/locales/de.yml +18 -0
  23. data/config/locales/en.yml +18 -0
  24. data/config/locales/es.yml +18 -0
  25. data/config/locales/fi.yml +18 -0
  26. data/config/locales/fr.yml +18 -0
  27. data/config/locales/it.yml +18 -0
  28. data/config/locales/ja.yml +18 -0
  29. data/config/locales/nb.yml +18 -0
  30. data/config/locales/nl.yml +18 -0
  31. data/config/locales/pt.yml +18 -0
  32. data/config/locales/sv.yml +18 -0
  33. data/config/routes.rb +1 -1
  34. data/lib/pgbus/client.rb +10 -2
  35. data/lib/pgbus/configuration.rb +3 -1
  36. data/lib/pgbus/version.rb +1 -1
  37. data/lib/pgbus/web/data_source.rb +5 -1
  38. metadata +1 -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,
@@ -43,6 +43,10 @@
43
43
  method: :post,
44
44
  class: "text-xs text-red-600 hover:text-red-800 font-medium",
45
45
  data: { turbo_confirm: t("pgbus.queues.queues_list.purge_confirm", name: q[:name]), turbo_frame: "_top" } %>
46
+ <%= button_to t("pgbus.queues.queues_list.delete"), pgbus.queue_path(name: q[:name]),
47
+ method: :delete,
48
+ class: "text-xs text-red-600 hover:text-red-800 font-medium",
49
+ data: { turbo_confirm: t("pgbus.queues.queues_list.delete_confirm", name: q[:name]), turbo_frame: "_top" } %>
46
50
  </td>
47
51
  </tr>
48
52
  <% end %>
@@ -1,6 +1,11 @@
1
1
  <div class="flex items-center justify-between mb-6">
2
2
  <div>
3
- <h1 class="text-2xl font-bold text-gray-900 dark:text-white"><%= @queue&.dig(:name) || params[:name] %></h1>
3
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-white">
4
+ <%= @queue&.dig(:name) || params[:name] %>
5
+ <% if @paused %>
6
+ <span class="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.queues.queues_list.paused") %></span>
7
+ <% end %>
8
+ </h1>
4
9
  <% if @queue %>
5
10
  <p class="text-sm text-gray-500 mt-1">
6
11
  <%= t("pgbus.queues.show.depth") %> <span class="font-mono"><%= @queue[:queue_length] %></span> |
@@ -10,10 +15,24 @@
10
15
  <% end %>
11
16
  </div>
12
17
  <div class="flex space-x-2">
18
+ <% if @paused %>
19
+ <%= button_to t("pgbus.queues.show.resume"), pgbus.resume_queue_path(name: params[:name]),
20
+ method: :post,
21
+ class: "rounded-md bg-green-600 px-3 py-2 text-sm font-medium text-white hover:bg-green-500" %>
22
+ <% else %>
23
+ <%= button_to t("pgbus.queues.show.pause"), pgbus.pause_queue_path(name: params[:name]),
24
+ method: :post,
25
+ class: "rounded-md bg-yellow-500 px-3 py-2 text-sm font-medium text-white hover:bg-yellow-400",
26
+ data: { turbo_confirm: t("pgbus.queues.show.pause_confirm") } %>
27
+ <% end %>
13
28
  <%= button_to t("pgbus.queues.show.purge_queue"), pgbus.purge_queue_path(name: params[:name]),
14
29
  method: :post,
15
30
  class: "rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-500",
16
31
  data: { turbo_confirm: t("pgbus.queues.show.purge_confirm") } %>
32
+ <%= button_to t("pgbus.queues.show.delete_queue"), pgbus.queue_path(name: params[:name]),
33
+ method: :delete,
34
+ class: "rounded-md bg-red-800 px-3 py-2 text-sm font-medium text-white hover:bg-red-700",
35
+ data: { turbo_confirm: t("pgbus.queues.show.delete_confirm") } %>
17
36
  </div>
18
37
  </div>
19
38
 
@@ -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 " \
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  da:
3
3
  pgbus:
4
+ dialogs:
5
+ cancel: Annuller
6
+ confirm: Bekræft
7
+ confirm_title: Er du sikker?
8
+ delete: Slet
9
+ delete_title: Slet
10
+ ok: OK
4
11
  dashboard:
5
12
  processes_table:
6
13
  empty: Ingen processer kører
@@ -217,8 +224,10 @@ da:
217
224
  processes: Processer
218
225
  queues: Køer
219
226
  recurring: Gentagende
227
+ return_to_app: Tilbage til app
220
228
  title: Pgbus Dashboard
221
229
  toggle_dark_mode: Skift til mørk tilstand
230
+ toggle_menu: Skift menu
222
231
  locks:
223
232
  index:
224
233
  description: Aktive unikke låse forhindrer duplikeret jobudførelse
@@ -278,13 +287,19 @@ da:
278
287
  queue: Kø
279
288
  total_ever: Total nogensinde
280
289
  visible: Synlig
290
+ delete: Slet
291
+ delete_confirm: Slet køen %{name} permanent? Dette kan ikke fortrydes.
281
292
  pause: Pause
282
293
  pause_confirm: Pause behandling i %{name}?
283
294
  paused: Pauset
284
295
  purge: Rens
285
296
  purge_confirm: Rens alle beskeder fra %{name}?
286
297
  resume: Genoptag
298
+ destroy:
299
+ success: Køen %{name} slettet.
287
300
  show:
301
+ delete_confirm: Slet denne kø permanent? Dette kan ikke fortrydes.
302
+ delete_queue: Slet kø
288
303
  depth: 'Dybde:'
289
304
  empty: Køen er tom
290
305
  headers:
@@ -293,8 +308,11 @@ da:
293
308
  payload: Indhold
294
309
  reads: Læsninger
295
310
  vt: VT
311
+ pause: Pause
312
+ pause_confirm: Pause behandling?
296
313
  purge_confirm: Rens alle beskeder?
297
314
  purge_queue: Rens kø
315
+ resume: Genoptag
298
316
  total: 'Total:'
299
317
  visible: 'Synlig:'
300
318
  recurring_tasks:
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  de:
3
3
  pgbus:
4
+ dialogs:
5
+ cancel: Abbrechen
6
+ confirm: Bestätigen
7
+ confirm_title: Sind Sie sicher?
8
+ delete: Löschen
9
+ delete_title: Löschen
10
+ ok: OK
4
11
  dashboard:
5
12
  processes_table:
6
13
  empty: Keine Prozesse laufen
@@ -217,8 +224,10 @@ de:
217
224
  processes: Prozesse
218
225
  queues: Warteschlangen
219
226
  recurring: Wiederkehrend
227
+ return_to_app: Zurück zur App
220
228
  title: Pgbus Dashboard
221
229
  toggle_dark_mode: Dunkelmodus umschalten
230
+ toggle_menu: Menü umschalten
222
231
  locks:
223
232
  index:
224
233
  description: Aktive Einzigartigkeitssperren verhindern doppelte Auftragserstellung
@@ -278,13 +287,19 @@ de:
278
287
  queue: Warteschlange
279
288
  total_ever: Insgesamt jemals
280
289
  visible: Sichtbar
290
+ delete: Löschen
291
+ delete_confirm: Warteschlange %{name} dauerhaft löschen? Dies kann nicht rückgängig gemacht werden.
281
292
  pause: Pause
282
293
  pause_confirm: Verarbeitung für %{name} pausieren?
283
294
  paused: Pausiert
284
295
  purge: Bereinigen
285
296
  purge_confirm: Alle Nachrichten von %{name} löschen?
286
297
  resume: Fortsetzen
298
+ destroy:
299
+ success: Warteschlange %{name} gelöscht.
287
300
  show:
301
+ delete_confirm: Diese Warteschlange dauerhaft löschen? Dies kann nicht rückgängig gemacht werden.
302
+ delete_queue: Warteschlange löschen
288
303
  depth: 'Tiefe:'
289
304
  empty: Warteschlange ist leer
290
305
  headers:
@@ -293,8 +308,11 @@ de:
293
308
  payload: Nutzlast
294
309
  reads: Lesevorgänge
295
310
  vt: VT
311
+ pause: Pause
312
+ pause_confirm: Verarbeitung pausieren?
296
313
  purge_confirm: Alle Nachrichten löschen?
297
314
  purge_queue: Warteschlange bereinigen
315
+ resume: Fortsetzen
298
316
  total: 'Insgesamt:'
299
317
  visible: 'Sichtbar:'
300
318
  recurring_tasks:
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  en:
3
3
  pgbus:
4
+ dialogs:
5
+ cancel: Cancel
6
+ confirm: Confirm
7
+ confirm_title: Are you sure?
8
+ delete: Delete
9
+ delete_title: Delete
10
+ ok: OK
4
11
  dashboard:
5
12
  processes_table:
6
13
  empty: No processes running
@@ -217,8 +224,10 @@ en:
217
224
  processes: Processes
218
225
  queues: Queues
219
226
  recurring: Recurring
227
+ return_to_app: Back to app
220
228
  title: Pgbus Dashboard
221
229
  toggle_dark_mode: Toggle dark mode
230
+ toggle_menu: Toggle menu
222
231
  locks:
223
232
  index:
224
233
  description: Active uniqueness locks preventing duplicate job execution
@@ -278,13 +287,19 @@ en:
278
287
  queue: Queue
279
288
  total_ever: Total Ever
280
289
  visible: Visible
290
+ delete: Delete
291
+ delete_confirm: Permanently delete queue %{name}? This cannot be undone.
281
292
  pause: Pause
282
293
  pause_confirm: Pause processing for %{name}?
283
294
  paused: Paused
284
295
  purge: Purge
285
296
  purge_confirm: Purge all messages from %{name}?
286
297
  resume: Resume
298
+ destroy:
299
+ success: Queue %{name} deleted.
287
300
  show:
301
+ delete_confirm: Permanently delete this queue? This cannot be undone.
302
+ delete_queue: Delete Queue
288
303
  depth: 'Depth:'
289
304
  empty: Queue is empty
290
305
  headers:
@@ -293,8 +308,11 @@ en:
293
308
  payload: Payload
294
309
  reads: Reads
295
310
  vt: VT
311
+ pause: Pause
312
+ pause_confirm: Pause processing?
296
313
  purge_confirm: Purge all messages?
297
314
  purge_queue: Purge Queue
315
+ resume: Resume
298
316
  total: 'Total:'
299
317
  visible: 'Visible:'
300
318
  recurring_tasks:
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  es:
3
3
  pgbus:
4
+ dialogs:
5
+ cancel: Cancelar
6
+ confirm: Confirmar
7
+ confirm_title: "¿Estás seguro?"
8
+ delete: Eliminar
9
+ delete_title: Eliminar
10
+ ok: Aceptar
4
11
  dashboard:
5
12
  processes_table:
6
13
  empty: No hay procesos en ejecución
@@ -217,8 +224,10 @@ es:
217
224
  processes: Procesos
218
225
  queues: Colas
219
226
  recurring: Recurrente
227
+ return_to_app: Volver a la app
220
228
  title: Panel de Pgbus
221
229
  toggle_dark_mode: Alternar modo oscuro
230
+ toggle_menu: Alternar menú
222
231
  locks:
223
232
  index:
224
233
  description: Bloqueos de unicidad activos que impiden la ejecución duplicada del trabajo
@@ -278,13 +287,19 @@ es:
278
287
  queue: Cola
279
288
  total_ever: Total acumulado
280
289
  visible: Visible
290
+ delete: Eliminar
291
+ delete_confirm: "¿Eliminar permanentemente la cola %{name}? Esto no se puede deshacer."
281
292
  pause: Pausar
282
293
  pause_confirm: "¿Pausar el procesamiento de %{name}?"
283
294
  paused: Pausado
284
295
  purge: Purgar
285
296
  purge_confirm: "¿Purgar todos los mensajes de %{name}?"
286
297
  resume: Reanudar
298
+ destroy:
299
+ success: Cola %{name} eliminada.
287
300
  show:
301
+ delete_confirm: "¿Eliminar permanentemente esta cola? Esto no se puede deshacer."
302
+ delete_queue: Eliminar cola
288
303
  depth: 'Profundidad:'
289
304
  empty: La cola está vacía
290
305
  headers:
@@ -293,8 +308,11 @@ es:
293
308
  payload: Carga útil
294
309
  reads: Lecturas
295
310
  vt: VT
311
+ pause: Pausar
312
+ pause_confirm: "¿Pausar el procesamiento?"
296
313
  purge_confirm: "¿Purgar todos los mensajes?"
297
314
  purge_queue: Purgar cola
315
+ resume: Reanudar
298
316
  total: 'Total:'
299
317
  visible: 'Visible:'
300
318
  recurring_tasks: