pghero 3.5.0 → 3.6.1

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/app/assets/javascripts/pghero/application.js +14 -0
  4. data/app/assets/stylesheets/pghero/application.css +101 -1
  5. data/app/controllers/pg_hero/home_controller.rb +3 -1
  6. data/app/helpers/pg_hero/home_helper.rb +1 -1
  7. data/app/views/layouts/pg_hero/application.html.erb +3 -3
  8. data/app/views/pg_hero/home/_connections_table.html.erb +1 -1
  9. data/app/views/pg_hero/home/_live_queries_table.html.erb +8 -8
  10. data/app/views/pg_hero/home/_queries_table.html.erb +5 -5
  11. data/app/views/pg_hero/home/_query_stats_slider.html.erb +2 -2
  12. data/app/views/pg_hero/home/_suggested_index.html.erb +6 -5
  13. data/app/views/pg_hero/home/connections.html.erb +6 -6
  14. data/app/views/pg_hero/home/explain.html.erb +2 -2
  15. data/app/views/pg_hero/home/index.html.erb +19 -19
  16. data/app/views/pg_hero/home/index_bloat.html.erb +6 -6
  17. data/app/views/pg_hero/home/maintenance.html.erb +3 -3
  18. data/app/views/pg_hero/home/queries.html.erb +4 -4
  19. data/app/views/pg_hero/home/relation_space.html.erb +3 -3
  20. data/app/views/pg_hero/home/show_query.html.erb +19 -19
  21. data/app/views/pg_hero/home/space.html.erb +11 -11
  22. data/app/views/pg_hero/home/system.html.erb +12 -12
  23. data/app/views/pg_hero/home/tune.html.erb +2 -2
  24. data/lib/pghero/database.rb +2 -2
  25. data/lib/pghero/engine.rb +2 -2
  26. data/lib/pghero/methods/basic.rb +22 -24
  27. data/lib/pghero/methods/explain.rb +1 -1
  28. data/lib/pghero/methods/query_stats.rb +1 -1
  29. data/lib/pghero/methods/suggested_indexes.rb +4 -2
  30. data/lib/pghero/methods/system.rb +2 -2
  31. data/lib/pghero/version.rb +1 -1
  32. data/lib/pghero.rb +3 -5
  33. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c69fbbfe2cc6c474e61c2350f2e2b70670b0c28c17146ed7393ae6e2ebba518
4
- data.tar.gz: 2496d325e1b0a32adcd301ba87bd272ae06e4f14a98537f36302f89f4d627fa1
3
+ metadata.gz: a7dc52d94fbdd96a3efcef37d4739855cb5e9ab0da76c53d345141c3a0f28021
4
+ data.tar.gz: 6e1640fb5ff339f5d0523d7acebb4655c0426b877a2a6545e53fb0af386769da
5
5
  SHA512:
6
- metadata.gz: 1fe1bef91fee759752b2217f3ffcf3f47eba8f5f05b21d5a9e2ad3e4955316c7d88c4b735219d917a7c776a0e71ca12653ed1e79d610d367df8241d7a5893779
7
- data.tar.gz: ecdbb066253e1d49a5f1ec8c987675b6c46ca7a139b6ca1b4a3110588cb22dd56101182a65c92b3727d64b9b61358c1d8014c9231a5326a311f40c6c15cea186
6
+ metadata.gz: d1ad10cfe918c2ab278af7aa96fcf5b08ccce3b6454f9630d7a628c9be0d54d0d3f5110d9ec4ab9248ee42556bba66166347412d284074820ed35069292ce72f
7
+ data.tar.gz: 4a7cc686fb1074c827a21e5b9ee8ce5a82119926b05e9ea8cdacd31759765932767df2638bd0790543e5f252507c97863c9572be7112be22244927396d93dce7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 3.6.1 (2024-10-14)
2
+
3
+ - Fixed error when Propshaft is installed but not used
4
+
5
+ ## 3.6.0 (2024-07-10)
6
+
7
+ - Improved CSP support
8
+ - Dropped support for Linux packages for Ubuntu 18.04, CentOS 7, and SLES 12
9
+ - Dropped support for Ruby < 3.1 and Rails < 6.1
10
+
1
11
  ## 3.5.0 (2024-05-21)
2
12
 
3
13
  - Added materialized views to space page and `relation_sizes` method
@@ -157,3 +157,17 @@ function initSlider() {
157
157
  refreshStats(false);
158
158
  });
159
159
  }
160
+
161
+ $(document).on("click", ".query-code", function () {
162
+ this.style.maxHeight = "none";
163
+ });
164
+
165
+ $(document).on("click", ".migration-link", function (e) {
166
+ e.preventDefault();
167
+ $(this).parent().next(".migration").css("display", "block");
168
+ });
169
+
170
+ $(document).on("click", ".show-details", function () {
171
+ $(this).nextAll(".details").css("display", "block");
172
+ $(this).css("display", "none");
173
+ });
@@ -483,12 +483,12 @@ body {
483
483
  line-height: 300px;
484
484
  text-align: center;
485
485
  color: #999;
486
+ margin-bottom: 20px;
486
487
  }
487
488
 
488
489
  .pie-chart {
489
490
  height: 260px;
490
491
  line-height: 260px;
491
- margin-bottom: 20px;
492
492
  }
493
493
 
