pgbus 0.17.1 → 0.17.2
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/CHANGELOG.md +38 -0
- data/README.md +48 -13
- data/app/controllers/concerns/pgbus/job_listing.rb +37 -0
- data/app/controllers/concerns/pgbus/process_listing.rb +17 -0
- data/app/controllers/pgbus/application_controller.rb +5 -2
- data/app/controllers/pgbus/batches_controller.rb +4 -1
- data/app/controllers/pgbus/dashboard_controller.rb +20 -6
- data/app/controllers/pgbus/dead_letter_controller.rb +27 -3
- data/app/controllers/pgbus/events_controller.rb +52 -3
- data/app/controllers/pgbus/insights_controller.rb +19 -1
- data/app/controllers/pgbus/jobs_controller.rb +45 -26
- data/app/controllers/pgbus/locks_controller.rb +7 -2
- data/app/controllers/pgbus/outbox_controller.rb +4 -1
- data/app/controllers/pgbus/processes_controller.rb +3 -1
- data/app/controllers/pgbus/queues_controller.rb +16 -4
- data/app/controllers/pgbus/recurring_tasks_controller.rb +5 -7
- data/app/frontend/pgbus/modules/charts.js +119 -77
- data/app/frontend/pgbus/style.css +1 -1
- data/app/frontend/pgbus/tailwind.css +18 -0
- data/app/helpers/pgbus/application_helper.rb +155 -113
- data/app/helpers/pgbus/button_helper.rb +88 -0
- data/app/helpers/pgbus/dashboard_helper.rb +157 -0
- data/app/helpers/pgbus/events_helper.rb +40 -0
- data/app/helpers/pgbus/insights_helper.rb +39 -0
- data/app/helpers/pgbus/processes_helper.rb +79 -0
- data/app/views/pgbus/batches/_batches_table.html.erb +16 -13
- data/app/views/pgbus/batches/show.html.erb +3 -4
- data/app/views/pgbus/dashboard/_processes_table.html.erb +11 -11
- data/app/views/pgbus/dashboard/_queue_health.html.erb +42 -51
- data/app/views/pgbus/dashboard/_queues_table.html.erb +18 -16
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +16 -12
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +70 -36
- data/app/views/pgbus/dashboard/show.html.erb +1 -1
- data/app/views/pgbus/dead_letter/_error.html.erb +13 -0
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +37 -96
- data/app/views/pgbus/dead_letter/_row.html.erb +75 -0
- data/app/views/pgbus/dead_letter/index.html.erb +6 -9
- data/app/views/pgbus/dead_letter/show.html.erb +37 -21
- data/app/views/pgbus/events/_list.html.erb +55 -0
- data/app/views/pgbus/events/_processed_table.html.erb +45 -0
- data/app/views/pgbus/events/_replay.html.erb +10 -0
- data/app/views/pgbus/events/_row.html.erb +162 -0
- data/app/views/pgbus/events/_tabs.html.erb +13 -0
- data/app/views/pgbus/events/index.html.erb +27 -48
- data/app/views/pgbus/events/show.html.erb +25 -7
- data/app/views/pgbus/insights/_chart_card.html.erb +21 -0
- data/app/views/pgbus/insights/_latency_table.html.erb +20 -0
- data/app/views/pgbus/insights/_status_table.html.erb +19 -0
- data/app/views/pgbus/insights/_throughput_table.html.erb +18 -0
- data/app/views/pgbus/insights/show.html.erb +83 -100
- data/app/views/pgbus/jobs/_list.html.erb +64 -0
- data/app/views/pgbus/jobs/_row.html.erb +107 -0
- data/app/views/pgbus/jobs/_tabs.html.erb +14 -0
- data/app/views/pgbus/jobs/index.html.erb +10 -10
- data/app/views/pgbus/jobs/show.html.erb +18 -20
- data/app/views/pgbus/locks/_concurrency.html.erb +25 -23
- data/app/views/pgbus/locks/_uniqueness.html.erb +20 -22
- data/app/views/pgbus/outbox/index.html.erb +30 -27
- data/app/views/pgbus/processes/_processes_table.html.erb +6 -25
- data/app/views/pgbus/processes/_row.html.erb +48 -0
- data/app/views/pgbus/processes/_status.html.erb +4 -0
- data/app/views/pgbus/queues/_queues_list.html.erb +28 -32
- data/app/views/pgbus/queues/show.html.erb +69 -118
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +75 -76
- data/app/views/pgbus/recurring_tasks/index.html.erb +1 -1
- data/app/views/pgbus/recurring_tasks/show.html.erb +45 -51
- data/app/views/pgbus/shared/_empty_row.html.erb +2 -0
- data/app/views/pgbus/shared/_pager.html.erb +34 -0
- data/config/i18n-tasks.yml +11 -2
- data/config/locales/da.yml +405 -113
- data/config/locales/de.yml +405 -113
- data/config/locales/en.yml +405 -113
- data/config/locales/es.yml +404 -112
- data/config/locales/fi.yml +404 -112
- data/config/locales/fr.yml +406 -114
- data/config/locales/it.yml +405 -113
- data/config/locales/ja.yml +405 -113
- data/config/locales/nb.yml +405 -113
- data/config/locales/nl.yml +405 -113
- data/config/locales/pt.yml +406 -114
- data/config/locales/sv.yml +404 -112
- data/lib/generators/pgbus/templates/initializer.rb.erb +4 -0
- data/lib/pgbus/active_job/executor.rb +16 -6
- data/lib/pgbus/cli/dlq.rb +32 -5
- data/lib/pgbus/client.rb +4 -2
- data/lib/pgbus/configuration.rb +47 -2
- data/lib/pgbus/dead_letter_header.rb +129 -0
- data/lib/pgbus/doctor.rb +8 -4
- data/lib/pgbus/event_bus/handler.rb +18 -11
- data/lib/pgbus/event_bus/registry.rb +6 -0
- data/lib/pgbus/failed_event_recorder.rb +34 -0
- data/lib/pgbus/instrumentation.rb +2 -1
- data/lib/pgbus/mcp/base_tool.rb +25 -0
- data/lib/pgbus/mcp/tools/concurrency_tool.rb +23 -4
- data/lib/pgbus/mcp/tools/dlq_detail_tool.rb +4 -2
- data/lib/pgbus/mcp/tools/dlq_tool.rb +10 -4
- data/lib/pgbus/mcp/tools/locks_tool.rb +23 -5
- data/lib/pgbus/mcp/tools/recurring_tool.rb +33 -4
- data/lib/pgbus/outbox/poller.rb +1 -1
- data/lib/pgbus/process/consumer.rb +53 -3
- data/lib/pgbus/process/dispatcher.rb +1 -1
- data/lib/pgbus/process/supervisor.rb +32 -11
- data/lib/pgbus/process/worker.rb +45 -8
- data/lib/pgbus/recurring/scheduler.rb +2 -1
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source/dashboard_signals.rb +170 -0
- data/lib/pgbus/web/data_source/dead_letter.rb +142 -0
- data/lib/pgbus/web/data_source/event_list.rb +161 -0
- data/lib/pgbus/web/data_source/job_list.rb +323 -0
- data/lib/pgbus/web/data_source/list_counts.rb +80 -0
- data/lib/pgbus/web/data_source/process_list.rb +69 -0
- data/lib/pgbus/web/data_source/queue_summary.rb +122 -0
- data/lib/pgbus/web/data_source.rb +132 -254
- data/lib/pgbus/web/dead_letter_reason.rb +81 -0
- data/lib/pgbus/web/event_state.rb +71 -0
- data/lib/pgbus/web/health_app.rb +7 -3
- data/lib/pgbus/web/health_verdict.rb +78 -0
- data/lib/pgbus/web/insights_charts.rb +84 -0
- data/lib/pgbus/web/job_state.rb +111 -0
- data/lib/pgbus/web/process_activity.rb +97 -0
- data/lib/pgbus/web/process_state.rb +187 -0
- data/lib/pgbus/web/queue_summary.rb +104 -0
- data/lib/pgbus/web/time_format.rb +128 -0
- metadata +43 -4
- data/app/views/pgbus/events/_pending_table.html.erb +0 -169
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +0 -118
- data/app/views/pgbus/jobs/_failed_table.html.erb +0 -76
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
# The Processes page's words (issue #493): ProcessState results as
|
|
5
|
+
# sentences. Moments go through pgbus_time, seconds through pgbus_duration,
|
|
6
|
+
# counts through number_with_delimiter.
|
|
7
|
+
module ProcessesHelper
|
|
8
|
+
PART_SEPARATOR = " · "
|
|
9
|
+
|
|
10
|
+
# Metadata keys the row always renders (status, activity, recycle limits,
|
|
11
|
+
# Process cell, uptime), left out of the expansion's raw chips. Counters
|
|
12
|
+
# and RSS stay: Recycle shows them only when a limit is configured.
|
|
13
|
+
RENDERED_METADATA_KEYS = %w[
|
|
14
|
+
rates in_flight loop_tick_at buffered capacity busy started_at supervisor_pid max_jobs max_memory_mb
|
|
15
|
+
max_lifetime queues topics tasks threads capsule process pid hostname
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
SECONDS_ARGS = %i[limit within interval age].freeze
|
|
19
|
+
NUMBER_ARGS = %i[number busy capacity].freeze
|
|
20
|
+
|
|
21
|
+
# "Loop last ticked 2m ago (limit 1m 30s) — the supervisor's watchdog
|
|
22
|
+
# restarts it". The _html key lets the <time> through.
|
|
23
|
+
def pgbus_process_status(result)
|
|
24
|
+
reason = translate("pgbus.processes.state.reasons.#{result.reason_key}_html", **pgbus_process_args(result.reason_args))
|
|
25
|
+
return reason unless result.next_key
|
|
26
|
+
|
|
27
|
+
safe_join([reason, " — ", t("pgbus.processes.state.next.#{result.next_key}", **pgbus_process_args(result.next_args))])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def pgbus_process_activity(result)
|
|
31
|
+
parts = result.activity.map do |key, args|
|
|
32
|
+
next t("pgbus.processes.state.activity.supervises", children: pgbus_process_children(args[:children])) if key == "supervises"
|
|
33
|
+
|
|
34
|
+
t("pgbus.processes.state.activity.#{key}", **pgbus_process_args(args))
|
|
35
|
+
end
|
|
36
|
+
pgbus_process_join(parts)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def pgbus_process_recycle(result)
|
|
40
|
+
parts = Array(result.recycle).map do |key, args|
|
|
41
|
+
values = if key == "lifetime"
|
|
42
|
+
{ uptime: pgbus_duration(args[:uptime]), limit: pgbus_duration(args[:limit]) }
|
|
43
|
+
else
|
|
44
|
+
args.transform_values { |v| v.nil? ? Web::TimeFormat::NONE : number_with_delimiter(v) }
|
|
45
|
+
end
|
|
46
|
+
t("pgbus.processes.state.recycle.#{key}", **values)
|
|
47
|
+
end
|
|
48
|
+
pgbus_process_join(parts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def pgbus_process_raw_metadata(metadata)
|
|
52
|
+
return {} unless metadata.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
metadata.reject { |k, _| RENDERED_METADATA_KEYS.include?(k.to_s) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def pgbus_process_args(args)
|
|
60
|
+
args.to_h do |key, value|
|
|
61
|
+
case key
|
|
62
|
+
when :ago then [key, pgbus_time(value)]
|
|
63
|
+
when *SECONDS_ARGS then [key, pgbus_duration(value)]
|
|
64
|
+
when *NUMBER_ARGS then [key, number_with_delimiter(value)]
|
|
65
|
+
else [key, value]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def pgbus_process_children(children)
|
|
71
|
+
children.map { |kind, count| t("pgbus.processes.state.kinds.#{kind}", count: count, default: "#{count} #{kind}") }
|
|
72
|
+
.join(", ")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pgbus_process_join(parts)
|
|
76
|
+
parts.empty? ? Web::TimeFormat::NONE : safe_join(parts, PART_SEPARATOR)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<turbo-frame id="batches-list" data-auto-refresh data-
|
|
1
|
+
<turbo-frame id="batches-list" data-auto-refresh data-turbo-action="advance"
|
|
2
|
+
data-src="<%= pgbus.batches_path(request.query_parameters.merge(frame: "list")) %>">
|
|
2
3
|
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
3
4
|
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
4
5
|
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
@@ -14,18 +15,17 @@
|
|
|
14
15
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
15
16
|
<% @batches.each do |batch| %>
|
|
16
17
|
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
17
|
-
<td data-label="
|
|
18
|
-
<%=
|
|
19
|
-
class: "font-mono
|
|
20
|
-
data: { turbo_frame: "_top" } %>
|
|
18
|
+
<td data-label="<%= t("pgbus.batches.index.headers.batch_id") %>" class="px-4 py-3 text-sm">
|
|
19
|
+
<%= pgbus_link_to batch[:batch_id][0..7], pgbus.batch_path(batch[:batch_id]),
|
|
20
|
+
class: "font-mono", data: { turbo_frame: "_top" } %>
|
|
21
21
|
</td>
|
|
22
|
-
<td data-label="
|
|
22
|
+
<td data-label="<%= t("pgbus.batches.index.headers.description") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-300 max-w-xs truncate">
|
|
23
23
|
<%= batch[:description] || "—" %>
|
|
24
24
|
</td>
|
|
25
|
-
<td data-label="
|
|
25
|
+
<td data-label="<%= t("pgbus.batches.index.headers.status") %>" class="px-4 py-3 text-sm">
|
|
26
26
|
<%= pgbus_batch_status_badge(batch[:status]) %>
|
|
27
27
|
</td>
|
|
28
|
-
<td data-label="
|
|
28
|
+
<td data-label="<%= t("pgbus.batches.index.headers.progress") %>" class="px-4 py-3 text-sm">
|
|
29
29
|
<div class="flex items-center space-x-2">
|
|
30
30
|
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
|
31
31
|
<div class="h-2 rounded-full <%= batch[:failed_jobs].to_i > 0 ? 'bg-amber-500' : 'bg-green-500' %>"
|
|
@@ -34,21 +34,24 @@
|
|
|
34
34
|
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono"><%= batch[:progress_pct] %>%</span>
|
|
35
35
|
</div>
|
|
36
36
|
</td>
|
|
37
|
-
<td data-label="
|
|
37
|
+
<td data-label="<%= t("pgbus.batches.index.headers.jobs") %>" class="px-4 py-3 text-sm text-right font-mono text-gray-500 dark:text-gray-400">
|
|
38
38
|
<%= batch[:completed_jobs] %>/<%= batch[:total_jobs] %>
|
|
39
39
|
<% if batch[:failed_jobs].to_i > 0 %>
|
|
40
|
-
<span class="text-red-
|
|
40
|
+
<span class="text-red-700 dark:text-red-400">(<%= batch[:failed_jobs] %> failed)</span>
|
|
41
41
|
<% end %>
|
|
42
42
|
</td>
|
|
43
|
-
<td data-label="
|
|
44
|
-
<%=
|
|
43
|
+
<td data-label="<%= t("pgbus.batches.index.headers.created") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
|
|
44
|
+
<%= pgbus_time(batch[:created_at]) %>
|
|
45
45
|
</td>
|
|
46
46
|
</tr>
|
|
47
47
|
<% end %>
|
|
48
48
|
<% if @batches.empty? %>
|
|
49
|
-
|
|
49
|
+
<%= render "pgbus/shared/empty_row", colspan: 6, message: t("pgbus.batches.index.empty") %>
|
|
50
50
|
<% end %>
|
|
51
51
|
</tbody>
|
|
52
52
|
</table>
|
|
53
|
+
<%= render "pgbus/shared/pager", page: @page, per_page: @per_page, shown: @batches.size,
|
|
54
|
+
total_count: @batches_count.total, has_more: @batches_count.capped? && @batches.size == @per_page,
|
|
55
|
+
page_path: ->(n) { pgbus.batches_path(page: n) } %>
|
|
53
56
|
</div>
|
|
54
57
|
</turbo-frame>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<div class="mb-6">
|
|
2
2
|
<div class="flex items-center space-x-2 mb-2">
|
|
3
|
-
<%=
|
|
4
|
-
class: "text-sm text-indigo-600 dark:text-indigo-400 hover:underline" %>
|
|
3
|
+
<%= pgbus_link_to t("pgbus.batches.show.back"), pgbus.batches_path %>
|
|
5
4
|
</div>
|
|
6
5
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
7
6
|
<%= t("pgbus.batches.show.title") %>
|
|
@@ -27,12 +26,12 @@
|
|
|
27
26
|
</div>
|
|
28
27
|
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4">
|
|
29
28
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.batches.show.created_at") %></dt>
|
|
30
|
-
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= @batch[:created_at] %></dd>
|
|
29
|
+
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= pgbus_timestamp(@batch[:created_at]) %></dd>
|
|
31
30
|
</div>
|
|
32
31
|
<% if @batch[:finished_at] %>
|
|
33
32
|
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4">
|
|
34
33
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.batches.show.finished_at") %></dt>
|
|
35
|
-
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= @batch[:finished_at] %></dd>
|
|
34
|
+
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= pgbus_timestamp(@batch[:finished_at]) %></dd>
|
|
36
35
|
</div>
|
|
37
36
|
<% end %>
|
|
38
37
|
<% if @batch[:on_finish_class] %>
|
|
@@ -5,23 +5,23 @@
|
|
|
5
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
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.processes_table.headers.kind") %></th>
|
|
9
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.processes_table.headers.host") %></th>
|
|
10
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.processes_table.headers.pid") %></th>
|
|
11
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.processes_table.headers.status") %></th>
|
|
8
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.processes_table.headers.kind") %></th>
|
|
9
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.processes_table.headers.host") %></th>
|
|
10
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.processes_table.headers.pid") %></th>
|
|
11
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.processes_table.headers.status") %></th>
|
|
12
12
|
</tr>
|
|
13
13
|
</thead>
|
|
14
14
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
15
|
-
<% @processes.each do |
|
|
16
|
-
<tr>
|
|
17
|
-
<td data-label="
|
|
18
|
-
<td data-label="
|
|
19
|
-
<td data-label="
|
|
20
|
-
<td data-label="
|
|
15
|
+
<% @processes.each do |process| %>
|
|
16
|
+
<tr id="dashboard-process-<%= process[:id] %>">
|
|
17
|
+
<td data-label="<%= t("pgbus.dashboard.processes_table.headers.kind") %>" class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white"><%= process[:kind] %></td>
|
|
18
|
+
<td data-label="<%= t("pgbus.dashboard.processes_table.headers.host") %>" class="px-4 py-3 text-sm lg:whitespace-nowrap text-gray-500 dark:text-gray-400"><%= process[:hostname] %></td>
|
|
19
|
+
<td data-label="<%= t("pgbus.dashboard.processes_table.headers.pid") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400"><%= process[:pid] %></td>
|
|
20
|
+
<td data-label="<%= t("pgbus.dashboard.processes_table.headers.status") %>" class="px-4 py-3 text-sm"><%= render "pgbus/processes/status", state: @process_states.fetch(process[:id]) %></td>
|
|
21
21
|
</tr>
|
|
22
22
|
<% end %>
|
|
23
23
|
<% if @processes.empty? %>
|
|
24
|
-
|
|
24
|
+
<%= render "pgbus/shared/empty_row", colspan: 4, message: t("pgbus.dashboard.processes_table.empty") %>
|
|
25
25
|
<% end %>
|
|
26
26
|
</tbody>
|
|
27
27
|
</table>
|
|
@@ -1,78 +1,69 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-health" data-auto-refresh data-src="<%= pgbus.root_path(frame: 'health') %>">
|
|
2
|
+
<% verdict = Pgbus::Web::HealthVerdict.present(@health) %>
|
|
2
3
|
<div class="mb-8">
|
|
3
4
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.dashboard.queue_health.title") %></h2>
|
|
4
5
|
|
|
5
6
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-4">
|
|
6
7
|
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
7
8
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.dead_tuples") %></p>
|
|
8
|
-
<p class="
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<p class="text-xs text-gray-400 dark:text-gray-500"><%= pgbus_number(@health[:total_live_tuples]) %> <%= t("pgbus.dashboard.queue_health.live") %></p>
|
|
9
|
+
<p class="<%= pgbus_health_number_css(verdict.dead_tuples) %>"><%= pgbus_number(@health[:total_dead_tuples]) %></p>
|
|
10
|
+
<p class="text-xs text-gray-500 dark:text-gray-400"><%= pgbus_number(@health[:total_live_tuples]) %> <%= t("pgbus.dashboard.queue_health.live") %></p>
|
|
11
|
+
<%= pgbus_health_words(verdict.dead_tuples) %>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
15
15
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.worst_bloat") %></p>
|
|
16
|
-
<p class="
|
|
17
|
-
<%= (@health[:worst_bloat_ratio] * 100).round(1) %>%
|
|
18
|
-
</p>
|
|
16
|
+
<p class="<%= pgbus_health_number_css(verdict.bloat) %>"><%= pgbus_percent(@health[:worst_bloat_ratio]) %></p>
|
|
19
17
|
<% if @health[:tables_needing_vacuum] > 0 %>
|
|
20
|
-
<p class="text-xs text-
|
|
18
|
+
<p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.tables_need_vacuum", count: @health[:tables_needing_vacuum]) %></p>
|
|
21
19
|
<% end %>
|
|
20
|
+
<%= pgbus_health_words(verdict.bloat) %>
|
|
22
21
|
</div>
|
|
23
22
|
|
|
24
23
|
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
25
24
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.last_vacuum") %></p>
|
|
26
|
-
<p class="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<% else %>
|
|
30
|
-
—
|
|
31
|
-
<% end %>
|
|
32
|
-
</p>
|
|
33
|
-
<p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.dashboard.queue_health.oldest_vacuum_ago") %></p>
|
|
25
|
+
<p class="<%= pgbus_health_number_css(verdict.vacuum) %>"><%= pgbus_duration(@health[:oldest_vacuum_ago_sec]) %></p>
|
|
26
|
+
<p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.oldest_vacuum_ago") %></p>
|
|
27
|
+
<%= pgbus_health_words(verdict.vacuum) %>
|
|
34
28
|
</div>
|
|
35
29
|
|
|
36
30
|
<div class="rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
37
31
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.mvcc_horizon") %></p>
|
|
38
|
-
<p class="
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<% else %>
|
|
42
|
-
—
|
|
43
|
-
<% end %>
|
|
44
|
-
</p>
|
|
45
|
-
<p class="text-xs text-gray-400 dark:text-gray-500"><%= t("pgbus.dashboard.queue_health.oldest_open_txn") %></p>
|
|
32
|
+
<p class="<%= pgbus_health_number_css(verdict.transaction) %>"><%= pgbus_duration(@health[:oldest_transaction_age_sec]) %></p>
|
|
33
|
+
<p class="text-xs text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.oldest_open_txn") %></p>
|
|
34
|
+
<%= pgbus_health_words(verdict.transaction) %>
|
|
46
35
|
</div>
|
|
47
36
|
</div>
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
39
|
+
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
40
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
41
|
+
<tr>
|
|
42
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.table") %></th>
|
|
43
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.kind") %></th>
|
|
44
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.live") %></th>
|
|
45
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.dead") %></th>
|
|
46
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.bloat") %></th>
|
|
47
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.headers.last_vacuum") %></th>
|
|
48
|
+
</tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
51
|
+
<% @health[:tables].each do |table| %>
|
|
52
|
+
<% bloat = Pgbus::Web::HealthVerdict.level(table[:bloat_ratio].to_f, Pgbus::Web::HealthVerdict::BLOAT_WARN, Pgbus::Web::HealthVerdict::BLOAT_CRITICAL) %>
|
|
53
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
54
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.table") %>" class="px-4 py-3 text-sm font-mono text-gray-900 dark:text-white"><%= table[:table] %></td>
|
|
55
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.kind") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queue_health.kinds.#{table[:kind]}") %></td>
|
|
56
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.live") %>" class="px-4 py-3 text-sm text-right text-gray-700 dark:text-gray-300"><%= pgbus_number(table[:live_tuples]) %></td>
|
|
57
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.dead") %>" class="px-4 py-3 text-sm text-right text-gray-700 dark:text-gray-300"><%= pgbus_number(table[:dead_tuples]) %></td>
|
|
58
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.bloat") %>" class="px-4 py-3 text-sm text-right <%= bloat == :ok ? 'text-gray-700 dark:text-gray-300' : bloat == :warn ? 'text-amber-700 dark:text-amber-300 font-semibold' : 'text-red-700 dark:text-red-300 font-semibold' %>"><%= pgbus_percent(table[:bloat_ratio]) %></td>
|
|
59
|
+
<td data-label="<%= t("pgbus.dashboard.queue_health.headers.last_vacuum") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(table[:last_vacuum_ago_sec]) %></td>
|
|
60
60
|
</tr>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<td class="px-4 py-3 text-sm text-right <%= t[:dead_tuples] > 1000 ? 'text-amber-600 dark:text-amber-400 font-semibold' : 'text-gray-700 dark:text-gray-300' %>"><%= pgbus_number(t[:dead_tuples]) %></td>
|
|
69
|
-
<td class="px-4 py-3 text-sm text-right <%= t[:bloat_ratio] > 0.2 ? 'text-red-600 dark:text-red-400 font-semibold' : t[:bloat_ratio] > 0.1 ? 'text-amber-600 dark:text-amber-400' : 'text-gray-500 dark:text-gray-400' %>"><%= (t[:bloat_ratio] * 100).round(1) %>%</td>
|
|
70
|
-
<td class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= t[:last_vacuum_ago_sec] ? pgbus_duration(t[:last_vacuum_ago_sec]) : "—" %></td>
|
|
71
|
-
</tr>
|
|
72
|
-
<% end %>
|
|
73
|
-
</tbody>
|
|
74
|
-
</table>
|
|
75
|
-
</div>
|
|
76
|
-
<% end %>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% if @health[:tables].empty? %>
|
|
63
|
+
<%= render "pgbus/shared/empty_row", colspan: 6, message: t("pgbus.dashboard.queue_health.empty") %>
|
|
64
|
+
<% end %>
|
|
65
|
+
</tbody>
|
|
66
|
+
</table>
|
|
67
|
+
</div>
|
|
77
68
|
</div>
|
|
78
69
|
</turbo-frame>
|
|
@@ -2,37 +2,39 @@
|
|
|
2
2
|
<div class="mb-8">
|
|
3
3
|
<div class="flex items-center justify-between mb-3">
|
|
4
4
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white"><%= t("pgbus.dashboard.queues_table.title") %></h2>
|
|
5
|
-
<%=
|
|
5
|
+
<%= pgbus_link_to t("pgbus.dashboard.queues_table.view_all"), pgbus.queues_path, data: { turbo_frame: "_top" } %>
|
|
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
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
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.queue") %></th>
|
|
13
|
-
<th class="px-4 py-3 text-
|
|
14
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.
|
|
15
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.
|
|
16
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.
|
|
17
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.
|
|
12
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.queue") %></th>
|
|
13
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.status") %></th>
|
|
14
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.depth") %></th>
|
|
15
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.visible") %></th>
|
|
16
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.parked") %></th>
|
|
17
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.oldest_claimable") %></th>
|
|
18
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.queues_table.headers.total") %></th>
|
|
18
19
|
</tr>
|
|
19
20
|
</thead>
|
|
20
21
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
21
22
|
<% @queues.each do |q| %>
|
|
22
|
-
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50
|
|
23
|
-
<td data-label="
|
|
24
|
-
<%=
|
|
23
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
24
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.queue") %>" class="px-4 py-3 text-sm">
|
|
25
|
+
<%= pgbus_link_to q[:name], pgbus.queue_path(name: q[:name]), data: { turbo_frame: "_top" } %>
|
|
25
26
|
<%= pgbus_queue_badge(q[:name]) %>
|
|
26
27
|
</td>
|
|
27
|
-
<td data-label="
|
|
28
|
-
<td data-label="
|
|
29
|
-
<td data-label="
|
|
30
|
-
<td data-label="
|
|
31
|
-
<td data-label="
|
|
28
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.status") %>" class="px-4 py-3 text-sm"><%= pgbus_queue_status(@queue_lines[q[:name]]) %></td>
|
|
29
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.depth") %>" class="px-4 py-3 text-sm text-right text-gray-700 dark:text-gray-200"><%= pgbus_number(q[:queue_length]) %></td>
|
|
30
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.visible") %>" class="px-4 py-3 text-sm text-right text-gray-700 dark:text-gray-200"><%= pgbus_number(q[:queue_visible_length]) %></td>
|
|
31
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.parked") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_number(q[:parked_length]) %></td>
|
|
32
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.oldest_claimable") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_duration(q[:oldest_claimable_age_sec]) %></td>
|
|
33
|
+
<td data-label="<%= t("pgbus.dashboard.queues_table.headers.total") %>" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400"><%= pgbus_number(q[:total_messages]) %></td>
|
|
32
34
|
</tr>
|
|
33
35
|
<% end %>
|
|
34
36
|
<% if @queues.empty? %>
|
|
35
|
-
|
|
37
|
+
<%= render "pgbus/shared/empty_row", colspan: 7, message: t("pgbus.dashboard.queues_table.empty") %>
|
|
36
38
|
<% end %>
|
|
37
39
|
</tbody>
|
|
38
40
|
</table>
|
|
@@ -2,29 +2,33 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<div class="flex items-center justify-between mb-3">
|
|
4
4
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white"><%= t("pgbus.dashboard.recent_failures.title") %></h2>
|
|
5
|
-
|
|
6
|
-
<%= link_to t("pgbus.dashboard.recent_failures.view_all"), pgbus.jobs_path(status: "failed"), class: "text-sm text-indigo-600 hover:text-indigo-500", data: { turbo_frame: "_top" } %>
|
|
7
|
-
<% end %>
|
|
5
|
+
<%= pgbus_link_to t("pgbus.dashboard.recent_failures.view_all"), pgbus.jobs_path(state: "retrying"), data: { turbo_frame: "_top" } %>
|
|
8
6
|
</div>
|
|
9
|
-
<div class="overflow-
|
|
7
|
+
<div class="overflow-x-auto rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
10
8
|
<table class="pgbus-table min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
11
9
|
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
12
10
|
<tr>
|
|
13
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.
|
|
14
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.
|
|
15
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.
|
|
11
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.recent_failures.headers.job") %></th>
|
|
12
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.recent_failures.headers.queue") %></th>
|
|
13
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.recent_failures.headers.error") %></th>
|
|
14
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.recent_failures.headers.attempt") %></th>
|
|
15
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.recent_failures.headers.when") %></th>
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
18
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
19
19
|
<% @recent_failures.each do |f| %>
|
|
20
|
-
<tr>
|
|
21
|
-
<td data-label="
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
21
|
+
<td data-label="<%= t("pgbus.dashboard.recent_failures.headers.job") %>" class="px-4 py-3 text-sm">
|
|
22
|
+
<%= pgbus_link_to pgbus_failure_job(f), pgbus.job_path(f["id"]), data: { turbo_frame: "_top" } %>
|
|
23
|
+
</td>
|
|
24
|
+
<td data-label="<%= t("pgbus.dashboard.recent_failures.headers.queue") %>" class="px-4 py-3 text-sm text-gray-700 dark:text-gray-200"><%= f["queue_name"] %></td>
|
|
25
|
+
<td data-label="<%= t("pgbus.dashboard.recent_failures.headers.error") %>" class="px-4 py-3 text-sm text-red-700 dark:text-red-300" title="<%= f["error_class"] %>: <%= f["error_message"] %>"><span class="block max-w-md truncate"><%= f["error_class"] %>: <%= truncate(f["error_message"].to_s, length: 60) %></span></td>
|
|
26
|
+
<td data-label="<%= t("pgbus.dashboard.recent_failures.headers.attempt") %>" class="px-4 py-3 text-sm text-right text-gray-700 dark:text-gray-200"><%= pgbus_failure_attempt(f) %></td>
|
|
27
|
+
<td data-label="<%= t("pgbus.dashboard.recent_failures.headers.when") %>" class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400"><%= pgbus_time(f["failed_at"]) %></td>
|
|
24
28
|
</tr>
|
|
25
29
|
<% end %>
|
|
26
30
|
<% if @recent_failures.empty? %>
|
|
27
|
-
|
|
31
|
+
<%= render "pgbus/shared/empty_row", colspan: 5, message: t("pgbus.dashboard.recent_failures.empty") %>
|
|
28
32
|
<% end %>
|
|
29
33
|
</tbody>
|
|
30
34
|
</table>
|
|
@@ -1,54 +1,88 @@
|
|
|
1
1
|
<turbo-frame id="dashboard-stats" data-auto-refresh data-src="<%= pgbus.root_path(frame: 'stats') %>">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<%# The verdict /readyz serves (HealthAnalyzer). Its reasons are operator
|
|
3
|
+
diagnostics in English and show verbatim, like an error message. %>
|
|
4
|
+
<% verdict = @signals[:verdict] %>
|
|
5
|
+
<section data-testid="health-verdict" aria-labelledby="health-verdict-title" class="mb-6 rounded-lg p-4 ring-1 <%= pgbus_verdict_css(verdict) %>">
|
|
6
|
+
<h2 id="health-verdict-title" class="text-sm font-semibold">
|
|
7
|
+
<% if verdict %>
|
|
8
|
+
<%= t("pgbus.dashboard.verdict.title", status: t("pgbus.dashboard.verdict.statuses.#{verdict[:status].to_s.downcase}")) %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<%= t("pgbus.dashboard.verdict.unavailable") %>
|
|
11
|
+
<% end %>
|
|
12
|
+
</h2>
|
|
13
|
+
<% if verdict %>
|
|
14
|
+
<p class="mt-1 text-sm"><%= t("pgbus.dashboard.verdict.meaning.#{verdict[:status].to_s.downcase}") %></p>
|
|
15
|
+
<% if verdict[:reasons].any? %>
|
|
16
|
+
<ul class="mt-2 list-disc pl-5 text-sm space-y-1">
|
|
17
|
+
<% verdict[:reasons].each do |reason| %>
|
|
18
|
+
<li><%= reason %></li>
|
|
19
|
+
<% end %>
|
|
20
|
+
</ul>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% links = pgbus_verdict_links(@signals) %>
|
|
23
|
+
<% if links.any? && verdict[:status] != "OK" %>
|
|
24
|
+
<p class="mt-2 text-sm"><%= t("pgbus.dashboard.verdict.look_at") %> <%= safe_join(links, " · ") %></p>
|
|
25
|
+
<% end %>
|
|
26
|
+
<% end %>
|
|
27
|
+
</section>
|
|
28
|
+
|
|
29
|
+
<%# Every card links to the page that explains its number. turbo_frame:
|
|
30
|
+
"_top" because the cards sit inside the dashboard-stats frame, and the
|
|
31
|
+
target pages have no frame of that id to render into. %>
|
|
32
|
+
<% card = Pgbus::DashboardHelper::CARD_CSS %>
|
|
33
|
+
<% number = Pgbus::DashboardHelper::CARD_NUMBER_CSS %>
|
|
34
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-8">
|
|
35
|
+
<%= link_to pgbus.queues_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
4
36
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.queues") %></p>
|
|
5
|
-
<p class="
|
|
6
|
-
|
|
37
|
+
<p class="<%= number %>"><%= number_with_delimiter(@signals[:queues][:total]) %></p>
|
|
38
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= pgbus_queue_card_words(@signals[:queues]) %></p>
|
|
39
|
+
<% end %>
|
|
7
40
|
|
|
8
|
-
|
|
41
|
+
<%= link_to pgbus.jobs_path(state: "ready"), data: { turbo_frame: "_top" }, class: card do %>
|
|
9
42
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.enqueued") %></p>
|
|
10
|
-
<p class="
|
|
11
|
-
<p class="text-xs text-gray-
|
|
12
|
-
|
|
43
|
+
<p class="<%= number %>"><%= pgbus_number(@signals[:queues][:depth]) %></p>
|
|
44
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= pgbus_enqueued_card_words(@signals[:queues]) %></p>
|
|
45
|
+
<% end %>
|
|
13
46
|
|
|
14
|
-
|
|
47
|
+
<%= link_to pgbus.processes_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
48
|
+
<% processes = @signals[:processes] %>
|
|
15
49
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.processes") %></p>
|
|
16
|
-
<p class="mt-1 text-3xl font-semibold <%=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</div>
|
|
50
|
+
<p class="mt-1 text-3xl font-semibold <%= (processes[:stalled] + processes[:stale]).positive? ? 'text-amber-600 dark:text-amber-400' : processes[:healthy].positive? ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white' %>"><%= number_with_delimiter(processes[:total]) %></p>
|
|
51
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= pgbus_process_card_words(processes) %></p>
|
|
52
|
+
<% end %>
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
<%= link_to pgbus.recurring_tasks_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
22
55
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.recurring") %></p>
|
|
23
|
-
<p class="
|
|
24
|
-
<p class="text-xs text-gray-
|
|
25
|
-
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
56
|
+
<p class="<%= number %>"><%= number_with_delimiter(@stats[:recurring_count]) %></p>
|
|
57
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= t("pgbus.dashboard.stats_cards.recurring_detail") %></p>
|
|
58
|
+
<% end %>
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<p class="
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</div>
|
|
60
|
+
<%= link_to pgbus.jobs_path(state: "retrying"), data: { turbo_frame: "_top" }, class: card do %>
|
|
61
|
+
<% retrying = @signals[:retrying] %>
|
|
62
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.retrying") %></p>
|
|
63
|
+
<p class="mt-1 text-3xl font-semibold <%= retrying[:count].positive? ? 'text-amber-600 dark:text-amber-400' : 'text-gray-900 dark:text-white' %>"><%= pgbus_retrying_count(retrying) %></p>
|
|
64
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= pgbus_retrying_card_words(retrying) %></p>
|
|
65
|
+
<% end %>
|
|
36
66
|
|
|
37
|
-
|
|
67
|
+
<%= link_to pgbus.dead_letter_index_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
68
|
+
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.dead_lettered") %></p>
|
|
69
|
+
<p class="mt-1 text-3xl font-semibold <%= @signals[:dlq][:depth].positive? ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white' %>"><%= number_with_delimiter(@signals[:dlq][:depth]) %></p>
|
|
70
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= pgbus_dlq_card_words(@signals[:dlq]) %></p>
|
|
71
|
+
<% end %>
|
|
72
|
+
|
|
73
|
+
<%= link_to pgbus.insights_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
74
|
+
<% throughput = @signals[:throughput] %>
|
|
38
75
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.throughput") %></p>
|
|
39
|
-
<p class="
|
|
40
|
-
<p class="text-xs text-gray-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
there. turbo_frame: "_top" because this card sits inside the
|
|
45
|
-
dashboard-stats frame, and /locks has no frame of that id to render into. %>
|
|
46
|
-
<%= link_to pgbus.locks_path, data: { turbo_frame: "_top" }, class: "rounded-lg bg-white dark:bg-gray-800 p-5 shadow ring-1 ring-gray-200 dark:ring-gray-700 block hover:ring-gray-300 dark:hover:ring-gray-600" do %>
|
|
76
|
+
<p class="<%= number %>"><%= throughput[:available] ? number_with_precision(throughput[:jobs_per_minute], precision: 1) : Pgbus::Web::TimeFormat::NONE %></p>
|
|
77
|
+
<p class="text-xs text-gray-600 dark:text-gray-300"><%= throughput[:available] ? t("pgbus.dashboard.stats_cards.throughput_detail", minutes: throughput[:window_minutes]) : t("pgbus.dashboard.stats_cards.throughput_unavailable") %></p>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<%= link_to pgbus.locks_path, data: { turbo_frame: "_top" }, class: card do %>
|
|
47
81
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.dashboard.stats_cards.parked_jobs") %></p>
|
|
48
82
|
<p class="mt-1 text-3xl font-semibold <%= @stats[:parked_total].to_i > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-gray-900 dark:text-white' %>">
|
|
49
83
|
<%= pgbus_number(@stats[:parked_total]) %>
|
|
50
84
|
</p>
|
|
51
|
-
<p class="text-xs text-gray-
|
|
85
|
+
<p class="text-xs text-gray-600 dark:text-gray-300">
|
|
52
86
|
<%= t("pgbus.dashboard.stats_cards.parked_jobs_oldest", age: pgbus_duration(@stats[:oldest_parked_age_sec])) %>
|
|
53
87
|
</p>
|
|
54
88
|
<% end %>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<%= render "pgbus/dashboard/queue_health" %>
|
|
8
8
|
|
|
9
|
-
<div class="grid grid-cols-1
|
|
9
|
+
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6">
|
|
10
10
|
<%= render "pgbus/dashboard/processes_table" %>
|
|
11
11
|
<%= render "pgbus/dashboard/recent_failures" %>
|
|
12
12
|
</div>
|