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
@@ -1,5 +1,5 @@
1
1
  <div class="mb-6">
2
- <%= link_to "\u2190 #{t("pgbus.jobs.show.back")}", pgbus.jobs_path, class: "text-sm text-indigo-600 hover:text-indigo-500" %>
2
+ <%= pgbus_link_to "\u2190 #{t("pgbus.jobs.show.back")}", pgbus.jobs_path %>
3
3
  </div>
4
4
 
5
5
  <% if @job %>
@@ -8,45 +8,45 @@
8
8
  <div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
9
9
  <dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
10
10
  <div>
11
- <dt class="text-sm font-medium text-gray-500"><%= t("pgbus.jobs.show.labels.queue") %></dt>
11
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.jobs.show.labels.queue") %></dt>
12
12
  <dd class="text-sm text-gray-900 dark:text-white"><%= @job["queue_name"] %></dd>
13
13
  </div>
14
14
  <div>
15
- <dt class="text-sm font-medium text-gray-500"><%= t("pgbus.jobs.show.labels.failed_at") %></dt>
16
- <dd class="text-sm text-gray-900 dark:text-white"><%= @job["failed_at"] %></dd>
15
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.jobs.show.labels.failed_at") %></dt>
16
+ <dd class="text-sm text-gray-900 dark:text-white"><%= pgbus_timestamp(@job["failed_at"]) %></dd>
17
17
  </div>
18
18
  <div>
19
- <dt class="text-sm font-medium text-gray-500"><%= t("pgbus.jobs.show.labels.error_class") %></dt>
20
- <dd class="text-sm text-red-600 font-medium"><%= @job["error_class"] %></dd>
19
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.jobs.show.labels.error_class") %></dt>
20
+ <dd class="text-sm text-red-600 dark:text-red-400 font-medium"><%= @job["error_class"] %></dd>
21
21
  </div>
22
22
  <div>
23
- <dt class="text-sm font-medium text-gray-500"><%= t("pgbus.jobs.show.labels.retry_count") %></dt>
23
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.jobs.show.labels.retry_count") %></dt>
24
24
  <dd class="text-sm text-gray-900 dark:text-white"><%= @job["retry_count"] %></dd>
25
25
  </div>
26
26
  </dl>
27
27
  </div>
28
28
 
29
29
  <div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
30
- <h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.jobs.show.error_message") %></h2>
31
- <p class="text-sm text-red-700"><%= @job["error_message"] %></p>
30
+ <h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.jobs.show.error_message") %></h2>
31
+ <p class="text-sm text-red-700 dark:text-red-400"><%= @job["error_message"] %></p>
32
32
  </div>
33
33
 
34
34
  <% if @job["backtrace"].present? %>
35
35
  <div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
36
- <h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.jobs.show.backtrace") %></h2>
37
- <pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto max-h-96"><%= @job["backtrace"] %></pre>
36
+ <h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.jobs.show.backtrace") %></h2>
37
+ <pre class="text-xs text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto max-h-96"><%= @job["backtrace"] %></pre>
38
38
  </div>
39
39
  <% end %>
40
40
 
41
41
  <% if (context = pgbus_job_context(@job["payload"])) %>
42
42
  <div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6" data-testid="job-context">
43
- <h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.jobs.show.context") %></h2>
43
+ <h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.jobs.show.context") %></h2>
44
44
  <% context.each do |klass_name, attrs| %>
45
45
  <h3 class="text-xs font-semibold text-gray-700 dark:text-gray-300 mt-3 mb-1 font-mono"><%= klass_name %></h3>
46
46
  <dl class="grid grid-cols-1 gap-x-4 gap-y-1 sm:grid-cols-2">
47
47
  <% attrs.each do |name, value| %>
48
48
  <div class="flex gap-2 text-xs">
49
- <dt class="text-gray-500 font-mono"><%= name %></dt>
49
+ <dt class="text-gray-500 dark:text-gray-400 font-mono"><%= name %></dt>
50
50
  <dd class="text-gray-900 dark:text-white font-mono break-all"><%= value %></dd>
51
51
  </div>
52
52
  <% end %>
@@ -56,19 +56,17 @@
56
56
  <% end %>
57
57
 
58
58
  <div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
59
- <h2 class="text-sm font-medium text-gray-500 mb-2"><%= t("pgbus.jobs.show.payload") %></h2>
60
- <pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(pgbus_parse_message(@job["payload"])) %></pre>
59
+ <h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2"><%= t("pgbus.jobs.show.payload") %></h2>
60
+ <pre class="text-xs text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(pgbus_parse_message(@job["payload"])) %></pre>
61
61
  </div>
