pg_reports 0.6.0 → 0.6.2
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 +45 -0
- data/README.md +143 -378
- data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
- data/app/views/layouts/pg_reports/application.html.erb +65 -8
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
- data/app/views/pg_reports/dashboard/index.html.erb +109 -106
- data/app/views/pg_reports/dashboard/show.html.erb +26 -26
- data/config/locales/en.yml +488 -0
- data/config/locales/ru.yml +481 -0
- data/config/locales/uk.yml +481 -0
- data/lib/pg_reports/annotation_parser.rb +13 -1
- data/lib/pg_reports/compatibility.rb +3 -3
- data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
- 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/tables/unused_tables.yml +30 -0
- data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
- data/lib/pg_reports/module_generator.rb +2 -1
- data/lib/pg_reports/modules/schema_analysis.rb +261 -2
- data/lib/pg_reports/modules/system.rb +3 -3
- data/lib/pg_reports/query_monitor.rb +2 -6
- data/lib/pg_reports/report_definition.rb +20 -24
- 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/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
- metadata +9 -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,7 @@
|
|
|
244
244
|
btn.style.color = '';
|
|
245
245
|
}, 1500);
|
|
246
246
|
}).catch(() => {
|
|
247
|
-
showToast(
|
|
247
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
248
248
|
});
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -288,15 +288,15 @@
|
|
|
288
288
|
function copyAiPrompt(el) {
|
|
289
289
|
const prompt = buildAiPrompt();
|
|
290
290
|
if (!prompt) {
|
|
291
|
-
showToast(
|
|
291
|
+
showToast(PG_REPORTS_I18N.errors.run_report_first, 'error');
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
document.getElementById('dropdown-menu').classList.remove('show');
|
|
296
296
|
navigator.clipboard.writeText(prompt).then(() => {
|
|
297
|
-
showToast(
|
|
297
|
+
showToast(PG_REPORTS_I18N.success.ai_prompt_copied);
|
|
298
298
|
}).catch(() => {
|
|
299
|
-
showToast(
|
|
299
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
300
300
|
});
|
|
301
301
|
}
|
|
302
302
|
|
|
@@ -351,15 +351,15 @@
|
|
|
351
351
|
|
|
352
352
|
for (const problem of problems) {
|
|
353
353
|
const levelClass = problem.level === 'critical' ? 'critical' : 'warning';
|
|
354
|
-
const levelText = problem.level === 'critical' ?
|
|
354
|
+
const levelText = problem.level === 'critical' ? PG_REPORTS_I18N.levels.critical : PG_REPORTS_I18N.levels.warning;
|
|
355
355
|
|
|
356
356
|
html += `
|
|
357
357
|
<div class="problem-field">
|
|
358
358
|
<span class="problem-field-label">${escapeHtml(problem.field)} (${levelText})</span>
|
|
359
359
|
<div class="problem-field-value ${levelClass}">
|
|
360
|
-
|
|
361
|
-
<br
|
|
362
|
-
${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>' : ''}
|
|
363
363
|
</div>
|
|
364
364
|
</div>
|
|
365
365
|
`;
|
|
@@ -372,7 +372,7 @@
|
|
|
372
372
|
if (explanation) {
|
|
373
373
|
html += `
|
|
374
374
|
<div class="problem-explanation">
|
|
375
|
-
<h4
|
|
375
|
+
<h4>${PG_REPORTS_I18N.sections.recommendation}</h4>
|
|
376
376
|
<p>${escapeHtml(explanation)}</p>
|
|
377
377
|
</div>
|
|
378
378
|
`;
|
|
@@ -632,7 +632,7 @@
|
|
|
632
632
|
<div class="row-detail-item${isLongText ? ' full-width' : ''}">
|
|
633
633
|
<span class="row-detail-label">${escapeHtml(col)}</span>
|
|
634
634
|
<div class="row-detail-value ${valueClass}">${escapeHtml(strValue)}</div>
|
|
635
|
-
${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>` : ''}
|
|
636
636
|
</div>
|
|
637
637
|
`;
|
|
638
638
|
});
|
|
@@ -684,7 +684,7 @@
|
|
|
684
684
|
|
|
685
685
|
if (button) {
|
|
686
686
|
button.disabled = true;
|
|
687
|
-
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;
|
|
688
688
|
}
|
|
689
689
|
|
|
690
690
|
if (loadingEl) loadingEl.style.display = 'flex';
|
|
@@ -761,18 +761,18 @@
|
|
|
761
761
|
renderTableBody(data.data, data.columns, thresholds, problemFields);
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
showToast(
|
|
764
|
+
showToast(PG_REPORTS_I18N.success.report_generated);
|
|
765
765
|
} else {
|
|
766
|
-
showToast(data.error ||
|
|
766
|
+
showToast(data.error || PG_REPORTS_I18N.errors.run_report_failed, 'error');
|
|
767
767
|
}
|
|
768
768
|
} catch (error) {
|
|
769
769
|
if (loadingEl) loadingEl.style.display = 'none';
|
|
770
|
-
showToast(
|
|
770
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
if (button) {
|
|
774
774
|
button.disabled = false;
|
|
775
|
-
button.innerHTML =
|
|
775
|
+
button.innerHTML = PG_REPORTS_I18N.actions.run_report;
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
|
|
@@ -885,8 +885,8 @@
|
|
|
885
885
|
// Remove
|
|
886
886
|
saved.splice(existingIdx, 1);
|
|
887
887
|
btn.classList.remove('saved');
|
|
888
|
-
btn.textContent =
|
|
889
|
-
showToast(
|
|
888
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
889
|
+
showToast(PG_REPORTS_I18N.success.record_removed_saved);
|
|
890
890
|
} else {
|
|
891
891
|
// Add
|
|
892
892
|
saved.unshift({
|
|
@@ -895,8 +895,8 @@
|
|
|
895
895
|
data: row
|
|
896
896
|
});
|
|
897
897
|
btn.classList.add('saved');
|
|
898
|
-
btn.textContent =
|
|
899
|
-
showToast(
|
|
898
|
+
btn.textContent = PG_REPORTS_I18N.actions.saved_marker;
|
|
899
|
+
showToast(PG_REPORTS_I18N.success.record_saved);
|
|
900
900
|
}
|
|
901
901
|
|
|
902
902
|
saveSavedRecords(saved);
|
|
@@ -913,24 +913,24 @@
|
|
|
913
913
|
const btn = document.querySelector(`.btn-save[onclick*="'${rowId}'"]`);
|
|
914
914
|
if (btn) {
|
|
915
915
|
btn.classList.remove('saved');
|
|
916
|
-
btn.textContent =
|
|
916
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
-
showToast(
|
|
919
|
+
showToast(PG_REPORTS_I18N.success.record_removed);
|
|
920
920
|
}
|
|
921
921
|
|
|
922
922
|
function clearAllSavedRecords() {
|
|
923
|
-
if (!confirm(
|
|
923
|
+
if (!confirm(PG_REPORTS_I18N.saved.confirm_clear_all)) return;
|
|
924
924
|
saveSavedRecords([]);
|
|
925
925
|
renderSavedRecords();
|
|
926
926
|
|
|
927
927
|
// Update all buttons in table
|
|
928
928
|
document.querySelectorAll('.btn-save.saved').forEach(btn => {
|
|
929
929
|
btn.classList.remove('saved');
|
|
930
|
-
btn.textContent =
|
|
930
|
+
btn.textContent = PG_REPORTS_I18N.actions.save_for_comparison;
|
|
931
931
|
});
|
|
932
932
|
|
|
933
|
-
showToast(
|
|
933
|
+
showToast(PG_REPORTS_I18N.success.all_saved_cleared);
|
|
934
934
|
}
|
|
935
935
|
|
|
936
936
|
function renderSavedRecords() {
|
|
@@ -959,9 +959,9 @@
|
|
|
959
959
|
html += `
|
|
960
960
|
<div class="saved-record-card" id="saved-card-${idx}" onclick="toggleSavedRecordDetail(${idx}, event)">
|
|
961
961
|
<div class="saved-record-header">
|
|
962
|
-
<span class="saved-record-time"
|
|
963
|
-
<span class="saved-record-expand-hint"
|
|
964
|
-
<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>
|
|
965
965
|
</div>
|
|
966
966
|
<div class="saved-record-data">
|
|
967
967
|
`;
|
|
@@ -1086,7 +1086,7 @@
|
|
|
1086
1086
|
const query = decodeURIComponent(escape(atob(queryBase64)));
|
|
1087
1087
|
runExplainAnalyze(query, queryHash);
|
|
1088
1088
|
} catch (e) {
|
|
1089
|
-
showToast(
|
|
1089
|
+
showToast(PG_REPORTS_I18N.errors.decode_query_failed, 'error');
|
|
1090
1090
|
}
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
@@ -1207,7 +1207,7 @@
|
|
|
1207
1207
|
// Show problems list
|
|
1208
1208
|
if (data.problems && data.problems.length > 0) {
|
|
1209
1209
|
html += '<div class="explain-problems">';
|
|
1210
|
-
html += '<div class="explain-problems-header"
|
|
1210
|
+
html += '<div class="explain-problems-header">' + PG_REPORTS_I18N.sections.detected_issues + '</div>';
|
|
1211
1211
|
html += '<div class="explain-problems-list">';
|
|
1212
1212
|
|
|
1213
1213
|
data.problems.forEach(problem => {
|
|
@@ -1219,7 +1219,7 @@
|
|
|
1219
1219
|
html += '<span class="explain-problem-icon">' + severityIcon + '</span>';
|
|
1220
1220
|
html += '<span class="explain-problem-message">' + escapeHtml(problem.message) + '</span>';
|
|
1221
1221
|
if (problem.line_number) {
|
|
1222
|
-
html += '<span class="explain-problem-line">
|
|
1222
|
+
html += '<span class="explain-problem-line">' + PG_REPORTS_I18N.sections.line_label + ' ' + problem.line_number + '</span>';
|
|
1223
1223
|
}
|
|
1224
1224
|
html += '</div>';
|
|
1225
1225
|
if (problem.details) {
|
|
@@ -1239,8 +1239,8 @@
|
|
|
1239
1239
|
if (data.annotated_lines && data.annotated_lines.length > 0) {
|
|
1240
1240
|
html += '<div class="explain-output">';
|
|
1241
1241
|
html += '<div class="explain-output-header">';
|
|
1242
|
-
html +=
|
|
1243
|
-
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>';
|
|
1244
1244
|
html += '</div>';
|
|
1245
1245
|
html += '<div class="explain-lines">';
|
|
1246
1246
|
|
|
@@ -1324,9 +1324,9 @@
|
|
|
1324
1324
|
const text = Array.from(lines).map(line => line.textContent).join('\n');
|
|
1325
1325
|
|
|
1326
1326
|
navigator.clipboard.writeText(text).then(() => {
|
|
1327
|
-
showToast(
|
|
1327
|
+
showToast(PG_REPORTS_I18N.success.explain_copied);
|
|
1328
1328
|
}).catch(() => {
|
|
1329
|
-
showToast(
|
|
1329
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
1330
1330
|
});
|
|
1331
1331
|
}
|
|
1332
1332
|
|
|
@@ -1337,11 +1337,10 @@
|
|
|
1337
1337
|
|
|
1338
1338
|
// Security check
|
|
1339
1339
|
if (!allowRawQueryExecution) {
|
|
1340
|
-
|
|
1341
|
-
showToast(message, 'error');
|
|
1340
|
+
showToast(PG_REPORTS_I18N.errors.explain_disabled_toast, 'error');
|
|
1342
1341
|
content.innerHTML = `<div class="error-message">
|
|
1343
|
-
<strong
|
|
1344
|
-
|
|
1342
|
+
<strong>${PG_REPORTS_I18N.modals.query_execution_disabled_title}</strong><br><br>
|
|
1343
|
+
${PG_REPORTS_I18N.modals.query_execution_disabled_intro}<br>
|
|
1345
1344
|
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
1346
1345
|
PgReports.configure do |config|<br>
|
|
1347
1346
|
config.allow_raw_query_execution = true<br>
|
|
@@ -1372,11 +1371,11 @@ end
|
|
|
1372
1371
|
if (data.success) {
|
|
1373
1372
|
content.innerHTML = renderExplainAnalysis(data);
|
|
1374
1373
|
} else {
|
|
1375
|
-
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>`;
|
|
1376
1375
|
}
|
|
1377
1376
|
} catch (error) {
|
|
1378
1377
|
loading.style.display = 'none';
|
|
1379
|
-
content.innerHTML = `<div class="error-message"
|
|
1378
|
+
content.innerHTML = `<div class="error-message">${PG_REPORTS_I18N.errors.network_error_prefix} ${escapeHtml(error.message)}</div>`;
|
|
1380
1379
|
}
|
|
1381
1380
|
}
|
|
1382
1381
|
|
|
@@ -1387,11 +1386,10 @@ end
|
|
|
1387
1386
|
|
|
1388
1387
|
// Security check
|
|
1389
1388
|
if (!allowRawQueryExecution) {
|
|
1390
|
-
|
|
1391
|
-
showToast(message, 'error');
|
|
1389
|
+
showToast(PG_REPORTS_I18N.errors.execute_disabled_toast, 'error');
|
|
1392
1390
|
content.innerHTML = `<div class="error-message">
|
|
1393
|
-
<strong
|
|
1394
|
-
|
|
1391
|
+
<strong>${PG_REPORTS_I18N.modals.query_execution_disabled_title}</strong><br><br>
|
|
1392
|
+
${PG_REPORTS_I18N.modals.query_execution_disabled_intro}<br>
|
|
1395
1393
|
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
1396
1394
|
PgReports.configure do |config|<br>
|
|
1397
1395
|
config.allow_raw_query_execution = true<br>
|
|
@@ -1424,8 +1422,8 @@ end
|
|
|
1424
1422
|
|
|
1425
1423
|
// Show info
|
|
1426
1424
|
html += `<div class="query-results-info">
|
|
1427
|
-
<span
|
|
1428
|
-
<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>
|
|
1429
1427
|
</div>`;
|
|
1430
1428
|
|
|
1431
1429
|
if (data.rows && data.rows.length > 0) {
|
|
@@ -1445,7 +1443,7 @@ end
|
|
|
1445
1443
|
html += '<tr>';
|
|
1446
1444
|
data.columns.forEach(col => {
|
|
1447
1445
|
const value = row[col];
|
|
1448
|
-
const displayValue = value === null ?
|
|
1446
|
+
const displayValue = value === null ? PG_REPORTS_I18N.results.null_placeholder : String(value);
|
|
1449
1447
|
html += `<td title="${escapeHtmlAttr(displayValue)}">${escapeHtml(displayValue)}</td>`;
|
|
1450
1448
|
});
|
|
1451
1449
|
html += '</tr>';
|
|
@@ -1456,19 +1454,19 @@ end
|
|
|
1456
1454
|
html += '</div>';
|
|
1457
1455
|
|
|
1458
1456
|
if (data.truncated) {
|
|
1459
|
-
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>`;
|
|
1460
1458
|
}
|
|
1461
1459
|
} else {
|
|
1462
|
-
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>';
|
|
1463
1461
|
}
|
|
1464
1462
|
|
|
1465
1463
|
content.innerHTML = html;
|
|
1466
1464
|
} else {
|
|
1467
|
-
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>`;
|
|
1468
1466
|
}
|
|
1469
1467
|
} catch (error) {
|
|
1470
1468
|
loading.style.display = 'none';
|
|
1471
|
-
content.innerHTML = `<div class="error-message"
|
|
1469
|
+
content.innerHTML = `<div class="error-message">${PG_REPORTS_I18N.errors.network_error_prefix} ${escapeHtml(error.message)}</div>`;
|
|
1472
1470
|
}
|
|
1473
1471
|
}
|
|
1474
1472
|
|
|
@@ -1543,9 +1541,9 @@ end
|
|
|
1543
1541
|
function copyMigrationCode() {
|
|
1544
1542
|
if (!currentMigrationData) return;
|
|
1545
1543
|
navigator.clipboard.writeText(currentMigrationData.code).then(() => {
|
|
1546
|
-
showToast(
|
|
1544
|
+
showToast(PG_REPORTS_I18N.success.migration_copied);
|
|
1547
1545
|
}).catch(() => {
|
|
1548
|
-
showToast(
|
|
1546
|
+
showToast(PG_REPORTS_I18N.errors.copy_failed, 'error');
|
|
1549
1547
|
});
|
|
1550
1548
|
}
|
|
1551
1549
|
|
|
@@ -1592,7 +1590,7 @@ end
|
|
|
1592
1590
|
const data = await response.json();
|
|
1593
1591
|
|
|
1594
1592
|
if (data.success) {
|
|
1595
|
-
showToast(
|
|
1593
|
+
showToast(PG_REPORTS_I18N.success.migration_created);
|
|
1596
1594
|
closeMigrationModal();
|
|
1597
1595
|
|
|
1598
1596
|
// Open in IDE if path provided
|
|
@@ -1607,10 +1605,10 @@ end
|
|
|
1607
1605
|
}
|
|
1608
1606
|
}
|
|
1609
1607
|
} else {
|
|
1610
|
-
showToast(data.error ||
|
|
1608
|
+
showToast(data.error || PG_REPORTS_I18N.errors.create_migration_failed, 'error');
|
|
1611
1609
|
}
|
|
1612
1610
|
} catch (error) {
|
|
1613
|
-
showToast(
|
|
1611
|
+
showToast(PG_REPORTS_I18N.errors.network_error_prefix + ' ' + error.message, 'error');
|
|
1614
1612
|
}
|
|
1615
1613
|
}
|
|
1616
1614
|
</script>
|
|
@@ -285,6 +285,24 @@
|
|
|
285
285
|
width: 90%;
|
|
286
286
|
max-height: 80vh;
|
|
287
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);
|
|
288
306
|
}
|
|
289
307
|
|
|
290
308
|
.problem-modal-header {
|