pgbus 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfd2ea5ccec3e3715dbcf2dd409a652527cf1d9e18c60fe4d5797fd5cf5be6ec
4
- data.tar.gz: 6983d4977aeb792ba0857884752a8b662a15164cde8cd6ee3bb5406d95d7f225
3
+ metadata.gz: 0d12150a0ea8aea034f08e626bfa2c553cf75349eb502d7a0fe0a50a867aa11b
4
+ data.tar.gz: 990216a4e6b7db7be81e888d90fe2d56ea536fa1e64a2507206dcd5524a255c5
5
5
  SHA512:
6
- metadata.gz: c6f3e19f1e3fe2d5eae02fe52dcd2878f5bbcb13cd7387a41b3902ad91cb8c20ce2d1e3c606488bdce1c901126d1192f7a34747e8746c397d016d0c2541a8828
7
- data.tar.gz: da5ed5a1a2012d757547e3e2f1f48586a84e7d096df91a793d9ba18f76da881e52dab650d4fb3c605c4da6de06b1e8b58b0d11c99bd90e30ef64b3c368c44093
6
+ metadata.gz: f7e706c2824a4f00401027bd08df2c663502586a0a48fdd2e4150341a1e50fac520c50a503b2a6cff2d48d482d069ac66a605b9ce73a14513d9e65537da45f88
7
+ data.tar.gz: 64ad1a0978e42ddd271d50c224540bb3200e291912e3fde1bee72fe30eb74470b60833580a0a211785aa0830eb640dde463546c3bd748a33e9de0f903e79abad
data/README.md CHANGED
@@ -211,7 +211,7 @@ mount Pgbus::Engine => "/pgbus"
211
211
 
212
212
  The dashboard shows queues, jobs, processes, failures, dead letter messages, and event subscribers. It auto-refreshes via Turbo Frames with no WebSocket dependency.
213
213
 
214
- Protect it in production:
214
+ Protect it in production with a simple auth lambda:
215
215
 
216
216
  ```ruby
217
217
  Pgbus.configure do |config|
@@ -221,6 +221,24 @@ Pgbus.configure do |config|
221
221
  end
222
222
  ```
223
223
 
224
+ Or inherit from your own authenticated controller (like mission_control-jobs):
225
+
226
+ ```ruby
227
+ Pgbus.configure do |config|
228
+ config.base_controller_class = "Admin::BaseController"
229
+ end
230
+ ```
231
+
232
+ When `base_controller_class` is set, all dashboard controllers inherit from that class instead of `ActionController::Base`. This is the recommended approach when mounting the dashboard inside an authenticated namespace -- your base controller's `before_action` filters, helper methods, and authentication logic apply automatically without monkey-patching.
233
+
234
+ Add a "back to app" button in the dashboard nav to return to your main application:
235
+
236
+ ```ruby
237
+ Pgbus.configure do |config|
238
+ config.return_to_app_url = "/admin"
239
+ end
240
+ ```
241
+
224
242
  ## Concurrency controls
225
243
 
226
244
  Limit how many jobs with the same key can run concurrently:
@@ -624,6 +642,8 @@ The dispatcher runs archive compaction as part of its maintenance loop, deleting
624
642
  | `outbox_batch_size` | `100` | Max entries per outbox poll cycle |
625
643
  | `outbox_retention` | `86400` | Seconds to keep published outbox entries (1 day) |
626
644
  | `idempotency_ttl` | `604800` | Seconds to keep processed event records (7 days, cleaned hourly) |
645
+ | `base_controller_class` | `"::ActionController::Base"` | Base class for dashboard controllers (string, constantized at load time) |
646
+ | `return_to_app_url` | `nil` | URL for "back to app" button in dashboard nav (nil hides the button) |
627
647
  | `web_auth` | `nil` | Lambda for dashboard authentication |
628
648
  | `web_refresh_interval` | `5000` | Dashboard auto-refresh interval in milliseconds |
629
649
  | `web_live_updates` | `true` | Enable Turbo Frames auto-refresh on dashboard |
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgbus
4
- class ApplicationController < ActionController::Base
4
+ class ApplicationController < Pgbus.configuration.base_controller_class.constantize
5
+ # Ensure core ActionController modules are available even when the
6
+ # base controller is a slim subclass that may not include them all.
7
+ ActionController::Base::MODULES.each do |mod|
8
+ include mod unless self < mod
9
+ end
10
+
5
11
  include Web::Authentication
6
12
 
7
13
  protect_from_forgery with: :exception
@@ -10,7 +16,7 @@ module Pgbus
10
16
 
11
17
  layout "pgbus/application"
12
18
 
13
- helper Pgbus::ApplicationHelper
19
+ helper Pgbus::ApplicationHelper unless self < Pgbus::ApplicationHelper
14
20
 
15
21
  # Make `pgbus` route proxy available in views (e.g. pgbus.root_path).
16
22
  # With isolate_namespace, the non-prefixed helpers (root_path) work inside
@@ -158,6 +158,16 @@ module Pgbus
158
158
  link_to label, path, class: css
159
159
  end
160
160
 
