pg_reports 0.6.0 → 0.6.2
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 +45 -0
- data/README.md +143 -378
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +65 -8
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
- data/app/views/pg_reports/dashboard/index.html.erb +109 -106
- data/app/views/pg_reports/dashboard/show.html.erb +26 -26
- data/config/locales/en.yml +488 -0
- data/config/locales/ru.yml +481 -0
- data/config/locales/uk.yml +481 -0
- data/lib/pg_reports/annotation_parser.rb +13 -1
- data/lib/pg_reports/compatibility.rb +3 -3
- data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
- data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
- data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
- data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +3 -3
- data/lib/pg_reports/query_monitor.rb +2 -6
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
- data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
- data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
- data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
- data/lib/pg_reports/version.rb +1 -1
- metadata +9 -1
|
@@ -4,36 +4,39 @@
|
|
|
4
4
|
<div class="logo">
|
|
5
5
|
<div class="logo-icon">🐘</div>
|
|
6
6
|
<div class="logo-text">
|
|
7
|
-
<h1>
|
|
8
|
-
|
|
7
|
+
<h1>
|
|
8
|
+
<%= t("pg_reports.ui.branding.title") %>
|
|
9
|
+
<span class="logo-version">v<%= PgReports::VERSION %></span>
|
|
10
|
+
</h1>
|
|
11
|
+
<span class="logo-subtitle"><%= t("pg_reports.ui.branding.subtitle") %></span>
|
|
9
12
|
</div>
|
|
10
13
|
</div>
|
|
11
14
|
|
|
12
15
|
<div class="header-actions">
|
|
13
16
|
<% if @pg_stat_status[:ready] %>
|
|
14
17
|
<button class="btn btn-small btn-muted" onclick="showResetConfirmModal()" id="reset-btn">
|
|
15
|
-
|
|
18
|
+
<%= t("pg_reports.ui.actions.reset_statistics") %>
|
|
16
19
|
</button>
|
|
17
20
|
<% else %>
|
|
18
21
|
<button class="btn btn-small btn-primary" onclick="enablePgStatStatements(this)" id="enable-btn">
|
|
19
|
-
|
|
22
|
+
<%= t("pg_reports.ui.actions.create_extension") %>
|
|
20
23
|
</button>
|
|
21
24
|
<button class="btn-info" onclick="showPgStatInfo()">?</button>
|
|
22
25
|
<% end %>
|
|
23
|
-
<button class="btn-info" onclick="showIdeSettingsModal()" title="
|
|
26
|
+
<button class="btn-info" onclick="showIdeSettingsModal()" title="<%= t("pg_reports.ui.actions.ide_settings_button_title") %>">⚙️</button>
|
|
24
27
|
<div class="header-badge" id="pg-stat-badge">
|
|
25
28
|
<% if @pg_stat_status[:ready] %>
|
|
26
29
|
<span class="badge-dot"></span>
|
|
27
|
-
<span
|
|
30
|
+
<span><%= t("pg_reports.ui.status.pg_stat_ready") %></span>
|
|
28
31
|
<% elsif @pg_stat_status[:extension_installed] %>
|
|
29
32
|
<span class="badge-dot warning"></span>
|
|
30
|
-
<span
|
|
33
|
+
<span><%= t("pg_reports.ui.status.extension_installed") %></span>
|
|
31
34
|
<% elsif @pg_stat_status[:preloaded] %>
|
|
32
35
|
<span class="badge-dot warning"></span>
|
|
33
|
-
<span
|
|
36
|
+
<span><%= t("pg_reports.ui.status.preloaded") %></span>
|
|
34
37
|
<% else %>
|
|
35
38
|
<span class="badge-dot error"></span>
|
|
36
|
-
<span
|
|
39
|
+
<span><%= t("pg_reports.ui.status.not_configured") %></span>
|
|
37
40
|
<% end %>
|
|
38
41
|
</div>
|
|
39
42
|
</div>
|
|
@@ -43,25 +46,25 @@
|
|
|
43
46
|
<div id="pg-stat-modal" class="modal" style="display: none;">
|
|
44
47
|
<div class="modal-content">
|
|
45
48
|
<div class="modal-header">
|
|
46
|
-
<h3
|
|
49
|
+
<h3><%= t("pg_reports.ui.modals.enable_pg_stat_title") %></h3>
|
|
47
50
|
<button class="modal-close" onclick="closePgStatModal()">×</button>
|
|
48
51
|
</div>
|
|
49
52
|
<div class="modal-body">
|
|
50
|
-
<p
|
|
53
|
+
<p><%= t("pg_reports.ui.modals.enable_pg_stat_intro") %></p>
|
|
51
54
|
<ol>
|
|
52
55
|
<li>
|
|
53
|
-
<strong
|
|
56
|
+
<strong><%= t("pg_reports.ui.modals.edit_postgresql_conf") %></strong>
|
|
54
57
|
<pre>shared_preload_libraries = 'pg_stat_statements'
|
|
55
58
|
pg_stat_statements.track = all</pre>
|
|
56
59
|
</li>
|
|
57
60
|
<li>
|
|
58
|
-
<strong
|
|
61
|
+
<strong><%= t("pg_reports.ui.modals.restart_postgresql") %></strong>
|
|
59
62
|
<pre>sudo systemctl restart postgresql</pre>
|
|
60
63
|
</li>
|
|
61
64
|
<li>
|
|
62
|
-
<strong
|
|
65
|
+
<strong><%= t("pg_reports.ui.modals.create_extension_step") %></strong>
|
|
63
66
|
<pre>CREATE EXTENSION IF NOT EXISTS pg_stat_statements;</pre>
|
|
64
|
-
<p
|
|
67
|
+
<p><%= t("pg_reports.ui.modals.enable_button_note") %></p>
|
|
65
68
|
</li>
|
|
66
69
|
</ol>
|
|
67
70
|
</div>
|
|
@@ -72,15 +75,15 @@ pg_stat_statements.track = all</pre>
|
|
|
72
75
|
<div id="reset-confirm-modal" class="modal" style="display: none;">
|
|
73
76
|
<div class="modal-content modal-small">
|
|
74
77
|
<div class="modal-header modal-header-danger">
|
|
75
|
-
<h3
|
|
78
|
+
<h3><%= t("pg_reports.ui.modals.reset_stats_title") %></h3>
|
|
76
79
|
<button class="modal-close" onclick="closeResetConfirmModal()">×</button>
|
|
77
80
|
</div>
|
|
78
81
|
<div class="modal-body">
|
|
79
|
-
<p class="warning-text"
|
|
80
|
-
<p class="warning-subtext"
|
|
82
|
+
<p class="warning-text"><%= t("pg_reports.ui.modals.reset_stats_confirm") %></p>
|
|
83
|
+
<p class="warning-subtext"><%= t("pg_reports.ui.modals.reset_stats_warning") %></p>
|
|
81
84
|
<div class="modal-actions">
|
|
82
|
-
<button class="btn btn-secondary" onclick="closeResetConfirmModal()"
|
|
83
|
-
<button class="btn btn-danger" onclick="resetStatistics()" id="confirm-reset-btn"
|
|
85
|
+
<button class="btn btn-secondary" onclick="closeResetConfirmModal()"><%= t("pg_reports.ui.actions.cancel") %></button>
|
|
86
|
+
<button class="btn btn-danger" onclick="resetStatistics()" id="confirm-reset-btn"><%= t("pg_reports.ui.actions.confirm_reset") %></button>
|
|
84
87
|
</div>
|
|
85
88
|
</div>
|
|
86
89
|
</div>
|
|
@@ -90,39 +93,39 @@ pg_stat_statements.track = all</pre>
|
|
|
90
93
|
<div id="ide-settings-modal" class="modal" style="display: none;">
|
|
91
94
|
<div class="modal-content modal-small">
|
|
92
95
|
<div class="modal-header">
|
|
93
|
-
<h3
|
|
96
|
+
<h3><%= t("pg_reports.ui.modals.ide_settings_title") %></h3>
|
|
94
97
|
<button class="modal-close" onclick="closeIdeSettingsModal()">×</button>
|
|
95
98
|
</div>
|
|
96
99
|
<div class="modal-body">
|
|
97
|
-
<p class="settings-label"
|
|
100
|
+
<p class="settings-label"><%= t("pg_reports.ui.settings.default_ide_label") %></p>
|
|
98
101
|
<div class="ide-options">
|
|
99
102
|
<label class="ide-option">
|
|
100
103
|
<input type="radio" name="default-ide" value="" onchange="setDefaultIde('')">
|
|
101
|
-
<span
|
|
104
|
+
<span><%= t("pg_reports.ui.settings.ide_show_menu") %></span>
|
|
102
105
|
</label>
|
|
103
106
|
<label class="ide-option">
|
|
104
107
|
<input type="radio" name="default-ide" value="vscode-wsl" onchange="setDefaultIde('vscode-wsl')">
|
|
105
|
-
<span
|
|
108
|
+
<span><%= t("pg_reports.ui.settings.ide_vscode_wsl") %></span>
|
|
106
109
|
</label>
|
|
107
110
|
<label class="ide-option">
|
|
108
111
|
<input type="radio" name="default-ide" value="vscode" onchange="setDefaultIde('vscode')">
|
|
109
|
-
<span
|
|
112
|
+
<span><%= t("pg_reports.ui.settings.ide_vscode") %></span>
|
|
110
113
|
</label>
|
|
111
114
|
<label class="ide-option">
|
|
112
115
|
<input type="radio" name="default-ide" value="rubymine" onchange="setDefaultIde('rubymine')">
|
|
113
|
-
<span
|
|
116
|
+
<span><%= t("pg_reports.ui.settings.ide_rubymine") %></span>
|
|
114
117
|
</label>
|
|
115
118
|
<label class="ide-option">
|
|
116
119
|
<input type="radio" name="default-ide" value="intellij" onchange="setDefaultIde('intellij')">
|
|
117
|
-
<span
|
|
120
|
+
<span><%= t("pg_reports.ui.settings.ide_intellij") %></span>
|
|
118
121
|
</label>
|
|
119
122
|
<label class="ide-option">
|
|
120
123
|
<input type="radio" name="default-ide" value="cursor-wsl" onchange="setDefaultIde('cursor-wsl')">
|
|
121
|
-
<span
|
|
124
|
+
<span><%= t("pg_reports.ui.settings.ide_cursor_wsl") %></span>
|
|
122
125
|
</label>
|
|
123
126
|
<label class="ide-option">
|
|
124
127
|
<input type="radio" name="default-ide" value="cursor" onchange="setDefaultIde('cursor')">
|
|
125
|
-
<span
|
|
128
|
+
<span><%= t("pg_reports.ui.settings.ide_cursor") %></span>
|
|
126
129
|
</label>
|
|
127
130
|
</div>
|
|
128
131
|
</div>
|
|
@@ -134,14 +137,14 @@ pg_stat_statements.track = all</pre>
|
|
|
134
137
|
<div class="live-monitoring-header">
|
|
135
138
|
<div class="live-monitoring-title">
|
|
136
139
|
<span class="live-indicator"></span>
|
|
137
|
-
<span
|
|
140
|
+
<span><%= t("pg_reports.ui.monitoring.live_title") %></span>
|
|
138
141
|
<span class="database-badge">
|
|
139
|
-
|
|
142
|
+
<strong><%= @current_database %></strong>
|
|
140
143
|
</span>
|
|
141
144
|
</div>
|
|
142
145
|
<div class="live-monitoring-controls">
|
|
143
|
-
<span class="live-monitoring-interval"
|
|
144
|
-
<button class="btn-toggle-live" onclick="toggleLiveMonitoring()" title="
|
|
146
|
+
<span class="live-monitoring-interval"><%= t("pg_reports.ui.monitoring.update_interval") %></span>
|
|
147
|
+
<button class="btn-toggle-live" onclick="toggleLiveMonitoring()" title="<%= t("pg_reports.ui.monitoring.toggle_title") %>">
|
|
145
148
|
<span id="toggle-icon">⏸</span>
|
|
146
149
|
</button>
|
|
147
150
|
</div>
|
|
@@ -152,7 +155,7 @@ pg_stat_statements.track = all</pre>
|
|
|
152
155
|
<div class="live-metric-card" data-metric="connections">
|
|
153
156
|
<div class="metric-header">
|
|
154
157
|
<span class="metric-icon" style="background: rgba(107, 159, 232, 0.12); color: var(--accent-blue);">🔗</span>
|
|
155
|
-
<span class="metric-name"
|
|
158
|
+
<span class="metric-name"><%= t("pg_reports.ui.metrics.connections_label") %></span>
|
|
156
159
|
</div>
|
|
157
160
|
<div class="metric-body">
|
|
158
161
|
<div class="metric-value">
|
|
@@ -160,7 +163,7 @@ pg_stat_statements.track = all</pre>
|
|
|
160
163
|
<span class="metric-unit">/ <span id="metric-connections-max">-</span></span>
|
|
161
164
|
</div>
|
|
162
165
|
<div class="metric-detail">
|
|
163
|
-
<span id="metric-connections-pct">-</span
|
|
166
|
+
<span id="metric-connections-pct">-</span><%= t("pg_reports.ui.metrics.percent_used_suffix") %>
|
|
164
167
|
</div>
|
|
165
168
|
<div class="metric-sparkline">
|
|
166
169
|
<svg id="sparkline-connections" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
@@ -173,15 +176,15 @@ pg_stat_statements.track = all</pre>
|
|
|
173
176
|
<div class="live-metric-card" data-metric="tps">
|
|
174
177
|
<div class="metric-header">
|
|
175
178
|
<span class="metric-icon" style="background: rgba(95, 184, 154, 0.12); color: var(--accent-green);">⚡</span>
|
|
176
|
-
<span class="metric-name"
|
|
179
|
+
<span class="metric-name"><%= t("pg_reports.ui.metrics.tps_label") %></span>
|
|
177
180
|
</div>
|
|
178
181
|
<div class="metric-body">
|
|
179
182
|
<div class="metric-value">
|
|
180
183
|
<span id="metric-tps-value">-</span>
|
|
181
|
-
<span class="metric-unit"
|
|
184
|
+
<span class="metric-unit"><%= t("pg_reports.ui.metrics.tps_unit") %></span>
|
|
182
185
|
</div>
|
|
183
186
|
<div class="metric-detail">
|
|
184
|
-
|
|
187
|
+
<%= t("pg_reports.ui.metrics.commit_label") %> <span id="metric-tps-commit">-</span> / <%= t("pg_reports.ui.metrics.rollback_label") %> <span id="metric-tps-rollback">-</span>
|
|
185
188
|
</div>
|
|
186
189
|
<div class="metric-sparkline">
|
|
187
190
|
<svg id="sparkline-tps" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
@@ -194,14 +197,14 @@ pg_stat_statements.track = all</pre>
|
|
|
194
197
|
<div class="live-metric-card" data-metric="cache">
|
|
195
198
|
<div class="metric-header">
|
|
196
199
|
<span class="metric-icon" style="background: rgba(157, 140, 214, 0.12); color: var(--accent-purple);">💾</span>
|
|
197
|
-
<span class="metric-name"
|
|
200
|
+
<span class="metric-name"><%= t("pg_reports.ui.metrics.cache_hit_label") %></span>
|
|
198
201
|
</div>
|
|
199
202
|
<div class="metric-body">
|
|
200
203
|
<div class="metric-value">
|
|
201
204
|
<span id="metric-cache-value">-</span>
|
|
202
205
|
<span class="metric-unit">%</span>
|
|
203
206
|
</div>
|
|
204
|
-
<div class="metric-detail"
|
|
207
|
+
<div class="metric-detail"><%= t("pg_reports.ui.metrics.cache_hit_detail") %></div>
|
|
205
208
|
<div class="metric-sparkline">
|
|
206
209
|
<svg id="sparkline-cache" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
207
210
|
</div>
|
|
@@ -213,14 +216,14 @@ pg_stat_statements.track = all</pre>
|
|
|
213
216
|
<div class="live-metric-card" data-metric="longrunning">
|
|
214
217
|
<div class="metric-header">
|
|
215
218
|
<span class="metric-icon" style="background: rgba(212, 160, 86, 0.12); color: var(--accent-amber);">🐢</span>
|
|
216
|
-
<span class="metric-name"
|
|
219
|
+
<span class="metric-name"><%= t("pg_reports.ui.metrics.long_queries_label") %></span>
|
|
217
220
|
</div>
|
|
218
221
|
<div class="metric-body">
|
|
219
222
|
<div class="metric-value">
|
|
220
223
|
<span id="metric-longrunning-value">-</span>
|
|
221
|
-
<span class="metric-unit"
|
|
224
|
+
<span class="metric-unit"><%= t("pg_reports.ui.metrics.queries_unit") %></span>
|
|
222
225
|
</div>
|
|
223
|
-
<div class="metric-detail"
|
|
226
|
+
<div class="metric-detail"><%= t("pg_reports.ui.metrics.long_running_threshold") %></div>
|
|
224
227
|
<div class="metric-sparkline">
|
|
225
228
|
<svg id="sparkline-longrunning" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
226
229
|
</div>
|
|
@@ -232,14 +235,14 @@ pg_stat_statements.track = all</pre>
|
|
|
232
235
|
<div class="live-metric-card" data-metric="blocked">
|
|
233
236
|
<div class="metric-header">
|
|
234
237
|
<span class="metric-icon" style="background: rgba(217, 112, 132, 0.12); color: var(--accent-rose);">🔒</span>
|
|
235
|
-
<span class="metric-name"
|
|
238
|
+
<span class="metric-name"><%= t("pg_reports.ui.metrics.blocked_label") %></span>
|
|
236
239
|
</div>
|
|
237
240
|
<div class="metric-body">
|
|
238
241
|
<div class="metric-value">
|
|
239
242
|
<span id="metric-blocked-value">-</span>
|
|
240
|
-
<span class="metric-unit"
|
|
243
|
+
<span class="metric-unit"><%= t("pg_reports.ui.metrics.processes_unit") %></span>
|
|
241
244
|
</div>
|
|
242
|
-
<div class="metric-detail"
|
|
245
|
+
<div class="metric-detail"><%= t("pg_reports.ui.metrics.waiting_for_locks") %></div>
|
|
243
246
|
<div class="metric-sparkline">
|
|
244
247
|
<svg id="sparkline-blocked" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
245
248
|
</div>
|
|
@@ -254,40 +257,40 @@ pg_stat_statements.track = all</pre>
|
|
|
254
257
|
<div class="query-monitoring-header">
|
|
255
258
|
<div class="query-monitoring-title">
|
|
256
259
|
<span class="monitoring-indicator" id="monitor-indicator"></span>
|
|
257
|
-
<span
|
|
260
|
+
<span><%= t("pg_reports.ui.monitoring.query_monitor_title") %></span>
|
|
258
261
|
<span class="session-badge" id="session-badge" style="display: none;">
|
|
259
|
-
|
|
262
|
+
<%= t("pg_reports.ui.monitoring.session_label") %> <strong id="session-id"></strong>
|
|
260
263
|
</span>
|
|
261
264
|
</div>
|
|
262
265
|
<div class="query-monitoring-controls">
|
|
263
266
|
<button class="btn btn-small btn-primary" onclick="startQueryMonitoring(this)" id="start-monitor-btn">
|
|
264
|
-
|
|
267
|
+
<%= t("pg_reports.ui.actions.start_monitoring") %>
|
|
265
268
|
</button>
|
|
266
269
|
<button class="btn btn-small btn-danger" onclick="stopQueryMonitoring(this)" id="stop-monitor-btn" style="display: none;">
|
|
267
|
-
|
|
270
|
+
<%= t("pg_reports.ui.actions.stop_monitoring") %>
|
|
268
271
|
</button>
|
|
269
272
|
<button class="btn btn-small btn-secondary" onclick="loadQueryHistory(this)" id="load-history-btn">
|
|
270
|
-
|
|
273
|
+
<%= t("pg_reports.ui.actions.load_history") %>
|
|
271
274
|
</button>
|
|
272
275
|
<div class="download-dropdown" id="monitor-download-dropdown" style="display: none;">
|
|
273
276
|
<button class="btn btn-small btn-secondary" onclick="toggleMonitorDownloadMenu(event)">
|
|
274
|
-
|
|
277
|
+
<%= t("pg_reports.ui.actions.download") %>
|
|
275
278
|
</button>
|
|
276
279
|
<div class="download-menu" id="monitor-download-menu" style="display: none;">
|
|
277
|
-
<a href="#" onclick="downloadQueryMonitor('txt'); return false;"
|
|
278
|
-
<a href="#" onclick="downloadQueryMonitor('csv'); return false;"
|
|
279
|
-
<a href="#" onclick="downloadQueryMonitor('json'); return false;"
|
|
280
|
+
<a href="#" onclick="downloadQueryMonitor('txt'); return false;"><%= t("pg_reports.ui.actions.download_text") %></a>
|
|
281
|
+
<a href="#" onclick="downloadQueryMonitor('csv'); return false;"><%= t("pg_reports.ui.actions.download_csv") %></a>
|
|
282
|
+
<a href="#" onclick="downloadQueryMonitor('json'); return false;"><%= t("pg_reports.ui.actions.download_json") %></a>
|
|
280
283
|
</div>
|
|
281
284
|
</div>
|
|
282
285
|
<span class="query-monitoring-count">
|
|
283
|
-
|
|
286
|
+
<%= t("pg_reports.ui.monitoring.queries_label") %> <strong id="query-count">0</strong>
|
|
284
287
|
</span>
|
|
285
288
|
</div>
|
|
286
289
|
</div>
|
|
287
290
|
|
|
288
291
|
<div class="query-feed" id="query-feed">
|
|
289
292
|
<div class="query-feed-empty">
|
|
290
|
-
|
|
293
|
+
<%= t("pg_reports.ui.monitoring.feed_empty") %>
|
|
291
294
|
</div>
|
|
292
295
|
</div>
|
|
293
296
|
</div>
|
|
@@ -297,7 +300,7 @@ pg_stat_statements.track = all</pre>
|
|
|
297
300
|
<div class="category-card<%= ' disabled' if category_key == :queries && !@pg_stat_status[:ready] %>">
|
|
298
301
|
<% if category_key == :queries && !@pg_stat_status[:ready] %>
|
|
299
302
|
<div class="category-warning">
|
|
300
|
-
|
|
303
|
+
<%= t("pg_reports.ui.categories.requires_pg_stat") %>
|
|
301
304
|
</div>
|
|
302
305
|
<% end %>
|
|
303
306
|
<div class="category-header">
|
|
@@ -305,7 +308,7 @@ pg_stat_statements.track = all</pre>
|
|
|
305
308
|
<%= category[:icon] %>
|
|
306
309
|
</div>
|
|
307
310
|
<span class="category-title"><%= category[:name] %></span>
|
|
308
|
-
<span class="category-count"><%= category[:reports].size %>
|
|
311
|
+
<span class="category-count"><%= category[:reports].size %> <%= t("pg_reports.ui.categories.reports_count_suffix") %></span>
|
|
309
312
|
</div>
|
|
310
313
|
|
|
311
314
|
<div class="reports-list">
|
|
@@ -865,7 +868,7 @@ pg_stat_statements.track = all</pre>
|
|
|
865
868
|
async function resetStatistics() {
|
|
866
869
|
const button = document.getElementById('confirm-reset-btn');
|
|
867
870
|
button.disabled = true;
|
|
868
|
-
button.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px;"></span>
|
|
871
|
+
button.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px;"></span> ' + PG_REPORTS_I18N.actions.resetting;
|
|
869
872
|
|
|
870
873
|
try {
|
|
871
874
|
const response = await fetch(`${pgReportsRoot}/reset_statistics`, {
|
|
@@ -882,20 +885,20 @@ pg_stat_statements.track = all</pre>
|
|
|
882
885
|
closeResetConfirmModal();
|
|
883
886
|
showToast(data.message);
|
|
884
887
|
} else {
|
|
885
|
-
showToast(data.error ||
|
|
888
|
+
showToast(data.error || PG_REPORTS_I18N.errors.reset_stats_failed, 'error');
|
|
886
889
|
button.disabled = false;
|
|
887
|
-
button.innerHTML =
|
|
890
|
+
button.innerHTML = PG_REPORTS_I18N.actions.confirm_reset;
|
|
888
891
|
}
|
|
889
892
|
} catch (error) {
|
|
890
|
-
showToast(
|
|
893
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
891
894
|
button.disabled = false;
|
|
892
|
-
button.innerHTML =
|
|
895
|
+
button.innerHTML = PG_REPORTS_I18N.actions.confirm_reset;
|
|
893
896
|
}
|
|
894
897
|
}
|
|
895
898
|
|
|
896
899
|
async function enablePgStatStatements(button) {
|
|
897
900
|
button.disabled = true;
|
|
898
|
-
button.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px;"></span>
|
|
901
|
+
button.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px;"></span> ' + PG_REPORTS_I18N.actions.creating;
|
|
899
902
|
|
|
900
903
|
try {
|
|
901
904
|
const response = await fetch(`${pgReportsRoot}/enable_pg_stat_statements`, {
|
|
@@ -918,12 +921,12 @@ pg_stat_statements.track = all</pre>
|
|
|
918
921
|
showPgStatInfo();
|
|
919
922
|
}
|
|
920
923
|
button.disabled = false;
|
|
921
|
-
button.innerHTML =
|
|
924
|
+
button.innerHTML = PG_REPORTS_I18N.actions.create_extension;
|
|
922
925
|
}
|
|
923
926
|
} catch (error) {
|
|
924
|
-
showToast(
|
|
927
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
925
928
|
button.disabled = false;
|
|
926
|
-
button.innerHTML =
|
|
929
|
+
button.innerHTML = PG_REPORTS_I18N.actions.create_extension;
|
|
927
930
|
}
|
|
928
931
|
}
|
|
929
932
|
|
|
@@ -991,19 +994,19 @@ pg_stat_statements.track = all</pre>
|
|
|
991
994
|
<div class="live-monitoring-header">
|
|
992
995
|
<div class="live-monitoring-title">
|
|
993
996
|
<span class="badge-dot error"></span>
|
|
994
|
-
<span
|
|
997
|
+
<span>${PG_REPORTS_I18N.status.monitoring_unavailable}</span>
|
|
995
998
|
</div>
|
|
996
999
|
</div>
|
|
997
1000
|
<div style="padding: 2rem; text-align: center; color: var(--text-muted);">
|
|
998
|
-
<p style="margin-bottom: 1rem;"
|
|
999
|
-
<p style="font-size: 0.875rem;"
|
|
1001
|
+
<p style="margin-bottom: 1rem;">${PG_REPORTS_I18N.errors.unable_fetch_metrics}</p>
|
|
1002
|
+
<p style="font-size: 0.875rem;">${PG_REPORTS_I18N.errors.possible_causes}</p>
|
|
1000
1003
|
<ul style="list-style: none; padding: 0; margin: 1rem 0; font-size: 0.875rem;">
|
|
1001
|
-
<li>•
|
|
1002
|
-
<li>•
|
|
1003
|
-
<li>•
|
|
1004
|
+
<li>• ${PG_REPORTS_I18N.errors.cause_permissions}</li>
|
|
1005
|
+
<li>• ${PG_REPORTS_I18N.errors.cause_views}</li>
|
|
1006
|
+
<li>• ${PG_REPORTS_I18N.errors.cause_connection}</li>
|
|
1004
1007
|
</ul>
|
|
1005
1008
|
<button class="btn btn-small btn-primary" onclick="location.reload()" style="margin-top: 1rem;">
|
|
1006
|
-
|
|
1009
|
+
${PG_REPORTS_I18N.actions.retry}
|
|
1007
1010
|
</button>
|
|
1008
1011
|
</div>
|
|
1009
1012
|
`;
|
|
@@ -1071,7 +1074,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1071
1074
|
showMetricsUnavailableMessage();
|
|
1072
1075
|
} else if (consecutiveErrors === 1) {
|
|
1073
1076
|
// Show toast on first error
|
|
1074
|
-
showToast(data.error ||
|
|
1077
|
+
showToast(data.error || PG_REPORTS_I18N.errors.fetch_metrics_failed, 'error');
|
|
1075
1078
|
}
|
|
1076
1079
|
return false;
|
|
1077
1080
|
}
|
|
@@ -1084,7 +1087,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1084
1087
|
stopPolling();
|
|
1085
1088
|
showMetricsUnavailableMessage();
|
|
1086
1089
|
} else if (consecutiveErrors === 1) {
|
|
1087
|
-
showToast(
|
|
1090
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1088
1091
|
}
|
|
1089
1092
|
return false;
|
|
1090
1093
|
}
|
|
@@ -1253,37 +1256,37 @@ pg_stat_statements.track = all</pre>
|
|
|
1253
1256
|
|
|
1254
1257
|
// VSCode (WSL Remote format)
|
|
1255
1258
|
urls.push({
|
|
1256
|
-
name:
|
|
1259
|
+
name: PG_REPORTS_I18N.settings.ide_vscode_wsl,
|
|
1257
1260
|
url: `vscode://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
|
|
1258
1261
|
});
|
|
1259
1262
|
|
|
1260
1263
|
// VSCode (direct path)
|
|
1261
1264
|
urls.push({
|
|
1262
|
-
name:
|
|
1265
|
+
name: PG_REPORTS_I18N.settings.ide_vscode,
|
|
1263
1266
|
url: `vscode://file${absolutePath}:${line}`
|
|
1264
1267
|
});
|
|
1265
1268
|
|
|
1266
1269
|
// RubyMine
|
|
1267
1270
|
urls.push({
|
|
1268
|
-
name:
|
|
1271
|
+
name: PG_REPORTS_I18N.settings.ide_rubymine,
|
|
1269
1272
|
url: `rubymine://open?file=${absolutePath}&line=${line}`
|
|
1270
1273
|
});
|
|
1271
1274
|
|
|
1272
1275
|
// IntelliJ
|
|
1273
1276
|
urls.push({
|
|
1274
|
-
name:
|
|
1277
|
+
name: PG_REPORTS_I18N.settings.ide_intellij,
|
|
1275
1278
|
url: `idea://open?file=${absolutePath}&line=${line}`
|
|
1276
1279
|
});
|
|
1277
1280
|
|
|
1278
1281
|
// Cursor (WSL Remote format)
|
|
1279
1282
|
urls.push({
|
|
1280
|
-
name:
|
|
1283
|
+
name: PG_REPORTS_I18N.settings.ide_cursor_wsl,
|
|
1281
1284
|
url: `cursor://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
|
|
1282
1285
|
});
|
|
1283
1286
|
|
|
1284
1287
|
// Cursor (direct path)
|
|
1285
1288
|
urls.push({
|
|
1286
|
-
name:
|
|
1289
|
+
name: PG_REPORTS_I18N.settings.ide_cursor,
|
|
1287
1290
|
url: `cursor://file${absolutePath}:${line}`
|
|
1288
1291
|
});
|
|
1289
1292
|
|
|
@@ -1419,7 +1422,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1419
1422
|
|
|
1420
1423
|
async function loadQueryHistory(button) {
|
|
1421
1424
|
button.disabled = true;
|
|
1422
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1425
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.loading;
|
|
1423
1426
|
|
|
1424
1427
|
try {
|
|
1425
1428
|
const response = await fetch(
|
|
@@ -1429,22 +1432,22 @@ pg_stat_statements.track = all</pre>
|
|
|
1429
1432
|
|
|
1430
1433
|
if (data.success && data.queries && data.queries.length > 0) {
|
|
1431
1434
|
renderQueryFeed(data.queries);
|
|
1432
|
-
showToast(
|
|
1435
|
+
showToast(pgReportsFormat(PG_REPORTS_I18N.success.queries_loaded, { count: data.queries.length }));
|
|
1433
1436
|
} else {
|
|
1434
|
-
showToast(
|
|
1437
|
+
showToast(PG_REPORTS_I18N.errors.no_query_history, 'error');
|
|
1435
1438
|
}
|
|
1436
1439
|
} catch (error) {
|
|
1437
1440
|
console.error('Failed to load query history:', error);
|
|
1438
|
-
showToast(
|
|
1441
|
+
showToast(PG_REPORTS_I18N.errors.load_history_failed + ' ' + error.message, 'error');
|
|
1439
1442
|
} finally {
|
|
1440
1443
|
button.disabled = false;
|
|
1441
|
-
button.innerHTML =
|
|
1444
|
+
button.innerHTML = PG_REPORTS_I18N.actions.load_history;
|
|
1442
1445
|
}
|
|
1443
1446
|
}
|
|
1444
1447
|
|
|
1445
1448
|
async function startQueryMonitoring(button) {
|
|
1446
1449
|
button.disabled = true;
|
|
1447
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1450
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.starting;
|
|
1448
1451
|
|
|
1449
1452
|
try {
|
|
1450
1453
|
const response = await fetch(`${pgReportsRoot}/query_monitor/start`, {
|
|
@@ -1464,20 +1467,20 @@ pg_stat_statements.track = all</pre>
|
|
|
1464
1467
|
startQueryMonitorPolling();
|
|
1465
1468
|
showToast(data.message);
|
|
1466
1469
|
} else {
|
|
1467
|
-
showToast(data.message ||
|
|
1470
|
+
showToast(data.message || PG_REPORTS_I18N.errors.start_monitoring_failed, 'error');
|
|
1468
1471
|
button.disabled = false;
|
|
1469
|
-
button.innerHTML =
|
|
1472
|
+
button.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1470
1473
|
}
|
|
1471
1474
|
} catch (error) {
|
|
1472
|
-
showToast(
|
|
1475
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1473
1476
|
button.disabled = false;
|
|
1474
|
-
button.innerHTML =
|
|
1477
|
+
button.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1475
1478
|
}
|
|
1476
1479
|
}
|
|
1477
1480
|
|
|
1478
1481
|
async function stopQueryMonitoring(button) {
|
|
1479
1482
|
button.disabled = true;
|
|
1480
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1483
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.stopping;
|
|
1481
1484
|
|
|
1482
1485
|
try {
|
|
1483
1486
|
const response = await fetch(`${pgReportsRoot}/query_monitor/stop`, {
|
|
@@ -1498,14 +1501,14 @@ pg_stat_statements.track = all</pre>
|
|
|
1498
1501
|
updateQueryMonitorUI(false);
|
|
1499
1502
|
showToast(data.message);
|
|
1500
1503
|
} else {
|
|
1501
|
-
showToast(data.message ||
|
|
1504
|
+
showToast(data.message || PG_REPORTS_I18N.errors.stop_monitoring_failed, 'error');
|
|
1502
1505
|
button.disabled = false;
|
|
1503
|
-
button.innerHTML =
|
|
1506
|
+
button.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1504
1507
|
}
|
|
1505
1508
|
} catch (error) {
|
|
1506
|
-
showToast(
|
|
1509
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1507
1510
|
button.disabled = false;
|
|
1508
|
-
button.innerHTML =
|
|
1511
|
+
button.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1509
1512
|
}
|
|
1510
1513
|
}
|
|
1511
1514
|
|
|
@@ -1527,7 +1530,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1527
1530
|
startBtn.style.display = 'none';
|
|
1528
1531
|
stopBtn.style.display = 'inline-block';
|
|
1529
1532
|
stopBtn.disabled = false;
|
|
1530
|
-
stopBtn.innerHTML =
|
|
1533
|
+
stopBtn.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1531
1534
|
loadHistoryBtn.style.display = 'none'; // Hide when monitoring active
|
|
1532
1535
|
sessionBadge.style.display = 'inline-block';
|
|
1533
1536
|
sessionIdEl.textContent = currentSessionId ? currentSessionId.substring(0, 8) : '';
|
|
@@ -1536,7 +1539,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1536
1539
|
indicator.classList.remove('active');
|
|
1537
1540
|
startBtn.style.display = 'inline-block';
|
|
1538
1541
|
startBtn.disabled = false;
|
|
1539
|
-
startBtn.innerHTML =
|
|
1542
|
+
startBtn.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1540
1543
|
stopBtn.style.display = 'none';
|
|
1541
1544
|
loadHistoryBtn.style.display = 'inline-block'; // Show when monitoring stopped
|
|
1542
1545
|
sessionBadge.style.display = 'none';
|
|
@@ -1547,7 +1550,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1547
1550
|
|
|
1548
1551
|
// Only clear feed if no queries captured
|
|
1549
1552
|
if (!hasQueries) {
|
|
1550
|
-
feed.innerHTML = '<div class="query-feed-empty">
|
|
1553
|
+
feed.innerHTML = '<div class="query-feed-empty">' + PG_REPORTS_I18N.monitoring.feed_empty + '</div>';
|
|
1551
1554
|
}
|
|
1552
1555
|
}
|
|
1553
1556
|
}
|
|
@@ -1614,7 +1617,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1614
1617
|
renderQueryFeed(data.queries);
|
|
1615
1618
|
} else if (!data.success) {
|
|
1616
1619
|
// Server returned an error (e.g., "Monitoring not active")
|
|
1617
|
-
const errorMsg = data.message || data.error ||
|
|
1620
|
+
const errorMsg = data.message || data.error || PG_REPORTS_I18N.errors.query_monitoring_error;
|
|
1618
1621
|
showToast(errorMsg, 'error');
|
|
1619
1622
|
|
|
1620
1623
|
// Stop polling and update UI since monitoring is not active
|
|
@@ -1625,7 +1628,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1625
1628
|
}
|
|
1626
1629
|
} catch (error) {
|
|
1627
1630
|
console.error('Failed to fetch query feed:', error);
|
|
1628
|
-
showToast(
|
|
1631
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1629
1632
|
}
|
|
1630
1633
|
}
|
|
1631
1634
|
|
|
@@ -1633,7 +1636,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1633
1636
|
const feed = document.getElementById('query-feed');
|
|
1634
1637
|
|
|
1635
1638
|
if (queries.length === 0) {
|
|
1636
|
-
feed.innerHTML = '<div class="query-feed-empty">
|
|
1639
|
+
feed.innerHTML = '<div class="query-feed-empty">' + PG_REPORTS_I18N.monitoring.feed_no_queries + '</div>';
|
|
1637
1640
|
queryCount = 0;
|
|
1638
1641
|
document.getElementById('query-count').textContent = '0';
|
|
1639
1642
|
return;
|
|
@@ -1653,7 +1656,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1653
1656
|
const timestamp = new Date(query.timestamp).toLocaleTimeString();
|
|
1654
1657
|
const queryId = `query-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1655
1658
|
|
|
1656
|
-
let sourceInfo =
|
|
1659
|
+
let sourceInfo = PG_REPORTS_I18N.monitoring.unknown_source;
|
|
1657
1660
|
if (query.source_location && query.source_location.file) {
|
|
1658
1661
|
const file = query.source_location.file;
|
|
1659
1662
|
const line = query.source_location.line;
|
|
@@ -1677,7 +1680,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1677
1680
|
<span class="query-duration ${durationClass}">${duration.toFixed(2)}ms</span>
|
|
1678
1681
|
<span class="query-source">${sourceInfo}</span>
|
|
1679
1682
|
</div>
|
|
1680
|
-
<button class="query-expand-btn" onclick="toggleQueryExpand('${queryId}')" title="
|
|
1683
|
+
<button class="query-expand-btn" onclick="toggleQueryExpand('${queryId}')" title="${PG_REPORTS_I18N.monitoring.expand_collapse_title}">
|
|
1681
1684
|
<span id="${queryId}-icon">▼</span>
|
|
1682
1685
|
</button>
|
|
1683
1686
|
</div>
|