EmDubyaJr/sql-backup-guardian
0
1 2// Processed backup data3const backupData = [4 {5 id: 1,6 name: "model",7 server: "BISQL2016",8 type: "Full",9 date: "2023-10-23T07:05:00",10 size: "N/A",11 status: "Success",12 recoveryModel: "FULL",13 logReuseWaitDesc: "LOG_BACKUP",14 lastDiffBackup: null,15 lastLogBackup: null,16 lastCheckDB: "2023-10-22T16:00:00"17 },18 {19 id: 2,20 name: "BrightStar_DW",21 server: "BISQL2016",22 type: "Full",23 date: "2023-10-22T23:21:00",24 size: "N/A",25 status: "Success",26 recoveryModel: "SIMPLE",27 logReuseWaitDesc: "NOTHING",28 lastDiffBackup: null,29 lastLogBackup: null,30 lastCheckDB: "2023-10-19T16:15:00"31 },32 {33 id: 3,34 name: "DBA",35 server: "BISQL2016",36 type: "Full",37 date: "2023-10-22T23:21:00",38 size: "N/A",39 status: "Success",40 recoveryModel: "SIMPLE",41 logReuseWaitDesc: "NOTHING",42 lastDiffBackup: null,43 lastLogBackup: null,44 lastCheckDB: "2023-10-19T16:25:00"45 },46 // Add more entries from the provided data...47{48 id: 2,49 name: "OrdersDB",50 type: "Differential",51 date: "2023-06-15T01:00:00Z",52 size: "4.2 GB",53 status: "Success",54 recoveryModel: "Full",55 backupPath: "E:\\Backups\\OrdersDB_Diff_20230615.bak",56 duration: "5 min",57 compression: "Enabled",58 expiration: "2023-06-22"59 },60 {61 id: 3,62 name: "InventoryDB",63 type: "Log",64 date: "2023-06-15T10:15:00Z",65 size: "1.8 GB",66 status: "Success",67 recoveryModel: "Full",68 backupPath: "E:\\Backups\\InventoryDB_Log_20230615.trn",69 duration: "2 min",70 compression: "Enabled",71 expiration: "2023-06-16"72 },73 {74 id: 4,75 name: "ReportingDB",76 type: "Full",77 date: "2023-06-14T22:00:00Z",78 size: "8.7 GB",79 status: "Warning",80 recoveryModel: "Simple",81 backupPath: "E:\\Backups\\ReportingDB_Full_20230614.bak",82 duration: "10 min",83 compression: "Disabled",84 expiration: "2023-07-14"85 },86 {87 id: 5,88 name: "HRDB",89 type: "Full",90 date: "2023-06-10T02:00:00Z",91 size: "3.5 GB",92 status: "Error",93 recoveryModel: "Full",94 backupPath: "E:\\Backups\\HRDB_Full_20230610.bak",95 duration: "8 min",96 compression: "Enabled",97 expiration: "2023-07-10"98 },99 {100 id: 6,101 name: "ProductsDB",102 type: "Full",103 date: "2023-06-15T03:00:00Z",104 size: "7.1 GB",105 status: "Success",106 recoveryModel: "Full",107 backupPath: "E:\\Backups\\ProductsDB_Full_20230615.bak",108 duration: "12 min",109 compression: "Enabled",110 expiration: "2023-07-15"111 },112 {113 id: 7,114 name: "LogisticsDB",115 type: "Differential",116 date: "2023-06-15T04:30:00Z",117 size: "2.9 GB",118 status: "Success",119 recoveryModel: "Full",120 backupPath: "E:\\Backups\\LogisticsDB_Diff_20230615.bak",121 duration: "4 min",122 compression: "Enabled",123 expiration: "2023-06-22"124 },125 {126 id: 8,127 name: "FinanceDB",128 type: "Full",129 date: "2023-06-13T01:00:00Z",130 size: "5.6 GB",131 status: "Warning",132 recoveryModel: "Full",133 backupPath: "E:\\Backups\\FinanceDB_Full_20230613.bak",134 duration: "9 min",135 compression: "Enabled",136 expiration: "2023-07-13"137 },138 {139 id: 9,140 name: "SupportDB",141 type: "Log",142 date: "2023-06-15T09:00:00Z",143 size: "0.8 GB",144 status: "Success",145 recoveryModel: "Full",146 backupPath: "E:\\Backups\\SupportDB_Log_20230615.trn",147 duration: "1 min",148 compression: "Enabled",149 expiration: "2023-06-16"150 },151 {152 id: 10,153 name: "AnalyticsDB",154 type: "Full",155 date: "2023-06-12T03:00:00Z",156 size: "14.2 GB",157 status: "Error",158 recoveryModel: "Simple",159 backupPath: "E:\\Backups\\AnalyticsDB_Full_20230612.bak",160 duration: "18 min",161 compression: "Disabled",162 expiration: "2023-07-12"163 }164];165 166// Pagination variables167let currentPage = 1;168const itemsPerPage = 8;169// Load backup data and render table170function loadBackupData() {171 // Update stats172 document.getElementById('total-databases').textContent = backupData.length;173 document.getElementById('backed-up-today').textContent = backupData.filter(b => {174 const backupDate = new Date(b.date);175 const today = new Date();176 return backupDate.toDateString() === today.toDateString();177 }).length;178 179 // Calculate at-risk databases (no backup in last 24 hours or log backup required)180 const atRiskCount = backupData.filter(b => {181 const backupDate = new Date(b.date);182 const now = new Date();183 const hoursSinceBackup = (now - backupDate) / (1000 * 60 * 60);184 185 // Check if FULL recovery model needs log backup186 const needsLogBackup = b.recoveryModel === 'FULL' && 187 b.logReuseWaitDesc === 'LOG_BACKUP' && 188 (!b.lastLogBackup || (new Date() - new Date(b.lastLogBackup)) > 24 * 60 * 60 * 1000);189 190 return hoursSinceBackup > 24 || needsLogBackup;191 }).length;192 193 document.getElementById('at-risk').textContent = atRiskCount;194// Render table195 renderTable();196}197// Render the backup table with pagination198function renderTable() {199 const tableBody = document.getElementById('backup-table-body');200 tableBody.innerHTML = '';201 202 const startIdx = (currentPage - 1) * itemsPerPage;203 const endIdx = Math.min(startIdx + itemsPerPage, backupData.length);204 205 for (let i = startIdx; i < endIdx; i++) {206 const backup = backupData[i];207 const row = document.createElement('tr');208 209 // Format date for display210 const backupDate = new Date(backup.date);211 const formattedDate = backupDate.toLocaleString();212 213 // Calculate time since last backup214 const now = new Date();215 const hoursSinceBackup = Math.round((now - backupDate) / (1000 * 60 * 60));216 217 // Determine status badge color based on backup age and log requirements218 let statusClass = '';219 let statusText = '';220 221 if (hoursSinceBackup > 48) {222 statusClass = 'bg-red-100 text-red-800 pulse-warning';223 statusText = 'Critical';224 } 225 else if (hoursSinceBackup > 24 || 226 (backup.recoveryModel === 'FULL' && backup.logReuseWaitDesc === 'LOG_BACKUP' && !backup.lastLogBackup)) {227 statusClass = 'bg-amber-100 text-amber-800';228 statusText = 'Warning';229 }230 else {231 statusClass = 'bg-green-100 text-green-800';232 statusText = 'OK';233 }234row.innerHTML = `235 <td class="px-6 py-4 whitespace-nowrap">236 <div class="flex items-center">237 <i data-feather="server" class="w-4 h-4 text-gray-500 mr-1"></i>238 <span class="text-sm font-medium text-gray-700">${backup.server}</span>239 </div>240 </td>241 <td class="px-6 py-4 whitespace-nowrap">242 <div class="flex items-center">243 <i data-feather="database" class="w-4 h-4 text-blue-500 mr-2"></i>244 <span class="font-medium">${backup.name}</span>245 </div>246 </td>247 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">248 ${formattedDate}249 </td>250 <td class="px-6 py-4 whitespace-nowrap">251 <span class="px-2 py-1 text-xs rounded-full bg-blue-100 text-blue-800">${backup.recoveryModel}</span>252 </td>253 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">254 ${hoursSinceBackup} hours ago255 </td>256 <td class="px-6 py-4 whitespace-nowrap">257 <span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span>258 </td>259<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">260 <button onclick="showBackupDetails(${backup.id})" class="text-blue-600 hover:text-blue-900 mr-3">Details</button>261 <button class="text-gray-600 hover:text-gray-900">Restore</button>262 </td>263 `;264 265 tableBody.appendChild(row);266 }267 268 // Update pagination info269 document.getElementById('showing-from').textContent = startIdx + 1;270 document.getElementById('showing-to').textContent = endIdx;271 document.getElementById('total-items').textContent = backupData.length;272 273 // Update pagination buttons274 document.getElementById('prev-btn').disabled = currentPage === 1;275 document.getElementById('next-btn').disabled = endIdx >= backupData.length;276 277 // Replace feather icons in new elements278 feather.replace();279}280// Show backup details in modal281function showBackupDetails(backupId) {282 const backup = backupData.find(b => b.id === backupId);283 if (!backup) return;284 285 const modal = document.getElementById('backup-details-modal');286 document.getElementById('modal-title').textContent = `Backup Details: ${backup.name} (${backup.server})`;287 288 // Format dates for display289 const backupDate = new Date(backup.date);290 const formattedDate = backupDate.toLocaleString();291 const lastCheckDBDate = new Date(backup.lastCheckDB);292 const formattedCheckDB = lastCheckDBDate.toLocaleString();293// Determine status badge color294 let statusClass = '';295 if (backup.status === 'Success') {296 statusClass = 'bg-green-100 text-green-800';297 } else if (backup.status === 'Warning') {298 statusClass = 'bg-amber-100 text-amber-800';299 } else if (backup.status === 'Error') {300 statusClass = 'bg-red-100 text-red-800';301 }302 303 document.getElementById('modal-content').innerHTML = `304 <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">305 <div>306 <h4 class="text-sm font-medium text-gray-500">Server</h4>307 <p class="mt-1 text-sm text-gray-900">${backup.server}</p>308 </div>309 <div>310 <h4 class="text-sm font-medium text-gray-500">Database</h4>311 <p class="mt-1 text-sm text-gray-900">${backup.name}</p>312 </div>313 <div>314 <h4 class="text-sm font-medium text-gray-500">Recovery Model</h4>315 <p class="mt-1 text-sm text-gray-900">${backup.recoveryModel}</p>316 </div>317 <div>318 <h4 class="text-sm font-medium text-gray-500">Log Reuse Wait</h4>319 <p class="mt-1 text-sm text-gray-900">${backup.logReuseWaitDesc}</p>320 </div>321 <div>322 <h4 class="text-sm font-medium text-gray-500">Last Full Backup</h4>323 <p class="mt-1 text-sm text-gray-900">${formattedDate}</p>324 </div>325 <div>326 <h4 class="text-sm font-medium text-gray-500">Last Differential</h4>327 <p class="mt-1 text-sm text-gray-900">${backup.lastDiffBackup || 'None'}</p>328 </div>329 <div>330 <h4 class="text-sm font-medium text-gray-500">Last Log Backup</h4>331 <p class="mt-1 text-sm text-gray-900">${backup.lastLogBackup || 'None'}</p>332 </div>333 <div>334 <h4 class="text-sm font-medium text-gray-500">Last DBCC CheckDB</h4>335 <p class="mt-1 text-sm text-gray-900">${formattedCheckDB}</p>336 </div>337 </div>338<div class="flex justify-end">339 <button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2">340 <i data-feather="download" class="w-4 h-4"></i>341 Download Backup342 </button>343 </div>344 `;345 346 modal.classList.remove('hidden');347 feather.replace();348}349 350// Event listeners351document.addEventListener('DOMContentLoaded', function() {352 // Pagination buttons353 document.getElementById('prev-btn').addEventListener('click', function() {354 if (currentPage > 1) {355 currentPage--;356 renderTable();357 }358 });359 360 document.getElementById('next-btn').addEventListener('click', function() {361 if ((currentPage * itemsPerPage) < backupData.length) {362 currentPage++;363 renderTable();364 }365 });366 367 // Close modal button368 document.getElementById('close-modal').addEventListener('click', function() {369 document.getElementById('backup-details-modal').classList.add('hidden');370 });371});