v1.4.0 — Mise à jour firmware hors-ligne par upload ZIP
Nouvelle fonctionnalité permettant de mettre à jour le firmware sans connexion internet, via upload d'un fichier .zip depuis l'interface admin. Fichiers ajoutés: - update_firmware_from_file.sh (rsync + exclusions + chown + restart services) - .update-exclude (liste d'exclusions évolutive, versionnée) - html/.htaccess (limite upload PHP 50MB) Fichiers modifiés: - html/launcher.php (handler upload_firmware) - html/admin.html (UI upload + barre de progression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
124
html/admin.html
124
html/admin.html
@@ -244,6 +244,20 @@
|
||||
<span id="updateSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||
</button>
|
||||
|
||||
<hr class="my-3">
|
||||
<label class="form-label fw-bold">Mise à jour hors-ligne (upload)</label>
|
||||
<div class="input-group mb-2">
|
||||
<input type="file" class="form-control" id="firmwareFileInput" accept=".zip">
|
||||
<button class="btn btn-warning" type="button" onclick="uploadFirmware()" id="uploadBtn">
|
||||
<span id="uploadBtnText">Upload & Install</span>
|
||||
<span id="uploadSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="progress mb-2" id="uploadProgressBar" style="display: none; height: 20px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%" id="uploadProgress">0%</div>
|
||||
</div>
|
||||
<small class="text-muted">Télécharger le .zip depuis Gitea, puis le déposer ici</small>
|
||||
|
||||
<!-- Update Output Console -->
|
||||
<div id="updateOutput" class="mt-3" style="display: none;">
|
||||
<div class="card">
|
||||
@@ -824,6 +838,116 @@ function updateFirmware() {
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFirmware() {
|
||||
const fileInput = document.getElementById('firmwareFileInput');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
showToast('Please select a .zip file first', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate extension
|
||||
if (!file.name.toLowerCase().endsWith('.zip')) {
|
||||
showToast('Only .zip files are allowed', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate size (50MB)
|
||||
if (file.size > 50 * 1024 * 1024) {
|
||||
showToast('File too large (max 50MB)', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Install firmware from "' + file.name + '"?\nThis will update the system files and restart services.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UI elements
|
||||
const uploadBtn = document.getElementById('uploadBtn');
|
||||
const uploadBtnText = document.getElementById('uploadBtnText');
|
||||
const uploadSpinner = document.getElementById('uploadSpinner');
|
||||
const progressBar = document.getElementById('uploadProgressBar');
|
||||
const progress = document.getElementById('uploadProgress');
|
||||
const updateOutput = document.getElementById('updateOutput');
|
||||
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||
|
||||
// Show loading state
|
||||
uploadBtn.disabled = true;
|
||||
uploadBtnText.textContent = 'Uploading...';
|
||||
uploadSpinner.style.display = 'inline-block';
|
||||
progressBar.style.display = 'flex';
|
||||
progress.style.width = '0%';
|
||||
progress.textContent = '0%';
|
||||
updateOutput.style.display = 'block';
|
||||
updateOutputContent.textContent = 'Uploading firmware file...\n';
|
||||
|
||||
// Build FormData
|
||||
const formData = new FormData();
|
||||
formData.append('firmware_file', file);
|
||||
|
||||
// Use XMLHttpRequest for upload progress
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.timeout = 300000; // 5 minutes
|
||||
|
||||
xhr.upload.addEventListener('progress', function(e) {
|
||||
if (e.lengthComputable) {
|
||||
const pct = Math.round((e.loaded / e.total) * 100);
|
||||
progress.style.width = pct + '%';
|
||||
progress.textContent = pct + '%';
|
||||
if (pct >= 100) {
|
||||
uploadBtnText.textContent = 'Installing...';
|
||||
updateOutputContent.textContent = 'Upload complete. Installing firmware...\n';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', function() {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success && response.output) {
|
||||
const formattedOutput = response.output
|
||||
.replace(/✓/g, '<span style="color: #28a745;">✓</span>')
|
||||
.replace(/✗/g, '<span style="color: #dc3545;">✗</span>')
|
||||
.replace(/⚠/g, '<span style="color: #ffc107;">⚠</span>')
|
||||
.replace(/ℹ/g, '<span style="color: #17a2b8;">ℹ</span>');
|
||||
updateOutputContent.innerHTML = formattedOutput;
|
||||
showToast('Firmware updated: ' + (response.old_version || '?') + ' → ' + (response.new_version || '?'), 'success');
|
||||
document.getElementById('reloadBtn').style.display = 'inline-block';
|
||||
} else {
|
||||
updateOutputContent.textContent = 'Error: ' + (response.message || 'Unknown error');
|
||||
showToast('Upload failed: ' + (response.message || 'Unknown error'), 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
updateOutputContent.textContent = 'Error parsing response: ' + xhr.responseText;
|
||||
showToast('Update failed: invalid server response', 'error');
|
||||
}
|
||||
resetUploadUI();
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', function() {
|
||||
updateOutputContent.textContent = 'Network error during upload';
|
||||
showToast('Upload failed: network error', 'error');
|
||||
resetUploadUI();
|
||||
});
|
||||
|
||||
xhr.addEventListener('timeout', function() {
|
||||
updateOutputContent.textContent = 'Upload timed out (5 min limit)';
|
||||
showToast('Upload timed out', 'error');
|
||||
resetUploadUI();
|
||||
});
|
||||
|
||||
function resetUploadUI() {
|
||||
uploadBtn.disabled = false;
|
||||
uploadBtnText.textContent = 'Upload & Install';
|
||||
uploadSpinner.style.display = 'none';
|
||||
progressBar.style.display = 'none';
|
||||
}
|
||||
|
||||
xhr.open('POST', 'launcher.php?type=upload_firmware');
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
function clearUpdateOutput() {
|
||||
const updateOutput = document.getElementById('updateOutput');
|
||||
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||
|
||||
Reference in New Issue
Block a user