pgbus 0.2.8 → 0.3.0
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/app/controllers/pgbus/frontends_controller.rb +68 -0
- data/app/frontend/pgbus/application.js +90 -0
- data/app/frontend/pgbus/modules/charts.js +79 -0
- data/app/frontend/pgbus/style.css +2 -0
- data/app/frontend/pgbus/tailwind.css +64 -0
- data/app/frontend/pgbus/vendor/apexcharts.js +38 -0
- data/app/frontend/pgbus/vendor/turbo.js +6696 -0
- data/app/views/layouts/pgbus/application.html.erb +20 -141
- data/app/views/pgbus/insights/show.html.erb +17 -78
- data/config/routes.rb +5 -0
- data/lib/pgbus/process/worker.rb +44 -5
- data/lib/pgbus/version.rb +1 -1
- metadata +8 -1
|
@@ -4,63 +4,19 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title><%= t("pgbus.layout.title") %></title>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Applied before Tailwind CDN loads so the background is correct immediately. */
|
|
10
|
-
html.dark { background-color: #030712; } /* gray-950 */
|
|
11
|
-
html.dark body { background-color: #030712; }
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<%= csp_meta_tag %>
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
}
|
|
49
|
-
</style>
|
|
50
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
51
|
-
<script>
|
|
52
|
-
tailwind.config = { darkMode: 'class' };
|
|
53
|
-
// Restore dark mode preference
|
|
10
|
+
<%# Prevent white flash in dark mode — must run before stylesheet loads %>
|
|
11
|
+
<script nonce="<%= content_security_policy_nonce %>">
|
|
54
12
|
if (localStorage.getItem('pgbus-dark') === 'true' ||
|
|
55
13
|
(!localStorage.getItem('pgbus-dark') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
56
14
|
document.documentElement.classList.add('dark');
|
|
57
15
|
}
|
|
58
|
-
// Dark mode toggle — must be in non-module script for onclick access
|
|
59
16
|
function toggleDarkMode() {
|
|
60
17
|
var isDark = document.documentElement.classList.toggle('dark');
|
|
61
18
|
localStorage.setItem('pgbus-dark', isDark);
|
|
62
19
|
}
|
|
63
|
-
// Mobile menu toggle
|
|
64
20
|
function toggleMobileMenu() {
|
|
65
21
|
var menu = document.getElementById('pgbus-mobile-menu');
|
|
66
22
|
var openIcon = document.getElementById('pgbus-menu-open');
|
|
@@ -69,7 +25,6 @@
|
|
|
69
25
|
openIcon.classList.toggle('hidden');
|
|
70
26
|
closeIcon.classList.toggle('hidden');
|
|
71
27
|
}
|
|
72
|
-
// Close locale dropdown when clicking outside
|
|
73
28
|
document.addEventListener('click', function(e) {
|
|
74
29
|
var switcher = document.getElementById('pgbus-locale-switcher');
|
|
75
30
|
var menu = document.getElementById('pgbus-locale-menu');
|
|
@@ -78,103 +33,27 @@
|
|
|
78
33
|
}
|
|
79
34
|
});
|
|
80
35
|
</script>
|
|
81
|
-
<script type="module">
|
|
82
|
-
import * as Turbo from "https://esm.sh/@hotwired/turbo@8";
|
|
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
36
|
|
|
112
|
-
|
|
37
|
+
<%# Self-hosted assets — no external CDN dependencies %>
|
|
38
|
+
<%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, locale: nil), nonce: content_security_policy_nonce %>
|
|
113
39
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
};
|
|
40
|
+
<%# Importmap for ES modules %>
|
|
41
|
+
<% importmaps = Pgbus::FrontendsController.js_modules.keys.index_with { |mod| frontend_module_path(mod, format: :js, locale: nil) } %>
|
|
42
|
+
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
|
|
43
|
+
<%= tag.script("", src: frontend_static_path(:apexcharts, format: :js, locale: nil), nonce: content_security_policy_nonce) %>
|
|
120
44
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
// Must run on turbo:load as well — module scripts only execute once,
|
|
145
|
-
// but Turbo Drive replaces the body on navigation.
|
|
146
|
-
function renderFlashToasts() {
|
|
147
|
-
document.querySelectorAll("template[data-pgbus-toast]").forEach(tpl => {
|
|
148
|
-
showToast(tpl.content.textContent.trim(), tpl.dataset.pgbusToast);
|
|
149
|
-
tpl.remove();
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
renderFlashToasts();
|
|
153
|
-
document.addEventListener("turbo:load", renderFlashToasts);
|
|
154
|
-
|
|
155
|
-
<% if Pgbus.configuration.web_live_updates %>
|
|
156
|
-
const interval = <%= Pgbus.configuration.web_refresh_interval %>;
|
|
157
|
-
if (interval > 0) {
|
|
158
|
-
let timer;
|
|
159
|
-
function refreshFrames() {
|
|
160
|
-
if (document.hidden) return;
|
|
161
|
-
document.querySelectorAll("turbo-frame[data-auto-refresh]")
|
|
162
|
-
.forEach(frame => {
|
|
163
|
-
try {
|
|
164
|
-
if (!frame.src && frame.dataset.src) frame.src = frame.dataset.src;
|
|
165
|
-
if (frame.src) frame.reload();
|
|
166
|
-
} catch (_) { /* Turbo may abort in-flight fetches during navigation */ }
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function start() { timer = setInterval(refreshFrames, interval); }
|
|
170
|
-
function stop() { clearInterval(timer); }
|
|
171
|
-
document.addEventListener("visibilitychange", () => document.hidden ? stop() : start());
|
|
172
|
-
start();
|
|
173
|
-
}
|
|
174
|
-
<% end %>
|
|
45
|
+
<script type="module" nonce="<%= content_security_policy_nonce %>">
|
|
46
|
+
import "application";
|
|
175
47
|
</script>
|
|
176
48
|
</head>
|
|
177
|
-
<body class="h-full bg-gray-50 dark:bg-gray-950 transition-colors"
|
|
49
|
+
<body class="h-full bg-gray-50 dark:bg-gray-950 transition-colors"
|
|
50
|
+
<% if Pgbus.configuration.web_live_updates %>data-pgbus-refresh-interval="<%= Pgbus.configuration.web_refresh_interval %>"<% end %>>
|
|
51
|
+
<%# i18n data for JS modules %>
|
|
52
|
+
<div id="pgbus-i18n" class="hidden"
|
|
53
|
+
data-delete-title="<%= t("pgbus.dialogs.delete_title", default: "Delete") %>"
|
|
54
|
+
data-confirm-title="<%= t("pgbus.dialogs.confirm_title", default: "Are you sure?") %>"
|
|
55
|
+
data-delete-label="<%= t("pgbus.dialogs.delete", default: "Delete") %>"
|
|
56
|
+
data-confirm-label="<%= t("pgbus.dialogs.confirm", default: "Confirm") %>"></div>
|
|
178
57
|
<div class="min-h-full">
|
|
179
58
|
<!-- Top nav -->
|
|
180
59
|
<nav class="bg-gray-900 dark:bg-gray-950 border-b border-gray-800">
|
|
@@ -93,91 +93,30 @@
|
|
|
93
93
|
</table>
|
|
94
94
|
</div>
|
|
95
95
|
|
|
96
|
-
<script
|
|
97
|
-
|
|
98
|
-
(function() {
|
|
99
|
-
// IIFE prevents "redeclaration of let" when Turbo re-executes on navigation
|
|
100
|
-
var throughputChart, statusChart;
|
|
96
|
+
<script type="module" nonce="<%= content_security_policy_nonce %>">
|
|
97
|
+
import { renderCharts, observeThemeChanges } from "charts";
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
grid: isDark ? '#374151' : '#e5e7eb',
|
|
108
|
-
tooltip: isDark ? 'dark' : 'light',
|
|
109
|
-
dataLabel: isDark ? '#fff' : '#000'
|
|
110
|
-
};
|
|
111
|
-
}
|
|
99
|
+
const i18n = {
|
|
100
|
+
seriesName: "<%= j(t("pgbus.insights.show.charts.series_name")) %>",
|
|
101
|
+
noData: "<%= j(t("pgbus.insights.show.charts.no_data")) %>",
|
|
102
|
+
failedToLoad: "<%= j(t("pgbus.insights.show.charts.failed_to_load")) %>",
|
|
103
|
+
};
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
var t = getThemeColors();
|
|
105
|
+
let chartData = null;
|
|
115
106
|
|
|
116
|
-
|
|
117
|
-
if (statusChart) statusChart.destroy();
|
|
107
|
+
observeThemeChanges(() => chartData, i18n);
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
throughputChart = new ApexCharts(document.querySelector('#throughput-chart'), {
|
|
124
|
-
series: [{ name: '<%= j(t("pgbus.insights.show.charts.series_name")) %>', data: throughputData }],
|
|
125
|
-
chart: { type: 'area', height: 280, toolbar: { show: false }, background: 'transparent', foreColor: t.text },
|
|
126
|
-
stroke: { curve: 'smooth', width: 2 },
|
|
127
|
-
fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.4, opacityTo: 0.05, stops: [0, 100] } },
|
|
128
|
-
colors: ['#6366f1'],
|
|
129
|
-
xaxis: { type: 'datetime', labels: { style: { colors: t.text } } },
|
|
130
|
-
yaxis: { labels: { style: { colors: t.text } } },
|
|
131
|
-
grid: { borderColor: t.grid },
|
|
132
|
-
tooltip: { theme: t.tooltip },
|
|
133
|
-
dataLabels: { enabled: false }
|
|
134
|
-
});
|
|
135
|
-
throughputChart.render();
|
|
136
|
-
|
|
137
|
-
var statusLabels = Object.keys(data.status_counts);
|
|
138
|
-
var statusValues = Object.values(data.status_counts);
|
|
139
|
-
var statusColors = statusLabels.map(function(s) {
|
|
140
|
-
if (s === 'success') return '#10b981';
|
|
141
|
-
if (s === 'failed') return '#ef4444';
|
|
142
|
-
if (s === 'dead_lettered') return '#f97316';
|
|
143
|
-
return '#6b7280';
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (statusLabels.length > 0) {
|
|
147
|
-
statusChart = new ApexCharts(document.querySelector('#status-chart'), {
|
|
148
|
-
series: statusValues, labels: statusLabels,
|
|
149
|
-
chart: { type: 'donut', height: 280, background: 'transparent', foreColor: t.text },
|
|
150
|
-
colors: statusColors,
|
|
151
|
-
legend: { position: 'bottom', labels: { colors: t.text } },
|
|
152
|
-
plotOptions: { pie: { donut: { size: '60%' } } },
|
|
153
|
-
dataLabels: { style: { colors: [t.dataLabel] } },
|
|
154
|
-
tooltip: { theme: t.tooltip }
|
|
155
|
-
});
|
|
156
|
-
statusChart.render();
|
|
157
|
-
} else {
|
|
158
|
-
document.querySelector('#status-chart').innerHTML =
|
|
159
|
-
'<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24"><%= j(t("pgbus.insights.show.charts.no_data")) %></p>';
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
var chartData = null;
|
|
164
|
-
fetch('<%= pgbus.api_insights_path(minutes: @minutes) %>')
|
|
165
|
-
.then(function(r) {
|
|
166
|
-
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
109
|
+
fetch("<%= pgbus.api_insights_path(minutes: @minutes) %>")
|
|
110
|
+
.then(r => {
|
|
111
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
167
112
|
return r.json();
|
|
168
113
|
})
|
|
169
|
-
.then(
|
|
170
|
-
.catch(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
var el2 = document.querySelector('#status-chart');
|
|
114
|
+
.then(data => { chartData = data; renderCharts(data, i18n); })
|
|
115
|
+
.catch(err => {
|
|
116
|
+
const msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24">' + i18n.failedToLoad + "</p>";
|
|
117
|
+
const el1 = document.querySelector("#throughput-chart");
|
|
118
|
+
const el2 = document.querySelector("#status-chart");
|
|
175
119
|
if (el1) el1.innerHTML = msg;
|
|
176
120
|
if (el2) el2.innerHTML = msg;
|
|
177
121
|
});
|
|
178
|
-
|
|
179
|
-
new MutationObserver(function() {
|
|
180
|
-
if (chartData) renderCharts(chartData);
|
|
181
|
-
}).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
182
|
-
})();
|
|
183
122
|
</script>
|
data/config/routes.rb
CHANGED
|
@@ -60,4 +60,9 @@ Pgbus::Engine.routes.draw do
|
|
|
60
60
|
get :stats, to: "stats#show"
|
|
61
61
|
get :insights, to: "insights#show"
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
scope :frontend, controller: :frontends, defaults: { version: Pgbus::VERSION.tr(".", "-") } do
|
|
65
|
+
get "modules/:version/:id", action: :module, as: :frontend_module, constraints: { format: "js" }
|
|
66
|
+
get "static/:version/:id", action: :static, as: :frontend_static
|
|
67
|
+
end
|
|
63
68
|
end
|
data/lib/pgbus/process/worker.rb
CHANGED
|
@@ -12,11 +12,13 @@ module Pgbus
|
|
|
12
12
|
def initialize(queues:, threads: 5, config: Pgbus.configuration,
|
|
13
13
|
single_active_consumer: false, consumer_priority: 0)
|
|
14
14
|
@queues = Array(queues)
|
|
15
|
+
@wildcard = @queues.include?("*")
|
|
15
16
|
@threads = threads
|
|
16
17
|
@config = config
|
|
17
18
|
@single_active_consumer = single_active_consumer
|
|
18
19
|
@consumer_priority = consumer_priority
|
|
19
20
|
@lifecycle = Lifecycle.new
|
|
21
|
+
@last_wildcard_resolve = nil
|
|
20
22
|
@jobs_processed = Concurrent::AtomicFixnum.new(0)
|
|
21
23
|
@jobs_failed = Concurrent::AtomicFixnum.new(0)
|
|
22
24
|
@in_flight = Concurrent::AtomicFixnum.new(0)
|
|
@@ -53,6 +55,7 @@ module Pgbus
|
|
|
53
55
|
loop do
|
|
54
56
|
process_signals
|
|
55
57
|
check_recycle
|
|
58
|
+
refresh_wildcard_queues
|
|
56
59
|
|
|
57
60
|
break if @lifecycle.stopped?
|
|
58
61
|
break if @lifecycle.draining? && @pool.queue_length.zero?
|
|
@@ -79,6 +82,8 @@ module Pgbus
|
|
|
79
82
|
@pool.kill
|
|
80
83
|
end
|
|
81
84
|
|
|
85
|
+
WILDCARD_REFRESH_INTERVAL = 30 # seconds
|
|
86
|
+
|
|
82
87
|
private
|
|
83
88
|
|
|
84
89
|
def claim_and_execute
|
|
@@ -125,7 +130,11 @@ module Pgbus
|
|
|
125
130
|
fetch_multi(active_queues, qty)
|
|
126
131
|
end
|
|
127
132
|
rescue StandardError => e
|
|
128
|
-
|
|
133
|
+
if e.message.include?("does not exist") && e.message.include?("pgmq.q_")
|
|
134
|
+
evict_missing_queues(e)
|
|
135
|
+
else
|
|
136
|
+
Pgbus.logger.error { "[Pgbus] Error fetching messages: #{e.message}" }
|
|
137
|
+
end
|
|
129
138
|
[]
|
|
130
139
|
end
|
|
131
140
|
|
|
@@ -184,9 +193,8 @@ module Pgbus
|
|
|
184
193
|
end
|
|
185
194
|
|
|
186
195
|
# Resolve "*" to all non-DLQ queues from pgmq.meta, stripping the prefix.
|
|
187
|
-
# Called once at startup. If no wildcard, this is a no-op.
|
|
188
196
|
def resolve_wildcard_queues
|
|
189
|
-
return unless @
|
|
197
|
+
return unless @wildcard
|
|
190
198
|
|
|
191
199
|
dlq_suffix = config.dead_letter_queue_suffix
|
|
192
200
|
prefix = "#{config.queue_prefix}_"
|
|
@@ -201,12 +209,39 @@ module Pgbus
|
|
|
201
209
|
Pgbus.logger.warn { "[Pgbus] Wildcard queue '*' resolved to no queues — falling back to default" }
|
|
202
210
|
@queues = [config.default_queue]
|
|
203
211
|
else
|
|
212
|
+
if @last_wildcard_resolve && resolved != @queues
|
|
213
|
+
Pgbus.logger.info { "[Pgbus] Wildcard queues changed: #{@queues.join(", ")} → #{resolved.join(", ")}" }
|
|
214
|
+
end
|
|
204
215
|
@queues = resolved
|
|
205
|
-
Pgbus.logger.info { "[Pgbus] Wildcard queue '*' resolved to: #{@queues.join(", ")}" }
|
|
216
|
+
Pgbus.logger.info { "[Pgbus] Wildcard queue '*' resolved to: #{@queues.join(", ")}" } unless @last_wildcard_resolve
|
|
206
217
|
end
|
|
218
|
+
@last_wildcard_resolve = monotonic_now
|
|
207
219
|
rescue StandardError => e
|
|
208
220
|
Pgbus.logger.error { "[Pgbus] Failed to resolve wildcard queues: #{e.message} — falling back to default" }
|
|
209
|
-
@queues = [config.default_queue]
|
|
221
|
+
@queues = [config.default_queue] unless @last_wildcard_resolve
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Periodically re-resolve wildcard queues to pick up new queues and
|
|
225
|
+
# drop deleted ones without requiring a worker restart.
|
|
226
|
+
def refresh_wildcard_queues
|
|
227
|
+
return unless @wildcard
|
|
228
|
+
return if @last_wildcard_resolve && (monotonic_now - @last_wildcard_resolve) < WILDCARD_REFRESH_INTERVAL
|
|
229
|
+
|
|
230
|
+
resolve_wildcard_queues
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# When a "relation does not exist" error occurs, the queue was deleted.
|
|
234
|
+
# Extract the queue name from the error and remove it from the active list.
|
|
235
|
+
def evict_missing_queues(error)
|
|
236
|
+
prefix = "#{config.queue_prefix}_"
|
|
237
|
+
if error.message =~ /pgmq\.q_(\w+)/
|
|
238
|
+
physical_name = Regexp.last_match(1)
|
|
239
|
+
logical_name = physical_name.delete_prefix(prefix)
|
|
240
|
+
if @queues.delete(logical_name)
|
|
241
|
+
Pgbus.logger.warn { "[Pgbus] Evicted deleted queue '#{logical_name}' (#{physical_name}) from worker" }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
Pgbus.logger.error { "[Pgbus] Queue table missing: #{error.message}" }
|
|
210
245
|
end
|
|
211
246
|
|
|
212
247
|
def check_recycle
|
|
@@ -287,6 +322,10 @@ module Pgbus
|
|
|
287
322
|
restore_signals
|
|
288
323
|
Pgbus.logger.info { "[Pgbus] Worker stopped. Processed: #{@jobs_processed.value}, Failed: #{@jobs_failed.value}" }
|
|
289
324
|
end
|
|
325
|
+
|
|
326
|
+
def monotonic_now
|
|
327
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
328
|
+
end
|
|
290
329
|
end
|
|
291
330
|
end
|
|
292
331
|
end
|
data/lib/pgbus/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -127,6 +127,7 @@ files:
|
|
|
127
127
|
- app/controllers/pgbus/dashboard_controller.rb
|
|
128
128
|
- app/controllers/pgbus/dead_letter_controller.rb
|
|
129
129
|
- app/controllers/pgbus/events_controller.rb
|
|
130
|
+
- app/controllers/pgbus/frontends_controller.rb
|
|
130
131
|
- app/controllers/pgbus/insights_controller.rb
|
|
131
132
|
- app/controllers/pgbus/jobs_controller.rb
|
|
132
133
|
- app/controllers/pgbus/locale_controller.rb
|
|
@@ -135,6 +136,12 @@ files:
|
|
|
135
136
|
- app/controllers/pgbus/processes_controller.rb
|
|
136
137
|
- app/controllers/pgbus/queues_controller.rb
|
|
137
138
|
- app/controllers/pgbus/recurring_tasks_controller.rb
|
|
139
|
+
- app/frontend/pgbus/application.js
|
|
140
|
+
- app/frontend/pgbus/modules/charts.js
|
|
141
|
+
- app/frontend/pgbus/style.css
|
|
142
|
+
- app/frontend/pgbus/tailwind.css
|
|
143
|
+
- app/frontend/pgbus/vendor/apexcharts.js
|
|
144
|
+
- app/frontend/pgbus/vendor/turbo.js
|
|
138
145
|
- app/helpers/pgbus/application_helper.rb
|
|
139
146
|
- app/models/pgbus/application_record.rb
|
|
140
147
|
- app/models/pgbus/batch_entry.rb
|