62
62
 
63
63
  <div class="flex space-x-2">
64
- <%= button_to t("pgbus.jobs.show.retry"), pgbus.retry_job_path(@job["id"]), method: :post,
65
- class: "rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500" %>
66
- <%= button_to t("pgbus.jobs.show.discard"), pgbus.discard_job_path(@job["id"]), method: :post,
67
- class: "rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-500",
64
+ <%= pgbus_button_to t("pgbus.jobs.show.retry"), pgbus.retry_job_path(@job["id"]), method: :post, variant: :primary %>
65
+ <%= pgbus_button_to t("pgbus.jobs.show.discard"), pgbus.discard_job_path(@job["id"]), method: :post, variant: :danger,
68
66
  data: { turbo_confirm: t("pgbus.jobs.show.discard_confirm") } %>
69
67
  </div>
70
68
  <% else %>
71
69
  <div class="text-center py-12">
72
- <p class="text-gray-400"><%= t("pgbus.jobs.show.not_found") %></p>
70
+ <p class="text-gray-500 dark:text-gray-400"><%= t("pgbus.jobs.show.not_found") %></p>
73
71
  </div>
74
72
  <% end %>
@@ -1,4 +1,5 @@
1
- <turbo-frame id="locks-concurrency" data-auto-refresh data-src="<%= pgbus.locks_path(frame: 'concurrency') %>">
1
+ <turbo-frame id="locks-concurrency" data-auto-refresh data-turbo-action="advance"
2
+ data-src="<%= pgbus.locks_path(request.query_parameters.merge(frame: "concurrency")) %>">
2
3
  <div class="mb-8">
3
4
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white"><%= t("pgbus.locks.concurrency.title") %></h2>
4
5
  <p class="mt-1 mb-3 text-sm text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.description") %></p>
@@ -9,25 +10,25 @@
9
10
  <p class="mt-1 text-3xl font-semibold <%= @concurrency[:parked_total] > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-gray-900 dark:text-white' %>">
10
11
  <%= pgbus_number(@concurrency[:parked_total]) %>
11
12
  </p>
12
- <p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.cards.parked_jobs_hint") %></p>
13
+ <p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.parked_jobs_hint") %></p>
13
14
  </div>
14
15
 
15
16
  <div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
16
17
  <p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.oldest_wait") %></p>
17
18
  <p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= pgbus_duration(@concurrency[:oldest_parked_age_sec]) %></p>
18
- <p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.cards.oldest_wait_hint") %></p>
19
+ <p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.oldest_wait_hint") %></p>
19
20
  </div>
20
21
 
21
22
  <div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
22
23
  <p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.slots_held") %></p>
23
24
  <p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@concurrency[:slots_held]) %></p>
24
- <p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.cards.slots_held_hint") %></p>
25
+ <p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.slots_held_hint") %></p>
25
26
  </div>
26
27
 
27
28
  <div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
28
29
  <p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.keys_at_limit") %></p>
29
30
  <p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@concurrency[:keys_at_limit]) %></p>
30
- <p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.cards.keys_at_limit_hint") %></p>
31
+ <p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.cards.keys_at_limit_hint") %></p>
31
32
  </div>
32
33
  </div>
33
34
 
@@ -40,43 +41,41 @@
40
41
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.headers.lease") %></th>
41
42
  <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.headers.parked") %></th>
42
43
  <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.headers.oldest_wait") %></th>
43
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"></th>
44
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><span class="sr-only"><%= t("pgbus.locks.concurrency.headers.actions") %></span></th>
44
45
  </tr>
45
46
  </thead>
46
47
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
47
48
  <% @concurrency[:keys].each do |row| %>
48
49
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
49
- <td data-label="Key" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= row[:key] %></td>
50
- <td data-label="In use" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300">
50
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.key") %>" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= row[:key] %></td>
51
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.in_use") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300">
51
52
  <% if row[:value] %>
52
53
  <%= row[:value] %> / <%= row[:max_value] %>
53
54
  <% else %>
54
55
  —
55
56
  <% end %>
56
57
  </td>
57
- <td data-label="Lease" class="px-4 py-3 text-sm">
58
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.lease") %>" class="px-4 py-3 text-sm">
58
59
  <% if row[:value].nil? %>
