pgbus 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +69 -14
  4. data/Rakefile +7 -1
  5. data/app/controllers/concerns/pgbus/job_listing.rb +37 -0
  6. data/app/controllers/concerns/pgbus/process_listing.rb +17 -0
  7. data/app/controllers/pgbus/application_controller.rb +5 -2
  8. data/app/controllers/pgbus/batches_controller.rb +4 -1
  9. data/app/controllers/pgbus/dashboard_controller.rb +20 -6
  10. data/app/controllers/pgbus/dead_letter_controller.rb +27 -3
  11. data/app/controllers/pgbus/events_controller.rb +52 -3
  12. data/app/controllers/pgbus/insights_controller.rb +19 -1
  13. data/app/controllers/pgbus/jobs_controller.rb +45 -26
  14. data/app/controllers/pgbus/locks_controller.rb +7 -2
  15. data/app/controllers/pgbus/outbox_controller.rb +4 -1
  16. data/app/controllers/pgbus/processes_controller.rb +3 -1
  17. data/app/controllers/pgbus/queues_controller.rb +16 -4
  18. data/app/controllers/pgbus/recurring_tasks_controller.rb +5 -7
  19. data/app/frontend/pgbus/modules/charts.js +119 -77
  20. data/app/frontend/pgbus/style.css +1 -1
  21. data/app/frontend/pgbus/tailwind.css +18 -0
  22. data/app/helpers/pgbus/application_helper.rb +155 -113
  23. data/app/helpers/pgbus/button_helper.rb +88 -0
  24. data/app/helpers/pgbus/dashboard_helper.rb +157 -0
  25. data/app/helpers/pgbus/events_helper.rb +40 -0
  26. data/app/helpers/pgbus/insights_helper.rb +39 -0
  27. data/app/helpers/pgbus/processes_helper.rb +79 -0
  28. data/app/views/pgbus/batches/_batches_table.html.erb +16 -13
  29. data/app/views/pgbus/batches/show.html.erb +3 -4
  30. data/app/views/pgbus/dashboard/_processes_table.html.erb +11 -11
  31. data/app/views/pgbus/dashboard/_queue_health.html.erb +42 -51
  32. data/app/views/pgbus/dashboard/_queues_table.html.erb +18 -16
  33. data/app/views/pgbus/dashboard/_recent_failures.html.erb +16 -12
  34. data/app/views/pgbus/dashboard/_stats_cards.html.erb +70 -36
  35. data/app/views/pgbus/dashboard/show.html.erb +1 -1
  36. data/app/views/pgbus/dead_letter/_error.html.erb +13 -0
  37. data/app/views/pgbus/dead_letter/_messages_table.html.erb +37 -96
  38. data/app/views/pgbus/dead_letter/_row.html.erb +75 -0
  39. data/app/views/pgbus/dead_letter/index.html.erb +6 -9
  40. data/app/views/pgbus/dead_letter/show.html.erb +37 -21
  41. data/app/views/pgbus/events/_list.html.erb +55 -0
  42. data/app/views/pgbus/events/_processed_table.html.erb +45 -0
  43. data/app/views/pgbus/events/_replay.html.erb +10 -0
  44. data/app/views/pgbus/events/_row.html.erb +162 -0
  45. data/app/views/pgbus/events/_tabs.html.erb +13 -0
  46. data/app/views/pgbus/events/index.html.erb +27 -48
  47. data/app/views/pgbus/events/show.html.erb +25 -7
  48. data/app/views/pgbus/insights/_chart_card.html.erb +21 -0
  49. data/app/views/pgbus/insights/_latency_table.html.erb +20 -0
  50. data/app/views/pgbus/insights/_status_table.html.erb +19 -0
  51. data/app/views/pgbus/insights/_throughput_table.html.erb +18 -0
  52. data/app/views/pgbus/insights/show.html.erb +83 -100
  53. data/app/views/pgbus/jobs/_list.html.erb +64 -0
  54. data/app/views/pgbus/jobs/_row.html.erb +107 -0
  55. data/app/views/pgbus/jobs/_tabs.html.erb +14 -0
  56. data/app/views/pgbus/jobs/index.html.erb +10 -10
  57. data/app/views/pgbus/jobs/show.html.erb +18 -20
  58. data/app/views/pgbus/locks/_concurrency.html.erb +25 -23
  59. data/app/views/pgbus/locks/_uniqueness.html.erb +20 -22
  60. data/app/views/pgbus/outbox/index.html.erb +30 -27
  61. data/app/views/pgbus/processes/_processes_table.html.erb +6 -25
  62. data/app/views/pgbus/processes/_row.html.erb +48 -0
  63. data/app/views/pgbus/processes/_status.html.erb +4 -0
  64. data/app/views/pgbus/queues/_queues_list.html.erb +28 -32
  65. data/app/views/pgbus/queues/show.html.erb +69 -118
  66. data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +75 -76
  67. data/app/views/pgbus/recurring_tasks/index.html.erb +1 -1
  68. data/app/views/pgbus/recurring_tasks/show.html.erb +45 -51
  69. data/app/views/pgbus/shared/_empty_row.html.erb +2 -0
  70. data/app/views/pgbus/shared/_pager.html.erb +34 -0
  71. data/config/i18n-tasks.yml +11 -2
  72. data/config/locales/da.yml +405 -113
  73. data/config/locales/de.yml +405 -113
  74. data/config/locales/en.yml +405 -113
  75. data/config/locales/es.yml +404 -112
  76. data/config/locales/fi.yml +404 -112
  77. data/config/locales/fr.yml +406 -114
  78. data/config/locales/it.yml +405 -113
  79. data/config/locales/ja.yml +405 -113
  80. data/config/locales/nb.yml +405 -113
  81. data/config/locales/nl.yml +405 -113
  82. data/config/locales/pt.yml +406 -114
  83. data/config/locales/sv.yml +404 -112
  84. data/lib/generators/pgbus/templates/initializer.rb.erb +4 -0
  85. data/lib/pgbus/active_job/executor.rb +36 -17
  86. data/lib/pgbus/cli/dlq.rb +32 -5
  87. data/lib/pgbus/client.rb +7 -2
  88. data/lib/pgbus/configuration.rb +93 -2
  89. data/lib/pgbus/dead_letter_header.rb +129 -0
  90. data/lib/pgbus/doctor.rb +37 -5
  91. data/lib/pgbus/event_bus/handler.rb +18 -11
  92. data/lib/pgbus/event_bus/registry.rb +6 -0
  93. data/lib/pgbus/failed_event_recorder.rb +51 -0
  94. data/lib/pgbus/instrumentation.rb +2 -1
  95. data/lib/pgbus/mcp/base_tool.rb +25 -0
  96. data/lib/pgbus/mcp/tools/concurrency_tool.rb +23 -4
  97. data/lib/pgbus/mcp/tools/dlq_detail_tool.rb +4 -2
  98. data/lib/pgbus/mcp/tools/dlq_tool.rb +10 -4
  99. data/lib/pgbus/mcp/tools/locks_tool.rb +23 -5
  100. data/lib/pgbus/mcp/tools/recurring_tool.rb +33 -4
  101. data/lib/pgbus/outbox/poller.rb +1 -1
  102. data/lib/pgbus/process/claim_buffer.rb +115 -0
  103. data/lib/pgbus/process/consumer.rb +94 -17
  104. data/lib/pgbus/process/dispatcher.rb +1 -1
  105. data/lib/pgbus/process/supervisor.rb +35 -12
  106. data/lib/pgbus/process/worker.rb +91 -25
  107. data/lib/pgbus/recurring/scheduler.rb +2 -1
  108. data/lib/pgbus/ruby_jit.rb +21 -0
  109. data/lib/pgbus/version.rb +1 -1
  110. data/lib/pgbus/visibility_heartbeat.rb +38 -3
  111. data/lib/pgbus/web/data_source/dashboard_signals.rb +170 -0
  112. data/lib/pgbus/web/data_source/dead_letter.rb +142 -0
  113. data/lib/pgbus/web/data_source/event_list.rb +161 -0
  114. data/lib/pgbus/web/data_source/job_list.rb +323 -0
  115. data/lib/pgbus/web/data_source/list_counts.rb +80 -0
  116. data/lib/pgbus/web/data_source/process_list.rb +69 -0
  117. data/lib/pgbus/web/data_source/queue_summary.rb +122 -0
  118. data/lib/pgbus/web/data_source.rb +132 -254
  119. data/lib/pgbus/web/dead_letter_reason.rb +81 -0
  120. data/lib/pgbus/web/event_state.rb +71 -0
  121. data/lib/pgbus/web/health_app.rb +7 -3
  122. data/lib/pgbus/web/health_verdict.rb +78 -0
  123. data/lib/pgbus/web/insights_charts.rb +84 -0
  124. data/lib/pgbus/web/job_state.rb +111 -0
  125. data/lib/pgbus/web/process_activity.rb +97 -0
  126. data/lib/pgbus/web/process_state.rb +187 -0
  127. data/lib/pgbus/web/queue_summary.rb +104 -0
  128. data/lib/pgbus/web/time_format.rb +128 -0
  129. metadata +45 -4
  130. data/app/views/pgbus/events/_pending_table.html.erb +0 -169
  131. data/app/views/pgbus/jobs/_enqueued_table.html.erb +0 -118
  132. data/app/views/pgbus/jobs/_failed_table.html.erb +0 -76
