pgbus 0.2.9 → 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/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/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
|