mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-21 10:43:36 +00:00
Merge pull request #142 from rcourtman/develop
fix: add CSS build step to RC release workflow
This commit is contained in:
@@ -93,19 +93,105 @@ jobs:
|
||||
rcourtman/pulse:v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}
|
||||
rcourtman/pulse:rc
|
||||
|
||||
- name: Set up Node.js
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies and build CSS
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
run: |
|
||||
echo "Installing dependencies for CSS build..."
|
||||
npm install
|
||||
echo "Building CSS with Tailwind..."
|
||||
npm run build:css
|
||||
|
||||
- name: Create Release Archive
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
run: |
|
||||
# Create release tarball
|
||||
tar -czf pulse-v${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}.tar.gz \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.env \
|
||||
--exclude=data \
|
||||
--exclude=*.log \
|
||||
--exclude=temp-release \
|
||||
server src scripts package.json package-lock.json README.md LICENSE CHANGELOG.md \
|
||||
docker-compose.yml Dockerfile
|
||||
echo "📦 Building RC release tarball..."
|
||||
|
||||
# Create staging directory for proper release structure
|
||||
RC_VERSION="${{ steps.check.outputs.base_version }}-rc${{ steps.check.outputs.rc_number }}"
|
||||
RELEASE_DIR_NAME="pulse-v$RC_VERSION"
|
||||
STAGING_PARENT_DIR="pulse-rc-staging"
|
||||
STAGING_FULL_PATH="$STAGING_PARENT_DIR/$RELEASE_DIR_NAME"
|
||||
|
||||
# Cleanup and create staging
|
||||
rm -rf "$STAGING_PARENT_DIR"
|
||||
mkdir -p "$STAGING_FULL_PATH"
|
||||
|
||||
echo "Copying application files to $STAGING_FULL_PATH..."
|
||||
|
||||
# Copy server files (excluding tests) - using rsync for better handling
|
||||
rsync -av --progress server/ "$STAGING_FULL_PATH/server/" --exclude 'tests/' || cp -r server "$STAGING_FULL_PATH/"
|
||||
|
||||
# Copy source files (including built CSS and public assets)
|
||||
mkdir -p "$STAGING_FULL_PATH/src"
|
||||
rsync -av --progress src/public/ "$STAGING_FULL_PATH/src/public/" || cp -r src/public "$STAGING_FULL_PATH/src/"
|
||||
cp src/index.css "$STAGING_FULL_PATH/src/" 2>/dev/null || echo "Warning: src/index.css not found"
|
||||
cp src/tailwind.config.js "$STAGING_FULL_PATH/src/" 2>/dev/null || echo "Warning: src/tailwind.config.js not found"
|
||||
cp src/postcss.config.js "$STAGING_FULL_PATH/src/" 2>/dev/null || echo "Warning: src/postcss.config.js not found"
|
||||
|
||||
# Copy root files
|
||||
cp package.json "$STAGING_FULL_PATH/"
|
||||
cp package-lock.json "$STAGING_FULL_PATH/"
|
||||
[ -f README.md ] && cp README.md "$STAGING_FULL_PATH/"
|
||||
[ -f LICENSE ] && cp LICENSE "$STAGING_FULL_PATH/"
|
||||
[ -f CHANGELOG.md ] && cp CHANGELOG.md "$STAGING_FULL_PATH/"
|
||||
[ -f docker-compose.yml ] && cp docker-compose.yml "$STAGING_FULL_PATH/"
|
||||
[ -f Dockerfile ] && cp Dockerfile "$STAGING_FULL_PATH/"
|
||||
|
||||
# Copy scripts and docs
|
||||
echo "Copying scripts directory..."
|
||||
mkdir -p "$STAGING_FULL_PATH/scripts/"
|
||||
if [ -f "scripts/install-pulse.sh" ]; then
|
||||
cp scripts/install-pulse.sh "$STAGING_FULL_PATH/scripts/"
|
||||
echo "✓ Copied install-pulse.sh"
|
||||
else
|
||||
echo "⚠️ Warning: scripts/install-pulse.sh not found"
|
||||
fi
|
||||
|
||||
if [ -d "docs" ]; then
|
||||
rsync -av --progress docs/ "$STAGING_FULL_PATH/docs/" || cp -r docs "$STAGING_FULL_PATH/"
|
||||
fi
|
||||
|
||||
# Install production dependencies in staging
|
||||
echo "Installing production dependencies..."
|
||||
(cd "$STAGING_FULL_PATH" && npm install --omit=dev --ignore-scripts)
|
||||
|
||||
# Verify essential files
|
||||
echo "Verifying essential files..."
|
||||
if [ ! -f "$STAGING_FULL_PATH/package.json" ]; then
|
||||
echo "Error: Missing package.json"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$STAGING_FULL_PATH/server/index.js" ]; then
|
||||
echo "Error: Missing server/index.js"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$STAGING_FULL_PATH/node_modules" ]; then
|
||||
echo "Error: Missing node_modules"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$STAGING_FULL_PATH/scripts/install-pulse.sh" ]; then
|
||||
echo "Error: Missing scripts/install-pulse.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ All essential files verified"
|
||||
|
||||
# Create tarball with proper directory structure
|
||||
echo "Creating tarball..."
|
||||
(cd "$STAGING_PARENT_DIR" && tar -czf "../pulse-v$RC_VERSION.tar.gz" "$RELEASE_DIR_NAME")
|
||||
|
||||
# Cleanup staging
|
||||
rm -rf "$STAGING_PARENT_DIR"
|
||||
|
||||
# Show tarball info
|
||||
ls -lh "pulse-v$RC_VERSION.tar.gz"
|
||||
echo "✅ RC release tarball created with production dependencies"
|
||||
|
||||
- name: Create RC Release
|
||||
if: steps.check.outputs.create_release == 'true'
|
||||
|
||||
+20
-9
@@ -739,15 +739,25 @@ app.get('/api/version', async (req, res) => {
|
||||
|
||||
let latestVersion = currentVersion;
|
||||
let updateAvailable = false;
|
||||
let releaseUrl = null;
|
||||
|
||||
try {
|
||||
// Try to check for updates, but don't fail if it doesn't work
|
||||
const updateInfo = await updateManager.checkForUpdates();
|
||||
latestVersion = updateInfo.latestVersion || currentVersion;
|
||||
updateAvailable = updateInfo.hasUpdate || 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({
|
||||
@@ -755,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);
|
||||
|
||||
+36
-20
@@ -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');
|
||||
|
||||
+27
-5
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,6 +543,22 @@ PulseApp.ui.settings = (() => {
|
||||
<div id="update-channel-description" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<strong>Stable:</strong> Thoroughly tested releases for production use
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button type="button" onclick="PulseApp.ui.settings.switchToChannelAndUpdate('stable')"
|
||||
class="px-3 py-2 bg-green-600 hover:bg-green-700 text-white text-xs rounded-md flex items-center gap-1">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
Switch to Stable & Update
|
||||
</button>
|
||||
<button type="button" onclick="PulseApp.ui.settings.switchToChannelAndUpdate('rc')"
|
||||
class="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded-md flex items-center gap-1">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
Switch to RC & Update
|
||||
</button>
|
||||
</div>
|
||||
<div id="rc-warning" class="mt-3 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg hidden">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg class="w-4 h-4 text-amber-600 dark:text-amber-400 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
@@ -1348,6 +1364,8 @@ PulseApp.ui.settings = (() => {
|
||||
const updateChannelInfoElement = document.getElementById('update-channel-info');
|
||||
const channelMismatchWarning = document.getElementById('channel-mismatch-warning');
|
||||
|
||||
try {
|
||||
|
||||
if (!latestVersionElement) return;
|
||||
|
||||
try {
|
||||
@@ -1373,13 +1391,38 @@ PulseApp.ui.settings = (() => {
|
||||
} else {
|
||||
// Use the server's update check API with optional channel override
|
||||
const url = channelOverride ? `/api/updates/check?channel=${channelOverride}` : '/api/updates/check';
|
||||
data = await PulseApp.apiClient.get(url);
|
||||
|
||||
// Cache the result
|
||||
updateCache.set(cacheKey, {
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
try {
|
||||
data = await PulseApp.apiClient.get(url);
|
||||
|
||||
// Cache the result
|
||||
updateCache.set(cacheKey, {
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message && (error.message.includes('rate limit') || error.message.includes('rateLimited'))) {
|
||||
// Show user-friendly rate limit message
|
||||
console.warn('[Settings] GitHub API rate limited, showing cached data if available');
|
||||
showMessage('Update check temporarily unavailable due to GitHub rate limits. Please try again later.', 'warning');
|
||||
|
||||
// Use cached data if available, even if expired
|
||||
if (cachedResult) {
|
||||
console.log('[Settings] Using expired cache due to rate limiting');
|
||||
data = cachedResult.data;
|
||||
} else {
|
||||
// No cached data available
|
||||
const versionStatusElement = document.getElementById('version-status');
|
||||
if (versionStatusElement) {
|
||||
versionStatusElement.innerHTML = '<span class="text-amber-600 dark:text-amber-400">⚠️ Update check unavailable - rate limited</span>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Re-throw other errors
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add preview indicator if using channel override
|
||||
@@ -1531,6 +1574,17 @@ PulseApp.ui.settings = (() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Settings] checkLatestVersion failed:', error);
|
||||
if (versionStatusElement) {
|
||||
if (error.message && (error.message.includes('rate limit') || error.message.includes('rateLimited'))) {
|
||||
versionStatusElement.innerHTML = '<span class="text-amber-600 dark:text-amber-400">⚠️ Update check unavailable - rate limited</span>';
|
||||
} else {
|
||||
versionStatusElement.innerHTML = '<span class="text-red-500 dark:text-red-400">Update check failed</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple version comparison (assumes semver format)
|
||||
@@ -1880,6 +1934,27 @@ PulseApp.ui.settings = (() => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the current channel setting matches the preview
|
||||
const currentChannelSelect = document.querySelector('select[name="UPDATE_CHANNEL"]');
|
||||
const selectedChannel = currentChannelSelect ? currentChannelSelect.value : 'stable';
|
||||
|
||||
// Save the current channel setting before applying update if it's different
|
||||
const channelMismatchWarning = document.getElementById('channel-mismatch-warning');
|
||||
if (channelMismatchWarning && !channelMismatchWarning.classList.contains('hidden')) {
|
||||
// User is previewing a different channel, save settings first
|
||||
showMessage('Saving channel preference before applying update...', 'info');
|
||||
|
||||
try {
|
||||
await saveConfiguration();
|
||||
// Wait a moment for settings to be saved
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
} catch (error) {
|
||||
console.error('Failed to save channel setting:', error);
|
||||
showMessage('Failed to save channel setting. Please save settings manually first.', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the tarball asset
|
||||
const tarballAsset = latestReleaseData.assets.find(asset =>
|
||||
asset.name.endsWith('.tar.gz') && asset.name.includes('pulse')
|
||||
@@ -1904,9 +1979,13 @@ PulseApp.ui.settings = (() => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show channel information in confirmation
|
||||
const isRCVersion = latestReleaseData.tag_name.toLowerCase().includes('-rc');
|
||||
const channelInfo = isRCVersion ? ' (RC Channel)' : ' (Stable Channel)';
|
||||
|
||||
// Confirm update
|
||||
PulseApp.ui.toast.confirm(
|
||||
`Update to version ${latestReleaseData.tag_name}? The application will restart automatically after the update is applied.`,
|
||||
`Update to version ${latestReleaseData.tag_name}${channelInfo}? The application will restart automatically after the update is applied.`,
|
||||
async () => {
|
||||
await _performUpdate(latestReleaseData, tarballAsset);
|
||||
}
|
||||
@@ -3227,6 +3306,25 @@ PulseApp.ui.settings = (() => {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Message display helper function
|
||||
function showMessage(message, type = 'info') {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
PulseApp.ui.toast.success(message);
|
||||
break;
|
||||
case 'error':
|
||||
PulseApp.ui.toast.error(message);
|
||||
break;
|
||||
case 'warning':
|
||||
PulseApp.ui.toast.warning(message);
|
||||
break;
|
||||
case 'info':
|
||||
default:
|
||||
PulseApp.ui.toast.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
// Handle update channel selection change
|
||||
function onUpdateChannelChange(value) {
|
||||
@@ -3239,13 +3337,40 @@ PulseApp.ui.settings = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading state during channel switch
|
||||
const versionStatusElement = document.getElementById('version-status');
|
||||
if (versionStatusElement) {
|
||||
versionStatusElement.innerHTML = '<span class="text-gray-500 dark:text-gray-400">Checking for updates...</span>';
|
||||
}
|
||||
|
||||
// Clear any existing update cache for the selected channel to ensure fresh data
|
||||
const cacheKey = value || 'default';
|
||||
updateCache.delete(cacheKey);
|
||||
|
||||
// Re-check for updates with the selected channel (preview mode)
|
||||
// Debounce rapid changes to prevent API spam
|
||||
if (updateCheckTimeout) {
|
||||
clearTimeout(updateCheckTimeout);
|
||||
}
|
||||
updateCheckTimeout = setTimeout(() => {
|
||||
checkLatestVersion(value);
|
||||
updateCheckTimeout = setTimeout(async () => {
|
||||
try {
|
||||
await checkLatestVersion(value);
|
||||
|
||||
// Show message indicating this is a preview
|
||||
if (versionStatusElement && !versionStatusElement.innerHTML.includes('Update available') && !versionStatusElement.innerHTML.includes('Up to date')) {
|
||||
const channelName = value === 'rc' ? 'RC' : 'Stable';
|
||||
versionStatusElement.innerHTML += `<br><small class="text-blue-600 dark:text-blue-400">Preview: ${channelName} channel (save settings to apply)</small>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Settings] Channel switch failed:', error);
|
||||
if (versionStatusElement) {
|
||||
if (error.message && (error.message.includes('rate limit') || error.message.includes('rateLimited'))) {
|
||||
versionStatusElement.innerHTML = '<span class="text-amber-600 dark:text-amber-400">⚠️ Update check unavailable - rate limited</span>';
|
||||
} else {
|
||||
versionStatusElement.innerHTML = '<span class="text-red-500 dark:text-red-400">Update check failed - please try again later</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
@@ -3276,6 +3401,42 @@ PulseApp.ui.settings = (() => {
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Show reminder to save settings
|
||||
showMessage(`Switched to ${targetChannel === 'rc' ? 'RC' : 'Stable'} channel. Remember to save settings to apply the change.`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// Quick switch to channel and apply update if available
|
||||
async function switchToChannelAndUpdate(targetChannel) {
|
||||
try {
|
||||
// Switch channel
|
||||
const channelSelect = document.querySelector('select[name="UPDATE_CHANNEL"]');
|
||||
if (channelSelect) {
|
||||
channelSelect.value = targetChannel;
|
||||
}
|
||||
|
||||
// Save settings immediately
|
||||
showMessage('Switching channel and saving settings...', 'info');
|
||||
await saveConfiguration();
|
||||
|
||||
// Clear cache and check for updates
|
||||
updateCache.clear();
|
||||
await checkLatestVersion();
|
||||
|
||||
// If update is available, offer to apply it immediately
|
||||
const versionStatusElement = document.getElementById('version-status');
|
||||
if (versionStatusElement && versionStatusElement.innerHTML.includes('Update available')) {
|
||||
const channelName = targetChannel === 'rc' ? 'RC' : 'Stable';
|
||||
showMessage(`Switched to ${channelName} channel successfully! An update is available.`, 'success');
|
||||
} else {
|
||||
const channelName = targetChannel === 'rc' ? 'RC' : 'Stable';
|
||||
showMessage(`Switched to ${channelName} channel successfully!`, 'success');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error switching channel:', error);
|
||||
showMessage(`Failed to switch to ${targetChannel} channel: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3769,6 +3930,25 @@ PulseApp.ui.settings = (() => {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showMessage(message, type = 'info') {
|
||||
// Use the proper toast system based on message type
|
||||
switch (type) {
|
||||
case 'success':
|
||||
PulseApp.ui.toast.success(message);
|
||||
break;
|
||||
case 'error':
|
||||
PulseApp.ui.toast.error(message);
|
||||
break;
|
||||
case 'warning':
|
||||
PulseApp.ui.toast.warning(message);
|
||||
break;
|
||||
case 'info':
|
||||
default:
|
||||
PulseApp.ui.toast.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function renderAlertManagementTab() {
|
||||
return `
|
||||
<div class="space-y-6">
|
||||
@@ -3894,6 +4074,7 @@ PulseApp.ui.settings = (() => {
|
||||
onUpdateChannelChange,
|
||||
switchToRecommendedChannel,
|
||||
switchToChannel,
|
||||
switchToChannelAndUpdate,
|
||||
acknowledgeStableChoice,
|
||||
proceedWithStableSwitch,
|
||||
clearUpdateCache,
|
||||
|
||||
Reference in New Issue
Block a user