@@ -41,6 +41,7 @@ en:
41
41
  title: Active Processes
42
42
  queue_health:
43
43
  dead_tuples: Dead Tuples
44
+ empty: No queue tables to measure yet
44
45
  headers:
45
46
  bloat: Bloat
46
47
  dead: Dead
@@ -60,14 +61,34 @@ en:
60
61
  one: "%{count} table needs vacuum"
61
62
  other: "%{count} tables need vacuum"
62
63
  title: Queue Health
64
+ words:
65
+ bloat:
66
+ critical: "%{ratio} on %{table} — reads are slowing; vacuum it now (critical above %{critical}, warns above %{warn})"
67
+ none: Nothing to measure yet
68
+ ok: "%{ratio} on %{table} — fine (warns above %{warn}, critical above %{critical})"
69
+ warn: "%{ratio} on %{table} — vacuum due (warns above %{warn}, critical above %{critical})"
70
+ dead_tuples:
71
+ ok: Fine — warns above %{limit} dead rows
72
+ warn: Above %{limit} — autovacuum is behind; the table below shows where
73
+ transaction:
74
+ critical: Over %{critical} — vacuum cannot reclaim rows until it ends; find it in pg_stat_activity (warns at %{warn}; the health verdict degrades after %{verdict})
75
+ none: No open transaction — vacuum can reclaim every dead row (warns at %{warn}, alerts at %{critical}; the health verdict degrades after %{verdict})
76
+ ok: Fine — warns at %{warn}, alerts at %{critical}; the health verdict degrades after %{verdict}
77
+ warn: Over %{warn} — a long transaction stops vacuum reclaiming rows (alerts at %{critical}; the health verdict degrades after %{verdict})
78
+ vacuum:
79
+ critical: Over %{critical} — autovacuum is not reaching these tables; check its settings (warns after %{warn})
80
+ ok: Fine — warns after %{warn}, critical after %{critical}
81
+ unknown: Never vacuumed, or the statistics are unavailable
82
+ warn: Over %{warn} — autovacuum may be falling behind (critical after %{critical})
63
83
  worst_bloat: Worst Bloat
