pg_reports 0.8.2 → 0.9.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 +38 -0
- data/app/controllers/pg_reports/dashboard_controller.rb +57 -6
- data/app/views/layouts/pg_reports/application.html.erb +616 -201
- data/app/views/pg_reports/dashboard/_database_selector.html.erb +2 -1
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +10 -7
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +100 -51
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +392 -476
- data/app/views/pg_reports/dashboard/index.html.erb +190 -388
- data/app/views/pg_reports/dashboard/show.html.erb +13 -11
- data/config/brakeman.ignore +24 -1
- data/config/locales/en.yml +53 -48
- data/config/locales/ru.yml +53 -48
- data/config/locales/uk.yml +53 -48
- data/lib/pg_reports/connection/error_translator.rb +43 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +10 -0
- data/lib/pg_reports/explain_analyzer.rb +0 -3
- data/lib/pg_reports/query_monitor.rb +15 -3
- data/lib/pg_reports/version.rb +1 -1
- metadata +1 -1
|
@@ -12,26 +12,25 @@
|
|
|
12
12
|
|
|
13
13
|
<header class="header">
|
|
14
14
|
<div class="logo">
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</h1>
|
|
21
|
-
<span class="logo-subtitle"><%= t("pg_reports.ui.branding.subtitle") %></span>
|
|
22
|
-
</div>
|
|
15
|
+
<h1>
|
|
16
|
+
<%= t("pg_reports.ui.branding.title") %>
|
|
17
|
+
<span class="logo-version">v<%= PgReports::VERSION %></span>
|
|
18
|
+
</h1>
|
|
19
|
+
<span class="logo-subtitle"><%= t("pg_reports.ui.branding.subtitle") %></span>
|
|
23
20
|
</div>
|
|
24
21
|
|
|
25
22
|
<div class="header-actions">
|
|
26
|
-
<button class="btn btn-
|
|
23
|
+
<button class="btn btn-primary" onclick="showRunQueryModal()" id="run-query-open-btn">
|
|
27
24
|
<%= t("pg_reports.ui.actions.run_query") %>
|
|
28
25
|
</button>
|
|
29
26
|
<% if @pg_stat_status[:ready] %>
|
|
30
|
-
<button class="btn btn-
|
|
27
|
+
<button class="btn btn-muted" onclick="showResetConfirmModal()" id="reset-btn">
|
|
31
28
|
<%= t("pg_reports.ui.actions.reset_statistics") %>
|
|
32
29
|
</button>
|
|
33
30
|
<% end %>
|
|
34
|
-
<button class="btn-
|
|
31
|
+
<button type="button" class="btn-icon" onclick="showIdeSettingsModal()" title="<%= t("pg_reports.ui.actions.ide_settings_button_title") %>" aria-label="<%= t("pg_reports.ui.actions.ide_settings_button_title") %>">
|
|
32
|
+
<svg class="icon icon-lg"><use href="#i-settings"></use></svg>
|
|
33
|
+
</button>
|
|
35
34
|
|
|
36
35
|
<% if !@pg_stat_status[:connected] %>
|
|
37
36
|
<div class="header-badge" id="pg-stat-badge">
|
|
@@ -48,14 +47,14 @@
|
|
|
48
47
|
onclick="showPgStatInfo()" title="<%= t("pg_reports.ui.status.click_for_details") %>">
|
|
49
48
|
<span class="badge-dot warning"></span>
|
|
50
49
|
<span><%= t("pg_reports.ui.status.not_preloaded") %></span>
|
|
51
|
-
<
|
|
50
|
+
<svg class="icon badge-info-icon" aria-hidden="true"><use href="#i-info"></use></svg>
|
|
52
51
|
</button>
|
|
53
52
|
<% else %>
|
|
54
53
|
<button type="button" class="header-badge header-badge-clickable" id="pg-stat-badge"
|
|
55
54
|
onclick="showCreateExtensionModal()" title="<%= t("pg_reports.ui.status.click_for_details") %>">
|
|
56
55
|
<span class="badge-dot warning"></span>
|
|
57
56
|
<span><%= t("pg_reports.ui.status.extension_missing") %></span>
|
|
58
|
-
<
|
|
57
|
+
<svg class="icon badge-info-icon" aria-hidden="true"><use href="#i-info"></use></svg>
|
|
59
58
|
</button>
|
|
60
59
|
<% end %>
|
|
61
60
|
</div>
|
|
@@ -191,15 +190,13 @@ pg_stat_statements.track = all</pre>
|
|
|
191
190
|
<div id="run-query-content"></div>
|
|
192
191
|
<% else %>
|
|
193
192
|
<div class="error-message">
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<p style="margin-top: 0.75rem;"><%= t("pg_reports.ui.modals.query_execution_disabled_env_note") %></p>
|
|
202
|
-
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">PG_REPORTS_ALLOW_RAW_QUERY_EXECUTION=true</code>
|
|
193
|
+
<p class="error-title"><%= t("pg_reports.ui.modals.query_execution_disabled_title") %></p>
|
|
194
|
+
<p><%= t("pg_reports.ui.modals.query_execution_disabled_intro") %></p>
|
|
195
|
+
<pre class="code-block">PgReports.configure do |config|
|
|
196
|
+
config.allow_raw_query_execution = true
|
|
197
|
+
end</pre>
|
|
198
|
+
<p><%= t("pg_reports.ui.modals.query_execution_disabled_env_note") %></p>
|
|
199
|
+
<pre class="code-block">PG_REPORTS_ALLOW_RAW_QUERY_EXECUTION=true</pre>
|
|
203
200
|
</div>
|
|
204
201
|
<% end %>
|
|
205
202
|
</div>
|
|
@@ -217,8 +214,8 @@ end
|
|
|
217
214
|
</div>
|
|
218
215
|
<div class="live-monitoring-controls">
|
|
219
216
|
<span class="live-monitoring-interval"><%= t("pg_reports.ui.monitoring.update_interval") %></span>
|
|
220
|
-
<button class="btn-
|
|
221
|
-
<
|
|
217
|
+
<button type="button" class="btn-icon btn-small" onclick="toggleLiveMonitoring()" title="<%= t("pg_reports.ui.monitoring.toggle_title") %>" aria-label="<%= t("pg_reports.ui.monitoring.toggle_title") %>">
|
|
218
|
+
<svg class="icon" id="toggle-icon"><use href="#i-pause"></use></svg>
|
|
222
219
|
</button>
|
|
223
220
|
</div>
|
|
224
221
|
</div>
|
|
@@ -227,7 +224,7 @@ end
|
|
|
227
224
|
<!-- Connections -->
|
|
228
225
|
<div class="live-metric-card" data-metric="connections">
|
|
229
226
|
<div class="metric-header">
|
|
230
|
-
<
|
|
227
|
+
<svg class="icon metric-icon"><use href="#i-link"></use></svg>
|
|
231
228
|
<span class="metric-name"><%= t("pg_reports.ui.metrics.connections_label") %></span>
|
|
232
229
|
</div>
|
|
233
230
|
<div class="metric-body">
|
|
@@ -248,7 +245,7 @@ end
|
|
|
248
245
|
<!-- TPS -->
|
|
249
246
|
<div class="live-metric-card" data-metric="tps">
|
|
250
247
|
<div class="metric-header">
|
|
251
|
-
<
|
|
248
|
+
<svg class="icon metric-icon"><use href="#i-activity"></use></svg>
|
|
252
249
|
<span class="metric-name"><%= t("pg_reports.ui.metrics.tps_label") %></span>
|
|
253
250
|
</div>
|
|
254
251
|
<div class="metric-body">
|
|
@@ -269,7 +266,7 @@ end
|
|
|
269
266
|
<!-- Cache Hit Ratio -->
|
|
270
267
|
<div class="live-metric-card" data-metric="cache">
|
|
271
268
|
<div class="metric-header">
|
|
272
|
-
<
|
|
269
|
+
<svg class="icon metric-icon"><use href="#i-database"></use></svg>
|
|
273
270
|
<span class="metric-name"><%= t("pg_reports.ui.metrics.cache_hit_label") %></span>
|
|
274
271
|
</div>
|
|
275
272
|
<div class="metric-body">
|
|
@@ -288,7 +285,7 @@ end
|
|
|
288
285
|
<!-- Long Running Queries -->
|
|
289
286
|
<div class="live-metric-card" data-metric="longrunning">
|
|
290
287
|
<div class="metric-header">
|
|
291
|
-
<
|
|
288
|
+
<svg class="icon metric-icon"><use href="#i-clock"></use></svg>
|
|
292
289
|
<span class="metric-name"><%= t("pg_reports.ui.metrics.long_queries_label") %></span>
|
|
293
290
|
</div>
|
|
294
291
|
<div class="metric-body">
|
|
@@ -307,7 +304,7 @@ end
|
|
|
307
304
|
<!-- Blocked Queries -->
|
|
308
305
|
<div class="live-metric-card" data-metric="blocked">
|
|
309
306
|
<div class="metric-header">
|
|
310
|
-
<
|
|
307
|
+
<svg class="icon metric-icon"><use href="#i-lock"></use></svg>
|
|
311
308
|
<span class="metric-name"><%= t("pg_reports.ui.metrics.blocked_label") %></span>
|
|
312
309
|
</div>
|
|
313
310
|
<div class="metric-body">
|
|
@@ -343,17 +340,17 @@ end
|
|
|
343
340
|
</span>
|
|
344
341
|
</div>
|
|
345
342
|
<div class="query-monitoring-controls">
|
|
346
|
-
<button class="btn btn-
|
|
343
|
+
<button class="btn btn-primary" onclick="startQueryMonitoring(this)" id="start-monitor-btn">
|
|
347
344
|
<%= t("pg_reports.ui.actions.start_monitoring") %>
|
|
348
345
|
</button>
|
|
349
|
-
<button class="btn btn-
|
|
346
|
+
<button class="btn btn-danger" onclick="stopQueryMonitoring(this)" id="stop-monitor-btn" style="display: none;">
|
|
350
347
|
<%= t("pg_reports.ui.actions.stop_monitoring") %>
|
|
351
348
|
</button>
|
|
352
|
-
<button class="btn btn-
|
|
349
|
+
<button class="btn btn-secondary" onclick="loadQueryHistory(this)" id="load-history-btn" disabled>
|
|
353
350
|
<%= t("pg_reports.ui.actions.load_history") %>
|
|
354
351
|
</button>
|
|
355
352
|
<div class="download-dropdown" id="monitor-download-dropdown" style="display: none;">
|
|
356
|
-
<button class="btn btn-
|
|
353
|
+
<button class="btn btn-secondary" onclick="toggleMonitorDownloadMenu(event)">
|
|
357
354
|
<%= t("pg_reports.ui.actions.download") %>
|
|
358
355
|
</button>
|
|
359
356
|
<div class="download-menu" id="monitor-download-menu" style="display: none;">
|
|
@@ -376,34 +373,33 @@ end
|
|
|
376
373
|
</div>
|
|
377
374
|
<% end %>
|
|
378
375
|
|
|
376
|
+
<%
|
|
377
|
+
# Sprite ids for the category glyphs defined in the layout. The registry's own
|
|
378
|
+
# :icon/:color keys stay untouched — they are part of its public shape.
|
|
379
|
+
category_icons = {
|
|
380
|
+
queries: "queries", indexes: "indexes", tables: "tables",
|
|
381
|
+
connections: "connections", system: "system", schema_analysis: "schema"
|
|
382
|
+
}
|
|
383
|
+
%>
|
|
379
384
|
<div class="categories-grid">
|
|
380
385
|
<% @categories.each do |category_key, category| %>
|
|
381
|
-
<%
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
is_disabled = requires_pg_stat || target_disabled_reason.present?
|
|
385
|
-
%>
|
|
386
|
-
<div class="category-card<%= ' disabled' if is_disabled %>">
|
|
387
|
-
<% if requires_pg_stat %>
|
|
386
|
+
<% disabled_reason = category_disabled_reason(category_key) %>
|
|
387
|
+
<div class="category-card<%= ' disabled' if disabled_reason %>">
|
|
388
|
+
<% if disabled_reason %>
|
|
388
389
|
<div class="category-warning">
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
<% elsif target_disabled_reason %>
|
|
392
|
-
<div class="category-warning">
|
|
393
|
-
<%= target_disabled_reason %>
|
|
390
|
+
<svg class="icon" aria-hidden="true"><use href="#i-lock"></use></svg>
|
|
391
|
+
<span><%= disabled_reason %></span>
|
|
394
392
|
</div>
|
|
395
393
|
<% end %>
|
|
396
394
|
<div class="category-header">
|
|
397
|
-
<
|
|
398
|
-
<%= category[:icon] %>
|
|
399
|
-
</div>
|
|
395
|
+
<svg class="icon category-icon" aria-hidden="true"><use href="#i-<%= category_icons.fetch(category_key, "tables") %>"></use></svg>
|
|
400
396
|
<span class="category-title"><%= category[:name] %></span>
|
|
401
397
|
<span class="category-count"><%= category[:reports].size %> <%= t("pg_reports.ui.categories.reports_count_suffix") %></span>
|
|
402
398
|
</div>
|
|
403
399
|
|
|
404
400
|
<div class="reports-list">
|
|
405
401
|
<% category[:reports].each do |report_key, report| %>
|
|
406
|
-
<% if
|
|
402
|
+
<% if disabled_reason %>
|
|
407
403
|
<div class="report-link disabled">
|
|
408
404
|
<div class="report-link-info">
|
|
409
405
|
<span class="report-link-name">
|
|
@@ -412,7 +408,7 @@ end
|
|
|
412
408
|
</span>
|
|
413
409
|
<span class="report-link-desc"><%= report[:description] %></span>
|
|
414
410
|
</div>
|
|
415
|
-
<
|
|
411
|
+
<svg class="icon lock" aria-hidden="true"><use href="#i-lock"></use></svg>
|
|
416
412
|
</div>
|
|
417
413
|
<% else %>
|
|
418
414
|
<%= link_to report_path(category: category_key, report: report_key), class: "report-link" do %>
|
|
@@ -423,7 +419,7 @@ end
|
|
|
423
419
|
</span>
|
|
424
420
|
<span class="report-link-desc"><%= report[:description] %></span>
|
|
425
421
|
</div>
|
|
426
|
-
<
|
|
422
|
+
<svg class="icon arrow" aria-hidden="true"><use href="#i-chevron-right"></use></svg>
|
|
427
423
|
<% end %>
|
|
428
424
|
<% end %>
|
|
429
425
|
<% end %>
|
|
@@ -456,7 +452,7 @@ end
|
|
|
456
452
|
text-decoration: none;
|
|
457
453
|
}
|
|
458
454
|
.dashboard-footer a:hover {
|
|
459
|
-
color: var(--
|
|
455
|
+
color: var(--text-primary);
|
|
460
456
|
text-decoration: underline;
|
|
461
457
|
}
|
|
462
458
|
.dashboard-footer .footer-sep {
|
|
@@ -469,104 +465,45 @@ end
|
|
|
469
465
|
gap: 0.5rem;
|
|
470
466
|
}
|
|
471
467
|
|
|
472
|
-
.btn.btn-small {
|
|
473
|
-
padding: 0.5rem 1rem;
|
|
474
|
-
font-size: 0.8rem;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/* Keep the header action button the same height as the status badge and
|
|
478
|
-
the settings button (all 2.125rem); inline-flex centers the label so no
|
|
479
|
-
vertical padding is needed. */
|
|
480
|
-
.header-actions .btn {
|
|
481
|
-
height: 2.125rem;
|
|
482
|
-
padding: 0 1rem;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.btn-info {
|
|
486
|
-
width: 2.125rem;
|
|
487
|
-
height: 2.125rem;
|
|
488
|
-
padding: 0;
|
|
489
|
-
display: inline-flex;
|
|
490
|
-
align-items: center;
|
|
491
|
-
justify-content: center;
|
|
492
|
-
background: var(--bg-tertiary);
|
|
493
|
-
color: var(--text-muted);
|
|
494
|
-
border: 1px solid var(--border-color);
|
|
495
|
-
border-radius: 4px;
|
|
496
|
-
font-size: 0.9rem;
|
|
497
|
-
font-weight: 600;
|
|
498
|
-
cursor: pointer;
|
|
499
|
-
transition: all 0.15s;
|
|
500
|
-
}
|
|
501
|
-
.btn-info:hover {
|
|
502
|
-
background: var(--accent-purple);
|
|
503
|
-
color: white;
|
|
504
|
-
border-color: var(--accent-purple);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
.btn.btn-danger {
|
|
508
|
-
background: var(--accent-rose);
|
|
509
|
-
border-color: var(--accent-rose);
|
|
510
|
-
}
|
|
511
|
-
.btn.btn-danger:hover {
|
|
512
|
-
background: #c45a6e;
|
|
513
|
-
border-color: #c45a6e;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.btn.btn-secondary {
|
|
517
|
-
background: var(--bg-tertiary);
|
|
518
|
-
border-color: var(--border-color);
|
|
519
|
-
color: var(--text-secondary);
|
|
520
|
-
}
|
|
521
|
-
.btn.btn-secondary:hover {
|
|
522
|
-
background: var(--bg-card);
|
|
523
|
-
color: var(--text-primary);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.btn.btn-muted {
|
|
527
|
-
background: var(--bg-tertiary);
|
|
528
|
-
border-color: var(--accent-rose);
|
|
529
|
-
color: var(--text-secondary);
|
|
530
|
-
}
|
|
531
|
-
.btn.btn-muted:hover {
|
|
532
|
-
background: var(--bg-card);
|
|
533
|
-
color: var(--text-primary);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
468
|
.badge-dot.error { background: var(--accent-rose); }
|
|
537
469
|
|
|
538
470
|
.category-card.disabled {
|
|
539
471
|
opacity: 0.7;
|
|
540
472
|
}
|
|
541
473
|
|
|
474
|
+
/* Why a category is unavailable. The icon carries the state; the reason
|
|
475
|
+
itself is body copy, not a wall of amber. */
|
|
542
476
|
.category-warning {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: flex-start;
|
|
479
|
+
gap: 0.45rem;
|
|
480
|
+
padding: 0.5rem 1rem;
|
|
481
|
+
margin: -1rem -1rem 1rem;
|
|
482
|
+
background: var(--bg-tertiary);
|
|
483
|
+
border-bottom: 1px solid var(--border-color);
|
|
484
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
|
485
|
+
color: var(--text-secondary);
|
|
486
|
+
font-size: 0.75rem;
|
|
487
|
+
line-height: 1.5;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.category-warning .icon {
|
|
491
|
+
width: 0.8125rem;
|
|
492
|
+
height: 0.8125rem;
|
|
493
|
+
margin-top: 0.25rem;
|
|
548
494
|
color: var(--accent-amber);
|
|
549
|
-
font-size: 0.8rem;
|
|
550
|
-
font-weight: 500;
|
|
551
|
-
text-align: center;
|
|
552
495
|
}
|
|
553
496
|
|
|
554
497
|
.report-link.disabled {
|
|
555
|
-
|
|
556
|
-
align-items: center;
|
|
557
|
-
justify-content: space-between;
|
|
558
|
-
padding: 0.75rem 1rem;
|
|
559
|
-
background: var(--bg-tertiary);
|
|
560
|
-
border: 1px solid transparent;
|
|
561
|
-
border-radius: 6px;
|
|
498
|
+
background: transparent;
|
|
562
499
|
color: var(--text-muted);
|
|
563
|
-
font-size: 0.9rem;
|
|
564
500
|
cursor: not-allowed;
|
|
565
501
|
}
|
|
566
502
|
|
|
567
503
|
.report-link .lock {
|
|
568
|
-
|
|
569
|
-
|
|
504
|
+
width: 0.8125rem;
|
|
505
|
+
height: 0.8125rem;
|
|
506
|
+
opacity: 0.6;
|
|
570
507
|
}
|
|
571
508
|
|
|
572
509
|
.report-link-info {
|
|
@@ -594,66 +531,8 @@ end
|
|
|
594
531
|
color: var(--text-secondary);
|
|
595
532
|
}
|
|
596
533
|
|
|
597
|
-
/* Modal */
|
|
598
|
-
.modal {
|
|
599
|
-
position: fixed;
|
|
600
|
-
inset: 0;
|
|
601
|
-
background: rgba(0, 0, 0, 0.7);
|
|
602
|
-
display: flex;
|
|
603
|
-
align-items: center;
|
|
604
|
-
justify-content: center;
|
|
605
|
-
z-index: 1000;
|
|
606
|
-
backdrop-filter: blur(4px);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
.modal-content {
|
|
610
|
-
background: var(--bg-card);
|
|
611
|
-
border: 1px solid var(--border-color);
|
|
612
|
-
border-radius: 6px;
|
|
613
|
-
max-width: 600px;
|
|
614
|
-
width: 90%;
|
|
615
|
-
max-height: 80vh;
|
|
616
|
-
overflow: auto;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
.modal-header {
|
|
620
|
-
display: flex;
|
|
621
|
-
align-items: center;
|
|
622
|
-
justify-content: space-between;
|
|
623
|
-
padding: 1.25rem 1.5rem;
|
|
624
|
-
border-bottom: 1px solid var(--border-color);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
.modal-header h3 {
|
|
628
|
-
font-size: 1.125rem;
|
|
629
|
-
font-weight: 600;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
.modal-close {
|
|
633
|
-
width: 32px;
|
|
634
|
-
height: 32px;
|
|
635
|
-
background: var(--bg-tertiary);
|
|
636
|
-
border: 1px solid var(--border-color);
|
|
637
|
-
border-radius: 8px;
|
|
638
|
-
color: var(--text-muted);
|
|
639
|
-
font-size: 1.25rem;
|
|
640
|
-
cursor: pointer;
|
|
641
|
-
transition: all 0.15s;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
.modal-close:hover {
|
|
645
|
-
background: var(--accent-rose);
|
|
646
|
-
border-color: var(--accent-rose);
|
|
647
|
-
color: white;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
.modal-body {
|
|
651
|
-
padding: 1.5rem;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
534
|
.modal-body p {
|
|
655
535
|
color: var(--text-secondary);
|
|
656
|
-
margin-bottom: 1rem;
|
|
657
536
|
}
|
|
658
537
|
|
|
659
538
|
.modal-body ol {
|
|
@@ -661,33 +540,14 @@ end
|
|
|
661
540
|
color: var(--text-secondary);
|
|
662
541
|
}
|
|
663
542
|
|
|
664
|
-
.modal-body li {
|
|
665
|
-
margin-bottom: 1.25rem;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
543
|
.modal-body strong {
|
|
669
544
|
color: var(--text-primary);
|
|
670
545
|
}
|
|
671
546
|
|
|
672
|
-
|
|
547
|
+
/* A step's label and its snippet are one unit — tighter than the gap
|
|
548
|
+
between steps. */
|
|
549
|
+
.modal-body li > strong + pre {
|
|
673
550
|
margin-top: 0.5rem;
|
|
674
|
-
padding: 1rem;
|
|
675
|
-
background: var(--bg-primary);
|
|
676
|
-
border: 1px solid var(--border-color);
|
|
677
|
-
border-radius: 8px;
|
|
678
|
-
font-family: 'JetBrains Mono', monospace;
|
|
679
|
-
font-size: 0.8rem;
|
|
680
|
-
color: var(--accent-green);
|
|
681
|
-
overflow-x: auto;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
.modal-small {
|
|
685
|
-
max-width: 420px;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
.modal-header-danger {
|
|
689
|
-
background: rgba(244, 63, 94, 0.1);
|
|
690
|
-
border-bottom-color: rgba(244, 63, 94, 0.2);
|
|
691
551
|
}
|
|
692
552
|
|
|
693
553
|
.modal-header-danger h3 {
|
|
@@ -695,58 +555,55 @@ end
|
|
|
695
555
|
}
|
|
696
556
|
|
|
697
557
|
.warning-text {
|
|
698
|
-
font-size:
|
|
558
|
+
font-size: 0.9375rem;
|
|
699
559
|
font-weight: 500;
|
|
700
|
-
color: var(--text-primary)
|
|
701
|
-
margin-bottom: 0.5rem !important;
|
|
560
|
+
color: var(--text-primary);
|
|
702
561
|
}
|
|
703
562
|
|
|
704
563
|
.warning-subtext {
|
|
705
|
-
font-size: 0.
|
|
706
|
-
color: var(--text-muted)
|
|
707
|
-
margin-bottom: 1.5rem !important;
|
|
564
|
+
font-size: 0.8125rem;
|
|
565
|
+
color: var(--text-muted);
|
|
708
566
|
}
|
|
709
567
|
|
|
710
568
|
.modal-actions {
|
|
711
569
|
display: flex;
|
|
712
|
-
gap: 0.
|
|
570
|
+
gap: 0.5rem;
|
|
713
571
|
justify-content: flex-end;
|
|
714
572
|
}
|
|
715
573
|
|
|
716
|
-
.modal-actions .btn {
|
|
717
|
-
padding: 0.625rem 1.25rem;
|
|
718
|
-
font-size: 0.875rem;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
574
|
/* Live Monitoring Panel */
|
|
722
575
|
.live-monitoring-panel {
|
|
723
|
-
margin-bottom:
|
|
576
|
+
margin-bottom: 1.5rem;
|
|
724
577
|
background: var(--bg-card);
|
|
725
578
|
border: 1px solid var(--border-color);
|
|
726
|
-
border-radius:
|
|
727
|
-
padding:
|
|
579
|
+
border-radius: var(--radius);
|
|
580
|
+
padding: 0.875rem 1rem;
|
|
728
581
|
}
|
|
729
582
|
|
|
730
583
|
.live-monitoring-header {
|
|
731
584
|
display: flex;
|
|
732
585
|
align-items: center;
|
|
733
586
|
justify-content: space-between;
|
|
734
|
-
|
|
735
|
-
|
|
587
|
+
gap: 1rem;
|
|
588
|
+
flex-wrap: wrap;
|
|
589
|
+
margin-bottom: 0.875rem;
|
|
590
|
+
padding-bottom: 0.75rem;
|
|
736
591
|
border-bottom: 1px solid var(--border-color);
|
|
737
592
|
}
|
|
738
593
|
|
|
739
594
|
.live-monitoring-title {
|
|
740
595
|
display: flex;
|
|
741
596
|
align-items: center;
|
|
742
|
-
gap: 0.
|
|
597
|
+
gap: 0.625rem;
|
|
743
598
|
font-weight: 600;
|
|
744
|
-
font-size:
|
|
599
|
+
font-size: 0.875rem;
|
|
600
|
+
min-width: 0;
|
|
745
601
|
}
|
|
746
602
|
|
|
747
603
|
.live-indicator {
|
|
748
|
-
width:
|
|
749
|
-
height:
|
|
604
|
+
width: 7px;
|
|
605
|
+
height: 7px;
|
|
606
|
+
flex: none;
|
|
750
607
|
border-radius: 50%;
|
|
751
608
|
background: var(--accent-green);
|
|
752
609
|
animation: pulse 2s infinite;
|
|
@@ -768,29 +625,11 @@ end
|
|
|
768
625
|
color: var(--text-muted);
|
|
769
626
|
}
|
|
770
627
|
|
|
771
|
-
.btn-toggle-live {
|
|
772
|
-
width: 32px;
|
|
773
|
-
height: 32px;
|
|
774
|
-
padding: 0;
|
|
775
|
-
background: var(--bg-tertiary);
|
|
776
|
-
border: 1px solid var(--border-color);
|
|
777
|
-
border-radius: 8px;
|
|
778
|
-
color: var(--text-muted);
|
|
779
|
-
font-size: 0.875rem;
|
|
780
|
-
cursor: pointer;
|
|
781
|
-
transition: all 0.15s;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
.btn-toggle-live:hover {
|
|
785
|
-
background: var(--bg-secondary);
|
|
786
|
-
color: var(--text-primary);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
628
|
/* Metrics Grid */
|
|
790
629
|
.live-metrics-grid {
|
|
791
630
|
display: grid;
|
|
792
631
|
grid-template-columns: repeat(5, 1fr);
|
|
793
|
-
gap:
|
|
632
|
+
gap: 0.75rem;
|
|
794
633
|
}
|
|
795
634
|
|
|
796
635
|
@media (max-width: 1200px) {
|
|
@@ -811,74 +650,69 @@ end
|
|
|
811
650
|
}
|
|
812
651
|
}
|
|
813
652
|
|
|
814
|
-
/* Metric Card
|
|
653
|
+
/* Metric Card — a plain bordered tile. Status is carried by the dot, the
|
|
654
|
+
value colour and the sparkline, never by a coloured edge: "healthy" is
|
|
655
|
+
the common case and should read as quiet, not decorated. */
|
|
815
656
|
.live-metric-card {
|
|
816
657
|
background: var(--bg-tertiary);
|
|
817
658
|
border: 1px solid var(--border-color);
|
|
818
|
-
border-radius:
|
|
819
|
-
padding: 1rem;
|
|
659
|
+
border-radius: var(--radius);
|
|
660
|
+
padding: 0.875rem 1rem;
|
|
820
661
|
position: relative;
|
|
821
|
-
transition:
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
.live-metric-card:hover {
|
|
825
|
-
border-color: var(--accent-purple);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
.live-metric-card.status-ok {
|
|
829
|
-
border-left: 3px solid var(--accent-green);
|
|
662
|
+
transition: border-color 0.12s ease;
|
|
830
663
|
}
|
|
831
664
|
|
|
832
665
|
.live-metric-card.status-warning {
|
|
833
|
-
border-
|
|
666
|
+
border-color: var(--amber-line);
|
|
834
667
|
}
|
|
835
668
|
|
|
836
669
|
.live-metric-card.status-critical {
|
|
837
|
-
border-
|
|
670
|
+
border-color: var(--rose-line);
|
|
838
671
|
}
|
|
839
672
|
|
|
840
673
|
.metric-header {
|
|
841
674
|
display: flex;
|
|
842
675
|
align-items: center;
|
|
843
676
|
gap: 0.5rem;
|
|
844
|
-
margin-bottom: 0.
|
|
677
|
+
margin-bottom: 0.625rem;
|
|
845
678
|
}
|
|
846
679
|
|
|
847
680
|
.metric-icon {
|
|
848
|
-
width:
|
|
849
|
-
height:
|
|
850
|
-
|
|
851
|
-
display: flex;
|
|
852
|
-
align-items: center;
|
|
853
|
-
justify-content: center;
|
|
854
|
-
font-size: 0.875rem;
|
|
681
|
+
width: 0.9375rem;
|
|
682
|
+
height: 0.9375rem;
|
|
683
|
+
color: var(--text-muted);
|
|
855
684
|
}
|
|
856
685
|
|
|
857
686
|
.metric-name {
|
|
858
|
-
font-size: 0.
|
|
687
|
+
font-size: 0.75rem;
|
|
859
688
|
font-weight: 500;
|
|
689
|
+
letter-spacing: 0.02em;
|
|
860
690
|
color: var(--text-secondary);
|
|
861
691
|
}
|
|
862
692
|
|
|
863
693
|
.metric-body {
|
|
864
694
|
display: flex;
|
|
865
695
|
flex-direction: column;
|
|
866
|
-
gap: 0.
|
|
696
|
+
gap: 0.1875rem;
|
|
867
697
|
}
|
|
868
698
|
|
|
869
699
|
.metric-value {
|
|
870
700
|
display: flex;
|
|
871
701
|
align-items: baseline;
|
|
872
|
-
gap: 0.
|
|
702
|
+
gap: 0.3rem;
|
|
873
703
|
}
|
|
874
704
|
|
|
875
705
|
.metric-value span:first-child {
|
|
876
|
-
font-size: 1.
|
|
877
|
-
font-weight:
|
|
706
|
+
font-size: 1.625rem;
|
|
707
|
+
font-weight: 600;
|
|
708
|
+
letter-spacing: -0.02em;
|
|
878
709
|
color: var(--text-primary);
|
|
879
|
-
line-height: 1;
|
|
710
|
+
line-height: 1.1;
|
|
880
711
|
}
|
|
881
712
|
|
|
713
|
+
.status-warning .metric-value span:first-child { color: var(--accent-amber); }
|
|
714
|
+
.status-critical .metric-value span:first-child { color: var(--accent-rose); }
|
|
715
|
+
|
|
882
716
|
.metric-unit {
|
|
883
717
|
font-size: 0.75rem;
|
|
884
718
|
color: var(--text-muted);
|
|
@@ -887,71 +721,41 @@ end
|
|
|
887
721
|
.metric-detail {
|
|
888
722
|
font-size: 0.7rem;
|
|
889
723
|
color: var(--text-muted);
|
|
724
|
+
white-space: nowrap;
|
|
725
|
+
overflow: hidden;
|
|
726
|
+
text-overflow: ellipsis;
|
|
890
727
|
}
|
|
891
728
|
|
|
892
729
|
/* Sparkline */
|
|
893
730
|
.metric-sparkline {
|
|
894
|
-
height:
|
|
895
|
-
margin-top: 0.
|
|
731
|
+
height: 28px;
|
|
732
|
+
margin-top: 0.625rem;
|
|
733
|
+
color: var(--text-muted);
|
|
896
734
|
}
|
|
897
735
|
|
|
736
|
+
.status-warning .metric-sparkline { color: var(--accent-amber); }
|
|
737
|
+
.status-critical .metric-sparkline { color: var(--accent-rose); }
|
|
738
|
+
|
|
898
739
|
.metric-sparkline svg {
|
|
899
740
|
width: 100%;
|
|
900
741
|
height: 100%;
|
|
742
|
+
display: block;
|
|
901
743
|
}
|
|
902
744
|
|
|
903
745
|
/* Status indicator */
|
|
904
746
|
.metric-status {
|
|
905
747
|
position: absolute;
|
|
906
|
-
top: 0.
|
|
907
|
-
right: 0.
|
|
748
|
+
top: 0.75rem;
|
|
749
|
+
right: 0.75rem;
|
|
908
750
|
width: 6px;
|
|
909
751
|
height: 6px;
|
|
910
752
|
border-radius: 50%;
|
|
753
|
+
background: var(--border-strong);
|
|
911
754
|
}
|
|
912
755
|
|
|
913
|
-
.metric-status.ok { background: var(--accent-green); }
|
|
914
756
|
.metric-status.warning { background: var(--accent-amber); }
|
|
915
757
|
.metric-status.critical { background: var(--accent-rose); }
|
|
916
758
|
|
|
917
|
-
/* Toast Notifications */
|
|
918
|
-
.toast {
|
|
919
|
-
position: fixed;
|
|
920
|
-
bottom: 2rem;
|
|
921
|
-
right: 2rem;
|
|
922
|
-
padding: 1rem 1.5rem;
|
|
923
|
-
background: var(--bg-card);
|
|
924
|
-
border: 1px solid var(--border-color);
|
|
925
|
-
border-radius: 6px;
|
|
926
|
-
color: var(--text-primary);
|
|
927
|
-
font-size: 0.875rem;
|
|
928
|
-
font-weight: 500;
|
|
929
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
930
|
-
z-index: 9999;
|
|
931
|
-
opacity: 0;
|
|
932
|
-
transform: translateY(20px);
|
|
933
|
-
transition: all 0.3s ease;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
.toast.show {
|
|
937
|
-
opacity: 1;
|
|
938
|
-
transform: translateY(0);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
.toast.toast-success {
|
|
942
|
-
border-left: 3px solid var(--accent-green);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
.toast.toast-error {
|
|
946
|
-
border-left: 3px solid var(--accent-rose);
|
|
947
|
-
background: rgba(244, 63, 94, 0.1);
|
|
948
|
-
border-color: var(--accent-rose);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
.toast.toast-warning {
|
|
952
|
-
border-left: 3px solid var(--accent-amber);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
759
|
/* Run Query Modal */
|
|
956
760
|
.modal-run-query {
|
|
957
761
|
max-width: 1100px;
|
|
@@ -960,62 +764,57 @@ end
|
|
|
960
764
|
|
|
961
765
|
.explain-label {
|
|
962
766
|
display: block;
|
|
963
|
-
font-size: 0.
|
|
767
|
+
font-size: 0.6875rem;
|
|
964
768
|
font-weight: 600;
|
|
769
|
+
line-height: 1.4;
|
|
770
|
+
letter-spacing: 0.06em;
|
|
965
771
|
color: var(--text-muted);
|
|
966
772
|
text-transform: uppercase;
|
|
967
|
-
margin-bottom: 0.
|
|
773
|
+
margin-bottom: 0.4rem;
|
|
968
774
|
}
|
|
969
775
|
|
|
970
776
|
.run-query-textarea {
|
|
971
777
|
width: 100%;
|
|
972
778
|
min-height: 180px;
|
|
973
|
-
padding:
|
|
779
|
+
padding: 0.875rem;
|
|
974
780
|
background: var(--bg-primary);
|
|
975
781
|
border: 1px solid var(--border-color);
|
|
976
|
-
border-radius:
|
|
782
|
+
border-radius: var(--radius-sm);
|
|
977
783
|
color: var(--text-primary);
|
|
978
|
-
font-family:
|
|
979
|
-
font-size: 0.
|
|
980
|
-
line-height: 1.
|
|
784
|
+
font-family: var(--font-mono);
|
|
785
|
+
font-size: 0.8125rem;
|
|
786
|
+
line-height: 1.6;
|
|
981
787
|
resize: vertical;
|
|
982
788
|
}
|
|
983
789
|
|
|
984
|
-
.run-query-textarea:focus {
|
|
985
|
-
outline:
|
|
790
|
+
.run-query-textarea:focus-visible {
|
|
791
|
+
outline: 2px solid var(--accent-blue);
|
|
792
|
+
outline-offset: -1px;
|
|
986
793
|
border-color: var(--accent-blue);
|
|
987
|
-
box-shadow: 0 0 0 2px rgba(107, 159, 232, 0.2);
|
|
988
794
|
}
|
|
989
795
|
|
|
990
796
|
.run-query-textarea::placeholder {
|
|
991
797
|
color: var(--text-muted);
|
|
992
|
-
font-style: italic;
|
|
993
798
|
}
|
|
994
799
|
|
|
995
800
|
.run-query-actions {
|
|
996
801
|
display: flex;
|
|
997
|
-
gap: 0.
|
|
998
|
-
margin: 1rem 0;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.run-query-actions .btn {
|
|
1002
|
-
flex: 1;
|
|
802
|
+
gap: 0.5rem;
|
|
1003
803
|
}
|
|
1004
804
|
|
|
1005
805
|
/* Query Results Table (mirrors _show_styles.html.erb — index.html.erb has
|
|
1006
806
|
its own <style> block and isn't loaded through that partial) */
|
|
1007
807
|
.query-results-wrapper {
|
|
1008
|
-
margin-top: 1rem;
|
|
1009
808
|
max-height: 400px;
|
|
1010
809
|
overflow: auto;
|
|
1011
810
|
border: 1px solid var(--border-color);
|
|
1012
|
-
border-radius:
|
|
811
|
+
border-radius: var(--radius);
|
|
1013
812
|
}
|
|
1014
813
|
|
|
1015
814
|
.query-results-table {
|
|
1016
815
|
width: 100%;
|
|
1017
816
|
border-collapse: collapse;
|
|
1018
|
-
font-family:
|
|
817
|
+
font-family: var(--font-mono);
|
|
1019
818
|
font-size: 0.75rem;
|
|
1020
819
|
}
|
|
1021
820
|
|
|
@@ -1054,8 +853,7 @@ end
|
|
|
1054
853
|
justify-content: space-between;
|
|
1055
854
|
padding: 0.75rem 1rem;
|
|
1056
855
|
background: var(--bg-tertiary);
|
|
1057
|
-
border-radius:
|
|
1058
|
-
margin-top: 1rem;
|
|
856
|
+
border-radius: var(--radius);
|
|
1059
857
|
font-size: 0.8rem;
|
|
1060
858
|
color: var(--text-secondary);
|
|
1061
859
|
}
|
|
@@ -1121,15 +919,7 @@ end
|
|
|
1121
919
|
|
|
1122
920
|
if (!allowRawQueryExecution) {
|
|
1123
921
|
showToast(PG_REPORTS_I18N.errors.execute_disabled_toast, 'error');
|
|
1124
|
-
content.innerHTML =
|
|
1125
|
-
<strong>${PG_REPORTS_I18N.modals.query_execution_disabled_title}</strong><br><br>
|
|
1126
|
-
${PG_REPORTS_I18N.modals.query_execution_disabled_intro}<br>
|
|
1127
|
-
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
1128
|
-
PgReports.configure do |config|<br>
|
|
1129
|
-
config.allow_raw_query_execution = true<br>
|
|
1130
|
-
end
|
|
1131
|
-
</code>
|
|
1132
|
-
</div>`;
|
|
922
|
+
content.innerHTML = pgReportsDisabledNotice(PG_REPORTS_I18N.modals.query_execution_disabled_title);
|
|
1133
923
|
return;
|
|
1134
924
|
}
|
|
1135
925
|
|
|
@@ -1425,14 +1215,12 @@ end
|
|
|
1425
1215
|
function updateToggleUI() {
|
|
1426
1216
|
const icon = document.getElementById('toggle-icon');
|
|
1427
1217
|
const indicator = document.querySelector('.live-indicator');
|
|
1218
|
+
const use = icon && icon.querySelector('use');
|
|
1428
1219
|
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1431
|
-
indicator.classList.remove('paused');
|
|
1432
|
-
} else {
|
|
1433
|
-
icon.textContent = '▶';
|
|
1434
|
-
indicator.classList.add('paused');
|
|
1220
|
+
if (use) {
|
|
1221
|
+
use.setAttribute('href', liveMonitoringEnabled ? '#i-pause' : '#i-play');
|
|
1435
1222
|
}
|
|
1223
|
+
indicator.classList.toggle('paused', !liveMonitoringEnabled);
|
|
1436
1224
|
}
|
|
1437
1225
|
|
|
1438
1226
|
async function fetchLiveMetrics() {
|
|
@@ -1505,18 +1293,18 @@ end
|
|
|
1505
1293
|
addToHistory('longrunning', metrics.long_running_count);
|
|
1506
1294
|
addToHistory('blocked', metrics.blocked_count);
|
|
1507
1295
|
|
|
1508
|
-
renderSparkline('connections', metricsHistory.connections, 'var(--accent-blue)');
|
|
1509
|
-
renderSparkline('tps', metricsHistory.tps, 'var(--accent-green)');
|
|
1510
|
-
renderSparkline('cache', metricsHistory.cache, 'var(--accent-purple)');
|
|
1511
|
-
renderSparkline('longrunning', metricsHistory.longrunning, 'var(--accent-amber)');
|
|
1512
|
-
renderSparkline('blocked', metricsHistory.blocked, 'var(--accent-rose)');
|
|
1513
|
-
|
|
1514
1296
|
updateStatus('connections', metrics.connections.percent, 'higher');
|
|
1515
1297
|
updateStatus('tps', tps, null);
|
|
1516
1298
|
updateStatus('cache', metrics.cache_hit_ratio, 'lower');
|
|
1517
1299
|
updateStatus('longrunning', metrics.long_running_count, 'higher');
|
|
1518
1300
|
updateStatus('blocked', metrics.blocked_count, 'higher');
|
|
1519
1301
|
|
|
1302
|
+
renderSparkline('connections', metricsHistory.connections);
|
|
1303
|
+
renderSparkline('tps', metricsHistory.tps);
|
|
1304
|
+
renderSparkline('cache', metricsHistory.cache);
|
|
1305
|
+
renderSparkline('longrunning', metricsHistory.longrunning);
|
|
1306
|
+
renderSparkline('blocked', metricsHistory.blocked);
|
|
1307
|
+
|
|
1520
1308
|
previousMetrics = metrics;
|
|
1521
1309
|
}
|
|
1522
1310
|
|
|
@@ -1533,7 +1321,7 @@ end
|
|
|
1533
1321
|
return num.toString();
|
|
1534
1322
|
}
|
|
1535
1323
|
|
|
1536
|
-
function renderSparkline(metric, data
|
|
1324
|
+
function renderSparkline(metric, data) {
|
|
1537
1325
|
const svg = document.getElementById(`sparkline-${metric}`);
|
|
1538
1326
|
if (!svg || data.length < 2) return;
|
|
1539
1327
|
|
|
@@ -1561,15 +1349,16 @@ end
|
|
|
1561
1349
|
|
|
1562
1350
|
const area = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1563
1351
|
area.setAttribute('d', areaD);
|
|
1564
|
-
area.setAttribute('fill',
|
|
1565
|
-
area.setAttribute('opacity', '0.
|
|
1352
|
+
area.setAttribute('fill', 'currentColor');
|
|
1353
|
+
area.setAttribute('opacity', '0.12');
|
|
1566
1354
|
svg.appendChild(area);
|
|
1567
1355
|
|
|
1568
1356
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1569
1357
|
path.setAttribute('d', pathD);
|
|
1570
1358
|
path.setAttribute('fill', 'none');
|
|
1571
|
-
path.setAttribute('stroke',
|
|
1572
|
-
path.setAttribute('stroke-width', '1.
|
|
1359
|
+
path.setAttribute('stroke', 'currentColor');
|
|
1360
|
+
path.setAttribute('stroke-width', '1.25');
|
|
1361
|
+
path.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
1573
1362
|
path.setAttribute('stroke-linecap', 'round');
|
|
1574
1363
|
path.setAttribute('stroke-linejoin', 'round');
|
|
1575
1364
|
svg.appendChild(path);
|
|
@@ -1581,24 +1370,22 @@ end
|
|
|
1581
1370
|
if (!card || !statusDot) return;
|
|
1582
1371
|
|
|
1583
1372
|
const thresholds = LIVE_CONFIG.thresholds[metric];
|
|
1584
|
-
if (!thresholds.warning && !thresholds.critical) {
|
|
1585
|
-
card.className = 'live-metric-card status-ok';
|
|
1586
|
-
statusDot.className = 'metric-status ok';
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
1373
|
let status = 'ok';
|
|
1591
1374
|
|
|
1592
|
-
if (
|
|
1593
|
-
if (
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
1375
|
+
if (thresholds.warning || thresholds.critical) {
|
|
1376
|
+
if (badDirection === 'higher') {
|
|
1377
|
+
if (value >= thresholds.critical) status = 'critical';
|
|
1378
|
+
else if (value >= thresholds.warning) status = 'warning';
|
|
1379
|
+
} else if (badDirection === 'lower') {
|
|
1380
|
+
if (value <= thresholds.critical) status = 'critical';
|
|
1381
|
+
else if (value <= thresholds.warning) status = 'warning';
|
|
1382
|
+
}
|
|
1598
1383
|
}
|
|
1599
1384
|
|
|
1600
|
-
card.
|
|
1601
|
-
|
|
1385
|
+
card.classList.remove('status-ok', 'status-warning', 'status-critical');
|
|
1386
|
+
card.classList.add(`status-${status}`);
|
|
1387
|
+
statusDot.classList.remove('ok', 'warning', 'critical');
|
|
1388
|
+
statusDot.classList.add(status);
|
|
1602
1389
|
}
|
|
1603
1390
|
|
|
1604
1391
|
document.addEventListener('DOMContentLoaded', initLiveMonitoring);
|
|
@@ -1784,6 +1571,8 @@ end
|
|
|
1784
1571
|
const response = await fetch(`${pgReportsRoot}/query_monitor/status`);
|
|
1785
1572
|
const data = await response.json();
|
|
1786
1573
|
|
|
1574
|
+
queryHistoryAvailable = data.history_available === true;
|
|
1575
|
+
|
|
1787
1576
|
if (data.success && data.enabled) {
|
|
1788
1577
|
queryMonitorEnabled = true;
|
|
1789
1578
|
currentSessionId = data.session_id;
|
|
@@ -1802,6 +1591,17 @@ end
|
|
|
1802
1591
|
}
|
|
1803
1592
|
}
|
|
1804
1593
|
|
|
1594
|
+
// Set from /query_monitor/status; null until the first check answers.
|
|
1595
|
+
let queryHistoryAvailable = null;
|
|
1596
|
+
|
|
1597
|
+
function applyHistoryAvailability(button) {
|
|
1598
|
+
const btn = button || document.getElementById('load-history-btn');
|
|
1599
|
+
if (!btn || queryHistoryAvailable === null) return;
|
|
1600
|
+
|
|
1601
|
+
btn.disabled = !queryHistoryAvailable;
|
|
1602
|
+
btn.title = queryHistoryAvailable ? '' : PG_REPORTS_I18N.actions.load_history_unavailable_title;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1805
1605
|
async function loadQueryHistory(button) {
|
|
1806
1606
|
button.disabled = true;
|
|
1807
1607
|
button.innerHTML = '<span class="spinner"></span> ' + PG_REPORTS_I18N.actions.loading;
|
|
@@ -1822,8 +1622,9 @@ end
|
|
|
1822
1622
|
console.error('Failed to load query history:', error);
|
|
1823
1623
|
showToast(PG_REPORTS_I18N.errors.load_history_failed + ' ' + error.message, 'error');
|
|
1824
1624
|
} finally {
|
|
1825
|
-
button.disabled = false;
|
|
1826
1625
|
button.innerHTML = PG_REPORTS_I18N.actions.load_history;
|
|
1626
|
+
button.disabled = false;
|
|
1627
|
+
applyHistoryAvailability(button);
|
|
1827
1628
|
}
|
|
1828
1629
|
}
|
|
1829
1630
|
|
|
@@ -1924,6 +1725,7 @@ end
|
|
|
1924
1725
|
startBtn.innerHTML = PG_REPORTS_I18N.actions.start_monitoring;
|
|
1925
1726
|
stopBtn.style.display = 'none';
|
|
1926
1727
|
loadHistoryBtn.style.display = 'inline-block'; // Show when monitoring stopped
|
|
1728
|
+
applyHistoryAvailability(loadHistoryBtn);
|
|
1927
1729
|
sessionBadge.style.display = 'none';
|
|
1928
1730
|
|
|
1929
1731
|
// Keep results visible after stopping, only hide download if no queries
|