59
- <span class="text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.lease.none") %></span>
60
+ <span class="text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.concurrency.lease.none") %></span>
60
61
  <% elsif row[:lease_fresh] %>
61
- <span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800"><%= t("pgbus.locks.concurrency.lease.live") %></span>
62
- <span class="ml-1 text-xs text-gray-400 dark:text-gray-500"><%= pgbus_time_ago_future(row[:expires_at]) %></span>
62
+ <span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/30 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:text-green-400"><%= t("pgbus.locks.concurrency.lease.live") %></span>
63
+ <span class="ml-1 text-xs text-gray-500 dark:text-gray-400"><%= pgbus_time(row[:expires_at]) %></span>
63
64
  <% else %>
64
- <span class="inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800"><%= t("pgbus.locks.concurrency.lease.expired") %></span>
65
+ <span class="inline-flex items-center rounded-full bg-red-100 dark:bg-red-900/30 px-2.5 py-0.5 text-xs font-medium text-red-800 dark:text-red-400"><%= t("pgbus.locks.concurrency.lease.expired") %></span>
65
66
  <% end %>
66
67
  </td>
67
- <td data-label="Parked" class="px-4 py-3 text-sm text-right <%= row[:parked_count] > 0 ? 'text-amber-600 dark:text-amber-400 font-semibold' : 'text-gray-500 dark:text-gray-400' %>"><%= pgbus_number(row[:parked_count]) %></td>
68
- <td data-label="Oldest wait" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(row[:oldest_parked_age_sec]) %></td>
69
- <td class="px-4 py-3 text-sm text-right">
68
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.parked") %>" class="px-4 py-3 text-sm text-right <%= row[:parked_count] > 0 ? 'text-amber-700 dark:text-amber-400 font-semibold' : 'text-gray-500 dark:text-gray-400' %>"><%= pgbus_number(row[:parked_count]) %></td>
69
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.oldest_wait") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(row[:oldest_parked_age_sec]) %></td>
70
+ <td data-label="<%= t("pgbus.locks.concurrency.headers.actions") %>" class="px-4 py-3 text-sm text-right">
70
71
  <div class="flex items-center justify-end space-x-3">
71
- <%= button_to t("pgbus.locks.concurrency.release"), pgbus.release_key_locks_path,
72
- method: :post, params: { key: row[:key] },
73
- class: "text-xs text-indigo-600 hover:text-indigo-800 font-medium",
72
+ <%= pgbus_button_to t("pgbus.locks.concurrency.release"), pgbus.release_key_locks_path,
73
+ method: :post, params: { key: row[:key] }, variant: :link, size: :sm,
74
74
  data: { turbo_frame: "_top",
75
75
  turbo_confirm: row[:lease_fresh] ? t("pgbus.locks.concurrency.release_confirm_live", key: row[:key]) : t("pgbus.locks.concurrency.release_confirm", key: row[:key]) } %>
76
76
  <% if row[:parked_count] > 0 %>
77
- <%= button_to t("pgbus.locks.concurrency.discard_parked"), pgbus.discard_parked_locks_path,
78
- method: :post, params: { key: row[:key] },
79
- class: "text-xs text-red-600 hover:text-red-800 font-medium",
77
+ <%= pgbus_button_to t("pgbus.locks.concurrency.discard_parked"), pgbus.discard_parked_locks_path,
78
+ method: :post, params: { key: row[:key] }, variant: :link, size: :sm, tone: :red,
80
79
  data: { turbo_frame: "_top",
81
80
  turbo_confirm: t("pgbus.locks.concurrency.discard_parked_confirm", count: row[:parked_count]) } %>
82
81
  <% end %>
@@ -85,10 +84,13 @@
85
84
  </tr>
86
85
  <% end %>
87
86
  <% if @concurrency[:keys].empty? %>
88
- <tr><td colspan="6" class="px-4 py-8 text-center text-sm text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.concurrency.empty") %></td></tr>
87
+ <%= render "pgbus/shared/empty_row", colspan: 6, message: t("pgbus.locks.concurrency.empty") %>
89
88
  <% end %>
90
89
  </tbody>
91
90
  </table>
91
+ <%= render "pgbus/shared/pager", page: @keys_page, per_page: @per_page, shown: @concurrency[:keys].size,
92
+ total_count: @keys_count.total, has_more: @keys_count.capped? && @concurrency[:keys].size == @per_page,
93
+ page_path: ->(n) { pgbus.locks_path(keys_page: n, page: @page) } %>
92
94
  </div>
93
95
  </div>