64
84
  queues_table:
65
85
  empty: No queues found
66
86
  headers:
67
87
  depth: Depth
68
- oldest_claimable: Oldest claimable (s)
88
+ oldest_claimable: Oldest claimable
69
89
  parked: Parked
70
90
  queue: Queue
91
+ status: Status
71
92
  total: Total
72
93
  visible: Visible
73
94
  title: Queues
@@ -75,7 +96,9 @@ en:
75
96
  recent_failures:
76
97
  empty: No failures
77
98
  headers:
99
+ attempt: Attempt
78
100
  error: Error
101
+ job: Job
79
102
  queue: Queue
80
103
  when: When
81
104
  title: Recent Failures
@@ -83,19 +106,72 @@ en:
83
106
  show:
84
107
  title: Dashboard
85
108
  stats_cards:
109
+ dead_lettered: Dead-lettered
110
+ dlq_newest_html: 'newest: %{error_class} in %{job} %{ago}'
111
+ dlq_newest_legacy_html: 'newest: %{job} %{ago} — no reason recorded'
112
+ dlq_newest_no_error_html: 'newest: %{job} %{ago} — no error recorded'
113
+ dlq_none: Nothing dead-lettered
114
+ dlq_see_why: open the dead-letter page to see why each died
86
115
  enqueued: Enqueued
87
- failed_dlq: Failed / DLQ
88
- failed_dlq_labels: Failed / Dead Lettered
116
+ enqueued_detail: "%{claimable} claimable now · %{waiting} scheduled, running or retrying"
117
+ enqueued_paused:
118
+ one: "%{count} in paused queues"
119
+ other: "%{count} in paused queues"
89
120
  parked_jobs: Parked jobs
90
121
  parked_jobs_oldest: oldest %{age}
91
122
  processes: Processes
123
+ processes_detail: "%{healthy} healthy · %{stalled} stalled · %{stale} stale"
124
+ processes_none: Nothing is running — no job is being claimed
125
+ processes_stale: stale processes stopped heartbeating; their in-flight jobs run again when their visibility timeout expires
92
126
  queues: Queues
127
+ queues_detail: "%{paused} paused · %{dlq} dead-letter"
128
+ queues_undrained:
129
+ one: "%{count} holds jobs that nothing drains"
130
+ other: "%{count} hold jobs that nothing drains"
93
131
  recurring: Recurring
132
+ recurring_detail: Tasks the scheduler enqueues on their schedule
133
+ retrying: Retrying
134
+ retrying_due:
135
+ one: "%{count} due now"
136
+ other: "%{count} due now"
137
+ retrying_newest: newest error %{error_class}
138
+ retrying_next_html: next attempt %{time}
139
+ retrying_none: Nothing is retrying
94
140
  throughput: Throughput
95
- throughput_unit: msgs/s
96
- view_tasks: View tasks
97
- visible: visible
141
+ throughput_detail: jobs/min over the last %{minutes} min
142
+ throughput_unavailable: needs job stats (rails g pgbus:add_job_stats)
143
+ verdict:
144
+ links:
145
+ dlq: Dead letters
146
+ processes: Processes
147
+ look_at: 'Look at:'
148
+ meaning:
149
+ degraded: Something needs attention, but jobs still flow. /readyz still answers ready.
150
+ ok: Every queue with work is being drained and every process is heartbeating. /readyz answers ready.
151
+ stalled: Jobs are waiting and nothing is claiming them. /readyz answers not ready (503).
152
+ statuses:
153
+ degraded: DEGRADED
154
+ ok: OK
155
+ stalled: STALLED
156
+ title: 'Health: %{status}'
157
+ unavailable: Health verdict unavailable — the checks behind it could not be read; see the log
98
158
  dead_letter:
159
+ detail:
160
+ backtrace: Backtrace (first lines)
161
+ died: 'Died:'
162
+ dlq: 'Dead-letter queue:'
163
+ error_from_attempt: Error from attempt %{attempt}; attempts %{from}–%{to} recorded no error.
164
+ source: 'Moved by:'
165
+ sources:
166
+ consumer: event consumer
167
+ worker: job worker
168
+ why: Why it died
169
+ filters:
170
+ all: All
171
+ clear_error_class: Clear filter
172
+ error_class: 'Error: %{error_class}'
173
+ filter_error_class: Show only %{error_class}
174
+ label: Filter dead letters
99
175
  index:
100
176
  discard_all: Discard All
101
177
  discard_all_confirm: Permanently discard all DLQ messages?
