pg_reports 0.8.1 → 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 +50 -0
- data/README.md +23 -2
- data/app/controllers/pg_reports/dashboard_controller.rb +241 -24
- 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 +426 -335
- data/app/views/pg_reports/dashboard/show.html.erb +13 -11
- data/bin/pg_reports +28 -2
- data/config/brakeman.ignore +97 -0
- data/config/locales/en.yml +62 -47
- data/config/locales/ru.yml +62 -47
- data/config/locales/uk.yml +62 -47
- data/config/routes.rb +1 -0
- data/lib/pg_reports/configuration.rb +8 -0
- 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/grafana/exporter.rb +5 -1
- data/lib/pg_reports/query_monitor.rb +15 -3
- data/lib/pg_reports/standalone.rb +59 -1
- data/lib/pg_reports/version.rb +1 -1
- data/lib/pg_reports.rb +5 -0
- metadata +2 -1
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="report-page">
|
|
4
4
|
<nav class="breadcrumb">
|
|
5
|
-
<%= link_to t("pg_reports.ui.navigation.
|
|
6
|
-
|
|
5
|
+
<%= link_to root_path, class: "breadcrumb-back", title: t("pg_reports.ui.navigation.back") do %>
|
|
6
|
+
<svg class="icon" aria-hidden="true"><use href="#i-arrow-left"></use></svg>
|
|
7
|
+
<span><%= t("pg_reports.ui.navigation.dashboard") %></span>
|
|
8
|
+
<% end %>
|
|
9
|
+
<span class="breadcrumb-sep">/</span>
|
|
7
10
|
<span><%= @categories[@category][:name] %></span>
|
|
8
|
-
<span>/</span>
|
|
9
|
-
<span><%= @report_info[:name] %></span>
|
|
11
|
+
<span class="breadcrumb-sep">/</span>
|
|
12
|
+
<span class="breadcrumb-current"><%= @report_info[:name] %></span>
|
|
10
13
|
|
|
11
14
|
<span class="breadcrumb-spacer"></span>
|
|
12
15
|
<%= render "target_selector" %>
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
<a href="#" onclick="downloadReport('json'); return false;"><%= t("pg_reports.ui.actions.download_json") %></a>
|
|
36
39
|
<% if @documentation && @documentation[:ai_prompt].present? %>
|
|
37
40
|
<div class="dropdown-divider"></div>
|
|
38
|
-
<a href="#" id="ai-prompt-btn" onclick="copyAiPrompt(this); return false;"><span class="ai-icon">AI</span
|
|
41
|
+
<a href="#" id="ai-prompt-btn" onclick="copyAiPrompt(this); return false;"><span class="ai-icon">AI</span><%= t("pg_reports.ui.actions.copy_ai_prompt") %></a>
|
|
39
42
|
<% end %>
|
|
40
43
|
</div>
|
|
41
44
|
</div>
|
|
@@ -45,10 +48,9 @@
|
|
|
45
48
|
<%= t("pg_reports.ui.actions.send_telegram") %>
|
|
46
49
|
</button>
|
|
47
50
|
<% end %>
|
|
48
|
-
<button class="btn
|
|
49
|
-
|
|
51
|
+
<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") %>">
|
|
52
|
+
<svg class="icon icon-lg"><use href="#i-settings"></use></svg>
|
|
50
53
|
</button>
|
|
51
|
-
<%= link_to t("pg_reports.ui.navigation.back"), root_path, class: "btn btn-secondary" %>
|
|
52
54
|
</div>
|
|
53
55
|
</div>
|
|
54
56
|
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
<% if @documentation && @documentation[:what].present? %>
|
|
64
66
|
<details class="documentation-section">
|
|
65
67
|
<summary class="documentation-toggle">
|
|
66
|
-
<
|
|
68
|
+
<svg class="icon toggle-icon" aria-hidden="true"><use href="#i-chevron-right"></use></svg>
|
|
67
69
|
<span><%= t("pg_reports.ui.documentation.toggle_title") %></span>
|
|
68
70
|
</summary>
|
|
69
71
|
<div class="documentation-content">
|
|
@@ -118,7 +120,7 @@
|
|
|
118
120
|
<div class="filter-section">
|
|
119
121
|
<details class="filter-details">
|
|
120
122
|
<summary class="filter-toggle">
|
|
121
|
-
<
|
|
123
|
+
<svg class="icon toggle-icon" aria-hidden="true"><use href="#i-chevron-right"></use></svg>
|
|
122
124
|
<span><%= t("pg_reports.ui.filters.title") %></span>
|
|
123
125
|
</summary>
|
|
124
126
|
<div class="filter-content">
|
|
@@ -176,7 +178,7 @@
|
|
|
176
178
|
</div>
|
|
177
179
|
|
|
178
180
|
<div id="empty-state" class="empty-state" style="display: none;">
|
|
179
|
-
<
|
|
181
|
+
<svg class="icon empty-state-icon" aria-hidden="true"><use href="#i-check"></use></svg>
|
|
180
182
|
<p><%= t("pg_reports.ui.results.empty_message") %></p>
|
|
181
183
|
</div>
|
|
182
184
|
|
data/bin/pg_reports
CHANGED
|
@@ -23,7 +23,16 @@ options = {
|
|
|
23
23
|
host: "127.0.0.1",
|
|
24
24
|
mount: "/",
|
|
25
25
|
database_url: nil,
|
|
26
|
-
server: nil
|
|
26
|
+
server: nil,
|
|
27
|
+
config_file: nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Per-setting overrides from flags. nil means "unset" — the config file / ENV
|
|
31
|
+
# value is kept. Populated below and forwarded to Standalone.run(overrides:).
|
|
32
|
+
overrides = {
|
|
33
|
+
allow_raw_query_execution: nil,
|
|
34
|
+
allow_migration_creation: nil,
|
|
35
|
+
load_external_fonts: nil
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
banner = <<~BANNER
|
|
@@ -35,6 +44,12 @@ banner = <<~BANNER
|
|
|
35
44
|
Connection is resolved from --database-url, else DATABASE_URL, else the
|
|
36
45
|
standard libpq env vars (PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE).
|
|
37
46
|
|
|
47
|
+
Configuration is layered, lowest to highest precedence:
|
|
48
|
+
PG_REPORTS_* env vars < config file < the flags below.
|
|
49
|
+
A config file (--config, else ./pg_reports.rb or config/pg_reports.rb) is
|
|
50
|
+
plain Ruby calling `PgReports.configure` — the way to set every option that
|
|
51
|
+
has no flag (thresholds, Telegram, Grafana, dashboard_auth, …).
|
|
52
|
+
|
|
38
53
|
Options:
|
|
39
54
|
BANNER
|
|
40
55
|
|
|
@@ -45,6 +60,10 @@ parser = OptionParser.new do |o|
|
|
|
45
60
|
o.on("-m", "--mount PATH", "Path to mount the dashboard at (default: /)") { |v| options[:mount] = v }
|
|
46
61
|
o.on("-d", "--database-url URL", "PostgreSQL connection URL") { |v| options[:database_url] = v }
|
|
47
62
|
o.on("-s", "--server NAME", "Web server to use (e.g. puma, webrick)") { |v| options[:server] = v }
|
|
63
|
+
o.on("-c", "--config PATH", "Ruby config file to load (default: ./pg_reports.rb or config/pg_reports.rb)") { |v| options[:config_file] = v }
|
|
64
|
+
o.on("--[no-]allow-raw-query-execution", "Allow running raw SQL / EXPLAIN ANALYZE from the dashboard") { |v| overrides[:allow_raw_query_execution] = v }
|
|
65
|
+
o.on("--[no-]allow-migration-creation", "Allow the dashboard to write migration files to db/migrate/") { |v| overrides[:allow_migration_creation] = v }
|
|
66
|
+
o.on("--[no-]external-fonts", "Load Google Fonts in the dashboard (off by default)") { |v| overrides[:load_external_fonts] = v }
|
|
48
67
|
o.on("-h", "--help", "Show this help") do
|
|
49
68
|
puts o
|
|
50
69
|
exit
|
|
@@ -70,11 +89,18 @@ when "server"
|
|
|
70
89
|
host: options[:host],
|
|
71
90
|
mount_path: options[:mount],
|
|
72
91
|
database_url: options[:database_url],
|
|
73
|
-
server: options[:server]
|
|
92
|
+
server: options[:server],
|
|
93
|
+
config_file: options[:config_file],
|
|
94
|
+
overrides: overrides
|
|
74
95
|
)
|
|
75
96
|
rescue PgReports::Standalone::ServerUnavailable => e
|
|
76
97
|
warn e.message
|
|
77
98
|
exit 1
|
|
99
|
+
rescue PgReports::Error => e
|
|
100
|
+
# Config-file and connection problems surface as PgReports::Error with a
|
|
101
|
+
# human-readable message — print it, not a backtrace.
|
|
102
|
+
warn "pg_reports: #{e.message}"
|
|
103
|
+
exit 1
|
|
78
104
|
rescue Interrupt
|
|
79
105
|
warn "\npg_reports: stopped"
|
|
80
106
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ignored_warnings": [
|
|
3
|
+
{
|
|
4
|
+
"warning_type": "SQL Injection",
|
|
5
|
+
"warning_code": 0,
|
|
6
|
+
"fingerprint": "5209397462e689c349f77e534f897c12a8550b9a013bd535153a929c12b944e2",
|
|
7
|
+
"check_name": "SQL",
|
|
8
|
+
"message": "Possible SQL injection",
|
|
9
|
+
"file": "app/controllers/pg_reports/dashboard_controller.rb",
|
|
10
|
+
"line": 357,
|
|
11
|
+
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
12
|
+
"code": "ActiveRecord::Base.connection.execute(\"SELECT COUNT(*) FROM (#{substitute_params(retrieve_query_by_hash(params[:query_hash]), (params[:params] or {}))}) AS count_query\")",
|
|
13
|
+
"render_path": null,
|
|
14
|
+
"location": {
|
|
15
|
+
"type": "method",
|
|
16
|
+
"class": "PgReports::DashboardController",
|
|
17
|
+
"method": "execute_query"
|
|
18
|
+
},
|
|
19
|
+
"user_input": "params[:params]",
|
|
20
|
+
"confidence": "High",
|
|
21
|
+
"cwe_id": [
|
|
22
|
+
89
|
|
23
|
+
],
|
|
24
|
+
"note": "Reviewed & accepted: user-query execution feature (execute_query count). Gated by config.allow_raw_query_execution; the query is fetched by hash from an internal registry (retrieve_query_by_hash), parameters are validated/substituted, and SecurityError checks reject dangerous input. Not arbitrary attacker-controlled SQL."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"warning_type": "SQL Injection",
|
|
28
|
+
"warning_code": 0,
|
|
29
|
+
"fingerprint": "52294539b2a9666f9a79f7ad52cdd1dc531a3b71e487e5134174029cd5138571",
|
|
30
|
+
"check_name": "SQL",
|
|
31
|
+
"message": "Possible SQL injection",
|
|
32
|
+
"file": "app/controllers/pg_reports/dashboard_controller.rb",
|
|
33
|
+
"line": 440,
|
|
34
|
+
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
35
|
+
"code": "ActiveRecord::Base.connection.execute(\"SELECT COUNT(*) FROM (#{params[:query].to_s}) AS count_query\")",
|
|
36
|
+
"render_path": null,
|
|
37
|
+
"location": {
|
|
38
|
+
"type": "method",
|
|
39
|
+
"class": "PgReports::DashboardController",
|
|
40
|
+
"method": "run_query"
|
|
41
|
+
},
|
|
42
|
+
"user_input": "params[:query]",
|
|
43
|
+
"confidence": "High",
|
|
44
|
+
"cwe_id": [
|
|
45
|
+
89
|
|
46
|
+
],
|
|
47
|
+
"note": "Reviewed & accepted: SQL Console (run_query count). Gated by config.allow_raw_query_execution; the client-typed query passes enforce_select_only! (SELECT-only, no semicolons, keyword denylist) and runs under a bounded statement_timeout. Intentional DBA tooling; same risk profile as execute_query/explain_analyze."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"warning_type": "Cross-Site Request Forgery",
|
|
51
|
+
"warning_code": 7,
|
|
52
|
+
"fingerprint": "b9682cc0e0a1cd86fddef96834d4f71c3558989c2136fe58b5ff51c75b83c8b4",
|
|
53
|
+
"check_name": "ForgerySetting",
|
|
54
|
+
"message": "`protect_from_forgery` should be called in `PgReports::MetricsController`",
|
|
55
|
+
"file": "app/controllers/pg_reports/metrics_controller.rb",
|
|
56
|
+
"line": 6,
|
|
57
|
+
"link": "https://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
|
|
58
|
+
"code": null,
|
|
59
|
+
"render_path": null,
|
|
60
|
+
"location": {
|
|
61
|
+
"type": "controller",
|
|
62
|
+
"controller": "PgReports::MetricsController"
|
|
63
|
+
},
|
|
64
|
+
"user_input": null,
|
|
65
|
+
"confidence": "High",
|
|
66
|
+
"cwe_id": [
|
|
67
|
+
352
|
|
68
|
+
],
|
|
69
|
+
"note": "Reviewed & accepted: read-only Prometheus /metrics endpoint. Uses bearer-token auth (config.grafana_metrics_token) with constant-time compare; CSRF protection is not applicable to a GET scrape endpoint."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"warning_type": "SQL Injection",
|
|
73
|
+
"warning_code": 0,
|
|
74
|
+
"fingerprint": "f133378f2542f3c09b527aebcc0939a1d227c46b0ab124be613e1e5aac829c79",
|
|
75
|
+
"check_name": "SQL",
|
|
76
|
+
"message": "Possible SQL injection",
|
|
77
|
+
"file": "app/controllers/pg_reports/dashboard_controller.rb",
|
|
78
|
+
"line": 278,
|
|
79
|
+
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
80
|
+
"code": "ActiveRecord::Base.connection.execute(\"EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) #{substitute_params(retrieve_query_by_hash(params[:query_hash]), (params[:params] or {}))}\")",
|
|
81
|
+
"render_path": null,
|
|
82
|
+
"location": {
|
|
83
|
+
"type": "method",
|
|
84
|
+
"class": "PgReports::DashboardController",
|
|
85
|
+
"method": "explain_analyze"
|
|
86
|
+
},
|
|
87
|
+
"user_input": "params[:params]",
|
|
88
|
+
"confidence": "High",
|
|
89
|
+
"cwe_id": [
|
|
90
|
+
89
|
|
91
|
+
],
|
|
92
|
+
"note": "Reviewed & accepted: EXPLAIN ANALYZE feature. Same safeguards as execute_query - query resolved by hash from internal registry, params validated, SecurityError guards. Intentional DBA tooling."
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"updated": "2026-07-10 19:15:00 +0300",
|
|
96
|
+
"brakeman_version": "8.0.5"
|
|
97
|
+
}
|
data/config/locales/en.yml
CHANGED
|
@@ -591,7 +591,7 @@ en:
|
|
|
591
591
|
page_title: "PgReports Dashboard"
|
|
592
592
|
navigation:
|
|
593
593
|
dashboard: "Dashboard"
|
|
594
|
-
back: "
|
|
594
|
+
back: "Back"
|
|
595
595
|
database_selector:
|
|
596
596
|
label: "Database"
|
|
597
597
|
target_selector:
|
|
@@ -599,39 +599,41 @@ en:
|
|
|
599
599
|
actions:
|
|
600
600
|
cancel: "Cancel"
|
|
601
601
|
retry: "Retry"
|
|
602
|
-
copy: "
|
|
603
|
-
copy_query: "
|
|
604
|
-
copy_code: "
|
|
602
|
+
copy: "Copy"
|
|
603
|
+
copy_query: "Copy Query"
|
|
604
|
+
copy_code: "Copy Code"
|
|
605
605
|
copy_to_clipboard_title: "Copy to clipboard"
|
|
606
|
-
copied_feedback: "
|
|
606
|
+
copied_feedback: "Copied!"
|
|
607
607
|
clear_all: "Clear All"
|
|
608
|
-
run_report: "
|
|
609
|
-
export: "
|
|
610
|
-
download_text: "
|
|
611
|
-
download_csv: "
|
|
612
|
-
download_json: "
|
|
613
|
-
download: "
|
|
608
|
+
run_report: "Run Report"
|
|
609
|
+
export: "Export"
|
|
610
|
+
download_text: "Text (.txt)"
|
|
611
|
+
download_csv: "CSV (.csv)"
|
|
612
|
+
download_json: "JSON (.json)"
|
|
613
|
+
download: "Download"
|
|
614
614
|
copy_ai_prompt: "Copy Prompt"
|
|
615
|
-
send_telegram: "
|
|
615
|
+
send_telegram: "Telegram"
|
|
616
616
|
sending: "Sending..."
|
|
617
|
-
reset_statistics: "
|
|
617
|
+
reset_statistics: "Reset Statistics"
|
|
618
618
|
resetting: "Resetting..."
|
|
619
619
|
confirm_reset: "Yes, Reset"
|
|
620
|
-
create_extension: "
|
|
620
|
+
create_extension: "Create Extension"
|
|
621
621
|
creating: "Creating..."
|
|
622
622
|
ide_settings_button_title: "IDE Settings"
|
|
623
|
-
explain_analyze: "
|
|
624
|
-
execute_query: "
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
623
|
+
explain_analyze: "EXPLAIN ANALYZE"
|
|
624
|
+
execute_query: "Execute Query"
|
|
625
|
+
run_query: "SQL Console"
|
|
626
|
+
create_migration_file: "Create File & Open in IDE"
|
|
627
|
+
start_monitoring: "Start Monitoring"
|
|
628
|
+
stop_monitoring: "Stop Monitoring"
|
|
628
629
|
starting: "Starting..."
|
|
629
630
|
stopping: "Stopping..."
|
|
630
|
-
load_history: "
|
|
631
|
+
load_history: "Load History"
|
|
632
|
+
load_history_unavailable_title: "No stored history — set config.query_monitor_log_file to keep captured queries across restarts"
|
|
631
633
|
loading: "Loading..."
|
|
632
634
|
running: "Running..."
|
|
633
|
-
save_for_comparison: "
|
|
634
|
-
saved_marker: "
|
|
635
|
+
save_for_comparison: "Save for Comparison"
|
|
636
|
+
saved_marker: "Saved"
|
|
635
637
|
status:
|
|
636
638
|
pg_stat_ready: "Active"
|
|
637
639
|
not_preloaded: "Preload required"
|
|
@@ -649,20 +651,24 @@ en:
|
|
|
649
651
|
create_extension_title: "Create pg_stat_statements extension"
|
|
650
652
|
create_extension_intro: "The pg_stat_statements extension isn't created in this database yet. Click below to create it:"
|
|
651
653
|
create_extension_note: "If the library isn't preloaded, you'll also need to add it to shared_preload_libraries and restart PostgreSQL."
|
|
652
|
-
reset_stats_title: "
|
|
654
|
+
reset_stats_title: "Reset Statistics"
|
|
653
655
|
reset_stats_confirm: "Are you sure you want to reset pg_stat_statements statistics?"
|
|
654
656
|
reset_stats_warning: "This action will clear all collected query statistics and cannot be undone."
|
|
655
|
-
ide_settings_title: "
|
|
656
|
-
problem_detected_title: "
|
|
657
|
-
query_analyzer_title: "
|
|
657
|
+
ide_settings_title: "IDE Settings"
|
|
658
|
+
problem_detected_title: "Problem Detected"
|
|
659
|
+
query_analyzer_title: "Query Analyzer"
|
|
658
660
|
query_label: "Query:"
|
|
659
661
|
parameters_label: "Parameters:"
|
|
660
|
-
migration_title: "
|
|
662
|
+
migration_title: "Drop Index Migration"
|
|
661
663
|
migration_subtitle: "Generated migration to remove the index:"
|
|
662
664
|
migration_warning: "Creating a migration will generate a migration file in your project. Running this migration will drop the index from the database, which may significantly impact application performance."
|
|
663
665
|
migration_warning_dev_only: "This operation should only be performed in a local development environment."
|
|
664
|
-
query_execution_disabled_title: "
|
|
666
|
+
query_execution_disabled_title: "Query execution is disabled"
|
|
667
|
+
migration_disabled_title: "Migration creation is disabled"
|
|
665
668
|
query_execution_disabled_intro: "To enable this feature, add to your configuration:"
|
|
669
|
+
query_execution_disabled_env_note: "...or set the following environment variable instead:"
|
|
670
|
+
run_query_title: "SQL Console"
|
|
671
|
+
run_query_placeholder: "SELECT * FROM users LIMIT 10;"
|
|
666
672
|
settings:
|
|
667
673
|
default_ide_label: "Default IDE for source links:"
|
|
668
674
|
ide_show_menu: "Show menu (default)"
|
|
@@ -701,24 +707,27 @@ en:
|
|
|
701
707
|
waiting_for_locks: "waiting for locks"
|
|
702
708
|
percent_used_suffix: "% used"
|
|
703
709
|
categories:
|
|
704
|
-
requires_pg_stat: "
|
|
710
|
+
requires_pg_stat: "Requires pg_stat_statements"
|
|
711
|
+
pg_stat_missing_reason: "Requires pg_stat_statements, which is not installed on \"%{database}\". Create the extension there, or switch to a database that has it."
|
|
712
|
+
pg_stat_not_preloaded_reason: "Requires pg_stat_statements. The extension exists on \"%{database}\" but is not in shared_preload_libraries, so it returns no data until PostgreSQL is restarted with it preloaded."
|
|
705
713
|
reports_count_suffix: "reports"
|
|
706
|
-
primary_only_reason: "
|
|
714
|
+
primary_only_reason: "This category only runs on the primary database — it inspects the host application's models. Switch back to the default database to use it."
|
|
715
|
+
standalone_no_host_app_reason: "Not available in standalone mode — this category inspects the host application's models, and there is no host app when running the dashboard on its own."
|
|
707
716
|
documentation:
|
|
708
|
-
toggle_title: "
|
|
709
|
-
what_section: "
|
|
710
|
-
why_section: "
|
|
711
|
-
nuances_section: "
|
|
712
|
-
thresholds_section: "
|
|
713
|
-
threshold_warning_label: "
|
|
714
|
-
threshold_critical_label: "
|
|
717
|
+
toggle_title: "What does this report show?"
|
|
718
|
+
what_section: "What"
|
|
719
|
+
why_section: "Why It Matters"
|
|
720
|
+
nuances_section: "Nuances"
|
|
721
|
+
thresholds_section: "Thresholds"
|
|
722
|
+
threshold_warning_label: "Warning:"
|
|
723
|
+
threshold_critical_label: "Critical:"
|
|
715
724
|
threshold_inverted_note: "(lower is worse)"
|
|
716
725
|
filters:
|
|
717
|
-
title: "
|
|
726
|
+
title: "Filter Parameters"
|
|
718
727
|
current_value: "current"
|
|
719
728
|
saved:
|
|
720
|
-
title: "
|
|
721
|
-
saved_at_prefix: "
|
|
729
|
+
title: "Saved for Comparison"
|
|
730
|
+
saved_at_prefix: "Saved:"
|
|
722
731
|
click_to_expand: "Click to expand"
|
|
723
732
|
confirm_clear_all: "Remove all saved records for this report?"
|
|
724
733
|
remove_title: "Remove"
|
|
@@ -732,9 +741,9 @@ en:
|
|
|
732
741
|
execution_time_label: "Execution time:"
|
|
733
742
|
null_placeholder: "<null>"
|
|
734
743
|
sections:
|
|
735
|
-
recommendation: "
|
|
736
|
-
detected_issues: "
|
|
737
|
-
execution_plan: "
|
|
744
|
+
recommendation: "Recommendation"
|
|
745
|
+
detected_issues: "Detected Issues"
|
|
746
|
+
execution_plan: "Execution Plan"
|
|
738
747
|
line_label: "Line"
|
|
739
748
|
current_label: "Current:"
|
|
740
749
|
threshold_label: "Threshold:"
|
|
@@ -742,8 +751,8 @@ en:
|
|
|
742
751
|
warning_eq: "warning"
|
|
743
752
|
critical_eq: "critical"
|
|
744
753
|
levels:
|
|
745
|
-
critical: "
|
|
746
|
-
warning: "
|
|
754
|
+
critical: "Critical"
|
|
755
|
+
warning: "Warning"
|
|
747
756
|
errors:
|
|
748
757
|
error_prefix: "Error:"
|
|
749
758
|
unable_fetch_metrics: "Unable to fetch database statistics."
|
|
@@ -768,13 +777,19 @@ en:
|
|
|
768
777
|
decode_query_failed: "Failed to decode query"
|
|
769
778
|
explain_analyze_failed: "Failed to run EXPLAIN ANALYZE"
|
|
770
779
|
execute_query_failed: "Failed to execute query"
|
|
780
|
+
run_query_failed: "Failed to run query"
|
|
781
|
+
query_required: "Please enter a query"
|
|
771
782
|
create_migration_failed: "Failed to create migration"
|
|
772
|
-
explain_disabled_toast: "
|
|
773
|
-
execute_disabled_toast: "
|
|
783
|
+
explain_disabled_toast: "EXPLAIN ANALYZE is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
784
|
+
execute_disabled_toast: "Query execution is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
785
|
+
migration_disabled_toast: "Migration creation is disabled. Enable in configuration: config.allow_raw_query_execution = true"
|
|
774
786
|
query_monitoring_error: "Query monitoring error"
|
|
775
787
|
query_hash_required: "Query hash is required"
|
|
776
788
|
query_execution_disabled: "Query execution from dashboard is disabled. Enable it in configuration with 'config.allow_raw_query_execution = true'"
|
|
777
789
|
query_not_found_expired: "Query not found or expired. Please refresh the page."
|
|
790
|
+
query_monitor_unavailable_standalone: "SQL Query Monitor isn't available in standalone mode — there is no host application process to observe."
|
|
791
|
+
query_timed_out: "Query timed out after %{ms}ms (statement_timeout). Try narrowing the query or increase config.raw_query_statement_timeout_ms."
|
|
792
|
+
rate_limit_exceeded: "Too many requests. Please wait a bit before trying again."
|
|
778
793
|
security_violation_prefix: "Security violation:"
|
|
779
794
|
trigger_variables_not_allowed: "Cannot EXPLAIN ANALYZE queries with trigger variables (NEW, OLD). These are only available within trigger functions."
|
|
780
795
|
missing_parameter_values: "Please provide values for all parameter placeholders ($1, $2, etc.)"
|
data/config/locales/ru.yml
CHANGED
|
@@ -556,7 +556,7 @@ ru:
|
|
|
556
556
|
page_title: "PgReports — Дашборд"
|
|
557
557
|
navigation:
|
|
558
558
|
dashboard: "Дашборд"
|
|
559
|
-
back: "
|
|
559
|
+
back: "Назад"
|
|
560
560
|
database_selector:
|
|
561
561
|
label: "База данных"
|
|
562
562
|
target_selector:
|
|
@@ -564,39 +564,41 @@ ru:
|
|
|
564
564
|
actions:
|
|
565
565
|
cancel: "Отмена"
|
|
566
566
|
retry: "Повторить"
|
|
567
|
-
copy: "
|
|
568
|
-
copy_query: "
|
|
569
|
-
copy_code: "
|
|
567
|
+
copy: "Копировать"
|
|
568
|
+
copy_query: "Копировать запрос"
|
|
569
|
+
copy_code: "Копировать код"
|
|
570
570
|
copy_to_clipboard_title: "Скопировать в буфер обмена"
|
|
571
|
-
copied_feedback: "
|
|
571
|
+
copied_feedback: "Скопировано!"
|
|
572
572
|
clear_all: "Очистить всё"
|
|
573
|
-
run_report: "
|
|
574
|
-
export: "
|
|
575
|
-
download_text: "
|
|
576
|
-
download_csv: "
|
|
577
|
-
download_json: "
|
|
578
|
-
download: "
|
|
573
|
+
run_report: "Запустить отчёт"
|
|
574
|
+
export: "Экспорт"
|
|
575
|
+
download_text: "Текст (.txt)"
|
|
576
|
+
download_csv: "CSV (.csv)"
|
|
577
|
+
download_json: "JSON (.json)"
|
|
578
|
+
download: "Скачать"
|
|
579
579
|
copy_ai_prompt: "Копировать промпт"
|
|
580
|
-
send_telegram: "
|
|
580
|
+
send_telegram: "Telegram"
|
|
581
581
|
sending: "Отправка..."
|
|
582
|
-
reset_statistics: "
|
|
582
|
+
reset_statistics: "Сбросить статистику"
|
|
583
583
|
resetting: "Сброс..."
|
|
584
584
|
confirm_reset: "Да, сбросить"
|
|
585
|
-
create_extension: "
|
|
585
|
+
create_extension: "Создать расширение"
|
|
586
586
|
creating: "Создание..."
|
|
587
587
|
ide_settings_button_title: "Настройки IDE"
|
|
588
|
-
explain_analyze: "
|
|
589
|
-
execute_query: "
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
588
|
+
explain_analyze: "EXPLAIN ANALYZE"
|
|
589
|
+
execute_query: "Выполнить запрос"
|
|
590
|
+
run_query: "SQL-консоль"
|
|
591
|
+
create_migration_file: "Создать файл и открыть в IDE"
|
|
592
|
+
start_monitoring: "Запустить мониторинг"
|
|
593
|
+
stop_monitoring: "Остановить мониторинг"
|
|
593
594
|
starting: "Запуск..."
|
|
594
595
|
stopping: "Остановка..."
|
|
595
|
-
load_history: "
|
|
596
|
+
load_history: "Загрузить историю"
|
|
597
|
+
load_history_unavailable_title: "Сохранённой истории нет — задайте config.query_monitor_log_file, чтобы запросы сохранялись между перезапусками"
|
|
596
598
|
loading: "Загрузка..."
|
|
597
599
|
running: "Выполнение..."
|
|
598
|
-
save_for_comparison: "
|
|
599
|
-
saved_marker: "
|
|
600
|
+
save_for_comparison: "Сохранить для сравнения"
|
|
601
|
+
saved_marker: "Сохранено"
|
|
600
602
|
status:
|
|
601
603
|
pg_stat_ready: "Активно"
|
|
602
604
|
not_preloaded: "Требуется предзагрузка"
|
|
@@ -614,20 +616,24 @@ ru:
|
|
|
614
616
|
create_extension_title: "Создание расширения pg_stat_statements"
|
|
615
617
|
create_extension_intro: "Расширение pg_stat_statements ещё не создано в этой базе данных. Нажмите, чтобы создать его:"
|
|
616
618
|
create_extension_note: "Если библиотека не предзагружена, её также нужно добавить в shared_preload_libraries и перезапустить PostgreSQL."
|
|
617
|
-
reset_stats_title: "
|
|
619
|
+
reset_stats_title: "Сброс статистики"
|
|
618
620
|
reset_stats_confirm: "Вы уверены, что хотите сбросить статистику pg_stat_statements?"
|
|
619
621
|
reset_stats_warning: "Это действие очистит всю собранную статистику запросов и не может быть отменено."
|
|
620
|
-
ide_settings_title: "
|
|
621
|
-
problem_detected_title: "
|
|
622
|
-
query_analyzer_title: "
|
|
622
|
+
ide_settings_title: "Настройки IDE"
|
|
623
|
+
problem_detected_title: "Обнаружена проблема"
|
|
624
|
+
query_analyzer_title: "Анализатор запроса"
|
|
623
625
|
query_label: "Запрос:"
|
|
624
626
|
parameters_label: "Параметры:"
|
|
625
|
-
migration_title: "
|
|
627
|
+
migration_title: "Миграция удаления индекса"
|
|
626
628
|
migration_subtitle: "Сгенерированная миграция для удаления индекса:"
|
|
627
629
|
migration_warning: "Создание миграции сгенерирует файл миграции в вашем проекте. Запуск этой миграции удалит индекс из БД, что может значительно повлиять на производительность приложения."
|
|
628
630
|
migration_warning_dev_only: "Эту операцию следует выполнять только в локальном dev-окружении."
|
|
629
|
-
query_execution_disabled_title: "
|
|
631
|
+
query_execution_disabled_title: "Выполнение запросов отключено"
|
|
632
|
+
migration_disabled_title: "Создание миграций отключено"
|
|
630
633
|
query_execution_disabled_intro: "Чтобы включить эту функцию, добавьте в конфигурацию:"
|
|
634
|
+
query_execution_disabled_env_note: "...или вместо этого установите переменную окружения:"
|
|
635
|
+
run_query_title: "SQL-консоль"
|
|
636
|
+
run_query_placeholder: "SELECT * FROM users LIMIT 10;"
|
|
631
637
|
settings:
|
|
632
638
|
default_ide_label: "IDE по умолчанию для ссылок на исходники:"
|
|
633
639
|
ide_show_menu: "Показывать меню (по умолчанию)"
|
|
@@ -666,24 +672,27 @@ ru:
|
|
|
666
672
|
waiting_for_locks: "ждут блокировок"
|
|
667
673
|
percent_used_suffix: "% использовано"
|
|
668
674
|
categories:
|
|
669
|
-
requires_pg_stat: "
|
|
675
|
+
requires_pg_stat: "Требуется pg_stat_statements"
|
|
676
|
+
pg_stat_missing_reason: "Требуется pg_stat_statements, но расширение не установлено в базе «%{database}». Создайте его там или переключитесь на базу, где оно есть."
|
|
677
|
+
pg_stat_not_preloaded_reason: "Требуется pg_stat_statements. Расширение есть в базе «%{database}», но отсутствует в shared_preload_libraries — данных не будет, пока PostgreSQL не перезапустят с ним."
|
|
670
678
|
reports_count_suffix: "отчётов"
|
|
671
|
-
primary_only_reason: "
|
|
679
|
+
primary_only_reason: "Эта категория работает только на первичной базе — она проверяет модели хост-приложения. Чтобы пользоваться, переключитесь обратно на базу по умолчанию."
|
|
680
|
+
standalone_no_host_app_reason: "Недоступно в автономном режиме — эта категория проверяет модели хост-приложения, а при самостоятельном запуске дашборда хост-приложения нет."
|
|
672
681
|
documentation:
|
|
673
|
-
toggle_title: "
|
|
674
|
-
what_section: "
|
|
675
|
-
why_section: "
|
|
676
|
-
nuances_section: "
|
|
677
|
-
thresholds_section: "
|
|
678
|
-
threshold_warning_label: "
|
|
679
|
-
threshold_critical_label: "
|
|
682
|
+
toggle_title: "Что показывает этот отчёт?"
|
|
683
|
+
what_section: "Что"
|
|
684
|
+
why_section: "Почему это важно"
|
|
685
|
+
nuances_section: "Нюансы"
|
|
686
|
+
thresholds_section: "Пороги"
|
|
687
|
+
threshold_warning_label: "Warning:"
|
|
688
|
+
threshold_critical_label: "Critical:"
|
|
680
689
|
threshold_inverted_note: "(меньше — хуже)"
|
|
681
690
|
filters:
|
|
682
|
-
title: "
|
|
691
|
+
title: "Параметры фильтрации"
|
|
683
692
|
current_value: "сейчас"
|
|
684
693
|
saved:
|
|
685
|
-
title: "
|
|
686
|
-
saved_at_prefix: "
|
|
694
|
+
title: "Сохранено для сравнения"
|
|
695
|
+
saved_at_prefix: "Сохранено:"
|
|
687
696
|
click_to_expand: "Нажмите, чтобы развернуть"
|
|
688
697
|
confirm_clear_all: "Удалить все сохранённые записи для этого отчёта?"
|
|
689
698
|
remove_title: "Удалить"
|
|
@@ -697,9 +706,9 @@ ru:
|
|
|
697
706
|
execution_time_label: "Время выполнения:"
|
|
698
707
|
null_placeholder: "<null>"
|
|
699
708
|
sections:
|
|
700
|
-
recommendation: "
|
|
701
|
-
detected_issues: "
|
|
702
|
-
execution_plan: "
|
|
709
|
+
recommendation: "Рекомендация"
|
|
710
|
+
detected_issues: "Обнаруженные проблемы"
|
|
711
|
+
execution_plan: "План выполнения"
|
|
703
712
|
line_label: "Строка"
|
|
704
713
|
current_label: "Текущее:"
|
|
705
714
|
threshold_label: "Порог:"
|
|
@@ -707,8 +716,8 @@ ru:
|
|
|
707
716
|
warning_eq: "warning"
|
|
708
717
|
critical_eq: "critical"
|
|
709
718
|
levels:
|
|
710
|
-
critical: "
|
|
711
|
-
warning: "
|
|
719
|
+
critical: "Критично"
|
|
720
|
+
warning: "Внимание"
|
|
712
721
|
errors:
|
|
713
722
|
error_prefix: "Ошибка:"
|
|
714
723
|
unable_fetch_metrics: "Не удалось получить статистику БД."
|
|
@@ -733,13 +742,19 @@ ru:
|
|
|
733
742
|
decode_query_failed: "Не удалось декодировать запрос"
|
|
734
743
|
explain_analyze_failed: "Не удалось выполнить EXPLAIN ANALYZE"
|
|
735
744
|
execute_query_failed: "Не удалось выполнить запрос"
|
|
745
|
+
run_query_failed: "Не удалось выполнить запрос"
|
|
746
|
+
query_required: "Введите запрос"
|
|
736
747
|
create_migration_failed: "Не удалось создать миграцию"
|
|
737
|
-
explain_disabled_toast: "
|
|
738
|
-
execute_disabled_toast: "
|
|
748
|
+
explain_disabled_toast: "EXPLAIN ANALYZE отключен. Включите в конфигурации: config.allow_raw_query_execution = true"
|
|
749
|
+
execute_disabled_toast: "Выполнение запросов отключено. Включите в конфигурации: config.allow_raw_query_execution = true"
|
|
750
|
+
migration_disabled_toast: "Создание миграций отключено. Включите в конфигурации: config.allow_raw_query_execution = true"
|
|
739
751
|
query_monitoring_error: "Ошибка мониторинга запросов"
|
|
740
752
|
query_hash_required: "Требуется хэш запроса"
|
|
741
753
|
query_execution_disabled: "Выполнение запросов из дашборда отключено. Включите в конфигурации: 'config.allow_raw_query_execution = true'"
|
|
742
754
|
query_not_found_expired: "Запрос не найден или истёк срок действия. Обновите страницу."
|
|
755
|
+
query_monitor_unavailable_standalone: "SQL Query Monitor недоступен в автономном режиме — нет процесса основного приложения для наблюдения."
|
|
756
|
+
query_timed_out: "Превышено время ожидания запроса (%{ms} мс, statement_timeout). Сузьте запрос или увеличьте config.raw_query_statement_timeout_ms."
|
|
757
|
+
rate_limit_exceeded: "Слишком много запросов. Подождите немного и попробуйте снова."
|
|
743
758
|
security_violation_prefix: "Нарушение безопасности:"
|
|
744
759
|
trigger_variables_not_allowed: "Нельзя выполнить EXPLAIN ANALYZE для запросов с триггерными переменными (NEW, OLD). Они доступны только в контексте триггерных функций."
|
|
745
760
|
missing_parameter_values: "Укажите значения для всех плейсхолдеров параметров ($1, $2 и т.д.)"
|