Squelette d'application web pour ESP32 avec implémentations de fonctions basiques telles que : -Authentification -Responsive design -Mise à jour OTA -Paramétrage
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

302 lines
11 KiB

<!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>