@@ -115,11 +191,13 @@ en:
115
191
  empty: Dead letter queue is empty
116
192
  full_json_payload: Full JSON payload
117
193
  headers:
118
- enqueued: Enqueued
194
+ actions: Actions
195
+ attempts: Attempts
196
+ died: Died
119
197
  id: ID
120
- job_class: Job Class
121
- reads: Reads
122
- source_queue: Source Queue
198
+ job: Job
199
+ reason: Reason
200
+ source_queue: Source queue
123
201
  headers_section: Headers
124
202
  job_id: 'Job ID:'
125
203
  metadata: Metadata
@@ -131,6 +209,16 @@ en:
131
209
  visible_at: 'Visible at:'
132
210
  retry: Retry
133
211
  total_label: messages in dead letter queue
212
+ view: View
213
+ reasons:
214
+ error_earlier_attempt_html: 'Retries exhausted. Last recorded error, from attempt %{error_attempt}: %{error_class}: %{error_message}'
215
+ error_html: 'Retries exhausted. Last error: %{error_class}: %{error_message}'
216
+ event_no_error_html: Handler failed on %{attempts} deliveries (max %{max}) — no handler error was recorded
217
+ no_error_recorded_html: Exceeded %{max} retries after %{attempts} reads — no error was recorded; every failing attempt crashed before it could report
218
+ not_recorded_html: Reason not recorded (dead-lettered before pgbus %{version})
219
+ retried_before:
220
+ one: Retried from the DLQ once before.
221
+ other: Retried from the DLQ %{count} times before.
134
222
  show:
135
223
  back: Back to DLQ
136
224
  context: Context
@@ -138,8 +226,10 @@ en:
138
226
  discard_confirm: Permanently discard?
139
227
  headers: Headers
140
228
  labels:
229
+ attempts: Attempts
230
+ died: Died
231
+ dlq: Dead-letter queue
141
232
  enqueued_at: Enqueued At
142
- read_count: Read Count
143
233
  source_queue: Source Queue
144
234
  visibility_timeout: Visibility Timeout
145
235
  not_found: Message not found
@@ -163,6 +253,7 @@ en:
163
253
  mark_handled_failed: Could not mark event as handled.
164
254
  marked_handled: Event marked as handled.
165
255
  none_selected: No events selected.
256
+ payload_redacted: 'Not saved: the payload still contains redacted ([FILTERED]) values, which would replace the real ones.'
166
257
  payload_update_failed: Could not update payload. Check that the JSON is valid.
167
258
  payload_updated: Payload updated and event re-enqueued.
168
259
  replay_failed: Event not found or could not be replayed.
@@ -170,56 +261,118 @@ en:
170
261
  reroute_failed: Could not reroute event.
171
262
  rerouted: Event rerouted to target handler.
172
263
  index:
173
- discard_selected: Discard Selected
174
- discard_selected_confirm: Discard selected events?
175
- pending_empty: No pending events
176
- pending_headers:
177
- enqueued: Enqueued
178
- handler_queue: Handler Queue
179
- id: ID
180
- reads: Reads
181
- routing_key: Routing Key
182
264
  pending_title: Pending Events
183
- processed_empty: No events processed yet
184
- processed_headers:
185
- event_id: Event ID
186
- handler: Handler
187
- processed_at: Processed At
188
265
  processed_title: Processed Events
266
+ subscribers:
267
+ covered: Drained by a running consumer
268
+ uncovered: No running consumer covers this pattern
269
+ unknown: Consumer coverage unknown
270
+ view_pending: View pending
189
271
  subscribers_empty: No subscribers registered
190
272
  subscribers_headers:
273
+ consumer: Consumer
191
274
  handler: Handler
192
275
  pattern: Pattern
276
+ pending: Pending
193
277
  queue: Queue
194
278
  subscribers_title: Registered Subscribers
195
279
  title: Events
196
- pending_table:
197
- arguments: Payload
280
+ list:
198
281
  context: Context
199
282
  discard: Discard
200
283
  discard_confirm: Discard this event? It will be archived.
284
+ discard_failure_confirm: Discard this failed event? Its message is no longer in the queue.
285
+ discard_selected: Discard Selected
286
+ discard_selected_confirm: Discard selected events?
201
287
  edit_payload: Edit & Retry
202
288
  edit_payload_confirm: Update payload and re-enqueue this event?
203
289
  edit_payload_label: 'JSON Payload:'
290
+ empty:
291
+ all: No pending events
292
+ ready: No events waiting to be handled
293
+ retrying: No events waiting to retry
294
+ running: No events being handled
295
+ scheduled: No scheduled events
204
296
  event_id: 'Event ID:'
205
297
  full_json_payload: Full JSON Payload
298
+ headers:
299
+ actions: Actions
300
+ attempts: Attempts
301
+ event: Event
302
+ handler: Handler
303
+ id: ID
304
+ reason: Next run / Reason
305
+ state: State
206
306
  headers_section: Headers
207
307
  mark_handled: Mark Handled
208
- mark_handled_confirm: Mark this event as handled? It will be archived and skipped on replay.
308
+ mark_handled_confirm: Mark this event as handled? Its message is archived and the handler does not run it.
209
309
  metadata: Metadata
210
310
  metadata_labels:
311
+ enqueued: 'Enqueued:'
211
312
  last_read: 'Last read:'
