pgbus 0.2.1 → 0.2.3
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/Rakefile +88 -25
- data/app/controllers/pgbus/api/insights_controller.rb +5 -4
- data/app/controllers/pgbus/application_controller.rb +40 -0
- data/app/controllers/pgbus/insights_controller.rb +3 -2
- data/app/controllers/pgbus/locale_controller.rb +15 -0
- data/app/helpers/pgbus/application_helper.rb +67 -7
- data/app/views/layouts/pgbus/application.html.erb +51 -16
- data/app/views/pgbus/dashboard/_processes_table.html.erb +7 -7
- data/app/views/pgbus/dashboard/_queues_table.html.erb +9 -9
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +7 -7
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +10 -10
- data/app/views/pgbus/dashboard/show.html.erb +1 -1
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +20 -20
- data/app/views/pgbus/dead_letter/index.html.erb +5 -5
- data/app/views/pgbus/dead_letter/show.html.erb +12 -12
- data/app/views/pgbus/events/index.html.erb +11 -11
- data/app/views/pgbus/events/show.html.erb +6 -6
- data/app/views/pgbus/insights/show.html.erb +41 -21
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +20 -20
- data/app/views/pgbus/jobs/_failed_table.html.erb +12 -12
- data/app/views/pgbus/jobs/index.html.erb +5 -5
- data/app/views/pgbus/jobs/show.html.erb +13 -13
- data/app/views/pgbus/locks/index.html.erb +11 -11
- data/app/views/pgbus/outbox/index.html.erb +15 -15
- data/app/views/pgbus/processes/_processes_table.html.erb +8 -8
- data/app/views/pgbus/processes/index.html.erb +1 -1
- data/app/views/pgbus/queues/_queues_list.html.erb +15 -15
- data/app/views/pgbus/queues/index.html.erb +1 -1
- data/app/views/pgbus/queues/show.html.erb +11 -11
- data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +15 -15
- data/app/views/pgbus/recurring_tasks/index.html.erb +2 -2
- data/app/views/pgbus/recurring_tasks/show.html.erb +21 -21
- data/config/i18n-tasks.yml +41 -0
- data/config/locales/da.yml +348 -0
- data/config/locales/de.yml +348 -0
- data/config/locales/en.yml +348 -0
- data/config/locales/es.yml +348 -0
- data/config/locales/fi.yml +348 -0
- data/config/locales/fr.yml +348 -0
- data/config/locales/it.yml +348 -0
- data/config/locales/ja.yml +348 -0
- data/config/locales/nb.yml +348 -0
- data/config/locales/nl.yml +348 -0
- data/config/locales/pt.yml +348 -0
- data/config/locales/sv.yml +348 -0
- data/config/routes.rb +2 -0
- data/lib/pgbus/configuration.rb +8 -2
- data/lib/pgbus/engine.rb +4 -0
- data/lib/pgbus/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85f2e8748cf70bb74cb736b23e3e0288be5ca0b946b0eb80c08e1d28650e98c0
|
|
4
|
+
data.tar.gz: d1fd675bb9d9a5beb4607f8e0da684c606d096a835948abf60034bd601cdd7f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99b168d4276ef5149effdbf7d18955c542b9f8507a67d022381a61c7c36cacdf58ab414e166e528495e244313ef12d10e054119361bbf77ca2e6cc260f6d1982
|
|
7
|
+
data.tar.gz: 606324c33ddd072b24135c91314258fc2c162e54b6610ec813457cae2b4df3ba5c0391fc5dbd8a1ff6e13f14032968320b41b9a1d9450d0b5fbe2d66686f2c9e
|
data/Rakefile
CHANGED
|
@@ -50,15 +50,24 @@ task :build do
|
|
|
50
50
|
sh("rm -rf /tmp/gem-verify #{gem_file}")
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
desc "Release a new version (rake release[1.2.3] or rake release[pre])"
|
|
54
|
-
task :release, [
|
|
53
|
+
desc "Release a new version (rake release[1.2.3] or rake release[pre] or rake release[1.2.3,force])"
|
|
54
|
+
task :release, %i[version force] do |_t, args|
|
|
55
55
|
require_relative "lib/pgbus/version"
|
|
56
56
|
|
|
57
|
+
def info(msg) = puts "\e[34m→\e[0m #{msg}"
|
|
58
|
+
def success(msg) = puts "\e[32m✓\e[0m #{msg}"
|
|
59
|
+
def skip(msg) = puts "\e[33m⊘\e[0m #{msg} \e[33m(skipped)\e[0m"
|
|
60
|
+
def warn(msg) = puts "\e[33m⚠\e[0m #{msg}"
|
|
61
|
+
def error(msg) = puts "\e[31m✗\e[0m #{msg}"
|
|
62
|
+
def header(msg) = puts "\n\e[1;36m#{msg}\e[0m\n#{"─" * msg.length}"
|
|
63
|
+
|
|
57
64
|
new_version = args[:version]
|
|
58
|
-
abort "
|
|
65
|
+
abort "\e[31mUsage: rake release[X.Y.Z] or rake release[X.Y.Z,force]\e[0m" unless new_version
|
|
66
|
+
|
|
67
|
+
force = args[:force]&.to_s&.downcase == "force"
|
|
59
68
|
|
|
60
69
|
dirty = `git status --porcelain`.strip
|
|
61
|
-
abort "
|
|
70
|
+
abort "\e[31mAborting: working directory is not clean.\e[0m\n#{dirty}" unless dirty.empty?
|
|
62
71
|
|
|
63
72
|
current = Pgbus::VERSION
|
|
64
73
|
prerelease = new_version.match?(/alpha|beta|rc|pre/) || new_version == "pre"
|
|
@@ -69,43 +78,97 @@ task :release, [:version] do |_t, args|
|
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
tag = "v#{new_version}"
|
|
81
|
+
version_file = "lib/pgbus/version.rb"
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
title = "Release #{tag}"
|
|
84
|
+
title += " (force)" if force
|
|
85
|
+
header title
|
|
86
|
+
info "Current version: #{current}"
|
|
87
|
+
info "New version: #{new_version}"
|
|
88
|
+
info "Pre-release: #{prerelease}"
|
|
89
|
+
|
|
90
|
+
# Step 0: Force cleanup — delete existing release and tag
|
|
91
|
+
if force
|
|
92
|
+
header "Force cleanup"
|
|
93
|
+
if system("gh release view #{tag} >/dev/null 2>&1")
|
|
94
|
+
sh("gh release delete #{tag} --yes --cleanup-tag")
|
|
95
|
+
success "Deleted release and remote tag #{tag}"
|
|
96
|
+
else
|
|
97
|
+
skip "No release #{tag} to delete"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if system("git rev-parse #{tag} >/dev/null 2>&1")
|
|
101
|
+
sh("git tag -d #{tag}")
|
|
102
|
+
success "Deleted local tag #{tag}"
|
|
103
|
+
else
|
|
104
|
+
skip "No local tag #{tag} to delete"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
78
107
|
|
|
79
|
-
# Update version file
|
|
80
|
-
|
|
81
|
-
if new_version
|
|
108
|
+
# Step 1: Update version file
|
|
109
|
+
header "Version"
|
|
110
|
+
if new_version == current
|
|
111
|
+
skip "Version already #{new_version}"
|
|
112
|
+
else
|
|
82
113
|
content = File.read(version_file)
|
|
83
114
|
content.sub!(/VERSION = ".*"/, "VERSION = \"#{new_version}\"")
|
|
84
115
|
File.write(version_file, content)
|
|
85
|
-
|
|
116
|
+
success "Updated #{version_file}"
|
|
86
117
|
end
|
|
87
118
|
|
|
88
|
-
# Verify gem builds cleanly
|
|
119
|
+
# Step 2: Verify gem builds cleanly
|
|
120
|
+
header "Build verification"
|
|
89
121
|
sh("gem build pgbus.gemspec --strict")
|
|
90
122
|
sh("rm -f pgbus-*.gem")
|
|
123
|
+
success "Gem builds cleanly"
|
|
91
124
|
|
|
92
|
-
#
|
|
93
|
-
|
|
125
|
+
# Step 3: Commit version bump
|
|
126
|
+
header "Git commit"
|
|
127
|
+
version_changed = !`git diff #{version_file}`.strip.empty? || !`git diff --cached #{version_file}`.strip.empty?
|
|
128
|
+
if version_changed
|
|
94
129
|
sh("git add #{version_file}")
|
|
95
130
|
sh("git commit -m 'chore: bump version to #{new_version}'")
|
|
131
|
+
success "Committed version bump"
|
|
132
|
+
else
|
|
133
|
+
skip "No version change to commit"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Step 4: Push to origin
|
|
137
|
+
header "Git push"
|
|
138
|
+
local_sha = `git rev-parse HEAD`.strip
|
|
139
|
+
remote_sha = `git rev-parse origin/main 2>/dev/null`.strip
|
|
140
|
+
if local_sha == remote_sha
|
|
141
|
+
skip "origin/main already at #{local_sha[0..6]}"
|
|
142
|
+
else
|
|
143
|
+
sh("git push origin main")
|
|
144
|
+
success "Pushed to origin/main"
|
|
96
145
|
end
|
|
97
|
-
sh("git push origin main")
|
|
98
146
|
|
|
99
|
-
|
|
100
|
-
|
|
147
|
+
# Step 5: Create release
|
|
148
|
+
header "Release"
|
|
149
|
+
tag_exists = system("git rev-parse #{tag} >/dev/null 2>&1")
|
|
150
|
+
release_exists = system("gh release view #{tag} >/dev/null 2>&1")
|
|
151
|
+
|
|
152
|
+
if release_exists
|
|
153
|
+
skip "Release #{tag} already exists (use force to re-create)"
|
|
154
|
+
elsif tag_exists
|
|
155
|
+
info "Tag #{tag} exists, creating release from it"
|
|
156
|
+
pre_flag = prerelease ? "--prerelease" : ""
|
|
157
|
+
sh("gh release create #{tag} --generate-notes #{pre_flag}".strip)
|
|
158
|
+
success "Release #{tag} created from existing tag"
|
|
159
|
+
else
|
|
160
|
+
pre_flag = prerelease ? "--prerelease" : ""
|
|
161
|
+
sh("gh release create #{tag} --generate-notes --target main #{pre_flag}".strip)
|
|
162
|
+
success "Release #{tag} created"
|
|
163
|
+
end
|
|
101
164
|
|
|
102
165
|
puts ""
|
|
103
|
-
|
|
104
|
-
puts "
|
|
105
|
-
puts "
|
|
106
|
-
puts "
|
|
107
|
-
puts "
|
|
108
|
-
puts "
|
|
166
|
+
success "\e[1mRelease #{tag} complete!\e[0m CI will handle the rest:"
|
|
167
|
+
puts " • Run tests"
|
|
168
|
+
puts " • Build + verify gem"
|
|
169
|
+
puts " • Sign with Sigstore"
|
|
170
|
+
puts " • Publish to RubyGems"
|
|
171
|
+
puts " • Upload assets to the release"
|
|
109
172
|
end
|
|
110
173
|
|
|
111
174
|
task default: %i[spec rubocop]
|
|
@@ -4,11 +4,12 @@ module Pgbus
|
|
|
4
4
|
module Api
|
|
5
5
|
class InsightsController < ApplicationController
|
|
6
6
|
def show
|
|
7
|
+
minutes = insights_minutes
|
|
7
8
|
render json: {
|
|
8
|
-
summary: data_source.job_stats_summary,
|
|
9
|
-
throughput: data_source.job_throughput,
|
|
10
|
-
status_counts: data_source.job_status_counts,
|
|
11
|
-
slowest: data_source.slowest_job_classes
|
|
9
|
+
summary: data_source.job_stats_summary(minutes: minutes),
|
|
10
|
+
throughput: data_source.job_throughput(minutes: minutes),
|
|
11
|
+
status_counts: data_source.job_status_counts(minutes: minutes),
|
|
12
|
+
slowest: data_source.slowest_job_classes(minutes: minutes)
|
|
12
13
|
}
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -5,6 +5,7 @@ module Pgbus
|
|
|
5
5
|
include Web::Authentication
|
|
6
6
|
|
|
7
7
|
protect_from_forgery with: :exception
|
|
8
|
+
before_action :set_locale
|
|
8
9
|
|
|
9
10
|
layout "pgbus/application"
|
|
10
11
|
|
|
@@ -20,6 +21,38 @@ module Pgbus
|
|
|
20
21
|
|
|
21
22
|
private
|
|
22
23
|
|
|
24
|
+
def set_locale
|
|
25
|
+
I18n.locale = extract_locale || I18n.default_locale
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def extract_locale
|
|
29
|
+
locale = params[:locale] || cookies[:pgbus_locale] || preferred_locale_from_header
|
|
30
|
+
locale if locale && available_locales.include?(locale.to_sym)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def preferred_locale_from_header
|
|
34
|
+
return unless request.env["HTTP_ACCEPT_LANGUAGE"]
|
|
35
|
+
|
|
36
|
+
request.env["HTTP_ACCEPT_LANGUAGE"]
|
|
37
|
+
.scan(/([a-z]{2}(?:-[A-Z]{2})?)\s*;?\s*(?:q=([0-9.]+))?/i)
|
|
38
|
+
.sort_by { |_, q| -(q&.to_f || 1.0) }
|
|
39
|
+
.each do |lang, _|
|
|
40
|
+
code = lang.downcase.to_sym
|
|
41
|
+
return code.to_s if available_locales.include?(code)
|
|
42
|
+
|
|
43
|
+
# Try base language (e.g., "de" from "de-AT")
|
|
44
|
+
base = lang.split("-").first.downcase.to_sym
|
|
45
|
+
return base.to_s if available_locales.include?(base)
|
|
46
|
+
end
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def available_locales
|
|
51
|
+
@available_locales ||= Dir[Pgbus::Engine.root.join("config", "locales", "*.yml")]
|
|
52
|
+
.map { |f| File.basename(f, ".yml").to_sym }
|
|
53
|
+
end
|
|
54
|
+
helper_method :available_locales
|
|
55
|
+
|
|
23
56
|
def data_source
|
|
24
57
|
@data_source ||= Pgbus.configuration.web_data_source || Web::DataSource.new
|
|
25
58
|
end
|
|
@@ -32,6 +65,13 @@ module Pgbus
|
|
|
32
65
|
Pgbus.configuration.web_per_page
|
|
33
66
|
end
|
|
34
67
|
|
|
68
|
+
def insights_minutes
|
|
69
|
+
config = Pgbus.configuration
|
|
70
|
+
default = config.insights_default_minutes.to_i
|
|
71
|
+
value = (params[:minutes] || default).to_i
|
|
72
|
+
value.clamp(1, [default, 43_200].max)
|
|
73
|
+
end
|
|
74
|
+
|
|
35
75
|
def turbo_frame_request?
|
|
36
76
|
request.headers["Turbo-Frame"].present? || params[:frame].present?
|
|
37
77
|
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Pgbus
|
|
4
4
|
class InsightsController < ApplicationController
|
|
5
5
|
def show
|
|
6
|
-
@
|
|
7
|
-
@
|
|
6
|
+
@minutes = insights_minutes
|
|
7
|
+
@summary = data_source.job_stats_summary(minutes: @minutes)
|
|
8
|
+
@slowest = data_source.slowest_job_classes(minutes: @minutes)
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
class LocaleController < ApplicationController
|
|
5
|
+
def update
|
|
6
|
+
locale = params[:locale].to_s
|
|
7
|
+
if available_locales.include?(locale.to_sym)
|
|
8
|
+
cookies[:pgbus_locale] = { value: locale, expires: 1.year.from_now, path: "/" }
|
|
9
|
+
I18n.locale = locale
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
redirect_back fallback_location: pgbus.root_path
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -31,17 +31,21 @@ module Pgbus
|
|
|
31
31
|
|
|
32
32
|
def pgbus_status_badge(healthy)
|
|
33
33
|
if healthy
|
|
34
|
-
tag.span("
|
|
34
|
+
tag.span(I18n.t("pgbus.helpers.status_badge.healthy"),
|
|
35
|
+
class: "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800")
|
|
35
36
|
else
|
|
36
|
-
tag.span("
|
|
37
|
+
tag.span(I18n.t("pgbus.helpers.status_badge.stale"),
|
|
38
|
+
class: "inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800")
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def pgbus_queue_badge(name)
|
|
41
43
|
if name.to_s.end_with?("_dlq")
|
|
42
|
-
tag.span("
|
|
44
|
+
tag.span(I18n.t("pgbus.helpers.queue_badge.dlq"),
|
|
45
|
+
class: "inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700")
|
|
43
46
|
else
|
|
44
|
-
tag.span("
|
|
47
|
+
tag.span(I18n.t("pgbus.helpers.queue_badge.queue"),
|
|
48
|
+
class: "inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700")
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
|
@@ -76,7 +80,8 @@ module Pgbus
|
|
|
76
80
|
def pgbus_paused_badge(paused)
|
|
77
81
|
return unless paused
|
|
78
82
|
|
|
79
|
-
tag.span("
|
|
83
|
+
tag.span(I18n.t("pgbus.helpers.paused_badge"),
|
|
84
|
+
class: "inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800")
|
|
80
85
|
end
|
|
81
86
|
|
|
82
87
|
def pgbus_parse_message(message)
|
|
@@ -123,14 +128,26 @@ module Pgbus
|
|
|
123
128
|
|
|
124
129
|
def pgbus_recurring_health_badge(task)
|
|
125
130
|
if task[:last_run_at].nil?
|
|
126
|
-
tag.span("
|
|
131
|
+
tag.span(I18n.t("pgbus.helpers.recurring_health.pending"),
|
|
127
132
|
class: "inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800")
|
|
128
133
|
else
|
|
129
|
-
tag.span("
|
|
134
|
+
tag.span(I18n.t("pgbus.helpers.recurring_health.active"),
|
|
130
135
|
class: "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800")
|
|
131
136
|
end
|
|
132
137
|
end
|
|
133
138
|
|
|
139
|
+
def pgbus_time_range_label(minutes)
|
|
140
|
+
minutes = [minutes.to_i, 1].max
|
|
141
|
+
|
|
142
|
+
if minutes > 1440 && (minutes % 1440).zero?
|
|
143
|
+
pgbus_pluralize_unit(minutes / 1440, "day")
|
|
144
|
+
elsif minutes >= 60 && (minutes % 60).zero?
|
|
145
|
+
pgbus_pluralize_unit(minutes / 60, "hour")
|
|
146
|
+
else
|
|
147
|
+
pgbus_pluralize_unit(minutes, "minute")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
134
151
|
def pgbus_nav_link(label, path)
|
|
135
152
|
active = request.path == path || (path != pgbus.root_path && request.path.start_with?(path))
|
|
136
153
|
css = if active
|
|
@@ -140,5 +157,48 @@ module Pgbus
|
|
|
140
157
|
end
|
|
141
158
|
link_to label, path, class: css
|
|
142
159
|
end
|
|
160
|
+
|
|
161
|
+
LOCALE_NAMES = {
|
|
162
|
+
da: "Dansk",
|
|
163
|
+
de: "Deutsch",
|
|
164
|
+
en: "English",
|
|
165
|
+
es: "Espa\u00f1ol",
|
|
166
|
+
fi: "Suomi",
|
|
167
|
+
fr: "Fran\u00e7ais",
|
|
168
|
+
it: "Italiano",
|
|
169
|
+
ja: "\u65E5\u672C\u8A9E",
|
|
170
|
+
nb: "Norsk",
|
|
171
|
+
nl: "Nederlands",
|
|
172
|
+
pt: "Portugu\u00eas",
|
|
173
|
+
sv: "Svenska"
|
|
174
|
+
}.freeze
|
|
175
|
+
|
|
176
|
+
def pgbus_locale_name(code)
|
|
177
|
+
LOCALE_NAMES[code.to_sym] || code.to_s.upcase
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def pgbus_locale_flag(code)
|
|
181
|
+
case code.to_sym
|
|
182
|
+
when :da then "\u{1F1E9}\u{1F1F0}"
|
|
183
|
+
when :de then "\u{1F1E9}\u{1F1EA}"
|
|
184
|
+
when :en then "\u{1F1EC}\u{1F1E7}"
|
|
185
|
+
when :es then "\u{1F1EA}\u{1F1F8}"
|
|
186
|
+
when :fi then "\u{1F1EB}\u{1F1EE}"
|
|
187
|
+
when :fr then "\u{1F1EB}\u{1F1F7}"
|
|
188
|
+
when :it then "\u{1F1EE}\u{1F1F9}"
|
|
189
|
+
when :ja then "\u{1F1EF}\u{1F1F5}"
|
|
190
|
+
when :nb then "\u{1F1F3}\u{1F1F4}"
|
|
191
|
+
when :nl then "\u{1F1F3}\u{1F1F1}"
|
|
192
|
+
when :pt then "\u{1F1F5}\u{1F1F9}"
|
|
193
|
+
when :sv then "\u{1F1F8}\u{1F1EA}"
|
|
194
|
+
else "\u{1F310}"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def pgbus_pluralize_unit(count, unit)
|
|
201
|
+
count == 1 ? "1 #{unit}" : "#{count} #{unit}s"
|
|
202
|
+
end
|
|
143
203
|
end
|
|
144
204
|
end
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="<%= I18n.locale %>" 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
|
-
<title
|
|
6
|
+
<title><%= t("pgbus.layout.title") %></title>
|
|
7
|
+
<style>
|
|
8
|
+
/* Prevent white flash during navigation in dark mode.
|
|
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; }
|
|
12
|
+
</style>
|
|
7
13
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
14
|
<script>
|
|
9
15
|
tailwind.config = { darkMode: 'class' };
|
|
@@ -17,9 +23,17 @@
|
|
|
17
23
|
var isDark = document.documentElement.classList.toggle('dark');
|
|
18
24
|
localStorage.setItem('pgbus-dark', isDark);
|
|
19
25
|
}
|
|
26
|
+
// Close locale dropdown when clicking outside
|
|
27
|
+
document.addEventListener('click', function(e) {
|
|
28
|
+
var switcher = document.getElementById('pgbus-locale-switcher');
|
|
29
|
+
var menu = document.getElementById('pgbus-locale-menu');
|
|
30
|
+
if (menu && switcher && !switcher.contains(e.target)) {
|
|
31
|
+
menu.classList.add('hidden');
|
|
32
|
+
}
|
|
33
|
+
});
|
|
20
34
|
</script>
|
|
21
35
|
<script type="module">
|
|
22
|
-
import * as Turbo from "https://
|
|
36
|
+
import * as Turbo from "https://esm.sh/@hotwired/turbo@8";
|
|
23
37
|
|
|
24
38
|
<% if Pgbus.configuration.web_live_updates %>
|
|
25
39
|
const interval = <%= Pgbus.configuration.web_refresh_interval %>;
|
|
@@ -28,7 +42,10 @@
|
|
|
28
42
|
function refreshFrames() {
|
|
29
43
|
if (document.hidden) return;
|
|
30
44
|
document.querySelectorAll("turbo-frame[data-auto-refresh]")
|
|
31
|
-
.forEach(frame =>
|
|
45
|
+
.forEach(frame => {
|
|
46
|
+
if (!frame.src && frame.dataset.src) frame.src = frame.dataset.src;
|
|
47
|
+
if (frame.src) frame.reload();
|
|
48
|
+
});
|
|
32
49
|
}
|
|
33
50
|
function start() { timer = setInterval(refreshFrames, interval); }
|
|
34
51
|
function stop() { clearInterval(timer); }
|
|
@@ -46,25 +63,42 @@
|
|
|
46
63
|
<div class="flex h-14 items-center justify-between">
|
|
47
64
|
<div class="flex items-center space-x-8">
|
|
48
65
|
<%= link_to pgbus.root_path, class: "flex items-center space-x-2" do %>
|
|
49
|
-
<span class="text-lg font-bold text-white"
|
|
66
|
+
<span class="text-lg font-bold text-white"><%= t("pgbus.layout.brand") %></span>
|
|
50
67
|
<span class="rounded bg-gray-700 px-1.5 py-0.5 text-xs text-gray-300"><%= Pgbus::VERSION %></span>
|
|
51
68
|
<% end %>
|
|
52
69
|
|
|
53
70
|
<div class="flex space-x-1">
|
|
54
|
-
<%= pgbus_nav_link "
|
|
55
|
-
<%= pgbus_nav_link "
|
|
56
|
-
<%= pgbus_nav_link "
|
|
57
|
-
<%= pgbus_nav_link "
|
|
58
|
-
<%= pgbus_nav_link "
|
|
59
|
-
<%= pgbus_nav_link "
|
|
60
|
-
<%= pgbus_nav_link "
|
|
61
|
-
<%= pgbus_nav_link "
|
|
62
|
-
<%= pgbus_nav_link "
|
|
63
|
-
<%= pgbus_nav_link "
|
|
71
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.dashboard"), pgbus.root_path %>
|
|
72
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.queues"), pgbus.queues_path %>
|
|
73
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.jobs"), pgbus.jobs_path %>
|
|
74
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.recurring"), pgbus.recurring_tasks_path %>
|
|
75
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.processes"), pgbus.processes_path %>
|
|
76
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.events"), pgbus.events_path %>
|
|
77
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.dlq"), pgbus.dead_letter_index_path %>
|
|
78
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.outbox"), pgbus.outbox_index_path %>
|
|
79
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.locks"), pgbus.locks_path %>
|
|
80
|
+
<%= pgbus_nav_link t("pgbus.layout.nav.insights"), pgbus.insights_path %>
|
|
64
81
|
</div>
|
|
65
82
|
</div>
|
|
66
83
|
|
|
67
|
-
<
|
|
84
|
+
<div class="flex items-center space-x-2">
|
|
85
|
+
<!-- Locale switcher -->
|
|
86
|
+
<div class="relative" id="pgbus-locale-switcher">
|
|
87
|
+
<button type="button" onclick="document.getElementById('pgbus-locale-menu').classList.toggle('hidden')" class="rounded-md px-2 py-1 text-sm text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
|
88
|
+
<%= pgbus_locale_flag(I18n.locale) %> <%= I18n.locale.to_s.upcase %>
|
|
89
|
+
</button>
|
|
90
|
+
<div id="pgbus-locale-menu" class="hidden absolute right-0 z-50 mt-1 w-44 origin-top-right rounded-md bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10">
|
|
91
|
+
<div class="py-1" role="menu">
|
|
92
|
+
<% available_locales.sort.each do |loc| %>
|
|
93
|
+
<a href="<%= pgbus.set_locale_path(locale: loc) %>" role="menuitem" class="block px-3 py-1 text-sm no-underline <%= loc == I18n.locale ? 'bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 font-medium' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700' %>">
|
|
94
|
+
<%= pgbus_locale_flag(loc) %> <%= pgbus_locale_name(loc) %>
|
|
95
|
+
</a>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<button onclick="toggleDarkMode()" class="rounded-md p-2 text-gray-400 hover:text-white focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-900" aria-label="<%= t("pgbus.layout.toggle_dark_mode") %>">
|
|
68
102
|
<svg class="h-5 w-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
|
|
69
103
|
<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
104
|
</svg>
|
|
@@ -72,6 +106,7 @@
|
|
|
72
106
|
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
|
73
107
|
</svg>
|
|
74
108
|
</button>
|
|
109
|
+
</div>
|
|
75
110
|
</div>
|
|
76
111
|
</div>
|
|
77
112
|
</nav>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<turbo-frame id="dashboard-processes" data-auto-refresh src="<%= pgbus.root_path(frame: 'processes') %>">
|
|
1
|
+
<turbo-frame id="dashboard-processes" data-auto-refresh data-src="<%= pgbus.root_path(frame: 'processes') %>">
|
|
2
2
|
<div>
|
|
3
|
-
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
3
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3"><%= t("pgbus.dashboard.processes_table.title") %></h2>
|
|
4
4
|
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
5
5
|
<table class="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"
|
|
9
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
10
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
11
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
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>
|
|
12
12
|
</tr>
|
|
13
13
|
</thead>
|
|
14
14
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</tr>
|
|
22
22
|
<% end %>
|
|
23
23
|
<% if @processes.empty? %>
|
|
24
|
-
<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-gray-400"
|
|
24
|
+
<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.dashboard.processes_table.empty") %></td></tr>
|
|
25
25
|
<% end %>
|
|
26
26
|
</tbody>
|
|
27
27
|
</table>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
<turbo-frame id="dashboard-queues" data-auto-refresh src="<%= pgbus.root_path(frame: 'queues') %>">
|
|
1
|
+
<turbo-frame id="dashboard-queues" data-auto-refresh data-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 dark:text-white"
|
|
5
|
-
<%= link_to "
|
|
4
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white"><%= t("pgbus.dashboard.queues_table.title") %></h2>
|
|
5
|
+
<%= link_to t("pgbus.dashboard.queues_table.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
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="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"
|
|
13
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"
|
|
14
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"
|
|
15
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"
|
|
16
|
-
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"
|
|
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-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.depth") %></th>
|
|
14
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.visible") %></th>
|
|
15
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.oldest") %></th>
|
|
16
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.queues_table.headers.total") %></th>
|
|
17
17
|
</tr>
|
|
18
18
|
</thead>
|
|
19
19
|
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
</tr>
|
|
31
31
|
<% end %>
|
|
32
32
|
<% if @queues.empty? %>
|
|
33
|
-
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400"
|
|
33
|
+
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.dashboard.queues_table.empty") %></td></tr>
|
|
34
34
|
<% end %>
|
|
35
35
|
</tbody>
|
|
36
36
|
</table>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<turbo-frame id="dashboard-failures" data-auto-refresh src="<%= pgbus.root_path(frame: 'failures') %>">
|
|
1
|
+
<turbo-frame id="dashboard-failures" data-auto-refresh data-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 dark:text-white"
|
|
4
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white"><%= t("pgbus.dashboard.recent_failures.title") %></h2>
|
|
5
5
|
<% if @recent_failures.any? %>
|
|
6
|
-
<%= link_to "
|
|
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
7
|
<% end %>
|
|
8
8
|
</div>
|
|
9
9
|
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow ring-1 ring-gray-200 dark:ring-gray-700">
|
|
10
10
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
11
11
|
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
12
12
|
<tr>
|
|
13
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
14
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
15
|
-
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"
|
|
13
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.queue") %></th>
|
|
14
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= t("pgbus.dashboard.recent_failures.headers.error") %></th>
|
|
15
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500"><%= 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">
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
</tr>
|
|
25
25
|
<% end %>
|
|
26
26
|
<% if @recent_failures.empty? %>
|
|
27
|
-
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400"
|
|
27
|
+
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400"><%= t("pgbus.dashboard.recent_failures.empty") %></td></tr>
|
|
28
28
|
<% end %>
|
|
29
29
|
</tbody>
|
|
30
30
|
</table>
|