494
494
  .unused-index {
@@ -503,8 +503,18 @@ body {
503
503
  font-weight: bold;
504
504
  }
505
505
 
506
+ .origins-table {
507
+ table-layout: auto;
508
+ }
509
+
506
510
  .origin {
507
511
  word-break: break-word;
512
+ width: 90%;
513
+ }
514
+
515
+ .origin-pct {
516
+ text-align: right;
517
+ width: 10%;
508
518
  }
509
519
 
510
520
  .duplicate-indexes td {
@@ -518,3 +528,93 @@ body {
518
528
  .no-outline:focus {
519
529
  outline: none;
520
530
  }
531
+
532
+ .width-10 {
533
+ width: 10%;
534
+ }
535
+
536
+ .width-15 {
537
+ width: 15%;
538
+ }
539
+
540
+ .width-20 {
541
+ width: 20%;
542
+ }
543
+
544
+ .width-25 {
545
+ width: 25%;
546
+ }
547
+
548
+ .width-33 {
549
+ width: 33.33%;
550
+ }
551
+
552
+ .right-5 {
553
+ margin-right: 5px;
554
+ }
555
+
556
+ .query-row {
557
+ border-top: none;
558
+ padding: 0;
559
+ }
560
+
561
+ .query-pre {
562
+ margin-top: 1em;
563
+ }
564
+
565
+ .query-code {
566
+ max-height: 230px;
567
+ overflow: hidden;
568
+ }
569
+
570
+ .break-all {
571
+ word-break: break-all;
572
+ }
573
+
574
+ .migration {
575
+ display: none;
576
+ }
577
+
578
+ .migration pre {
579
+ overflow: scroll;
580
+ white-space: pre;
581
+ word-break: normal;
582
+ }
583
+
584
+ .space-index {
585
+ font-style: italic;
586
+ }
587
+
588
+ a.relation-link {
589
+ color: inherit;
590
+ }
591
+
592
+ .push-left {
593
+ float: left;
594
+ }
595
+
596
+ .clear-both {
597
+ clear: both;
598
+ }
599
+
600
+ .create-index {
601
+ color: #eee;
602
+ background-color: #333;
603
+ }
604
+
605
+ .hide {
606
+ display: none;
607
+ }
608
+
609
+ .show-details {
610
+ float: right;
611
+ color: #f0ad4e;
612
+ margin-top: 0px;
613
+ padding: 10px;
614
+ cursor: pointer;
615
+ }
616
+
617
+ .details pre {
618
+ color: #f0ad4e;
619
+ background-color: #333;
620
+ }
@@ -166,6 +166,8 @@ module PgHero
166
166
 
167
167
  if !@historical_query_stats_enabled || request.xhr?
168
168
  set_suggested_indexes
169
+ else
170
+ @debug = params[:debug].present?
169
171
  end
170
172
 
171
173
  # fix back button issue with caching
@@ -274,7 +276,7 @@ module PgHero
274
276
 
275
277
  def free_space_stats
276
278
  render json: [
277
- {name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour), library: chart_library_options},
279
+ {name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour), library: chart_library_options}
278
280
  ]
279
281
  end
280
282
 
@@ -5,7 +5,7 @@ module PgHero
5
5
  if schema && schema != "public"
6
6
  ident = "#{schema}.#{table}"
7
7
  end
8
- if ident =~ /\A[a-z0-9_]+\z/
8
+ if /\A[a-z0-9_]+\z/.match?(ident)
9
9
  ident
10
10
  else
11
11
  @database.quote_ident(ident)
@@ -5,12 +5,12 @@
5
5
 
6
6
  <meta charset="utf-8" />
7
7
  <%= favicon_link_tag "pghero/favicon.png" %>
8
- <% if defined?(Propshaft::Railtie) %>
8
+ <% if defined?(Propshaft::Railtie) && Rails.application.assets.is_a?(Propshaft::Assembly) %>
9
9
  <%= stylesheet_link_tag "pghero/nouislider", "pghero/arduino-light", "pghero/application" %>
10
- <%= javascript_include_tag "pghero/jquery", "pghero/nouislider", "pghero/Chart.bundle", "pghero/chartkick", "pghero/highlight.min", "pghero/application" %>
10
+ <%= javascript_include_tag "pghero/jquery", "pghero/nouislider", "pghero/Chart.bundle", "pghero/chartkick", "pghero/highlight.min", "pghero/application", nonce: true %>
11
11
  <% else %>
12
12
  <%= stylesheet_link_tag "pghero/application" %>
13
- <%= javascript_include_tag "pghero/application" %>
13
+ <%= javascript_include_tag "pghero/application", nonce: true %>
14
14
  <% end %>
15
15
  </head>
16
16
  <body>
@@ -2,7 +2,7 @@
2
2
  <thead>
3
3
  <tr>
4
4
  <th>Top Sources</th>
5
- <th style="width: 20%;">Connections</th>
5
+ <th class="width-20">Connections</th>
6
6
  </tr>
7
7
  </thead>
8
8
  <tbody>
@@ -1,10 +1,10 @@
1
1
  <table class="table queries">
2
2
  <thead>
3
3
  <tr>
4
- <th style="width: 25%;">Pid</th>
5
- <th style="width: 25%;">Duration</th>
6
- <th style="width: 25%;">State</th>
7
- <th style="width: 25%;"></th>
4
+ <th class="width-25">Pid</th>
5
+ <th class="width-25">Duration</th>
6
+ <th class="width-25">State</th>
7
+ <th class="width-25"></th>
8
8
  </tr>
9
9
  </thead>
10
10
  <tbody>
@@ -39,15 +39,15 @@
39
39
  </td>
40
40
  </tr>
41
41
  <tr>
42
- <td colspan="6" style="border-top: none; padding: 0;">
42
+ <td colspan="4" class="query-row">
43
43
  <%= query[:source] %> <span class="text-muted"><%= query[:user] %></span>
44
- <pre style="margin-top: 1em;"><code><%= query[:query] %></code></pre>
44
+ <pre class="query-pre"><code><%= query[:query] %></code></pre>
45
45
  </td>
46
46
  </tr>
47
47
  <% end %>
48
48
  </tbody>
49
49
  </table>
50
50
 
51
- <script>
51
+ <%= javascript_tag nonce: true do %>
52
52
  highlightQueries();
53
- </script>
53
+ <% end %>
@@ -2,9 +2,9 @@
2
2
  <% unless local_assigns[:xhr] %>
3
3
  <thead>
4
4
  <tr>
5
- <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
6
- <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
7
- <th style="width: 33.33%;"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
5
+ <th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Total Time", {sort: nil}, data: {sort: nil}) : "Total Time" %></th>
6
+ <th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Average Time", {sort: "average_time"}, data: {sort: "average_time"}) : "Average Time" %></th>
7
+ <th class="width-33"><%= local_assigns[:sort_headers] ? link_to("Calls", {sort: "calls"}, data: {sort: "calls"}) : "Calls" %></th>
8
8
  </tr>
9
9
  </thead>
10
10
  <% end %>
@@ -57,8 +57,8 @@
57
57
  </td>
58
58
  </tr>
59
59
  <tr>
60
- <td colspan="3" style="border-top: none; padding: 0;">
61
- <pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= query[:query] %></code></pre>
60
+ <td colspan="3" class="query-row">
61
+ <pre><code class="query-code"><%= query[:query] %></code></pre>
62
62
  <% if query[:query] == "<insufficient privilege>" %>
63
63
  <p class="text-muted">For security reasons, only superusers can see queries executed by other users.</p>
64
64
  <% end %>
@@ -4,7 +4,7 @@
4
4
  <div id="range-start"></div>
5
5
  </div>
6
6
 
7
- <script>
7
+ <%= javascript_tag nonce: true do %>
8
8
  var sort = <%= pghero_js_value(@sort) %>;
9
9
  var minAverageTime = <%= pghero_js_value(@min_average_time) %>;
10
10
  var minCalls = <%= pghero_js_value(@min_calls) %>;
@@ -13,4 +13,4 @@
13
13
  var endAt = <%= pghero_js_value(@end_at.to_i * 1000) %>;
14
14
 
15
15
  initSlider();
16
- </script>
16
+ <% end %>
@@ -1,13 +1,14 @@
1
1
  <% if index && !details[:covering_index] %>
2
2
  <% unless @debug %>
3
- <div style="float: right; color: #f0ad4e; margin-top: 0px; padding: 10px; cursor: pointer;" onclick="document.getElementById('details-<%= index.object_id %>').style.display = 'block'; this.style.display = 'none';">Details</div>
3
+ <div class="show-details">Details</div>
4
4
  <% end %>
5
- <code><pre style="color: #eee; background-color: #333;">CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)</pre></code>
5
+ <code><pre class="create-index">CREATE INDEX CONCURRENTLY ON <%= index[:table] %><% if index[:using] %> USING <%= index[:using] %><% end %> (<%= index[:columns].join(", ") %>)</pre></code>
6
6
  <% end %>
