|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Mise à jour - ESP32 Webapp</title> |
|
|
<link rel="stylesheet" href="/css/styles.css"> |
|
|
<style> |
|
|
.modal { |
|
|
display: none; |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background-color: rgba(0, 0, 0, 0.5); |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
z-index: 1000; |
|
|
} |
|
|
|
|
|
.modal-content { |
|
|
background: white; |
|
|
border-radius: 8px; |
|
|
padding: 2rem; |
|
|
max-width: 400px; |
|
|
width: 90%; |
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
|
animation: slideIn 0.3s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes slideIn { |
|
|
from { |
|
|
transform: translateY(-50px); |
|
|
opacity: 0; |
|
|
} |
|
|
to { |
|
|
transform: translateY(0); |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
.modal-content h2 { |
|
|
margin-top: 0; |
|
|
color: #333; |
|
|
} |
|
|
|
|
|
.modal-content p { |
|
|
color: #666; |
|
|
margin: 1rem 0; |
|
|
} |
|
|
|
|
|
.modal-buttons { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin-top: 2rem; |
|
|
justify-content: flex-end; |
|
|
} |
|
|
|
|
|
.btn-danger { |
|
|
background-color: #dc3545; |
|
|
} |
|
|
|
|
|
.btn-danger:hover { |
|
|
background-color: #c82333; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<nav class="navbar"> |
|
|
<div class="nav-brand">🔔 ESP32 Webapp</div> |
|
|
<div class="nav-menu"> |
|
|
<a href="/index.html">Tableau de bord</a> |
|
|
<a href="/settings.html">Paramètres</a> |
|
|
<a href="/update.html" class="active">Mise à jour</a> |
|
|
<a href="/logout">Déconnexion</a> |
|
|
</div> |
|
|
</nav> |
|
|
|
|
|
<div class="container"> |
|
|
<h1>Mise à jour OTA</h1> |
|
|
|
|
|
<div class="card warning"> |
|
|
<h3>⚠️ Attention</h3> |
|
|
<ul> |
|
|
<li>Ne débranchez pas l'ESP32 pendant la mise à jour</li> |
|
|
<li>La mise à jour du système de fichiers effacera toutes les données</li> |
|
|
<li>L'ESP32 redémarrera automatiquement après la mise à jour</li> |
|
|
<li>Vous devrez vous reconnecter après le redémarrage</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div id="message" class="message"></div> |
|
|
|
|
|
<div class="card"> |
|
|
<h2>Mise à jour du firmware</h2> |
|
|
<p>Sélectionnez un fichier <strong>.bin</strong> pour mettre à jour le firmware de l'ESP32.</p> |
|
|
|
|
|
<form id="firmwareForm"> |
|
|
<div class="form-group"> |
|
|
<label for="firmwareFile">Fichier firmware (.bin)</label> |
|
|
<input type="file" id="firmwareFile" name="firmwareFile" accept=".bin" required> |
|
|
</div> |
|
|
|
|
|
<button type="submit" class="btn btn-primary">Mettre à jour le firmware</button> |
|
|
</form> |
|
|
|
|
|
<div class="progress-container" id="firmwareProgress" style="display: none;"> |
|
|
<div class="progress-bar"> |
|
|
<div class="progress-fill" id="firmwareProgressFill"></div> |
|
|
</div> |
|
|
<div class="progress-text" id="firmwareProgressText">0%</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="card"> |
|
|
<h2>Mise à jour du système de fichiers</h2> |
|
|
<p>Sélectionnez un fichier <strong>.bin</strong> contenant le système de fichiers LittleFS.</p> |
|
|
|
|
|
<form id="filesystemForm"> |
|
|
<div class="form-group"> |
|
|
<label for="filesystemFile">Fichier système de fichiers (.bin)</label> |
|
|
<input type="file" id="filesystemFile" name="filesystemFile" accept=".bin" required> |
|
|
</div> |
|
|
|
|
|
<button type="submit" class="btn btn-primary">Mettre à jour le système de fichiers</button> |
|
|
</form> |
|
|
|
|
|
<div class="progress-container" id="filesystemProgress" style="display: none;"> |
|
|
<div class="progress-bar"> |
|
|
<div class="progress-fill" id="filesystemProgressFill"></div> |
|
|
</div> |
|
|
<div class="progress-text" id="filesystemProgressText">0%</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Modal de confirmation --> |
|
|
<div id="confirmModal" class="modal" style="display: none;"> |
|
|
<div class="modal-content"> |
|
|
<h2 id="modalTitle">Confirmation</h2> |
|
|
<p id="modalMessage"></p> |
|
|
<div class="modal-buttons"> |
|
|
<button id="modalCancel" class="btn btn-secondary">Annuler</button> |
|
|
<button id="modalConfirm" class="btn btn-danger">Confirmer</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script src="/js/api.js"></script> |
|
|
<script> |
|
|
function showMessage(text, type = 'info') { |
|
|
const messageDiv = document.getElementById('message'); |
|
|
messageDiv.className = `message ${type}`; |
|
|
messageDiv.textContent = text; |
|
|
} |
|
|
|
|
|
function updateProgress(progressId, textId, percent) { |
|
|
document.getElementById(progressId).style.width = percent + '%'; |
|
|
document.getElementById(textId).textContent = percent + '%'; |
|
|
} |
|
|
|
|
|
function showConfirmModal(title, message) { |
|
|
return new Promise((resolve) => { |
|
|
document.getElementById('modalTitle').textContent = title; |
|
|
document.getElementById('modalMessage').textContent = message; |
|
|
document.getElementById('confirmModal').style.display = 'flex'; |
|
|
|
|
|
const confirmBtn = document.getElementById('modalConfirm'); |
|
|
const cancelBtn = document.getElementById('modalCancel'); |
|
|
|
|
|
const handleConfirm = () => { |
|
|
cleanup(); |
|
|
resolve(true); |
|
|
}; |
|
|
|
|
|
const handleCancel = () => { |
|
|
cleanup(); |
|
|
resolve(false); |
|
|
}; |
|
|
|
|
|
const cleanup = () => { |
|
|
document.getElementById('confirmModal').style.display = 'none'; |
|
|
confirmBtn.removeEventListener('click', handleConfirm); |
|
|
cancelBtn.removeEventListener('click', handleCancel); |
|
|
}; |
|
|
|
|
|
confirmBtn.addEventListener('click', handleConfirm); |
|
|
cancelBtn.addEventListener('click', handleCancel); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function uploadFile(file, progressContainerId, progressId, textId) { |
|
|
const formData = new FormData(); |
|
|
formData.append('update', file); |
|
|
|
|
|
document.getElementById(progressContainerId).style.display = 'block'; |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
const xhr = new XMLHttpRequest(); |
|
|
|
|
|
xhr.upload.addEventListener('progress', (e) => { |
|
|
if (e.lengthComputable) { |
|
|
const percent = Math.round((e.loaded / e.total) * 100); |
|
|
updateProgress(progressId, textId, percent); |
|
|
} |
|
|
}); |
|
|
|
|
|
xhr.addEventListener('load', () => { |
|
|
if (xhr.status === 200) { |
|
|
updateProgress(progressId, textId, 100); |
|
|
resolve(xhr.responseText); |
|
|
} else { |
|
|
reject(new Error('Erreur de mise à jour')); |
|
|
} |
|
|
}); |
|
|
|
|
|
xhr.addEventListener('error', () => { |
|
|
// Pendant une mise à jour OTA, la connexion se ferme intentionnellement |
|
|
// Considérer comme un succès si le statut n'a pas d'erreur avant |
|
|
if (xhr.status === 0) { |
|
|
resolve('Connexion fermée (mise à jour en cours)'); |
|
|
} else { |
|
|
reject(new Error('Erreur de connexion')); |
|
|
} |
|
|
}); |
|
|
|
|
|
xhr.addEventListener('abort', () => { |
|
|
resolve('Connexion fermée (mise à jour en cours)'); |
|
|
}); |
|
|
|
|
|
xhr.open('POST', '/api/update'); |
|
|
xhr.send(formData); |
|
|
}); |
|
|
} |
|
|
|
|
|
document.getElementById('firmwareForm').addEventListener('submit', async (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const fileInput = document.getElementById('firmwareFile'); |
|
|
const file = fileInput.files[0]; |
|
|
|
|
|
if (!file) { |
|
|
showMessage('Veuillez sélectionner un fichier', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const confirmed = await showConfirmModal( |
|
|
'Confirmation', |
|
|
'Ne débranchez pas l\'ESP32 pendant la mise à jour. Continuer?' |
|
|
); |
|
|
if (!confirmed) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
showMessage('Mise à jour du firmware en cours...', 'info'); |
|
|
await uploadFile(file, 'firmwareProgress', 'firmwareProgressFill', 'firmwareProgressText'); |
|
|
showMessage('Firmware mis à jour avec succès! L\'ESP32 redémarre...', 'success'); |
|
|
setTimeout(() => { |
|
|
window.location.href = '/logout'; |
|
|
}, 3000); |
|
|
} catch (error) { |
|
|
showMessage('Erreur lors de la mise à jour du firmware', 'error'); |
|
|
document.getElementById('firmwareProgress').style.display = 'none'; |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('filesystemForm').addEventListener('submit', async (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const fileInput = document.getElementById('filesystemFile'); |
|
|
const file = fileInput.files[0]; |
|
|
|
|
|
if (!file) { |
|
|
showMessage('Veuillez sélectionner un fichier', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const confirmed = await showConfirmModal( |
|
|
'Confirmation', |
|
|
'La mise à jour du système de fichiers effacera toutes les données. Continuer?' |
|
|
); |
|
|
if (!confirmed) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
showMessage('Mise à jour du système de fichiers en cours...', 'info'); |
|
|
await uploadFile(file, 'filesystemProgress', 'filesystemProgressFill', 'filesystemProgressText'); |
|
|
showMessage('Système de fichiers mis à jour avec succès! L\'ESP32 redémarre...', 'success'); |
|
|
setTimeout(() => { |
|
|
window.location.href = '/logout'; |
|
|
}, 3000); |
|
|
} catch (error) { |
|
|
showMessage('Erreur lors de la mise à jour du système de fichiers', 'error'); |
|
|
document.getElementById('filesystemProgress').style.display = 'none'; |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html>
|
|
|
|