pghero 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -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 +2 -2
  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/methods/basic.rb +3 -3
  26. data/lib/pghero/methods/explain.rb +1 -1
  27. data/lib/pghero/methods/query_stats.rb +1 -1
  28. data/lib/pghero/methods/suggested_indexes.rb +1 -1
  29. data/lib/pghero/methods/system.rb +2 -2
  30. data/lib/pghero/version.rb +1 -1
  31. data/lib/pghero.rb +2 -4
  32. 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: 725af86da804f9f7869d727483cdc077de020c77cf5d364abecb943db6b2e7e9
4
+ data.tar.gz: 0e15c553a23d7288711f1b16e619ffff6cc38bbb26a1539477ed6f7eb11e2e6b
5
5
  SHA512:
6
- metadata.gz: 1fe1bef91fee759752b2217f3ffcf3f47eba8f5f05b21d5a9e2ad3e4955316c7d88c4b735219d917a7c776a0e71ca12653ed1e79d610d367df8241d7a5893779
7
- data.tar.gz: ecdbb066253e1d49a5f1ec8c987675b6c46ca7a139b6ca1b4a3110588cb22dd56101182a65c92b3727d64b9b61358c1d8014c9231a5326a311f40c6c15cea186
6
+ metadata.gz: 435b20217db356ac5d28450d92a5f96d44fef1ea3a2b909f8d9c713393f3dee3f9fea9e2be6737320280d1663dbab20fb19165d01b70d4842ea6f32978bb0465
7
+ data.tar.gz: edab3df03d6a47681f9fff134d0fc5fa070fa5262d74092948aa76ade0dbba2977ef885120894ec0cb4a1e007b46454373c529239cb4fc1f002b2d8702dba17e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 3.6.0 (2024-07-10)
2
+
3
+ - Improved CSP support
4
+ - Dropped support for Ruby < 3.1 and Rails < 6.1
5
+
1
6
  ## 3.5.0 (2024-05-21)
2
7
 
3
8
  - 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)
@@ -7,10 +7,10 @@
7
7
  <%= favicon_link_tag "pghero/favicon.png" %>
8
8
  <% if defined?(Propshaft::Railtie) %>
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.adapter_name.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
@@ -42,10 +42,10 @@ module PgHero
42
42
  retries = 0
43
43
  begin
44
44
  result = conn.select_all(add_source(squish(sql)))
45
- if ActiveRecord::VERSION::STRING.to_f >= 6.1
46
- result = result.map(&:symbolize_keys)
45
+ if ActiveRecord::VERSION::MAJOR >= 8
46
+ result = result.to_a.map(&:symbolize_keys)
47
47
  else
48
- result = result.map { |row| row.to_h { |col, val| [col.to_sym, result.column_types[col].send(:cast_value, val)] } }
48
+ result = result.map(&:symbolize_keys)
49
49
  end
50
50
  if filter_data
51
51
  query_columns.each do |column|
@@ -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(", ")})"
@@ -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.0"
3
3
  end
data/lib/pghero.rb CHANGED
@@ -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.0
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-07-11 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.11
128
128
  signing_key:
129
129
  specification_version: 4
130
130
  summary: A performance dashboard for Postgres