pgbus 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +33 -3
- data/app/controllers/pgbus/application_controller.rb +8 -2
- data/app/controllers/pgbus/queues_controller.rb +6 -0
- data/app/helpers/pgbus/application_helper.rb +10 -0
- data/app/views/layouts/pgbus/application.html.erb +212 -25
- data/app/views/pgbus/dashboard/_processes_table.html.erb +5 -5
- data/app/views/pgbus/dashboard/_queues_table.html.erb +6 -6
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +4 -4
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +1 -1
- data/app/views/pgbus/events/index.html.erb +8 -8
- data/app/views/pgbus/insights/show.html.erb +31 -29
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +1 -1
- data/app/views/pgbus/jobs/_failed_table.html.erb +7 -7
- data/app/views/pgbus/locks/index.html.erb +7 -7
- data/app/views/pgbus/outbox/index.html.erb +7 -7
- data/app/views/pgbus/processes/_processes_table.html.erb +7 -7
- data/app/views/pgbus/queues/_queues_list.html.erb +12 -8
- data/app/views/pgbus/queues/show.html.erb +20 -1
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +8 -8
- data/config/locales/da.yml +18 -0
- data/config/locales/de.yml +18 -0
- data/config/locales/en.yml +18 -0
- data/config/locales/es.yml +18 -0
- data/config/locales/fi.yml +18 -0
- data/config/locales/fr.yml +18 -0
- data/config/locales/it.yml +18 -0
- data/config/locales/ja.yml +18 -0
- data/config/locales/nb.yml +18 -0
- data/config/locales/nl.yml +18 -0
- data/config/locales/pt.yml +18 -0
- data/config/locales/sv.yml +18 -0
- data/config/routes.rb +1 -1
- data/lib/pgbus/client.rb +10 -2
- data/lib/pgbus/configuration.rb +3 -1
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e16e68e83d8c1963b60a9c2137d3fbd737512b7e0fed1566b5f1320ef33c470
|
|
4
|
+
data.tar.gz: 03d54ebcc0357a61b71bbb3f11fcafa662114245c3fedc33b58a83d40718ffc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5579b34e30ee64cfff273975174d4023b921f8f64a98c1baa09df0673ccb5ba6a5db73566452a35feead0bb4584d8a969b5513efb34ada4614e2945fbc85790d
|
|
7
|
+
data.tar.gz: ea716b54c14023d91d440fb37db87358330ced5671365fd808e0e092a86963d5c2587519d7493e185c51c106a070eb5e49e36d79d441af37c5f2098528aea26e
|
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 |
|
|
@@ -687,7 +707,7 @@ pgbus help # Show help
|
|
|
687
707
|
The dashboard is a mountable Rails engine at `/pgbus` with:
|
|
688
708
|
|
|
689
709
|
- **Overview** -- queue depths, enqueued count, active processes, failure count, throughput rate
|
|
690
|
-
- **Queues** -- per-queue metrics, purge/pause/resume actions
|
|
710
|
+
- **Queues** -- per-queue metrics, purge/pause/resume/delete actions
|
|
691
711
|
- **Jobs** -- enqueued and failed jobs, retry/discard actions
|
|
692
712
|
- **Dead letter** -- DLQ messages with retry/discard, bulk actions
|
|
693
713
|
- **Processes** -- active workers/dispatcher/consumers with heartbeat status
|
|
@@ -696,7 +716,17 @@ The dashboard is a mountable Rails engine at `/pgbus` with:
|
|
|
696
716
|
- **Locks** -- active job uniqueness locks with state (queued/executing), owner PID@hostname, age
|
|
697
717
|
- **Insights** -- throughput chart (jobs/min), status distribution donut, slowest job classes table
|
|
698
718
|
|
|
699
|
-
All tables use Turbo Frames for periodic auto-refresh without page reloads.
|
|
719
|
+
All tables use Turbo Frames for periodic auto-refresh without page reloads. Destructive actions use styled confirmation dialogs (not browser `confirm()`), and flash messages appear as auto-dismissing toast notifications.
|
|
720
|
+
|
|
721
|
+
### Queue management
|
|
722
|
+
|
|
723
|
+
The queues page lets you manage PGMQ queues directly:
|
|
724
|
+
|
|
725
|
+
- **Purge** -- removes all messages from the queue (the queue itself remains)
|
|
726
|
+
- **Delete** -- permanently drops the queue from PGMQ (removes the queue table and metadata)
|
|
727
|
+
- **Pause / Resume** -- pauses or resumes job processing for a queue
|
|
728
|
+
|
|
729
|
+
All destructive actions require confirmation. Pause/resume and delete are available on both the queue index and detail pages.
|
|
700
730
|
|
|
701
731
|
### Dark mode
|
|
702
732
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Pgbus
|
|
4
|
-
class ApplicationController <
|
|
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
|
|
@@ -10,6 +10,7 @@ module Pgbus
|
|
|
10
10
|
@queue = data_source.queue_detail(params[:name])
|
|
11
11
|
redirect_to queues_path, alert: "Queue not found." and return unless @queue
|
|
12
12
|
|
|
13
|
+
@paused = data_source.queue_paused?(params[:name])
|
|
13
14
|
@messages = data_source.jobs(queue_name: params[:name], page: page_param, per_page: per_page)
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -18,6 +19,11 @@ module Pgbus
|
|
|
18
19
|
redirect_to queue_path(name: params[:name]), notice: "Queue purged."
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
def destroy
|
|
23
|
+
data_source.drop_queue(params[:name])
|
|
24
|
+
redirect_to queues_path, notice: t("pgbus.queues.destroy.success", name: params[:name])
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
def pause
|
|
22
28
|
data_source.pause_queue(params[:name], reason: params[:reason])
|
|
23
29
|
redirect_to queue_path(name: params[:name]), notice: "Queue paused."
|
|
@@ -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');
|
|
@@ -35,6 +81,71 @@
|
|
|
35
81
|
<script type="module">
|
|
36
82
|
import * as Turbo from "https://esm.sh/@hotwired/turbo@8";
|
|
37
83
|
|
|
84
|
+
// -- Custom confirm dialog (replaces browser confirm) --
|
|
85
|
+
Turbo.config.forms.confirm = (message, element) => {
|
|
86
|
+
const dialog = document.getElementById("pgbus-confirm-dialog");
|
|
87
|
+
const messageEl = document.getElementById("pgbus-confirm-message");
|
|
88
|
+
const titleEl = document.getElementById("pgbus-confirm-title");
|
|
89
|
+
const confirmBtn = document.getElementById("pgbus-confirm-btn");
|
|
90
|
+
const iconEl = document.getElementById("pgbus-confirm-icon");
|
|
91
|
+
|
|
92
|
+
// Detect action type from the element
|
|
93
|
+
const turboMethod = element.getAttribute("data-turbo-method");
|
|
94
|
+
const isDelete = turboMethod === "delete";
|
|
95
|
+
|
|
96
|
+
// Set title based on action
|
|
97
|
+
titleEl.textContent = isDelete ? "<%= t("pgbus.dialogs.delete_title", default: "Delete") %>" : "<%= t("pgbus.dialogs.confirm_title", default: "Are you sure?") %>";
|
|
98
|
+
messageEl.textContent = message;
|
|
99
|
+
|
|
100
|
+
// Style confirm button based on action severity
|
|
101
|
+
confirmBtn.className = "rounded-md px-4 py-2 text-sm font-medium text-white focus:outline-none focus:ring-2";
|
|
102
|
+
if (isDelete) {
|
|
103
|
+
confirmBtn.classList.add("bg-red-600", "hover:bg-red-500", "focus:ring-red-500");
|
|
104
|
+
confirmBtn.textContent = "<%= t("pgbus.dialogs.delete", default: "Delete") %>";
|
|
105
|
+
iconEl.className = "flex-shrink-0 flex items-center justify-center h-10 w-10 rounded-full bg-red-100 dark:bg-red-900/30";
|
|
106
|
+
} else {
|
|
107
|
+
confirmBtn.classList.add("bg-yellow-500", "hover:bg-yellow-400", "focus:ring-yellow-500");
|
|
108
|
+
confirmBtn.textContent = "<%= t("pgbus.dialogs.confirm", default: "Confirm") %>";
|
|
109
|
+
iconEl.className = "flex-shrink-0 flex items-center justify-center h-10 w-10 rounded-full bg-yellow-100 dark:bg-yellow-900/30";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
dialog.showModal();
|
|
113
|
+
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
dialog.addEventListener("close", () => {
|
|
116
|
+
resolve(dialog.returnValue === "confirm");
|
|
117
|
+
}, { once: true });
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// -- Toast notifications --
|
|
122
|
+
function showToast(message, type = "success") {
|
|
123
|
+
const container = document.getElementById("pgbus-toast-container");
|
|
124
|
+
const toast = document.createElement("div");
|
|
125
|
+
|
|
126
|
+
const colors = {
|
|
127
|
+
success: "bg-green-50 dark:bg-green-900/30 text-green-800 dark:text-green-300 border-green-200 dark:border-green-800",
|
|
128
|
+
error: "bg-red-50 dark:bg-red-900/30 text-red-800 dark:text-red-300 border-red-200 dark:border-red-800",
|
|
129
|
+
info: "bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-800",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
toast.className = `rounded-md border p-3 text-sm shadow-lg transition-all duration-300 ${colors[type] || colors.info}`;
|
|
133
|
+
toast.textContent = message;
|
|
134
|
+
container.appendChild(toast);
|
|
135
|
+
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
toast.style.opacity = "0";
|
|
138
|
+
toast.style.transform = "translateX(100%)";
|
|
139
|
+
setTimeout(() => toast.remove(), 300);
|
|
140
|
+
}, 5000);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Render flash toasts from <template> tags
|
|
144
|
+
document.querySelectorAll("template[data-pgbus-toast]").forEach(tpl => {
|
|
145
|
+
showToast(tpl.content.textContent.trim(), tpl.dataset.pgbusToast);
|
|
146
|
+
tpl.remove();
|
|
147
|
+
});
|
|
148
|
+
|
|
38
149
|
<% if Pgbus.configuration.web_live_updates %>
|
|
39
150
|
const interval = <%= Pgbus.configuration.web_refresh_interval %>;
|
|
40
151
|
if (interval > 0) {
|
|
@@ -43,8 +154,10 @@
|
|
|
43
154
|
if (document.hidden) return;
|
|
44
155
|
document.querySelectorAll("turbo-frame[data-auto-refresh]")
|
|
45
156
|
.forEach(frame => {
|
|
46
|
-
|
|
47
|
-
|
|
157
|
+
try {
|
|
158
|
+
if (!frame.src && frame.dataset.src) frame.src = frame.dataset.src;
|
|
159
|
+
if (frame.src) frame.reload();
|
|
160
|
+
} catch (_) { /* Turbo may abort in-flight fetches during navigation */ }
|
|
48
161
|
});
|
|
49
162
|
}
|
|
50
163
|
function start() { timer = setInterval(refreshFrames, interval); }
|
|
@@ -61,13 +174,24 @@
|
|
|
61
174
|
<nav class="bg-gray-900 dark:bg-gray-950 border-b border-gray-800">
|
|
62
175
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
63
176
|
<div class="flex h-14 items-center justify-between">
|
|
177
|
+
<!-- Left: brand + desktop nav -->
|
|
64
178
|
<div class="flex items-center space-x-8">
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
179
|
+
<div class="flex items-center space-x-3">
|
|
180
|
+
<% if Pgbus.configuration.return_to_app_url %>
|
|
181
|
+
<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") %>">
|
|
182
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
183
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/>
|
|
184
|
+
</svg>
|
|
185
|
+
</a>
|
|
186
|
+
<% end %>
|
|
187
|
+
<%= link_to pgbus.root_path, class: "flex items-center space-x-2" do %>
|
|
188
|
+
<span class="text-lg font-bold text-white"><%= t("pgbus.layout.brand") %></span>
|
|
189
|
+
<span class="rounded bg-gray-700 px-1.5 py-0.5 text-xs text-gray-300"><%= Pgbus::VERSION %></span>
|
|
190
|
+
<% end %>
|
|
191
|
+
</div>
|
|
69
192
|
|
|
70
|
-
|
|
193
|
+
<!-- Desktop nav links (hidden on small screens) -->
|
|
194
|
+
<div class="hidden lg:flex space-x-1">
|
|
71
195
|
<%= pgbus_nav_link t("pgbus.layout.nav.dashboard"), pgbus.root_path %>
|
|
72
196
|
<%= pgbus_nav_link t("pgbus.layout.nav.queues"), pgbus.queues_path %>
|
|
73
197
|
<%= pgbus_nav_link t("pgbus.layout.nav.jobs"), pgbus.jobs_path %>
|
|
@@ -81,6 +205,7 @@
|
|
|
81
205
|
</div>
|
|
82
206
|
</div>
|
|
83
207
|
|
|
208
|
+
<!-- Right: locale, dark mode, hamburger -->
|
|
84
209
|
<div class="flex items-center space-x-2">
|
|
85
210
|
<!-- Locale switcher -->
|
|
86
211
|
<div class="relative" id="pgbus-locale-switcher">
|
|
@@ -98,34 +223,96 @@
|
|
|
98
223
|
</div>
|
|
99
224
|
</div>
|
|
100
225
|
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
226
|
+
<!-- Dark mode toggle -->
|
|
227
|
+
<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") %>">
|
|
228
|
+
<svg class="h-5 w-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
|
|
229
|
+
<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"/>
|
|
230
|
+
</svg>
|
|
231
|
+
<svg class="h-5 w-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
|
|
232
|
+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
|
233
|
+
</svg>
|
|
234
|
+
</button>
|
|
235
|
+
|
|
236
|
+
<!-- Mobile menu button (hidden on large screens) -->
|
|
237
|
+
<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") %>">
|
|
238
|
+
<!-- Hamburger icon -->
|
|
239
|
+
<svg id="pgbus-menu-open" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
240
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
|
|
241
|
+
</svg>
|
|
242
|
+
<!-- Close icon (hidden by default) -->
|
|
243
|
+
<svg id="pgbus-menu-close" class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
244
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
245
|
+
</svg>
|
|
246
|
+
</button>
|
|
109
247
|
</div>
|
|
110
248
|
</div>
|
|
111
249
|
</div>
|
|
112
|
-
</nav>
|
|
113
250
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
251
|
+
<!-- Mobile menu (hidden by default, shown on small screens when toggled) -->
|
|
252
|
+
<div id="pgbus-mobile-menu" class="hidden lg:hidden border-t border-gray-800">
|
|
253
|
+
<div class="space-y-1 px-3 py-3">
|
|
254
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.dashboard"), pgbus.root_path %>
|
|
255
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.queues"), pgbus.queues_path %>
|
|
256
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.jobs"), pgbus.jobs_path %>
|
|
257
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.recurring"), pgbus.recurring_tasks_path %>
|
|
258
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.processes"), pgbus.processes_path %>
|
|
259
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.events"), pgbus.events_path %>
|
|
260
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.dlq"), pgbus.dead_letter_index_path %>
|
|
261
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.outbox"), pgbus.outbox_index_path %>
|
|
262
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.locks"), pgbus.locks_path %>
|
|
263
|
+
<%= pgbus_mobile_nav_link t("pgbus.layout.nav.insights"), pgbus.insights_path %>
|
|
119
264
|
</div>
|
|
120
265
|
</div>
|
|
266
|
+
</nav>
|
|
267
|
+
|
|
268
|
+
<!-- Toast container (fixed top-right) -->
|
|
269
|
+
<div id="pgbus-toast-container" class="fixed top-4 right-4 z-[100] flex flex-col space-y-2 max-w-sm"></div>
|
|
270
|
+
|
|
271
|
+
<!-- Flash messages rendered as toasts -->
|
|
272
|
+
<% if notice %>
|
|
273
|
+
<template data-pgbus-toast="success"><%= notice %></template>
|
|
121
274
|
<% end %>
|
|
122
275
|
<% if alert %>
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
276
|
+
<template data-pgbus-toast="error"><%= alert %></template>
|
|
277
|
+
<% end %>
|
|
278
|
+
|
|
279
|
+
<!-- Confirm dialog -->
|
|
280
|
+
<dialog id="pgbus-confirm-dialog" class="rounded-lg shadow-xl bg-white dark:bg-gray-800 p-0 backdrop:bg-gray-900/50 max-w-md w-full">
|
|
281
|
+
<div class="p-6">
|
|
282
|
+
<div class="flex items-start space-x-4">
|
|
283
|
+
<div id="pgbus-confirm-icon" class="flex-shrink-0 flex items-center justify-center h-10 w-10 rounded-full bg-red-100 dark:bg-red-900/30">
|
|
284
|
+
<svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
285
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/>
|
|
286
|
+
</svg>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="flex-1">
|
|
289
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-white" id="pgbus-confirm-title"></h3>
|
|
290
|
+
<p class="mt-2 text-sm text-gray-600 dark:text-gray-300" id="pgbus-confirm-message"></p>
|
|
291
|
+
</div>
|
|
126
292
|
</div>
|
|
127
293
|
</div>
|
|
128
|
-
|
|
294
|
+
<div class="flex justify-end space-x-3 px-6 py-4 bg-gray-50 dark:bg-gray-900/50 rounded-b-lg">
|
|
295
|
+
<button value="cancel" class="rounded-md px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500"><%= t("pgbus.dialogs.cancel", default: "Cancel") %></button>
|
|
296
|
+
<button value="confirm" id="pgbus-confirm-btn" class="rounded-md px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-500 focus:outline-none focus:ring-2 focus:ring-red-500"><%= t("pgbus.dialogs.confirm", default: "Confirm") %></button>
|
|
297
|
+
</div>
|
|
298
|
+
</dialog>
|
|
299
|
+
|
|
300
|
+
<!-- Alert dialog -->
|
|
301
|
+
<dialog id="pgbus-alert-dialog" class="rounded-lg shadow-xl bg-white dark:bg-gray-800 p-0 backdrop:bg-gray-900/50 max-w-md w-full">
|
|
302
|
+
<div class="p-6">
|
|
303
|
+
<div class="flex items-start space-x-4">
|
|
304
|
+
<div class="flex-shrink-0 flex items-center justify-center h-10 w-10 rounded-full bg-blue-100 dark:bg-blue-900/30">
|
|
305
|
+
<svg class="h-6 w-6 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
306
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"/>
|
|
307
|
+
</svg>
|
|
308
|
+
</div>
|
|
309
|
+
<p class="text-sm text-gray-700 dark:text-gray-300" id="pgbus-alert-message"></p>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="flex justify-end px-6 py-4 bg-gray-50 dark:bg-gray-900/50 rounded-b-lg">
|
|
313
|
+
<button value="ok" class="rounded-md px-4 py-2 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"><%= t("pgbus.dialogs.ok", default: "OK") %></button>
|
|
314
|
+
</div>
|
|
315
|
+
</dialog>
|
|
129
316
|
|
|
130
317
|
<!-- Content -->
|
|
131
318
|
<main class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
@@ -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? %>
|