7
- <div id="details-<%= index.object_id %>" style="<%= "display: none;" unless @debug %>">
8
- <code><pre style="color: #f0ad4e; background-color: #333;"><% if details[:explanation] %><%= details[:explanation] %>
7
+ <div class="details<%= " hide" unless @debug %>">
8
+ <code><pre><% if details[:explanation] %><%= details[:explanation] %>
9
9
  <% end %><% if details[:row_estimates] %>Rows: <%= details[:rows] %>
10
- Row progression: <%= details[:row_progression].to_a.join(", ") %>
10
+ <% if details[:row_progression] %>Row progression: <%= details[:row_progression].to_a.join(", ") %>
11
+ <% end %>
11
12
 
12
13
  Row estimates
13
14
  <%= details[:row_estimates].to_a.map { |k, v| "- #{k}: #{v}" }.join("\n") %><% end %><% if details[:table_indexes] %>
@@ -7,24 +7,24 @@
7
7
  <h3>By Database</h3>
8
8
 
9
9
  <div id="chart-1" class="chart pie-chart">Loading...</div>
10
- <script>
10
+ <%= javascript_tag nonce: true do %>
11
11
  new Chartkick.PieChart("chart-1", <%= pghero_js_value(@connections_by_database) %>);
12
- </script>
12
+ <% end %>
13
13
 
14
14
  <h3>By User</h3>
15
15
 
16
16
  <div id="chart-2" class="chart pie-chart">Loading...</div>
17
- <script>
17
+ <%= javascript_tag nonce: true do %>
18
18
  new Chartkick.PieChart("chart-2", <%= pghero_js_value(@connections_by_user) %>);
19
- </script>
19
+ <% end %>
20
20
 
21
21
  <% if @connections_by_ssl_status %>
22
22
  <h3>By Security</h3>
23
23
 
24
24
  <div id="chart-3" class="chart pie-chart">Loading...</div>
25
- <script>
25
+ <%= javascript_tag nonce: true do %>
26
26
  new Chartkick.PieChart("chart-3", <%= pghero_js_value(@connections_by_ssl_status) %>);
27
- </script>
27
+ <% end %>
28
28
  <% end %>
29
29
 
30
30
  <%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
@@ -4,9 +4,9 @@
4
4
  <%= form_tag explain_path do %>
5
5
  <div class="field"><%= text_area_tag :query, @query, placeholder: "Enter a SQL query" %></div>
6
6
  <p>
7
- <%= submit_tag "Explain", class: "btn btn-info", style: "margin-right: 10px;" %>
7
+ <%= submit_tag "Explain", class: "btn btn-info right-5" %>
8
8
  <% if @explain_analyze_enabled %>
9
- <%= submit_tag "Analyze", class: "btn btn-danger", style: "margin-right: 10px;" %>
9
+ <%= submit_tag "Analyze", class: "btn btn-danger right-5" %>
10
10
  <% end %>
11
11
  <%= submit_tag "Visualize", class: "btn btn-danger" %>
12
12
  </p>
@@ -127,7 +127,7 @@
127
127
  <table class="table">
128
128
  <thead>
129
129
  <tr>
130
- <th style="width: 33%;">Column</th>
130
+ <th class="width-33">Column</th>
131
131
  <th>Sequence</th>
132
132
  </tr>
133
133
  </thead>
@@ -251,7 +251,7 @@
251
251
  <thead>
252
252
  <tr>
253
253
  <th>Table</th>
254
- <th style="width: 20%;">Transactions Left</th>
254
+ <th class="width-20">Transactions Left</th>
255
255
  </tr>
256
256
  </thead>
257
257
  <tbody>
@@ -279,9 +279,9 @@
279
279
  <thead>
280
280
  <tr>
281
281
  <th>Column</th>
282
- <th style="width: 20%;">Type</th>
283
- <th style="width: 20%;">Values Left</th>
284
- <th style="width: 10%;">% Left</th>
282
+ <th class="width-20">Type</th>
283
+ <th class="width-20">Values Left</th>
284
+ <th class="width-10">% Left</th>
285
285
  </tr>
286
286
  </thead>
287
287
  <tbody>
@@ -332,7 +332,7 @@
332
332
  </td>
333
333
  </tr>
334
334
  <tr>
335
- <td style="border-top: none; padding: 0;">
335
+ <td class="query-row">
336
336
  <pre><code>DROP INDEX CONCURRENTLY <%= pghero_pretty_ident(index[:name], schema: index[:schema]) %>;
337
337
  <%= index[:definition].sub("CREATE INDEX ", "CREATE INDEX CONCURRENTLY ") %>;</code></pre>
338
338
  </td>
@@ -366,7 +366,7 @@
366
366
  </td>
367
367
  </tr>
368
368
  <tr>
369
- <td style="border-top: none; padding: 0;">
369
+ <td class="query-row">
370
370
  <pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
371
371
  </td>
372
372
  </tr>
@@ -383,15 +383,15 @@
383
383
  <p>
384
384
  These indexes exist, but aren’t needed. Remove them
385
385
  <% if @show_migrations %>
386
- <a href="javascript: void(0);" onclick="document.getElementById('migration2').style.display = 'block';">with a migration</a>
386
+ <a href="#" class="migration-link">with a migration</a>
387
387
  <% end %>
388
388
  for faster writes.
389
389
  </p>
390
390
 
391
- <div id="migration2" style="display: none;">
391
+ <div class="migration">
392
392
  <pre>rails generate migration remove_unneeded_indexes</pre>
393
393
  <p>And paste</p>
394
- <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
394
+ <pre><% @duplicate_indexes.each do |query| %>
395
395
  <%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
396
396
  </div>
397
397
 
@@ -426,14 +426,14 @@
426
426
  Add indexes to speed up queries.
427
427
  <% if @show_migrations %>
428
428
  Here’s a
429
- <a href="javascript: void(0);" onclick="document.getElementById('migration3').style.display = 'block';">migration</a> to help.
429
+ <a href="#" class="migration-link">migration</a> to help.
430
430
  <% end %>
431
431
  </p>
432
432
 
433
- <div id="migration3" style="display: none;">
433
+ <div class="migration">
434
434
  <pre>rails generate migration add_suggested_indexes</pre>
435
435
  <p>And paste</p>
436
- <pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
436
+ <pre>commit_db_transaction
437
437
  <% @suggested_indexes.each do |index| %>
438
438
  <% if index[:using] && index[:using] != "btree" %>
439
439
  add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
@@ -487,15 +487,15 @@ pg_stat_statements.track = all</pre>
487
487
  <p>
488
488
  Unused indexes cause unnecessary overhead. Remove them
489
489
  <% if @show_migrations %>
490
- <a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
490
+ <a href="#" class="migration-link">with a migration</a>
491
491
  <% end %>
492
492
  for faster writes.
493
493
  </p>
494
494
 
495
- <div id="migration" style="display: none;">
495
+ <div class="migration">
496
496
  <pre>rails generate migration remove_unused_indexes</pre>
497
497
  <p>And paste</p>
498
- <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
498
+ <pre><% @unused_indexes.each do |query| %>
499
499
  <%= pghero_remove_index(query)%><% end %></pre>
500
500
  </div>
501
501
 
@@ -503,7 +503,7 @@ pg_stat_statements.track = all</pre>
503
503
  <thead>
504
504
  <tr>
505
505
  <th>Name</th>
506
- <th style="width: 20%;">Index Size</th>
506
+ <th class="width-20">Index Size</th>
507
507
  </tr>
508
508
  </thead>
509
509
  <tbody>
@@ -518,6 +518,6 @@ pg_stat_statements.track = all</pre>
518
518
  </div>
519
519
  <% end %>
520
520
 
521
- <script>
521
+ <%= javascript_tag nonce: true do %>
522
522
  highlightQueries();
523
- </script>
523
+ <% end %>
@@ -25,15 +25,15 @@ ALTER INDEX new_index RENAME TO index;</code></pre>
25
25
  <thead>
26
26
  <tr>
27
27
  <th>Index</th>
28
- <th style="width: 15%;">Bloat</th>
29
- <th style="width: 15%;">Size</th>
28
+ <th class="width-15">Bloat</th>
29
+ <th class="width-15">Size</th>
30
30
  </tr>
31
31
  </thead>
32
32
  <tbody>
33
33
  <% @index_bloat.each do |index| %>
34
34
  <tr>
35
35
  <td>
36
- <span style="word-break: break-all;"><%= index[:index] %></span>
36
+ <span class="break-all"><%= index[:index] %></span>
37
37
  <% if index[:primary] %>
38
38
  <span class="primary-key">PRIMARY</span>
39
39
  <% end %>
@@ -43,7 +43,7 @@ ALTER INDEX new_index RENAME TO index;</code></pre>
43
43
  </tr>
