boothifier/data/www/upgrade.html

234 lines
8.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="/css/nav.css">
<title>Firmware Upgrade</title>
<style>
.status-circle {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
}
.connected { background-color: #4CAF50; }
.disconnected { background-color: #f44336; }
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.version-info {
margin: 20px 0;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.progress-box {
height: calc(100vh - 500px);
min-height: 200px;
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
overflow-y: auto;
font-family: monospace;
}
button {
background-color: #2196F3;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="navbar"></div>
<div class="container">
<h1>Firmware Upgrade</h1>
<div>
<span class="status-circle disconnected" id="status-indicator"></span>
<span id="connection-status">Disconnected</span>
</div>
<div class="version-info">
<p>Current Version: <span id="current-version">-</span></p>
<p>Latest Version: <span id="latest-version">-</span></p>
</div>
<div>
<button id="check-upgrades" onclick="checkUpdates()">Check for Upgrades</button>
<button id="start-upgrade" onclick="startUpdate()" disabled>Start upgrade</button>
</div>
<div class="progress-box" id="progress-log"></div>
</div>
<script>
// Load the navigation bar from an external HTML file and insert it into the page
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
// Flag to track if a firmware upgrade is available
let updateAvailable = false;
// Updates the UI to show connection status
// Changes the color and text of the status indicator
function updateStatus(connected) {
const indicator = document.getElementById('status-indicator');
const status = document.getElementById('connection-status');
indicator.className = `status-circle ${connected ? 'connected' : 'disconnected'}`;
status.textContent = connected ? 'Connected' : 'Disconnected';
}
// Adds a message to the progress log box
// Auto-scrolls to the bottom to show latest messages
function log(message) {
const logBox = document.getElementById('progress-log');
logBox.innerHTML += `${message}<br>`;
logBox.scrollTop = logBox.scrollHeight;
}
// Checks for firmware updates by calling the server API
// Updates the UI with version information and enables/disables upgrade button
async function checkUpdates() {
const checkButton = document.getElementById('check-upgrades');
checkButton.disabled = true; // Disable button while checking
try {
const response = await fetch('/upgrade/check');
const data = await response.json();
// Upgrade version information in the UI
document.getElementById('current-version').textContent = data.currentVersion;
document.getElementById('latest-version').textContent = data.latestVersion;
// Enable/disable upgrade button based on availability
updateAvailable = data.updateAvailable;
document.getElementById('start-upgrade').disabled = !updateAvailable;
log(updateAvailable ? 'Upgrade available!' : 'No upgrades available');
} catch (error) {
log('Error checking for upgrades: ' + error);
} finally {
checkButton.disabled = false; // Re-enable button when done
}
}
// Initiates the firmware upgrade process
// Uses Websocket to receive progress updates
async function startUpdate() {
const startButton = document.getElementById('start-upgrade');
const checkButton = document.getElementById('check-upgrades');
let retryCount = 0;
const maxRetries = 3;
// Disable buttons during the update
startButton.disabled = true;
checkButton.disabled = true;
try {
// Start the upgrade process with a timeout
log('Starting update...');
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 10000)
);
const response = await Promise.race([
fetch('/upgrade/start', {
method: 'POST',
credentials: 'same-origin'
}),
timeout
]);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
log('Update initiated successfully.');
// Create SSE connection
const eventSource = new EventSource('/upgrade-progress');
log('Connecting to update server...');
eventSource.onopen = () => {
console.log("EventSource connected");
log('Update connection established.');
};
eventSource.addEventListener('update', (event) => {
try {
console.log("Received message:", event.data);
const data = JSON.parse(event.data);
log(data.message); // Log the update message
// Handle completion
if (data.complete) {
eventSource.close();
log('Upgrade complete! Rebooting...');
//setTimeout(() => window.location.reload(), 5000);
}
} catch (error) {
console.error("Message parsing error:", error);
log(`Error processing update message: ${error.message}`);
}
});
eventSource.onerror = (event) => {
const errorDetails = event.error ? `Error: ${event.error}` : event.status ? `Status: ${event.status}` : 'Unknown error';
log('Connection error occurred' + errorDetails);
if (retryCount++ >= maxRetries || eventSource.readyState === EventSource.CLOSED) {
log('Max retries reached. Please refresh the page to retry.');
eventSource.close();
startButton.disabled = false;
checkButton.disabled = false;
} else {
log(`Attempting to reconnect... (${retryCount}/${maxRetries})`);
}
};
} catch (error) {
console.error("Update error:", error);
log(`Update error: ${error.message}`);
} finally {
startButton.disabled = false;
checkButton.disabled = false;
}
}
// Poll server every 5 seconds to check if device is still connected
// Updates the status indicator accordingly
setInterval(async () => {
try {
const response = await fetch('/api/status');
updateStatus(response.ok);
} catch {
updateStatus(false);
}
}, 5000);
</script>
</body>
</html>