pgbus 0.3.4 → 0.3.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35114da580e6620d0e519cd3d7bf4340728273a2e945eb30d228464d8b8dcc2c
4
- data.tar.gz: ce2d743e27e02e05b2d94a7632403670cbeb5bf305be1fab0b39823cb87ce4e2
3
+ metadata.gz: a25921e6a7a0ac72023501978b6dec0b38645a18b45cece73bf15ddbc655dae8
4
+ data.tar.gz: 810980a58f382aad948b660a9102cdb6ea3a3af57c3768f1205c3582b0705003
5
5
  SHA512:
6
- metadata.gz: 6f917b1f6c754fd12b687d714653e87a8766156498c468a9708b05aa26e51d0f852de3e0b6a9445b1f02f2569d18bd28e35c76cba40adcc09b089fa4bb2b7093
7
- data.tar.gz: 1f54ebbd27831d925d2b73de1ac763461d1712e965cf79f1dc20c1c6cc9a99c1955cb777072a0aeee0b6ef4c8d0423bcb063b67cd6291faf3510c4efadda8069
6
+ metadata.gz: 597fbc986e88b2c37339156de5f21a576d50f45bab5f72343dc3eaed02b730d35dce777285f65b63f3264f4a91d76fa0b7a001635a6c103b08ef95454c6dc71f
7
+ data.tar.gz: 2a305826eb3b9b0618e64c99e0724c2daf020047933e83d231a35866d8e8b1e334a45c45c03a7fbef577b8dd2243686dc4e351595a671a8d8cd1a63f940602ed
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
- if data_source.toggle_recurring_task(params[:id])
22
- redirect_to pgbus.recurring_tasks_path, notice: "Task toggled"
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: "Failed to toggle task"
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
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
55
- <td class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= m[:msg_id] %></td>
56
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:enqueued_at]) %></td>
57
- <td class="px-4 py-3 text-sm text-gray-500"><%= m[:read_ct] %></td>
58
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:vt]) %></td>
59
- <td class="px-4 py-3 text-sm text-gray-600 font-mono text-xs max-w-md truncate">
60
- <%= pgbus_json_preview(m[:message]) %>
61
- </td>
62
- <td class="px-4 py-3 text-sm text-right space-x-1 whitespace-nowrap">
63
- <%= button_to t("pgbus.queues.show.retry"), pgbus.retry_message_queue_path(name: params[:name], msg_id: m[:msg_id]),
64
- method: :post,
65
- class: "text-xs text-indigo-600 hover:text-indigo-800 font-medium",
66
- data: { turbo_confirm: t("pgbus.queues.show.retry_confirm") } %>
67
- <%= button_to t("pgbus.queues.show.discard"), pgbus.discard_message_queue_path(name: params[:name], msg_id: m[:msg_id]),
68
- method: :post,
69
- class: "text-xs text-red-600 hover:text-red-800 font-medium",
70
- data: { turbo_confirm: t("pgbus.queues.show.discard_confirm") } %>
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="6" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.queues.show.empty") %></td></tr>
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] %>
@@ -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
- payload: Indhold
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
@@ -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
- payload: Nutzlast
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
@@ -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
- payload: Payload
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
@@ -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
- payload: Carga útil
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
@@ -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
- payload: Kuorma
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
@@ -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
- payload: Charge utile
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
@@ -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
- payload: Carico utile
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
@@ -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
- payload: ペイロード
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: 設定
@@ -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
- payload: Innhold
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
@@ -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
- payload: Inhoud
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
@@ -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
- payload: Carga útil
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
@@ -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
- payload: Innehåll
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
@@ -68,21 +68,36 @@ module Pgbus
68
68
  def write_to_database(entries)
69
69
  return unless JobStat.table_exists?
70
70
 
71
- columns = %i[job_class queue_name status duration_ms]
72
- columns.push(:enqueue_latency_ms, :retry_count) if JobStat.latency_columns?
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
- row = [e[:job_class], e[:queue_name], e[:status], e[:duration_ms]]
76
- row.push(e[:enqueue_latency_ms], e[:retry_count]) if JobStat.latency_columns?
77
- row
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
- rows.map { |row| columns.zip(row).to_h },
82
- record_timestamps: true
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.debug { "[Pgbus] Stat buffer flush failed: #{e.message}" }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgbus
4
- VERSION = "0.3.4"
4
+ VERSION = "0.3.5"
5
5
  end
@@ -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 false unless record
482
+ return nil unless record
483
483
 
484
484
  record.update!(enabled: !record.enabled)
485
- true
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
- false
488
+ nil
489
489
  end
490
490
 
491
491
  def enqueue_recurring_task_now(id)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson