/**
* Transmission RSS Manager - System Status Module
* @description Functionality for system status display and updates
*/
// System status and updates functionality
function initSystemStatus() {
// Elements
const versionElement = document.getElementById('system-version');
const uptimeElement = document.getElementById('uptime');
const transmissionStatusElement = document.getElementById('transmission-status');
const updateStatusElement = document.getElementById('update-status');
const updateAvailableDiv = document.getElementById('update-available');
const updateButton = document.getElementById('btn-update-now');
const refreshButton = document.getElementById('btn-refresh-status');
// Load system status
function loadSystemStatus() {
fetch('/api/system/status', {
headers: authHeaders()
})
.then(handleResponse)
.then(data => {
if (data.status === 'success') {
// Update version display
versionElement.textContent = data.data.version;
uptimeElement.textContent = data.data.uptime;
// Also update footer version
const footerVersion = document.getElementById('footer-version');
if (footerVersion) {
footerVersion.textContent = 'v' + data.data.version;
}
// Update version in about modal too if it exists
const aboutVersionElement = document.getElementById('about-version');
if (aboutVersionElement) {
aboutVersionElement.textContent = 'Transmission RSS Manager v' + data.data.version;
}
// Update transmission status with icon
if (data.data.transmissionStatus === 'Connected') {
transmissionStatusElement.innerHTML = ' Connected';
} else {
transmissionStatusElement.innerHTML = ' Disconnected';
}
} else {
showNotification('Failed to load system status', 'danger');
}
})
.catch(error => {
console.error('Error fetching system status:', error);
showNotification('Failed to connect to server', 'danger');
});
}
// More robust update check status tracking
const UPDATE_KEY = 'trm_update_available';
const CURRENT_VERSION_KEY = 'trm_current_version';
const REMOTE_VERSION_KEY = 'trm_remote_version';
let updateCheckInProgress = false;
// Function to show update alert
function showUpdateAlert(currentVersion, remoteVersion) {
// Set status text in the system status panel
updateStatusElement.innerHTML = ' Update available';
// Try to show the original alert box, but don't rely on it
try {
const alertBox = updateAvailableDiv.querySelector('.alert');
if (alertBox) {
alertBox.style.display = 'block';
const spanElement = alertBox.querySelector('span');
if (spanElement) {
spanElement.textContent = `A new version is available: ${currentVersion} → ${remoteVersion}`;
}
}
} catch (e) {
console.error('Error showing original alert box:', e);
}
// Show the floating notification (our new approach)
try {
const floatingNotification = document.getElementById('floating-update-notification');
if (floatingNotification) {
// Set text
const versionElement = document.getElementById('floating-update-version');
if (versionElement) {
versionElement.textContent = `Version ${currentVersion} → ${remoteVersion}`;
}
// Show with inline styles to force display
floatingNotification.style.display = 'block';
// Make absolutely sure it's visible
floatingNotification.setAttribute('style',
'display: block !important; ' +
'visibility: visible !important; ' +
'opacity: 1 !important; ' +
'position: fixed !important; ' +
'top: 20px !important; ' +
'right: 20px !important; ' +
'width: 300px !important; ' +
'padding: 15px !important; ' +
'background-color: #ff5555 !important; ' +
'color: white !important; ' +
'border: 3px solid #cc0000 !important; ' +
'border-radius: 5px !important; ' +
'box-shadow: 0 0 20px rgba(0,0,0,0.5) !important; ' +
'z-index: 10000 !important; ' +
'font-weight: bold !important; ' +
'text-align: center !important;'
);
// Set up update button click handler
const updateButton = document.getElementById('floating-update-button');
if (updateButton) {
// Remove any existing listeners
updateButton.removeEventListener('click', applyUpdate);
// Add new listener
updateButton.addEventListener('click', applyUpdate);
}
console.log('Floating update notification shown:', currentVersion, '->', remoteVersion);
} else {
console.error('Floating notification element not found!');
}
} catch (e) {
console.error('Error showing floating notification:', e);
}
// Store in localStorage
localStorage.setItem(UPDATE_KEY, 'true');
localStorage.setItem(CURRENT_VERSION_KEY, currentVersion);
localStorage.setItem(REMOTE_VERSION_KEY, remoteVersion);
}
// Function to hide update alert
function hideUpdateAlert() {
// Hide original alert
try {
const alertBox = updateAvailableDiv.querySelector('.alert');
if (alertBox) {
alertBox.style.display = 'none';
}
} catch (e) {
console.error('Error hiding original alert:', e);
}
// Hide floating notification
try {
const floatingNotification = document.getElementById('floating-update-notification');
if (floatingNotification) {
floatingNotification.style.display = 'none';
}
} catch (e) {
console.error('Error hiding floating notification:', e);
}
// Clear localStorage
localStorage.removeItem(UPDATE_KEY);
localStorage.removeItem(CURRENT_VERSION_KEY);
localStorage.removeItem(REMOTE_VERSION_KEY);
}
// Check localStorage on init and set up MutationObserver to prevent hiding
(function checkStoredUpdateStatus() {
const isUpdateAvailable = localStorage.getItem(UPDATE_KEY) === 'true';
if (isUpdateAvailable) {
const currentVersion = localStorage.getItem(CURRENT_VERSION_KEY);
const remoteVersion = localStorage.getItem(REMOTE_VERSION_KEY);
if (currentVersion && remoteVersion) {
showUpdateAlert(currentVersion, remoteVersion);
// Set up mutation observer to detect and revert any attempts to hide the update alert
const alertBox = updateAvailableDiv.querySelector('.alert');
if (alertBox) {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'style' ||
mutation.attributeName === 'class')) {
// If display is being changed to hide the element, force it back to visible
if (alertBox.style.display !== 'block' ||
alertBox.classList.contains('d-none') ||
alertBox.style.visibility === 'hidden' ||
alertBox.style.opacity === '0') {
console.log('Detected attempt to hide update button, forcing display');
showUpdateAlert(currentVersion, remoteVersion);
}
}
});
});
// Observe style and class attribute changes
observer.observe(alertBox, {
attributes: true,
attributeFilter: ['style', 'class']
});
// Store observer in window object to prevent garbage collection
window._updateButtonObserver = observer;
}
}
}
})();
// Check for updates
function checkForUpdates() {
updateStatusElement.innerHTML = ' Checking...';
updateCheckInProgress = true;
// Add test=true parameter to force update availability for testing
const testMode = localStorage.getItem('showUpdateButton') === 'true';
const url = testMode ? '/api/system/check-updates?test=true' : '/api/system/check-updates';
// Set a timeout to detect network issues
const timeoutId = setTimeout(() => {
updateStatusElement.innerHTML = ' Check timed out';
updateCheckInProgress = false;
showNotification('Update check timed out. Please try again later.', 'warning');
}, 10000); // 10 second timeout
// Create a timeout controller
const controller = new AbortController();
const timeoutId2 = setTimeout(() => controller.abort(), 15000);
fetch(url, {
headers: authHeaders(),
// Add a fetch timeout using abort controller
signal: controller.signal // 15 second timeout
})
.then(response => {
clearTimeout(timeoutId2);
clearTimeout(timeoutId);
return response;
})
.catch(error => {
clearTimeout(timeoutId2);
clearTimeout(timeoutId);
throw error;
})
.then(response => {
// Better error checking
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || `Server error: ${response.status}`);
}).catch(e => {
if (e instanceof SyntaxError) {
throw new Error(`Server error: ${response.status}`);
}
throw e;
});
}
return response.json();
})
.then(data => {
updateCheckInProgress = false;
if (data.status === 'success') {
if (data.data && data.data.updateAvailable) {
// Show update alert with version info
showUpdateAlert(data.data.currentVersion, data.data.remoteVersion);
// Log to console for debugging
console.log('Update available detected:', data.data.currentVersion, '->', data.data.remoteVersion);
} else {
// No update available
updateStatusElement.innerHTML = ' Up to date';
hideUpdateAlert();
}
} else {
// Error status but with a response
updateStatusElement.innerHTML = ' Check failed';
showNotification(data.message || 'Failed to check for updates', 'danger');
// Don't clear update status on error - keep any previous update notification
}
})
.catch(error => {
updateCheckInProgress = false;
clearTimeout(timeoutId);
console.error('Error checking for updates:', error);
updateStatusElement.innerHTML = ' Check failed';
// More specific error message based on the error type
if (error.name === 'AbortError') {
showNotification('Update check timed out. Please try again later.', 'warning');
} else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
showNotification('Network error. Please check your connection and try again.', 'danger');
} else {
showNotification(error.message || 'Failed to connect to server', 'danger');
}
// Don't clear update status on error - keep any previous update notification
});
}
// Apply update
function applyUpdate() {
// Show confirmation dialog
if (!confirm('Are you sure you want to update the application? The service will restart.')) {
return;
}
// Show loading state
updateButton.disabled = true;
updateButton.innerHTML = ' Updating...';
showNotification('Applying update. Please wait...', 'info');
// Set a timeout for the update process
const updateTimeoutId = setTimeout(() => {
updateButton.disabled = false;
updateButton.innerHTML = ' Update Now';
showNotification('Update process timed out. Please try again or check server logs.', 'warning');
}, 60000); // 60 second timeout for the entire update process
// Create a timeout controller
const updateController = new AbortController();
const updateTimeoutId2 = setTimeout(() => updateController.abort(), 45000);
fetch('/api/system/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders()
},
signal: updateController.signal // 45 second timeout
})
.then(response => {
clearTimeout(updateTimeoutId2);
return response;
})
.catch(error => {
clearTimeout(updateTimeoutId2);
throw error;
})
.then(response => {
// Better error checking
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || `Server error: ${response.status}`);
}).catch(e => {
if (e instanceof SyntaxError) {
throw new Error(`Server error: ${response.status}`);
}
throw e;
});
}
return response.json();
})
.then(data => {
clearTimeout(updateTimeoutId);
if (data.status === 'success') {
showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
// Show a countdown to reload
let secondsLeft = 30;
updateButton.innerHTML = ` Reloading in ${secondsLeft}s...`;
const countdownInterval = setInterval(() => {
secondsLeft--;
updateButton.innerHTML = ` Reloading in ${secondsLeft}s...`;
if (secondsLeft <= 0) {
clearInterval(countdownInterval);
window.location.reload();
}
}, 1000);
// Set a timer to reload the page after the service has time to restart
setTimeout(() => {
clearInterval(countdownInterval);
window.location.reload();
}, 30000);
} else {
updateButton.disabled = false;
updateButton.innerHTML = ' Update Now';
showNotification(data.message || 'Failed to apply update', 'danger');
}
})
.catch(error => {
clearTimeout(updateTimeoutId);
console.error('Error applying update:', error);
updateButton.disabled = false;
updateButton.innerHTML = ' Update Now';
// More specific error message based on the error type
if (error.name === 'AbortError') {
showNotification('Update request timed out. The server might still be processing the update.', 'warning');
} else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
showNotification('Network error. Please check your connection and try again.', 'danger');
} else {
showNotification(error.message || 'Failed to connect to server', 'danger');
}
});
}
// Event listeners
if (refreshButton) {
refreshButton.addEventListener('click', () => {
loadSystemStatus();
checkForUpdates();
});
}
if (updateButton) {
updateButton.addEventListener('click', applyUpdate);
}
// Test mode toggle (for developers)
const testToggle = document.getElementById('toggle-test-update-button');
if (testToggle) {
// Initialize based on current localStorage setting
const isTestMode = localStorage.getItem('showUpdateButton') === 'true';
// Update toggle text
testToggle.innerText = isTestMode ? 'Disable Test Update' : 'Enable Test Update';
// Add click handler
testToggle.addEventListener('click', (e) => {
e.preventDefault();
const currentSetting = localStorage.getItem('showUpdateButton') === 'true';
const newSetting = !currentSetting;
localStorage.setItem('showUpdateButton', newSetting);
testToggle.innerText = newSetting ? 'Disable Test Update' : 'Enable Test Update';
if (newSetting) {
// If enabling test mode, force show update button
showUpdateAlert('2.0.10', '2.1.0-test');
updateStatusElement.innerHTML = ' Update available';
showNotification('Test update button enabled', 'info');
} else {
// If disabling test mode, check for real updates
hideUpdateAlert();
checkForUpdates();
showNotification('Test update button disabled', 'info');
}
});
}
// Persistent update button - force display every second if update is available
function forceShowUpdateButton() {
const isUpdateAvailable = localStorage.getItem(UPDATE_KEY) === 'true';
if (isUpdateAvailable) {
const currentVersion = localStorage.getItem(CURRENT_VERSION_KEY);
const remoteVersion = localStorage.getItem(REMOTE_VERSION_KEY);
if (currentVersion && remoteVersion) {
// Check floating notification
const floatingNotification = document.getElementById('floating-update-notification');
if (floatingNotification && floatingNotification.style.display !== 'block') {
console.log('Forcing floating update notification display');
// Set the version text
const versionElement = document.getElementById('floating-update-version');
if (versionElement) {
versionElement.textContent = `Version ${currentVersion} → ${remoteVersion}`;
}
// Apply strong styling
floatingNotification.setAttribute('style',
'display: block !important; ' +
'visibility: visible !important; ' +
'opacity: 1 !important; ' +
'position: fixed !important; ' +
'top: 20px !important; ' +
'right: 20px !important; ' +
'width: 300px !important; ' +
'padding: 15px !important; ' +
'background-color: #ff5555 !important; ' +
'color: white !important; ' +
'border: 3px solid #cc0000 !important; ' +
'border-radius: 5px !important; ' +
'box-shadow: 0 0 20px rgba(0,0,0,0.5) !important; ' +
'z-index: 10000 !important; ' +
'font-weight: bold !important; ' +
'text-align: center !important;'
);
// Ensure button has correct event handler
const updateButton = document.getElementById('floating-update-button');
if (updateButton) {
// Remove any existing listeners
updateButton.removeEventListener('click', applyUpdate);
// Add new listener
updateButton.addEventListener('click', applyUpdate);
}
}
// Still try the original alert as a fallback
try {
const alertBox = updateAvailableDiv.querySelector('.alert');
if (alertBox && alertBox.style.display !== 'block') {
alertBox.style.display = 'block';
updateAvailableDiv.style.display = 'block';
// Update message
const spanElement = alertBox.querySelector('span');
if (spanElement) {
spanElement.textContent = `A new version is available: ${currentVersion} → ${remoteVersion}`;
}
}
} catch (e) {
console.error('Error forcing original update button:', e);
}
}
}
}
// Initialize
loadSystemStatus();
checkForUpdates();
// Force check the update button status every second
setInterval(forceShowUpdateButton, 1000);
// Set interval to refresh uptime every minute
setInterval(loadSystemStatus, 60000);
}