pgbus 0.1.5 → 0.1.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 +326 -11
- data/app/controllers/pgbus/api/insights_controller.rb +16 -0
- data/app/controllers/pgbus/insights_controller.rb +10 -0
- data/app/controllers/pgbus/locks_controller.rb +9 -0
- data/app/helpers/pgbus/application_helper.rb +28 -0
- data/app/models/pgbus/job_lock.rb +82 -0
- data/app/models/pgbus/job_stat.rb +94 -0
- data/app/views/layouts/pgbus/application.html.erb +31 -8
- data/app/views/pgbus/dashboard/_processes_table.html.erb +6 -6
- data/app/views/pgbus/dashboard/_queues_table.html.erb +6 -6
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +5 -5
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +20 -20
- data/app/views/pgbus/dashboard/show.html.erb +1 -1
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +12 -12
- data/app/views/pgbus/dead_letter/index.html.erb +1 -1
- data/app/views/pgbus/dead_letter/show.html.erb +10 -10
- data/app/views/pgbus/events/index.html.erb +15 -15
- data/app/views/pgbus/events/show.html.erb +5 -5
- data/app/views/pgbus/insights/show.html.erb +161 -0
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +13 -13
- data/app/views/pgbus/jobs/_failed_table.html.erb +7 -7
- data/app/views/pgbus/jobs/index.html.erb +1 -1
- data/app/views/pgbus/jobs/show.html.erb +10 -10
- data/app/views/pgbus/locks/index.html.erb +53 -0
- data/app/views/pgbus/outbox/index.html.erb +12 -12
- data/app/views/pgbus/processes/_processes_table.html.erb +6 -6
- data/app/views/pgbus/processes/index.html.erb +1 -1
- data/app/views/pgbus/queues/_queues_list.html.erb +5 -5
- data/app/views/pgbus/queues/index.html.erb +1 -1
- data/app/views/pgbus/queues/show.html.erb +7 -7
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +6 -6
- data/app/views/pgbus/recurring_tasks/index.html.erb +1 -1
- data/app/views/pgbus/recurring_tasks/show.html.erb +22 -22
- data/config/routes.rb +3 -0
- data/lib/generators/pgbus/add_job_locks_generator.rb +52 -0
- data/lib/generators/pgbus/add_job_stats_generator.rb +52 -0
- data/lib/generators/pgbus/add_outbox_generator.rb +1 -1
- data/lib/generators/pgbus/add_queue_states_generator.rb +1 -1
- data/lib/generators/pgbus/add_recurring_generator.rb +1 -1
- data/lib/generators/pgbus/install_generator.rb +1 -1
- data/lib/generators/pgbus/templates/add_job_locks.rb.erb +21 -0
- data/lib/generators/pgbus/templates/add_job_stats.rb.erb +18 -0
- data/lib/generators/pgbus/upgrade_pgmq_generator.rb +1 -1
- data/lib/pgbus/active_job/adapter.rb +58 -4
- data/lib/pgbus/active_job/executor.rb +45 -0
- data/lib/pgbus/client.rb +8 -22
- data/lib/pgbus/configuration.rb +6 -0
- data/lib/pgbus/engine.rb +1 -0
- data/lib/pgbus/process/consumer_priority.rb +64 -0
- data/lib/pgbus/process/dispatcher.rb +29 -0
- data/lib/pgbus/process/queue_lock.rb +87 -0
- data/lib/pgbus/process/supervisor.rb +6 -1
- data/lib/pgbus/process/wake_signal.rb +53 -0
- data/lib/pgbus/process/worker.rb +36 -6
- data/lib/pgbus/queue_factory.rb +62 -0
- data/lib/pgbus/uniqueness.rb +169 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +49 -0
- data/lib/pgbus.rb +1 -0
- metadata +17 -1
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" class="h-full
|
|
2
|
+
<html lang="en" class="h-full">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Pgbus Dashboard</title>
|
|
7
7
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script>
|
|
9
|
+
tailwind.config = { darkMode: 'class' };
|
|
10
|
+
// Restore dark mode preference
|
|
11
|
+
if (localStorage.getItem('pgbus-dark') === 'true' ||
|
|
12
|
+
(!localStorage.getItem('pgbus-dark') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
13
|
+
document.documentElement.classList.add('dark');
|
|
14
|
+
}
|
|
15
|
+
// Dark mode toggle — must be in non-module script for onclick access
|
|
16
|
+
function toggleDarkMode() {
|
|
17
|
+
var isDark = document.documentElement.classList.toggle('dark');
|
|
18
|
+
localStorage.setItem('pgbus-dark', isDark);
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
8
21
|
<script type="module">
|
|
9
22
|
import * as Turbo from "https://cdn.jsdelivr.net/npm/@hotwired/turbo@8/dist/turbo.es2017.esm.js";
|
|
10
23
|
|
|
11
24
|
<% if Pgbus.configuration.web_live_updates %>
|
|
12
|
-
// Auto-refresh turbo frames, pausing when tab is hidden
|
|
13
25
|
const interval = <%= Pgbus.configuration.web_refresh_interval %>;
|
|
14
26
|
if (interval > 0) {
|
|
15
27
|
let timer;
|
|
@@ -26,10 +38,10 @@
|
|
|
26
38
|
<% end %>
|
|
27
39
|
</script>
|
|
28
40
|
</head>
|
|
29
|
-
<body class="h-full">
|
|
41
|
+
<body class="h-full bg-gray-50 dark:bg-gray-950 transition-colors">
|
|
30
42
|
<div class="min-h-full">
|
|
31
43
|
<!-- Top nav -->
|
|
32
|
-
<nav class="bg-gray-900">
|
|
44
|
+
<nav class="bg-gray-900 dark:bg-gray-950 border-b border-gray-800">
|
|
33
45
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
34
46
|
<div class="flex h-14 items-center justify-between">
|
|
35
47
|
<div class="flex items-center space-x-8">
|
|
@@ -47,8 +59,19 @@
|
|
|
47
59
|
<%= pgbus_nav_link "Events", pgbus.events_path %>
|
|
48
60
|
<%= pgbus_nav_link "DLQ", pgbus.dead_letter_index_path %>
|
|
49
61
|
<%= pgbus_nav_link "Outbox", pgbus.outbox_index_path %>
|
|
62
|
+
<%= pgbus_nav_link "Locks", pgbus.locks_path %>
|
|
63
|
+
<%= pgbus_nav_link "Insights", pgbus.insights_path %>
|
|
50
64
|
</div>
|
|
51
65
|
</div>
|
|
66
|
+
|
|
67
|
+
<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="Toggle dark mode">
|
|
68
|
+
<svg class="h-5 w-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
|
|
69
|
+
<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"/>
|
|
70
|
+
</svg>
|
|
71
|
+
<svg class="h-5 w-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
|
|
72
|
+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
|
73
|
+
</svg>
|
|
74
|
+
</button>
|
|
52
75
|
</div>
|
|
53
76
|
</div>
|
|
54
77
|
</nav>
|
|
@@ -56,15 +79,15 @@
|
|
|
56
79
|
<!-- Flash messages -->
|
|
57
80
|
<% if notice %>
|
|
58
81
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 mt-4">
|
|
59
|
-
<div class="rounded-md bg-green-50 p-3">
|
|
60
|
-
<p class="text-sm text-green-800"><%= notice %></p>
|
|
82
|
+
<div class="rounded-md bg-green-50 dark:bg-green-900/30 p-3">
|
|
83
|
+
<p class="text-sm text-green-800 dark:text-green-300"><%= notice %></p>
|
|
61
84
|
</div>
|
|
62
85
|
</div>
|
|
63
86
|
<% end %>
|
|
64
87
|
<% if alert %>
|
|
65
88
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 mt-4">
|
|
66
|
-
<div class="rounded-md bg-red-50 p-3">
|
|
67
|
-
<p class="text-sm text-red-800"><%= alert %></p>
|
|
89
|
+
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-3">
|
|
90
|
+
<p class="text-sm text-red-800 dark:text-red-300"><%= alert %></p>
|
|
68
91
|
</div>
|
|
69
92
|
</div>
|
|
70
93
|
<% end %>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-processes" data-auto-refresh src="<%= pgbus.root_path(frame: 'processes') %>">
|
|
2
2
|
<div>
|
|
3
|
-
<h2 class="text-lg font-semibold text-gray-900 mb-3">Active Processes</h2>
|
|
4
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
5
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
6
|
-
<thead class="bg-gray-50">
|
|
3
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Active Processes</h2>
|
|
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">
|
|
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">Kind</th>
|
|
9
9
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Host</th>
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Status</th>
|
|
12
12
|
</tr>
|
|
13
13
|
</thead>
|
|
14
|
-
<tbody class="divide-y divide-gray-100">
|
|
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"><%= p[:kind] %></td>
|
|
17
|
+
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= p[:kind] %></td>
|
|
18
18
|
<td class="px-4 py-3 text-sm text-gray-500"><%= p[:hostname] %></td>
|
|
19
19
|
<td class="px-4 py-3 text-sm text-gray-500"><%= p[:pid] %></td>
|
|
20
20
|
<td class="px-4 py-3 text-sm"><%= pgbus_status_badge(p[:healthy]) %></td>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-queues" data-auto-refresh src="<%= pgbus.root_path(frame: 'queues') %>">
|
|
2
2
|
<div class="mb-8">
|
|
3
3
|
<div class="flex items-center justify-between mb-3">
|
|
4
|
-
<h2 class="text-lg font-semibold text-gray-900">Queues</h2>
|
|
4
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Queues</h2>
|
|
5
5
|
<%= link_to "View all", pgbus.queues_path, class: "text-sm text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
9
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
10
|
-
<thead class="bg-gray-50">
|
|
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">
|
|
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">Queue</th>
|
|
13
13
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500">Depth</th>
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500">Total</th>
|
|
17
17
|
</tr>
|
|
18
18
|
</thead>
|
|
19
|
-
<tbody class="divide-y divide-gray-100">
|
|
19
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
20
20
|
<% @queues.each do |q| %>
|
|
21
|
-
<tr class="hover:bg-gray-50">
|
|
21
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
|
|
22
22
|
<td 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]) %>
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-failures" data-auto-refresh src="<%= pgbus.root_path(frame: 'failures') %>">
|
|
2
2
|
<div>
|
|
3
3
|
<div class="flex items-center justify-between mb-3">
|
|
4
|
-
<h2 class="text-lg font-semibold text-gray-900">Recent Failures</h2>
|
|
4
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Recent Failures</h2>
|
|
5
5
|
<% if @recent_failures.any? %>
|
|
6
6
|
<%= link_to "View all", pgbus.jobs_path(status: "failed"), class: "text-sm text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
|
|
7
7
|
<% end %>
|
|
8
8
|
</div>
|
|
9
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
10
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
11
|
-
<thead class="bg-gray-50">
|
|
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">
|
|
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">Queue</th>
|
|
14
14
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Error</th>
|
|
15
15
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">When</th>
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
|
-
<tbody class="divide-y divide-gray-100">
|
|
18
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
19
19
|
<% @recent_failures.each do |f| %>
|
|
20
20
|
<tr>
|
|
21
21
|
<td class="px-4 py-3 text-sm text-gray-700"><%= f["queue_name"] %></td>
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-stats" data-auto-refresh src="<%= pgbus.root_path(frame: 'stats') %>">
|
|
2
2
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-6 mb-8">
|
|
3
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
4
|
-
<p class="text-sm font-medium text-gray-500">Queues</p>
|
|
5
|
-
<p class="mt-1 text-3xl font-semibold text-gray-900"><%= @stats[:total_queues] %></p>
|
|
3
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
4
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Queues</p>
|
|
5
|
+
<p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= @stats[:total_queues] %></p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
9
|
-
<p class="text-sm font-medium text-gray-500">Enqueued</p>
|
|
10
|
-
<p class="mt-1 text-3xl font-semibold text-gray-900"><%= pgbus_number(@stats[:total_depth]) %></p>
|
|
11
|
-
<p class="text-xs text-gray-400"><%= pgbus_number(@stats[:total_visible]) %> visible</p>
|
|
8
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
9
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Enqueued</p>
|
|
10
|
+
<p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@stats[:total_depth]) %></p>
|
|
11
|
+
<p class="text-xs text-gray-400 dark:text-gray-500"><%= pgbus_number(@stats[:total_visible]) %> visible</p>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
15
|
-
<p class="text-sm font-medium text-gray-500">Processes</p>
|
|
16
|
-
<p class="mt-1 text-3xl font-semibold <%= @stats[:active_processes] > 0 ? 'text-green-600' : 'text-gray-400' %>">
|
|
14
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
15
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Processes</p>
|
|
16
|
+
<p class="mt-1 text-3xl font-semibold <%= @stats[:active_processes] > 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-400' %>">
|
|
17
17
|
<%= @stats[:active_processes] %>
|
|
18
18
|
</p>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
22
|
-
<p class="text-sm font-medium text-gray-500">Recurring</p>
|
|
23
|
-
<p class="mt-1 text-3xl font-semibold text-gray-900"><%= @stats[:recurring_count] %></p>
|
|
21
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
22
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Recurring</p>
|
|
23
|
+
<p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= @stats[:recurring_count] %></p>
|
|
24
24
|
<p class="text-xs text-gray-400">
|
|
25
|
-
<%= link_to "View tasks", pgbus.recurring_tasks_path, class: "text-blue-500 hover:text-blue-700" %>
|
|
25
|
+
<%= link_to "View tasks", pgbus.recurring_tasks_path, class: "text-blue-500 hover:text-blue-700 dark:text-blue-400" %>
|
|
26
26
|
</p>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
30
|
-
<p class="text-sm font-medium text-gray-500">Failed / DLQ</p>
|
|
31
|
-
<p class="mt-1 text-3xl font-semibold <%= (@stats[:failed_count] + @stats[:dlq_depth]) > 0 ? 'text-red-600' : 'text-gray-900' %>">
|
|
29
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
30
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Failed / DLQ</p>
|
|
31
|
+
<p class="mt-1 text-3xl font-semibold <%= (@stats[:failed_count] + @stats[:dlq_depth]) > 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white' %>">
|
|
32
32
|
<%= @stats[:failed_count] %> / <%= @stats[:dlq_depth] %>
|
|
33
33
|
</p>
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
|
-
<div class="rounded-lg bg-white p-5 shadow ring-1 ring-gray-200">
|
|
37
|
-
<p class="text-sm font-medium text-gray-500">Throughput</p>
|
|
38
|
-
<p class="mt-1 text-3xl font-semibold text-gray-900"><%= @stats[:throughput_rate] %></p>
|
|
36
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
37
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Throughput</p>
|
|
38
|
+
<p class="mt-1 text-3xl font-semibold text-gray-900 dark:text-white"><%= @stats[:throughput_rate] %></p>
|
|
39
39
|
<p class="text-xs text-gray-400">msgs/s</p>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<turbo-frame id="dlq-messages" data-auto-refresh src="<%= pgbus.dead_letter_index_path(frame: 'list') %>">
|
|
2
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
3
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
4
|
-
<thead class="bg-gray-50">
|
|
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">
|
|
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">ID</th>
|
|
7
7
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Job Class</th>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Reads</th>
|
|
11
11
|
</tr>
|
|
12
12
|
</thead>
|
|
13
|
-
<tbody class="divide-y divide-gray-100">
|
|
13
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
14
14
|
<% @messages.each do |m| %>
|
|
15
15
|
<% payload = pgbus_parse_message(m[:message]) %>
|
|
16
16
|
<% dlq_suffix = Pgbus.configuration.dead_letter_queue_suffix %>
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
<tr>
|
|
19
19
|
<td colspan="5" class="p-0">
|
|
20
20
|
<details class="group">
|
|
21
|
-
<summary class="flex cursor-pointer hover:bg-gray-50 list-none">
|
|
22
|
-
<span class="w-16 shrink-0 px-4 py-3 text-sm font-mono text-gray-900"><%= m[:msg_id] %></span>
|
|
23
|
-
<span class="flex-1 px-4 py-3 text-sm font-medium text-gray-900"><%= payload["job_class"] || "—" %></span>
|
|
21
|
+
<summary class="flex cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900 list-none">
|
|
22
|
+
<span class="w-16 shrink-0 px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= m[:msg_id] %></span>
|
|
23
|
+
<span class="flex-1 px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= payload["job_class"] || "—" %></span>
|
|
24
24
|
<span class="w-40 shrink-0 px-4 py-3 text-sm text-gray-700"><%= source_queue %></span>
|
|
25
25
|
<span class="w-28 shrink-0 px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(m[:enqueued_at]) %></span>
|
|
26
26
|
<span class="w-16 shrink-0 px-4 py-3 text-sm text-gray-500"><%= m[:read_ct] %></span>
|
|
27
27
|
</summary>
|
|
28
|
-
<div class="px-4 pb-4 bg-gray-50 border-t border-gray-100">
|
|
28
|
+
<div class="px-4 pb-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-100">
|
|
29
29
|
<div class="flex items-center justify-between mt-3 mb-3">
|
|
30
30
|
<span class="text-xs font-mono text-gray-400">Job ID: <%= payload["job_id"] %></span>
|
|
31
31
|
<div class="flex space-x-2">
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
<div class="grid grid-cols-2 gap-4 mb-3">
|
|
41
41
|
<div>
|
|
42
42
|
<span class="text-xs font-medium text-gray-500">Arguments</span>
|
|
43
|
-
<pre class="text-xs text-gray-700 bg-white rounded p-2 mt-1 overflow-x-auto max-h-40"><%= JSON.pretty_generate(payload["arguments"] || []) rescue "—" %></pre>
|
|
43
|
+
<pre class="text-xs text-gray-700 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-40"><%= JSON.pretty_generate(payload["arguments"] || []) rescue "—" %></pre>
|
|
44
44
|
</div>
|
|
45
45
|
<div>
|
|
46
46
|
<span class="text-xs font-medium text-gray-500">Metadata</span>
|
|
47
|
-
<div class="text-xs text-gray-600 bg-white rounded p-2 mt-1 space-y-1">
|
|
47
|
+
<div class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 space-y-1">
|
|
48
48
|
<% if payload["queue_name"] %><p><strong>Queue:</strong> <%= payload["queue_name"] %></p><% end %>
|
|
49
49
|
<% if payload["priority"] %><p><strong>Priority:</strong> <%= payload["priority"] %></p><% end %>
|
|
50
50
|
<% if payload["executions"] %><p><strong>Executions:</strong> <%= payload["executions"] %></p><% end %>
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
</div>
|
|
56
56
|
<details class="mt-2">
|
|
57
57
|
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700">Full JSON payload</summary>
|
|
58
|
-
<pre class="text-xs text-gray-600 bg-white rounded p-2 mt-1 overflow-x-auto max-h-96"><%= JSON.pretty_generate(payload) rescue m[:message] %></pre>
|
|
58
|
+
<pre class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-96"><%= JSON.pretty_generate(payload) rescue m[:message] %></pre>
|
|
59
59
|
</details>
|
|
60
60
|
<% if m[:headers] %>
|
|
61
61
|
<details class="mt-2">
|
|
62
62
|
<summary class="text-xs font-medium text-gray-500 cursor-pointer hover:text-gray-700">Headers</summary>
|
|
63
|
-
<pre class="text-xs text-gray-600 bg-white rounded p-2 mt-1 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(m[:headers])) rescue m[:headers] %></pre>
|
|
63
|
+
<pre class="text-xs text-gray-600 bg-white dark:bg-gray-800 rounded p-2 mt-1 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(m[:headers])) rescue m[:headers] %></pre>
|
|
64
64
|
</details>
|
|
65
65
|
<% end %>
|
|
66
66
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="flex items-center justify-between mb-6">
|
|
2
|
-
<h1 class="text-2xl font-bold text-gray-900">Dead Letter Queue</h1>
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Dead Letter Queue</h1>
|
|
3
3
|
<% if @messages.any? %>
|
|
4
4
|
<div class="flex space-x-2">
|
|
5
5
|
<%= button_to "Retry All", pgbus.retry_all_dead_letter_index_path, method: :post,
|
|
@@ -3,38 +3,38 @@
|
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<% if @message %>
|
|
6
|
-
<h1 class="text-2xl font-bold text-gray-900 mb-6">DLQ Message #<%= @message[:msg_id] %></h1>
|
|
6
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">DLQ Message #<%= @message[:msg_id] %></h1>
|
|
7
7
|
|
|
8
|
-
<div class="rounded-lg bg-white shadow ring-1 ring-gray-200 p-6 mb-6">
|
|
8
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
9
9
|
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
10
10
|
<div>
|
|
11
11
|
<dt class="text-sm font-medium text-gray-500">Source Queue</dt>
|
|
12
|
-
<dd class="text-sm text-gray-900"><%= @message[:queue_name] %></dd>
|
|
12
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:queue_name] %></dd>
|
|
13
13
|
</div>
|
|
14
14
|
<div>
|
|
15
15
|
<dt class="text-sm font-medium text-gray-500">Enqueued At</dt>
|
|
16
|
-
<dd class="text-sm text-gray-900"><%= @message[:enqueued_at] %></dd>
|
|
16
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:enqueued_at] %></dd>
|
|
17
17
|
</div>
|
|
18
18
|
<div>
|
|
19
19
|
<dt class="text-sm font-medium text-gray-500">Read Count</dt>
|
|
20
|
-
<dd class="text-sm text-gray-900"><%= @message[:read_ct] %></dd>
|
|
20
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:read_ct] %></dd>
|
|
21
21
|
</div>
|
|
22
22
|
<div>
|
|
23
23
|
<dt class="text-sm font-medium text-gray-500">Visibility Timeout</dt>
|
|
24
|
-
<dd class="text-sm text-gray-900"><%= @message[:vt] %></dd>
|
|
24
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @message[:vt] %></dd>
|
|
25
25
|
</div>
|
|
26
26
|
</dl>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
|
-
<div class="rounded-lg bg-white shadow ring-1 ring-gray-200 p-6 mb-6">
|
|
29
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
30
30
|
<h2 class="text-sm font-medium text-gray-500 mb-2">Payload</h2>
|
|
31
|
-
<pre class="text-xs text-gray-600 bg-gray-50 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:message])) rescue @message[:message] %></pre>
|
|
31
|
+
<pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:message])) rescue @message[:message] %></pre>
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<% if @message[:headers] %>
|
|
35
|
-
<div class="rounded-lg bg-white shadow ring-1 ring-gray-200 p-6 mb-6">
|
|
35
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6 mb-6">
|
|
36
36
|
<h2 class="text-sm font-medium text-gray-500 mb-2">Headers</h2>
|
|
37
|
-
<pre class="text-xs text-gray-600 bg-gray-50 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:headers])) rescue @message[:headers] %></pre>
|
|
37
|
+
<pre class="text-xs text-gray-600 bg-gray-50 dark:bg-gray-900 rounded p-4 overflow-x-auto"><%= JSON.pretty_generate(JSON.parse(@message[:headers])) rescue @message[:headers] %></pre>
|
|
38
38
|
</div>
|
|
39
39
|
<% end %>
|
|
40
40
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
<h1 class="text-2xl font-bold text-gray-900 mb-6">Events</h1>
|
|
1
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Events</h1>
|
|
2
2
|
|
|
3
3
|
<!-- Registered Subscribers -->
|
|
4
4
|
<div class="mb-8">
|
|
5
|
-
<h2 class="text-lg font-semibold text-gray-900 mb-3">Registered Subscribers</h2>
|
|
6
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
7
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
8
|
-
<thead class="bg-gray-50">
|
|
5
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Registered Subscribers</h2>
|
|
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">
|
|
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">Pattern</th>
|
|
11
11
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Handler</th>
|
|
12
12
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Queue</th>
|
|
13
13
|
</tr>
|
|
14
14
|
</thead>
|
|
15
|
-
<tbody class="divide-y divide-gray-100">
|
|
15
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
16
16
|
<% @subscribers.each do |s| %>
|
|
17
|
-
<tr class="hover:bg-gray-50">
|
|
17
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 dark:bg-gray-900">
|
|
18
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"><%= s[:handler_class] %></td>
|
|
19
|
+
<td class="px-4 py-3 text-sm text-gray-900 dark:text-white"><%= s[:handler_class] %></td>
|
|
20
20
|
<td class="px-4 py-3 text-sm text-gray-500"><%= s[:queue_name] %></td>
|
|
21
21
|
</tr>
|
|
22
22
|
<% end %>
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
|
|
31
31
|
<!-- Processed Events (Audit Trail) -->
|
|
32
32
|
<div>
|
|
33
|
-
<h2 class="text-lg font-semibold text-gray-900 mb-3">Processed Events</h2>
|
|
34
|
-
<div class="overflow-hidden rounded-lg bg-white shadow ring-1 ring-gray-200">
|
|
35
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
36
|
-
<thead class="bg-gray-50">
|
|
33
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Processed Events</h2>
|
|
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">
|
|
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">Event ID</th>
|
|
39
39
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Handler</th>
|
|
40
40
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Processed At</th>
|
|
41
41
|
</tr>
|
|
42
42
|
</thead>
|
|
43
|
-
<tbody class="divide-y divide-gray-100">
|
|
43
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
44
44
|
<% @events.each do |e| %>
|
|
45
|
-
<tr class="hover:bg-gray-50">
|
|
46
|
-
<td class="px-4 py-3 text-sm font-mono text-gray-900"><%= e["event_id"] %></td>
|
|
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
47
|
<td class="px-4 py-3 text-sm text-gray-700"><%= e["handler_class"] %></td>
|
|
48
48
|
<td class="px-4 py-3 text-sm text-gray-500"><%= pgbus_time_ago(e["processed_at"]) %></td>
|
|
49
49
|
</tr>
|
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<% if @event %>
|
|
6
|
-
<h1 class="text-2xl font-bold text-gray-900 mb-6">Event <%= @event["event_id"] %></h1>
|
|
6
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Event <%= @event["event_id"] %></h1>
|
|
7
7
|
|
|
8
|
-
<div class="rounded-lg bg-white shadow ring-1 ring-gray-200 p-6">
|
|
8
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700 p-6">
|
|
9
9
|
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
10
10
|
<div>
|
|
11
11
|
<dt class="text-sm font-medium text-gray-500">Event ID</dt>
|
|
12
|
-
<dd class="text-sm font-mono text-gray-900"><%= @event["event_id"] %></dd>
|
|
12
|
+
<dd class="text-sm font-mono text-gray-900 dark:text-white"><%= @event["event_id"] %></dd>
|
|
13
13
|
</div>
|
|
14
14
|
<div>
|
|
15
15
|
<dt class="text-sm font-medium text-gray-500">Handler</dt>
|
|
16
|
-
<dd class="text-sm text-gray-900"><%= @event["handler_class"] %></dd>
|
|
16
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @event["handler_class"] %></dd>
|
|
17
17
|
</div>
|
|
18
18
|
<div>
|
|
19
19
|
<dt class="text-sm font-medium text-gray-500">Processed At</dt>
|
|
20
|
-
<dd class="text-sm text-gray-900"><%= @event["processed_at"] %></dd>
|
|
20
|
+
<dd class="text-sm text-gray-900 dark:text-white"><%= @event["processed_at"] %></dd>
|
|
21
21
|
</div>
|
|
22
22
|
</dl>
|
|
23
23
|
</div>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<div class="mb-6">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Insights</h1>
|
|
3
|
+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Job performance metrics for the last hour</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<!-- Summary cards -->
|
|
7
|
+
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6 mb-8">
|
|
8
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
9
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Total Jobs</dt>
|
|
10
|
+
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_number(@summary[:total]) %></dd>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
13
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Succeeded</dt>
|
|
14
|
+
<dd class="mt-1 text-2xl font-semibold text-green-600 dark:text-green-400"><%= pgbus_number(@summary[:success]) %></dd>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
17
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Failed</dt>
|
|
18
|
+
<dd class="mt-1 text-2xl font-semibold text-red-600 dark:text-red-400"><%= pgbus_number(@summary[:failed]) %></dd>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
21
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Dead Lettered</dt>
|
|
22
|
+
<dd class="mt-1 text-2xl font-semibold text-orange-600 dark:text-orange-400"><%= pgbus_number(@summary[:dead_lettered]) %></dd>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
25
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Avg Duration</dt>
|
|
26
|
+
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_ms_duration(@summary[:avg_duration_ms]) %></dd>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-4 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
29
|
+
<dt class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Max Duration</dt>
|
|
30
|
+
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white"><%= pgbus_ms_duration(@summary[:max_duration_ms]) %></dd>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Charts -->
|
|
35
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
36
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
37
|
+
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-4">Throughput (jobs/min)</h3>
|
|
38
|
+
<div id="throughput-chart" style="height: 280px;"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
41
|
+
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-4">Status Distribution</h3>
|
|
42
|
+
<div id="status-chart" style="height: 280px;"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Slowest job classes -->
|
|
47
|
+
<div class="rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
48
|
+
<div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
49
|
+
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Slowest Job Classes (avg duration)</h3>
|
|
50
|
+
</div>
|
|
51
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
52
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
53
|
+
<tr>
|
|
54
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Job Class</th>
|
|
55
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Count</th>
|
|
56
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Avg</th>
|
|
57
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400">Max</th>
|
|
58
|
+
</tr>
|
|
59
|
+
</thead>
|
|
60
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
61
|
+
<% @slowest.each do |row| %>
|
|
62
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
63
|
+
<td class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300"><%= row[:job_class] %></td>
|
|
64
|
+
<td class="px-4 py-3 text-sm text-right font-mono text-gray-700 dark:text-gray-300"><%= pgbus_number(row[:count]) %></td>
|
|
65
|
+
<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>
|
|
66
|
+
<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>
|
|
67
|
+
</tr>
|
|
68
|
+
<% end %>
|
|
69
|
+
<% if @slowest.empty? %>
|
|
70
|
+
<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-gray-400 dark:text-gray-500">No job stats yet</td></tr>
|
|
71
|
+
<% end %>
|
|
72
|
+
</tbody>
|
|
73
|
+
</table>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<script src="https://cdn.jsdelivr.net/npm/apexcharts@4"></script>
|
|
77
|
+
<script>
|
|
78
|
+
let throughputChart, statusChart;
|
|
79
|
+
|
|
80
|
+
function getThemeColors() {
|
|
81
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
82
|
+
return {
|
|
83
|
+
isDark,
|
|
84
|
+
text: isDark ? '#9ca3af' : '#6b7280',
|
|
85
|
+
grid: isDark ? '#374151' : '#e5e7eb',
|
|
86
|
+
tooltip: isDark ? 'dark' : 'light',
|
|
87
|
+
dataLabel: isDark ? '#fff' : '#000'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderCharts(data) {
|
|
92
|
+
const t = getThemeColors();
|
|
93
|
+
|
|
94
|
+
// Destroy existing charts before re-rendering
|
|
95
|
+
if (throughputChart) throughputChart.destroy();
|
|
96
|
+
if (statusChart) statusChart.destroy();
|
|
97
|
+
|
|
98
|
+
// Throughput chart
|
|
99
|
+
const throughputData = data.throughput.map(p => ({
|
|
100
|
+
x: new Date(p.time).getTime(), y: p.count
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
throughputChart = new ApexCharts(document.querySelector('#throughput-chart'), {
|
|
104
|
+
series: [{ name: 'Jobs/min', data: throughputData }],
|
|
105
|
+
chart: { type: 'area', height: 280, toolbar: { show: false }, background: 'transparent', foreColor: t.text },
|
|
106
|
+
stroke: { curve: 'smooth', width: 2 },
|
|
107
|
+
fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.4, opacityTo: 0.05, stops: [0, 100] } },
|
|
108
|
+
colors: ['#6366f1'],
|
|
109
|
+
xaxis: { type: 'datetime', labels: { style: { colors: t.text } } },
|
|
110
|
+
yaxis: { labels: { style: { colors: t.text } } },
|
|
111
|
+
grid: { borderColor: t.grid },
|
|
112
|
+
tooltip: { theme: t.tooltip },
|
|
113
|
+
dataLabels: { enabled: false }
|
|
114
|
+
});
|
|
115
|
+
throughputChart.render();
|
|
116
|
+
|
|
117
|
+
// Status distribution chart
|
|
118
|
+
const statusLabels = Object.keys(data.status_counts);
|
|
119
|
+
const statusValues = Object.values(data.status_counts);
|
|
120
|
+
const statusColors = statusLabels.map(s => {
|
|
121
|
+
if (s === 'success') return '#10b981';
|
|
122
|
+
if (s === 'failed') return '#ef4444';
|
|
123
|
+
if (s === 'dead_lettered') return '#f97316';
|
|
124
|
+
return '#6b7280';
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (statusLabels.length > 0) {
|
|
128
|
+
statusChart = new ApexCharts(document.querySelector('#status-chart'), {
|
|
129
|
+
series: statusValues, labels: statusLabels,
|
|
130
|
+
chart: { type: 'donut', height: 280, background: 'transparent', foreColor: t.text },
|
|
131
|
+
colors: statusColors,
|
|
132
|
+
legend: { position: 'bottom', labels: { colors: t.text } },
|
|
133
|
+
plotOptions: { pie: { donut: { size: '60%' } } },
|
|
134
|
+
dataLabels: { style: { colors: [t.dataLabel] } },
|
|
135
|
+
tooltip: { theme: t.tooltip }
|
|
136
|
+
});
|
|
137
|
+
statusChart.render();
|
|
138
|
+
} else {
|
|
139
|
+
document.querySelector('#status-chart').innerHTML =
|
|
140
|
+
'<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24">No data yet</p>';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Fetch data and render
|
|
145
|
+
let chartData = null;
|
|
146
|
+
fetch('<%= pgbus.api_insights_path %>')
|
|
147
|
+
.then(r => r.json())
|
|
148
|
+
.then(data => { chartData = data; renderCharts(data); })
|
|
149
|
+
.catch(() => {
|
|
150
|
+
const msg = '<p class="text-center text-sm text-gray-400 dark:text-gray-500 pt-24">Failed to load chart data</p>';
|
|
151
|
+
document.querySelector('#throughput-chart').innerHTML = msg;
|
|
152
|
+
document.querySelector('#status-chart').innerHTML = msg;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Re-render charts when dark mode toggles.
|
|
156
|
+
// Listen for class changes on <html> instead of wrapping the toggle function,
|
|
157
|
+
// so it works regardless of script loading order.
|
|
158
|
+
new MutationObserver(function() {
|
|
159
|
+
if (chartData) renderCharts(chartData);
|
|
160
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
161
|
+
</script>
|