diff --git a/src/public/js/ui/settings.js b/src/public/js/ui/settings.js index 3d354373d..a41b49ca1 100644 --- a/src/public/js/ui/settings.js +++ b/src/public/js/ui/settings.js @@ -4,6 +4,7 @@ PulseApp.ui.settings = (() => { let currentConfig = {}; let isInitialized = false; let activeTab = 'proxmox'; + let latestReleaseData = null; // Store the latest release data function init() { if (isInitialized) return; @@ -1142,31 +1143,47 @@ PulseApp.ui.settings = (() => { latestVersionElement.textContent = 'Checking...'; latestVersionElement.className = 'font-mono font-semibold text-gray-500 dark:text-gray-400'; - const response = await fetch('https://api.github.com/repos/rcourtman/Pulse/releases/latest'); + // Use the server's update check API to get proper update info + const response = await fetch('/api/updates/check'); const data = await response.json(); - if (response.ok && data.tag_name) { - const latestVersion = data.tag_name.replace(/^v/, ''); // Remove 'v' prefix if present - const currentVersion = currentConfig.version || 'Unknown'; + if (response.ok && data.latestVersion) { + const latestVersion = data.latestVersion; + const currentVersion = data.currentVersion || currentConfig.version || 'Unknown'; latestVersionElement.textContent = latestVersion; - // Compare versions - if (currentVersion !== 'Unknown' && compareVersions(currentVersion, latestVersion) < 0) { + // Update stored config version if server provided it + if (data.currentVersion) { + currentConfig.version = data.currentVersion; + } + + if (data.updateAvailable) { // Update available latestVersionElement.className = 'font-mono font-semibold text-green-600 dark:text-green-400'; versionStatusElement.innerHTML = '📦 Update available!'; + // Convert server response to match GitHub API format for showUpdateDetails + const releaseData = { + tag_name: 'v' + latestVersion, + body: data.releaseNotes, + published_at: data.publishedAt, + html_url: data.releaseUrl, + assets: data.assets, + isDocker: data.isDocker // Store Docker status + }; + // Show update details - showUpdateDetails(data); - } else if (currentVersion !== 'Unknown' && compareVersions(currentVersion, latestVersion) >= 0) { + showUpdateDetails(releaseData); + + // Show Docker warning if applicable + if (data.isDocker) { + versionStatusElement.innerHTML += '
Note: Docker deployments require manual update'; + } + } else { // Up to date latestVersionElement.className = 'font-mono font-semibold text-gray-700 dark:text-gray-300'; versionStatusElement.innerHTML = '✅ Up to date'; - } else { - // Unknown current version - latestVersionElement.className = 'font-mono font-semibold text-gray-700 dark:text-gray-300'; - versionStatusElement.innerHTML = 'Unable to compare versions'; } } else { throw new Error('Failed to fetch release data'); @@ -1200,6 +1217,9 @@ PulseApp.ui.settings = (() => { const updateDetails = document.getElementById('update-details'); if (!updateDetails) return; + // Store the release data for use in applyUpdate + latestReleaseData = releaseData; + const updateVersion = document.getElementById('update-version'); const updateReleaseNotes = document.getElementById('update-release-notes'); const updatePublished = document.getElementById('update-published'); @@ -1236,8 +1256,101 @@ PulseApp.ui.settings = (() => { } async function applyUpdate() { - showMessage('Update functionality not implemented yet', 'info'); - // Implementation would go here + if (!latestReleaseData || !latestReleaseData.assets || latestReleaseData.assets.length === 0) { + showMessage('No update information available. Please check for updates first.', 'error'); + return; + } + + // Find the tarball asset + const tarballAsset = latestReleaseData.assets.find(asset => + asset.name.endsWith('.tar.gz') && asset.name.includes('pulse') + ); + + if (!tarballAsset) { + showMessage('No update package found in the release', 'error'); + return; + } + + // Check if running in Docker + if (latestReleaseData.isDocker) { + showMessage('Automatic updates are not supported in Docker. Please pull the latest Docker image.', 'warning'); + return; + } + + // Confirm update + if (!confirm(`Update to version ${latestReleaseData.tag_name}?\n\nThe application will restart automatically after the update is applied.`)) { + return; + } + + try { + // Hide update details and show progress + const updateDetails = document.getElementById('update-available'); + const updateProgress = document.getElementById('update-progress'); + const progressBar = document.getElementById('update-progress-bar'); + const progressText = document.getElementById('update-progress-text'); + + if (updateDetails) updateDetails.classList.add('hidden'); + if (updateProgress) updateProgress.classList.remove('hidden'); + + // Start the update + const updateResponse = await fetch('/api/updates/apply', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + downloadUrl: tarballAsset.browser_download_url + }) + }); + + if (!updateResponse.ok) { + const error = await updateResponse.json(); + throw new Error(error.error || 'Failed to start update'); + } + + // Listen for progress updates via WebSocket + if (window.socket) { + window.socket.on('updateProgress', (data) => { + if (progressBar) { + progressBar.style.width = `${data.progress}%`; + } + if (progressText) { + const phaseText = { + 'download': 'Downloading update...', + 'backup': 'Backing up current installation...', + 'extract': 'Extracting update files...', + 'apply': 'Applying update...' + }; + progressText.textContent = phaseText[data.phase] || 'Processing...'; + } + }); + + window.socket.on('updateComplete', () => { + if (progressText) { + progressText.textContent = 'Update complete! Restarting...'; + } + showMessage('Update applied successfully. The application will restart momentarily.', 'success'); + }); + + window.socket.on('updateError', (data) => { + showMessage(`Update failed: ${data.error}`, 'error'); + if (updateDetails) updateDetails.classList.remove('hidden'); + if (updateProgress) updateProgress.classList.add('hidden'); + }); + } + + showMessage('Update started. Please wait...', 'info'); + + } catch (error) { + console.error('Error applying update:', error); + showMessage(`Failed to apply update: ${error.message}`, 'error'); + + // Restore UI state + const updateDetails = document.getElementById('update-available'); + const updateProgress = document.getElementById('update-progress'); + if (updateDetails) updateDetails.classList.remove('hidden'); + if (updateProgress) updateProgress.classList.add('hidden'); + } } // Theme management function