pghero 3.7.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -2
- data/LICENSE.txt +1 -1
- data/README.md +4 -3
- data/app/assets/javascripts/pghero/Chart.bundle.js +2228 -5320
- data/app/assets/javascripts/pghero/application.js +104 -68
- data/app/assets/javascripts/pghero/highlight.min.js +330 -341
- data/app/assets/javascripts/pghero/nouislider.js +64 -5
- data/app/assets/stylesheets/pghero/application.css +7 -1
- data/app/controllers/pg_hero/home_controller.rb +75 -41
- data/app/views/layouts/pg_hero/application.html.erb +1 -1
- data/app/views/pg_hero/home/_live_queries_table.html.erb +4 -2
- data/app/views/pg_hero/home/_queries_table.html.erb +8 -7
- data/app/views/pg_hero/home/_query_stats_slider.html.erb +1 -0
- data/app/views/pg_hero/home/_suggested_index.html.erb +3 -3
- data/app/views/pg_hero/home/explain.html.erb +2 -1
- data/app/views/pg_hero/home/index.html.erb +17 -18
- data/app/views/pg_hero/home/index_bloat.html.erb +2 -2
- data/app/views/pg_hero/home/live_queries.html.erb +7 -3
- data/app/views/pg_hero/home/queries.html.erb +31 -6
- data/app/views/pg_hero/home/show_query.html.erb +5 -5
- data/app/views/pg_hero/home/space.html.erb +5 -4
- data/app/views/pg_hero/home/tune.html.erb +1 -1
- data/lib/generators/pghero/templates/config.yml.tt +6 -1
- data/lib/generators/pghero/templates/query_stats.rb.tt +7 -1
- data/lib/generators/pghero/templates/upgrade_query_stats.rb.tt +23 -0
- data/lib/generators/pghero/upgrade_query_stats_generator.rb +19 -0
- data/lib/pghero/database.rb +6 -9
- data/lib/pghero/engine.rb +3 -10
- data/lib/pghero/methods/basic.rb +28 -27
- data/lib/pghero/methods/connections.rb +16 -32
- data/lib/pghero/methods/explain.rb +13 -20
- data/lib/pghero/methods/indexes.rb +8 -5
- data/lib/pghero/methods/kill.rb +4 -1
- data/lib/pghero/methods/maintenance.rb +14 -20
- data/lib/pghero/methods/queries.rb +7 -5
- data/lib/pghero/methods/query_stats.rb +264 -238
- data/lib/pghero/methods/replication.rb +9 -20
- data/lib/pghero/methods/sequences.rb +1 -1
- data/lib/pghero/methods/settings.rb +6 -11
- data/lib/pghero/methods/space.rb +11 -8
- data/lib/pghero/methods/suggested_indexes.rb +15 -16
- data/lib/pghero/methods/system.rb +6 -106
- data/lib/pghero/methods/tables.rb +7 -3
- data/lib/pghero/query.rb +8 -0
- data/lib/pghero/query_stats.rb +3 -0
- data/lib/pghero/version.rb +1 -1
- data/lib/pghero.rb +10 -42
- data/licenses/LICENSE-chart.js.txt +1 -1
- metadata +8 -8
- data/app/assets/javascripts/pghero/jquery.js +0 -10993
- data/lib/pghero/methods/users.rb +0 -95
- data/licenses/LICENSE-jquery.txt +0 -20
|
@@ -1,39 +1,37 @@
|
|
|
1
|
-
//= require ./jquery
|
|
2
1
|
//= require ./nouislider
|
|
3
2
|
//= require ./Chart.bundle
|
|
4
3
|
//= require ./chartkick
|
|
5
4
|
//= require ./highlight.min
|
|
6
5
|
|
|
7
6
|
function highlightQueries() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
for (const block of document.querySelectorAll("code.language-pgsql")) {
|
|
8
|
+
if (!block.dataset.highlighted) {
|
|
9
|
+
hljs.highlightElement(block);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function initSlider() {
|
|
15
15
|
function roundTime(time) {
|
|
16
|
-
|
|
16
|
+
const period = 1000 * 60 * 5;
|
|
17
17
|
return new Date(Math.ceil(time.getTime() / period) * period);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function pad(num) {
|
|
21
|
-
return
|
|
21
|
+
return num < 10 ? "0" + num : num;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
var sliderStartAt = roundTime(now) - days * 24 * 60 * 60 * 1000;
|
|
29
|
-
var sliderMax = 24 * 12 * days;
|
|
24
|
+
const days = 1;
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const sliderStartAt = roundTime(now) - days * 24 * 60 * 60 * 1000;
|
|
27
|
+
const sliderMax = 24 * 12 * days;
|
|
30
28
|
|
|
31
29
|
startAt = startAt || sliderStartAt;
|
|
32
|
-
|
|
30
|
+
const min = startAt > 0 ? (startAt - sliderStartAt) / (1000 * 60 * 5) : 0;
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
const max = endAt > 0 ? (endAt - sliderStartAt) / (1000 * 60 * 5) : sliderMax;
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
const slider = document.getElementById("slider");
|
|
37
35
|
|
|
38
36
|
noUiSlider.create(slider, {
|
|
39
37
|
range: {
|
|
@@ -46,35 +44,37 @@ function initSlider() {
|
|
|
46
44
|
});
|
|
47
45
|
|
|
48
46
|
// remove outline for mouse only
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const handle = document.querySelector(".noUi-handle");
|
|
48
|
+
handle.addEventListener("mousedown", function (e) {
|
|
49
|
+
e.target.classList.add("no-outline");
|
|
50
|
+
});
|
|
51
|
+
handle.addEventListener("blur", function (e) {
|
|
52
|
+
e.target.classList.remove("no-outline");
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
function updateText() {
|
|
56
|
-
|
|
56
|
+
const values = slider.noUiSlider.get();
|
|
57
57
|
setText("#range-start", values[0]);
|
|
58
58
|
setText("#range-end", values[1]);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function setText(selector, offset) {
|
|
62
|
-
|
|
62
|
+
const time = timeAt(offset);
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
let html = "";
|
|
65
65
|
if (time === now) {
|
|
66
66
|
if (selector === "#range-end") {
|
|
67
67
|
html = "Now";
|
|
68
68
|
}
|
|
69
69
|
} else {
|
|
70
|
-
html = time.
|
|
70
|
+
html = time.toLocaleDateString(undefined, {month: 'short', day: 'numeric'}) + " " + pad(time.getHours()) + ":" + pad(time.getMinutes());
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
document.querySelector(selector).textContent = html;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function timeAt(offset) {
|
|
76
|
-
|
|
77
|
-
return
|
|
76
|
+
const time = new Date(sliderStartAt + offset * 5 * 60 * 1000);
|
|
77
|
+
return time > now ? now : time;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function timeParam(time) {
|
|
@@ -82,19 +82,19 @@ function initSlider() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function queriesPath(params) {
|
|
85
|
-
|
|
86
|
-
if (params.start_at || params.end_at || params.sort || params.min_average_time || params.min_calls || params.debug) {
|
|
87
|
-
path += "?" +
|
|
85
|
+
let path = "queries";
|
|
86
|
+
if (params.start_at || params.end_at || params.sort || params.min_average_time || params.min_calls || params.user || params.debug) {
|
|
87
|
+
path += "?" + (new URLSearchParams(params)).toString();
|
|
88
88
|
}
|
|
89
89
|
return path;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function refreshStats(push) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
const values = slider.noUiSlider.get();
|
|
94
|
+
const startAt = push ? timeAt(values[0]) : new Date(window.startAt);
|
|
95
|
+
const endAt = timeAt(values[1]);
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
const params = {};
|
|
98
98
|
if (startAt.getTime() != sliderStartAt) {
|
|
99
99
|
params.start_at = timeParam(startAt);
|
|
100
100
|
}
|
|
@@ -110,38 +110,44 @@ function initSlider() {
|
|
|
110
110
|
if (minCalls) {
|
|
111
111
|
params.min_calls = minCalls;
|
|
112
112
|
}
|
|
113
|
+
if (user) {
|
|
114
|
+
params.user = user;
|
|
115
|
+
}
|
|
113
116
|
if (debug) {
|
|
114
117
|
params.debug = debug;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (!p.min_average_time) {
|
|
125
|
-
delete p.min_average_time;
|
|
126
|
-
}
|
|
127
|
-
if (!p.min_calls) {
|
|
128
|
-
delete p.min_calls;
|
|
129
|
-
}
|
|
130
|
-
if (!p.debug) {
|
|
131
|
-
delete p.debug;
|
|
132
|
-
}
|
|
133
|
-
$(this).attr("href", queriesPath(p));
|
|
134
|
-
});
|
|
120
|
+
const showAll = document.getElementById("show-all");
|
|
121
|
+
if (showAll) {
|
|
122
|
+
const showAllParams = Object.assign({}, params);
|
|
123
|
+
delete showAllParams.user;
|
|
124
|
+
showAll.setAttribute("href", queriesPath(showAllParams));
|
|
125
|
+
}
|
|
135
126
|
|
|
127
|
+
for (const link of document.querySelectorAll(".queries-table th a")) {
|
|
128
|
+
const linkParams = Object.assign({}, params, {sort: link.getAttribute("data-sort")});
|
|
129
|
+
link.setAttribute("href", queriesPath(linkParams));
|
|
130
|
+
}
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
const queries = document.getElementById("queries");
|
|
133
|
+
queries.innerHTML = '<tr><td colspan="3"><p class="queries-info text-muted">...</p></td></tr>';
|
|
134
|
+
const path = queriesPath(params);
|
|
135
|
+
fetch(path, {headers: {"X-Requested-With": "XMLHttpRequest"}})
|
|
136
|
+
.then(function (response) {
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(response.statusText);
|
|
139
|
+
}
|
|
140
|
+
return response.text();
|
|
141
|
+
})
|
|
142
|
+
.then(function (text) {
|
|
143
|
+
queries.innerHTML = text;
|
|
141
144
|
highlightQueries();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
})
|
|
146
|
+
.catch(function (error) {
|
|
147
|
+
const queriesInfo = document.querySelector(".queries-info");
|
|
148
|
+
queriesInfo.style.color = "red";
|
|
149
|
+
queriesInfo.textContent = error.message;
|
|
150
|
+
});
|
|
145
151
|
|
|
146
152
|
if (push && history.pushState) {
|
|
147
153
|
history.pushState(null, null, path);
|
|
@@ -153,21 +159,51 @@ function initSlider() {
|
|
|
153
159
|
refreshStats(true);
|
|
154
160
|
});
|
|
155
161
|
updateText();
|
|
156
|
-
|
|
162
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
157
163
|
refreshStats(false);
|
|
158
164
|
});
|
|
159
165
|
}
|
|
160
166
|
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
document.addEventListener("click", function (e) {
|
|
168
|
+
const target = e.target.closest(".query-code");
|
|
169
|
+
if (target) {
|
|
170
|
+
target.style.maxHeight = "none";
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
document.addEventListener("click", function (e) {
|
|
175
|
+
const target = e.target.closest(".migration-link");
|
|
176
|
+
if (target) {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
target.parentElement.nextElementSibling.style.display = "block";
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
document.addEventListener("click", function (e) {
|
|
183
|
+
const target = e.target.closest(".show-details");
|
|
184
|
+
if (target) {
|
|
185
|
+
target.nextElementSibling.nextElementSibling.style.display = "block";
|
|
186
|
+
target.style.display = "none";
|
|
187
|
+
}
|
|
163
188
|
});
|
|
164
189
|
|
|
165
|
-
|
|
166
|
-
e.
|
|
167
|
-
|
|
190
|
+
document.addEventListener("click", function (e) {
|
|
191
|
+
const target = e.target.closest("#copy");
|
|
192
|
+
if (target) {
|
|
193
|
+
const explanation = document.getElementById("explanation").textContent;
|
|
194
|
+
navigator.clipboard.writeText(explanation).then(function () {
|
|
195
|
+
const buttonText = target.textContent;
|
|
196
|
+
target.textContent = "Copied!";
|
|
197
|
+
setTimeout(function () {
|
|
198
|
+
target.textContent = buttonText;
|
|
199
|
+
}, 2000);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
168
202
|
});
|
|
169
203
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
204
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
205
|
+
const copyButton = document.getElementById("copy");
|
|
206
|
+
if (copyButton && navigator.clipboard) {
|
|
207
|
+
copyButton.classList.remove("hide");
|
|
208
|
+
}
|
|
173
209
|
});
|