44
44
  <% if @show_sql && !index[:primary] %>
45
45
  <tr>
46
- <td colspan="3" style="border-top: none; padding: 0;">
46
+ <td colspan="3" class="query-row">
47
47
  <% new_index = "new_#{index[:index]}".first(63) %>
48
48
  <pre><code><%= index[:definition].sub(" INDEX ", " INDEX CONCURRENTLY \n ").sub(index[:index], new_index) %>;
49
49
 
@@ -67,6 +67,6 @@ ALTER INDEX <%= pghero_pretty_ident(new_index) %>
67
67
  <% end %>
68
68
  </div>
69
69
 
70
- <script>
70
+ <%= javascript_tag nonce: true do %>
71
71
  highlightQueries();
72
- </script>
72
+ <% end %>
@@ -5,10 +5,10 @@
5
5
  <thead>
6
6
  <tr>
7
7
  <th>Table</th>
8
- <th style="width: 20%;">Last Vacuum</th>
9
- <th style="width: 20%;">Last Analyze</th>
8
+ <th class="width-20">Last Vacuum</th>
9
+ <th class="width-20">Last Analyze</th>
10
10
  <% if @show_dead_rows %>
11
- <th style="width: 20%;">Dead Rows</th>
11
+ <th class="width-20">Dead Rows</th>
12
12
  <% end %>
13
13
  </tr>
14
14
  </thead>
@@ -4,13 +4,13 @@
4
4
  <% end %>
5
5
 
6
6
  <% if !@historical_query_stats_enabled %>
7
- <h1 style="float: left;">Queries</h1>
7
+ <h1 class="push-left">Queries</h1>
8
8
  <% end %>
9
9
 
10
10
  <% if @historical_query_stats_enabled %>
11
11
  <%= render partial: "query_stats_slider" %>
12
12
  <% elsif @database.query_stats_table_exists? && (columns = @database.missing_query_stats_columns).any? %>
13
- <div style="clear: both;">
13
+ <div class="clear-both">
14
14
  <p>Add missing columns to re-enable historical query stats.</p>
15
15
  <pre><code><% @database.missing_query_stats_columns.each do |column| %>ALTER TABLE pghero_query_stats ADD COLUMN "<%= column %>" <%= column == "query_hash" ? "bigint" : "text" %>;
16
16
  <% end %></code></pre>
@@ -23,9 +23,9 @@
23
23
  <div class="alert alert-danger">Cannot understand start or end time.</div>
24
24
  <% elsif @query_stats.any? || @historical_query_stats_enabled %>
25
25
  <%= render partial: "queries_table", locals: {queries: @query_stats, sort_headers: true} %>
26
- <script>
26
+ <%= javascript_tag nonce: true do %>
27
27
  highlightQueries();
28
- </script>
28
+ <% end %>
29
29
  <% else %>
30
30
  <p>Stats are not available yet. Come back soon!</p>
31
31
  <% end %>
@@ -7,8 +7,8 @@
7
7
  </h1>
8
8
 
9
9
  <h1>Size</h1>
