pghero 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -0
- data/app/assets/javascripts/pghero/Chart.bundle.js +16260 -15580
- data/app/assets/javascripts/pghero/application.js +7 -6
- data/app/assets/javascripts/pghero/chartkick.js +1973 -1325
- data/app/assets/javascripts/pghero/highlight.pack.js +2 -2
- data/app/assets/javascripts/pghero/jquery.js +3605 -4015
- data/app/assets/javascripts/pghero/nouislider.js +2479 -0
- data/app/assets/stylesheets/pghero/application.css +1 -1
- data/app/assets/stylesheets/pghero/nouislider.css +299 -0
- data/app/controllers/pg_hero/home_controller.rb +16 -17
- data/app/views/pg_hero/home/_live_queries_table.html.erb +11 -1
- data/app/views/pg_hero/home/index.html.erb +47 -8
- data/app/views/pg_hero/home/live_queries.html.erb +1 -1
- data/app/views/pg_hero/home/relation_space.html.erb +2 -2
- data/app/views/pg_hero/home/show_query.html.erb +3 -3
- data/app/views/pg_hero/home/space.html.erb +2 -2
- data/app/views/pg_hero/home/system.html.erb +4 -4
- data/lib/generators/pghero/templates/config.yml.tt +14 -1
- data/lib/pghero.rb +39 -7
- data/lib/pghero/database.rb +29 -4
- data/lib/pghero/methods/constraints.rb +30 -0
- data/lib/pghero/methods/indexes.rb +1 -1
- data/lib/pghero/methods/system.rb +0 -16
- data/lib/pghero/stats.rb +1 -1
- data/lib/pghero/version.rb +1 -1
- metadata +5 -4
- data/app/assets/javascripts/pghero/jquery.nouislider.min.js +0 -31
- data/app/assets/stylesheets/pghero/jquery.nouislider.css +0 -165
@@ -0,0 +1,299 @@
|
|
1
|
+
/*! nouislider - 14.0.3 - 10/10/2019 */
|
2
|
+
/* Functional styling;
|
3
|
+
* These styles are required for noUiSlider to function.
|
4
|
+
* You don't need to change these rules to apply your design.
|
5
|
+
*/
|
6
|
+
.noUi-target,
|
7
|
+
.noUi-target * {
|
8
|
+
-webkit-touch-callout: none;
|
9
|
+
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
10
|
+
-webkit-user-select: none;
|
11
|
+
-ms-touch-action: none;
|
12
|
+
touch-action: none;
|
13
|
+
-ms-user-select: none;
|
14
|
+
-moz-user-select: none;
|
15
|
+
user-select: none;
|
16
|
+
-moz-box-sizing: border-box;
|
17
|
+
box-sizing: border-box;
|
18
|
+
}
|
19
|
+
.noUi-target {
|
20
|
+
position: relative;
|
21
|
+
direction: ltr;
|
22
|
+
}
|
23
|
+
.noUi-base,
|
24
|
+
.noUi-connects {
|
25
|
+
width: 100%;
|
26
|
+
height: 100%;
|
27
|
+
position: relative;
|
28
|
+
z-index: 1;
|
29
|
+
}
|
30
|
+
/* Wrapper for all connect elements.
|
31
|
+
*/
|
32
|
+
.noUi-connects {
|
33
|
+
overflow: hidden;
|
34
|
+
z-index: 0;
|
35
|
+
}
|
36
|
+
.noUi-connect,
|
37
|
+
.noUi-origin {
|
38
|
+
will-change: transform;
|
39
|
+
position: absolute;
|
40
|
+
z-index: 1;
|
41
|
+
top: 0;
|
42
|
+
left: 0;
|
43
|
+
-ms-transform-origin: 0 0;
|
44
|
+
-webkit-transform-origin: 0 0;
|
45
|
+
-webkit-transform-style: preserve-3d;
|
46
|
+
transform-origin: 0 0;
|
47
|
+
transform-style: flat;
|
48
|
+
}
|
49
|
+
.noUi-connect {
|
50
|
+
height: 100%;
|
51
|
+
width: 100%;
|
52
|
+
}
|
53
|
+
.noUi-origin {
|
54
|
+
height: 10%;
|
55
|
+
width: 10%;
|
56
|
+
}
|
57
|
+
/* Offset direction
|
58
|
+
*/
|
59
|
+
html:not([dir="rtl"]) .noUi-horizontal .noUi-origin {
|
60
|
+
left: auto;
|
61
|
+
right: 0;
|
62
|
+
}
|
63
|
+
/* Give origins 0 height/width so they don't interfere with clicking the
|
64
|
+
* connect elements.
|
65
|
+
*/
|
66
|
+
.noUi-vertical .noUi-origin {
|
67
|
+
width: 0;
|
68
|
+
}
|
69
|
+
.noUi-horizontal .noUi-origin {
|
70
|
+
height: 0;
|
71
|
+
}
|
72
|
+
.noUi-handle {
|
73
|
+
-webkit-backface-visibility: hidden;
|
74
|
+
backface-visibility: hidden;
|
75
|
+
position: absolute;
|
76
|
+
}
|
77
|
+
.noUi-touch-area {
|
78
|
+
height: 100%;
|
79
|
+
width: 100%;
|
80
|
+
}
|
81
|
+
.noUi-state-tap .noUi-connect,
|
82
|
+
.noUi-state-tap .noUi-origin {
|
83
|
+
-webkit-transition: transform 0.3s;
|
84
|
+
transition: transform 0.3s;
|
85
|
+
}
|
86
|
+
.noUi-state-drag * {
|
87
|
+
cursor: inherit !important;
|
88
|
+
}
|
89
|
+
/* Slider size and handle placement;
|
90
|
+
*/
|
91
|
+
.noUi-horizontal {
|
92
|
+
height: 18px;
|
93
|
+
}
|
94
|
+
.noUi-horizontal .noUi-handle {
|
95
|
+
width: 34px;
|
96
|
+
height: 28px;
|
97
|
+
left: -17px;
|
98
|
+
top: -6px;
|
99
|
+
}
|
100
|
+
.noUi-vertical {
|
101
|
+
width: 18px;
|
102
|
+
}
|
103
|
+
.noUi-vertical .noUi-handle {
|
104
|
+
width: 28px;
|
105
|
+
height: 34px;
|
106
|
+
left: -6px;
|
107
|
+
top: -17px;
|
108
|
+
}
|
109
|
+
html:not([dir="rtl"]) .noUi-horizontal .noUi-handle {
|
110
|
+
right: -17px;
|
111
|
+
left: auto;
|
112
|
+
}
|
113
|
+
/* Styling;
|
114
|
+
* Giving the connect element a border radius causes issues with using transform: scale
|
115
|
+
*/
|
116
|
+
.noUi-target {
|
117
|
+
background: #FAFAFA;
|
118
|
+
border-radius: 4px;
|
119
|
+
border: 1px solid #D3D3D3;
|
120
|
+
box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB;
|
121
|
+
}
|
122
|
+
.noUi-connects {
|
123
|
+
border-radius: 3px;
|
124
|
+
}
|
125
|
+
.noUi-connect {
|
126
|
+
background: #3FB8AF;
|
127
|
+
}
|
128
|
+
/* Handles and cursors;
|
129
|
+
*/
|
130
|
+
.noUi-draggable {
|
131
|
+
cursor: ew-resize;
|
132
|
+
}
|
133
|
+
.noUi-vertical .noUi-draggable {
|
134
|
+
cursor: ns-resize;
|
135
|
+
}
|
136
|
+
.noUi-handle {
|
137
|
+
border: 1px solid #D9D9D9;
|
138
|
+
border-radius: 3px;
|
139
|
+
background: #FFF;
|
140
|
+
cursor: default;
|
141
|
+
box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #EBEBEB, 0 3px 6px -3px #BBB;
|
142
|
+
}
|
143
|
+
.noUi-active {
|
144
|
+
box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB;
|
145
|
+
}
|
146
|
+
/* Handle stripes;
|
147
|
+
*/
|
148
|
+
.noUi-handle:before,
|
149
|
+
.noUi-handle:after {
|
150
|
+
content: "";
|
151
|
+
display: block;
|
152
|
+
position: absolute;
|
153
|
+
height: 14px;
|
154
|
+
width: 1px;
|
155
|
+
background: #E8E7E6;
|
156
|
+
left: 14px;
|
157
|
+
top: 6px;
|
158
|
+
}
|
159
|
+
.noUi-handle:after {
|
160
|
+
left: 17px;
|
161
|
+
}
|
162
|
+
.noUi-vertical .noUi-handle:before,
|
163
|
+
.noUi-vertical .noUi-handle:after {
|
164
|
+
width: 14px;
|
165
|
+
height: 1px;
|
166
|
+
left: 6px;
|
167
|
+
top: 14px;
|
168
|
+
}
|
169
|
+
.noUi-vertical .noUi-handle:after {
|
170
|
+
top: 17px;
|
171
|
+
}
|
172
|
+
/* Disabled state;
|
173
|
+
*/
|
174
|
+
[disabled] .noUi-connect {
|
175
|
+
background: #B8B8B8;
|
176
|
+
}
|
177
|
+
[disabled].noUi-target,
|
178
|
+
[disabled].noUi-handle,
|
179
|
+
[disabled] .noUi-handle {
|
180
|
+
cursor: not-allowed;
|
181
|
+
}
|
182
|
+
/* Base;
|
183
|
+
*
|
184
|
+
*/
|
185
|
+
.noUi-pips,
|
186
|
+
.noUi-pips * {
|
187
|
+
-moz-box-sizing: border-box;
|
188
|
+
box-sizing: border-box;
|
189
|
+
}
|
190
|
+
.noUi-pips {
|
191
|
+
position: absolute;
|
192
|
+
color: #999;
|
193
|
+
}
|
194
|
+
/* Values;
|
195
|
+
*
|
196
|
+
*/
|
197
|
+
.noUi-value {
|
198
|
+
position: absolute;
|
199
|
+
white-space: nowrap;
|
200
|
+
text-align: center;
|
201
|
+
}
|
202
|
+
.noUi-value-sub {
|
203
|
+
color: #ccc;
|
204
|
+
font-size: 10px;
|
205
|
+
}
|
206
|
+
/* Markings;
|
207
|
+
*
|
208
|
+
*/
|
209
|
+
.noUi-marker {
|
210
|
+
position: absolute;
|
211
|
+
background: #CCC;
|
212
|
+
}
|
213
|
+
.noUi-marker-sub {
|
214
|
+
background: #AAA;
|
215
|
+
}
|
216
|
+
.noUi-marker-large {
|
217
|
+
background: #AAA;
|
218
|
+
}
|
219
|
+
/* Horizontal layout;
|
220
|
+
*
|
221
|
+
*/
|
222
|
+
.noUi-pips-horizontal {
|
223
|
+
padding: 10px 0;
|
224
|
+
height: 80px;
|
225
|
+
top: 100%;
|
226
|
+
left: 0;
|
227
|
+
width: 100%;
|
228
|
+
}
|
229
|
+
.noUi-value-horizontal {
|
230
|
+
-webkit-transform: translate(-50%, 50%);
|
231
|
+
transform: translate(-50%, 50%);
|
232
|
+
}
|
233
|
+
.noUi-rtl .noUi-value-horizontal {
|
234
|
+
-webkit-transform: translate(50%, 50%);
|
235
|
+
transform: translate(50%, 50%);
|
236
|
+
}
|
237
|
+
.noUi-marker-horizontal.noUi-marker {
|
238
|
+
margin-left: -1px;
|
239
|
+
width: 2px;
|
240
|
+
height: 5px;
|
241
|
+
}
|
242
|
+
.noUi-marker-horizontal.noUi-marker-sub {
|
243
|
+
height: 10px;
|
244
|
+
}
|
245
|
+
.noUi-marker-horizontal.noUi-marker-large {
|
246
|
+
height: 15px;
|
247
|
+
}
|
248
|
+
/* Vertical layout;
|
249
|
+
*
|
250
|
+
*/
|
251
|
+
.noUi-pips-vertical {
|
252
|
+
padding: 0 10px;
|
253
|
+
height: 100%;
|
254
|
+
top: 0;
|
255
|
+
left: 100%;
|
256
|
+
}
|
257
|
+
.noUi-value-vertical {
|
258
|
+
-webkit-transform: translate(0, -50%);
|
259
|
+
transform: translate(0, -50%);
|
260
|
+
padding-left: 25px;
|
261
|
+
}
|
262
|
+
.noUi-rtl .noUi-value-vertical {
|
263
|
+
-webkit-transform: translate(0, 50%);
|
264
|
+
transform: translate(0, 50%);
|
265
|
+
}
|
266
|
+
.noUi-marker-vertical.noUi-marker {
|
267
|
+
width: 5px;
|
268
|
+
height: 2px;
|
269
|
+
margin-top: -1px;
|
270
|
+
}
|
271
|
+
.noUi-marker-vertical.noUi-marker-sub {
|
272
|
+
width: 10px;
|
273
|
+
}
|
274
|
+
.noUi-marker-vertical.noUi-marker-large {
|
275
|
+
width: 15px;
|
276
|
+
}
|
277
|
+
.noUi-tooltip {
|
278
|
+
display: block;
|
279
|
+
position: absolute;
|
280
|
+
border: 1px solid #D9D9D9;
|
281
|
+
border-radius: 3px;
|
282
|
+
background: #fff;
|
283
|
+
color: #000;
|
284
|
+
padding: 5px;
|
285
|
+
text-align: center;
|
286
|
+
white-space: nowrap;
|
287
|
+
}
|
288
|
+
.noUi-horizontal .noUi-tooltip {
|
289
|
+
-webkit-transform: translate(-50%, 0);
|
290
|
+
transform: translate(-50%, 0);
|
291
|
+
left: 50%;
|
292
|
+
bottom: 120%;
|
293
|
+
}
|
294
|
+
.noUi-vertical .noUi-tooltip {
|
295
|
+
-webkit-transform: translate(0, -50%);
|
296
|
+
transform: translate(0, -50%);
|
297
|
+
top: 50%;
|
298
|
+
right: 120%;
|
299
|
+
}
|
@@ -4,20 +4,18 @@ module PgHero
|
|
4
4
|
|
5
5
|
protect_from_forgery
|
6
6
|
|
7
|
-
http_basic_authenticate_with name:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
before_filter :set_show_details, only: [:index, :queries, :show_query]
|
20
|
-
before_filter :ensure_query_stats, only: [:queries]
|
7
|
+
http_basic_authenticate_with name: PgHero.username, password: PgHero.password if PgHero.password
|
8
|
+
|
9
|
+
before_action :check_api
|
10
|
+
before_action :set_database
|
11
|
+
before_action :set_query_stats_enabled
|
12
|
+
before_action :set_show_details, only: [:index, :queries, :show_query]
|
13
|
+
before_action :ensure_query_stats, only: [:queries]
|
14
|
+
|
15
|
+
if PgHero.config["override_csp"]
|
16
|
+
after_action do
|
17
|
+
response.headers["Content-Security-Policy"] = "default-src 'self' 'unsafe-inline'"
|
18
|
+
end
|
21
19
|
end
|
22
20
|
|
23
21
|
def index
|
@@ -48,6 +46,7 @@ module PgHero
|
|
48
46
|
|
49
47
|
@indexes = @database.indexes
|
50
48
|
@invalid_indexes = @database.invalid_indexes(indexes: @indexes)
|
49
|
+
@invalid_constraints = @database.invalid_constraints
|
51
50
|
@duplicate_indexes = @database.duplicate_indexes(indexes: @indexes)
|
52
51
|
|
53
52
|
if @query_stats_enabled
|
@@ -100,7 +99,7 @@ module PgHero
|
|
100
99
|
@relation = params[:relation]
|
101
100
|
@title = @relation
|
102
101
|
relation_space_stats = @database.relation_space_stats(@relation, schema: @schema)
|
103
|
-
@chart_data = [{name: "Value", data: relation_space_stats.map { |r| [r[:captured_at],
|
102
|
+
@chart_data = [{name: "Value", data: relation_space_stats.map { |r| [r[:captured_at].change(sec: 0), r[:size_bytes].to_i] }, library: chart_library_options}]
|
104
103
|
end
|
105
104
|
|
106
105
|
def index_bloat
|
@@ -230,7 +229,7 @@ module PgHero
|
|
230
229
|
|
231
230
|
def free_space_stats
|
232
231
|
render json: [
|
233
|
-
{name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour)
|
232
|
+
{name: "Free Space", data: @database.free_space_stats(duration: 14.days, period: 1.hour), library: chart_library_options},
|
234
233
|
]
|
235
234
|
end
|
236
235
|
|
@@ -369,7 +368,7 @@ module PgHero
|
|
369
368
|
end
|
370
369
|
|
371
370
|
def chart_library_options
|
372
|
-
{pointRadius: 0, pointHitRadius: 5, borderWidth: 4}
|
371
|
+
{pointRadius: 0, pointHoverRadius: 0, pointHitRadius: 5, borderWidth: 4}
|
373
372
|
end
|
374
373
|
|
375
374
|
def set_show_details
|
@@ -11,7 +11,17 @@
|
|
11
11
|
<% queries.reverse.each do |query| %>
|
12
12
|
<tr>
|
13
13
|
<td><%= query[:pid] %></td>
|
14
|
-
<td
|
14
|
+
<td>
|
15
|
+
<% sec = query[:duration_ms] / 1000.0 %>
|
16
|
+
<% if sec < 1.minute %>
|
17
|
+
<%= sec.round(1) %> s
|
18
|
+
<% elsif sec < 1.day %>
|
19
|
+
<%= Time.at(sec).utc.strftime("%H:%M:%S") %>
|
20
|
+
<% else %>
|
21
|
+
<% days = (sec / 1.day).floor %>
|
22
|
+
<%= days %>d <%= Time.at(sec - days.days).utc.strftime("%H:%M:%S") %>
|
23
|
+
<% end %>
|
24
|
+
</td>
|
15
25
|
<td>
|
16
26
|
<%= query[:state] %>
|
17
27
|
<% if vacuum_progress[query[:pid]] %>
|
@@ -62,11 +62,17 @@
|
|
62
62
|
(<%= link_to pluralize(@unreadable_sequences.size, "unreadable sequence", "unreadable sequences"), {unreadable: "t"} %>)
|
63
63
|
<% end %>
|
64
64
|
</div>
|
65
|
-
<div class="alert alert-<%= @invalid_indexes.empty? ? "success" : "warning" %>">
|
66
|
-
<% if @invalid_indexes.
|
67
|
-
|
65
|
+
<div class="alert alert-<%= @invalid_indexes.empty? && @invalid_constraints.empty? ? "success" : "warning" %>">
|
66
|
+
<% if @invalid_indexes.empty? && @invalid_constraints.empty? %>
|
67
|
+
No invalid indexes or constraints
|
68
68
|
<% else %>
|
69
|
-
|
69
|
+
<% if @invalid_indexes.any? %>
|
70
|
+
<%= pluralize(@invalid_indexes.size, "invalid index", "invalid indexes") %>
|
71
|
+
<% end %>
|
72
|
+
<% if @invalid_constraints.any? %>
|
73
|
+
<% if @invalid_indexes.any? %>and<% end %>
|
74
|
+
<%= pluralize(@invalid_constraints.size, "invalid constraint", "invalid constraints") %>
|
75
|
+
<% end %>
|
70
76
|
<% end %>
|
71
77
|
</div>
|
72
78
|
<% if @duplicate_indexes %>
|
@@ -332,6 +338,39 @@
|
|
332
338
|
</div>
|
333
339
|
<% end %>
|
334
340
|
|
341
|
+
<% if @invalid_constraints.any? %>
|
342
|
+
<div class="content">
|
343
|
+
<h1>Invalid Constraints</h1>
|
344
|
+
|
345
|
+
<p>These constraints are marked as <code>NOT VALID</code>. You should validate them.</p>
|
346
|
+
|
347
|
+
<table class="table">
|
348
|
+
<thead>
|
349
|
+
<tr>
|
350
|
+
<th>Name</th>
|
351
|
+
</tr>
|
352
|
+
</thead>
|
353
|
+
<tbody>
|
354
|
+
<% @invalid_constraints.each do |constraint| %>
|
355
|
+
<tr>
|
356
|
+
<td>
|
357
|
+
<%= constraint[:name] %>
|
358
|
+
<% if constraint[:schema] != "public" %>
|
359
|
+
<span class="text-muted"><%= constraint[:schema] %></span>
|
360
|
+
<% end %>
|
361
|
+
</td>
|
362
|
+
</tr>
|
363
|
+
<tr>
|
364
|
+
<td style="border-top: none; padding: 0;">
|
365
|
+
<pre><code>ALTER TABLE <%= pghero_pretty_ident(constraint[:table], schema: constraint[:schema]) %> VALIDATE CONSTRAINT <%= pghero_pretty_ident(constraint[:name]) %>;</code></pre>
|
366
|
+
</td>
|
367
|
+
</tr>
|
368
|
+
<% end %>
|
369
|
+
</tbody>
|
370
|
+
</table>
|
371
|
+
</div>
|
372
|
+
<% end %>
|
373
|
+
|
335
374
|
<% if @duplicate_indexes && @duplicate_indexes.any? %>
|
336
375
|
<div class="content">
|
337
376
|
<h1>Duplicate Indexes</h1>
|
@@ -345,7 +384,7 @@
|
|
345
384
|
</p>
|
346
385
|
|
347
386
|
<div id="migration2" style="display: none;">
|
348
|
-
<pre>rails
|
387
|
+
<pre>rails generate migration remove_unneeded_indexes</pre>
|
349
388
|
<p>And paste</p>
|
350
389
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
351
390
|
remove_index <%= query[:unneeded_index][:table].to_sym.inspect %>, name: <%= query[:unneeded_index][:name].to_s.inspect %><% end %></pre>
|
@@ -387,12 +426,12 @@ remove_index <%= query[:unneeded_index][:table].to_sym.inspect %>, name: <%= que
|
|
387
426
|
</p>
|
388
427
|
|
389
428
|
<div id="migration3" style="display: none;">
|
390
|
-
<pre>rails
|
429
|
+
<pre>rails generate migration add_suggested_indexes</pre>
|
391
430
|
<p>And paste</p>
|
392
431
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;">commit_db_transaction
|
393
432
|
<% @suggested_indexes.each do |index| %>
|
394
433
|
<% if index[:using] && index[:using] != "btree" %>
|
395
|
-
|
434
|
+
add_index <%= index[:table].to_sym.inspect %>, <%= index[:columns].first.inspect %>, using: <%= index[:using].inspect %>, algorithm: :concurrently
|
396
435
|
<% else %>
|
397
436
|
add_index <%= index[:table].to_sym.inspect %>, [<%= index[:columns].map(&:to_sym).map(&:inspect).join(", ") %>], algorithm: :concurrently<% end %>
|
398
437
|
<% end %></pre>
|
@@ -449,7 +488,7 @@ pg_stat_statements.track = all</pre>
|
|
449
488
|
</p>
|
450
489
|
|
451
490
|
<div id="migration" style="display: none;">
|
452
|
-
<pre>rails
|
491
|
+
<pre>rails generate migration remove_unused_indexes</pre>
|
453
492
|
<p>And paste</p>
|
454
493
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
455
494
|
remove_index <%= query[:table].to_sym.inspect %>, name: <%= query[:index].to_s.inspect %><% end %></pre>
|