313
+ published: 'Published:'
212
314
  read_count: 'Read count:'
213
315
  visible_at: 'Visible at:'
316
+ payload: Payload
317
+ reasons:
318
+ handling_html: Being handled by %{handler} — claimed %{ago} · lease expires %{time}
319
+ lease_expired_html: Lease expired (consumer likely died) — redelivering to %{handler}, attempt %{attempt}
320
+ no_consumer_for_queue_html: No running consumer subscribes to %{pattern} (%{handler})
321
+ no_consumers_html: No healthy consumer running
322
+ orphaned_html: Failed with %{error} in %{handler} — message no longer in queue
323
+ paused_html: Queue paused
324
+ retrying_dlq_html: "%{handler} attempt %{attempt}/%{max} failed: %{error} — next read moves it to the DLQ"
325
+ retrying_due_html: "%{handler} attempt %{attempt}/%{max} failed: %{error} — next attempt due now"
326
+ retrying_html: "%{handler} attempt %{attempt}/%{max} failed: %{error} — next attempt %{time}"
327
+ scheduled_html: Scheduled — handled by %{handler} %{time}
328
+ waiting_html: 'Waiting — %{count} ahead · next: %{handler}'
329
+ waiting_unknown_html: Waiting for %{handler}
214
330
  reroute: Reroute
215
331
  reroute_confirm: Reroute this event to a different handler?
216
332
  reroute_label: 'Target handler:'
333
+ states:
334
+ ready: Ready
335
+ retrying: Retrying
336
+ running: Handling
337
+ scheduled: Scheduled
338
+ tabs:
339
+ all: All
340
+ ready: Ready
341
+ retrying: Retrying
342
+ running: Handling
343
+ scheduled: Scheduled
344
+ tabs_label: Event states
345
+ view_failure: View failure
346
+ processed:
347
+ empty: No events processed yet
348
+ headers:
349
+ actions: Actions
350
+ event_id: Event ID
351
+ handler: Handler
352
+ reason: Reason
353
+ state: State
354
+ reasons:
355
+ abandoned_html: Claim went silent %{ago} — the next delivery re-runs the handler
356
+ completed_html: Completed %{ago}
357
+ completed_legacy_html: Recorded %{ago} (single-phase claim; upgrade with pgbus:add_processed_event_completion)
358
+ handling_html: Being handled — claim refreshed %{ago}
359
+ replay: Replay
360
+ replay_confirm: Re-deliver this event to its handler under a new event ID? The handler runs again.
361
+ replay_unavailable:
362
+ no_handler: Handler no longer registered
363
+ not_archived: No longer in the archive
364
+ states:
365
+ abandoned: Silent
366
+ completed: Completed
367
+ handling: Handling
217
368
  show:
218
369
  back: Back to Events
219
370
  labels:
371
+ completed_at: Completed At
220
372
  event_id: Event ID
221
373
  handler: Handler
222
374
  processed_at: Processed At
375
+ state: State
223
376
  not_found: Event not found
224
377
  title: Event %{event_id}
225
378
  helpers:
@@ -231,6 +384,7 @@ en:
231
384
  bulk_select_row: Select %{id}
232
385
  bulk_selected: selected
233
386
  pagination:
387
+ label: Pagination
234
388
  next: Next
235
389
  previous: Previous
236
390
  showing: Showing %{from}–%{to} of %{total}
@@ -245,24 +399,50 @@ en:
245
399
  healthy: Healthy
246
400
  stale: Stale
247
401
  stalled: Stalled
402
+ time:
403
+ absolute_format: "%Y-%m-%d %H:%M:%S %Z"
404
+ clock_format: "%H:%M"
405
+ future: in %{distance}
406
+ now: now
407
+ past: "%{distance} ago"
408
+ range:
409
+ days:
410
+ one: "%{count} day"
411
+ other: "%{count} days"
412
+ hours:
413
+ one: "%{count} hour"
414
+ other: "%{count} hours"
415
+ minutes:
416
+ one: "%{count} minute"
417
+ other: "%{count} minutes"
418
+ units:
419
+ days: "%{count}d"
420
+ hours: "%{count}h"
421
+ milliseconds: "%{count}ms"
422
+ minutes: "%{count}m"
423
+ seconds: "%{count}s"
248
424
  insights:
249
425
  show:
250
426
  charts:
251
- failed_to_load: Failed to load chart data
252
- latency: Queue Latency (ms)
427
+ captions:
428
+ latency: Jobs waited %{avg} on average before a worker picked them up, p95 %{p95}
429
+ status: "%{success} succeeded, %{failed} failed, %{dead_lettered} dead-lettered"
430
+ throughput_html: "%{total} jobs in the last %{range}, peak %{peak} jobs/min %{at}"
431
+ data_table: Data table
432
+ latency: Queue Latency
253
433
  latency_avg: Avg
254
434
  latency_p95: P95
255
435
  no_data: No data yet
256
436
  series_name: Jobs/min
257
437
  status_distribution: Status Distribution
258
- throughput: Throughput (jobs/min)
438
+ throughput: Throughput
259
439
  description_html: Job performance metrics for the last %{range}
260
440
  latency_by_queue:
261
441
  empty: No latency data yet
262
442
  headers:
263
- avg: Avg (ms)
443
+ avg: Avg
264
444
  count: Count
265
- p95: P95 (ms)
445
+ p95: P95
266
446
  queue: Queue