94
96
  </turbo-frame>
@@ -8,15 +8,11 @@
8
8
  <div class="flex items-center space-x-2">
9
9
  <div data-bulk-actions class="hidden flex items-center space-x-2">
10
10
  <span class="text-sm text-gray-500 dark:text-gray-400"><span data-bulk-count>0</span> <%= t("pgbus.helpers.bulk_selected") %></span>
11
- <button type="submit" form="bulk-discard-locks-form"
12
- class="rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-500"
13
- data-turbo-confirm="<%= t("pgbus.locks.index.discard_selected_confirm") %>">
14
- <%= t("pgbus.locks.index.discard_selected") %>
15
- </button>
11
+ <%= pgbus_button_tag t("pgbus.locks.index.discard_selected"), variant: :danger, form: "bulk-discard-locks-form",
12
+ confirm: t("pgbus.locks.index.discard_selected_confirm") %>
16
13
  </div>
17
- <%= button_to t("pgbus.locks.index.discard_all"), pgbus.discard_all_locks_path, method: :post,
18
- class: "rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-500",
19
- data: { turbo_confirm: t("pgbus.locks.index.discard_all_confirm") } %>
14
+ <%= pgbus_button_to t("pgbus.locks.index.discard_all"), pgbus.discard_all_locks_path, method: :post,
15
+ variant: :danger, data: { turbo_confirm: t("pgbus.locks.index.discard_all_confirm") } %>
20
16
  </div>
21
17
  <% end %>
22
18
  </div>
@@ -34,15 +30,15 @@
34
30
  <th class="w-10 px-4 py-3">
35
31
  <input type="checkbox" data-bulk-select-all
36
32
  aria-label="<%= t("pgbus.helpers.bulk_select_all") %>"
37
- class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600">
33
+ class="h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-600">
38
34
  </th>
39
35
  <% end %>
40
36
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.lock_key") %></th>
41
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.queue_name", default: "Queue") %></th>
42
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.msg_id", default: "Message ID") %></th>
37
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.queue_name") %></th>
38
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.msg_id") %></th>
43
39
  <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.age") %></th>
44
40
  <% if @locks.any? %>
45
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"></th>
41
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><span class="sr-only"><%= t("pgbus.locks.index.headers.actions") %></span></th>
46
42
  <% end %>
47
43
  </tr>
48
44
  </thead>
@@ -53,28 +49,30 @@
53
49
  <input type="checkbox" name="lock_keys[]" value="<%= lock[:lock_key] %>"
54
50
  aria-label="<%= t("pgbus.helpers.bulk_select_row", id: lock[:lock_key]) %>"
55
51
  form="bulk-discard-locks-form" data-bulk-item
56
- class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600">
52
+ class="h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-600">
57
53
  </td>
58
- <td data-label="Lock Key" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
59
- <td data-label="Queue" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:queue_name] %></td>
60
- <td data-label="Message ID" class="px-4 py-3 text-sm text-right font-mono text-gray-500 dark:text-gray-400"><%= lock[:msg_id] %></td>
61
- <td data-label="Age" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
54
+ <td data-label="<%= t("pgbus.locks.index.headers.lock_key") %>" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
55
+ <td data-label="<%= t("pgbus.locks.index.headers.queue_name") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:queue_name] %></td>
56
+ <td data-label="<%= t("pgbus.locks.index.headers.msg_id") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-500 dark:text-gray-400"><%= lock[:msg_id] %></td>
57
+ <td data-label="<%= t("pgbus.locks.index.headers.age") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
62
58
  <% if lock[:age_seconds] %>
63
59
  <%= pgbus_duration(lock[:age_seconds]) %>
64
60
  <% end %>
65
61
  </td>
66
- <td class="px-4 py-3 text-sm text-right">
67
- <%= button_to t("pgbus.locks.index.discard"), pgbus.discard_lock_path(lock[:lock_key]),
68
- method: :post,
69
- class: "text-xs text-red-600 hover:text-red-800 font-medium",
62
+ <td data-label="<%= t("pgbus.locks.index.headers.actions") %>" class="px-4 py-3 text-sm text-right">
63
+ <%= pgbus_button_to t("pgbus.locks.index.discard"), pgbus.discard_lock_path(lock[:lock_key]),
64
+ method: :post, variant: :link, size: :sm, tone: :red,
70
65
  data: { turbo_confirm: t("pgbus.locks.index.discard_confirm"), turbo_frame: "_top" } %>
71
66
  </td>
72
67
  </tr>
