pgbus 0.3.4 → 0.3.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.
- checksums.yaml +4 -4
- data/Rakefile +15 -0
- data/app/controllers/pgbus/recurring_tasks_controller.rb +5 -3
- data/app/frontend/pgbus/application.js +13 -1
- data/app/views/pgbus/queues/show.html.erb +58 -21
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +2 -1
- data/config/locales/da.yml +18 -2
- data/config/locales/de.yml +18 -2
- data/config/locales/en.yml +18 -2
- data/config/locales/es.yml +18 -2
- data/config/locales/fi.yml +18 -2
- data/config/locales/fr.yml +18 -2
- data/config/locales/it.yml +18 -2
- data/config/locales/ja.yml +18 -2
- data/config/locales/nb.yml +18 -2
- data/config/locales/nl.yml +18 -2
- data/config/locales/pt.yml +18 -2
- data/config/locales/sv.yml +18 -2
- data/lib/pgbus/recurring/schedule.rb +31 -14
- data/lib/pgbus/recurring/scheduler.rb +1 -4
- data/lib/pgbus/stat_buffer.rb +26 -11
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd4586e94a76eaf20987039101a038af4e64a9d3bcfd5d68b5b2ef984f3459f3
|
|
4
|
+
data.tar.gz: 98f2e88891c3e7f00963d697d4fc135271a61eccef04aa89dbfb40e0d568b240
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2df68ef64f8b4ac37dfe509c800688c51e811910855f69807c28a5e3777d0eff10dc989ea4e0180b17ee9feada2549a6faa182052c2bbf88be225ccfa7a45d2
|
|
7
|
+
data.tar.gz: 4890d5ab0a55df38ba8d74bb704c00a6d82a4abe17784600e19615288b6dfebec50ac07ac7a89a0cdffa95c85299b795071d4a5dbcc9d351bac076a5baca8bc9
|
data/Rakefile
CHANGED
|
@@ -176,4 +176,19 @@ task :release, %i[version force] do |_t, args|
|
|
|
176
176
|
puts " • Upload assets to the release"
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
+
namespace :dummy do
|
|
180
|
+
desc "Start dummy app with stub data for dashboard QA (PORT=3003, no database required)"
|
|
181
|
+
task :server do
|
|
182
|
+
port = ENV.fetch("PORT", "3003")
|
|
183
|
+
ENV["PGBUS_STUB_DATA"] = "1"
|
|
184
|
+
ENV["RAILS_ENV"] = "development"
|
|
185
|
+
|
|
186
|
+
puts "\n\e[32m→ Starting dummy app with stub data at http://localhost:#{port}/pgbus\e[0m"
|
|
187
|
+
puts " Dashboard: http://localhost:#{port}/pgbus"
|
|
188
|
+
puts " Using stub data source (no database needed)"
|
|
189
|
+
puts " Press Ctrl+C to stop\n\n"
|
|
190
|
+
sh("bundle exec puma spec/dummy/config.ru -p #{port}")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
179
194
|
task default: %i[spec rubocop]
|
|
@@ -18,10 +18,12 @@ module Pgbus
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def toggle
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
result = data_source.toggle_recurring_task(params[:id])
|
|
22
|
+
if result
|
|
23
|
+
message = result == :enabled ? t("pgbus.recurring_tasks.toggle.enabled") : t("pgbus.recurring_tasks.toggle.disabled")
|
|
24
|
+
redirect_to pgbus.recurring_tasks_path, notice: message
|
|
23
25
|
else
|
|
24
|
-
redirect_to pgbus.recurring_tasks_path, alert: "
|
|
26
|
+
redirect_to pgbus.recurring_tasks_path, alert: t("pgbus.recurring_tasks.toggle.failed")
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -114,12 +114,24 @@ initBulkSelect();
|
|
|
114
114
|
document.addEventListener("turbo:load", initBulkSelect);
|
|
115
115
|
document.addEventListener("turbo:frame-load", initBulkSelect);
|
|
116
116
|
|
|
117
|
-
// -- Auto-refresh --
|
|
117
|
+
// -- Auto-refresh (pauses when user is interacting) --
|
|
118
118
|
const refreshInterval = parseInt(document.body?.dataset.pgbusRefreshInterval || "0", 10);
|
|
119
119
|
if (refreshInterval > 0) {
|
|
120
120
|
let timer;
|
|
121
|
+
|
|
122
|
+
function hasUserInteraction() {
|
|
123
|
+
// Pause when any checkbox is checked
|
|
124
|
+
const checked = document.querySelector("input[data-bulk-item]:checked, input[data-bulk-select-all]:checked");
|
|
125
|
+
if (checked) return true;
|
|
126
|
+
// Pause when any job detail row is expanded
|
|
127
|
+
const openDetails = document.querySelector("turbo-frame[data-auto-refresh] details[open]");
|
|
128
|
+
if (openDetails) return true;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
121
132
|
function refreshFrames() {
|
|
122
133
|
if (document.hidden) return;
|
|
134
|
+
if (hasUserInteraction()) return;
|
|
123
135
|
document.querySelectorAll("turbo-frame[data-auto-refresh]")
|
|
124
136
|
.forEach(frame => {
|
|
125
137
|
try {
|
|
@@ -38,41 +38,78 @@
|
|
|
38
38
|
|
|
39
39
|
<!-- Messages in Queue -->
|
|
40
40
|
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
41
|
-
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
41
|
+
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
42
42
|
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
43
43
|
<tr>
|
|
44
44
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.id") %></th>
|
|
45
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.job_class") %></th>
|
|
45
46
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.enqueued") %></th>
|
|
46
47
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.reads") %></th>
|
|
47
48
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.vt") %></th>
|
|
48
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.payload") %></th>
|
|
49
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.show.headers.actions") %></th>
|
|
50
49
|
</tr>
|
|
51
50
|
</thead>
|
|
52
51
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
53
52
|
<% @messages.each do |m| %>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<td class="
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
<% payload = pgbus_parse_message(m[:message]) %>
|
|
54
|
+
<tr>
|
|
55
|
+
<td colspan="5" class="p-0">
|
|
56
|
+
<details class="group">
|
|
57
|
+
<summary class="flex cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900 list-none">
|
|
58
|
+
<span class="w-16 shrink-0 px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= m[:msg_id] %></span>
|
|
59
|
+
<span class="flex-1 px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= payload["job_class"] || "—" %></span>
|
|
60
|
+
<span class="w-28 shrink-0 px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:enqueued_at]) %></span>
|
|
61
|
+
<span class="w-16 shrink-0 px-4 py-3 text-sm text-gray-500"><%= m[:read_ct] %></span>
|
|
62
|
+
<span class="w-28 shrink-0 px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:vt]) %></span>
|
|
63
|
+
</summary>
|
|
64
|
+
<div class="px-4 pb-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100">
|
|
65
|
+
<div class="flex items-center mt-3 mb-3">
|
|
66
|
+
<span class="text-xs font-mono text-gray-400"><%= t("pgbus.queues.show.job_id") %> <%= payload["job_id"] %></span>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="grid grid-cols-2 gap-4 mb-3">
|
|
69
|
+
<div>
|
|
70
|
+
<span class="text-xs font-medium text-gray-500"><%= t("pgbus.queues.show.arguments") %></span>
|
|
71
|
+
<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>
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
<span class="text-xs font-medium text-gray-500"><%= t("pgbus.queues.show.metadata") %></span>
|
|
75
|
+
<div class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 space-y-1">
|
|
76
|
+
<% if payload["queue_name"] %><p><strong><%= t("pgbus.queues.show.metadata_labels.queue") %></strong> <%= payload["queue_name"] %></p><% end %>
|
|
77
|
+
<% if payload["priority"] %><p><strong><%= t("pgbus.queues.show.metadata_labels.priority") %></strong> <%= payload["priority"] %></p><% end %>
|
|
78
|
+
<% if payload["locale"] %><p><strong><%= t("pgbus.queues.show.metadata_labels.locale") %></strong> <%= payload["locale"] %></p><% end %>
|
|
79
|
+
<% if payload["timezone"] %><p><strong><%= t("pgbus.queues.show.metadata_labels.timezone") %></strong> <%= payload["timezone"] %></p><% end %>
|
|
80
|
+
<% if payload["scheduled_at"] %><p><strong><%= t("pgbus.queues.show.metadata_labels.scheduled") %></strong> <%= payload["scheduled_at"] %></p><% end %>
|
|
81
|
+
<% if m[:vt] %><p><strong><%= t("pgbus.queues.show.metadata_labels.visible_at") %></strong> <%= m[:vt] %></p><% end %>
|
|
82
|
+
<% if m[:last_read_at] %><p><strong><%= t("pgbus.queues.show.metadata_labels.last_read") %></strong> <%= m[:last_read_at] %></p><% end %>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<details class="mt-2">
|
|
87
|
+
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700"><%= t("pgbus.queues.show.full_json_payload") %></summary>
|
|
88
|
+
<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>
|
|
89
|
+
</details>
|
|
90
|
+
<% if m[:headers] %>
|
|
91
|
+
<details class="mt-2">
|
|
92
|
+
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700"><%= t("pgbus.queues.show.headers_section") %></summary>
|
|
93
|
+
<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>
|
|
94
|
+
</details>
|
|
95
|
+
<% end %>
|
|
96
|
+
<div class="flex space-x-2 mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
|
97
|
+
<%= button_to t("pgbus.queues.show.retry"), pgbus.retry_message_queue_path(name: params[:name], msg_id: m[:msg_id]),
|
|
98
|
+
method: :post,
|
|
99
|
+
class: "rounded-md bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-indigo-500",
|
|
100
|
+
data: { turbo_confirm: t("pgbus.queues.show.retry_confirm") } %>
|
|
101
|
+
<%= button_to t("pgbus.queues.show.discard"), pgbus.discard_message_queue_path(name: params[:name], msg_id: m[:msg_id]),
|
|
102
|
+
method: :post,
|
|
103
|
+
class: "rounded-md bg-red-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-red-500",
|
|
104
|
+
data: { turbo_confirm: t("pgbus.queues.show.discard_confirm") } %>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</details>
|
|
71
108
|
</td>
|
|
72
109
|
</tr>
|
|
73
110
|
<% end %>
|
|
74
111
|
<% if @messages.empty? %>
|
|
75
|
-
<tr><td colspan="
|
|
112
|
+
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.queues.show.empty") %></td></tr>
|
|
76
113
|
<% end %>
|
|
77
114
|
</tbody>
|
|
78
115
|
</table>
|
|
@@ -24,7 +24,8 @@
|
|
|
24
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
|
-
class: "text-sm font-medium text-blue-600 hover:text-blue-800"
|
|
27
|
+
class: "text-sm font-medium text-blue-600 hover:text-blue-800",
|
|
28
|
+
data: { turbo_frame: "_top" } %>
|
|
28
29
|
</div>
|
|
29
30
|
<div class="text-xs text-gray-500"><%= task[:class_name] || task[:command]&.truncate(40) %></div>
|
|
30
31
|
<% if task[:description] %>
|
data/config/locales/da.yml
CHANGED
|
@@ -301,23 +301,35 @@ da:
|
|
|
301
301
|
purge_confirm: Rens alle beskeder fra %{name}?
|
|
302
302
|
resume: Genoptag
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumenter
|
|
304
305
|
delete_confirm: Slet denne kø permanent? Dette kan ikke fortrydes.
|
|
305
306
|
delete_queue: Slet kø
|
|
306
307
|
depth: 'Dybde:'
|
|
307
308
|
discard: Kassér
|
|
308
309
|
discard_confirm: Kassér denne besked?
|
|
309
310
|
empty: Køen er tom
|
|
311
|
+
full_json_payload: Fuld JSON-payload
|
|
310
312
|
headers:
|
|
311
|
-
actions: Handlinger
|
|
312
313
|
enqueued: Sat i kø
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Jobklasse
|
|
315
316
|
reads: Læsninger
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Headers
|
|
319
|
+
job_id: 'Job ID:'
|
|
317
320
|
message_discard_failed: Kunne ikke kassere besked.
|
|
318
321
|
message_discarded: Besked kasseret.
|
|
319
322
|
message_retried: Beskedens synlighed nulstillet.
|
|
320
323
|
message_retry_failed: Kunne ikke prøve besked igen.
|
|
324
|
+
metadata: Metadata
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Sidst læst:'
|
|
327
|
+
locale: 'Locale:'
|
|
328
|
+
priority: 'Prioritet:'
|
|
329
|
+
queue: 'Kø:'
|
|
330
|
+
scheduled: 'Planlagt:'
|
|
331
|
+
timezone: 'Tidszone:'
|
|
332
|
+
visible_at: 'Synlig fra:'
|
|
321
333
|
pause: Pause
|
|
322
334
|
pause_confirm: Pause behandling?
|
|
323
335
|
purge_confirm: Rens alle beskeder?
|
|
@@ -333,6 +345,10 @@ da:
|
|
|
333
345
|
one: "%{count} opgave konfigureret"
|
|
334
346
|
other: "%{count} opgaver konfigureret"
|
|
335
347
|
title: Gentagne opgaver
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Opgave deaktiveret
|
|
350
|
+
enabled: Opgave aktiveret
|
|
351
|
+
failed: Kunne ikke ændre opgave
|
|
336
352
|
show:
|
|
337
353
|
back: Tilbage
|
|
338
354
|
configuration: Konfiguration
|
data/config/locales/de.yml
CHANGED
|
@@ -301,23 +301,35 @@ de:
|
|
|
301
301
|
purge_confirm: Alle Nachrichten von %{name} löschen?
|
|
302
302
|
resume: Fortsetzen
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumente
|
|
304
305
|
delete_confirm: Diese Warteschlange dauerhaft löschen? Dies kann nicht rückgängig gemacht werden.
|
|
305
306
|
delete_queue: Warteschlange löschen
|
|
306
307
|
depth: 'Tiefe:'
|
|
307
308
|
discard: Verwerfen
|
|
308
309
|
discard_confirm: Diese Nachricht verwerfen?
|
|
309
310
|
empty: Warteschlange ist leer
|
|
311
|
+
full_json_payload: Vollständige JSON-Nutzlast
|
|
310
312
|
headers:
|
|
311
|
-
actions: Aktionen
|
|
312
313
|
enqueued: Eingereiht
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Job-Klasse
|
|
315
316
|
reads: Lesevorgänge
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Header
|
|
319
|
+
job_id: 'Job-ID:'
|
|
317
320
|
message_discard_failed: Nachricht konnte nicht verworfen werden.
|
|
318
321
|
message_discarded: Nachricht verworfen.
|
|
319
322
|
message_retried: Sichtbarkeit der Nachricht zurückgesetzt.
|
|
320
323
|
message_retry_failed: Nachricht konnte nicht wiederholt werden.
|
|
324
|
+
metadata: Metadaten
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Letzter Lesevorgang:'
|
|
327
|
+
locale: 'Sprache:'
|
|
328
|
+
priority: 'Priorität:'
|
|
329
|
+
queue: 'Warteschlange:'
|
|
330
|
+
scheduled: 'Geplant:'
|
|
331
|
+
timezone: 'Zeitzone:'
|
|
332
|
+
visible_at: 'Sichtbar ab:'
|
|
321
333
|
pause: Pause
|
|
322
334
|
pause_confirm: Verarbeitung pausieren?
|
|
323
335
|
purge_confirm: Alle Nachrichten löschen?
|
|
@@ -333,6 +345,10 @@ de:
|
|
|
333
345
|
one: "%{count} Aufgabe konfiguriert"
|
|
334
346
|
other: "%{count} Aufgaben konfiguriert"
|
|
335
347
|
title: Wiederkehrende Aufgaben
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Aufgabe deaktiviert
|
|
350
|
+
enabled: Aufgabe aktiviert
|
|
351
|
+
failed: Aufgabe konnte nicht umgeschaltet werden
|
|
336
352
|
show:
|
|
337
353
|
back: Zurück
|
|
338
354
|
configuration: Konfiguration
|
data/config/locales/en.yml
CHANGED
|
@@ -347,23 +347,35 @@ en:
|
|
|
347
347
|
purge_confirm: Purge all messages from %{name}?
|
|
348
348
|
resume: Resume
|
|
349
349
|
show:
|
|
350
|
+
arguments: Arguments
|
|
350
351
|
delete_confirm: Permanently delete this queue? This cannot be undone.
|
|
351
352
|
delete_queue: Delete Queue
|
|
352
353
|
depth: 'Depth:'
|
|
353
354
|
discard: Discard
|
|
354
355
|
discard_confirm: Discard this message?
|
|
355
356
|
empty: Queue is empty
|
|
357
|
+
full_json_payload: Full JSON payload
|
|
356
358
|
headers:
|
|
357
|
-
actions: Actions
|
|
358
359
|
enqueued: Enqueued
|
|
359
360
|
id: ID
|
|
360
|
-
|
|
361
|
+
job_class: Job Class
|
|
361
362
|
reads: Reads
|
|
362
363
|
vt: VT
|
|
364
|
+
headers_section: Headers
|
|
365
|
+
job_id: 'Job ID:'
|
|
363
366
|
message_discard_failed: Could not discard message.
|
|
364
367
|
message_discarded: Message discarded.
|
|
365
368
|
message_retried: Message visibility reset.
|
|
366
369
|
message_retry_failed: Could not retry message.
|
|
370
|
+
metadata: Metadata
|
|
371
|
+
metadata_labels:
|
|
372
|
+
last_read: 'Last read:'
|
|
373
|
+
locale: 'Locale:'
|
|
374
|
+
priority: 'Priority:'
|
|
375
|
+
queue: 'Queue:'
|
|
376
|
+
scheduled: 'Scheduled at:'
|
|
377
|
+
timezone: 'Timezone:'
|
|
378
|
+
visible_at: 'Visible at:'
|
|
367
379
|
pause: Pause
|
|
368
380
|
pause_confirm: Pause processing?
|
|
369
381
|
purge_confirm: Purge all messages?
|
|
@@ -379,6 +391,10 @@ en:
|
|
|
379
391
|
one: "%{count} task configured"
|
|
380
392
|
other: "%{count} tasks configured"
|
|
381
393
|
title: Recurring Tasks
|
|
394
|
+
toggle:
|
|
395
|
+
disabled: Task disabled
|
|
396
|
+
enabled: Task enabled
|
|
397
|
+
failed: Failed to toggle task
|
|
382
398
|
show:
|
|
383
399
|
back: Back
|
|
384
400
|
configuration: Configuration
|
data/config/locales/es.yml
CHANGED
|
@@ -301,23 +301,35 @@ es:
|
|
|
301
301
|
purge_confirm: "¿Purgar todos los mensajes de %{name}?"
|
|
302
302
|
resume: Reanudar
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumentos
|
|
304
305
|
delete_confirm: "¿Eliminar permanentemente esta cola? Esto no se puede deshacer."
|
|
305
306
|
delete_queue: Eliminar cola
|
|
306
307
|
depth: 'Profundidad:'
|
|
307
308
|
discard: Descartar
|
|
308
309
|
discard_confirm: "¿Descartar este mensaje?"
|
|
309
310
|
empty: La cola está vacía
|
|
311
|
+
full_json_payload: Carga JSON completa
|
|
310
312
|
headers:
|
|
311
|
-
actions: Acciones
|
|
312
313
|
enqueued: Encolado
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Clase de Trabajo
|
|
315
316
|
reads: Lecturas
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Encabezados
|
|
319
|
+
job_id: 'ID del Trabajo:'
|
|
317
320
|
message_discard_failed: No se pudo descartar el mensaje.
|
|
318
321
|
message_discarded: Mensaje descartado.
|
|
319
322
|
message_retried: Visibilidad del mensaje restablecida.
|
|
320
323
|
message_retry_failed: No se pudo reintentar el mensaje.
|
|
324
|
+
metadata: Metadatos
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Última lectura:'
|
|
327
|
+
locale: 'Configuración regional:'
|
|
328
|
+
priority: 'Prioridad:'
|
|
329
|
+
queue: 'Cola:'
|
|
330
|
+
scheduled: 'Programado:'
|
|
331
|
+
timezone: 'Zona horaria:'
|
|
332
|
+
visible_at: 'Visible en:'
|
|
321
333
|
pause: Pausar
|
|
322
334
|
pause_confirm: "¿Pausar el procesamiento?"
|
|
323
335
|
purge_confirm: "¿Purgar todos los mensajes?"
|
|
@@ -333,6 +345,10 @@ es:
|
|
|
333
345
|
one: Tarea %{count} configurada
|
|
334
346
|
other: Tareas %{count} configuradas
|
|
335
347
|
title: Tareas recurrentes
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Tarea deshabilitada
|
|
350
|
+
enabled: Tarea habilitada
|
|
351
|
+
failed: No se pudo cambiar la tarea
|
|
336
352
|
show:
|
|
337
353
|
back: Atrás
|
|
338
354
|
configuration: Configuración
|
data/config/locales/fi.yml
CHANGED
|
@@ -301,23 +301,35 @@ fi:
|
|
|
301
301
|
purge_confirm: Tyhjennä kaikki viestit %{name}:sta?
|
|
302
302
|
resume: Jatka
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumentit
|
|
304
305
|
delete_confirm: Poista tämä jono pysyvästi? Tätä ei voi kumota.
|
|
305
306
|
delete_queue: Poista jono
|
|
306
307
|
depth: 'Syvyys:'
|
|
307
308
|
discard: Hylkää
|
|
308
309
|
discard_confirm: Hylätä tämä viesti?
|
|
309
310
|
empty: Jono on tyhjä
|
|
311
|
+
full_json_payload: Täysi JSON-payload
|
|
310
312
|
headers:
|
|
311
|
-
actions: Toiminnot
|
|
312
313
|
enqueued: Jonoon lisätty
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Työn luokka
|
|
315
316
|
reads: Lukukerrat
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Otsikot
|
|
319
|
+
job_id: 'Työn ID:'
|
|
317
320
|
message_discard_failed: Viestiä ei voitu hylätä.
|
|
318
321
|
message_discarded: Viesti hylätty.
|
|
319
322
|
message_retried: Viestin näkyvyys nollattu.
|
|
320
323
|
message_retry_failed: Viestiä ei voitu yrittää uudelleen.
|
|
324
|
+
metadata: Metatiedot
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Viimeksi luettu:'
|
|
327
|
+
locale: 'Alue:'
|
|
328
|
+
priority: 'Prioriteetti:'
|
|
329
|
+
queue: 'Jono:'
|
|
330
|
+
scheduled: 'Aikataulutettu:'
|
|
331
|
+
timezone: 'Aikavyöhyke:'
|
|
332
|
+
visible_at: 'Näkyvissä:'
|
|
321
333
|
pause: Tauko
|
|
322
334
|
pause_confirm: Keskeytetäänkö käsittely?
|
|
323
335
|
purge_confirm: Tyhjennetäänkö kaikki viestit?
|
|
@@ -333,6 +345,10 @@ fi:
|
|
|
333
345
|
one: "%{count} tehtävä määritetty"
|
|
334
346
|
other: "%{count} tehtävää määritetty"
|
|
335
347
|
title: Toistuvat tehtävät
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Tehtävä poistettu käytöstä
|
|
350
|
+
enabled: Tehtävä otettu käyttöön
|
|
351
|
+
failed: Tehtävän vaihto epäonnistui
|
|
336
352
|
show:
|
|
337
353
|
back: Takaisin
|
|
338
354
|
configuration: Asetukset
|
data/config/locales/fr.yml
CHANGED
|
@@ -301,23 +301,35 @@ fr:
|
|
|
301
301
|
purge_confirm: Purger tous les messages de %{name} ?
|
|
302
302
|
resume: Reprendre
|
|
303
303
|
show:
|
|
304
|
+
arguments: Arguments
|
|
304
305
|
delete_confirm: Supprimer définitivement cette file d'attente ? Cette action est irréversible.
|
|
305
306
|
delete_queue: Supprimer la file d'attente
|
|
306
307
|
depth: 'Profondeur :'
|
|
307
308
|
discard: Rejeter
|
|
308
309
|
discard_confirm: Rejeter ce message ?
|
|
309
310
|
empty: La file d'attente est vide
|
|
311
|
+
full_json_payload: Charge utile JSON complète
|
|
310
312
|
headers:
|
|
311
|
-
actions: Actions
|
|
312
313
|
enqueued: Enregistré
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Classe de travail
|
|
315
316
|
reads: Lectures
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: En-têtes
|
|
319
|
+
job_id: 'ID du travail :'
|
|
317
320
|
message_discard_failed: Impossible de rejeter le message.
|
|
318
321
|
message_discarded: Message rejeté.
|
|
319
322
|
message_retried: Visibilité du message réinitialisée.
|
|
320
323
|
message_retry_failed: Impossible de réessayer le message.
|
|
324
|
+
metadata: Métadonnées
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Dernière lecture :'
|
|
327
|
+
locale: 'Langue :'
|
|
328
|
+
priority: 'Priorité :'
|
|
329
|
+
queue: 'File d''attente :'
|
|
330
|
+
scheduled: 'Planifié :'
|
|
331
|
+
timezone: 'Fuseau horaire :'
|
|
332
|
+
visible_at: 'Visible à :'
|
|
321
333
|
pause: Pause
|
|
322
334
|
pause_confirm: Mettre en pause le traitement ?
|
|
323
335
|
purge_confirm: Purger tous les messages ?
|
|
@@ -333,6 +345,10 @@ fr:
|
|
|
333
345
|
one: Tâche %{count} configurée
|
|
334
346
|
other: Tâches %{count} configurées
|
|
335
347
|
title: Tâches récurrentes
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Tâche désactivée
|
|
350
|
+
enabled: Tâche activée
|
|
351
|
+
failed: Impossible de basculer la tâche
|
|
336
352
|
show:
|
|
337
353
|
back: Retour
|
|
338
354
|
configuration: Configuration
|
data/config/locales/it.yml
CHANGED
|
@@ -301,23 +301,35 @@ it:
|
|
|
301
301
|
purge_confirm: Eliminare tutti i messaggi da %{name}?
|
|
302
302
|
resume: Riprendi
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argomenti
|
|
304
305
|
delete_confirm: Eliminare permanentemente questa coda? Questa azione non può essere annullata.
|
|
305
306
|
delete_queue: Elimina coda
|
|
306
307
|
depth: 'Profondità:'
|
|
307
308
|
discard: Scarta
|
|
308
309
|
discard_confirm: Scartare questo messaggio?
|
|
309
310
|
empty: La coda è vuota
|
|
311
|
+
full_json_payload: Payload JSON completo
|
|
310
312
|
headers:
|
|
311
|
-
actions: Azioni
|
|
312
313
|
enqueued: In coda
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Classe Lavoro
|
|
315
316
|
reads: Letture
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Intestazioni
|
|
319
|
+
job_id: 'ID Lavoro:'
|
|
317
320
|
message_discard_failed: Impossibile scartare il messaggio.
|
|
318
321
|
message_discarded: Messaggio scartato.
|
|
319
322
|
message_retried: Visibilità del messaggio reimpostata.
|
|
320
323
|
message_retry_failed: Impossibile riprovare il messaggio.
|
|
324
|
+
metadata: Metadati
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Ultima lettura:'
|
|
327
|
+
locale: 'Locale:'
|
|
328
|
+
priority: 'Priorità:'
|
|
329
|
+
queue: 'Coda:'
|
|
330
|
+
scheduled: 'Programmato:'
|
|
331
|
+
timezone: 'Fuso orario:'
|
|
332
|
+
visible_at: 'Visibile alle:'
|
|
321
333
|
pause: Pausa
|
|
322
334
|
pause_confirm: Mettere in pausa l'elaborazione?
|
|
323
335
|
purge_confirm: Eliminare tutti i messaggi?
|
|
@@ -333,6 +345,10 @@ it:
|
|
|
333
345
|
one: "%{count} attività configurata"
|
|
334
346
|
other: "%{count} attività configurate"
|
|
335
347
|
title: Attività ricorrenti
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Attività disabilitata
|
|
350
|
+
enabled: Attività abilitata
|
|
351
|
+
failed: Impossibile cambiare lo stato dell'attività
|
|
336
352
|
show:
|
|
337
353
|
back: Indietro
|
|
338
354
|
configuration: Configurazione
|
data/config/locales/ja.yml
CHANGED
|
@@ -301,23 +301,35 @@ ja:
|
|
|
301
301
|
purge_confirm: "%{name} からすべてのメッセージを削除しますか?"
|
|
302
302
|
resume: 再開
|
|
303
303
|
show:
|
|
304
|
+
arguments: 引数
|
|
304
305
|
delete_confirm: このキューを完全に削除しますか?この操作は取り消せません。
|
|
305
306
|
delete_queue: キューを削除
|
|
306
307
|
depth: 深さ:
|
|
307
308
|
discard: 破棄
|
|
308
309
|
discard_confirm: このメッセージを破棄しますか?
|
|
309
310
|
empty: キューは空です
|
|
311
|
+
full_json_payload: 完全なJSONペイロード
|
|
310
312
|
headers:
|
|
311
|
-
actions: アクション
|
|
312
313
|
enqueued: エンキュー済み
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: ジョブクラス
|
|
315
316
|
reads: 読み取り回数
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: ヘッダー
|
|
319
|
+
job_id: ジョブID:
|
|
317
320
|
message_discard_failed: メッセージを破棄できませんでした。
|
|
318
321
|
message_discarded: メッセージを破棄しました。
|
|
319
322
|
message_retried: メッセージの可視性をリセットしました。
|
|
320
323
|
message_retry_failed: メッセージをリトライできませんでした。
|
|
324
|
+
metadata: メタデータ
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 最終読み取り:
|
|
327
|
+
locale: ロケール:
|
|
328
|
+
priority: 優先度:
|
|
329
|
+
queue: キュー:
|
|
330
|
+
scheduled: スケジュール済み:
|
|
331
|
+
timezone: タイムゾーン:
|
|
332
|
+
visible_at: 表示可能日時:
|
|
321
333
|
pause: 一時停止
|
|
322
334
|
pause_confirm: 処理を一時停止しますか?
|
|
323
335
|
purge_confirm: すべてのメッセージを削除しますか?
|
|
@@ -333,6 +345,10 @@ ja:
|
|
|
333
345
|
one: "%{count} タスクが設定されています"
|
|
334
346
|
other: "%{count} タスクが設定されています"
|
|
335
347
|
title: 定期タスク
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: タスクが無効になりました
|
|
350
|
+
enabled: タスクが有効になりました
|
|
351
|
+
failed: タスクの切り替えに失敗しました
|
|
336
352
|
show:
|
|
337
353
|
back: 戻る
|
|
338
354
|
configuration: 設定
|
data/config/locales/nb.yml
CHANGED
|
@@ -301,23 +301,35 @@ nb:
|
|
|
301
301
|
purge_confirm: Rens alle meldinger fra %{name}?
|
|
302
302
|
resume: Gjenoppta
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumenter
|
|
304
305
|
delete_confirm: Slette denne køen permanent? Dette kan ikke angres.
|
|
305
306
|
delete_queue: Slett kø
|
|
306
307
|
depth: 'Dybde:'
|
|
307
308
|
discard: Forkast
|
|
308
309
|
discard_confirm: Forkaste denne meldingen?
|
|
309
310
|
empty: Køen er tom
|
|
311
|
+
full_json_payload: Full JSON-payload
|
|
310
312
|
headers:
|
|
311
|
-
actions: Handlinger
|
|
312
313
|
enqueued: I kø
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Jobbklasse
|
|
315
316
|
reads: Lesninger
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Overskrifter
|
|
319
|
+
job_id: 'Jobb-ID:'
|
|
317
320
|
message_discard_failed: Kunne ikke forkaste meldingen.
|
|
318
321
|
message_discarded: Melding forkastet.
|
|
319
322
|
message_retried: Meldingens synlighet tilbakestilt.
|
|
320
323
|
message_retry_failed: Kunne ikke prøve meldingen igjen.
|
|
324
|
+
metadata: Metadata
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Sist lest:'
|
|
327
|
+
locale: 'Språk:'
|
|
328
|
+
priority: 'Prioritet:'
|
|
329
|
+
queue: 'Kø:'
|
|
330
|
+
scheduled: 'Planlagt:'
|
|
331
|
+
timezone: 'Tidssone:'
|
|
332
|
+
visible_at: 'Synlig fra:'
|
|
321
333
|
pause: Pause
|
|
322
334
|
pause_confirm: Pause behandling?
|
|
323
335
|
purge_confirm: Rens alle meldinger?
|
|
@@ -333,6 +345,10 @@ nb:
|
|
|
333
345
|
one: "%{count} oppgave konfigurert"
|
|
334
346
|
other: "%{count} oppgaver konfigurert"
|
|
335
347
|
title: Gjentakende oppgaver
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Oppgave deaktivert
|
|
350
|
+
enabled: Oppgave aktivert
|
|
351
|
+
failed: Kunne ikke endre oppgave
|
|
336
352
|
show:
|
|
337
353
|
back: Tilbake
|
|
338
354
|
configuration: Konfigurasjon
|
data/config/locales/nl.yml
CHANGED
|
@@ -301,23 +301,35 @@ nl:
|
|
|
301
301
|
purge_confirm: Alle berichten verwijderen uit %{name}?
|
|
302
302
|
resume: Hervatten
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumenten
|
|
304
305
|
delete_confirm: Deze wachtrij permanent verwijderen? Dit kan niet ongedaan worden gemaakt.
|
|
305
306
|
delete_queue: Wachtrij verwijderen
|
|
306
307
|
depth: 'Diepte:'
|
|
307
308
|
discard: Verwerpen
|
|
308
309
|
discard_confirm: Dit bericht verwerpen?
|
|
309
310
|
empty: Wachtrij is leeg
|
|
311
|
+
full_json_payload: Volledige JSON payload
|
|
310
312
|
headers:
|
|
311
|
-
actions: Acties
|
|
312
313
|
enqueued: In de wachtrij geplaatst
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Taakklasse
|
|
315
316
|
reads: Lezingen
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Headers
|
|
319
|
+
job_id: 'Taak ID:'
|
|
317
320
|
message_discard_failed: Kon bericht niet verwerpen.
|
|
318
321
|
message_discarded: Bericht verworpen.
|
|
319
322
|
message_retried: Zichtbaarheid van bericht gereset.
|
|
320
323
|
message_retry_failed: Kon bericht niet opnieuw proberen.
|
|
324
|
+
metadata: Metadata
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Laatst gelezen:'
|
|
327
|
+
locale: 'Locale:'
|
|
328
|
+
priority: 'Prioriteit:'
|
|
329
|
+
queue: 'Wachtrij:'
|
|
330
|
+
scheduled: 'Gepland:'
|
|
331
|
+
timezone: 'Tijdzone:'
|
|
332
|
+
visible_at: 'Zichtbaar op:'
|
|
321
333
|
pause: Pauzeren
|
|
322
334
|
pause_confirm: Verwerking pauzeren?
|
|
323
335
|
purge_confirm: Alle berichten verwijderen?
|
|
@@ -333,6 +345,10 @@ nl:
|
|
|
333
345
|
one: "%{count} taak geconfigureerd"
|
|
334
346
|
other: "%{count} taken geconfigureerd"
|
|
335
347
|
title: Terugkerende taken
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Taak uitgeschakeld
|
|
350
|
+
enabled: Taak ingeschakeld
|
|
351
|
+
failed: Kon taak niet omschakelen
|
|
336
352
|
show:
|
|
337
353
|
back: Terug
|
|
338
354
|
configuration: Configuratie
|
data/config/locales/pt.yml
CHANGED
|
@@ -301,23 +301,35 @@ pt:
|
|
|
301
301
|
purge_confirm: Limpar todas as mensagens de %{name}?
|
|
302
302
|
resume: Retomar
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argumentos
|
|
304
305
|
delete_confirm: Excluir permanentemente esta fila? Esta ação não pode ser desfeita.
|
|
305
306
|
delete_queue: Excluir fila
|
|
306
307
|
depth: 'Profundidade:'
|
|
307
308
|
discard: Descartar
|
|
308
309
|
discard_confirm: Descartar esta mensagem?
|
|
309
310
|
empty: Fila está vazia
|
|
311
|
+
full_json_payload: Carga JSON completa
|
|
310
312
|
headers:
|
|
311
|
-
actions: Ações
|
|
312
313
|
enqueued: Enfileirado
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Classe do Trabalho
|
|
315
316
|
reads: Leituras
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Cabeçalhos
|
|
319
|
+
job_id: 'ID do Trabalho:'
|
|
317
320
|
message_discard_failed: Não foi possível descartar a mensagem.
|
|
318
321
|
message_discarded: Mensagem descartada.
|
|
319
322
|
message_retried: Visibilidade da mensagem redefinida.
|
|
320
323
|
message_retry_failed: Não foi possível tentar novamente a mensagem.
|
|
324
|
+
metadata: Metadados
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Última leitura:'
|
|
327
|
+
locale: 'Localidade:'
|
|
328
|
+
priority: 'Prioridade:'
|
|
329
|
+
queue: 'Fila:'
|
|
330
|
+
scheduled: 'Agendado:'
|
|
331
|
+
timezone: 'Fuso horário:'
|
|
332
|
+
visible_at: 'Visível em:'
|
|
321
333
|
pause: Pausar
|
|
322
334
|
pause_confirm: Pausar processamento?
|
|
323
335
|
purge_confirm: Limpar todas as mensagens?
|
|
@@ -333,6 +345,10 @@ pt:
|
|
|
333
345
|
one: "%{count} tarefa configurada"
|
|
334
346
|
other: "%{count} tarefas configuradas"
|
|
335
347
|
title: Tarefas Recorrentes
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Tarefa desativada
|
|
350
|
+
enabled: Tarefa ativada
|
|
351
|
+
failed: Falha ao alternar tarefa
|
|
336
352
|
show:
|
|
337
353
|
back: Voltar
|
|
338
354
|
configuration: Configuração
|
data/config/locales/sv.yml
CHANGED
|
@@ -301,23 +301,35 @@ sv:
|
|
|
301
301
|
purge_confirm: Rensa alla meddelanden från %{name}?
|
|
302
302
|
resume: Återuppta
|
|
303
303
|
show:
|
|
304
|
+
arguments: Argument
|
|
304
305
|
delete_confirm: Ta bort denna kö permanent? Detta kan inte ångras.
|
|
305
306
|
delete_queue: Ta bort kö
|
|
306
307
|
depth: 'Djup:'
|
|
307
308
|
discard: Kassera
|
|
308
309
|
discard_confirm: Kassera detta meddelande?
|
|
309
310
|
empty: Kön är tom
|
|
311
|
+
full_json_payload: Fullständig JSON-payload
|
|
310
312
|
headers:
|
|
311
|
-
actions: Åtgärder
|
|
312
313
|
enqueued: Inlagd
|
|
313
314
|
id: ID
|
|
314
|
-
|
|
315
|
+
job_class: Jobbklass
|
|
315
316
|
reads: Läsningar
|
|
316
317
|
vt: VT
|
|
318
|
+
headers_section: Headers
|
|
319
|
+
job_id: 'Jobb-ID:'
|
|
317
320
|
message_discard_failed: Kunde inte kassera meddelandet.
|
|
318
321
|
message_discarded: Meddelande kasserat.
|
|
319
322
|
message_retried: Meddelandets synlighet återställd.
|
|
320
323
|
message_retry_failed: Kunde inte försöka igen med meddelandet.
|
|
324
|
+
metadata: Metadata
|
|
325
|
+
metadata_labels:
|
|
326
|
+
last_read: 'Senast läst:'
|
|
327
|
+
locale: 'Språk:'
|
|
328
|
+
priority: 'Prioritet:'
|
|
329
|
+
queue: 'Kö:'
|
|
330
|
+
scheduled: 'Schemalagt:'
|
|
331
|
+
timezone: 'Tidszon:'
|
|
332
|
+
visible_at: 'Synlig vid:'
|
|
321
333
|
pause: Pausa
|
|
322
334
|
pause_confirm: Pausa bearbetning?
|
|
323
335
|
purge_confirm: Rensa alla meddelanden?
|
|
@@ -333,6 +345,10 @@ sv:
|
|
|
333
345
|
one: "%{count} uppgift konfigurerad"
|
|
334
346
|
other: "%{count} uppgifter konfigurerade"
|
|
335
347
|
title: Återkommande uppgifter
|
|
348
|
+
toggle:
|
|
349
|
+
disabled: Uppgift inaktiverad
|
|
350
|
+
enabled: Uppgift aktiverad
|
|
351
|
+
failed: Kunde inte ändra uppgift
|
|
336
352
|
show:
|
|
337
353
|
back: Tillbaka
|
|
338
354
|
configuration: Konfiguration
|
|
@@ -11,7 +11,10 @@ module Pgbus
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def due_tasks(time = Time.current)
|
|
14
|
-
tasks.
|
|
14
|
+
tasks.filter_map do |task|
|
|
15
|
+
run_at = canonical_run_at(task, time)
|
|
16
|
+
[task, run_at] if run_at
|
|
17
|
+
end
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def enqueue_task(task, run_at:)
|
|
@@ -34,6 +37,12 @@ module Pgbus
|
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
rescue AlreadyRecorded
|
|
40
|
+
# AlreadyRecorded means this (task_key, run_at) was already enqueued.
|
|
41
|
+
# If we acquired a NEW lock (prior lock was already released because the
|
|
42
|
+
# job completed), release it — no message will use it. If we didn't
|
|
43
|
+
# acquire a lock (nil or :already_locked), there's nothing to release.
|
|
44
|
+
# In either case, we are NOT opening a race window because the job for
|
|
45
|
+
# this run_at already ran or is running.
|
|
37
46
|
release_uniqueness_lock(acquired_key)
|
|
38
47
|
Pgbus.logger.debug { "[Pgbus] Recurring task #{task.key} already enqueued for #{run_at.iso8601}" }
|
|
39
48
|
rescue StandardError
|
|
@@ -75,23 +84,31 @@ module Pgbus
|
|
|
75
84
|
end
|
|
76
85
|
end
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
# Returns the canonical run_at time if the task is due, or nil if not.
|
|
88
|
+
# This ensures a consistent run_at regardless of which tick detects
|
|
89
|
+
# the cron occurrence — fixing a bug where match?(t) at the exact
|
|
90
|
+
# boundary returns previous_time=T-1, while a tick 1s later gets
|
|
91
|
+
# previous_time=T, producing different run_at values for the same
|
|
92
|
+
# cron occurrence and bypassing RecurringExecution deduplication.
|
|
93
|
+
def canonical_run_at(task, time)
|
|
82
94
|
cron = task.parsed_schedule
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
#
|
|
86
|
-
|
|
95
|
+
return nil unless cron
|
|
96
|
+
|
|
97
|
+
# Exact boundary hit: cron.match?(time) is true.
|
|
98
|
+
# previous_time returns the PRIOR occurrence here, but the cron
|
|
99
|
+
# time that fired is `time` itself (truncated to the minute).
|
|
100
|
+
if cron.match?(time)
|
|
101
|
+
# Fugit next_time from 1 second before gives us the current cron time
|
|
102
|
+
return cron.next_time(time - 1).to_t
|
|
103
|
+
end
|
|
87
104
|
|
|
88
|
-
#
|
|
89
|
-
# still fire it (handles the case where we tick slightly after the
|
|
90
|
-
# cron time). The window is the scheduler interval.
|
|
105
|
+
# Within the scheduler interval window after the cron time.
|
|
91
106
|
prev = task.previous_time(time)
|
|
92
|
-
return
|
|
107
|
+
return nil unless prev
|
|
93
108
|
|
|
94
|
-
(time - prev) <= @config.recurring_schedule_interval
|
|
109
|
+
return prev if (time - prev) <= @config.recurring_schedule_interval
|
|
110
|
+
|
|
111
|
+
nil
|
|
95
112
|
end
|
|
96
113
|
|
|
97
114
|
def resolve_queue(task)
|
|
@@ -40,10 +40,7 @@ module Pgbus
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def tick(now)
|
|
43
|
-
schedule.due_tasks(now).each do |task|
|
|
44
|
-
run_at = task.previous_time(now)
|
|
45
|
-
next unless run_at
|
|
46
|
-
|
|
43
|
+
schedule.due_tasks(now).each do |task, run_at|
|
|
47
44
|
schedule.enqueue_task(task, run_at: run_at)
|
|
48
45
|
@last_runs[task.key] = now
|
|
49
46
|
rescue StandardError => e
|
data/lib/pgbus/stat_buffer.rb
CHANGED
|
@@ -68,21 +68,36 @@ module Pgbus
|
|
|
68
68
|
def write_to_database(entries)
|
|
69
69
|
return unless JobStat.table_exists?
|
|
70
70
|
|
|
71
|
-
columns
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
# Always attempt all 6 columns. If the latency migration hasn't
|
|
72
|
+
# run, the first flush will fail, and we fall back to base columns.
|
|
73
|
+
# This avoids relying on the memoized latency_columns? flag which
|
|
74
|
+
# can be stuck at false if evaluated before the connection is ready.
|
|
74
75
|
rows = entries.map do |e|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
{ job_class: e[:job_class], queue_name: e[:queue_name],
|
|
77
|
+
status: e[:status], duration_ms: e[:duration_ms],
|
|
78
|
+
enqueue_latency_ms: e[:enqueue_latency_ms], retry_count: e[:retry_count] }
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
JobStat.insert_all(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
81
|
+
JobStat.insert_all(rows)
|
|
82
|
+
rescue ActiveRecord::StatementInvalid, ActiveModel::UnknownAttributeError => e
|
|
83
|
+
# Column doesn't exist — fall back to base columns only
|
|
84
|
+
if e.message.include?("enqueue_latency_ms") || e.message.include?("retry_count")
|
|
85
|
+
Pgbus.logger.warn { "[Pgbus] Latency columns missing — recording stats without latency data" } unless @degraded_warned
|
|
86
|
+
@degraded_warned = true
|
|
87
|
+
base_rows = entries.map do |entry|
|
|
88
|
+
{ job_class: entry[:job_class], queue_name: entry[:queue_name],
|
|
89
|
+
status: entry[:status], duration_ms: entry[:duration_ms] }
|
|
90
|
+
end
|
|
91
|
+
begin
|
|
92
|
+
JobStat.insert_all(base_rows)
|
|
93
|
+
rescue StandardError => fallback_error
|
|
94
|
+
Pgbus.logger.warn { "[Pgbus] Stat buffer fallback flush failed: #{fallback_error.message}" }
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
Pgbus.logger.warn { "[Pgbus] Stat buffer flush failed: #{e.message}" }
|
|
98
|
+
end
|
|
84
99
|
rescue StandardError => e
|
|
85
|
-
Pgbus.logger.
|
|
100
|
+
Pgbus.logger.warn { "[Pgbus] Stat buffer flush failed: #{e.message}" }
|
|
86
101
|
end
|
|
87
102
|
|
|
88
103
|
def monotonic_now
|
data/lib/pgbus/version.rb
CHANGED
|
@@ -479,13 +479,13 @@ module Pgbus
|
|
|
479
479
|
|
|
480
480
|
def toggle_recurring_task(id)
|
|
481
481
|
record = RecurringTask.find_by(id: id)
|
|
482
|
-
return
|
|
482
|
+
return nil unless record
|
|
483
483
|
|
|
484
484
|
record.update!(enabled: !record.enabled)
|
|
485
|
-
|
|
485
|
+
record.enabled ? :enabled : :disabled
|
|
486
486
|
rescue StandardError => e
|
|
487
487
|
Pgbus.logger.error { "[Pgbus::Web] Error toggling recurring task #{id}: #{e.message}" }
|
|
488
|
-
|
|
488
|
+
nil
|
|
489
489
|
end
|
|
490
490
|
|
|
491
491
|
def enqueue_recurring_task_now(id)
|