pg_reports 0.9.0 → 0.9.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 +10 -0
- data/app/controllers/pg_reports/dashboard_controller.rb +47 -0
- data/app/views/layouts/pg_reports/application.html.erb +96 -13
- data/app/views/pg_reports/dashboard/_settings_modal.html.erb +55 -0
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +1 -42
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +1 -39
- data/app/views/pg_reports/dashboard/index.html.erb +36 -114
- data/app/views/pg_reports/dashboard/show.html.erb +1 -1
- data/config/locales/en.yml +6 -3
- data/config/locales/ru.yml +6 -3
- data/config/locales/uk.yml +6 -3
- data/config/routes.rb +1 -0
- data/lib/pg_reports/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2063c9302c5b737dc5272ef382242464d7f11d1dbc7d926bd5db0b55a842b54
|
|
4
|
+
data.tar.gz: 61ca606a24d3e6fed362acb58270a2c03526803c18c7c89e2e31cd13cc35f3ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf7867350c74791dfb0a36ec2ad013e7f42ef5186f8a29209472599e36ca147b76117039f2009bac86d456370e41bca9d49e45a4b6c51ae34d0829bf1ae0d300
|
|
7
|
+
data.tar.gz: 75910cc42fc73da32e88f2161b10054ffe1775e8cbfce8085c488d419355eacc3b8a5ed42af1b9149198c64f4a252a6ee0178aeb99ccc2b63fba4a7847c40466
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.1] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Settings modal.** The gear used to open an IDE-only dialog that existed as two separate copies — one inlined in the dashboard, one in the report-page partial, each with its own duplicate of the JavaScript. It is now a single shared partial with one copy of the script in the layout, titled **Settings** and split into sections, so there is somewhere to put the next preference. Language is the first section; the IDE choice is the second. Picking a language reloads the page (everything on it has to re-render in the new language), so the modal is reopened afterwards rather than vanishing mid-edit.
|
|
15
|
+
- **Language switcher on the dashboard.** The engine ships English, Russian and Ukrainian translations covering the whole UI — including category and report names — but there was no way to choose between them; the dashboard simply followed the host application's `I18n.locale`. A section in the settings modal now stores the choice in the session (the same pattern as the database and target switchers) and it applies to every page. Only locales that actually carry `pg_reports` translations are offered, detected by probing a key rather than by listing the shipped files, so a host app supplying its own translation is offered too. The locale is applied with `I18n.with_locale` around the action: `I18n.locale` is process-global, and assigning it outright would carry the dashboard's language into the host application's own rendering.
|
|
16
|
+
|
|
10
17
|
## [0.9.0] - 2026-09-16
|
|
11
18
|
|
|
12
19
|
### Changed
|
|
@@ -18,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
18
25
|
- **Buttons are solid, classic controls.** `.btn-primary` was a translucent tinted outline (`rgba(124,138,246,.14)` fill, accent text, accent border); it is now a filled button with white text, plus proper `:active` and `:focus-visible` states. `.btn-secondary`/`.btn-danger`/`.btn-muted`/`.btn-ghost`/`.btn-icon` share one height, radius and transition, and every variant is declared in exactly one place.
|
|
19
26
|
- **Status is no longer signalled by coloured left stripes.** The live metric cards carried a 3px green/amber/rose `border-left` — "healthy" was as decorated as "critical". Status now reads from the corner dot, the value colour and the sparkline, with the card's own border tinting only for warning and critical. The same treatment replaced the coloured rails and `linear-gradient` washes on EXPLAIN summary cards, problem cards and stat tiles.
|
|
20
27
|
- Table problem rows used `border-left` on `<tr>`, which does not render under `border-collapse: collapse`; they now use an inset shadow on the first cell, so the marker is actually visible.
|
|
28
|
+
- **Dropped the status dot from the live metric cards.** In the healthy state it was a grey dot in the corner conveying nothing, and in the warning/critical state it was a fourth encoding of what the card border, the value colour and the sparkline already say. Status now reads from those three alone.
|
|
29
|
+
- **Metric figures are right-aligned.** The value and its sub-label sit against the card's right edge, with the metric's name on the left — so each card reads name-then-number across, and the column of figures lines up down the row.
|
|
30
|
+
- **The sparkline became the card's bottom band.** It used to float inset in the lower half with margins on every side, leaving the bottom of the card reading as dead space. It now spans the full width and sits flush with the bottom edge. A series that never moves is drawn mid-band rather than pinned to the floor, where a steady 100% cache hit looked like a stray underline at zero.
|
|
21
31
|
- **Sparkline colour encodes state, not identity.** Each metric drew its trend in its own hue (blue/green/purple/amber/rose), which read as decoration. All five now draw in a muted neutral, switching to amber or rose only when that metric crosses its threshold.
|
|
22
32
|
- **Emoji replaced with an inline SVG icon sprite.** Emoji were used as UI icons throughout — the header logo tile, category and metric icons, the settings gear, lock, play/pause, and as prefixes baked into ~48 locale strings per language (`"📋 Copy"`, `"⬇ Export"`, `"🔒 Requires pg_stat_statements"`). They rendered differently on every OS and could not be coloured. All are now stroke-drawn `<symbol>`s defined once in the layout and referenced with `<use>`, inheriting colour and size from their context. Locale strings carry text only.
|
|
23
33
|
- **Removed the header logo tile.** The wordmark and version carry the branding.
|
|
@@ -12,6 +12,7 @@ module PgReports
|
|
|
12
12
|
protect_from_forgery with: :exception
|
|
13
13
|
|
|
14
14
|
before_action :authenticate_dashboard!, if: -> { PgReports.config.dashboard_auth.present? }
|
|
15
|
+
around_action :within_selected_locale
|
|
15
16
|
before_action :set_categories
|
|
16
17
|
before_action :resolve_database_selection
|
|
17
18
|
around_action :within_selected_database
|
|
@@ -24,6 +25,7 @@ module PgReports
|
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
helper_method :category_disabled_reason, :category_disabled?
|
|
28
|
+
helper_method :dashboard_locales, :selected_locale
|
|
27
29
|
|
|
28
30
|
def index
|
|
29
31
|
@pg_stat_status = pg_stat_status
|
|
@@ -48,6 +50,21 @@ module PgReports
|
|
|
48
50
|
# POST /switch_target
|
|
49
51
|
# Persists the chosen target in session, clears the database choice (each
|
|
50
52
|
# target has its own list of databases), and redirects back.
|
|
53
|
+
# POST /switch_locale
|
|
54
|
+
# Persists the chosen language in session; #within_selected_locale applies it
|
|
55
|
+
# to this and every later request.
|
|
56
|
+
def switch_locale
|
|
57
|
+
requested = params[:locale].to_s
|
|
58
|
+
|
|
59
|
+
if requested.empty?
|
|
60
|
+
session.delete(:pg_reports_locale)
|
|
61
|
+
elsif dashboard_locales.any? { |locale| locale.to_s == requested }
|
|
62
|
+
session[:pg_reports_locale] = requested
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
redirect_back fallback_location: root_path
|
|
66
|
+
end
|
|
67
|
+
|
|
51
68
|
def switch_target
|
|
52
69
|
requested = params[:target].to_s
|
|
53
70
|
|
|
@@ -629,6 +646,36 @@ module PgReports
|
|
|
629
646
|
@categories = Dashboard::ReportsRegistry.all
|
|
630
647
|
end
|
|
631
648
|
|
|
649
|
+
# I18n.locale is global to the process, so assigning it outright would leak
|
|
650
|
+
# the dashboard's language into the host application's own rendering (and
|
|
651
|
+
# into whatever request runs next on this thread). with_locale restores the
|
|
652
|
+
# previous value once the action and its view are done.
|
|
653
|
+
def within_selected_locale(&block)
|
|
654
|
+
I18n.with_locale(selected_locale, &block)
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def selected_locale
|
|
658
|
+
@selected_locale ||= begin
|
|
659
|
+
stored = session[:pg_reports_locale].to_s
|
|
660
|
+
match = dashboard_locales.find { |locale| locale.to_s == stored }
|
|
661
|
+
match || I18n.locale
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Only locales that actually carry pg_reports translations. A host app can
|
|
666
|
+
# have dozens of its own locales while translating none of this dashboard,
|
|
667
|
+
# and offering those would just switch the UI to fallback English. Picked by
|
|
668
|
+
# probing one key rather than by listing the shipped files, so a host app
|
|
669
|
+
# that supplies its own pg_reports translations is offered too.
|
|
670
|
+
def dashboard_locales
|
|
671
|
+
@dashboard_locales ||= begin
|
|
672
|
+
translated = I18n.available_locales.select do |locale|
|
|
673
|
+
I18n.exists?("pg_reports.ui.branding.title", locale)
|
|
674
|
+
end
|
|
675
|
+
translated.presence || [I18n.default_locale]
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
632
679
|
# Resolves which target/database every action should run against, based on:
|
|
633
680
|
# 1. session[:pg_reports_target] (set by #switch_target)
|
|
634
681
|
# 2. session[:pg_reports_database] (set by #switch_database)
|
|
@@ -1330,44 +1330,57 @@
|
|
|
1330
1330
|
font-size: 0.9rem;
|
|
1331
1331
|
}
|
|
1332
1332
|
|
|
1333
|
-
.
|
|
1333
|
+
/* Settings modal sections. The option rows are shared by every section —
|
|
1334
|
+
language, IDE, and whatever is added next. */
|
|
1335
|
+
.settings-section-title {
|
|
1336
|
+
font-size: 0.6875rem;
|
|
1337
|
+
font-weight: 600;
|
|
1338
|
+
line-height: 1.4;
|
|
1339
|
+
color: var(--text-muted);
|
|
1340
|
+
text-transform: uppercase;
|
|
1341
|
+
letter-spacing: 0.06em;
|
|
1342
|
+
margin-bottom: 0.5rem;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
.settings-options {
|
|
1334
1346
|
display: flex;
|
|
1335
1347
|
flex-direction: column;
|
|
1336
|
-
gap: 0.
|
|
1348
|
+
gap: 0.375rem;
|
|
1337
1349
|
}
|
|
1338
1350
|
|
|
1339
|
-
.
|
|
1351
|
+
.settings-option {
|
|
1340
1352
|
display: flex;
|
|
1341
1353
|
align-items: center;
|
|
1342
|
-
gap: 0.
|
|
1343
|
-
padding: 0.75rem
|
|
1354
|
+
gap: 0.625rem;
|
|
1355
|
+
padding: 0.5rem 0.75rem;
|
|
1344
1356
|
background: var(--bg-tertiary);
|
|
1345
1357
|
border: 1px solid var(--border-color);
|
|
1346
|
-
border-radius:
|
|
1358
|
+
border-radius: var(--radius-sm);
|
|
1347
1359
|
cursor: pointer;
|
|
1348
|
-
transition:
|
|
1360
|
+
transition: background-color 0.12s ease, border-color 0.12s ease;
|
|
1349
1361
|
}
|
|
1350
1362
|
|
|
1351
|
-
.
|
|
1363
|
+
.settings-option:hover {
|
|
1352
1364
|
background: var(--bg-hover);
|
|
1353
1365
|
border-color: var(--border-strong);
|
|
1354
1366
|
}
|
|
1355
1367
|
|
|
1356
|
-
.
|
|
1368
|
+
.settings-option:has(input:checked) {
|
|
1357
1369
|
background: var(--purple-soft);
|
|
1358
1370
|
border-color: var(--purple-line);
|
|
1359
1371
|
}
|
|
1360
1372
|
|
|
1361
|
-
.
|
|
1373
|
+
.settings-option input[type="radio"] {
|
|
1362
1374
|
accent-color: var(--accent-purple);
|
|
1375
|
+
flex: none;
|
|
1363
1376
|
}
|
|
1364
1377
|
|
|
1365
|
-
.
|
|
1378
|
+
.settings-option span {
|
|
1366
1379
|
color: var(--text-secondary);
|
|
1367
|
-
font-size: 0.
|
|
1380
|
+
font-size: 0.8125rem;
|
|
1368
1381
|
}
|
|
1369
1382
|
|
|
1370
|
-
.
|
|
1383
|
+
.settings-option:has(input:checked) span {
|
|
1371
1384
|
color: var(--text-primary);
|
|
1372
1385
|
}
|
|
1373
1386
|
|
|
@@ -1634,6 +1647,76 @@
|
|
|
1634
1647
|
].join('');
|
|
1635
1648
|
};
|
|
1636
1649
|
|
|
1650
|
+
// Settings modal. Shared by the dashboard and the report pages; the language
|
|
1651
|
+
// section posts a form (server-side session), the IDE section only writes
|
|
1652
|
+
// localStorage, so there is nothing to submit for it.
|
|
1653
|
+
// Picking a language posts a form and the page reloads (everything on it has
|
|
1654
|
+
// to re-render in the new language), which would leave the user staring at a
|
|
1655
|
+
// dashboard with the settings they were editing gone. Remember that the
|
|
1656
|
+
// modal was open and bring it back on the way in.
|
|
1657
|
+
const SETTINGS_REOPEN_KEY = 'pgReportsSettingsReopen';
|
|
1658
|
+
|
|
1659
|
+
function rememberSettingsOpen() {
|
|
1660
|
+
try {
|
|
1661
|
+
sessionStorage.setItem(SETTINGS_REOPEN_KEY, '1');
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
// Storage can be unavailable (private mode, blocked cookies); losing the
|
|
1664
|
+
// reopen is a cosmetic downgrade, not a reason to block the switch.
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function consumeSettingsReopen() {
|
|
1669
|
+
try {
|
|
1670
|
+
if (!sessionStorage.getItem(SETTINGS_REOPEN_KEY)) return false;
|
|
1671
|
+
sessionStorage.removeItem(SETTINGS_REOPEN_KEY);
|
|
1672
|
+
return true;
|
|
1673
|
+
} catch (e) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function showSettingsModal() {
|
|
1679
|
+
const modal = document.getElementById('settings-modal');
|
|
1680
|
+
if (!modal) return;
|
|
1681
|
+
|
|
1682
|
+
modal.style.display = 'flex';
|
|
1683
|
+
loadSettingsState();
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function closeSettingsModal() {
|
|
1687
|
+
const modal = document.getElementById('settings-modal');
|
|
1688
|
+
if (modal) modal.style.display = 'none';
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function setDefaultIde(ideKey) {
|
|
1692
|
+
if (ideKey) {
|
|
1693
|
+
localStorage.setItem('pgReportsDefaultIde', ideKey);
|
|
1694
|
+
} else {
|
|
1695
|
+
localStorage.removeItem('pgReportsDefaultIde');
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function getDefaultIde() {
|
|
1700
|
+
return localStorage.getItem('pgReportsDefaultIde') || '';
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// The language radios are rendered already checked by the server; only the
|
|
1704
|
+
// IDE choice lives in localStorage and has to be restored on open.
|
|
1705
|
+
function loadSettingsState() {
|
|
1706
|
+
const currentIde = getDefaultIde();
|
|
1707
|
+
document.querySelectorAll('input[name="default-ide"]').forEach(radio => {
|
|
1708
|
+
radio.checked = (radio.value === currentIde);
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1713
|
+
document.getElementById('settings-modal')?.addEventListener('click', function(e) {
|
|
1714
|
+
if (e.target === this) closeSettingsModal();
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
if (consumeSettingsReopen()) showSettingsModal();
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1637
1720
|
function showToast(message, type = 'success') {
|
|
1638
1721
|
const toast = document.getElementById('toast');
|
|
1639
1722
|
toast.textContent = message;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<%# Shared by the dashboard and the report pages. Language is persisted server
|
|
2
|
+
side (session, via a form post); the IDE choice is client side in
|
|
3
|
+
localStorage — hence one <form> section and one plain section. %>
|
|
4
|
+
<div id="settings-modal" class="modal" style="display: none;">
|
|
5
|
+
<div class="modal-content modal-small">
|
|
6
|
+
<div class="modal-header">
|
|
7
|
+
<h3><%= t("pg_reports.ui.modals.settings_title") %></h3>
|
|
8
|
+
<button class="modal-close" onclick="closeSettingsModal()" aria-label="<%= t("pg_reports.ui.actions.cancel") %>">×</button>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="modal-body">
|
|
11
|
+
<% if dashboard_locales.size > 1 %>
|
|
12
|
+
<section class="settings-section">
|
|
13
|
+
<h4 class="settings-section-title"><%= t("pg_reports.ui.language.label") %></h4>
|
|
14
|
+
<form class="settings-options"
|
|
15
|
+
action="<%= switch_locale_path %>"
|
|
16
|
+
method="post"
|
|
17
|
+
data-turbo="false">
|
|
18
|
+
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
|
|
19
|
+
<% dashboard_locales.each do |locale| %>
|
|
20
|
+
<%# Each option is named in its own language, not the current one. %>
|
|
21
|
+
<label class="settings-option">
|
|
22
|
+
<input type="radio"
|
|
23
|
+
name="locale"
|
|
24
|
+
value="<%= locale %>"
|
|
25
|
+
<%= "checked" if locale.to_s == selected_locale.to_s %>
|
|
26
|
+
onchange="rememberSettingsOpen(); this.form.submit()">
|
|
27
|
+
<span><%= t("pg_reports.ui.language.name", locale: locale, default: locale.to_s.upcase) %></span>
|
|
28
|
+
</label>
|
|
29
|
+
<% end %>
|
|
30
|
+
</form>
|
|
31
|
+
</section>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<section class="settings-section">
|
|
35
|
+
<h4 class="settings-section-title"><%= t("pg_reports.ui.settings.default_ide_label") %></h4>
|
|
36
|
+
<div class="settings-options">
|
|
37
|
+
<% {
|
|
38
|
+
"" => "ide_show_menu",
|
|
39
|
+
"vscode-wsl" => "ide_vscode_wsl",
|
|
40
|
+
"vscode" => "ide_vscode",
|
|
41
|
+
"rubymine" => "ide_rubymine",
|
|
42
|
+
"intellij" => "ide_intellij",
|
|
43
|
+
"cursor-wsl" => "ide_cursor_wsl",
|
|
44
|
+
"cursor" => "ide_cursor"
|
|
45
|
+
}.each do |value, key| %>
|
|
46
|
+
<label class="settings-option">
|
|
47
|
+
<input type="radio" name="default-ide" value="<%= value %>" onchange="setDefaultIde('<%= value %>')">
|
|
48
|
+
<span><%= t("pg_reports.ui.settings.#{key}") %></span>
|
|
49
|
+
</label>
|
|
50
|
+
<% end %>
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
@@ -1,45 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
<div id="ide-settings-modal" class="modal" style="display: none;">
|
|
3
|
-
<div class="modal-content modal-small">
|
|
4
|
-
<div class="modal-header">
|
|
5
|
-
<h3><%= t("pg_reports.ui.modals.ide_settings_title") %></h3>
|
|
6
|
-
<button class="modal-close" onclick="closeIdeSettingsModal()">×</button>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="modal-body">
|
|
9
|
-
<p class="settings-label"><%= t("pg_reports.ui.settings.default_ide_label") %></p>
|
|
10
|
-
<div class="ide-options">
|
|
11
|
-
<label class="ide-option">
|
|
12
|
-
<input type="radio" name="default-ide" value="" onchange="setDefaultIde('')">
|
|
13
|
-
<span><%= t("pg_reports.ui.settings.ide_show_menu") %></span>
|
|
14
|
-
</label>
|
|
15
|
-
<label class="ide-option">
|
|
16
|
-
<input type="radio" name="default-ide" value="vscode-wsl" onchange="setDefaultIde('vscode-wsl')">
|
|
17
|
-
<span><%= t("pg_reports.ui.settings.ide_vscode_wsl") %></span>
|
|
18
|
-
</label>
|
|
19
|
-
<label class="ide-option">
|
|
20
|
-
<input type="radio" name="default-ide" value="vscode" onchange="setDefaultIde('vscode')">
|
|
21
|
-
<span><%= t("pg_reports.ui.settings.ide_vscode") %></span>
|
|
22
|
-
</label>
|
|
23
|
-
<label class="ide-option">
|
|
24
|
-
<input type="radio" name="default-ide" value="rubymine" onchange="setDefaultIde('rubymine')">
|
|
25
|
-
<span><%= t("pg_reports.ui.settings.ide_rubymine") %></span>
|
|
26
|
-
</label>
|
|
27
|
-
<label class="ide-option">
|
|
28
|
-
<input type="radio" name="default-ide" value="intellij" onchange="setDefaultIde('intellij')">
|
|
29
|
-
<span><%= t("pg_reports.ui.settings.ide_intellij") %></span>
|
|
30
|
-
</label>
|
|
31
|
-
<label class="ide-option">
|
|
32
|
-
<input type="radio" name="default-ide" value="cursor-wsl" onchange="setDefaultIde('cursor-wsl')">
|
|
33
|
-
<span><%= t("pg_reports.ui.settings.ide_cursor_wsl") %></span>
|
|
34
|
-
</label>
|
|
35
|
-
<label class="ide-option">
|
|
36
|
-
<input type="radio" name="default-ide" value="cursor" onchange="setDefaultIde('cursor')">
|
|
37
|
-
<span><%= t("pg_reports.ui.settings.ide_cursor") %></span>
|
|
38
|
-
</label>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
1
|
+
<%= render "settings_modal" %>
|
|
43
2
|
|
|
44
3
|
<!-- Problem Explanation Modal -->
|
|
45
4
|
<div id="problem-modal" class="problem-modal" style="display: none;">
|
|
@@ -854,7 +854,7 @@
|
|
|
854
854
|
document.addEventListener('keydown', function(e) {
|
|
855
855
|
if (e.key === 'Escape') {
|
|
856
856
|
closeProblemModal();
|
|
857
|
-
|
|
857
|
+
closeSettingsModal();
|
|
858
858
|
closeExplainModal();
|
|
859
859
|
closeMigrationModal();
|
|
860
860
|
}
|
|
@@ -867,44 +867,6 @@
|
|
|
867
867
|
}
|
|
868
868
|
});
|
|
869
869
|
|
|
870
|
-
// IDE Settings functions
|
|
871
|
-
function showIdeSettingsModal() {
|
|
872
|
-
const modal = document.getElementById('ide-settings-modal');
|
|
873
|
-
modal.style.display = 'flex';
|
|
874
|
-
loadIdeSettingsState();
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function closeIdeSettingsModal() {
|
|
878
|
-
document.getElementById('ide-settings-modal').style.display = 'none';
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
function setDefaultIde(ideKey) {
|
|
882
|
-
if (ideKey) {
|
|
883
|
-
localStorage.setItem('pgReportsDefaultIde', ideKey);
|
|
884
|
-
} else {
|
|
885
|
-
localStorage.removeItem('pgReportsDefaultIde');
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function getDefaultIde() {
|
|
890
|
-
return localStorage.getItem('pgReportsDefaultIde') || '';
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
function loadIdeSettingsState() {
|
|
894
|
-
const currentIde = getDefaultIde();
|
|
895
|
-
const radios = document.querySelectorAll('input[name="default-ide"]');
|
|
896
|
-
radios.forEach(radio => {
|
|
897
|
-
radio.checked = (radio.value === currentIde);
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Close IDE settings modal on backdrop click
|
|
902
|
-
document.getElementById('ide-settings-modal')?.addEventListener('click', function(e) {
|
|
903
|
-
if (e.target === this) {
|
|
904
|
-
closeIdeSettingsModal();
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
|
-
|
|
908
870
|
// ==========================================
|
|
909
871
|
// SAVED RECORDS FUNCTIONALITY
|
|
910
872
|
// ==========================================
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<%= t("pg_reports.ui.actions.reset_statistics") %>
|
|
29
29
|
</button>
|
|
30
30
|
<% end %>
|
|
31
|
-
<button type="button" class="btn-icon" onclick="
|
|
31
|
+
<button type="button" class="btn-icon" onclick="showSettingsModal()" title="<%= t("pg_reports.ui.actions.settings_button_title") %>" aria-label="<%= t("pg_reports.ui.actions.settings_button_title") %>">
|
|
32
32
|
<svg class="icon icon-lg"><use href="#i-settings"></use></svg>
|
|
33
33
|
</button>
|
|
34
34
|
|
|
@@ -127,47 +127,7 @@ pg_stat_statements.track = all</pre>
|
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
129
|
<!-- IDE Settings Modal -->
|
|
130
|
-
|
|
131
|
-
<div class="modal-content modal-small">
|
|
132
|
-
<div class="modal-header">
|
|
133
|
-
<h3><%= t("pg_reports.ui.modals.ide_settings_title") %></h3>
|
|
134
|
-
<button class="modal-close" onclick="closeIdeSettingsModal()">×</button>
|
|
135
|
-
</div>
|
|
136
|
-
<div class="modal-body">
|
|
137
|
-
<p class="settings-label"><%= t("pg_reports.ui.settings.default_ide_label") %></p>
|
|
138
|
-
<div class="ide-options">
|
|
139
|
-
<label class="ide-option">
|
|
140
|
-
<input type="radio" name="default-ide" value="" onchange="setDefaultIde('')">
|
|
141
|
-
<span><%= t("pg_reports.ui.settings.ide_show_menu") %></span>
|
|
142
|
-
</label>
|
|
143
|
-
<label class="ide-option">
|
|
144
|
-
<input type="radio" name="default-ide" value="vscode-wsl" onchange="setDefaultIde('vscode-wsl')">
|
|
145
|
-
<span><%= t("pg_reports.ui.settings.ide_vscode_wsl") %></span>
|
|
146
|
-
</label>
|
|
147
|
-
<label class="ide-option">
|
|
148
|
-
<input type="radio" name="default-ide" value="vscode" onchange="setDefaultIde('vscode')">
|
|
149
|
-
<span><%= t("pg_reports.ui.settings.ide_vscode") %></span>
|
|
150
|
-
</label>
|
|
151
|
-
<label class="ide-option">
|
|
152
|
-
<input type="radio" name="default-ide" value="rubymine" onchange="setDefaultIde('rubymine')">
|
|
153
|
-
<span><%= t("pg_reports.ui.settings.ide_rubymine") %></span>
|
|
154
|
-
</label>
|
|
155
|
-
<label class="ide-option">
|
|
156
|
-
<input type="radio" name="default-ide" value="intellij" onchange="setDefaultIde('intellij')">
|
|
157
|
-
<span><%= t("pg_reports.ui.settings.ide_intellij") %></span>
|
|
158
|
-
</label>
|
|
159
|
-
<label class="ide-option">
|
|
160
|
-
<input type="radio" name="default-ide" value="cursor-wsl" onchange="setDefaultIde('cursor-wsl')">
|
|
161
|
-
<span><%= t("pg_reports.ui.settings.ide_cursor_wsl") %></span>
|
|
162
|
-
</label>
|
|
163
|
-
<label class="ide-option">
|
|
164
|
-
<input type="radio" name="default-ide" value="cursor" onchange="setDefaultIde('cursor')">
|
|
165
|
-
<span><%= t("pg_reports.ui.settings.ide_cursor") %></span>
|
|
166
|
-
</label>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
130
|
+
<%= render "settings_modal" %>
|
|
171
131
|
|
|
172
132
|
<!-- Run Query Modal -->
|
|
173
133
|
<div id="run-query-modal" class="modal" style="display: none;">
|
|
@@ -239,7 +199,6 @@ end</pre>
|
|
|
239
199
|
<svg id="sparkline-connections" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
240
200
|
</div>
|
|
241
201
|
</div>
|
|
242
|
-
<div class="metric-status" id="status-connections"></div>
|
|
243
202
|
</div>
|
|
244
203
|
|
|
245
204
|
<!-- TPS -->
|
|
@@ -260,7 +219,6 @@ end</pre>
|
|
|
260
219
|
<svg id="sparkline-tps" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
261
220
|
</div>
|
|
262
221
|
</div>
|
|
263
|
-
<div class="metric-status" id="status-tps"></div>
|
|
264
222
|
</div>
|
|
265
223
|
|
|
266
224
|
<!-- Cache Hit Ratio -->
|
|
@@ -279,7 +237,6 @@ end</pre>
|
|
|
279
237
|
<svg id="sparkline-cache" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
280
238
|
</div>
|
|
281
239
|
</div>
|
|
282
|
-
<div class="metric-status" id="status-cache"></div>
|
|
283
240
|
</div>
|
|
284
241
|
|
|
285
242
|
<!-- Long Running Queries -->
|
|
@@ -298,7 +255,6 @@ end</pre>
|
|
|
298
255
|
<svg id="sparkline-longrunning" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
299
256
|
</div>
|
|
300
257
|
</div>
|
|
301
|
-
<div class="metric-status" id="status-longrunning"></div>
|
|
302
258
|
</div>
|
|
303
259
|
|
|
304
260
|
<!-- Blocked Queries -->
|
|
@@ -317,7 +273,6 @@ end</pre>
|
|
|
317
273
|
<svg id="sparkline-blocked" viewBox="0 0 100 30" preserveAspectRatio="none"></svg>
|
|
318
274
|
</div>
|
|
319
275
|
</div>
|
|
320
|
-
<div class="metric-status" id="status-blocked"></div>
|
|
321
276
|
</div>
|
|
322
277
|
</div>
|
|
323
278
|
</div>
|
|
@@ -654,11 +609,14 @@ end</pre>
|
|
|
654
609
|
value colour and the sparkline, never by a coloured edge: "healthy" is
|
|
655
610
|
the common case and should read as quiet, not decorated. */
|
|
656
611
|
.live-metric-card {
|
|
612
|
+
display: flex;
|
|
613
|
+
flex-direction: column;
|
|
657
614
|
background: var(--bg-tertiary);
|
|
658
615
|
border: 1px solid var(--border-color);
|
|
659
616
|
border-radius: var(--radius);
|
|
660
617
|
padding: 0.875rem 1rem;
|
|
661
|
-
|
|
618
|
+
/* The trend band below bleeds to the card's edges. */
|
|
619
|
+
overflow: hidden;
|
|
662
620
|
transition: border-color 0.12s ease;
|
|
663
621
|
}
|
|
664
622
|
|
|
@@ -674,7 +632,7 @@ end</pre>
|
|
|
674
632
|
display: flex;
|
|
675
633
|
align-items: center;
|
|
676
634
|
gap: 0.5rem;
|
|
677
|
-
margin-bottom: 0.
|
|
635
|
+
margin-bottom: 0.25rem;
|
|
678
636
|
}
|
|
679
637
|
|
|
680
638
|
.metric-icon {
|
|
@@ -693,6 +651,9 @@ end</pre>
|
|
|
693
651
|
.metric-body {
|
|
694
652
|
display: flex;
|
|
695
653
|
flex-direction: column;
|
|
654
|
+
/* Figures hug the right edge; the trend band below opts back out. */
|
|
655
|
+
align-items: flex-end;
|
|
656
|
+
flex: 1;
|
|
696
657
|
gap: 0.1875rem;
|
|
697
658
|
}
|
|
698
659
|
|
|
@@ -719,17 +680,27 @@ end</pre>
|
|
|
719
680
|
}
|
|
720
681
|
|
|
721
682
|
.metric-detail {
|
|
683
|
+
max-width: 100%;
|
|
722
684
|
font-size: 0.7rem;
|
|
723
685
|
color: var(--text-muted);
|
|
686
|
+
text-align: right;
|
|
724
687
|
white-space: nowrap;
|
|
725
688
|
overflow: hidden;
|
|
726
689
|
text-overflow: ellipsis;
|
|
727
690
|
}
|
|
728
691
|
|
|
729
|
-
/*
|
|
692
|
+
/* Trend band. Negative margins cancel the card's padding so it spans the full
|
|
693
|
+
width and sits flush with the bottom edge — as a floating inset line it left
|
|
694
|
+
the lower half of the card reading as dead space. `margin-top: auto` keeps
|
|
695
|
+
it pinned down when cards in a row differ in height. */
|
|
730
696
|
.metric-sparkline {
|
|
731
|
-
|
|
732
|
-
|
|
697
|
+
/* Full width, regardless of the right-alignment its siblings use. */
|
|
698
|
+
align-self: stretch;
|
|
699
|
+
height: 2.25rem;
|
|
700
|
+
/* auto keeps the band on the bottom edge when cards in a row differ in
|
|
701
|
+
height; the negative side/bottom margins cancel the card's padding. */
|
|
702
|
+
margin: auto -1rem -0.875rem;
|
|
703
|
+
padding-top: 0.5rem;
|
|
733
704
|
color: var(--text-muted);
|
|
734
705
|
}
|
|
735
706
|
|
|
@@ -742,20 +713,6 @@ end</pre>
|
|
|
742
713
|
display: block;
|
|
743
714
|
}
|
|
744
715
|
|
|
745
|
-
/* Status indicator */
|
|
746
|
-
.metric-status {
|
|
747
|
-
position: absolute;
|
|
748
|
-
top: 0.75rem;
|
|
749
|
-
right: 0.75rem;
|
|
750
|
-
width: 6px;
|
|
751
|
-
height: 6px;
|
|
752
|
-
border-radius: 50%;
|
|
753
|
-
background: var(--border-strong);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
.metric-status.warning { background: var(--accent-amber); }
|
|
757
|
-
.metric-status.critical { background: var(--accent-rose); }
|
|
758
|
-
|
|
759
716
|
/* Run Query Modal */
|
|
760
717
|
.modal-run-query {
|
|
761
718
|
max-width: 1100px;
|
|
@@ -1329,23 +1286,30 @@ end</pre>
|
|
|
1329
1286
|
|
|
1330
1287
|
const width = 100;
|
|
1331
1288
|
const height = 30;
|
|
1289
|
+
// No horizontal inset: the band spans the card's full width, so the trace
|
|
1290
|
+
// should reach both edges. Vertical inset only, to keep the stroke from
|
|
1291
|
+
// being clipped at the extremes.
|
|
1332
1292
|
const padding = 2;
|
|
1333
1293
|
|
|
1334
1294
|
const min = Math.min(...data);
|
|
1335
1295
|
const max = Math.max(...data);
|
|
1336
|
-
const range = max - min
|
|
1296
|
+
const range = max - min;
|
|
1337
1297
|
|
|
1338
1298
|
const points = data.map((value, index) => {
|
|
1339
|
-
const x =
|
|
1340
|
-
|
|
1299
|
+
const x = (index / (data.length - 1)) * width;
|
|
1300
|
+
// A series that never moves is drawn mid-band: pinned to the floor it
|
|
1301
|
+
// reads as "zero" (or as a stray underline) rather than "steady".
|
|
1302
|
+
const y = range === 0
|
|
1303
|
+
? height / 2
|
|
1304
|
+
: height - padding - ((value - min) / range) * (height - 2 * padding);
|
|
1341
1305
|
return { x, y };
|
|
1342
1306
|
});
|
|
1343
1307
|
|
|
1344
1308
|
const pathD = points.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(' ');
|
|
1345
1309
|
|
|
1346
1310
|
const areaD = pathD +
|
|
1347
|
-
` L ${points[points.length - 1].x} ${height
|
|
1348
|
-
` L ${points[0].x} ${height
|
|
1311
|
+
` L ${points[points.length - 1].x} ${height}` +
|
|
1312
|
+
` L ${points[0].x} ${height} Z`;
|
|
1349
1313
|
|
|
1350
1314
|
const area = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1351
1315
|
area.setAttribute('d', areaD);
|
|
@@ -1366,8 +1330,7 @@ end</pre>
|
|
|
1366
1330
|
|
|
1367
1331
|
function updateStatus(metric, value, badDirection) {
|
|
1368
1332
|
const card = document.querySelector(`[data-metric="${metric}"]`);
|
|
1369
|
-
|
|
1370
|
-
if (!card || !statusDot) return;
|
|
1333
|
+
if (!card) return;
|
|
1371
1334
|
|
|
1372
1335
|
const thresholds = LIVE_CONFIG.thresholds[metric];
|
|
1373
1336
|
let status = 'ok';
|
|
@@ -1384,8 +1347,6 @@ end</pre>
|
|
|
1384
1347
|
|
|
1385
1348
|
card.classList.remove('status-ok', 'status-warning', 'status-critical');
|
|
1386
1349
|
card.classList.add(`status-${status}`);
|
|
1387
|
-
statusDot.classList.remove('ok', 'warning', 'critical');
|
|
1388
|
-
statusDot.classList.add(status);
|
|
1389
1350
|
}
|
|
1390
1351
|
|
|
1391
1352
|
document.addEventListener('DOMContentLoaded', initLiveMonitoring);
|
|
@@ -1407,10 +1368,6 @@ end</pre>
|
|
|
1407
1368
|
'cursor': 5
|
|
1408
1369
|
};
|
|
1409
1370
|
|
|
1410
|
-
function getDefaultIde() {
|
|
1411
|
-
return localStorage.getItem('pgReportsDefaultIde') || '';
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
1371
|
function generateIdeUrls(filePath, lineNumber) {
|
|
1415
1372
|
if (!filePath) return [];
|
|
1416
1373
|
|
|
@@ -1509,41 +1466,6 @@ end</pre>
|
|
|
1509
1466
|
document.getElementById('ide-dropdown-overlay')?.classList.remove('show');
|
|
1510
1467
|
}
|
|
1511
1468
|
|
|
1512
|
-
function showIdeSettingsModal() {
|
|
1513
|
-
const modal = document.getElementById('ide-settings-modal');
|
|
1514
|
-
modal.style.display = 'flex';
|
|
1515
|
-
loadIdeSettingsState();
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
function closeIdeSettingsModal() {
|
|
1519
|
-
document.getElementById('ide-settings-modal').style.display = 'none';
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
function setDefaultIde(ideKey) {
|
|
1523
|
-
if (ideKey) {
|
|
1524
|
-
localStorage.setItem('pgReportsDefaultIde', ideKey);
|
|
1525
|
-
} else {
|
|
1526
|
-
localStorage.removeItem('pgReportsDefaultIde');
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
function loadIdeSettingsState() {
|
|
1531
|
-
const currentIde = getDefaultIde();
|
|
1532
|
-
const radios = document.querySelectorAll('input[name="default-ide"]');
|
|
1533
|
-
radios.forEach(radio => {
|
|
1534
|
-
radio.checked = (radio.value === currentIde);
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
// Close IDE settings modal on backdrop click
|
|
1539
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
1540
|
-
document.getElementById('ide-settings-modal')?.addEventListener('click', function(e) {
|
|
1541
|
-
if (e.target === this) {
|
|
1542
|
-
closeIdeSettingsModal();
|
|
1543
|
-
}
|
|
1544
|
-
});
|
|
1545
|
-
});
|
|
1546
|
-
|
|
1547
1469
|
// ============================================
|
|
1548
1470
|
// Query Monitoring
|
|
1549
1471
|
// ============================================
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
<%= t("pg_reports.ui.actions.send_telegram") %>
|
|
49
49
|
</button>
|
|
50
50
|
<% end %>
|
|
51
|
-
<button type="button" class="btn-icon" onclick="
|
|
51
|
+
<button type="button" class="btn-icon" onclick="showSettingsModal()" title="<%= t("pg_reports.ui.actions.settings_button_title") %>" aria-label="<%= t("pg_reports.ui.actions.settings_button_title") %>">
|
|
52
52
|
<svg class="icon icon-lg"><use href="#i-settings"></use></svg>
|
|
53
53
|
</button>
|
|
54
54
|
</div>
|
data/config/locales/en.yml
CHANGED
|
@@ -589,6 +589,9 @@ en:
|
|
|
589
589
|
title: "PgReports"
|
|
590
590
|
subtitle: "PostgreSQL Analysis Dashboard"
|
|
591
591
|
page_title: "PgReports Dashboard"
|
|
592
|
+
language:
|
|
593
|
+
label: "Language"
|
|
594
|
+
name: "English"
|
|
592
595
|
navigation:
|
|
593
596
|
dashboard: "Dashboard"
|
|
594
597
|
back: "Back"
|
|
@@ -619,7 +622,7 @@ en:
|
|
|
619
622
|
confirm_reset: "Yes, Reset"
|
|
620
623
|
create_extension: "Create Extension"
|
|
621
624
|
creating: "Creating..."
|
|
622
|
-
|
|
625
|
+
settings_button_title: "Settings"
|
|
623
626
|
explain_analyze: "EXPLAIN ANALYZE"
|
|
624
627
|
execute_query: "Execute Query"
|
|
625
628
|
run_query: "SQL Console"
|
|
@@ -654,7 +657,7 @@ en:
|
|
|
654
657
|
reset_stats_title: "Reset Statistics"
|
|
655
658
|
reset_stats_confirm: "Are you sure you want to reset pg_stat_statements statistics?"
|
|
656
659
|
reset_stats_warning: "This action will clear all collected query statistics and cannot be undone."
|
|
657
|
-
|
|
660
|
+
settings_title: "Settings"
|
|
658
661
|
problem_detected_title: "Problem Detected"
|
|
659
662
|
query_analyzer_title: "Query Analyzer"
|
|
660
663
|
query_label: "Query:"
|
|
@@ -670,7 +673,7 @@ en:
|
|
|
670
673
|
run_query_title: "SQL Console"
|
|
671
674
|
run_query_placeholder: "SELECT * FROM users LIMIT 10;"
|
|
672
675
|
settings:
|
|
673
|
-
default_ide_label: "Default IDE for source links
|
|
676
|
+
default_ide_label: "Default IDE for source links"
|
|
674
677
|
ide_show_menu: "Show menu (default)"
|
|
675
678
|
ide_vscode_wsl: "VS Code (WSL)"
|
|
676
679
|
ide_vscode: "VS Code"
|
data/config/locales/ru.yml
CHANGED
|
@@ -554,6 +554,9 @@ ru:
|
|
|
554
554
|
title: "PgReports"
|
|
555
555
|
subtitle: "Дашборд анализа PostgreSQL"
|
|
556
556
|
page_title: "PgReports — Дашборд"
|
|
557
|
+
language:
|
|
558
|
+
label: "Язык"
|
|
559
|
+
name: "Русский"
|
|
557
560
|
navigation:
|
|
558
561
|
dashboard: "Дашборд"
|
|
559
562
|
back: "Назад"
|
|
@@ -584,7 +587,7 @@ ru:
|
|
|
584
587
|
confirm_reset: "Да, сбросить"
|
|
585
588
|
create_extension: "Создать расширение"
|
|
586
589
|
creating: "Создание..."
|
|
587
|
-
|
|
590
|
+
settings_button_title: "Настройки"
|
|
588
591
|
explain_analyze: "EXPLAIN ANALYZE"
|
|
589
592
|
execute_query: "Выполнить запрос"
|
|
590
593
|
run_query: "SQL-консоль"
|
|
@@ -619,7 +622,7 @@ ru:
|
|
|
619
622
|
reset_stats_title: "Сброс статистики"
|
|
620
623
|
reset_stats_confirm: "Вы уверены, что хотите сбросить статистику pg_stat_statements?"
|
|
621
624
|
reset_stats_warning: "Это действие очистит всю собранную статистику запросов и не может быть отменено."
|
|
622
|
-
|
|
625
|
+
settings_title: "Настройки"
|
|
623
626
|
problem_detected_title: "Обнаружена проблема"
|
|
624
627
|
query_analyzer_title: "Анализатор запроса"
|
|
625
628
|
query_label: "Запрос:"
|
|
@@ -635,7 +638,7 @@ ru:
|
|
|
635
638
|
run_query_title: "SQL-консоль"
|
|
636
639
|
run_query_placeholder: "SELECT * FROM users LIMIT 10;"
|
|
637
640
|
settings:
|
|
638
|
-
default_ide_label: "IDE по умолчанию для ссылок на
|
|
641
|
+
default_ide_label: "IDE по умолчанию для ссылок на исходники"
|
|
639
642
|
ide_show_menu: "Показывать меню (по умолчанию)"
|
|
640
643
|
ide_vscode_wsl: "VS Code (WSL)"
|
|
641
644
|
ide_vscode: "VS Code"
|
data/config/locales/uk.yml
CHANGED
|
@@ -554,6 +554,9 @@ uk:
|
|
|
554
554
|
title: "PgReports"
|
|
555
555
|
subtitle: "Дашборд аналізу PostgreSQL"
|
|
556
556
|
page_title: "PgReports — Дашборд"
|
|
557
|
+
language:
|
|
558
|
+
label: "Мова"
|
|
559
|
+
name: "Українська"
|
|
557
560
|
navigation:
|
|
558
561
|
dashboard: "Дашборд"
|
|
559
562
|
back: "Назад"
|
|
@@ -584,7 +587,7 @@ uk:
|
|
|
584
587
|
confirm_reset: "Так, скинути"
|
|
585
588
|
create_extension: "Створити розширення"
|
|
586
589
|
creating: "Створення..."
|
|
587
|
-
|
|
590
|
+
settings_button_title: "Налаштування"
|
|
588
591
|
explain_analyze: "EXPLAIN ANALYZE"
|
|
589
592
|
execute_query: "Виконати запит"
|
|
590
593
|
run_query: "SQL-консоль"
|
|
@@ -619,7 +622,7 @@ uk:
|
|
|
619
622
|
reset_stats_title: "Скидання статистики"
|
|
620
623
|
reset_stats_confirm: "Ви впевнені, що хочете скинути статистику pg_stat_statements?"
|
|
621
624
|
reset_stats_warning: "Ця дія очистить усю зібрану статистику запитів і не може бути скасована."
|
|
622
|
-
|
|
625
|
+
settings_title: "Налаштування"
|
|
623
626
|
problem_detected_title: "Виявлено проблему"
|
|
624
627
|
query_analyzer_title: "Аналізатор запиту"
|
|
625
628
|
query_label: "Запит:"
|
|
@@ -635,7 +638,7 @@ uk:
|
|
|
635
638
|
run_query_title: "SQL-консоль"
|
|
636
639
|
run_query_placeholder: "SELECT * FROM users LIMIT 10;"
|
|
637
640
|
settings:
|
|
638
|
-
default_ide_label: "IDE за замовчуванням для посилань на
|
|
641
|
+
default_ide_label: "IDE за замовчуванням для посилань на джерела"
|
|
639
642
|
ide_show_menu: "Показувати меню (за замовчуванням)"
|
|
640
643
|
ide_vscode_wsl: "VS Code (WSL)"
|
|
641
644
|
ide_vscode: "VS Code"
|
data/config/routes.rb
CHANGED
|
@@ -9,6 +9,7 @@ PgReports::Engine.routes.draw do
|
|
|
9
9
|
|
|
10
10
|
post "switch_database", to: "dashboard#switch_database", as: :switch_database
|
|
11
11
|
post "switch_target", to: "dashboard#switch_target", as: :switch_target
|
|
12
|
+
post "switch_locale", to: "dashboard#switch_locale", as: :switch_locale
|
|
12
13
|
|
|
13
14
|
post "enable_pg_stat_statements", to: "dashboard#enable_pg_stat_statements", as: :enable_pg_stat_statements
|
|
14
15
|
post "reset_statistics", to: "dashboard#reset_statistics", as: :reset_statistics
|
data/lib/pg_reports/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_reports
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eldar Avatov
|
|
@@ -153,6 +153,7 @@ files:
|
|
|
153
153
|
- app/views/layouts/pg_reports/application.html.erb
|
|
154
154
|
- app/views/pg_reports/dashboard/_database_selector.html.erb
|
|
155
155
|
- app/views/pg_reports/dashboard/_fake_source_data.html.erb
|
|
156
|
+
- app/views/pg_reports/dashboard/_settings_modal.html.erb
|
|
156
157
|
- app/views/pg_reports/dashboard/_show_modals.html.erb
|
|
157
158
|
- app/views/pg_reports/dashboard/_show_scripts.html.erb
|
|
158
159
|
- app/views/pg_reports/dashboard/_show_styles.html.erb
|