73
68
  <% end %>
74
69
  <% if @locks.empty? %>
75
- <tr><td colspan="4" class="px-4 py-8 text-center text-sm text-gray-400 dark:text-gray-500"><%= t("pgbus.locks.index.empty") %></td></tr>
70
+ <%= render "pgbus/shared/empty_row", colspan: 4, message: t("pgbus.locks.index.empty") %>
76
71
  <% end %>
77
72
  </tbody>
78
73
  </table>
74
+ <%= render "pgbus/shared/pager", page: @page, per_page: @per_page, shown: @locks.size,
75
+ total_count: @locks_count.total, has_more: @locks_count.capped? && @locks.size == @per_page,
76
+ page_path: ->(n) { pgbus.locks_path(page: n, keys_page: @keys_page) } %>
79
77
  </div>
80
78
  </div>
@@ -1,55 +1,58 @@
1
1
  <div class="mb-6">
2
2
  <h1 class="text-2xl font-bold text-gray-900 dark:text-white"><%= t("pgbus.outbox.index.title") %></h1>
3
- <p class="mt-1 text-sm text-gray-500"><%= t("pgbus.outbox.index.description") %></p>
3
+ <p class="mt-1 text-sm text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.description") %></p>
4
4
  </div>
5
5
 
6
6
  <div class="mb-6 grid grid-cols-3 gap-4">
7
- <div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
8
- <dt class="text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.stats.unpublished") %></dt>
7
+ <dl class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
8
+ <dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.stats.unpublished") %></dt>
9
9
  <dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@stats[:unpublished]) %></dd>
10
- </div>
11
- <div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
12
- <dt class="text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.stats.total") %></dt>
10
+ </dl>
11
+ <dl class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
12
+ <dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.stats.total") %></dt>
13
13
  <dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@stats[:total]) %></dd>
14
- </div>
15
- <div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
16
- <dt class="text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.stats.oldest_unpublished") %></dt>
17
- <dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= @stats[:oldest_unpublished_age] ? t("pgbus.outbox.index.stats.oldest_age", seconds: @stats[:oldest_unpublished_age]) : t("pgbus.outbox.index.stats.no_age") %></dd>
18
- </div>
14
+ </dl>
15
+ <dl class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
16
+ <dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.stats.oldest_unpublished") %></dt>
17
+ <dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_duration(@stats[:oldest_unpublished_age]) %></dd>
18
+ </dl>
19
19
  </div>
20
20
 
21
21
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
22
22
  <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
23
23
  <thead class="bg-gray-50 dark:bg-gray-900">
24
24
  <tr>
25
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.id") %></th>
26
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.queue_topic") %></th>
27
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.payload") %></th>
28
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.priority") %></th>
29
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.status") %></th>
30
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.created") %></th>
25
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.id") %></th>
26
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.queue_topic") %></th>
27
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.payload") %></th>
28
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.priority") %></th>
29
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.status") %></th>
30
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.outbox.index.headers.created") %></th>
31
31
  </tr>
32
32
  </thead>
33
33
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
34
34
  <% @entries.each do |entry| %>
35
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
36
- <td data-label="ID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= entry.id %></td>
37
- <td data-label="Routing Key" class="px-4 py-3 text-sm text-gray-700"><%= entry.routing_key || entry.queue_name %></td>
38
- <td data-label="Payload" class="px-4 py-3 text-sm text-gray-500 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
- <td data-label="Priority" class="px-4 py-3 text-sm text-right text-gray-500"><%= entry.priority %></td>
40
- <td data-label="Status" class="px-4 py-3 text-sm text-right">
35
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
36
+ <td data-label="<%= t("pgbus.outbox.index.headers.id") %>" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-200"><%= entry.id %></td>
37
+ <td data-label="<%= t("pgbus.outbox.index.headers.queue_topic") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200"><%= entry.routing_key || entry.queue_name %></td>
38
+ <td data-label="<%= t("pgbus.outbox.index.headers.payload") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
+ <td data-label="<%= t("pgbus.outbox.index.headers.priority") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= entry.priority %></td>
40
+ <td data-label="<%= t("pgbus.outbox.index.headers.status") %>" class="px-4 py-3 text-sm text-right">
41
41
  <% if entry.published_at %>
42
- <span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"><%= t("pgbus.outbox.index.published") %></span>
42
+ <span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/30 px-2 py-0.5 text-xs font-medium text-green-800 dark:text-green-400"><%= t("pgbus.outbox.index.published") %></span>
43
43
  <% else %>