10
- <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
- <script>
10
+ <div id="chart-1" class="chart">Loading...</div>
11
+ <%= javascript_tag nonce: true do %>
12
12
  new Chartkick.LineChart("chart-1", <%= pghero_js_value(@chart_data) %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
13
- </script>
13
+ <% end %>
14
14
  </div>
@@ -1,8 +1,8 @@
1
1
  <div class="content">
2
- <pre><code style="max-height: 230px; overflow: hidden;" onclick="this.style.maxHeight = 'none';"><%= @query %></code></pre>
3
- <script>
2
+ <pre><code class="query-code"><%= @query %></code></pre>
3
+ <%= javascript_tag nonce: true do %>
4
4
  highlightQueries()
5
- </script>
5
+ <% end %>
6
6
 
7
7
  <% if @explain_enabled && @explainable_query %>
8
8
  <p>
@@ -11,11 +11,11 @@
11
11
  <% end %>
12
12
 
13
13
  <% if @origins && @origins.keys.select { |k| k.length > 0 }.any? %>
14
- <table style="table-layout: auto;">
14
+ <table class="origins-table">
15
15
  <thead>
16
16
  <tr>
17
17
  <th colspan="2">
18
- <div style="float: right;">Approx. Time</div>
18
+ <div class="push-right">Approx. Time</div>
19
19
  Origin
20
20
  </th>
21
21
  </tr>
@@ -23,14 +23,14 @@
23
23
  <tbody>
24
24
  <% @origins.sort_by { |o, c| [-c, o.to_s] }.each do |origin, count| %>
25
25
  <tr>
26
- <td class="origin" style="width: 90%;">
26
+ <td class="origin">
27
27
  <% if origin.length > 0 %>
28
28
  <%= origin %>
29
29
  <% else %>
30
30
  <span class="text-muted">Unknown</span>
31
31
  <% end %>
32
32
  </td>
33
- <td style="text-align: right; width: 10%;">
33
+ <td class="origin-pct">
34
34
  <% pct = (100.0 * count / @total_count).round %>
35
35
  <% if pct == 0 %>
36
36
  &lt; 1%
@@ -47,22 +47,22 @@
47
47
  <!-- chart -->
48
48
  <% if @chart_data %>
49
49
  <h1>Total Time <small>ms</small></h1>
50
- <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
51
- <script>
50
+ <div id="chart-1" class="chart">Loading...</div>
51
+ <%= javascript_tag nonce: true do %>
52
52
  new Chartkick.LineChart("chart-1", <%= pghero_js_value(@chart_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
53
- </script>
53
+ <% end %>
54
54
 
55
55
  <h1>Average Time <small>ms</small></h1>
56
- <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
57
- <script>
56
+ <div id="chart-2" class="chart">Loading...</div>
57
+ <%= javascript_tag nonce: true do %>
58
58
  new Chartkick.LineChart("chart-2", <%= pghero_js_value(@chart2_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
59
- </script>
59
+ <% end %>
60
60
 
61
61
  <h1>Calls</h1>
62
- <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
63
- <script>
62
+ <div id="chart-3" class="chart">Loading...</div>
63
+ <%= javascript_tag nonce: true do %>
64
64
  new Chartkick.LineChart("chart-3", <%= pghero_js_value(@chart3_data) %>, {colors: ["#5bc0de"], legend: false, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
65
- </script>
65
+ <% end %>
66
66
  <% else %>
67
67
  <p>
68
68
  Enable
@@ -77,8 +77,8 @@
77
77
  <table>
78
78
  <thead>
79
79
  <tr>
80
- <th style="width: 25%;">Name</th>
81
- <th style="width: 25%;">Rows</th>
80
+ <th class="width-25">Name</th>
81
+ <th class="width-25">Rows</th>
82
82
  <th>Indexes</th>
83
83
  </tr>
84
84
  </thead>
@@ -86,7 +86,7 @@
86
86
  <% @tables.each do |table| %>
87
87
  <tr>
88
88
  <td><%= table %></td>
89
- <td><%= @row_counts[table] %></td>
89
+ <td><%= number_with_delimiter(@row_counts[table]) if @row_counts[table] %></td>
90
90
  <td>
91
91
  <% if @indexes_timeout %>
92
92
  Not available
@@ -4,10 +4,10 @@
4
4
  <p>Database Size: <%= @database_size %></p>
5
5
 
6
6
  <% if @system_stats_enabled %>
7
- <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
8
- <script>
7
+ <div id="chart-1" class="chart">Loading...</div>
8
+ <%= javascript_tag nonce: true do %>
9
9
  new Chartkick.LineChart("chart-1", <%= pghero_js_value(free_space_stats_path) %>, {colors: ["#5bc0de"], bytes: true, library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
10
- </script>
10
+ <% end %>
11
11
  <% end %>
12
12
 
13
13
  <!--
@@ -20,7 +20,7 @@
20
20
  <p>
21
21
  <%= pluralize(@unused_indexes.size, "unused index") %>. Remove them
22
22
  <% if @show_migrations %>
23
- <a href="javascript: void(0);" onclick="document.getElementById('migration').style.display = 'block';">with a migration</a>
23
+ <a href="#" class="migration-link">with a migration</a>
24
24
  <% end %>
25
25
  for faster writes.
26
26
 
@@ -29,10 +29,10 @@
29
29
  <% end %>
30
30
  </p>
31
31
 
32
- <div id="migration" style="display: none;">
32
+ <div class="migration">
33
33
  <pre>rails generate migration remove_unused_indexes</pre>
34
34
  <p>And paste</p>
35
- <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
35
+ <pre><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
36
36
  <%= pghero_remove_index(query) %><% end %></pre>
37
37
  </div>
38
38
  <% end %>
@@ -44,20 +44,20 @@
44
44
  <thead>
45
45
  <tr>
46
46
  <th><%= link_to (@only_tables ? "Table" : "Relation"), @header_options.merge(sort: "name") %></th>
47
- <th style="width: 15%;"><%= link_to "Size", @header_options %></th>
47
+ <th class="width-15"><%= link_to "Size", @header_options %></th>
48
48
  <% if @space_stats_enabled %>
49
- <th style="width: 15%;"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
49
+ <th class="width-15"><%= link_to "#{@days}d Growth", @header_options.merge(sort: "growth") %></th>
50
50
  <% end %>
51
51
  </tr>
52
52
  </thead>
53
53
  <tbody>
54
54
  <% @relation_sizes.each do |query| %>
55
55
  <tr>
56
- <td style="<%= query[:type] == "index" ? "font-style: italic;" : "" %>">
57
- <span style="word-break: break-all;">
56
+ <td class="<%= query[:type] == "index" ? "space-index" : "" %>">
57
+ <span class="break-all">
58
58
  <% name = query[:relation] || query[:table] %>
59
59
  <% if @space_stats_enabled %>
60
- <%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank", style: "color: inherit;" %>
60
+ <%= link_to name, relation_space_path(name, schema: query[:schema]), target: "_blank", class: "relation-link" %>
61
61
  <% else %>
62
62
  <%= name %>
63
63
  <% end %>
@@ -7,28 +7,28 @@
7
7
  <% path_options = {params: {duration: @duration, period: @period}} %>
8
8
 
9
9
  <h1>CPU</h1>
10
- <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
- <script>
10
+ <div id="chart-1" class="chart">Loading...</div>
11
+ <%= javascript_tag nonce: true do %>
12
12
  new Chartkick.LineChart("chart-1", <%= pghero_js_value(cpu_usage_path(path_options)) %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
13
- </script>
13
+ <% end %>
14
14
 
15
15
  <h1>Load</h1>
16
- <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
17
- <script>
16
+ <div id="chart-2" class="chart">Loading...</div>
17
+ <%= javascript_tag nonce: true do %>
18
18
  new Chartkick.LineChart("chart-2", <%= pghero_js_value(load_stats_path(path_options)) %>, {colors: ["#5bc0de", "#d9534f"], library: {plugins: {tooltip: {intersect: false, mode: "nearest"}}}})
19
- </script>
19
+ <% end %>
20
20
 
21
21
  <h1>Connections</h1>
22
- <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
23
- <script>
22
+ <div id="chart-3" class="chart">Loading...</div>
23
+ <%= javascript_tag nonce: true do %>
24
24
  new Chartkick.LineChart("chart-3", <%= pghero_js_value(connection_stats_path(path_options)) %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
25
- </script>
25
+ <% end %>
26
26
 
27
27
  <% if @database.replica? %>
28
28
  <h1>Replication Lag</h1>
29
- <div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
30
- <script>
29
+ <div id="chart-4" class="chart">Loading...</div>
30
+ <%= javascript_tag nonce: true do %>
31
31
  new Chartkick.LineChart("chart-4", <%= pghero_js_value(replication_lag_stats_path(path_options)) %>, {colors: ["#5bc0de"], library: {plugins: {tooltip: {intersect: false, mode: "index"}}}})
32
- </script>
32
+ <% end %>
33
33
  <% end %>
34
34
  </div>
@@ -5,7 +5,7 @@
5
5
  <thead>
6
6
  <tr>
7
7
  <th>Setting</th>
8
- <th style="width: 20%;">Value</th>
8
+ <th class="width-20">Value</th>
9
9
  </tr>
10
10
  </thead>
11
11
  <tbody>
@@ -30,7 +30,7 @@
30
30
  <thead>
31
31
  <tr>
32
32
  <th>Setting</th>
33
- <th style="width: 20%;">Value</th>
33
+ <th class="width-20">Value</th>
34
34
  </tr>
35
35
  </thead>
36
36
  <tbody>
@@ -126,7 +126,7 @@ module PgHero
126
126
  # rough check for Postgres adapter
127
127
  # keep this message generic so it's useful
128
128
  # when empty url set in Docker image pghero.yml
129
- unless @connection_model.connection.adapter_name =~ /postg/i
129
+ unless @connection_model.connection_db_config.adapter.to_s.match?(/postg/i)
130
130
  raise Error, "Invalid connection URL"
131
131
  end
132
132
  @adapter_checked = true
@@ -145,7 +145,7 @@ module PgHero
145
145
  config_options = {env_name: PgHero.env, PgHero.spec_name_key => config["spec"], PgHero.include_replicas_key => true}
146
146
  resolved = ActiveRecord::Base.configurations.configs_for(**config_options)
147
147
  raise Error, "Spec not found: #{config["spec"]}" unless resolved
148
- url = ActiveRecord::VERSION::STRING.to_f >= 6.1 ? resolved.configuration_hash : resolved.config
148
+ url = resolved.configuration_hash
149
149
  end
150
150
 
151
151
  url = url.dup
data/lib/pghero/engine.rb CHANGED
@@ -4,8 +4,8 @@ module PgHero
4
4
 
5
5
  initializer "pghero", group: :all do |app|
6
6
  # check if Rails api mode
7
- if app.config.respond_to?(:assets)
8
- if defined?(Sprockets) && Sprockets::VERSION.to_i >= 4
7
+ if app.config.respond_to?(:assets) && defined?(Sprockets)
8
+ if Sprockets::VERSION.to_i >= 4
9
9
  app.config.assets.precompile << "pghero/application.js"
10
10
  app.config.assets.precompile << "pghero/application.css"
11
11
  app.config.assets.precompile << "pghero/favicon.png"
@@ -31,21 +31,26 @@ module PgHero
31
31
  end
32
32
 
33
33
  def quote_ident(value)
34
- connection.quote_column_name(value)
34
+ with_connection { |c| c.quote_column_name(value) }
35
35
  end
36
36
 
37
37
  private
38
38
 
39
- def select_all(sql, conn: nil, query_columns: [])
40
- conn ||= connection
39
+ def select_all(sql, stats: false, query_columns: [])
40
+ with_connection(stats: stats) do |conn|
41
+ select_all_leased(sql, conn: conn, query_columns: query_columns)
42
+ end
43
+ end
44
+
45
+ def select_all_leased(sql, conn:, query_columns:)
41
46
  # squish for logs
42
47
  retries = 0
43
48
  begin
44
49
  result = conn.select_all(add_source(squish(sql)))
45
- if ActiveRecord::VERSION::STRING.to_f >= 6.1
46
- result = result.map(&:symbolize_keys)
50
+ if ActiveRecord::VERSION::MAJOR >= 8
51
+ result = result.to_a.map(&:symbolize_keys)
47
52
  else
48
- result = result.map { |row| row.to_h { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] } }
53
+ result = result.map(&:symbolize_keys)
49
54
  end
50
55
  if filter_data
51
56
  query_columns.each do |column|
@@ -81,7 +86,7 @@ module PgHero
81
86
  end
82
87
 
83
88
  def select_all_stats(sql, **options)
84
- select_all(sql, **options, conn: stats_connection)
89
+ select_all(sql, **options, stats: true)
85
90
  end
86
91
 
87
92
  def select_all_size(sql)
@@ -92,24 +97,17 @@ module PgHero
92
97
  result
93
98
  end
94
99
 
95
- def select_one(sql, conn: nil)
96
- select_all(sql, conn: conn).first.values.first
97
- end
98
-
99
- def select_one_stats(sql)
100
- select_one(sql, conn: stats_connection)
100
+ def select_one(sql)
101
+ select_all(sql).first.values.first
101
102
  end
102
103
 
103
104
  def execute(sql)
104
- connection.execute(add_source(sql))
105
- end
106
-
107
- def connection
108
- connection_model.connection
105
+ with_connection { |c| c.execute(add_source(sql)) }
109
106
  end
110
107
 
111
- def stats_connection
112
- ::PgHero::Stats.connection
108
+ def with_connection(stats: false, &block)
109
+ model = stats ? ::PgHero::Stats : connection_model
110
+ model.connection_pool.with_connection(&block)
113
111
  end
114
112
 
115
113
  def squish(str)
@@ -121,15 +119,15 @@ module PgHero
121
119
  end
122
120
 
123
121
  def quote(value)
124
- connection.quote(value)
122
+ with_connection { |c| c.quote(value) }
125
123
  end
126
124
 
127
125
  def quote_table_name(value)
128
- connection.quote_table_name(value)
126
+ with_connection { |c| c.quote_table_name(value) }
129
127
  end
130
128
 
131
129
  def quote_column_name(value)
132
- connection.quote_column_name(value)
130
+ with_connection { |c| c.quote_column_name(value) }
133
131
  end
134
132
 
135
133
  def unquote(part)
@@ -150,7 +148,7 @@ module PgHero
150
148
  end
151
149
 
152
150
  def table_exists?(table)
153
- stats_connection.table_exists?(table)
151
+ with_connection(stats: true) { |c| c.table_exists?(table) }
154
152
  end
155
153
  end
156
154
  end
@@ -9,7 +9,7 @@ module PgHero
9
9
 
10
10
  # use transaction for safety
11
11
  with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do
12
- if (sql.sub(/;\z/, "").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
12
+ if (sql.delete_suffix(";").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe?
13
13
  raise ActiveRecord::StatementInvalid, "Unsafe statement"
14
14
  end
15
15
  explanation = execute("EXPLAIN #{sql}").map { |v| v["QUERY PLAN"] }.join("\n")
@@ -322,7 +322,7 @@ module PgHero
322
322
  all_queries_total_minutes: stats2.sum { |s| s[:all_queries_total_minutes] }
323
323
  }
324
324
  value[:total_percent] = value[:total_minutes] * 100.0 / value[:all_queries_total_minutes]
325
- value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.select { |q| q && explainable?(q) }.first
325
+ value[:explainable_query] = stats2.map { |s| s[:explainable_query] }.find { |q| q && explainable?(q) }
326
326
  query_stats << value
327
327
  end
328
328
  query_stats
@@ -46,7 +46,7 @@ module PgHero
46
46
  indexes += existing_columns["brin"][index[:table]]
47
47
  end
48
48
 
49
- covering_index = indexes.find { |e| index_covers?(e.map { |v| v.sub(/ inet_ops\z/, "") }, index[:columns]) }
49
+ covering_index = indexes.find { |e| index_covers?(e.map { |v| v.delete_suffix(" inet_ops") }, index[:columns]) }
50
50
  if covering_index
51
51
  best_index[:covering_index] = covering_index
52
52
  best_index[:explanation] = "Covered by index on (#{covering_index.join(", ")})"
@@ -79,7 +79,9 @@ module PgHero
79
79
  suggested_indexes.each do |index|
80
80
  p index
81
81
  if create
82
- connection.execute("CREATE INDEX CONCURRENTLY ON #{quote_table_name(index[:table])} (#{index[:columns].map { |c| quote_column_name(c) }.join(",")})")
82
+ with_connection do |connection|
83
+ connection.execute("CREATE INDEX CONCURRENTLY ON #{quote_table_name(index[:table])} (#{index[:columns].map { |c| quote_column_name(c) }.join(",")})")
84
+ end
83
85
  end
84
86
  end
85
87
  end
@@ -136,8 +136,8 @@ module PgHero
136
136
  start_time = end_time - duration
137
137
 
138
138
  # validate input since we need to interpolate below
139
- raise Error, "Invalid metric name" unless metric_name =~ /\A[a-z\/_]+\z/i
140
- raise Error, "Invalid database id" unless gcp_database_id =~ /\A[a-z0-9\-:]+\z/i
139
+ raise Error, "Invalid metric name" unless /\A[a-z\/_]+\z/i.match?(metric_name)
140
+ raise Error, "Invalid database id" unless /\A[a-z0-9\-:]+\z/i.match?(gcp_database_id)
141
141
 
142
142
  # we handle four situations:
143
143
  # 1. google-cloud-monitoring-v3
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "3.5.0"
2
+ VERSION = "3.6.1"
3
3
  end
data/lib/pghero.rb CHANGED
@@ -64,7 +64,7 @@ module PgHero
64
64
  :query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
65
65
  :rds_stats, :read_iops_stats, :aws_region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
66
66
  :reset_query_stats, :reset_stats, :running_queries, :aws_secret_access_key, :sequence_danger, :sequences, :settings,
67
- :slow_queries, :space_growth, :ssl_used?, :stats_connection, :suggested_indexes, :suggested_indexes_by_query,
67
+ :slow_queries, :space_growth, :ssl_used?, :suggested_indexes, :suggested_indexes_by_query,
68
68
  :suggested_indexes_enabled?, :system_stats_enabled?, :table_caching, :table_hit_rate, :table_stats,
69
69
  :total_connections, :transaction_id_danger, :unused_indexes, :unused_tables, :write_iops_stats
70
70
 
@@ -243,14 +243,12 @@ module PgHero
243
243
 
244
244
  # private
245
245
  def connection_config(model)
246
- ActiveRecord::VERSION::STRING.to_f >= 6.1 ? model.connection_db_config.configuration_hash : model.connection_config
246
+ model.connection_db_config.configuration_hash
247
247
  end
248
248
 
249
249
  # private
250
- # Rails 6.1 deprecates `spec_name` for `name`
251
- # https://github.com/rails/rails/pull/38536
252
250
  def spec_name_key
253
- ActiveRecord::VERSION::STRING.to_f >= 6.1 ? :name : :spec_name
251
+ :name
254
252
  end
255
253
 
256
254
  # private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pghero
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-21 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6'
26
+ version: '6.1'
27
27
  description:
28
28
  email: andrew@ankane.org
29
29
  executables: []
@@ -117,14 +117,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
117
  requirements:
118
118
  - - ">="
119
119
  - !ruby/object:Gem::Version
120
- version: '2.7'
120
+ version: '3.1'
121
121
  required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  requirements: []
127
- rubygems_version: 3.5.9
127
+ rubygems_version: 3.5.16
128
128
  signing_key:
129
129
  specification_version: 4
130
130
  summary: A performance dashboard for Postgres