diff --git a/public/css/styles.css b/public/css/styles.css
index f300848..a22459a 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -625,6 +625,21 @@ header {
font-size: 0.875rem;
}
+/* Testing Controls */
+.testing-controls {
+ opacity: 0.5;
+ font-size: 0.8rem;
+}
+
+.testing-controls:hover {
+ opacity: 1;
+}
+
+.testing-controls a {
+ text-decoration: underline;
+ color: var(--text-color);
+}
+
/* Utilities */
.text-center { text-align: center; }
.text-right { text-align: right; }
diff --git a/public/index.html b/public/index.html
index 72d2893..74aa175 100644
--- a/public/index.html
+++ b/public/index.html
@@ -133,6 +133,10 @@
Checking...
+
+
diff --git a/public/js/system-status.js b/public/js/system-status.js
new file mode 100644
index 0000000..1c881ea
--- /dev/null
+++ b/public/js/system-status.js
@@ -0,0 +1,163 @@
+/**
+ * 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') {
+ versionElement.textContent = data.data.version;
+ uptimeElement.textContent = data.data.uptime;
+
+ // 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');
+ });
+ }
+
+ // Check for updates
+ function checkForUpdates() {
+ updateStatusElement.innerHTML = ' Checking...';
+ updateAvailableDiv.classList.add('d-none');
+
+ // 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';
+
+ fetch(url, {
+ headers: authHeaders()
+ })
+ .then(handleResponse)
+ .then(data => {
+ if (data.status === 'success') {
+ if (data.data.updateAvailable) {
+ updateStatusElement.innerHTML = ' Update available';
+ updateAvailableDiv.classList.remove('d-none');
+ updateAvailableDiv.querySelector('span').textContent =
+ `A new version is available: ${data.data.currentVersion} → ${data.data.remoteVersion}`;
+ } else {
+ updateStatusElement.innerHTML = ' Up to date';
+ }
+ } else {
+ updateStatusElement.innerHTML = ' Check failed';
+ showNotification(data.message || 'Failed to check for updates', 'danger');
+ }
+ })
+ .catch(error => {
+ console.error('Error checking for updates:', error);
+ updateStatusElement.innerHTML = ' Check failed';
+ showNotification('Failed to connect to server', 'danger');
+ });
+ }
+
+ // 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');
+
+ fetch('/api/system/update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...authHeaders()
+ }
+ })
+ .then(handleResponse)
+ .then(data => {
+ if (data.status === 'success') {
+ showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
+ // Set a timer to reload the page after the service has time to restart
+ setTimeout(() => {
+ window.location.reload();
+ }, 30000);
+ } else {
+ updateButton.disabled = false;
+ updateButton.innerHTML = ' Update Now';
+ showNotification(data.message || 'Failed to apply update', 'danger');
+ }
+ })
+ .catch(error => {
+ console.error('Error applying update:', error);
+ updateButton.disabled = false;
+ updateButton.innerHTML = ' Update Now';
+ showNotification('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';
+
+ // Re-check for updates with new setting
+ checkForUpdates();
+
+ showNotification(`Test update button ${newSetting ? 'enabled' : 'disabled'}`, 'info');
+ });
+ }
+
+ // Initialize
+ loadSystemStatus();
+ checkForUpdates();
+
+ // Set interval to refresh uptime every minute
+ setInterval(loadSystemStatus, 60000);
+}
\ No newline at end of file
diff --git a/server-endpoints.js b/server-endpoints.js
index 2a3c422..d2a6d57 100644
--- a/server-endpoints.js
+++ b/server-endpoints.js
@@ -64,7 +64,24 @@ app.get('/api/system/check-updates', async (req, res) => {
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
const currentVersion = packageJson.version;
- // Fetch latest updates without applying them
+ // Check for test mode flag which forces update availability for testing
+ const testMode = req.query.test === 'true';
+
+ if (testMode) {
+ // In test mode, always return that an update is available
+ return res.json({
+ status: 'success',
+ data: {
+ updateAvailable: true,
+ currentVersion,
+ remoteVersion: '2.1.0-test',
+ commitsBehind: 1,
+ testMode: true
+ }
+ });
+ }
+
+ // Normal mode - fetch latest updates without applying them
await execAsync('git fetch');
// Check if we're behind the remote repository