44
- <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.outbox.index.pending") %></span>
44
+ <span class="inline-flex items-center rounded-full bg-yellow-100 dark:bg-yellow-900/30 px-2 py-0.5 text-xs font-medium text-yellow-800 dark:text-yellow-400"><%= t("pgbus.outbox.index.pending") %></span>
45
45
  <% end %>
46
46
  </td>
47
- <td data-label="Created" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_time_ago(entry.created_at) %></td>
47
+ <td data-label="<%= t("pgbus.outbox.index.headers.created") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_time(entry.created_at) %></td>
48
48
  </tr>
49
49
  <% end %>
50
50
  <% if @entries.empty? %>
51
- <tr><td colspan="6" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.outbox.index.empty") %></td></tr>
51
+ <%= render "pgbus/shared/empty_row", colspan: 6, message: t("pgbus.outbox.index.empty") %>
52
52
  <% end %>
53
53
  </tbody>
54
54
  </table>
55
+ <%= render "pgbus/shared/pager", page: @page, per_page: @per_page, shown: @entries.size,
56
+ total_count: @entries_count.total, has_more: @entries_count.capped? && @entries.size == @per_page,
57
+ page_path: ->(n) { pgbus.outbox_index_path(page: n) } %>
55
58
  </div>
@@ -3,36 +3,17 @@
3
3
  <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.kind") %></th>
7
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.hostname") %></th>
8
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.pid") %></th>
9
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.status") %></th>
10
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.last_heartbeat") %></th>
11
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.metadata") %></th>
6
+ <% %w[process status activity recycle heartbeat].each do |column| %>
7
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.processes.processes_table.headers.#{column}") %></th>
8
+ <% end %>
12
9
  </tr>
13
10
  </thead>
14
11
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
15
- <% @processes.each do |p| %>
16
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
17
- <td data-label="Kind" class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
- <td data-label="Host" class="px-4 py-3 text-sm text-gray-700"><%= p[:hostname] %></td>
19
- <td data-label="PID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= p[:pid] %></td>
20
- <td data-label="Status" class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:status] || p[:healthy]) %></td>
21
- <td data-label="Last Heartbeat" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(p[:last_heartbeat_at]) %></td>
22
- <td data-label="Metadata" class="px-4 py-3 text-sm text-gray-500 font-mono text-xs max-w-xs truncate">
23
- <% if p[:metadata].is_a?(Hash) %>
24
- <% if (rates = pgbus_worker_rates(p[:metadata])) %>
25
- <span class="inline-flex items-center rounded bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 px-1.5 py-0.5 text-xs mr-1 font-medium"><%= rates %></span>
26
- <% end %>
27
- <% pgbus_display_metadata(p[:metadata]).each do |k, v| %>
28
- <span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-xs mr-1"><%= k %>: <%= v %></span>
29
- <% end %>
30
- <% end %>
31
- </td>
32
- </tr>
12
+ <% @processes.each do |process| %>
13
+ <%= render "pgbus/processes/row", process: process, state: @process_states.fetch(process[:id]) %>
33
14
  <% end %>
34
15
  <% if @processes.empty? %>
35
- <tr><td colspan="6" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.processes.processes_table.empty") %></td></tr>
16
+ <%= render "pgbus/shared/empty_row", colspan: 5, message: t("pgbus.processes.processes_table.empty") %>
36
17
  <% end %>
37
18
  </tbody>
38
19
  </table>
