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