v1.8.2: Pre-flight check sudoers avec instructions de fix dans l'UI

Sur les anciens capteurs sans regle sudoers NOPASSWD pour
/var/www/nebuleair_pro_4g/*, l'update echouait avec un message
sudo cryptique.

Nouveau:
- preflight_sudo_check() en PHP teste 'sudo -n -l <script>' avant
  de lancer l'update (online ou offline)
- Si KO: la route retourne error_type=sudoers_missing avec un
  message clair et la sortie technique de sudo
- L'UI affiche une alerte warning structuree avec etapes numerotees,
  contenu du fichier /etc/sudoers.d/nebuleair pret a coller, et un
  bouton 'Copier le contenu' (presse-papier)
- Echec immediat (<1s) au lieu d'attendre le timeout du script

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-05-12 18:19:17 +02:00
parent 11ac2b184a
commit 54283b8e3a
4 changed files with 140 additions and 5 deletions

View File

@@ -926,7 +926,11 @@ function updateFirmware() {
success: function(response) {
if (!response.success) {
clearInterval(timerInterval);
showUpdateError('Impossible de lancer la mise à jour: ' + (response.error || 'erreur inconnue'));
if (response.error_type === 'sudoers_missing') {
showSudoersMissingError(response);
} else {
showUpdateError('Impossible de lancer la mise à jour: ' + (response.message || response.error || 'erreur inconnue'));
}
resetUpdateButton();
return;
}
@@ -1088,6 +1092,67 @@ function showUpdateError(message) {
finalStatus.style.display = 'block';
}
// Specific error display for the "sudoers missing" pre-flight failure.
// Shows a clear explanation and the exact SSH commands to apply the fix,
// with a copy-to-clipboard button so the user can paste it on the sensor.
function showSudoersMissingError(response) {
const finalStatus = document.getElementById('updateFinalStatus');
const fixCommand = `sudo nano /etc/sudoers.d/nebuleair
# Then paste the content shown below, save (Ctrl+O, Enter, Ctrl+X), then:
sudo chmod 0440 /etc/sudoers.d/nebuleair
sudo visudo -c`;
const sudoersContent = `# NebuleAir Pro 4G sudo rules
ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
www-data ALL=(ALL) NOPASSWD: /bin/systemctl *
www-data ALL=(ALL) NOPASSWD: /usr/bin/pkill *
www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*`;
finalStatus.className = 'alert alert-warning mb-3';
finalStatus.innerHTML = `
<h5 class="alert-heading"><i class="bi bi-exclamation-triangle-fill"></i> Configuration sudoers manquante</h5>
<p class="mb-2">${escapeHtml(response.message || '')}</p>
<p class="mb-1"><strong>Fix (SSH sur le capteur) :</strong></p>
<ol class="mb-2">
<li>Connectez-vous en SSH au capteur</li>
<li>Exécutez <code>sudo nano /etc/sudoers.d/nebuleair</code></li>
<li>Collez le contenu ci-dessous puis sauvez (<kbd>Ctrl+O</kbd>, <kbd>Enter</kbd>, <kbd>Ctrl+X</kbd>)</li>
<li>Exécutez <code>sudo chmod 0440 /etc/sudoers.d/nebuleair && sudo visudo -c</code></li>
<li>Relancez la mise à jour ici</li>
</ol>
<div class="position-relative">
<pre class="bg-light p-2 rounded mb-1" style="font-size: 0.8rem; max-height: 200px; overflow-y: auto;"><code id="sudoersFixContent">${escapeHtml(sudoersContent)}</code></pre>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="copySudoersFix()">
<i class="bi bi-clipboard"></i> Copier le contenu
</button>
</div>
${response.raw ? '<details class="mt-2"><summary class="small text-muted">Sortie technique sudo</summary><pre class="small mt-1 mb-0">' + escapeHtml(response.raw) + '</pre></details>' : ''}
`;
finalStatus.style.display = 'block';
}
function copySudoersFix() {
const content = document.getElementById('sudoersFixContent').textContent;
navigator.clipboard.writeText(content).then(() => {
showToast('Contenu copié dans le presse-papier', 'success');
}).catch(() => {
showToast('Échec de la copie — sélectionnez et copiez manuellement', 'warning');
});
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function resetUpdateButton() {
// Online update button
const updateBtn = document.getElementById('updateBtn');
@@ -1190,9 +1255,13 @@ function uploadFirmware() {
}
if (!response.success) {
finalStatus.className = 'alert alert-danger mb-3';
finalStatus.textContent = 'Erreur: ' + (response.message || 'Erreur inconnue');
finalStatus.style.display = 'block';
if (response.error_type === 'sudoers_missing') {
showSudoersMissingError(response);
} else {
finalStatus.className = 'alert alert-danger mb-3';
finalStatus.textContent = 'Erreur: ' + (response.message || 'Erreur inconnue');
finalStatus.style.display = 'block';
}
resetUploadUI();
showToast('Upload failed: ' + (response.message || 'Unknown error'), 'error');
return;