pgbouncerhero 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +155 -58
- data/app/assets/builds/pgbouncerhero/application.css +2 -0
- data/app/assets/stylesheets/pgbouncerhero/application.css +1 -0
- data/app/controllers/pg_bouncer_hero/application_controller.rb +6 -9
- data/app/controllers/pg_bouncer_hero/database_controller.rb +39 -13
- data/app/controllers/pg_bouncer_hero/home_controller.rb +0 -2
- data/app/helpers/pg_bouncer_hero/application_helper.rb +19 -13
- data/app/javascript/pgbouncerhero/application.js +6 -0
- data/app/javascript/pgbouncerhero/controllers/polling_controller.js +27 -0
- data/app/views/layouts/pg_bouncer_hero/application.html.erb +19 -22
- data/app/views/pg_bouncer_hero/database/_actions.html.erb +9 -9
- data/app/views/pg_bouncer_hero/database/_clients.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_conf.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_databases.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_menu.html.erb +20 -18
- data/app/views/pg_bouncer_hero/database/_pools.html.erb +21 -23
- data/app/views/pg_bouncer_hero/database/_state.html.erb +11 -0
- data/app/views/pg_bouncer_hero/database/_stats.html.erb +27 -33
- data/app/views/pg_bouncer_hero/database/state.html.erb +4 -0
- data/app/views/pg_bouncer_hero/database/summary.html.erb +3 -0
- data/app/views/pg_bouncer_hero/home/_card.html.erb +9 -22
- data/app/views/pg_bouncer_hero/home/_card_content.html.erb +33 -33
- data/app/views/pg_bouncer_hero/home/_card_loading_content.html.erb +5 -5
- data/app/views/pg_bouncer_hero/home/index.html.erb +13 -37
- data/app/views/shared/_alert.html.erb +2 -5
- data/config/importmap.rb +5 -0
- data/config/routes.rb +10 -2
- data/lib/generators/pgbouncerhero/templates/config.yml +13 -8
- data/lib/pgbouncerhero/connection.rb +53 -16
- data/lib/pgbouncerhero/database.rb +69 -4
- data/lib/pgbouncerhero/engine.rb +13 -10
- data/lib/pgbouncerhero/group.rb +0 -2
- data/lib/pgbouncerhero/methods/basics.rb +28 -12
- data/lib/pgbouncerhero/version.rb +1 -1
- data/lib/pgbouncerhero.rb +124 -41
- metadata +187 -46
- data/.gitignore +0 -19
- data/Gemfile +0 -4
- data/app/assets/images/pgbouncerhero/short-paragraph.png +0 -0
- data/app/assets/javascripts/pgbouncerhero/application.js +0 -3
- data/app/assets/stylesheets/pgbouncerhero/application.css.scss +0 -15
- data/app/views/pg_bouncer_hero/database/summary.js.erb +0 -5
- data/doc/screenshot-1.png +0 -0
- data/doc/screenshot-2.png +0 -0
- data/doc/screenshot-3.png +0 -0
- data/pgbouncerhero.gemspec +0 -32
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<h4 class="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm p-4">
|
|
2
|
+
<h4 class="text-base font-semibold text-gray-900 mb-3">Actions</h4>
|
|
3
|
+
<div class="flex flex-wrap gap-2">
|
|
4
|
+
<button class="px-3 py-1.5 text-sm font-medium rounded-md border border-red-300 text-red-700 hover:bg-red-50">Pause</button>
|
|
5
|
+
<button class="px-3 py-1.5 text-sm font-medium rounded-md border border-green-300 text-green-700 hover:bg-green-50">Resume</button>
|
|
6
|
+
<button class="px-3 py-1.5 text-sm font-medium rounded-md border border-red-300 text-red-700 hover:bg-red-50">Disable</button>
|
|
7
|
+
<button class="px-3 py-1.5 text-sm font-medium rounded-md border border-green-300 text-green-700 hover:bg-green-50">Enable</button>
|
|
8
|
+
<button class="px-3 py-1.5 text-sm font-medium rounded-md border border-red-300 text-red-700 hover:bg-red-50">Kill</button>
|
|
9
|
+
</div>
|
|
10
10
|
</div>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
</
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<% clients.first.keys.each do |key| %>
|
|
9
|
-
<th>
|
|
10
|
-
<%= key.titleize %>
|
|
11
|
-
</th>
|
|
12
|
-
<% end %>
|
|
13
|
-
</tr>
|
|
14
|
-
</thead>
|
|
15
|
-
<tbody>
|
|
16
|
-
<% clients.each do |row| %>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h4 class="text-base font-semibold text-gray-900">Clients</h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="overflow-x-auto">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
7
|
+
<thead class="bg-gray-50">
|
|
17
8
|
<tr>
|
|
18
|
-
<%
|
|
19
|
-
<
|
|
20
|
-
<%= v %>
|
|
21
|
-
</td>
|
|
9
|
+
<% clients.first.keys.each do |key| %>
|
|
10
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= key.titleize %></th>
|
|
22
11
|
<% end %>
|
|
23
12
|
</tr>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
15
|
+
<% clients.each do |row| %>
|
|
16
|
+
<tr class="hover:bg-gray-50">
|
|
17
|
+
<% row.each do |_k, v| %>
|
|
18
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= v %></td>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tr>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
27
25
|
</div>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
</
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<% conf.first.keys.each do |key| %>
|
|
9
|
-
<th>
|
|
10
|
-
<%= key.titleize %>
|
|
11
|
-
</th>
|
|
12
|
-
<% end %>
|
|
13
|
-
</tr>
|
|
14
|
-
</thead>
|
|
15
|
-
<tbody>
|
|
16
|
-
<% conf.each do |row| %>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h4 class="text-base font-semibold text-gray-900">Configuration</h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="overflow-x-auto">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
7
|
+
<thead class="bg-gray-50">
|
|
17
8
|
<tr>
|
|
18
|
-
<%
|
|
19
|
-
<
|
|
20
|
-
<%= v %>
|
|
21
|
-
</td>
|
|
9
|
+
<% conf.first.keys.each do |key| %>
|
|
10
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= key.titleize %></th>
|
|
22
11
|
<% end %>
|
|
23
12
|
</tr>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
15
|
+
<% conf.each do |row| %>
|
|
16
|
+
<tr class="hover:bg-gray-50">
|
|
17
|
+
<% row.each do |_k, v| %>
|
|
18
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= v %></td>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tr>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
27
25
|
</div>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
</
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<% databases.first.keys.each do |key| %>
|
|
9
|
-
<th>
|
|
10
|
-
<%= key.titleize %>
|
|
11
|
-
</th>
|
|
12
|
-
<% end %>
|
|
13
|
-
</tr>
|
|
14
|
-
</thead>
|
|
15
|
-
<tbody>
|
|
16
|
-
<% databases.each do |row| %>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h4 class="text-base font-semibold text-gray-900">Databases</h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="overflow-x-auto">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
7
|
+
<thead class="bg-gray-50">
|
|
17
8
|
<tr>
|
|
18
|
-
<%
|
|
19
|
-
<
|
|
20
|
-
<%= v %>
|
|
21
|
-
</td>
|
|
9
|
+
<% databases.first.keys.each do |key| %>
|
|
10
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= key.titleize %></th>
|
|
22
11
|
<% end %>
|
|
23
12
|
</tr>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
15
|
+
<% databases.each do |row| %>
|
|
16
|
+
<tr class="hover:bg-gray-50">
|
|
17
|
+
<% row.each do |_k, v| %>
|
|
18
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= v %></td>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tr>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
27
25
|
</div>
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
<
|
|
2
|
-
<div class="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<div class="item">
|
|
12
|
-
<%= link_to 'Reload', reload_path(database: database.name.parameterize), remote: :true, method: :post, class: 'ui button basic positive button', data: { confirm: 'Are you sure?' } %>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="item">
|
|
15
|
-
<%= link_to 'Suspend', suspend_path(database: database.name.parameterize), remote: :true, method: :post, class: 'ui button basic primary button', data: { confirm: 'Are you sure?' } %>
|
|
1
|
+
<nav class="bg-white rounded-lg border border-gray-200 shadow-sm mb-6">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200 flex items-center justify-between flex-wrap gap-2">
|
|
3
|
+
<div class="flex items-center gap-4 flex-wrap">
|
|
4
|
+
<span class="font-semibold text-gray-900"><%= "#{database.group.name} > #{database.name}" %></span>
|
|
5
|
+
<%= link_to "Databases", databases_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('databases') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
6
|
+
<%= link_to "Stats", stats_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('stats') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
7
|
+
<%= link_to "Pools", pools_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('pools') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
8
|
+
<%= link_to "Clients", clients_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('clients') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
9
|
+
<%= link_to "Configuration", conf_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('conf') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
10
|
+
<%= link_to "State", state_path(database: database.name.parameterize), class: "px-3 py-1.5 text-sm rounded-md #{is_active('state') ? 'bg-gray-900 text-white' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" %>
|
|
16
11
|
</div>
|
|
17
|
-
<div class="
|
|
18
|
-
|
|
12
|
+
<div class="flex items-center gap-2">
|
|
13
|
+
<% if PgBouncerHero.read_only? %>
|
|
14
|
+
<span class="inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-gray-300 ring-inset">Read-only</span>
|
|
15
|
+
<% else %>
|
|
16
|
+
<%= button_to "Reload", reload_path(database: database.name.parameterize), method: :post, class: "px-3 py-1.5 text-sm font-medium rounded-md border border-green-300 text-green-700 hover:bg-green-50", form: { data: { turbo_confirm: "Reload PgBouncer configuration?" } } %>
|
|
17
|
+
<%= button_to "Suspend", suspend_path(database: database.name.parameterize), method: :post, class: "px-3 py-1.5 text-sm font-medium rounded-md border border-blue-300 text-blue-700 hover:bg-blue-50", form: { data: { turbo_confirm: "Suspend all PgBouncer socket processing until Resume is selected?" } } %>
|
|
18
|
+
<%= button_to "Resume", resume_path(database: database.name.parameterize), method: :post, class: "px-3 py-1.5 text-sm font-medium rounded-md border border-green-300 text-green-700 hover:bg-green-50", form: { data: { turbo_confirm: "Resume PgBouncer socket processing?" } } %>
|
|
19
|
+
<%= button_to "Graceful shutdown", shutdown_path(database: database.name.parameterize), method: :post, class: "px-3 py-1.5 text-sm font-medium rounded-md border border-red-300 text-red-700 hover:bg-red-50", form: { data: { turbo_confirm: "Stop accepting new clients and shut down after existing clients disconnect?" } } %>
|
|
20
|
+
<% end %>
|
|
19
21
|
</div>
|
|
20
22
|
</div>
|
|
21
|
-
</
|
|
23
|
+
</nav>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
</
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<% pools.first.keys.each do |key| %>
|
|
9
|
-
<th>
|
|
10
|
-
<%= key.titleize %>
|
|
11
|
-
</th>
|
|
12
|
-
<% end %>
|
|
13
|
-
</tr>
|
|
14
|
-
</thead>
|
|
15
|
-
<tbody>
|
|
16
|
-
<% pools.each do |row| %>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h4 class="text-base font-semibold text-gray-900">Pools</h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="overflow-x-auto">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
7
|
+
<thead class="bg-gray-50">
|
|
17
8
|
<tr>
|
|
18
|
-
<%
|
|
19
|
-
<
|
|
20
|
-
<%= v %>
|
|
21
|
-
</td>
|
|
9
|
+
<% pools.first.keys.each do |key| %>
|
|
10
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= key.titleize %></th>
|
|
22
11
|
<% end %>
|
|
23
12
|
</tr>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
15
|
+
<% pools.each do |row| %>
|
|
16
|
+
<tr class="hover:bg-gray-50">
|
|
17
|
+
<% row.each do |_k, v| %>
|
|
18
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= v %></td>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tr>
|
|
21
|
+
<% end %>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
27
25
|
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm p-4">
|
|
2
|
+
<h4 class="text-base font-semibold text-gray-900 mb-3">State</h4>
|
|
3
|
+
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
4
|
+
<% state.each do |row| %>
|
|
5
|
+
<div>
|
|
6
|
+
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wider"><%= row["key"].titleize %></dt>
|
|
7
|
+
<dd class="mt-1 text-sm text-gray-900"><%= row["value"] %></dd>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
</dl>
|
|
11
|
+
</div>
|
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
</
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<% stats.first.keys.each do |key| %>
|
|
9
|
-
<th>
|
|
10
|
-
<%= key.titleize %>
|
|
11
|
-
</th>
|
|
12
|
-
<% end %>
|
|
13
|
-
</tr>
|
|
14
|
-
</thead>
|
|
15
|
-
<tbody>
|
|
16
|
-
<% stats.each do |row| %>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h4 class="text-base font-semibold text-gray-900">Stats</h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="overflow-x-auto">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
7
|
+
<thead class="bg-gray-50">
|
|
17
8
|
<tr>
|
|
18
|
-
<%
|
|
19
|
-
|
|
20
|
-
<td>
|
|
21
|
-
<%= number_to_human_size(v) %>
|
|
22
|
-
</td>
|
|
23
|
-
<% elsif ['total_query_time', 'avg_query'].include? k %>
|
|
24
|
-
<td>
|
|
25
|
-
<%= humanize_ms(v.to_i) %>
|
|
26
|
-
</td>
|
|
27
|
-
<% else %>
|
|
28
|
-
<td>
|
|
29
|
-
<%= v %>
|
|
30
|
-
</td>
|
|
31
|
-
<% end %>
|
|
9
|
+
<% stats.first.keys.each do |key| %>
|
|
10
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= key.titleize %></th>
|
|
32
11
|
<% end %>
|
|
33
12
|
</tr>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
15
|
+
<% stats.each do |row| %>
|
|
16
|
+
<tr class="hover:bg-gray-50">
|
|
17
|
+
<% row.each do |k, v| %>
|
|
18
|
+
<% if %w[total_received total_sent avg_recv avg_sent].include?(k) %>
|
|
19
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= number_to_human_size(v) %></td>
|
|
20
|
+
<% elsif %w[total_query_time avg_query].include?(k) %>
|
|
21
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= humanize_ms(v.to_i) %></td>
|
|
22
|
+
<% else %>
|
|
23
|
+
<td class="px-4 py-2 text-sm text-gray-700 whitespace-nowrap"><%= v %></td>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% end %>
|
|
26
|
+
</tr>
|
|
27
|
+
<% end %>
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
</div>
|
|
37
31
|
</div>
|
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<%= link_to databases_path(group: database.group.name.parameterize, database: database.name.parameterize),
|
|
3
|
-
<div class="
|
|
4
|
-
<div
|
|
5
|
-
<div
|
|
6
|
-
|
|
7
|
-
<%= database.name %>
|
|
8
|
-
</div>
|
|
9
|
-
<div style="font-size: 12px; color: grey">
|
|
10
|
-
<%= database.host %>
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
13
|
-
<button class="grey ui button basic button" id="segment_status_<%= database.group.name.parameterize %>_<%= database.name.parameterize %>" style="float: right;">
|
|
14
|
-
<%= 'N/A' %>
|
|
15
|
-
</button>
|
|
16
|
-
</div>
|
|
17
|
-
<%= render partial: partial, locals: {database: database, id: "segment_content_#{database.group.name.parameterize}_#{database.name.parameterize}"} %>
|
|
18
|
-
<div class="ui segment" id="segment_refreshed_<%= database.group.name.parameterize %>_<%= database.name.parameterize %>" style="height: 35px; display: none;">
|
|
19
|
-
<span class="right floated" id="segment_refreshed_span_<%= database.group.name.parameterize %>_<%= database.name.parameterize %>" style="float:right; margin-top: -6px; color: grey">
|
|
20
|
-
Last refresh:
|
|
21
|
-
<%= Time.now.strftime("%H:%M:%S GMT%z (%Z)") %>
|
|
22
|
-
</span>
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
2
|
+
<%= link_to databases_path(group: database.group.name.parameterize, database: database.name.parameterize), class: "block no-underline text-inherit" do %>
|
|
3
|
+
<div class="px-4 py-3 flex items-center justify-between border-b border-gray-100">
|
|
4
|
+
<div>
|
|
5
|
+
<div class="text-base font-bold text-gray-900"><%= database.name %></div>
|
|
6
|
+
<div class="text-xs text-gray-500"><%= database.host %></div>
|
|
23
7
|
</div>
|
|
24
8
|
</div>
|
|
25
9
|
<% end %>
|
|
10
|
+
<turbo-frame id="card_<%= database.group.name.parameterize %>_<%= database.name.parameterize %>" src="<%= summary_path(group: database.group.name.parameterize, database: database.name.parameterize) %>" loading="lazy">
|
|
11
|
+
<%= render partial: "card_loading_content" %>
|
|
12
|
+
</turbo-frame>
|
|
26
13
|
</div>
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
<%
|
|
2
|
-
<%
|
|
3
|
-
<div class="
|
|
4
|
-
<div class="
|
|
5
|
-
<% summary = database.summary %>
|
|
1
|
+
<% if database.connection %>
|
|
2
|
+
<% summary = database.summary %>
|
|
3
|
+
<div class="px-4 py-3 flex items-start gap-6">
|
|
4
|
+
<div class="space-y-1 text-sm text-gray-700">
|
|
6
5
|
<div>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<%= pluralize(number, 'user') %>
|
|
6
|
+
<% number = summary.select { |row| row["list"] == "users" }.first["items"] %>
|
|
7
|
+
<%= pluralize(number, "user") %>
|
|
10
8
|
</div>
|
|
11
9
|
<div>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<%= pluralize(number, 'database') %>
|
|
10
|
+
<% number = summary.select { |row| row["list"] == "databases" }.first["items"].to_i - 1 %>
|
|
11
|
+
<%= pluralize(number, "database") %>
|
|
15
12
|
</div>
|
|
16
13
|
<div>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<%= pluralize(number, 'pool') %>
|
|
14
|
+
<% number = summary.select { |row| row["list"] == "pools" }.first["items"].to_i - 1 %>
|
|
15
|
+
<%= pluralize(number, "pool") %>
|
|
20
16
|
</div>
|
|
21
17
|
</div>
|
|
22
|
-
<div class="
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<div class="
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</span>
|
|
38
|
-
</li>
|
|
39
|
-
<% end %>
|
|
40
|
-
</ul>
|
|
18
|
+
<div class="flex-1 space-y-2">
|
|
19
|
+
<% summary.select { |row| row.key?(:databases_details) }.first[:databases_details].each do |db| %>
|
|
20
|
+
<div class="flex items-center gap-2 text-sm">
|
|
21
|
+
<span class="font-medium text-gray-900 w-24 truncate"><%= db["name"] %></span>
|
|
22
|
+
<div class="flex-1">
|
|
23
|
+
<% current = db["current_connections"].to_i %>
|
|
24
|
+
<% max = db["max_connections"].to_i %>
|
|
25
|
+
<% pct = max > 0 ? (current.to_f / max * 100).round : 0 %>
|
|
26
|
+
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
|
27
|
+
<div class="bg-green-500 h-2.5 rounded-full" style="width: <%= pct %>%"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="text-xs text-gray-500 mt-0.5"><%= current %> / <%= max %> connections</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
41
33
|
</div>
|
|
42
34
|
</div>
|
|
35
|
+
<div class="px-4 py-2 border-t border-gray-100 flex items-center justify-between">
|
|
36
|
+
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset">Online</span>
|
|
37
|
+
<span class="text-xs text-gray-400">Last refresh: <%= Time.now.strftime("%H:%M:%S %Z") %></span>
|
|
38
|
+
</div>
|
|
39
|
+
<% else %>
|
|
40
|
+
<div class="px-4 py-6 text-center">
|
|
41
|
+
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-red-600/20 ring-inset">Offline</span>
|
|
42
|
+
</div>
|
|
43
43
|
<% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<div class="
|
|
3
|
-
<div class="
|
|
1
|
+
<div class="px-4 py-6 animate-pulse">
|
|
2
|
+
<div class="space-y-3">
|
|
3
|
+
<div class="h-3 bg-gray-200 rounded w-3/4"></div>
|
|
4
|
+
<div class="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
5
|
+
<div class="h-3 bg-gray-200 rounded w-2/3"></div>
|
|
4
6
|
</div>
|
|
5
|
-
<%= image_tag('pgbouncerhero/short-paragraph.png', class: 'ui wireframe image') %>
|
|
6
|
-
<%= image_tag('pgbouncerhero/short-paragraph.png', class: 'ui wireframe image') %>
|
|
7
7
|
</div>
|
|
@@ -1,44 +1,20 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<div class="
|
|
3
|
-
Overview
|
|
1
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow-sm mb-6">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-200">
|
|
3
|
+
<h2 class="text-lg font-semibold text-gray-900">Overview</h2>
|
|
4
4
|
</div>
|
|
5
5
|
</div>
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
<div data-controller="polling" data-polling-interval-value="60000">
|
|
7
8
|
<% @groups.each do |_, group| %>
|
|
8
|
-
<div class="
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
<div class="mb-8">
|
|
10
|
+
<h3 class="text-xl font-semibold text-gray-900 mb-4 pb-2 border-b border-gray-200">
|
|
11
|
+
<%= group.name %>
|
|
12
|
+
</h3>
|
|
13
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
14
|
+
<% group.databases.each do |database| %>
|
|
15
|
+
<%= render partial: "card", locals: { database: database } %>
|
|
16
|
+
<% end %>
|
|
13
17
|
</div>
|
|
14
|
-
<%= render partial: "card", collection: group.databases, as: :database, locals: {partial: 'card_loading_content', lazy: true} %>
|
|
15
18
|
</div>
|
|
16
19
|
<% end %>
|
|
17
20
|
</div>
|
|
18
|
-
<script>
|
|
19
|
-
|
|
20
|
-
document.addEventListener("DOMContentLoaded", function(event) {
|
|
21
|
-
// First
|
|
22
|
-
$("[id^='segment_href']").each(function(idx, el) {
|
|
23
|
-
$.get($(el).data('href'));
|
|
24
|
-
});
|
|
25
|
-
// Refresh every minutes
|
|
26
|
-
setInterval(function (){
|
|
27
|
-
$("[id^='segment_href']").each(function(idx, el) {
|
|
28
|
-
var el = $(el)
|
|
29
|
-
var id = el.attr('id');
|
|
30
|
-
var href = el.data('href');
|
|
31
|
-
var refreshed_id = id.replace('href', 'refreshed_span');
|
|
32
|
-
var refreshed_div = document.getElementById(refreshed_id);
|
|
33
|
-
if (refreshed_div) {
|
|
34
|
-
refreshed_div.innerHTML = 'Refreshing...';
|
|
35
|
-
$.get(href, function(data) {
|
|
36
|
-
refreshed_div.innerHTML = 'Last refresh: ' + new Date().toTimeString();
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}, 60 * 1000)
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</script>
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
|
|
3
|
-
<i class="close icon"></i>
|
|
4
|
-
<% end %>
|
|
5
|
-
<%= message.html_safe %>
|
|
1
|
+
<div class="rounded-md p-4 mb-4 <%= alert_style_for(class_name) %>">
|
|
2
|
+
<p class="text-sm"><%= message %></p>
|
|
6
3
|
</div>
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pin "pgbouncerhero/application", to: "pgbouncerhero/application.js"
|
|
2
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
|
3
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
|
4
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
|
5
|
+
pin_all_from PgBouncerHero::Engine.root.join("app/javascript/pgbouncerhero/controllers"), under: "pgbouncerhero/controllers"
|
data/config/routes.rb
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
PgBouncerHero::Engine.routes.draw do
|
|
2
|
+
group_exists = ->(request) { PgBouncerHero.groups.key?(request.params[:group]) }
|
|
3
|
+
database_exists = lambda do |request|
|
|
4
|
+
group = PgBouncerHero.groups[request.params[:group]]
|
|
5
|
+
group&.databases&.any? { |database| database.name.parameterize == request.params[:database] }
|
|
6
|
+
end
|
|
7
|
+
|
|
2
8
|
root to: "home#index"
|
|
3
|
-
scope path: ":group", constraints:
|
|
4
|
-
scope path: ":database", constraints:
|
|
9
|
+
scope path: ":group", constraints: group_exists do
|
|
10
|
+
scope path: ":database", constraints: database_exists do
|
|
5
11
|
get :summary, controller: :database
|
|
6
12
|
get :databases, controller: :database
|
|
7
13
|
get :stats, controller: :database
|
|
8
14
|
get :pools, controller: :database
|
|
9
15
|
get :clients, controller: :database
|
|
10
16
|
get :conf, controller: :database
|
|
17
|
+
get :state, controller: :database
|
|
11
18
|
post :reload, controller: :database
|
|
12
19
|
post :suspend, controller: :database
|
|
20
|
+
post :resume, controller: :database
|
|
13
21
|
post :shutdown, controller: :database
|
|
14
22
|
end
|
|
15
23
|
end
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
pgbouncers:
|
|
2
2
|
production:
|
|
3
|
-
|
|
3
|
+
primary:
|
|
4
4
|
# eg. postgres://user:password@host:port/pgbouncer
|
|
5
|
-
# url:
|
|
5
|
+
# url: <%%= ENV["PGBOUNCER_PRODUCTION_PRIMARY_DATABASE_URL"] %>
|
|
6
|
+
# pool_size: 5
|
|
7
|
+
# pool_timeout: 5
|
|
6
8
|
# Add more databases
|
|
7
|
-
#
|
|
8
|
-
# url:
|
|
9
|
+
# replica:
|
|
10
|
+
# url: <%%= ENV["PGBOUNCER_PRODUCTION_REPLICA_DATABASE_URL"] %>
|
|
9
11
|
# staging:
|
|
10
|
-
#
|
|
11
|
-
# url:
|
|
12
|
-
#
|
|
13
|
-
# url:
|
|
12
|
+
# primary:
|
|
13
|
+
# url: <%%= ENV["PGBOUNCER_STAGING_PRIMARY_DATABASE_URL"] %>
|
|
14
|
+
# replica:
|
|
15
|
+
# url: <%%= ENV["PGBOUNCER_STAGING_REPLICA_DATABASE_URL"] %>
|
|
14
16
|
|
|
15
17
|
# Time zone (defaults to app time zone)
|
|
16
18
|
# time_zone: "Pacific Time (US & Canada)"
|
|
19
|
+
|
|
20
|
+
# Hide and reject Reload, Suspend, Resume, and Shutdown commands
|
|
21
|
+
# read_only: true
|