|
89 | 89 | function hide(e) { e.classList.add('mg-hidden'); } |
90 | 90 | function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } |
91 | 91 |
|
| 92 | + function formatBytes(bytes) { |
| 93 | + if (bytes === null || bytes === undefined) return '—'; |
| 94 | + var n = Number(bytes); |
| 95 | + if (!isFinite(n) || n <= 0) return '0 B'; |
| 96 | + var units = ['B', 'KB', 'MB', 'GB', 'TB']; |
| 97 | + var i = Math.min(units.length - 1, Math.floor(Math.log(n) / Math.log(1024))); |
| 98 | + return (n / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1) + ' ' + units[i]; |
| 99 | + } |
| 100 | + |
| 101 | + function formatRelativeTime(isoString) { |
| 102 | + if (!isoString) return null; |
| 103 | + var then = new Date(isoString); |
| 104 | + if (isNaN(then.getTime())) return null; |
| 105 | + var diffMs = Date.now() - then.getTime(); |
| 106 | + if (diffMs < 0) diffMs = 0; |
| 107 | + var seconds = Math.floor(diffMs / 1000); |
| 108 | + if (seconds < 60) return seconds + 's ago'; |
| 109 | + var minutes = Math.floor(seconds / 60); |
| 110 | + if (minutes < 60) return minutes + 'm ago'; |
| 111 | + var hours = Math.floor(minutes / 60); |
| 112 | + if (hours < 48) return hours + 'h ago'; |
| 113 | + var days = Math.floor(hours / 24); |
| 114 | + return days + 'd ago'; |
| 115 | + } |
| 116 | + |
| 117 | + function renderUnusedStatsContext(data) { |
| 118 | + var ctx = el('unused-stats-context'); |
| 119 | + if (!ctx) return; |
| 120 | + var parts = []; |
| 121 | + if (data && data.stats_reset_at) { |
| 122 | + var rel = formatRelativeTime(data.stats_reset_at); |
| 123 | + if (rel) parts.push('Stats last reset <strong>' + escHtml(rel) + '</strong>'); |
| 124 | + } |
| 125 | + var threshold = data && (data.min_scans || data.min_scans === 0) ? data.min_scans : null; |
| 126 | + if (threshold !== null) { |
| 127 | + parts.push('threshold: scans ≤ <strong>' + threshold + '</strong>'); |
| 128 | + } |
| 129 | + ctx.innerHTML = parts.length ? parts.join(' · ') : ''; |
| 130 | + if (parts.length) show(ctx); else hide(ctx); |
| 131 | + } |
| 132 | + |
92 | 133 | // --- Table Sorting --- |
93 | 134 |
|
94 | 135 | function parseSortValue(text) { |
|
459 | 500 |
|
460 | 501 | // Unused indexes count |
461 | 502 | ajaxGet(ROUTES.unused_indexes, {}, function(data) { |
462 | | - var count = Array.isArray(data) ? data.length : 0; |
| 503 | + var indexes = (data && data.indexes) || []; |
| 504 | + var count = indexes.length; |
463 | 505 | var countEl = el('dash-unused-count'); |
464 | 506 | if (count === 0) { |
465 | 507 | countEl.innerHTML = '<span style="color:#28a745;">✓ 0</span>'; |
|
1142 | 1184 | el('unused-error').innerHTML = '<div class="mg-alert mg-alert-warning">' + escHtml(data.error) + '</div>'; |
1143 | 1185 | show(el('unused-error')); return; |
1144 | 1186 | } |
1145 | | - if (!data.length) { show(el('unused-empty')); el('unused-count').textContent = '0'; return; } |
1146 | | - el('unused-count').textContent = data.length + ' found'; |
1147 | | - el('unused-tbody').innerHTML = data.map(function(d) { |
| 1187 | + var indexes = (data && data.indexes) || []; |
| 1188 | + renderUnusedStatsContext(data); |
| 1189 | + if (!indexes.length) { show(el('unused-empty')); el('unused-count').textContent = '0'; return; } |
| 1190 | + el('unused-count').textContent = indexes.length + ' found'; |
| 1191 | + el('unused-tbody').innerHTML = indexes.map(function(d) { |
1148 | 1192 | return '<tr>' + |
1149 | 1193 | '<td><strong>' + escHtml(d.table) + '</strong></td>' + |
1150 | 1194 | '<td><code>' + escHtml(d.index_name) + '</code></td>' + |
| 1195 | + '<td class="mg-num">' + formatBytes(d.size_bytes) + '</td>' + |
1151 | 1196 | '<td class="mg-num">' + d.reads + '</td>' + |
1152 | 1197 | '<td class="mg-num">' + Number(d.writes).toLocaleString() + '</td>' + |
1153 | 1198 | '<td class="mg-num">' + Number(d.table_rows).toLocaleString() + '</td>' + |
|
1161 | 1206 | var ts = migrationTimestamp(); |
1162 | 1207 | var migrationLines = ['# ' + ts + '_remove_unused_indexes.rb', '', |
1163 | 1208 | 'class RemoveUnusedIndexes < ActiveRecord::Migration[' + RAILS_MIGRATION_VERSION + ']', ' def change']; |
1164 | | - data.forEach(function(d) { |
| 1209 | + indexes.forEach(function(d) { |
1165 | 1210 | migrationLines.push(' remove_index :' + d.table + ', name: :' + d.index_name); |
1166 | 1211 | }); |
1167 | 1212 | migrationLines.push(' end', 'end'); |
|
0 commit comments