@@ -0,0 +1,48 @@
1
+ <%# One process plus its expansion row (issue #493). Locals: process (a
2
+ DataSource#processes hash), state (its ProcessState::Result). A child of
3
+ a supervisor row is indented under it. %>
4
+ <% metadata = process[:metadata].is_a?(Hash) ? process[:metadata] : {} %>
5
+ <% raw = pgbus_process_raw_metadata(metadata) %>
6
+ <tr id="process-<%= process[:id] %>" class="hover:bg-gray-50 dark:hover:bg-gray-700/50" data-process-row data-status="<%= state.status %>">
7
+ <td data-label="<%= t("pgbus.processes.processes_table.headers.process") %>" class="px-4 py-3 text-sm align-top <%= "lg:pl-10" if state.supervisor_pid %>">
8
+ <details data-job-toggle class="group">
9
+ <summary class="cursor-pointer list-none font-medium text-indigo-600 hover:text-indigo-800 dark:hover:text-indigo-300 dark:text-indigo-400"><%= process[:kind] %></summary>
10
+ </details>
11
+ <span class="block lg:whitespace-nowrap text-xs font-mono text-gray-700 dark:text-gray-300"><%= process[:hostname] %> · <%= process[:pid] %></span>
12
+ <% if metadata["capsule"] %>
13
+ <span class="block text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.processes.processes_table.capsule", capsule: metadata["capsule"], process: metadata["process"] || "—") %></span>
14
+ <% end %>
15
+ <% if state.supervisor_pid %>
16
+ <span class="block lg:whitespace-nowrap text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.processes.processes_table.under_supervisor", pid: state.supervisor_pid) %></span>
17
+ <% end %>
18
+ </td>
19
+ <td data-label="<%= t("pgbus.processes.processes_table.headers.status") %>" class="px-4 py-3 text-sm align-top">
20
+ <%= render "pgbus/processes/status", state: state %>
21
+ </td>
22
+ <td data-label="<%= t("pgbus.processes.processes_table.headers.activity") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200 align-top" data-testid="process-activity"><%= pgbus_process_activity(state) %></td>
23
+ <td data-label="<%= t("pgbus.processes.processes_table.headers.recycle") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200 align-top" data-testid="process-recycle"><%= pgbus_process_recycle(state) %></td>
24
+ <td data-label="<%= t("pgbus.processes.processes_table.headers.heartbeat") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200 align-top">
25
+ <span class="block"><%= pgbus_time(process[:last_heartbeat_at]) %></span>
26
+ <% if state.uptime_seconds %>
27
+ <span class="block text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.processes.processes_table.uptime", duration: pgbus_duration(state.uptime_seconds)) %></span>
28
+ <% end %>
29
+ </td>
30
+ </tr>
31
+ <tr class="pgbus-job-detail" data-process-detail="<%= process[:id] %>">
32
+ <td colspan="5" class="px-4 pb-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100 dark:border-gray-700">
33
+ <dl class="mt-3 grid grid-cols-1 gap-2 text-sm sm:grid-cols-[auto_1fr] sm:gap-x-6">
34
+ <dt class="font-medium text-gray-700 dark:text-gray-200"><%= t("pgbus.processes.processes_table.started") %></dt>
35
+ <dd class="text-gray-700 dark:text-gray-200"><%= pgbus_timestamp(state.started_at) %></dd>
36
+ <dt class="font-medium text-gray-700 dark:text-gray-200"><%= t("pgbus.processes.processes_table.raw_metadata") %></dt>
37
+ <dd class="font-mono text-xs">
38
+ <% if raw.empty? %>
39
+ <span class="text-gray-700 dark:text-gray-200"><%= t("pgbus.processes.processes_table.no_raw_metadata") %></span>
40
+ <% else %>
41
+ <% raw.each do |k, v| %>
42
+ <span class="inline-flex items-center rounded bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 text-xs text-gray-700 dark:text-gray-200 mr-1 mb-1"><%= k %>: <%= v %></span>
43
+ <% end %>
44
+ <% end %>
45
+ </dd>
46
+ </dl>
47
+ </td>
48
+ </tr>
@@ -0,0 +1,4 @@
1
+ <%# A process's status in words (issue #493): the badge, then why, then what
2
+ happens next. Locals: state (a ProcessState::Result). %>
3
+ <%= pgbus_status_badge(state.status) %>
4
+ <p class="mt-1 text-xs text-gray-700 dark:text-gray-200" data-testid="process-reason"><%= pgbus_process_status(state) %></p>
@@ -3,57 +3,53 @@
3
3
  <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
- <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.queue") %></th>
7
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.depth") %></th>
8
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.visible") %></th>
9
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.parked") %></th>
10
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.oldest_claimable") %></th>
11
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.newest") %></th>
12
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.total_ever") %></th>
13
- <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.actions") %></th>
6
+ <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.queue") %></th>
7
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.depth") %></th>
8
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.visible") %></th>
9
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.parked") %></th>
10
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.oldest_claimable") %></th>
11
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.newest") %></th>
12
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.total_ever") %></th>
13
+ <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.queues.queues_list.headers.actions") %></th>
14
14
  </tr>
15
15
  </thead>
16
16
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
17
17
  <% @queues.each do |q| %>
18
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
19
- <td data-label="Queue" class="px-4 py-3 text-sm">
20
- <%= link_to q[:name], pgbus.queue_path(name: q[:name]), class: "font-medium text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
18
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
19
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.queue") %>" class="px-4 py-3 text-sm">
20
+ <%= pgbus_link_to q[:name], pgbus.queue_path(name: q[:name]), data: { turbo_frame: "_top" } %>
21
21
  <%= pgbus_queue_badge(q[:name]) %>