161
+ def pgbus_mobile_nav_link(label, path)
162
+ active = request.path == path || (path != pgbus.root_path && request.path.start_with?(path))
163
+ css = if active
164
+ "block rounded-md px-3 py-2 text-base font-medium text-white bg-gray-800"
165
+ else
166
+ "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700"
167
+ end
168
+ link_to label, path, class: css
169
+ end
170
+
161
171
  LOCALE_NAMES = {
162
172
  da: "Dansk",
163
173
  de: "Deutsch",
@@ -9,6 +9,43 @@
9
9
  Applied before Tailwind CDN loads so the background is correct immediately. */
10
10
  html.dark { background-color: #030712; } /* gray-950 */
11
11
  html.dark body { background-color: #030712; }
12
+
13
+ /* Responsive tables: stack rows as cards on small screens */
14
+ @media (max-width: 1023px) {
15
+ .pgbus-table thead { display: none; }
16
+ .pgbus-table tbody tr {
17
+ display: block;
18
+ margin-bottom: 0.75rem;
19
+ border-radius: 0.5rem;
20
+ padding: 0.75rem;
21
+ border: 1px solid #e5e7eb;
22
+ }
23
+ html.dark .pgbus-table tbody tr { border-color: #374151; }
24
+ .pgbus-table tbody td {
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: baseline;
28
+ padding: 0.25rem 0;
29
+ border: none;
30
+ text-align: right;
31
+ }
32
+ .pgbus-table tbody td::before {
33
+ content: attr(data-label);
34
+ font-weight: 600;
35
+ font-size: 0.75rem;
36
+ text-transform: uppercase;
37
+ color: #6b7280;
38
+ text-align: left;
39
+ margin-right: 1rem;
40
+ flex-shrink: 0;
41
+ }
42
+ html.dark .pgbus-table tbody td::before { color: #9ca3af; }
43
+ .pgbus-table tbody td[colspan] {
44
+ display: block;
45
+ text-align: center;
46
+ }
47
+ .pgbus-table tbody td[colspan]::before { display: none; }
48
+ }
12
49
  </style>
13
50
  <script src="https://cdn.tailwindcss.com"></script>
14
51
  <script>
@@ -23,6 +60,15 @@
23
60
  var isDark = document.documentElement.classList.toggle('dark');
24
61
  localStorage.setItem('pgbus-dark', isDark);
25
62
  }
63
+ // Mobile menu toggle
64
+ function toggleMobileMenu() {
65
+ var menu = document.getElementById('pgbus-mobile-menu');
66
+ var openIcon = document.getElementById('pgbus-menu-open');
67
+ var closeIcon = document.getElementById('pgbus-menu-close');
68
+ menu.classList.toggle('hidden');
69
+ openIcon.classList.toggle('hidden');
70
+ closeIcon.classList.toggle('hidden');
71
+ }
26
72
  // Close locale dropdown when clicking outside
27
73
  document.addEventListener('click', function(e) {
28
74
  var switcher = document.getElementById('pgbus-locale-switcher');
@@ -43,8 +89,10 @@
43
89
  if (document.hidden) return;
44
90
  document.querySelectorAll("turbo-frame[data-auto-refresh]")
45
91
  .forEach(frame => {
46
- if (!frame.src && frame.dataset.src) frame.src = frame.dataset.src;
47
- if (frame.src) frame.reload();
92
+ try {
93
+ if (!frame.src && frame.dataset.src) frame.src = frame.dataset.src;
94
+ if (frame.src) frame.reload();
95
+ } catch (_) { /* Turbo may abort in-flight fetches during navigation */ }
48
96
  });
49
97
  }
50
98
  function start() { timer = setInterval(refreshFrames, interval); }
@@ -61,13 +109,24 @@
61
109
  <nav class="bg-gray-900 dark:bg-gray-950 border-b border-gray-800">
62
110
  <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
63
111
  <div class="flex h-14 items-center justify-between">
112
+ <!-- Left: brand + desktop nav -->
64
113
  <div class="flex items-center space-x-8">
65
- <%= link_to pgbus.root_path, class: "flex items-center space-x-2" do %>
66
- <span class="text-lg font-bold text-white"><%= t("pgbus.layout.brand") %></span>
67
- <span class="rounded bg-gray-700 px-1.5 py-0.5 text-xs text-gray-300"><%= Pgbus::VERSION %></span>
68
- <% end %>
114
+ <div class="flex items-center space-x-3">
115
+ <% if Pgbus.configuration.return_to_app_url %>
116
+ <a href="<%= Pgbus.configuration.return_to_app_url %>" class="rounded-md p-1.5 text-gray-400 hover:text-white hover:bg-gray-700" title="<%= t("pgbus.layout.return_to_app") %>">
117
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
118
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/>
119
+ </svg>
120
+ </a>
121
+ <% end %>
122
+ <%= link_to pgbus.root_path, class: "flex items-center space-x-2" do %>
123
+ <span class="text-lg font-bold text-white"><%= t("pgbus.layout.brand") %></span>
124
+ <span class="rounded bg-gray-700 px-1.5 py-0.5 text-xs text-gray-300"><%= Pgbus::VERSION %></span>
125
+ <% end %>
126
+ </div>
69
127
 
70
- <div class="flex space-x-1">
128
+ <!-- Desktop nav links (hidden on small screens) -->
129
+ <div class="hidden lg:flex space-x-1">
71
130
  <%= pgbus_nav_link t("pgbus.layout.nav.dashboard"), pgbus.root_path %>
72
131
  <%= pgbus_nav_link t("pgbus.layout.nav.queues"), pgbus.queues_path %>
73
132
  <%= pgbus_nav_link t("pgbus.layout.nav.jobs"), pgbus.jobs_path %>
@@ -81,6 +140,7 @@
81
140
  </div>
82
141
  </div>
83
142
 
143
+ <!-- Right: locale, dark mode, hamburger -->
84
144
  <div class="flex items-center space-x-2">
85
145
  <!-- Locale switcher -->
86
146
  <div class="relative" id="pgbus-locale-switcher">
@@ -98,17 +158,46 @@
98
158
  </div>
99
159
  </div>
100
160
 
101
- <button onclick="toggleDarkMode()" class="rounded-md p-2 text-gray-400 hover:text-white focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-900" aria-label="<%= t("pgbus.layout.toggle_dark_mode") %>">
102
- <svg class="h-5 w-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
103
- <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/>
104
- </svg>
105
- <svg class="h-5 w-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
106
- <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
107
- </svg>
108
- </button>
161
+ <!-- Dark mode toggle -->
162
+ <button onclick="toggleDarkMode()" class="rounded-md p-2 text-gray-400 hover:text-white focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-900" aria-label="<%= t("pgbus.layout.toggle_dark_mode") %>">
163
+ <svg class="h-5 w-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
164
+ <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/>
165
+ </svg>
166
+ <svg class="h-5 w-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
167
+ <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
168
+ </svg>
169
+ </button>
170
+
171
+ <!-- Mobile menu button (hidden on large screens) -->
172
+ <button onclick="toggleMobileMenu()" class="lg:hidden rounded-md p-2 text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500" aria-label="<%= t("pgbus.layout.toggle_menu") %>">
173
+ <!-- Hamburger icon -->
174
+ <svg id="pgbus-menu-open" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
175
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
176
+ </svg>
177
+ <!-- Close icon (hidden by default) -->
178
+ <svg id="pgbus-menu-close" class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
179
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
180
+ </svg>
181
+ </button>
109
182
  </div>
110
183
  </div>
111
184
  </div>
185
+
186
+ <!-- Mobile menu (hidden by default, shown on small screens when toggled) -->
187
+ <div id="pgbus-mobile-menu" class="hidden lg:hidden border-t border-gray-800">
188
+ <div class="space-y-1 px-3 py-3">
189
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.dashboard"), pgbus.root_path %>
190
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.queues"), pgbus.queues_path %>
191
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.jobs"), pgbus.jobs_path %>
192
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.recurring"), pgbus.recurring_tasks_path %>
193
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.processes"), pgbus.processes_path %>
194
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.events"), pgbus.events_path %>
195
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.dlq"), pgbus.dead_letter_index_path %>
196
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.outbox"), pgbus.outbox_index_path %>
197
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.locks"), pgbus.locks_path %>
198
+ <%= pgbus_mobile_nav_link t("pgbus.layout.nav.insights"), pgbus.insights_path %>
199
+ </div>
200
+ </div>
112
201
  </nav>
113
202
 
114
203
  <!-- Flash messages -->
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.dashboard.processes_table.title") %></h2>
4
4
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
5
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
5
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
6
6
  <thead class="bg-gray-50 dark:bg-gray-900">
7
7
  <tr>
8
8
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.processes_table.headers.kind") %></th>
@@ -14,10 +14,10 @@
14
14
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
15
15
  <% @processes.each do |p| %>
16
16
  <tr>
17
- <td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
- <td class="px-4 py-3 text-sm text-gray-500"><%= p[:hostname] %></td>
19
- <td class="px-4 py-3 text-sm text-gray-500"><%= p[:pid] %></td>
20
- <td class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
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-500"><%= p[:hostname] %></td>
19
+ <td data-label="PID" class="px-4 py-3 text-sm text-gray-500"><%= p[:pid] %></td>
20
+ <td data-label="Status" class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
21
21
  </tr>
22
22
  <% end %>
23
23
  <% if @processes.empty? %>
@@ -6,7 +6,7 @@
6
6
  </div>
7
7
 
8
8
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
9
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
9
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
10
10
  <thead class="bg-gray-50 dark:bg-gray-900">
11
11
  <tr>
12
12
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.queue") %></th>
@@ -19,14 +19,14 @@
19
19
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
20
20
  <% @queues.each do |q| %>
21
21
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
22
- <td class="px-4 py-3 text-sm">
22
+ <td data-label="Queue" class="px-4 py-3 text-sm">
23
23
  <%= link_to q[:name], pgbus.queue_path(name: q[:name]), class: "font-medium text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
24
24
  <%= pgbus_queue_badge(q[:name]) %>
25
25
  </td>
26
- <td class="px-4 py-3 text-sm text-right text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
27
- <td class="px-4 py-3 text-sm text-right text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
28
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
29
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
26
+ <td data-label="Depth" class="px-4 py-3 text-sm text-right text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
27
+ <td data-label="Visible" class="px-4 py-3 text-sm text-right text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
28
+ <td data-label="Oldest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
29
+ <td data-label="Total" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
30
30
  </tr>
31
31
  <% end %>
32
32
  <% if @queues.empty? %>
@@ -7,7 +7,7 @@
7
7
  <% end %>
8
8
  </div>
9
9
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
10
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
10
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
11
11
  <thead class="bg-gray-50 dark:bg-gray-900">
12
12
  <tr>
13
13
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.queue") %></th>
@@ -18,9 +18,9 @@
18
18
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
19
19
  <% @recent_failures.each do |f| %>
20
20
  <tr>
21
- <td class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
22
- <td class="px-4 py-3 text-sm text-red-600 truncate max-w-xs"><%= f["error_class"] %>: <%= truncate(f["error_message"].to_s, length: 60) %></td>
23
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
21
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
22
+ <td data-label="Error" class="px-4 py-3 text-sm text-red-600 truncate max-w-xs"><%= f["error_class"] %>: <%= truncate(f["error_message"].to_s, length: 60) %></td>
23
+ <td data-label="Time" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
24
24
  </tr>
25
25
  <% end %>
26
26
  <% if @recent_failures.empty? %>
@@ -1,6 +1,6 @@
1
1
  <turbo-frame id="dlq-messages" data-auto-refresh data-src="<%= pgbus.dead_letter_index_path(frame: 'list') %>">
2
2
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
3
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
3
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
6
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dead_letter.messages_table.headers.id") %></th>
@@ -4,7 +4,7 @@
4
4
  <div class="mb-8">
5
5
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.events.index.subscribers_title") %></h2>
6
6
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
7
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
7
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
8
8
  <thead class="bg-gray-50 dark:bg-gray-900">
9
9
  <tr>
10
10
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.events.index.subscribers_headers.pattern") %></th>
@@ -15,9 +15,9 @@
15
15
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
16
16
  <% @subscribers.each do |s| %>
17
17
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
18
- <td class="px-4 py-3 text-sm font-mono text-indigo-600"><%= s[:pattern] %></td>
19
- <td class="px-4 py-3 text-sm text-gray-900 dark:text-white"><%= s[:handler_class] %></td>
20
- <td class="px-4 py-3 text-sm text-gray-500"><%= s[:queue_name] %></td>
18
+ <td data-label="Pattern" class="px-4 py-3 text-sm font-mono text-indigo-600"><%= s[:pattern] %></td>
19
+ <td data-label="Handler" class="px-4 py-3 text-sm text-gray-900 dark:text-white"><%= s[:handler_class] %></td>
20
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-500"><%= s[:queue_name] %></td>
21
21
  </tr>
22
22
  <% end %>
23
23
  <% if @subscribers.empty? %>
@@ -32,7 +32,7 @@
32
32
  <div>
33
33
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.events.index.processed_title") %></h2>
34
34
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
35
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
35
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
36
36
  <thead class="bg-gray-50 dark:bg-gray-900">
37
37
  <tr>
38
38
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.events.index.processed_headers.event_id") %></th>
@@ -43,9 +43,9 @@
43
43
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
44
44
  <% @events.each do |e| %>
45
45
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
46
- <td class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= e["event_id"] %></td>
47
- <td class="px-4 py-3 text-sm text-gray-700"><%= e["handler_class"] %></td>
48
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(e["processed_at"]) %></td>
46
+ <td data-label="Event ID" class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= e["event_id"] %></td>
47
+ <td data-label="Handler" class="px-4 py-3 text-sm text-gray-700"><%= e["handler_class"] %></td>
48
+ <td data-label="Processed At" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(e["processed_at"]) %></td>
49
49
  </tr>
50
50
  <% end %>
51
51
  <% if @events.empty? %>
@@ -68,7 +68,7 @@
68
68
  <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
69
69
  <h3 class="text-sm font-medium text-gray-700 dark:text-gray-300"><%= t("pgbus.insights.show.slowest.title") %></h3>
70
70
  </div>
71
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
71
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
72
72
  <thead class="bg-gray-50 dark:bg-gray-900">
73
73
  <tr>
74
74
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.insights.show.slowest.headers.job_class") %></th>
@@ -80,10 +80,10 @@
80
80
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
81
81
  <% @slowest.each do |row| %>
82
82
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
83
- <td class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300"><%= row[:job_class] %></td>
84
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_number(row[:count]) %></td>
85
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:avg_ms]) %></td>
86
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:max_ms]) %></td>
83
+ <td data-label="Job Class" class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300"><%= row[:job_class] %></td>
84
+ <td data-label="Count" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_number(row[:count]) %></td>
85
+ <td data-label="Avg" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:avg_ms]) %></td>
86
+ <td data-label="Max" class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_ms_duration(row[:max_ms]) %></td>
87
87
  </tr>
88
88
  <% end %>
89
89
  <% if @slowest.empty? %>
@@ -95,12 +95,14 @@
95
95
 
96
96
  <script src="https://cdn.jsdelivr.net/npm/apexcharts@4"></script>
97
97
  <script>
98
- let throughputChart, statusChart;
98
+ (function() {
99
+ // IIFE prevents "redeclaration of let" when Turbo re-executes on navigation
100
+ var throughputChart, statusChart;
99
101
 
100
102
  function getThemeColors() {
101
- const isDark = document.documentElement.classList.contains('dark');
103
+ var isDark = document.documentElement.classList.contains('dark');
102
104
  return {
103
- isDark,
105
+ isDark: isDark,
104
106
  text: isDark ? '#9ca3af' : '#6b7280',
105
107
  grid: isDark ? '#374151' : '#e5e7eb',
106
108
  tooltip: isDark ? 'dark' : 'light',
@@ -109,16 +111,14 @@
109
111
  }
110
112
 
111
113
  function renderCharts(data) {
112
- const t = getThemeColors();
114
+ var t = getThemeColors();
113
115
 
114
- // Destroy existing charts before re-rendering
115
116
  if (throughputChart) throughputChart.destroy();
116
117
  if (statusChart) statusChart.destroy();
117
118
 
118
- // Throughput chart
119
- const throughputData = data.throughput.map(p => ({
120
- x: new Date(p.time).getTime(), y: p.count
121
- }));
119
+ var throughputData = data.throughput.map(function(p) {
120
+ return { x: new Date(p.time).getTime(), y: p.count };
121
+ });
122
122
 
123
123
  throughputChart = new ApexCharts(document.querySelector('#throughput-chart'), {
124
124
  series: [{ name: '<%= j(t("pgbus.insights.show.charts.series_name")) %>', data: throughputData }],
@@ -134,10 +134,9 @@
134
134
  });
135
135
  throughputChart.render();
136
136
 
137
- // Status distribution chart
138
- const statusLabels = Object.keys(data.status_counts);
139
- const statusValues = Object.values(data.status_counts);
140
- const statusColors = statusLabels.map(s => {
137
+ var statusLabels = Object.keys(data.status_counts);
138
+ var statusValues = Object.values(data.status_counts);
139
+ var statusColors = statusLabels.map(function(s) {
141
140
  if (s === 'success') return '#10b981';
142
141
  if (s === 'failed') return '#ef4444';
143
142
  if (s === 'dead_lettered') return '#f97316';
@@ -161,21 +160,24 @@
161
160
  }
162
161
  }
163
162
 
164
- // Fetch data and render
165
- let chartData = null;
163
+ var chartData = null;
166
164
  fetch('<%= pgbus.api_insights_path(minutes: @minutes) %>')
167
- .then(r => r.json())
168
- .then(data => { chartData = data; renderCharts(data); })
169
- .catch(() => {
170
- const msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24"><%= j(t("pgbus.insights.show.charts.failed_to_load")) %></p>';
171
- document.querySelector('#throughput-chart').innerHTML = msg;
172
- document.querySelector('#status-chart').innerHTML = msg;
165
+ .then(function(r) {
166
+ if (!r.ok) throw new Error('HTTP ' + r.status);
167
+ return r.json();
168
+ })
169
+ .then(function(data) { chartData = data; renderCharts(data); })
170
+ .catch(function(err) {
171
+ if (err.name === 'AbortError') return;
172
+ var msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24"><%= j(t("pgbus.insights.show.charts.failed_to_load")) %></p>';
173
+ var el1 = document.querySelector('#throughput-chart');
174
+ var el2 = document.querySelector('#status-chart');
175
+ if (el1) el1.innerHTML = msg;
176
+ if (el2) el2.innerHTML = msg;
173
177
  });
174
178
 
175
- // Re-render charts when dark mode toggles.
176
- // Listen for class changes on <html> instead of wrapping the toggle function,
177
- // so it works regardless of script loading order.
178
179
  new MutationObserver(function() {
179
180
  if (chartData) renderCharts(chartData);
180
181
  }).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
182
+ })();
181
183
  </script>
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.jobs.enqueued_table.title") %></h2>
4
4
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
5
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
5
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
6
6
  <thead class="bg-gray-50 dark:bg-gray-900">
7
7
  <tr>
8
8
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.jobs.enqueued_table.headers.id") %></th>
@@ -2,7 +2,7 @@
2
2
  <div class="mb-8">
3
3
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.jobs.failed_table.title") %></h2>
4
4
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
5
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
5
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
6
6
  <thead class="bg-gray-50 dark:bg-gray-900">
7
7
  <tr>
8
8
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.jobs.failed_table.headers.id") %></th>
@@ -16,16 +16,16 @@
16
16
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
17
17
  <% @failed.each do |f| %>
18
18
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
19
- <td class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
19
+ <td data-label="ID" class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
20
20
  <%= link_to f["id"], pgbus.job_path(f["id"]), class: "text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
21
21
  </td>
22
- <td class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
23
- <td class="px-4 py-3 text-sm text-red-600 max-w-sm truncate">
22
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
23
+ <td data-label="Error" class="px-4 py-3 text-sm text-red-600 max-w-sm truncate">
24
24
  <span class="font-medium"><%= f["error_class"] %></span>: <%= truncate(f["error_message"].to_s, length: 80) %>
25
25
  </td>
26
- <td class="px-4 py-3 text-sm text-gray-500"><%= f["retry_count"] %></td>
27
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
28
- <td class="px-4 py-3 text-sm text-right space-x-2">
26
+ <td data-label="Retries" class="px-4 py-3 text-sm text-gray-500"><%= f["retry_count"] %></td>
27
+ <td data-label="Failed" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(f["failed_at"]) %></td>
28
+ <td data-label="Actions" class="px-4 py-3 text-sm text-right space-x-2">
29
29
  <%= button_to t("pgbus.jobs.failed_table.retry"), pgbus.retry_job_path(f["id"]), method: :post,
30
30
  class: "text-xs text-indigo-600 hover:text-indigo-800 font-medium",
31
31
  data: { turbo_frame: "_top" } %>
@@ -4,7 +4,7 @@
4
4
  </div>
5
5
 
6
6
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
7
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
7
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
8
8
  <thead class="bg-gray-50 dark:bg-gray-900">
9
9
  <tr>
10
10
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.locks.index.headers.lock_key") %></th>
@@ -18,16 +18,16 @@
18
18
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
19
19
  <% @locks.each do |lock| %>
20
20
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
21
- <td class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
22
- <td class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:job_class] %></td>
23
- <td class="px-4 py-3 text-sm">
21
+ <td data-label="Lock Key" class="px-4 py-3 text-sm font-mono text-gray-700 dark:text-gray-300 max-w-xs truncate"><%= lock[:lock_key] %></td>
22
+ <td data-label="Job Class" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300"><%= lock[:job_class] %></td>
23
+ <td data-label="State" class="px-4 py-3 text-sm">
24
24
  <% if lock[:state] == "executing" %>
25
25
  <span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:text-blue-300"><%= t("pgbus.locks.index.executing") %></span>
26
26
  <% else %>
27
27
  <span class="inline-flex items-center rounded-full bg-yellow-100 dark:bg-yellow-900/50 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:text-yellow-300"><%= t("pgbus.locks.index.queued") %></span>
28
28
  <% end %>
29
29
  </td>
30
- <td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
30
+ <td data-label="Owner" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
31
31
  <% if lock[:owner_pid] %>
32
32
  <span class="font-mono"><%= lock[:owner_pid] %></span>
33
33
  <% if lock[:owner_hostname] %>
@@ -37,12 +37,12 @@
37
37
  <span class="text-gray-400 dark:text-gray-500">—</span>
38
38
  <% end %>
39
39
  </td>
40
- <td class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
40
+ <td data-label="Age" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
41
41
  <% if lock[:age_seconds] %>
42
42
  <%= pgbus_duration(lock[:age_seconds]) %>
43
43
  <% end %>
44
44
  </td>
45
- <td class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_time_ago_future(lock[:expires_at]) %></td>
45
+ <td data-label="Expires" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_time_ago_future(lock[:expires_at]) %></td>
46
46
  </tr>
47
47
  <% end %>
48
48
  <% if @locks.empty? %>
@@ -19,7 +19,7 @@
19
19
  </div>
20
20
 
21
21
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
22
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
22
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
23
23
  <thead class="bg-gray-50 dark:bg-gray-900">
24
24
  <tr>
25
25
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.outbox.index.headers.id") %></th>
@@ -33,18 +33,18 @@
33
33
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
34
34
  <% @entries.each do |entry| %>
35
35
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
36
- <td class="px-4 py-3 text-sm font-mono text-gray-700"><%= entry.id %></td>
37
- <td class="px-4 py-3 text-sm text-gray-700"><%= entry.routing_key || entry.queue_name %></td>
38
- <td class="px-4 py-3 text-sm text-gray-500 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= entry.priority %></td>
40
- <td class="px-4 py-3 text-sm text-right">
36
+ <td data-label="ID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= entry.id %></td>
37
+ <td data-label="Routing Key" class="px-4 py-3 text-sm text-gray-700"><%= entry.routing_key || entry.queue_name %></td>
38
+ <td data-label="Payload" class="px-4 py-3 text-sm text-gray-500 max-w-xs truncate"><%= pgbus_json_preview(entry.payload) %></td>
39
+ <td data-label="Priority" class="px-4 py-3 text-sm text-right text-gray-500"><%= entry.priority %></td>
40
+ <td data-label="Status" class="px-4 py-3 text-sm text-right">
41
41
  <% if entry.published_at %>
42
42
  <span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"><%= t("pgbus.outbox.index.published") %></span>
43
43
  <% else %>
44
44
  <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.outbox.index.pending") %></span>
45
45
  <% end %>
46
46
  </td>
47
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_time_ago(entry.created_at) %></td>
47
+ <td data-label="Created" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_time_ago(entry.created_at) %></td>
48
48
  </tr>
49
49
  <% end %>
50
50
  <% if @entries.empty? %>
@@ -1,6 +1,6 @@
1
1
  <turbo-frame id="processes-list" data-auto-refresh data-src="<%= pgbus.processes_path(frame: 'list') %>">
2
2
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
3
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
3
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
6
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.processes.processes_table.headers.kind") %></th>
@@ -14,12 +14,12 @@
14
14
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
15
15
  <% @processes.each do |p| %>
16
16
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
17
- <td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
- <td class="px-4 py-3 text-sm text-gray-700"><%= p[:hostname] %></td>
19
- <td class="px-4 py-3 text-sm font-mono text-gray-700"><%= p[:pid] %></td>
20
- <td class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
21
- <td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(p[:last_heartbeat_at]) %></td>
22
- <td class="px-4 py-3 text-sm text-gray-500 font-mono text-xs max-w-xs truncate">
17
+ <td data-label="Kind" class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
18
+ <td data-label="Host" class="px-4 py-3 text-sm text-gray-700"><%= p[:hostname] %></td>
19
+ <td data-label="PID" class="px-4 py-3 text-sm font-mono text-gray-700"><%= p[:pid] %></td>
20
+ <td data-label="Status" class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
21
+ <td data-label="Last Heartbeat" class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(p[:last_heartbeat_at]) %></td>
22
+ <td data-label="Metadata" class="px-4 py-3 text-sm text-gray-500 font-mono text-xs max-w-xs truncate">
23
23
  <% if p[:metadata].is_a?(Hash) %>
24
24
  <% p[:metadata].each do |k, v| %>
25
25
  <span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-xs mr-1"><%= k %>: <%= v %></span>
@@ -1,6 +1,6 @@
1
1
  <turbo-frame id="queues-list" data-auto-refresh data-src="<%= pgbus.queues_path(frame: 'list') %>">
2
2
  <div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
3
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
3
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
4
4
  <thead class="bg-gray-50 dark:bg-gray-900">
5
5
  <tr>
6
6
  <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.queues.queues_list.headers.queue") %></th>
@@ -15,19 +15,19 @@
15
15
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
16
16
  <% @queues.each do |q| %>
17
17
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
18
- <td class="px-4 py-3 text-sm">
18
+ <td data-label="Queue" class="px-4 py-3 text-sm">
19
19
  <%= link_to q[:name], pgbus.queue_path(name: q[:name]), class: "font-medium text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
20
20
  <%= pgbus_queue_badge(q[:name]) %>
21
21
  <% if q[:paused] %>
22
22
  <span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800"><%= t("pgbus.queues.queues_list.paused") %></span>
23
23
  <% end %>
24
24
  </td>
25
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
26
- <td class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
27
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
28
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:newest_msg_age_sec] || "—" %></td>
29
- <td class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
30
- <td class="px-4 py-3 text-sm text-right space-x-2">
25
+ <td data-label="Depth" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_length]) %></td>
26
+ <td data-label="Visible" class="px-4 py-3 text-sm text-right font-mono text-gray-700"><%= pgbus_number(q[:queue_visible_length]) %></td>
27
+ <td data-label="Oldest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:oldest_msg_age_sec] || "—" %></td>
28
+ <td data-label="Newest" class="px-4 py-3 text-sm text-right text-gray-500"><%= q[:newest_msg_age_sec] || "—" %></td>
29
+ <td data-label="Total" class="px-4 py-3 text-sm text-right text-gray-500"><%= pgbus_number(q[:total_messages]) %></td>
30
+ <td data-label="Actions" class="px-4 py-3 text-sm text-right space-x-2">
31
31
  <% if q[:paused] %>
32
32
  <%= button_to t("pgbus.queues.queues_list.resume"), pgbus.resume_queue_path(name: q[:name]),
33
33
  method: :post,
@@ -6,7 +6,7 @@
6
6
  <p class="mt-1 text-sm"><%= t("pgbus.recurring_tasks.tasks_table.empty_hint") %></p>
7
7
  </div>
8
8
  <% else %>
9
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
9
+ <table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
10
10
  <thead class="bg-gray-50 dark:bg-gray-900">
11
11
  <tr>
12
12
  <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"><%= t("pgbus.recurring_tasks.tasks_table.headers.task") %></th>
@@ -21,7 +21,7 @@
21
21
  <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
22
22
  <% @recurring_tasks.each do |task| %>
23
23
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
24
- <td class="px-4 py-3">
24
+ <td data-label="Task" class="px-4 py-3">
25
25
  <div>
26
26
  <%= link_to task[:key], pgbus.recurring_task_path(task[:id]),
27
27
  class: "text-sm font-medium text-blue-600 hover:text-blue-800" %>
@@ -31,26 +31,26 @@
31
31
  <div class="text-xs text-gray-400 mt-0.5"><%= task[:description] %></div>
32
32
  <% end %>
33
33
  </td>
34
- <td class="px-4 py-3">
34
+ <td data-label="Schedule" class="px-4 py-3">
35
35
  <div class="text-sm text-gray-900 dark:text-white"><%= task[:schedule] %></div>
36
36
  <% if task[:human_schedule] && task[:human_schedule] != task[:schedule] %>
37
37
  <div class="text-xs text-gray-400"><%= task[:human_schedule] %></div>
38
38
  <% end %>
39
39
  </td>
40
- <td class="px-4 py-3 text-sm text-gray-600">
40
+ <td data-label="Queue" class="px-4 py-3 text-sm text-gray-600">
41
41
  <%= task[:queue_name] || t("pgbus.recurring_tasks.tasks_table.default_queue") %>
42
42
  </td>
43
- <td class="px-4 py-3 text-sm text-gray-600">
43
+ <td data-label="Last Run" class="px-4 py-3 text-sm text-gray-600">
44
44
  <%= task[:last_run_at] ? pgbus_time_ago(task[:last_run_at]) : t("pgbus.recurring_tasks.tasks_table.never") %>
45
45
  </td>
46
- <td class="px-4 py-3 text-sm text-gray-600">
46
+ <td data-label="Next Run" class="px-4 py-3 text-sm text-gray-600">
47
47
  <% if task[:enabled] && task[:next_run_at] %>
48
48
  <%= pgbus_time_ago_future(task[:next_run_at]) %>
49
49
  <% else %>
50
50
  <span class="text-gray-400">—</span>
51
51
  <% end %>
52
52
  </td>
53
- <td class="px-4 py-3">
53
+ <td data-label="Status" class="px-4 py-3">
54
54
  <% if task[:enabled] %>
55
55
  <%= pgbus_recurring_health_badge(task) %>
56
56
  <% else %>
@@ -59,7 +59,7 @@
59
59
  </span>
60
60
  <% end %>
61
61
  </td>
62
- <td class="px-4 py-3 text-right space-x-1">
62
+ <td data-label="Actions" class="px-4 py-3 text-right space-x-1">
63
63
  <%= button_to task[:enabled] ? t("pgbus.recurring_tasks.tasks_table.disable") : t("pgbus.recurring_tasks.tasks_table.enable"),
64
64
  pgbus.toggle_recurring_task_path(task[:id]),
65
65
  class: "inline-flex items-center rounded px-2 py-1 text-xs font-medium " \
@@ -217,8 +217,10 @@ da:
217
217
  processes: Processer
218
218
  queues: Køer
219
219
  recurring: Gentagende
220
+ return_to_app: Tilbage til app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Skift til mørk tilstand
223
+ toggle_menu: Skift menu
222
224
  locks:
223
225
  index:
224
226
  description: Aktive unikke låse forhindrer duplikeret jobudførelse
@@ -217,8 +217,10 @@ de:
217
217
  processes: Prozesse
218
218
  queues: Warteschlangen
219
219
  recurring: Wiederkehrend
220
+ return_to_app: Zurück zur App
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Dunkelmodus umschalten
223
+ toggle_menu: Menü umschalten
222
224
  locks:
223
225
  index:
224
226
  description: Aktive Einzigartigkeitssperren verhindern doppelte Auftragserstellung
@@ -217,8 +217,10 @@ en:
217
217
  processes: Processes
218
218
  queues: Queues
219
219
  recurring: Recurring
220
+ return_to_app: Back to app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Toggle dark mode
223
+ toggle_menu: Toggle menu
222
224
  locks:
223
225
  index:
224
226
  description: Active uniqueness locks preventing duplicate job execution
@@ -217,8 +217,10 @@ es:
217
217
  processes: Procesos
218
218
  queues: Colas
219
219
  recurring: Recurrente
220
+ return_to_app: Volver a la app
220
221
  title: Panel de Pgbus
221
222
  toggle_dark_mode: Alternar modo oscuro
223
+ toggle_menu: Alternar menú
222
224
  locks:
223
225
  index:
224
226
  description: Bloqueos de unicidad activos que impiden la ejecución duplicada del trabajo
@@ -217,8 +217,10 @@ fi:
217
217
  processes: Prosessit
218
218
  queues: Jonot
219
219
  recurring: Toistuva
220
+ return_to_app: Takaisin sovellukseen
220
221
  title: Pgbus-hallintapaneeli
221
222
  toggle_dark_mode: Vaihda tumma tila
223
+ toggle_menu: Vaihda valikko
222
224
  locks:
223
225
  index:
224
226
  description: Aktiiviset ainutlaatuisuuden lukot estävät päällekkäisen työn suorittamisen
@@ -217,8 +217,10 @@ fr:
217
217
  processes: Processus
218
218
  queues: Files d'attente
219
219
  recurring: Récurrent
220
+ return_to_app: Retour à l'application
220
221
  title: Tableau de bord Pgbus
221
222
  toggle_dark_mode: Basculer en mode sombre
223
+ toggle_menu: Basculer le menu
222
224
  locks:
223
225
  index:
224
226
  description: Verrous d'unicité actifs empêchant l'exécution de travaux en double
@@ -217,8 +217,10 @@ it:
217
217
  processes: Processi
218
218
  queues: Code
219
219
  recurring: Ricorrente
220
+ return_to_app: Torna all'app
220
221
  title: Cruscotto Pgbus
221
222
  toggle_dark_mode: Attiva/disattiva modalità scura
223
+ toggle_menu: Attiva/disattiva menu
222
224
  locks:
223
225
  index:
224
226
  description: Blocchi di unicità attivi che impediscono l'esecuzione duplicata del lavoro
@@ -217,8 +217,10 @@ ja:
217
217
  processes: プロセス
218
218
  queues: キュー
219
219
  recurring: 定期実行
220
+ return_to_app: アプリに戻る
220
221
  title: Pgbus ダッシュボード
221
222
  toggle_dark_mode: ダークモード切替
223
+ toggle_menu: メニュー切替
222
224
  locks:
223
225
  index:
224
226
  description: 重複ジョブ実行を防ぐアクティブなユニークロック
@@ -217,8 +217,10 @@ nb:
217
217
  processes: Prosesser
218
218
  queues: Køer
219
219
  recurring: Gjentakende
220
+ return_to_app: Tilbake til appen
220
221
  title: Pgbus-dashbord
221
222
  toggle_dark_mode: Bytt til mørk modus
223
+ toggle_menu: Veksle meny
222
224
  locks:
223
225
  index:
224
226
  description: Aktive unike låser som forhindrer duplisert jobbkjøring
@@ -217,8 +217,10 @@ nl:
217
217
  processes: Processen
218
218
  queues: Wachtrijen
219
219
  recurring: Terugkerend
220
+ return_to_app: Terug naar app
220
221
  title: Pgbus Dashboard
221
222
  toggle_dark_mode: Donkere modus schakelen
223
+ toggle_menu: Menu wisselen
222
224
  locks:
223
225
  index:
224
226
  description: Actieve uniekheidsvergrendelingen voorkomen dubbele taakuitvoering
@@ -217,8 +217,10 @@ pt:
217
217
  processes: Processos
218
218
  queues: Filas
219
219
  recurring: Recorrente
220
+ return_to_app: Voltar ao aplicativo
220
221
  title: Painel Pgbus
221
222
  toggle_dark_mode: Alternar modo escuro
223
+ toggle_menu: Alternar menu
222
224
  locks:
223
225
  index:
224
226
  description: Bloqueios de exclusividade ativos impedindo a execução duplicada do trabalho
@@ -217,8 +217,10 @@ sv:
217
217
  processes: Processer
218
218
  queues: Köer
219
219
  recurring: Återkommande
220
+ return_to_app: Tillbaka till appen
220
221
  title: Pgbus-instrumentpanel
221
222
  toggle_dark_mode: Växla mörkt läge
223
+ toggle_menu: Växla meny
222
224
  locks:
223
225
  index:
224
226
  description: Aktiva unika lås som förhindrar duplicerad jobbexekvering
@@ -64,7 +64,7 @@ module Pgbus
64
64
 
65
65
  # Web dashboard
66
66
  attr_accessor :web_auth, :web_refresh_interval, :web_per_page, :web_live_updates, :web_data_source,
67
- :insights_default_minutes
67
+ :insights_default_minutes, :base_controller_class, :return_to_app_url
68
68
 
69
69
  def initialize
70
70
  @database_url = nil
@@ -136,6 +136,8 @@ module Pgbus
136
136
  @web_live_updates = true
137
137
  @web_data_source = nil
138
138
  @insights_default_minutes = 30 * 24 * 60 # 30 days
139
+ @base_controller_class = "::ActionController::Base"
140
+ @return_to_app_url = nil
139
141
  end
140
142
 
141
143
  def queue_name(name)
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.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
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.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson