From 50cd958ec8882f527534e1a8af989154f83f2366 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 13 Jun 2025 18:13:53 +0000 Subject: [PATCH 01/11] chore: bump version to 3.27.0-rc3 for RC release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28772a7d8..7252fffb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse", - "version": "3.27.0-rc2", + "version": "3.27.0-rc3", "description": "A lightweight monitoring application for Proxmox VE.", "main": "server/index.js", "scripts": { From f7d6be8b2406f6d551c1673afb5b61acb70417c1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 13 Jun 2025 18:20:13 +0000 Subject: [PATCH 02/11] chore: bump version to 3.27.0-rc4 for RC release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7252fffb1..00a5cf84e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse", - "version": "3.27.0-rc3", + "version": "3.27.0-rc4", "description": "A lightweight monitoring application for Proxmox VE.", "main": "server/index.js", "scripts": { From ecec704e44145b6a12232089ac975efc65b26951 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 13 Jun 2025 20:19:28 +0100 Subject: [PATCH 03/11] fix: make /api/version endpoint respect channel query parameter - Add support for ?channel=stable or ?channel=rc query parameter to /api/version - This enables frontend channel switching preview functionality - Aligns /api/version behavior with /api/updates/check endpoint - Fixes issue where channel switching in settings UI didn't work properly --- server/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/index.js b/server/index.js index aa048b4f5..1c6caf0ab 100644 --- a/server/index.js +++ b/server/index.js @@ -741,10 +741,13 @@ app.get('/api/version', async (req, res) => { let updateAvailable = false; try { - // Try to check for updates, but don't fail if it doesn't work - const updateInfo = await updateManager.checkForUpdates(); + // Check for channel override from query parameter (for preview functionality) + const channelOverride = req.query.channel; + + // Try to check for updates with optional channel override + const updateInfo = await updateManager.checkForUpdates(channelOverride); latestVersion = updateInfo.latestVersion || currentVersion; - updateAvailable = updateInfo.hasUpdate || false; + updateAvailable = updateInfo.updateAvailable || false; } catch (updateError) { // Log the error but continue with current version info console.error("[Version API] Error checking for updates:", updateError.message); From 4fab5b3cea11bb8b210c86db907a4b91ef1493fd Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 13 Jun 2025 22:34:19 +0100 Subject: [PATCH 04/11] fix: comprehensive update system improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix frontend update functionality with proper channel switching - Add channel-aware version detection in footer - Implement proper development build versioning using git describe - Fix footer spacing issue with update indicators - Add quick channel switch buttons with immediate updates - Prevent update notifications for development builds ahead of releases - Fix release URL linking to correct channel-specific releases - Improve error handling and rate limiting for GitHub API calls The update system now properly: - Detects development builds vs release builds - Shows channel-appropriate updates only - Handles stable/RC channel switching seamlessly - Provides proper version semantics for development environments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- server/index.js | 32 +++--- server/versionUtils.js | 56 ++++++---- src/public/js/main.js | 32 +++++- src/public/js/ui/settings.js | 199 +++++++++++++++++++++++++++++++++-- 4 files changed, 273 insertions(+), 46 deletions(-) diff --git a/server/index.js b/server/index.js index 1c6caf0ab..b370d1896 100644 --- a/server/index.js +++ b/server/index.js @@ -739,18 +739,25 @@ app.get('/api/version', async (req, res) => { let latestVersion = currentVersion; let updateAvailable = false; + let releaseUrl = null; - try { - // Check for channel override from query parameter (for preview functionality) - const channelOverride = req.query.channel; - - // Try to check for updates with optional channel override - const updateInfo = await updateManager.checkForUpdates(channelOverride); - latestVersion = updateInfo.latestVersion || currentVersion; - updateAvailable = updateInfo.updateAvailable || false; - } catch (updateError) { - // Log the error but continue with current version info - console.error("[Version API] Error checking for updates:", updateError.message); + // Check if this is a development build (ahead of all releases) + const isDevelopmentBuild = currentVersion.includes('-dev.') || currentVersion.includes('-dirty'); + + if (!isDevelopmentBuild) { + try { + // Check for channel override from query parameter (for preview functionality) + const channelOverride = req.query.channel; + + // Try to check for updates with optional channel override + const updateInfo = await updateManager.checkForUpdates(channelOverride); + latestVersion = updateInfo.latestVersion || currentVersion; + updateAvailable = updateInfo.updateAvailable || false; + releaseUrl = updateInfo.releaseUrl; + } catch (updateError) { + // Log the error but continue with current version info + console.error("[Version API] Error checking for updates:", updateError.message); + } } res.json({ @@ -758,7 +765,8 @@ app.get('/api/version', async (req, res) => { latestVersion: latestVersion, updateAvailable: updateAvailable, gitBranch: gitBranch, - isDevelopment: isDevelopment + isDevelopment: isDevelopment, + releaseUrl: releaseUrl }); } catch (error) { console.error("[Version API] Error in version endpoint:", error); diff --git a/server/versionUtils.js b/server/versionUtils.js index e6730216c..b784a90f7 100644 --- a/server/versionUtils.js +++ b/server/versionUtils.js @@ -32,32 +32,48 @@ function getCurrentVersionInfo() { if (gitBranch === 'develop') { isDevelopment = true; try { - // Get the latest stable release tag - const latestStableTag = execSync('git tag -l "v*" | grep -v "rc\\\\|alpha\\\\|beta" | sort -V | tail -1', { + // Use git describe for accurate development versioning + const gitDescribe = execSync('git describe --tags --dirty', { cwd: gitDir, - encoding: 'utf8', - shell: '/bin/bash' + encoding: 'utf8' }).trim(); - if (latestStableTag) { - // Remove 'v' prefix to get base version - const baseVersion = latestStableTag.replace(/^v/, ''); + if (gitDescribe) { + // Parse git describe output: v3.23.1-109-g3ee29b2-dirty + const match = gitDescribe.match(/^v([^-]+)(?:-(\d+)-g([a-f0-9]+))?(-dirty)?$/); - // Count commits since the latest stable tag - const commitsSince = execSync(`git rev-list --count ${latestStableTag}..HEAD`, { - cwd: gitDir, - encoding: 'utf8' - }).trim(); - - const commitsCount = parseInt(commitsSince, 10); - - if (commitsCount > 0) { - // Calculate RC version: base version + rc + commit count - currentVersion = `${baseVersion}-rc${commitsCount}`; + if (match) { + const [, baseVersion, commitsAhead, shortHash, isDirty] = match; + + if (commitsAhead && parseInt(commitsAhead) > 0) { + // We're ahead of the latest tag - this is development + const commits = parseInt(commitsAhead); + const dirtyFlag = isDirty ? '-dirty' : ''; + + // For development builds, show as dev version ahead of the base + // Calculate next logical version based on base + const versionParts = baseVersion.split('.'); + const major = parseInt(versionParts[0]) || 0; + const minor = parseInt(versionParts[1]) || 0; + const patch = parseInt(versionParts[2]) || 0; + + // Increment minor version for development + const nextVersion = `${major}.${minor + 1}.0`; + currentVersion = `${nextVersion}-dev.${commits}+${shortHash}${dirtyFlag}`; + } else if (isDirty) { + // On a tag but with uncommitted changes + currentVersion = `${baseVersion}-dirty`; + } else { + // Exactly on a tag + currentVersion = baseVersion; + } } else { - // No commits since stable, use base version - currentVersion = baseVersion; + // Fallback if git describe doesn't match expected pattern + console.warn('[VersionUtils] Could not parse git describe output:', gitDescribe); + currentVersion = gitDescribe.replace(/^v/, ''); } + } else { + throw new Error('git describe returned empty'); } } catch (versionError) { console.log('[VersionUtils] Could not calculate RC version from git, using package.json'); diff --git a/src/public/js/main.js b/src/public/js/main.js index 54fd13b15..b65d47a7d 100644 --- a/src/public/js/main.js +++ b/src/public/js/main.js @@ -160,14 +160,28 @@ document.addEventListener('DOMContentLoaded', function() { return allFound; } - function fetchVersion() { + async function fetchVersion() { const versionSpan = document.getElementById('app-version'); if (!versionSpan) { console.error('Version span element not found'); return; } - fetch('/api/version') + // Get user's channel preference from config + let userChannel = 'stable'; // default + try { + const configResponse = await fetch('/api/config'); + if (configResponse.ok) { + const config = await configResponse.json(); + userChannel = config.advanced?.updateChannel || 'stable'; + } + } catch (error) { + console.warn('Could not load user channel preference, using stable'); + } + + // Check version with user's preferred channel + const versionUrl = userChannel === 'stable' ? '/api/version' : `/api/version?channel=${userChannel}`; + fetch(versionUrl) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); @@ -221,16 +235,24 @@ document.addEventListener('DOMContentLoaded', function() { updateIndicator.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); - window.open('https://github.com/rcourtman/Pulse/releases/latest', '_blank'); + const releaseUrl = data.releaseUrl || 'https://github.com/rcourtman/Pulse/releases/latest'; + window.open(releaseUrl, '_blank'); }); - // Insert after version link - versionSpan.parentNode.insertBefore(updateIndicator, versionSpan.nextSibling); + // Insert after version link with a space + const spacer = document.createTextNode(' '); + versionSpan.parentNode.insertBefore(spacer, versionSpan.nextSibling); + versionSpan.parentNode.insertBefore(updateIndicator, spacer.nextSibling); } } else { // Remove update indicator if no update available const existingIndicator = document.getElementById('update-indicator'); if (existingIndicator) { + // Also remove the spacer that comes before it + const spacer = existingIndicator.previousSibling; + if (spacer && spacer.nodeType === Node.TEXT_NODE && spacer.textContent === ' ') { + spacer.remove(); + } existingIndicator.remove(); } } diff --git a/src/public/js/ui/settings.js b/src/public/js/ui/settings.js index 9b7a01915..e50ccdc86 100644 --- a/src/public/js/ui/settings.js +++ b/src/public/js/ui/settings.js @@ -543,6 +543,22 @@ PulseApp.ui.settings = (() => {
Stable: Thoroughly tested releases for production use
+
+ + +