pg_reports 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +123 -370
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +135 -69
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +105 -55
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +49 -11
- data/app/views/pg_reports/dashboard/index.html.erb +123 -114
- data/app/views/pg_reports/dashboard/show.html.erb +30 -26
- data/config/locales/en.yml +597 -0
- data/config/locales/ru.yml +562 -0
- data/config/locales/uk.yml +607 -0
- data/lib/pg_reports/compatibility.rb +63 -0
- data/lib/pg_reports/configuration.rb +2 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +112 -5
- data/lib/pg_reports/definitions/indexes/fk_without_indexes.yml +30 -0
- data/lib/pg_reports/definitions/indexes/index_correlation.yml +31 -0
- data/lib/pg_reports/definitions/indexes/inefficient_indexes.yml +45 -0
- data/lib/pg_reports/definitions/queries/temp_file_queries.yml +39 -0
- 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/system/wraparound_risk.yml +31 -0
- data/lib/pg_reports/definitions/tables/tables_without_pk.yml +28 -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/engine.rb +6 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/indexes.rb +3 -0
- data/lib/pg_reports/modules/queries.rb +1 -0
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +27 -0
- data/lib/pg_reports/modules/tables.rb +1 -0
- data/lib/pg_reports/query_monitor.rb +64 -36
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/indexes/fk_without_indexes.sql +23 -0
- data/lib/pg_reports/sql/indexes/index_correlation.sql +27 -0
- data/lib/pg_reports/sql/indexes/inefficient_indexes.sql +22 -0
- data/lib/pg_reports/sql/queries/temp_file_queries.sql +16 -0
- 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/system/checkpoint_stats.sql +20 -0
- data/lib/pg_reports/sql/system/checkpoint_stats_legacy.sql +19 -0
- data/lib/pg_reports/sql/system/wraparound_risk.sql +21 -0
- data/lib/pg_reports/sql/tables/tables_without_pk.sql +20 -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
- data/lib/pg_reports.rb +5 -0
- metadata +24 -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">
|
|
@@ -313,7 +316,10 @@ pg_stat_statements.track = all</pre>
|
|
|
313
316
|
<% if category_key == :queries && !@pg_stat_status[:ready] %>
|
|
314
317
|
<div class="report-link disabled">
|
|
315
318
|
<div class="report-link-info">
|
|
316
|
-
<span class="report-link-name"
|
|
319
|
+
<span class="report-link-name">
|
|
320
|
+
<%= report[:name] %>
|
|
321
|
+
<% if report[:new] %><span class="report-badge-new">NEW</span><% end %>
|
|
322
|
+
</span>
|
|
317
323
|
<span class="report-link-desc"><%= report[:description] %></span>
|
|
318
324
|
</div>
|
|
319
325
|
<span class="lock">🔒</span>
|
|
@@ -321,7 +327,10 @@ pg_stat_statements.track = all</pre>
|
|
|
321
327
|
<% else %>
|
|
322
328
|
<%= link_to report_path(category: category_key, report: report_key), class: "report-link" do %>
|
|
323
329
|
<div class="report-link-info">
|
|
324
|
-
<span class="report-link-name"
|
|
330
|
+
<span class="report-link-name">
|
|
331
|
+
<%= report[:name] %>
|
|
332
|
+
<% if report[:new] %><span class="report-badge-new">NEW</span><% end %>
|
|
333
|
+
</span>
|
|
325
334
|
<span class="report-link-desc"><%= report[:description] %></span>
|
|
326
335
|
</div>
|
|
327
336
|
<span class="arrow">→</span>
|
|
@@ -404,7 +413,7 @@ pg_stat_statements.track = all</pre>
|
|
|
404
413
|
margin: -1.5rem -1.5rem 1rem -1.5rem;
|
|
405
414
|
background: rgba(245, 158, 11, 0.1);
|
|
406
415
|
border-bottom: 1px solid rgba(245, 158, 11, 0.2);
|
|
407
|
-
border-radius:
|
|
416
|
+
border-radius: 6px 6px 0 0;
|
|
408
417
|
color: var(--accent-amber);
|
|
409
418
|
font-size: 0.8rem;
|
|
410
419
|
font-weight: 500;
|
|
@@ -418,7 +427,7 @@ pg_stat_statements.track = all</pre>
|
|
|
418
427
|
padding: 0.75rem 1rem;
|
|
419
428
|
background: var(--bg-tertiary);
|
|
420
429
|
border: 1px solid transparent;
|
|
421
|
-
border-radius:
|
|
430
|
+
border-radius: 6px;
|
|
422
431
|
color: var(--text-muted);
|
|
423
432
|
font-size: 0.9rem;
|
|
424
433
|
cursor: not-allowed;
|
|
@@ -469,7 +478,7 @@ pg_stat_statements.track = all</pre>
|
|
|
469
478
|
.modal-content {
|
|
470
479
|
background: var(--bg-card);
|
|
471
480
|
border: 1px solid var(--border-color);
|
|
472
|
-
border-radius:
|
|
481
|
+
border-radius: 6px;
|
|
473
482
|
max-width: 600px;
|
|
474
483
|
width: 90%;
|
|
475
484
|
max-height: 80vh;
|
|
@@ -583,7 +592,7 @@ pg_stat_statements.track = all</pre>
|
|
|
583
592
|
margin-bottom: 2rem;
|
|
584
593
|
background: var(--bg-card);
|
|
585
594
|
border: 1px solid var(--border-color);
|
|
586
|
-
border-radius:
|
|
595
|
+
border-radius: 6px;
|
|
587
596
|
padding: 1.25rem 1.5rem;
|
|
588
597
|
}
|
|
589
598
|
|
|
@@ -691,7 +700,7 @@ pg_stat_statements.track = all</pre>
|
|
|
691
700
|
.live-metric-card {
|
|
692
701
|
background: var(--bg-tertiary);
|
|
693
702
|
border: 1px solid var(--border-color);
|
|
694
|
-
border-radius:
|
|
703
|
+
border-radius: 6px;
|
|
695
704
|
padding: 1rem;
|
|
696
705
|
position: relative;
|
|
697
706
|
transition: all 0.2s;
|
|
@@ -798,7 +807,7 @@ pg_stat_statements.track = all</pre>
|
|
|
798
807
|
padding: 1rem 1.5rem;
|
|
799
808
|
background: var(--bg-card);
|
|
800
809
|
border: 1px solid var(--border-color);
|
|
801
|
-
border-radius:
|
|
810
|
+
border-radius: 6px;
|
|
802
811
|
color: var(--text-primary);
|
|
803
812
|
font-size: 0.875rem;
|
|
804
813
|
font-weight: 500;
|
|
@@ -859,7 +868,7 @@ pg_stat_statements.track = all</pre>
|
|
|
859
868
|
async function resetStatistics() {
|
|
860
869
|
const button = document.getElementById('confirm-reset-btn');
|
|
861
870
|
button.disabled = true;
|
|
862
|
-
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;
|
|
863
872
|
|
|
864
873
|
try {
|
|
865
874
|
const response = await fetch(`${pgReportsRoot}/reset_statistics`, {
|
|
@@ -876,20 +885,20 @@ pg_stat_statements.track = all</pre>
|
|
|
876
885
|
closeResetConfirmModal();
|
|
877
886
|
showToast(data.message);
|
|
878
887
|
} else {
|
|
879
|
-
showToast(data.error ||
|
|
888
|
+
showToast(data.error || PG_REPORTS_I18N.errors.reset_stats_failed, 'error');
|
|
880
889
|
button.disabled = false;
|
|
881
|
-
button.innerHTML =
|
|
890
|
+
button.innerHTML = PG_REPORTS_I18N.actions.confirm_reset;
|
|
882
891
|
}
|
|
883
892
|
} catch (error) {
|
|
884
|
-
showToast(
|
|
893
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
885
894
|
button.disabled = false;
|
|
886
|
-
button.innerHTML =
|
|
895
|
+
button.innerHTML = PG_REPORTS_I18N.actions.confirm_reset;
|
|
887
896
|
}
|
|
888
897
|
}
|
|
889
898
|
|
|
890
899
|
async function enablePgStatStatements(button) {
|
|
891
900
|
button.disabled = true;
|
|
892
|
-
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;
|
|
893
902
|
|
|
894
903
|
try {
|
|
895
904
|
const response = await fetch(`${pgReportsRoot}/enable_pg_stat_statements`, {
|
|
@@ -912,12 +921,12 @@ pg_stat_statements.track = all</pre>
|
|
|
912
921
|
showPgStatInfo();
|
|
913
922
|
}
|
|
914
923
|
button.disabled = false;
|
|
915
|
-
button.innerHTML =
|
|
924
|
+
button.innerHTML = PG_REPORTS_I18N.actions.create_extension;
|
|
916
925
|
}
|
|
917
926
|
} catch (error) {
|
|
918
|
-
showToast(
|
|
927
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
919
928
|
button.disabled = false;
|
|
920
|
-
button.innerHTML =
|
|
929
|
+
button.innerHTML = PG_REPORTS_I18N.actions.create_extension;
|
|
921
930
|
}
|
|
922
931
|
}
|
|
923
932
|
|
|
@@ -985,19 +994,19 @@ pg_stat_statements.track = all</pre>
|
|
|
985
994
|
<div class="live-monitoring-header">
|
|
986
995
|
<div class="live-monitoring-title">
|
|
987
996
|
<span class="badge-dot error"></span>
|
|
988
|
-
<span
|
|
997
|
+
<span>${PG_REPORTS_I18N.status.monitoring_unavailable}</span>
|
|
989
998
|
</div>
|
|
990
999
|
</div>
|
|
991
1000
|
<div style="padding: 2rem; text-align: center; color: var(--text-muted);">
|
|
992
|
-
<p style="margin-bottom: 1rem;"
|
|
993
|
-
<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>
|
|
994
1003
|
<ul style="list-style: none; padding: 0; margin: 1rem 0; font-size: 0.875rem;">
|
|
995
|
-
<li>•
|
|
996
|
-
<li>•
|
|
997
|
-
<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>
|
|
998
1007
|
</ul>
|
|
999
1008
|
<button class="btn btn-small btn-primary" onclick="location.reload()" style="margin-top: 1rem;">
|
|
1000
|
-
|
|
1009
|
+
${PG_REPORTS_I18N.actions.retry}
|
|
1001
1010
|
</button>
|
|
1002
1011
|
</div>
|
|
1003
1012
|
`;
|
|
@@ -1065,7 +1074,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1065
1074
|
showMetricsUnavailableMessage();
|
|
1066
1075
|
} else if (consecutiveErrors === 1) {
|
|
1067
1076
|
// Show toast on first error
|
|
1068
|
-
showToast(data.error ||
|
|
1077
|
+
showToast(data.error || PG_REPORTS_I18N.errors.fetch_metrics_failed, 'error');
|
|
1069
1078
|
}
|
|
1070
1079
|
return false;
|
|
1071
1080
|
}
|
|
@@ -1078,7 +1087,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1078
1087
|
stopPolling();
|
|
1079
1088
|
showMetricsUnavailableMessage();
|
|
1080
1089
|
} else if (consecutiveErrors === 1) {
|
|
1081
|
-
showToast(
|
|
1090
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1082
1091
|
}
|
|
1083
1092
|
return false;
|
|
1084
1093
|
}
|
|
@@ -1247,37 +1256,37 @@ pg_stat_statements.track = all</pre>
|
|
|
1247
1256
|
|
|
1248
1257
|
// VSCode (WSL Remote format)
|
|
1249
1258
|
urls.push({
|
|
1250
|
-
name:
|
|
1259
|
+
name: PG_REPORTS_I18N.settings.ide_vscode_wsl,
|
|
1251
1260
|
url: `vscode://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
|
|
1252
1261
|
});
|
|
1253
1262
|
|
|
1254
1263
|
// VSCode (direct path)
|
|
1255
1264
|
urls.push({
|
|
1256
|
-
name:
|
|
1265
|
+
name: PG_REPORTS_I18N.settings.ide_vscode,
|
|
1257
1266
|
url: `vscode://file${absolutePath}:${line}`
|
|
1258
1267
|
});
|
|
1259
1268
|
|
|
1260
1269
|
// RubyMine
|
|
1261
1270
|
urls.push({
|
|
1262
|
-
name:
|
|
1271
|
+
name: PG_REPORTS_I18N.settings.ide_rubymine,
|
|
1263
1272
|
url: `rubymine://open?file=${absolutePath}&line=${line}`
|
|
1264
1273
|
});
|
|
1265
1274
|
|
|
1266
1275
|
// IntelliJ
|
|
1267
1276
|
urls.push({
|
|
1268
|
-
name:
|
|
1277
|
+
name: PG_REPORTS_I18N.settings.ide_intellij,
|
|
1269
1278
|
url: `idea://open?file=${absolutePath}&line=${line}`
|
|
1270
1279
|
});
|
|
1271
1280
|
|
|
1272
1281
|
// Cursor (WSL Remote format)
|
|
1273
1282
|
urls.push({
|
|
1274
|
-
name:
|
|
1283
|
+
name: PG_REPORTS_I18N.settings.ide_cursor_wsl,
|
|
1275
1284
|
url: `cursor://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
|
|
1276
1285
|
});
|
|
1277
1286
|
|
|
1278
1287
|
// Cursor (direct path)
|
|
1279
1288
|
urls.push({
|
|
1280
|
-
name:
|
|
1289
|
+
name: PG_REPORTS_I18N.settings.ide_cursor,
|
|
1281
1290
|
url: `cursor://file${absolutePath}:${line}`
|
|
1282
1291
|
});
|
|
1283
1292
|
|
|
@@ -1413,7 +1422,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1413
1422
|
|
|
1414
1423
|
async function loadQueryHistory(button) {
|
|
1415
1424
|
button.disabled = true;
|
|
1416
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1425
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.loading;
|
|
1417
1426
|
|
|
1418
1427
|
try {
|
|
1419
1428
|
const response = await fetch(
|
|
@@ -1423,22 +1432,22 @@ pg_stat_statements.track = all</pre>
|
|
|
1423
1432
|
|
|
1424
1433
|
if (data.success && data.queries && data.queries.length > 0) {
|
|
1425
1434
|
renderQueryFeed(data.queries);
|
|
1426
|
-
showToast(
|
|
1435
|
+
showToast(pgReportsFormat(PG_REPORTS_I18N.success.queries_loaded, { count: data.queries.length }));
|
|
1427
1436
|
} else {
|
|
1428
|
-
showToast(
|
|
1437
|
+
showToast(PG_REPORTS_I18N.errors.no_query_history, 'error');
|
|
1429
1438
|
}
|
|
1430
1439
|
} catch (error) {
|
|
1431
1440
|
console.error('Failed to load query history:', error);
|
|
1432
|
-
showToast(
|
|
1441
|
+
showToast(PG_REPORTS_I18N.errors.load_history_failed + ' ' + error.message, 'error');
|
|
1433
1442
|
} finally {
|
|
1434
1443
|
button.disabled = false;
|
|
1435
|
-
button.innerHTML =
|
|
1444
|
+
button.innerHTML = PG_REPORTS_I18N.actions.load_history;
|
|
1436
1445
|
}
|
|
1437
1446
|
}
|
|
1438
1447
|
|
|
1439
1448
|
async function startQueryMonitoring(button) {
|
|
1440
1449
|
button.disabled = true;
|
|
1441
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1450
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.starting;
|
|
1442
1451
|
|
|
1443
1452
|
try {
|
|
1444
1453
|
const response = await fetch(`${pgReportsRoot}/query_monitor/start`, {
|
|
@@ -1458,20 +1467,20 @@ pg_stat_statements.track = all</pre>
|
|
|
1458
1467
|
startQueryMonitorPolling();
|
|
1459
1468
|
showToast(data.message);
|
|
1460
1469
|
} else {
|
|
1461
|
-
showToast(data.message ||
|
|
1470
|
+
showToast(data.message || PG_REPORTS_I18N.errors.start_monitoring_failed, 'error');
|
|
1462
1471
|
button.disabled = false;
|
|
1463
|
-
button.innerHTML =
|
|
1472
|
+
button.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1464
1473
|
}
|
|
1465
1474
|
} catch (error) {
|
|
1466
|
-
showToast(
|
|
1475
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1467
1476
|
button.disabled = false;
|
|
1468
|
-
button.innerHTML =
|
|
1477
|
+
button.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1469
1478
|
}
|
|
1470
1479
|
}
|
|
1471
1480
|
|
|
1472
1481
|
async function stopQueryMonitoring(button) {
|
|
1473
1482
|
button.disabled = true;
|
|
1474
|
-
button.innerHTML = '<span class="spinner"></span>
|
|
1483
|
+
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.stopping;
|
|
1475
1484
|
|
|
1476
1485
|
try {
|
|
1477
1486
|
const response = await fetch(`${pgReportsRoot}/query_monitor/stop`, {
|
|
@@ -1492,14 +1501,14 @@ pg_stat_statements.track = all</pre>
|
|
|
1492
1501
|
updateQueryMonitorUI(false);
|
|
1493
1502
|
showToast(data.message);
|
|
1494
1503
|
} else {
|
|
1495
|
-
showToast(data.message ||
|
|
1504
|
+
showToast(data.message || PG_REPORTS_I18N.errors.stop_monitoring_failed, 'error');
|
|
1496
1505
|
button.disabled = false;
|
|
1497
|
-
button.innerHTML =
|
|
1506
|
+
button.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1498
1507
|
}
|
|
1499
1508
|
} catch (error) {
|
|
1500
|
-
showToast(
|
|
1509
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1501
1510
|
button.disabled = false;
|
|
1502
|
-
button.innerHTML =
|
|
1511
|
+
button.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1503
1512
|
}
|
|
1504
1513
|
}
|
|
1505
1514
|
|
|
@@ -1521,7 +1530,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1521
1530
|
startBtn.style.display = 'none';
|
|
1522
1531
|
stopBtn.style.display = 'inline-block';
|
|
1523
1532
|
stopBtn.disabled = false;
|
|
1524
|
-
stopBtn.innerHTML =
|
|
1533
|
+
stopBtn.innerHTML = PG_REPORTS_I18N.actions.stop_monitoring;
|
|
1525
1534
|
loadHistoryBtn.style.display = 'none'; // Hide when monitoring active
|
|
1526
1535
|
sessionBadge.style.display = 'inline-block';
|
|
1527
1536
|
sessionIdEl.textContent = currentSessionId ? currentSessionId.substring(0, 8) : '';
|
|
@@ -1530,7 +1539,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1530
1539
|
indicator.classList.remove('active');
|
|
1531
1540
|
startBtn.style.display = 'inline-block';
|
|
1532
1541
|
startBtn.disabled = false;
|
|
1533
|
-
startBtn.innerHTML =
|
|
1542
|
+
startBtn.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1534
1543
|
stopBtn.style.display = 'none';
|
|
1535
1544
|
loadHistoryBtn.style.display = 'inline-block'; // Show when monitoring stopped
|
|
1536
1545
|
sessionBadge.style.display = 'none';
|
|
@@ -1541,7 +1550,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1541
1550
|
|
|
1542
1551
|
// Only clear feed if no queries captured
|
|
1543
1552
|
if (!hasQueries) {
|
|
1544
|
-
feed.innerHTML = '<div class="query-feed-empty">
|
|
1553
|
+
feed.innerHTML = '<div class="query-feed-empty">' + PG_REPORTS_I18N.monitoring.feed_empty + '</div>';
|
|
1545
1554
|
}
|
|
1546
1555
|
}
|
|
1547
1556
|
}
|
|
@@ -1608,7 +1617,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1608
1617
|
renderQueryFeed(data.queries);
|
|
1609
1618
|
} else if (!data.success) {
|
|
1610
1619
|
// Server returned an error (e.g., "Monitoring not active")
|
|
1611
|
-
const errorMsg = data.message || data.error ||
|
|
1620
|
+
const errorMsg = data.message || data.error || PG_REPORTS_I18N.errors.query_monitoring_error;
|
|
1612
1621
|
showToast(errorMsg, 'error');
|
|
1613
1622
|
|
|
1614
1623
|
// Stop polling and update UI since monitoring is not active
|
|
@@ -1619,7 +1628,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1619
1628
|
}
|
|
1620
1629
|
} catch (error) {
|
|
1621
1630
|
console.error('Failed to fetch query feed:', error);
|
|
1622
|
-
showToast(
|
|
1631
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1623
1632
|
}
|
|
1624
1633
|
}
|
|
1625
1634
|
|
|
@@ -1627,7 +1636,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1627
1636
|
const feed = document.getElementById('query-feed');
|
|
1628
1637
|
|
|
1629
1638
|
if (queries.length === 0) {
|
|
1630
|
-
feed.innerHTML = '<div class="query-feed-empty">
|
|
1639
|
+
feed.innerHTML = '<div class="query-feed-empty">' + PG_REPORTS_I18N.monitoring.feed_no_queries + '</div>';
|
|
1631
1640
|
queryCount = 0;
|
|
1632
1641
|
document.getElementById('query-count').textContent = '0';
|
|
1633
1642
|
return;
|
|
@@ -1647,7 +1656,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1647
1656
|
const timestamp = new Date(query.timestamp).toLocaleTimeString();
|
|
1648
1657
|
const queryId = `query-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1649
1658
|
|
|
1650
|
-
let sourceInfo =
|
|
1659
|
+
let sourceInfo = PG_REPORTS_I18N.monitoring.unknown_source;
|
|
1651
1660
|
if (query.source_location && query.source_location.file) {
|
|
1652
1661
|
const file = query.source_location.file;
|
|
1653
1662
|
const line = query.source_location.line;
|
|
@@ -1671,7 +1680,7 @@ pg_stat_statements.track = all</pre>
|
|
|
1671
1680
|
<span class="query-duration ${durationClass}">${duration.toFixed(2)}ms</span>
|
|
1672
1681
|
<span class="query-source">${sourceInfo}</span>
|
|
1673
1682
|
</div>
|
|
1674
|
-
<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}">
|
|
1675
1684
|
<span id="${queryId}-icon">▼</span>
|
|
1676
1685
|
</button>
|
|
1677
1686
|
</div>
|