pg_reports 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +123 -370
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +135 -69
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +105 -55
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +49 -11
- data/app/views/pg_reports/dashboard/index.html.erb +123 -114
- data/app/views/pg_reports/dashboard/show.html.erb +30 -26
- data/config/locales/en.yml +597 -0
- data/config/locales/ru.yml +562 -0
- data/config/locales/uk.yml +607 -0
- data/lib/pg_reports/compatibility.rb +63 -0
- data/lib/pg_reports/configuration.rb +2 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +112 -5
- data/lib/pg_reports/definitions/indexes/fk_without_indexes.yml +30 -0
- data/lib/pg_reports/definitions/indexes/index_correlation.yml +31 -0
- data/lib/pg_reports/definitions/indexes/inefficient_indexes.yml +45 -0
- data/lib/pg_reports/definitions/queries/temp_file_queries.yml +39 -0
- data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
- data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
- data/lib/pg_reports/definitions/system/wraparound_risk.yml +31 -0
- data/lib/pg_reports/definitions/tables/tables_without_pk.yml +28 -0
- data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/engine.rb +6 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/indexes.rb +3 -0
- data/lib/pg_reports/modules/queries.rb +1 -0
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +27 -0
- data/lib/pg_reports/modules/tables.rb +1 -0
- data/lib/pg_reports/query_monitor.rb +64 -36
- data/lib/pg_reports/report_definition.rb +20 -24
- data/lib/pg_reports/sql/indexes/fk_without_indexes.sql +23 -0
- data/lib/pg_reports/sql/indexes/index_correlation.sql +27 -0
- data/lib/pg_reports/sql/indexes/inefficient_indexes.sql +22 -0
- data/lib/pg_reports/sql/queries/temp_file_queries.sql +16 -0
- data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
- data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
- data/lib/pg_reports/sql/system/checkpoint_stats.sql +20 -0
- data/lib/pg_reports/sql/system/checkpoint_stats_legacy.sql +19 -0
- data/lib/pg_reports/sql/system/wraparound_risk.sql +21 -0
- data/lib/pg_reports/sql/tables/tables_without_pk.sql +20 -0
- data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
- data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
- data/lib/pg_reports/version.rb +1 -1
- data/lib/pg_reports.rb +5 -0
- metadata +24 -1
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
function copyToClipboard(text, btn) {
|
|
234
234
|
navigator.clipboard.writeText(text).then(() => {
|
|
235
235
|
const originalText = btn.textContent;
|
|
236
|
-
btn.textContent =
|
|
236
|
+
btn.textContent = PG_REPORTS_I18N.actions.copied_feedback;
|
|
237
237
|
btn.style.background = 'var(--accent-green)';
|
|
238
238
|
btn.style.borderColor = 'var(--accent-green)';
|
|
239
239
|
btn.style.color = 'white';
|
|
@@ -244,7 +244,59 @@
|
|
|
244
244
|
btn.style.color = '';
|
|
245
245
|
}, 1500);
|
|
246
246
|
}).catch(() => {
|
|
247
|
-
showToast(
|
|
247
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// AI Prompt builder — assembles a prompt from documentation + report data
|
|
252
|
+
const aiPromptInstruction = <%== @documentation[:ai_prompt].to_json %>;
|
|
253
|
+
const aiPromptWhat = <%== @documentation[:what].to_json %>;
|
|
254
|
+
const aiPromptHow = <%== @documentation[:how].to_json %>;
|
|
255
|
+
const aiPromptNuances = <%== (@documentation[:nuances] || []).to_json %>;
|
|
256
|
+
|
|
257
|
+
function buildAiPrompt() {
|
|
258
|
+
if (!currentReportData || !currentReportData.data) return null;
|
|
259
|
+
|
|
260
|
+
const rows = currentReportData.data.slice(0, 15);
|
|
261
|
+
const cols = currentReportData.columns || [];
|
|
262
|
+
|
|
263
|
+
// Build markdown table from report data
|
|
264
|
+
let table = '| ' + cols.join(' | ') + ' |\n';
|
|
265
|
+
table += '| ' + cols.map(() => '---').join(' | ') + ' |\n';
|
|
266
|
+
rows.forEach(row => {
|
|
267
|
+
const values = cols.map(c => {
|
|
268
|
+
const v = row[c];
|
|
269
|
+
return v == null ? '' : String(v).replace(/\|/g, '\\|').substring(0, 120);
|
|
270
|
+
});
|
|
271
|
+
table += '| ' + values.join(' | ') + ' |\n';
|
|
272
|
+
});
|
|
273
|
+
if (currentReportData.data.length > 15) {
|
|
274
|
+
table += `\n(${currentReportData.data.length - 15} more rows omitted)\n`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Assemble the prompt
|
|
278
|
+
let prompt = `## Problem\n\n${aiPromptWhat}\n\n${aiPromptHow}\n`;
|
|
279
|
+
prompt += `\n## What needs to be done\n\n${aiPromptInstruction}\n`;
|
|
280
|
+
prompt += `\n## Detected issues\n\n${table}`;
|
|
281
|
+
if (aiPromptNuances.length > 0) {
|
|
282
|
+
prompt += `\n## Important context\n\n`;
|
|
283
|
+
aiPromptNuances.forEach(n => { prompt += `- ${n}\n`; });
|
|
284
|
+
}
|
|
285
|
+
return prompt;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function copyAiPrompt(el) {
|
|
289
|
+
const prompt = buildAiPrompt();
|
|
290
|
+
if (!prompt) {
|
|
291
|
+
showToast(PG_REPORTS_I18N.errors.run_report_first, 'error');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
document.getElementById('dropdown-menu').classList.remove('show');
|
|
296
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
297
|
+
showToast(PG_REPORTS_I18N.success.ai_prompt_copied);
|
|
298
|
+
}).catch(() => {
|
|
299
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
248
300
|
});
|
|
249
301
|
}
|
|
250
302
|
|
|
@@ -299,15 +351,15 @@
|
|
|
299
351
|
|
|
300
352
|
for (const problem of problems) {
|
|
301
353
|
const levelClass = problem.level === 'critical' ? 'critical' : 'warning';
|
|
302
|
-
const levelText = problem.level === 'critical' ?
|
|
354
|
+
const levelText = problem.level === 'critical' ? PG_REPORTS_I18N.levels.critical : PG_REPORTS_I18N.levels.warning;
|
|
303
355
|
|
|
304
356
|
html += `
|
|
305
357
|
<div class="problem-field">
|
|
306
358
|
<span class="problem-field-label">${escapeHtml(problem.field)} (${levelText})</span>
|
|
307
359
|
<div class="problem-field-value ${levelClass}">
|
|
308
|
-
|
|
309
|
-
<br
|
|
310
|
-
${problem.threshold.inverted ? '<br><em>
|
|
360
|
+
${PG_REPORTS_I18N.sections.current_label} ${escapeHtml(String(problem.value))}
|
|
361
|
+
<br>${PG_REPORTS_I18N.sections.threshold_label} ${PG_REPORTS_I18N.sections.warning_eq}=${problem.threshold.warning}, ${PG_REPORTS_I18N.sections.critical_eq}=${problem.threshold.critical}
|
|
362
|
+
${problem.threshold.inverted ? '<br><em>' + PG_REPORTS_I18N.sections.threshold_inverted_long + '</em>' : ''}
|
|
311
363
|
</div>
|
|
312
364
|
</div>
|
|
313
365
|
`;
|
|
@@ -320,7 +372,7 @@
|
|
|
320
372
|
if (explanation) {
|
|
321
373
|
html += `
|
|
322
374
|
<div class="problem-explanation">
|
|
323
|
-
<h4
|
|
375
|
+
<h4>${PG_REPORTS_I18N.sections.recommendation}</h4>
|
|
324
376
|
<p>${escapeHtml(explanation)}</p>
|
|
325
377
|
</div>
|
|
326
378
|
`;
|
|
@@ -580,7 +632,7 @@
|
|
|
580
632
|
<div class="row-detail-item${isLongText ? ' full-width' : ''}">
|
|
581
633
|
<span class="row-detail-label">${escapeHtml(col)}</span>
|
|
582
634
|
<div class="row-detail-value ${valueClass}">${escapeHtml(strValue)}</div>
|
|
583
|
-
${isQuery ? `<button class="copy-btn" data-query="${escapeHtmlAttr(strValue)}" onclick="event.stopPropagation(); copyQueryFromButton(this)"
|
|
635
|
+
${isQuery ? `<button class="copy-btn" data-query="${escapeHtmlAttr(strValue)}" onclick="event.stopPropagation(); copyQueryFromButton(this)">${PG_REPORTS_I18N.actions.copy_query}</button>` : ''}
|
|
584
636
|
</div>
|
|
585
637
|
`;
|
|
586
638
|
});
|
|
@@ -632,7 +684,7 @@
|
|
|
632
684
|
|
|
633
685
|
if (button) {
|
|
634
686
|
button.disabled = true;
|
|
635
|
-
button.innerHTML = '<span class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block;vertical-align:middle;"></span>
|
|
687
|
+
button.innerHTML = '<span class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block;vertical-align:middle;"></span> ' + PG_REPORTS_I18N.actions.running;
|
|
636
688
|
}
|
|
637
689
|
|
|
638
690
|
if (loadingEl) loadingEl.style.display = 'flex';
|
|
@@ -688,7 +740,7 @@
|
|
|
688
740
|
`;
|
|
689
741
|
}
|
|
690
742
|
|
|
691
|
-
// Show
|
|
743
|
+
// Show export and telegram buttons
|
|
692
744
|
if (downloadDropdown) downloadDropdown.style.display = 'inline-block';
|
|
693
745
|
if (telegramBtn) telegramBtn.style.display = 'inline-flex';
|
|
694
746
|
|
|
@@ -709,18 +761,18 @@
|
|
|
709
761
|
renderTableBody(data.data, data.columns, thresholds, problemFields);
|
|
710
762
|
}
|
|
711
763
|
|
|
712
|
-
showToast(
|
|
764
|
+
showToast(PG_REPORTS_I18N.success.report_generated);
|
|
713
765
|
} else {
|
|
714
|
-
showToast(data.error ||
|
|
766
|
+
showToast(data.error || PG_REPORTS_I18N.errors.run_report_failed, 'error');
|
|
715
767
|
}
|
|
716
768
|
} catch (error) {
|
|
717
769
|
if (loadingEl) loadingEl.style.display = 'none';
|
|
718
|
-
showToast(
|
|
770
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
719
771
|
}
|
|
720
772
|
|
|
721
773
|
if (button) {
|
|
722
774
|
button.disabled = false;
|
|
723
|
-
button.innerHTML =
|
|
775
|
+
button.innerHTML = PG_REPORTS_I18N.actions.run_report;
|
|
724
776
|
}
|
|
725
777
|
}
|
|
726
778
|
|
|
@@ -833,8 +885,8 @@
|
|
|
833
885
|
// Remove
|
|
834
886
|
saved.splice(existingIdx, 1);
|
|
835
887
|
btn.classList.remove('saved');
|
|
836
|
-
btn.textContent =
|
|
837
|
-
showToast(
|
|
888
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
889
|
+
showToast(PG_REPORTS_I18N.success.record_removed_saved);
|
|
838
890
|
} else {
|
|
839
891
|
// Add
|
|
840
892
|
saved.unshift({
|
|
@@ -843,8 +895,8 @@
|
|
|
843
895
|
data: row
|
|
844
896
|
});
|
|
845
897
|
btn.classList.add('saved');
|
|
846
|
-
btn.textContent =
|
|
847
|
-
showToast(
|
|
898
|
+
btn.textContent = PG_REPORTS_I18N.actions.saved_marker;
|
|
899
|
+
showToast(PG_REPORTS_I18N.success.record_saved);
|
|
848
900
|
}
|
|
849
901
|
|
|
850
902
|
saveSavedRecords(saved);
|
|
@@ -861,24 +913,24 @@
|
|
|
861
913
|
const btn = document.querySelector(`.btn-save[onclick*="'${rowId}'"]`);
|
|
862
914
|
if (btn) {
|
|
863
915
|
btn.classList.remove('saved');
|
|
864
|
-
btn.textContent =
|
|
916
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
865
917
|
}
|
|
866
918
|
|
|
867
|
-
showToast(
|
|
919
|
+
showToast(PG_REPORTS_I18N.success.record_removed);
|
|
868
920
|
}
|
|
869
921
|
|
|
870
922
|
function clearAllSavedRecords() {
|
|
871
|
-
if (!confirm(
|
|
923
|
+
if (!confirm(PG_REPORTS_I18N.saved.confirm_clear_all)) return;
|
|
872
924
|
saveSavedRecords([]);
|
|
873
925
|
renderSavedRecords();
|
|
874
926
|
|
|
875
927
|
// Update all buttons in table
|
|
876
928
|
document.querySelectorAll('.btn-save.saved').forEach(btn => {
|
|
877
929
|
btn.classList.remove('saved');
|
|
878
|
-
btn.textContent =
|
|
930
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
879
931
|
});
|
|
880
932
|
|
|
881
|
-
showToast(
|
|
933
|
+
showToast(PG_REPORTS_I18N.success.all_saved_cleared);
|
|
882
934
|
}
|
|
883
935
|
|
|
884
936
|
function renderSavedRecords() {
|
|
@@ -907,9 +959,9 @@
|
|
|
907
959
|
html += `
|
|
908
960
|
<div class="saved-record-card" id="saved-card-${idx}" onclick="toggleSavedRecordDetail(${idx}, event)">
|
|
909
961
|
<div class="saved-record-header">
|
|
910
|
-
<span class="saved-record-time"
|
|
911
|
-
<span class="saved-record-expand-hint"
|
|
912
|
-
<button class="saved-record-remove" onclick="event.stopPropagation(); removeSavedRecord('${record.id}')" title="
|
|
962
|
+
<span class="saved-record-time">${PG_REPORTS_I18N.saved.saved_at_prefix} ${savedTime}</span>
|
|
963
|
+
<span class="saved-record-expand-hint">${PG_REPORTS_I18N.saved.click_to_expand}</span>
|
|
964
|
+
<button class="saved-record-remove" onclick="event.stopPropagation(); removeSavedRecord('${record.id}')" title="${PG_REPORTS_I18N.saved.remove_title}">×</button>
|
|
913
965
|
</div>
|
|
914
966
|
<div class="saved-record-data">
|
|
915
967
|
`;
|
|
@@ -1034,7 +1086,7 @@
|
|
|
1034
1086
|
const query = decodeURIComponent(escape(atob(queryBase64)));
|
|
1035
1087
|
runExplainAnalyze(query, queryHash);
|
|
1036
1088
|
} catch (e) {
|
|
1037
|
-
showToast(
|
|
1089
|
+
showToast(PG_REPORTS_I18N.errors.decode_query_failed, 'error');
|
|
1038
1090
|
}
|
|
1039
1091
|
}
|
|
1040
1092
|
|
|
@@ -1155,7 +1207,7 @@
|
|
|
1155
1207
|
// Show problems list
|
|
1156
1208
|
if (data.problems && data.problems.length > 0) {
|
|
1157
1209
|
html += '<div class="explain-problems">';
|
|
1158
|
-
html += '<div class="explain-problems-header"
|
|
1210
|
+
html += '<div class="explain-problems-header">' + PG_REPORTS_I18N.sections.detected_issues + '</div>';
|
|
1159
1211
|
html += '<div class="explain-problems-list">';
|
|
1160
1212
|
|
|
1161
1213
|
data.problems.forEach(problem => {
|
|
@@ -1167,7 +1219,7 @@
|
|
|
1167
1219
|
html += '<span class="explain-problem-icon">' + severityIcon + '</span>';
|
|
1168
1220
|
html += '<span class="explain-problem-message">' + escapeHtml(problem.message) + '</span>';
|
|
1169
1221
|
if (problem.line_number) {
|
|
1170
|
-
html += '<span class="explain-problem-line">
|
|
1222
|
+
html += '<span class="explain-problem-line">' + PG_REPORTS_I18N.sections.line_label + ' ' + problem.line_number + '</span>';
|
|
1171
1223
|
}
|
|
1172
1224
|
html += '</div>';
|
|
1173
1225
|
if (problem.details) {
|
|
@@ -1187,8 +1239,8 @@
|
|
|
1187
1239
|
if (data.annotated_lines && data.annotated_lines.length > 0) {
|
|
1188
1240
|
html += '<div class="explain-output">';
|
|
1189
1241
|
html += '<div class="explain-output-header">';
|
|
1190
|
-
html +=
|
|
1191
|
-
html += '<button class="btn-copy-small" onclick="copyExplainOutput()" title="
|
|
1242
|
+
html += PG_REPORTS_I18N.sections.execution_plan;
|
|
1243
|
+
html += '<button class="btn-copy-small" onclick="copyExplainOutput()" title="' + PG_REPORTS_I18N.actions.copy_to_clipboard_title + '">' + PG_REPORTS_I18N.actions.copy + '</button>';
|
|
1192
1244
|
html += '</div>';
|
|
1193
1245
|
html += '<div class="explain-lines">';
|
|
1194
1246
|
|
|
@@ -1272,9 +1324,9 @@
|
|
|
1272
1324
|
const text = Array.from(lines).map(line => line.textContent).join('\n');
|
|
1273
1325
|
|
|
1274
1326
|
navigator.clipboard.writeText(text).then(() => {
|
|
1275
|
-
showToast(
|
|
1327
|
+
showToast(PG_REPORTS_I18N.success.explain_copied);
|
|
1276
1328
|
}).catch(() => {
|
|
1277
|
-
showToast(
|
|
1329
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
1278
1330
|
});
|
|
1279
1331
|
}
|
|
1280
1332
|
|
|
@@ -1285,11 +1337,10 @@
|
|
|
1285
1337
|
|
|
1286
1338
|
// Security check
|
|
1287
1339
|
if (!allowRawQueryExecution) {
|
|
1288
|
-
|
|
1289
|
-
showToast(message, 'error');
|
|
1340
|
+
showToast(PG_REPORTS_I18N.errors.explain_disabled_toast, 'error');
|
|
1290
1341
|
content.innerHTML = `<div class="error-message">
|
|
1291
|
-
<strong
|
|
1292
|
-
|
|
1342
|
+
<strong>${PG_REPORTS_I18N.modals.query_execution_disabled_title}</strong><br><br>
|
|
1343
|
+
${PG_REPORTS_I18N.modals.query_execution_disabled_intro}<br>
|
|
1293
1344
|
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
1294
1345
|
PgReports.configure do |config|<br>
|
|
1295
1346
|
config.allow_raw_query_execution = true<br>
|
|
@@ -1320,11 +1371,11 @@ end
|
|
|
1320
1371
|
if (data.success) {
|
|
1321
1372
|
content.innerHTML = renderExplainAnalysis(data);
|
|
1322
1373
|
} else {
|
|
1323
|
-
content.innerHTML = `<div class="error-message">${escapeHtml(data.error ||
|
|
1374
|
+
content.innerHTML = `<div class="error-message">${escapeHtml(data.error || PG_REPORTS_I18N.errors.explain_analyze_failed)}</div>`;
|
|
1324
1375
|
}
|
|
1325
1376
|
} catch (error) {
|
|
1326
1377
|
loading.style.display = 'none';
|
|
1327
|
-
content.innerHTML = `<div class="error-message"
|
|
1378
|
+
content.innerHTML = `<div class="error-message">${PG_REPORTS_I18N.errors.network_error_prefix} ${escapeHtml(error.message)}</div>`;
|
|
1328
1379
|
}
|
|
1329
1380
|
}
|
|
1330
1381
|
|
|
@@ -1335,11 +1386,10 @@ end
|
|
|
1335
1386
|
|
|
1336
1387
|
// Security check
|
|
1337
1388
|
if (!allowRawQueryExecution) {
|
|
1338
|
-
|
|
1339
|
-
showToast(message, 'error');
|
|
1389
|
+
showToast(PG_REPORTS_I18N.errors.execute_disabled_toast, 'error');
|
|
1340
1390
|
content.innerHTML = `<div class="error-message">
|
|
1341
|
-
<strong
|
|
1342
|
-
|
|
1391
|
+
<strong>${PG_REPORTS_I18N.modals.query_execution_disabled_title}</strong><br><br>
|
|
1392
|
+
${PG_REPORTS_I18N.modals.query_execution_disabled_intro}<br>
|
|
1343
1393
|
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
1344
1394
|
PgReports.configure do |config|<br>
|
|
1345
1395
|
config.allow_raw_query_execution = true<br>
|
|
@@ -1372,8 +1422,8 @@ end
|
|
|
1372
1422
|
|
|
1373
1423
|
// Show info
|
|
1374
1424
|
html += `<div class="query-results-info">
|
|
1375
|
-
<span
|
|
1376
|
-
<span
|
|
1425
|
+
<span>${PG_REPORTS_I18N.results.rows_label} <span class="count">${data.count}</span></span>
|
|
1426
|
+
<span>${PG_REPORTS_I18N.results.execution_time_label} <span class="time">${data.execution_time} ms</span></span>
|
|
1377
1427
|
</div>`;
|
|
1378
1428
|
|
|
1379
1429
|
if (data.rows && data.rows.length > 0) {
|
|
@@ -1393,7 +1443,7 @@ end
|
|
|
1393
1443
|
html += '<tr>';
|
|
1394
1444
|
data.columns.forEach(col => {
|
|
1395
1445
|
const value = row[col];
|
|
1396
|
-
const displayValue = value === null ?
|
|
1446
|
+
const displayValue = value === null ? PG_REPORTS_I18N.results.null_placeholder : String(value);
|
|
1397
1447
|
html += `<td title="${escapeHtmlAttr(displayValue)}">${escapeHtml(displayValue)}</td>`;
|
|
1398
1448
|
});
|
|
1399
1449
|
html += '</tr>';
|
|
@@ -1404,19 +1454,19 @@ end
|
|
|
1404
1454
|
html += '</div>';
|
|
1405
1455
|
|
|
1406
1456
|
if (data.truncated) {
|
|
1407
|
-
html += `<p style="margin-top: 0.5rem; color: var(--text-muted); font-size: 0.75rem;"
|
|
1457
|
+
html += `<p style="margin-top: 0.5rem; color: var(--text-muted); font-size: 0.75rem;">${pgReportsFormat(PG_REPORTS_I18N.results.showing_first_of_total, { count: data.rows.length, total: data.total_count })}</p>`;
|
|
1408
1458
|
}
|
|
1409
1459
|
} else {
|
|
1410
|
-
html += '<p style="margin-top: 1rem; color: var(--text-muted);">
|
|
1460
|
+
html += '<p style="margin-top: 1rem; color: var(--text-muted);">' + PG_REPORTS_I18N.results.no_rows_returned + '</p>';
|
|
1411
1461
|
}
|
|
1412
1462
|
|
|
1413
1463
|
content.innerHTML = html;
|
|
1414
1464
|
} else {
|
|
1415
|
-
content.innerHTML = `<div class="error-message">${escapeHtml(data.error ||
|
|
1465
|
+
content.innerHTML = `<div class="error-message">${escapeHtml(data.error || PG_REPORTS_I18N.errors.execute_query_failed)}</div>`;
|
|
1416
1466
|
}
|
|
1417
1467
|
} catch (error) {
|
|
1418
1468
|
loading.style.display = 'none';
|
|
1419
|
-
content.innerHTML = `<div class="error-message"
|
|
1469
|
+
content.innerHTML = `<div class="error-message">${PG_REPORTS_I18N.errors.network_error_prefix} ${escapeHtml(error.message)}</div>`;
|
|
1420
1470
|
}
|
|
1421
1471
|
}
|
|
1422
1472
|
|
|
@@ -1491,9 +1541,9 @@ end
|
|
|
1491
1541
|
function copyMigrationCode() {
|
|
1492
1542
|
if (!currentMigrationData) return;
|
|
1493
1543
|
navigator.clipboard.writeText(currentMigrationData.code).then(() => {
|
|
1494
|
-
showToast(
|
|
1544
|
+
showToast(PG_REPORTS_I18N.success.migration_copied);
|
|
1495
1545
|
}).catch(() => {
|
|
1496
|
-
showToast(
|
|
1546
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
1497
1547
|
});
|
|
1498
1548
|
}
|
|
1499
1549
|
|
|
@@ -1540,7 +1590,7 @@ end
|
|
|
1540
1590
|
const data = await response.json();
|
|
1541
1591
|
|
|
1542
1592
|
if (data.success) {
|
|
1543
|
-
showToast(
|
|
1593
|
+
showToast(PG_REPORTS_I18N.success.migration_created);
|
|
1544
1594
|
closeMigrationModal();
|
|
1545
1595
|
|
|
1546
1596
|
// Open in IDE if path provided
|
|
@@ -1555,10 +1605,10 @@ end
|
|
|
1555
1605
|
}
|
|
1556
1606
|
}
|
|
1557
1607
|
} else {
|
|
1558
|
-
showToast(data.error ||
|
|
1608
|
+
showToast(data.error || PG_REPORTS_I18N.errors.create_migration_failed, 'error');
|
|
1559
1609
|
}
|
|
1560
1610
|
} catch (error) {
|
|
1561
|
-
showToast(
|
|
1611
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1562
1612
|
}
|
|
1563
1613
|
}
|
|
1564
1614
|
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
.documentation-section {
|
|
4
4
|
background: var(--bg-card);
|
|
5
5
|
border: 1px solid var(--border-color);
|
|
6
|
-
border-radius:
|
|
6
|
+
border-radius: 6px;
|
|
7
7
|
overflow: hidden;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -138,9 +138,9 @@
|
|
|
138
138
|
margin-top: 4px;
|
|
139
139
|
background: var(--bg-card);
|
|
140
140
|
border: 1px solid var(--border-color);
|
|
141
|
-
border-radius:
|
|
141
|
+
border-radius: 6px;
|
|
142
142
|
min-width: 160px;
|
|
143
|
-
box-shadow: 0
|
|
143
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
144
144
|
z-index: 100;
|
|
145
145
|
overflow: hidden;
|
|
146
146
|
}
|
|
@@ -167,6 +167,26 @@
|
|
|
167
167
|
border-bottom: 1px solid var(--border-color);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
.dropdown-divider {
|
|
171
|
+
height: 0;
|
|
172
|
+
margin: 0;
|
|
173
|
+
border-top: 1px solid var(--border-color);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.dropdown-menu .ai-icon {
|
|
177
|
+
display: inline-block;
|
|
178
|
+
font-size: 0.6rem;
|
|
179
|
+
font-weight: 800;
|
|
180
|
+
background: #8b5cf6;
|
|
181
|
+
color: #fff;
|
|
182
|
+
border-radius: 3px;
|
|
183
|
+
padding: 0.1rem 0.3rem;
|
|
184
|
+
letter-spacing: 0.03em;
|
|
185
|
+
line-height: 1.2;
|
|
186
|
+
vertical-align: middle;
|
|
187
|
+
margin-right: 0.2rem;
|
|
188
|
+
}
|
|
189
|
+
|
|
170
190
|
/* Clickable rows */
|
|
171
191
|
.results-table tbody tr.data-row {
|
|
172
192
|
cursor: pointer;
|
|
@@ -260,11 +280,29 @@
|
|
|
260
280
|
.problem-modal-content {
|
|
261
281
|
background: var(--bg-card);
|
|
262
282
|
border: 1px solid var(--border-color);
|
|
263
|
-
border-radius:
|
|
283
|
+
border-radius: 6px;
|
|
264
284
|
max-width: 600px;
|
|
265
285
|
width: 90%;
|
|
266
286
|
max-height: 80vh;
|
|
267
287
|
overflow: auto;
|
|
288
|
+
scrollbar-width: thin;
|
|
289
|
+
scrollbar-color: var(--border-color) var(--bg-secondary);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.problem-modal-content::-webkit-scrollbar {
|
|
293
|
+
width: 8px;
|
|
294
|
+
height: 8px;
|
|
295
|
+
}
|
|
296
|
+
.problem-modal-content::-webkit-scrollbar-track {
|
|
297
|
+
background: var(--bg-secondary);
|
|
298
|
+
border-radius: 4px;
|
|
299
|
+
}
|
|
300
|
+
.problem-modal-content::-webkit-scrollbar-thumb {
|
|
301
|
+
background: var(--border-color);
|
|
302
|
+
border-radius: 4px;
|
|
303
|
+
}
|
|
304
|
+
.problem-modal-content::-webkit-scrollbar-thumb:hover {
|
|
305
|
+
background: var(--text-muted);
|
|
268
306
|
}
|
|
269
307
|
|
|
270
308
|
.problem-modal-header {
|
|
@@ -649,7 +687,7 @@
|
|
|
649
687
|
.saved-records-section {
|
|
650
688
|
background: var(--bg-card);
|
|
651
689
|
border: 1px solid var(--accent-purple);
|
|
652
|
-
border-radius:
|
|
690
|
+
border-radius: 6px;
|
|
653
691
|
margin-bottom: 1rem;
|
|
654
692
|
overflow: hidden;
|
|
655
693
|
}
|
|
@@ -1161,7 +1199,7 @@
|
|
|
1161
1199
|
.filter-section {
|
|
1162
1200
|
background: var(--bg-card);
|
|
1163
1201
|
border: 1px solid var(--border-color);
|
|
1164
|
-
border-radius:
|
|
1202
|
+
border-radius: 6px;
|
|
1165
1203
|
overflow: hidden;
|
|
1166
1204
|
}
|
|
1167
1205
|
|
|
@@ -1272,7 +1310,7 @@
|
|
|
1272
1310
|
/* Summary card at top */
|
|
1273
1311
|
.explain-summary {
|
|
1274
1312
|
background: var(--bg-card);
|
|
1275
|
-
border-radius:
|
|
1313
|
+
border-radius: 6px;
|
|
1276
1314
|
padding: 1.25rem;
|
|
1277
1315
|
margin-bottom: 1.5rem;
|
|
1278
1316
|
border-left: 4px solid var(--accent-blue);
|
|
@@ -1344,7 +1382,7 @@
|
|
|
1344
1382
|
.explain-stat {
|
|
1345
1383
|
background: var(--bg-card);
|
|
1346
1384
|
border: 1px solid var(--border-color);
|
|
1347
|
-
border-radius:
|
|
1385
|
+
border-radius: 6px;
|
|
1348
1386
|
padding: 1rem;
|
|
1349
1387
|
display: flex;
|
|
1350
1388
|
flex-direction: column;
|
|
@@ -1387,7 +1425,7 @@
|
|
|
1387
1425
|
.explain-problems {
|
|
1388
1426
|
background: var(--bg-card);
|
|
1389
1427
|
border: 1px solid var(--border-color);
|
|
1390
|
-
border-radius:
|
|
1428
|
+
border-radius: 6px;
|
|
1391
1429
|
padding: 1.25rem;
|
|
1392
1430
|
margin-bottom: 1.5rem;
|
|
1393
1431
|
}
|
|
@@ -1482,7 +1520,7 @@
|
|
|
1482
1520
|
.explain-output {
|
|
1483
1521
|
background: var(--bg-card);
|
|
1484
1522
|
border: 1px solid var(--border-color);
|
|
1485
|
-
border-radius:
|
|
1523
|
+
border-radius: 6px;
|
|
1486
1524
|
overflow: hidden;
|
|
1487
1525
|
}
|
|
1488
1526
|
|
|
@@ -1633,7 +1671,7 @@
|
|
|
1633
1671
|
overflow-x: auto;
|
|
1634
1672
|
background: var(--bg-card);
|
|
1635
1673
|
border: 1px solid var(--border-color);
|
|
1636
|
-
border-radius:
|
|
1674
|
+
border-radius: 6px;
|
|
1637
1675
|
padding: 1.25rem;
|
|
1638
1676
|
color: var(--text-secondary);
|
|
1639
1677
|
}
|