From ae605a7ef0713be52a6b610ba86679ebf5c7db9d Mon Sep 17 00:00:00 2001 From: "courtmanr@gmail.com" Date: Thu, 8 May 2025 11:23:50 +0100 Subject: [PATCH] Refactor(ui/backups): Decompose updateBackupsTab into focused helper functions --- src/public/js/ui/backups.js | 341 +++++++++++++++++------------------- 1 file changed, 165 insertions(+), 176 deletions(-) diff --git a/src/public/js/ui/backups.js b/src/public/js/ui/backups.js index 88446cc4d..c68ea2f78 100644 --- a/src/public/js/ui/backups.js +++ b/src/public/js/ui/backups.js @@ -29,47 +29,13 @@ PulseApp.ui.backups = (() => { } } - function updateBackupsTab() { - const tableContainer = document.getElementById('backups-table-container'); - const tableBody = document.getElementById('backups-overview-tbody'); - const loadingMsg = document.getElementById('backups-loading-message'); - const noDataMsg = document.getElementById('backups-no-data-message'); - const statusTextElement = document.getElementById('backups-status-text'); - - if (!tableContainer || !tableBody || !loadingMsg || !noDataMsg || !statusTextElement) { - console.error("UI elements for Backups tab not found!"); - return; - } - + function _getInitialBackupData() { const vmsData = PulseApp.state.get('vmsData') || []; const containersData = PulseApp.state.get('containersData') || []; const pbsDataArray = PulseApp.state.get('pbsDataArray') || []; const initialDataReceived = PulseApp.state.get('initialDataReceived'); - const allGuests = [...vmsData, ...containersData]; - if (!initialDataReceived) { - loadingMsg.classList.remove('hidden'); - tableContainer.classList.add('hidden'); - noDataMsg.classList.add('hidden'); - return; - } - - if (allGuests.length === 0) { - loadingMsg.classList.add('hidden'); - tableContainer.classList.add('hidden'); - noDataMsg.textContent = "No Proxmox guests (VMs/Containers) found."; - noDataMsg.classList.remove('hidden'); - return; - } - - loadingMsg.classList.add('hidden'); - - const backupStatusByGuest = []; - const now = Math.floor(Date.now() / 1000); - const sevenDaysAgo = now - (7 * 24 * 60 * 60); - const threeDaysAgo = now - (3 * 24 * 60 * 60); - const allRecentBackupTasks = pbsDataArray.flatMap(pbs => (pbs.backupTasks?.recentTasks || []).map(task => ({ ...task, @@ -91,75 +57,74 @@ PulseApp.ui.backups = (() => { ) ); - allGuests.forEach(guest => { - const guestId = String(guest.vmid); - const guestTypePve = guest.type === 'qemu' ? 'vm' : 'ct'; + return { allGuests, initialDataReceived, allRecentBackupTasks, allSnapshots }; + } - const guestSnapshots = allSnapshots.filter(snap => - String(snap.backupVMID) === guestId && snap.backupType === guestTypePve - ); - const totalBackups = guestSnapshots.length; - const latestSnapshot = guestSnapshots.reduce((latest, snap) => { - return (!latest || (snap['backup-time'] && snap['backup-time'] > latest['backup-time'])) ? snap : latest; - }, null); - const latestSnapshotTime = latestSnapshot ? latestSnapshot['backup-time'] : null; + function _determineGuestBackupStatus(guest, allSnapshots, allRecentBackupTasks) { + const guestId = String(guest.vmid); + const guestTypePve = guest.type === 'qemu' ? 'vm' : 'ct'; + const now = Math.floor(Date.now() / 1000); + const sevenDaysAgo = now - (7 * 24 * 60 * 60); + const threeDaysAgo = now - (3 * 24 * 60 * 60); - const guestTasks = allRecentBackupTasks.filter(task => - task.guestId === guestId && task.guestTypePbs === guestTypePve - ); - const latestTask = guestTasks.reduce((latest, task) => { - return (!latest || (task.startTime && task.startTime > latest.startTime)) ? task : latest; - }, null); + const guestSnapshots = allSnapshots.filter(snap => + String(snap.backupVMID) === guestId && snap.backupType === guestTypePve + ); + const totalBackups = guestSnapshots.length; + const latestSnapshot = guestSnapshots.reduce((latest, snap) => { + return (!latest || (snap['backup-time'] && snap['backup-time'] > latest['backup-time'])) ? snap : latest; + }, null); + const latestSnapshotTime = latestSnapshot ? latestSnapshot['backup-time'] : null; - let healthStatus = 'none'; - let displayTimestamp = latestSnapshotTime; + const guestTasks = allRecentBackupTasks.filter(task => + task.guestId === guestId && task.guestTypePbs === guestTypePve + ); + const latestTask = guestTasks.reduce((latest, task) => { + return (!latest || (task.startTime && task.startTime > latest.startTime)) ? task : latest; + }, null); - if (latestTask) { - displayTimestamp = latestTask.startTime; - if (latestTask.status === 'OK') { - if (latestTask.startTime >= threeDaysAgo) { - healthStatus = 'ok'; - } else if (latestTask.startTime >= sevenDaysAgo) { - healthStatus = 'stale'; - } else { - healthStatus = 'old'; - } - } else { - healthStatus = 'failed'; - } - } else if (latestSnapshotTime) { - if (latestSnapshotTime >= threeDaysAgo) { - healthStatus = 'ok'; - } else if (latestSnapshotTime >= sevenDaysAgo) { - healthStatus = 'stale'; - } else { - healthStatus = 'old'; - } + let healthStatus = 'none'; + let displayTimestamp = latestSnapshotTime; + + if (latestTask) { + displayTimestamp = latestTask.startTime; + if (latestTask.status === 'OK') { + if (latestTask.startTime >= threeDaysAgo) healthStatus = 'ok'; + else if (latestTask.startTime >= sevenDaysAgo) healthStatus = 'stale'; + else healthStatus = 'old'; } else { - healthStatus = 'none'; - displayTimestamp = null; + healthStatus = 'failed'; } + } else if (latestSnapshotTime) { + if (latestSnapshotTime >= threeDaysAgo) healthStatus = 'ok'; + else if (latestSnapshotTime >= sevenDaysAgo) healthStatus = 'stale'; + else healthStatus = 'old'; + } else { + healthStatus = 'none'; + displayTimestamp = null; + } - backupStatusByGuest.push({ - guestName: guest.name || `Guest ${guest.vmid}`, - guestId: guest.vmid, - guestType: guest.type === 'qemu' ? 'VM' : 'LXC', - node: guest.node, - guestPveStatus: guest.status, - latestBackupTime: displayTimestamp, - pbsInstanceName: latestSnapshot?.pbsInstanceName || latestTask?.pbsInstanceName || 'N/A', - datastoreName: latestSnapshot?.datastoreName || 'N/A', - totalBackups: totalBackups, - backupHealthStatus: healthStatus - }); - }); + return { + guestName: guest.name || `Guest ${guest.vmid}`, + guestId: guest.vmid, + guestType: guest.type === 'qemu' ? 'VM' : 'LXC', + node: guest.node, + guestPveStatus: guest.status, + latestBackupTime: displayTimestamp, + pbsInstanceName: latestSnapshot?.pbsInstanceName || latestTask?.pbsInstanceName || 'N/A', + datastoreName: latestSnapshot?.datastoreName || 'N/A', + totalBackups: totalBackups, + backupHealthStatus: healthStatus + }; + } + function _filterBackupData(backupStatusByGuest, backupsSearchInput) { const currentBackupsSearchTerm = backupsSearchInput ? backupsSearchInput.value.toLowerCase() : ''; const backupsSearchTerms = currentBackupsSearchTerm.split(',').map(term => term.trim()).filter(term => term); const backupsFilterHealth = PulseApp.state.get('backupsFilterHealth'); const backupsFilterGuestType = PulseApp.state.get('backupsFilterGuestType'); - const filteredBackupStatus = backupStatusByGuest.filter(item => { + return backupStatusByGuest.filter(item => { const healthMatch = (backupsFilterHealth === 'all') || (backupsFilterHealth === 'ok' && (item.backupHealthStatus === 'ok' || item.backupHealthStatus === 'stale')) || (backupsFilterHealth === 'warning' && (item.backupHealthStatus === 'old')) || @@ -173,98 +138,134 @@ PulseApp.ui.backups = (() => { if (!typeMatch) return false; if (backupsSearchTerms.length > 0) { - const nameMatch = backupsSearchTerms.some(term => + return backupsSearchTerms.some(term => (item.guestName?.toLowerCase() || '').includes(term) || (item.node?.toLowerCase() || '').includes(term) || (item.guestId?.toString() || '').includes(term) ); - if (!nameMatch) return false; } - return true; }); + } + + function _renderBackupTableRow(guestStatus) { + const row = document.createElement('tr'); + row.className = `transition-all duration-150 ease-out hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md hover:-translate-y-px ${guestStatus.guestPveStatus === 'stopped' ? 'opacity-60 grayscale' : ''}`; + + const latestBackupFormatted = guestStatus.latestBackupTime + ? PulseApp.utils.formatPbsTimestamp(guestStatus.latestBackupTime) + : 'No backups found'; + + let healthIndicator = ''; + switch (guestStatus.backupHealthStatus) { + case 'ok': healthIndicator = ''; break; + case 'stale': healthIndicator = ''; break; + case 'failed': healthIndicator = ''; break; + case 'old': healthIndicator = ''; break; + case 'none': healthIndicator = '-'; break; + } + + const typeIconClass = guestStatus.guestType === 'VM' + ? 'vm-icon bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 font-medium' + : 'ct-icon bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 px-1.5 py-0.5 font-medium'; + const typeIcon = `${guestStatus.guestType}`; + + row.innerHTML = ` + ${healthIndicator} + ${guestStatus.guestName} + ${guestStatus.guestId} + ${typeIcon} + ${guestStatus.node} + ${latestBackupFormatted} + ${guestStatus.pbsInstanceName} + ${guestStatus.datastoreName} + ${guestStatus.totalBackups} + `; + return row; + } + + function _updateBackupStatusMessages(statusTextElement, visibleCount, backupsSearchInput) { + if (!statusTextElement) return; + + const currentBackupsSearchTerm = backupsSearchInput ? backupsSearchInput.value : ''; + const backupsFilterGuestType = PulseApp.state.get('backupsFilterGuestType'); + const backupsFilterHealth = PulseApp.state.get('backupsFilterHealth'); + + const statusBaseText = `Updated: ${new Date().toLocaleTimeString()}`; + let statusFilterText = currentBackupsSearchTerm ? ` | Filter: "${currentBackupsSearchTerm}"` : ''; + const typeFilterLabel = backupsFilterGuestType !== 'all' ? backupsFilterGuestType.toUpperCase() : ''; + const healthFilterLabel = backupsFilterHealth !== 'all' ? backupsFilterHealth.charAt(0).toUpperCase() + backupsFilterHealth.slice(1) : ''; + const otherFilters = [typeFilterLabel, healthFilterLabel].filter(Boolean).join('/'); + if (otherFilters) { + statusFilterText += ` | ${otherFilters}`; + } + const statusCountText = ` | Showing ${visibleCount} guests`; + statusTextElement.textContent = statusBaseText + statusFilterText + statusCountText; + } + + + function updateBackupsTab() { + const tableContainer = document.getElementById('backups-table-container'); + const tableBody = document.getElementById('backups-overview-tbody'); + const loadingMsg = document.getElementById('backups-loading-message'); + const noDataMsg = document.getElementById('backups-no-data-message'); + const statusTextElement = document.getElementById('backups-status-text'); + + if (!tableContainer || !tableBody || !loadingMsg || !noDataMsg || !statusTextElement) { + console.error("UI elements for Backups tab not found!"); + return; + } + + const { allGuests, initialDataReceived, allRecentBackupTasks, allSnapshots } = _getInitialBackupData(); + + if (!initialDataReceived) { + loadingMsg.classList.remove('hidden'); + tableContainer.classList.add('hidden'); + noDataMsg.classList.add('hidden'); + return; + } + + if (allGuests.length === 0) { + loadingMsg.classList.add('hidden'); + tableContainer.classList.add('hidden'); + noDataMsg.textContent = "No Proxmox guests (VMs/Containers) found."; + noDataMsg.classList.remove('hidden'); + _updateBackupStatusMessages(statusTextElement, 0, backupsSearchInput); + return; + } + loadingMsg.classList.add('hidden'); + + const backupStatusByGuest = allGuests.map(guest => _determineGuestBackupStatus(guest, allSnapshots, allRecentBackupTasks)); + const filteredBackupStatus = _filterBackupData(backupStatusByGuest, backupsSearchInput); const sortStateBackups = PulseApp.state.getSortState('backups'); const sortedBackupStatus = PulseApp.utils.sortData(filteredBackupStatus, sortStateBackups.column, sortStateBackups.direction, 'backups'); tableBody.innerHTML = ''; - let visibleCount = 0; if (sortedBackupStatus.length > 0) { sortedBackupStatus.forEach(guestStatus => { - const row = tableBody.insertRow(); - row.className = `transition-all duration-150 ease-out hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md hover:-translate-y-px ${guestStatus.guestPveStatus === 'stopped' ? 'opacity-60 grayscale' : ''}`; - - const latestBackupFormatted = guestStatus.latestBackupTime - ? PulseApp.utils.formatPbsTimestamp(guestStatus.latestBackupTime) - : 'No backups found'; - - let healthIndicator = ''; - switch (guestStatus.backupHealthStatus) { - case 'ok': - healthIndicator = ''; - break; - case 'stale': - healthIndicator = ''; - break; - case 'failed': - healthIndicator = ''; - break; - case 'old': - healthIndicator = ''; - break; - case 'none': - healthIndicator = '-'; - break; - } - - const typeIconClass = guestStatus.guestType === 'VM' - ? 'vm-icon bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 font-medium' - : 'ct-icon bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 px-1.5 py-0.5 font-medium'; - const typeIcon = `${guestStatus.guestType}`; - - row.innerHTML = ` - ${healthIndicator} - ${guestStatus.guestName} - ${guestStatus.guestId} - ${typeIcon} - ${guestStatus.node} - ${latestBackupFormatted} - ${guestStatus.pbsInstanceName} - ${guestStatus.datastoreName} - ${guestStatus.totalBackups} - `; - visibleCount++; + const row = _renderBackupTableRow(guestStatus); + tableBody.appendChild(row); }); - - loadingMsg.classList.add('hidden'); noDataMsg.classList.add('hidden'); tableContainer.classList.remove('hidden'); } else { - loadingMsg.classList.add('hidden'); tableContainer.classList.add('hidden'); let emptyMessage = "No backup information found for any guests."; - if (backupStatusByGuest.length === 0) { - if (allGuests.length === 0) { - emptyMessage = "No Proxmox guests (VMs/Containers) found."; - } else { - emptyMessage = "No backup information found for any guests."; - } - } - else if (filteredBackupStatus.length === 0) { - const typeFilterText = backupsFilterGuestType === 'all' ? '' : `Type: ${backupsFilterGuestType.toUpperCase()}`; - const filtersApplied = [typeFilterText].filter(Boolean).join(', '); + if (backupStatusByGuest.length > 0 && filteredBackupStatus.length === 0) { // Data exists, but filters hide all + const currentBackupsSearchTerm = backupsSearchInput ? backupsSearchInput.value : ''; + const backupsFilterGuestType = PulseApp.state.get('backupsFilterGuestType'); + const typeFilterText = backupsFilterGuestType === 'all' ? '' : `Type: ${backupsFilterGuestType.toUpperCase()}`; + const filtersApplied = [typeFilterText].filter(Boolean).join(', '); - if (filtersApplied) { - emptyMessage = `No guests found matching the selected filters (${filtersApplied}).`; - } else { - emptyMessage = "No guests with backup information found."; - } - } - if (filteredBackupStatus.length === 0 && backupsSearchTerms.length > 0) { - emptyMessage = `No guests found matching search "${currentBackupsSearchTerm}".`; - if (filtersApplied) { - emptyMessage += ` and filters (${filtersApplied})`; - } + if (currentBackupsSearchTerm) { + emptyMessage = `No guests found matching search "${currentBackupsSearchTerm}"`; + if (filtersApplied) emptyMessage += ` and filters (${filtersApplied})`; + } else if (filtersApplied) { + emptyMessage = `No guests found matching the selected filters (${filtersApplied}).`; + } else { + emptyMessage = "No guests with backup information found matching current filters."; + } } noDataMsg.textContent = emptyMessage; noDataMsg.classList.remove('hidden'); @@ -277,19 +278,7 @@ PulseApp.ui.backups = (() => { } else { console.warn('[Backups] PulseApp.ui.common not available for updateSortUI'); } - - if (statusTextElement) { - const statusBaseText = `Updated: ${new Date().toLocaleTimeString()}`; - let statusFilterText = currentBackupsSearchTerm ? ` | Filter: "${currentBackupsSearchTerm}"` : ''; - const typeFilterLabel = backupsFilterGuestType !== 'all' ? backupsFilterGuestType.toUpperCase() : ''; - const healthFilterLabel = backupsFilterHealth !== 'all' ? backupsFilterHealth.charAt(0).toUpperCase() + backupsFilterHealth.slice(1) : ''; - const otherFilters = [typeFilterLabel, healthFilterLabel].filter(Boolean).join('/'); - if (otherFilters) { - statusFilterText += ` | ${otherFilters}`; - } - let statusCountText = ` | Showing ${visibleCount} guests`; - statusTextElement.textContent = statusBaseText + statusFilterText + statusCountText; - } + _updateBackupStatusMessages(statusTextElement, sortedBackupStatus.length, backupsSearchInput); } function resetBackupsView() { @@ -316,4 +305,4 @@ PulseApp.ui.backups = (() => { updateBackupsTab, resetBackupsView }; -})(); \ No newline at end of file +})();