267
447
  title: Latency by Queue
268
448
  live_streams:
@@ -282,6 +462,10 @@ en:
282
462
  job_class: Job Class
283
463
  max: Max
284
464
  title: Slowest Job Classes (avg duration)
465
+ statuses:
466
+ dead_lettered: Dead-lettered
467
+ failed: Failed
468
+ success: Succeeded
285
469
  streams:
286
470
  summary:
287
471
  active: Active
@@ -310,6 +494,20 @@ en:
310
494
  p99_latency: P99 Latency
311
495
  succeeded: Succeeded
312
496
  total_jobs: Total Jobs
497
+ tables:
498
+ captions:
499
+ latency_minute: Queue latency per minute
500
+ latency_step: Queue latency per %{step}
501
+ status: Jobs by final status
502
+ throughput_minute: Jobs per minute
503
+ throughput_step: Jobs per %{step}
504
+ headers:
505
+ avg: Avg
506
+ from: From
507
+ highest_p95: Highest p95
508
+ jobs: Jobs
509
+ share: Share
510
+ status: Status
313
511
  time_ranges:
314
512
  1h: 1h
315
513
  24h: 24h
@@ -317,60 +515,87 @@ en:
317
515
  7d: 7d
318
516
  title: Insights
319
517
  jobs:
320
- enqueued_table:
518
+ index:
519
+ discard_all: Discard All
520
+ discard_all_confirm: Discard all failed jobs?
521
+ discard_all_enqueued_notice: Discarded %{count} enqueued jobs and released their locks.
522
+ discard_selected: Discard Selected
523
+ discard_selected_confirm: Discard selected items?
524
+ discarded_selected:
525
+ one: Discarded 1 selected item.
526
+ other: Discarded %{count} selected items.
527
+ none_selected: No items selected.
528
+ retry_all: Retry All
529
+ retry_all_confirm: Retry all failed jobs?
530
+ title: Jobs
531
+ list:
321
532
  arguments: Arguments
322
533
  discard: Discard
323
- discard_all: Discard All
324
- discard_all_confirm: Discard all enqueued jobs and release their locks? This cannot be undone.
325
- discard_confirm: Discard this message?
326
- empty: No enqueued jobs
534
+ discard_all_enqueued: Discard All Enqueued
535
+ discard_all_enqueued_confirm: Discard all enqueued jobs and release their locks? This cannot be undone.
536
+ discard_confirm: Discard this job?
537
+ empty:
538
+ all: No jobs
539
+ blocked: No jobs blocked by a concurrency limit
540
+ ready: No jobs waiting to run
541
+ retrying: No jobs waiting to retry
542
+ running: No jobs running
543
+ scheduled: No scheduled jobs
327
544
  full_json_payload: Full JSON payload
328
545
  headers:
329
- enqueued: Enqueued
546
+ actions: Actions
547
+ attempts: Attempts
330
548
  id: ID
331
- job_class: Job Class
549
+ job: Job
332
550
  queue: Queue
333
- reads: Reads
551
+ reason: Next run / Reason
552
+ state: State
334
553
  headers_section: Headers
335
554
  job_id: 'Job ID:'
336
555
  metadata: Metadata
337
556
  metadata_labels:
557
+ concurrency_key: 'Concurrency key:'
558
+ enqueued: 'Enqueued:'
338
559
  last_read: 'Last read:'
339
560
  locale: 'Locale:'
340
561
  priority: 'Priority:'
341
- queue: 'Queue:'
342
562
  scheduled: 'Scheduled:'
343
563
  timezone: 'Timezone:'
344
564
  visible_at: 'Visible at:'
565
+ reasons:
566
+ blocked_html: Waiting for a concurrency slot on %{key} (held %{held}/%{limit}) · parked %{ago}
567
+ blocked_no_slots_html: Waiting for a concurrency slot on %{key} · parked %{ago}
568
+ lease_expired_html: Lease expired (worker likely died) — redelivering, attempt %{attempt}
569
+ no_workers_html: No healthy worker running
570
+ not_drained_html: No worker drains this queue
571
+ orphaned_html: Failed with %{error} — message no longer in queue
572
+ paused_html: Queue paused
573
+ retrying_dlq_html: 'Attempt %{attempt}/%{max} failed: %{error} — next read moves it to the DLQ'
574
+ retrying_due_html: 'Attempt %{attempt}/%{max} failed: %{error} — next attempt due now'
575
+ retrying_html: 'Attempt %{attempt}/%{max} failed: %{error} — next attempt %{time}'
576
+ running_html: Claimed %{ago} · lease expires %{time}
577
+ scheduled_html: Scheduled — runs %{time}
578
+ waiting_html: Waiting — %{count} ahead
579
+ waiting_priority_html: Waiting — %{count} ahead at this priority
580
+ waiting_unknown_html: Waiting to be picked up
345
581
  retry: Retry
346
582
  retry_confirm: Reset visibility timeout and retry?