22
22
  <% if q[:paused] %>
23
- <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.queues.queues_list.paused") %></span>
23
+ <%= pgbus_paused_badge(q[:paused]) %>
24
24
  <% end %>
25
25
  </td>
26
- <td data-label="Depth" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
27
- <td data-label="Visible" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
28
- <td data-label="Parked" class="px-4 py-3 text-sm text-right font-mono text-gray-500"><%= pgbus_number(q[:parked_length]) %></td>
29
- <td data-label="Oldest claimable" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_claimable_age_sec] || "—" %></td>
30
- <td data-label="Newest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:newest_msg_age_sec] || "—" %></td>
31
- <td data-label="Total" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
32
- <td data-label="Actions" class="px-4 py-3 text-sm text-right space-x-2">
26
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.depth") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-200"><%= pgbus_number(q[:queue_length]) %></td>
27
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.visible") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-200"><%= pgbus_number(q[:queue_visible_length]) %></td>
28
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.parked") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-500 dark:text-gray-400"><%= pgbus_number(q[:parked_length]) %></td>
29
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.oldest_claimable") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(q[:oldest_claimable_age_sec]) %></td>
30
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.newest") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(q[:newest_msg_age_sec]) %></td>
31
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.total_ever") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_number(q[:total_messages]) %></td>
32
+ <td data-label="<%= t("pgbus.queues.queues_list.headers.actions") %>" class="px-4 py-3 text-sm text-right space-x-2">
33
33
  <% if q[:paused] %>
34
- <%= button_to t("pgbus.queues.queues_list.resume"), pgbus.resume_queue_path(name: q[:name]),
35
- method: :post,
36
- class: "text-xs text-green-600 hover:text-green-800 font-medium",
34
+ <%= pgbus_button_to t("pgbus.queues.queues_list.resume"), pgbus.resume_queue_path(name: q[:name]),
35
+ method: :post, variant: :link, size: :sm, tone: :green,
37
36
  data: { turbo_frame: "_top" } %>
38
37
  <% else %>
39
- <%= button_to t("pgbus.queues.queues_list.pause"), pgbus.pause_queue_path(name: q[:name]),
40
- method: :post,
41
- class: "text-xs text-yellow-600 hover:text-yellow-800 font-medium",
38
+ <%= pgbus_button_to t("pgbus.queues.queues_list.pause"), pgbus.pause_queue_path(name: q[:name]),
39
+ method: :post, variant: :link, size: :sm, tone: :yellow,
42
40
  data: { turbo_confirm: t("pgbus.queues.queues_list.pause_confirm", name: q[:name]), turbo_frame: "_top" } %>
43
41
  <% end %>
44
- <%= button_to t("pgbus.queues.queues_list.purge"), pgbus.purge_queue_path(name: q[:name]),
45
- method: :post,
46
- class: "text-xs text-red-600 hover:text-red-800 font-medium",
42
+ <%= pgbus_button_to t("pgbus.queues.queues_list.purge"), pgbus.purge_queue_path(name: q[:name]),
43
+ method: :post, variant: :link, size: :sm, tone: :red,
47
44
  data: { turbo_confirm: t("pgbus.queues.queues_list.purge_confirm", name: q[:name]), turbo_frame: "_top" } %>
48
- <%= button_to t("pgbus.queues.queues_list.delete"), pgbus.queue_path(name: q[:name]),
49
- method: :delete,
50
- class: "text-xs text-red-600 hover:text-red-800 font-medium",
45
+ <%= pgbus_button_to t("pgbus.queues.queues_list.delete"), pgbus.queue_path(name: q[:name]),
46
+ method: :delete, variant: :link, size: :sm, tone: :red,
51
47
  data: { turbo_confirm: t("pgbus.queues.queues_list.delete_confirm", name: q[:name]), turbo_frame: "_top" } %>
52
48
  </td>
53
49
  </tr>
54
50
  <% end %>
55
51
  <% if @queues.empty? %>
56
- <tr><td colspan="8" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.queues.queues_list.empty") %></td></tr>
52
+ <%= render "pgbus/shared/empty_row", colspan: 8, message: t("pgbus.queues.queues_list.empty") %>
57
53
  <% end %>
58
54
  </tbody>
59
55
  </table>