347
- title: Enqueued Jobs
348
- failed_table:
349
- discard: Discard
350
- discard_confirm: Discard this job?
351
- empty: No failed jobs
352
- headers:
353
- actions: Actions
354
- error: Error
355
- failed: Failed
356
- id: ID
357
- queue: Queue
358
- retries: Retries
359
- retry: Retry
360
- title: Failed Jobs
361
- index:
362
- discard_all: Discard All
363
- discard_all_confirm: Discard all failed jobs?
364
- discard_all_enqueued_notice: Discarded %{count} enqueued jobs and released their locks.
365
- discard_selected: Discard Selected
366
- discard_selected_confirm: Discard selected items?
367
- discarded_selected:
368
- one: Discarded 1 selected item.
369
- other: Discarded %{count} selected items.
370
- none_selected: No items selected.
371
- retry_all: Retry All
372
- retry_all_confirm: Retry all failed jobs?
373
- title: Jobs
583
+ states:
584
+ blocked: Blocked
585
+ ready: Ready
586
+ retrying: Retrying
587
+ running: Running
588
+ scheduled: Scheduled
589
+ tabs:
590
+ all: All
591
+ blocked: Blocked
592
+ ready: Ready
593
+ retrying: Retrying
594
+ running: Running
595
+ scheduled: Scheduled
596
+ tabs_label: Job states
597
+ view_failure: View failure
598
+ view_locks: View locks
374
599
  show:
375
600
  back: Back to Jobs
376
601
  backtrace: Backtrace
@@ -423,6 +648,7 @@ en:
423
648
  other: Discard the %{count} jobs parked behind this key? They will never run.
424
649
  empty: No concurrency keys in use
425
650
  headers:
651
+ actions: Actions
426
652
  in_use: In use
427
653
  key: Key
428
654
  lease: Lease
@@ -456,6 +682,7 @@ en:
456
682
  discard_selected_confirm: Discard selected locks?
457
683
  empty: No active locks
458
684
  headers:
685
+ actions: Actions
459
686
  age: Age
460
687
  lock_key: Lock Key
461
688
  msg_id: Message ID
@@ -483,8 +710,6 @@ en:
483
710
  pending: Pending
484
711
  published: Published
485
712
  stats:
486
- no_age: "—"
487
- oldest_age: "%{seconds}s"
488
713
  oldest_unpublished: Oldest Unpublished
489
714
  total: Total
490
715
  unpublished: Unpublished
@@ -493,18 +718,76 @@ en:
493
718
  index:
494
719
  title: Processes
495
720
  processes_table:
721
+ capsule: capsule %{capsule} · process %{process}
496
722
  empty: No processes running
497
723
  headers:
498
- hostname: Hostname
499
- kind: Kind
500
- last_heartbeat: Last Heartbeat
501
- metadata: Metadata
502
- pid: PID
724
+ activity: Activity
725
+ heartbeat: Heartbeat
726
+ process: Process
727
+ recycle: Recycle
503
728
  status: Status
504
- rates:
505
- dequeued: dequeued
506
- failed: failed
507
- processed: processed
729
+ no_raw_metadata: No other metadata
730
+ raw_metadata: Other metadata
731
+ started: Started
732
+ under_supervisor: under supervisor %{pid}
733
+ uptime: up %{duration}
734
+ state:
735
+ activity:
736
+ buffered: "%{number} buffered"
737
+ busy_slots: "%{busy}/%{capacity} slots busy"
738
+ busy_threads: "%{busy}/%{capacity} threads busy"
739
+ consumes: Consumes %{topics}
740
+ drains: Drains %{queues}
741
+ drains_all: Drains every queue
742
+ failed_rate: "%{rate} failed/s"
743
+ handled: "%{number} handled"
744
+ maintenance: Runs maintenance every %{interval} (scheduled jobs, stale processes, archives)
745
+ outbox: Publishes outbox entries
746
+ rate: "%{rate} jobs/s"
747
+ schedule:
748
+ one: Runs %{count} recurring task, checks every %{interval}
749
+ other: Runs %{count} recurring tasks, checks every %{interval}
750
+ slots:
751
+ one: "%{count} slot"
752
+ other: "%{count} slots"
753
+ supervises: Supervises %{children}
754
+ supervises_none: Supervises no processes on this host
755
+ threads:
756
+ one: "%{count} thread"
757
+ other: "%{count} threads"
758
+ kinds:
759
+ consumer:
760
+ one: "%{count} consumer"
761
+ other: "%{count} consumers"
762
+ dispatcher:
763
+ one: dispatcher
764
+ other: "%{count} dispatchers"
765
+ outbox_poller:
766
+ one: outbox poller
767
+ other: "%{count} outbox pollers"
768
+ scheduler:
769
+ one: scheduler
770
+ other: "%{count} schedulers"
771
+ worker:
772
+ one: "%{count} worker"
773
+ other: "%{count} workers"
774
+ next:
775
+ stale_reaped: probably dead; removed at the dispatcher's next sweep (within %{within})
776
+ stale_unreaped: probably dead; no live dispatcher to remove it
777
+ stalled_maintenance: scheduled jobs, stale-process cleanup and archive compaction are not advancing
778
+ stalled_schedule: recurring tasks are not being enqueued
779
+ stalled_standalone: no supervisor to restart it
780
+ stalled_supervised: the supervisor's watchdog restarts it
781
+ reasons:
782
+ healthy_detection_off_html: Heartbeat %{ago} (limit %{limit}) · stall detection off
783
+ healthy_heartbeat_html: Heartbeat %{ago} (limit %{limit})
784
+ healthy_loop_html: Loop ticked %{ago} (limit %{limit})
785
+ stale_html: No heartbeat for %{age} (limit %{limit})
786
+ stalled_html: Loop last ticked %{ago} (limit %{limit})
787
+ recycle:
788
+ jobs: "%{done} / %{limit} jobs"
789
+ lifetime: up %{uptime} / %{limit}
790
+ memory: "%{used} / %{limit} MB"
508
791
  queues:
509
792
  destroy:
510
793
  success: Queue %{name} deleted.
@@ -517,59 +800,70 @@ en:
517
800
  headers:
518
801
  actions: Actions
519
802
  depth: Depth
520
- newest: Newest (s)
521
- oldest_claimable: Oldest claimable (s)
803
+ newest: Newest
804
+ oldest_claimable: Oldest claimable
522
805
  parked: Parked
523
806
  queue: Queue
524
807
  total_ever: Total Ever
525
808
  visible: Visible
526
809
  pause: Pause
527
810
  pause_confirm: Pause processing for %{name}?
528
- paused: Paused
529
811
  purge: Purge
530
812
  purge_confirm: Purge all messages from %{name}?
531
813
  resume: Resume
532
814
  show:
533
- arguments: Arguments
534
815
  back: Back to Queues
535
816
  delete_confirm: Permanently delete this queue? This cannot be undone.
536
817
  delete_queue: Delete Queue
537
- depth: 'Depth:'
538
- discard: Discard
539
- discard_confirm: Discard this message?
540
- empty: Queue is empty
541
- full_json_payload: Full JSON payload
542
- headers:
543
- enqueued: Enqueued
544
- id: ID
545
- job_class: Job Class
546
- reads: Reads
547
- vt: VT
548
- headers_section: Headers
549
- job_id: 'Job ID:'
818
+ dlq_link: Retry or discard them on the Dead Letter page
819
+ dlq_pointer:
820
+ one: Dead-letter queue for %{logical} — %{count} message.
821
+ other: Dead-letter queue for %{logical} — %{count} messages.
550
822
  message_discard_failed: Could not discard message.
551
823
  message_discarded: Message discarded.
552
824
  message_retried: Message visibility reset.
553
825
  message_retry_failed: Could not retry message.
554
- metadata: Metadata
555
- metadata_labels:
556
- last_read: 'Last read:'
557
- locale: 'Locale:'
558
- priority: 'Priority:'
559
- queue: 'Queue:'
560
- scheduled: 'Scheduled at:'
561
- timezone: 'Timezone:'
562
- visible_at: 'Visible at:'
563
- oldest: 'Oldest:'
564
- oldest_claimable: 'Oldest claimable:'
565
- parked: 'Parked:'
826
+ metrics:
827
+ claimable: Claimable now
828
+ depth: Depth
829
+ oldest: Oldest
830
+ oldest_claimable: Oldest claimable
831
+ parked: Parked
832
+ total: Total ever
833
+ other_levels: 'Other priority levels:'
566
834
  pause: Pause
567
835
  pause_confirm: Pause processing?
568
836
  purge_confirm: Purge all messages?
569
837
  purge_queue: Purge Queue
570
838
  resume: Resume
571
- retry: Retry
572
- retry_confirm: Reset visibility timeout and retry?
839
+ summary:
840
+ backlog_html: "%{visible} claimable now, oldest waiting %{age} · %{parked} parked (scheduled or retrying)"
841
+ backlog_parked_only_html: Nothing claimable now · %{parked} parked (scheduled or retrying)
842
+ dlq_html: 'Dead-letter queue for %{logical}: jobs land here after %{max} failed attempts'
843
+ drained_by_html:
844
+ one: Drained by %{capsules} (%{count} healthy worker)
845
+ other: Drained by %{capsules} (%{count} healthy workers)
846
+ drained_live_html:
847
+ one: Drained by %{count} healthy worker listening on this queue
848
+ other: Drained by %{count} healthy workers listening on this queue
849
+ drained_wildcard_html:
850
+ one: Drained by a wildcard worker capsule (%{count} healthy worker)
851
+ other: Drained by a wildcard worker capsule (%{count} healthy workers)
852
+ empty_html: Nothing waiting
853
+ handler_html: Event handler queue — drained by consumers (%{count} healthy)
854
+ no_consumers_html:
855
+ one: Event handler queue with no healthy consumer — %{count} event waits
856
+ other: Event handler queue with no healthy consumer — %{count} events wait
857
+ no_workers_html:
858
+ one: No healthy worker running — %{count} job is claimable but nothing claims it
859
+ other: No healthy worker running — %{count} jobs are claimable but nothing claims them
860
+ not_drained_html: No worker capsule drains this queue — jobs wait until one is configured
861
+ paused_circuit_breaker_html: 'Paused automatically %{ago} after %{failures} consecutive failures — resumes %{time} (trip #%{trip})'
862
+ paused_operator_html: Paused %{ago} — %{reason}
863
+ paused_operator_no_reason_html: Paused %{ago} by an operator
864
+ priority_level_html: Priority level %{level} of %{logical}
865
+ stream_html: Stream queue — delivered by peek, not drained by workers
866
+ summary_label: Queue summary
573
867
  table_health:
574
868
  headers:
575
869
  bloat: Bloat
@@ -579,8 +873,6 @@ en:
579
873
  table: Table
580
874
  oldest_txn: 'Oldest open transaction:'
581
875
  title: Table Health
582
- total: 'Total:'
583
- visible: 'Visible:'
584
876
  recurring_tasks:
585
877
  index:
586
878
  task_count: