// Bootloader Update Manager - Frontend JavaScript document.addEventListener("DOMContentLoaded", function () { // Elements const uploadForm = document.getElementById("upload-form"); const fileInput = document.getElementById("file-input"); const fileName = document.getElementById("file-name"); const uploadType = document.getElementById("upload-type"); const progressContainer = document.getElementById("progress-container"); const progressFill = document.getElementById("progress-fill"); const progressPercent = document.getElementById("progress-percent"); const progressStatus = document.getElementById("progress-status"); const progressBytes = document.getElementById("progress-bytes"); const resultMessage = document.getElementById("result-message"); const mountStatus = document.getElementById("mount-status"); const extlinuxEditor = document.getElementById("extlinux-editor"); const editorStatus = document.getElementById("editor-status"); const uploadHint = document.getElementById("upload-hint"); // Tab buttons const tabBtns = document.querySelectorAll(".tab-btn"); // Initialize checkMountStatus(); checkTemperature(); checkFileDates(); setInterval(checkMountStatus, 10000); // Check every 10 seconds setInterval(checkTemperature, 5000); // Check temperature every 5 seconds setInterval(checkFileDates, 10000); // Check file dates every 10 seconds // Tab switching tabBtns.forEach((btn) => { btn.addEventListener("click", function () { tabBtns.forEach((b) => b.classList.remove("active")); this.classList.add("active"); uploadType.value = this.dataset.type; hideResult(); // Show/hide U-Boot warning if (this.dataset.type === "uboot_bin") { uploadHint.classList.add("visible"); } else { uploadHint.classList.remove("visible"); } }); }); // File input change fileInput.addEventListener("change", function () { if (this.files && this.files[0]) { fileName.textContent = this.files[0].name; } else { fileName.textContent = ""; } }); // Drag and drop const fileLabel = document.querySelector(".file-label"); ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { fileLabel.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ["dragenter", "dragover"].forEach((eventName) => { fileLabel.addEventListener(eventName, () => { fileLabel.style.borderColor = "#00d9ff"; fileLabel.style.background = "rgba(0,217,255,0.1)"; }); }); ["dragleave", "drop"].forEach((eventName) => { fileLabel.addEventListener(eventName, () => { fileLabel.style.borderColor = ""; fileLabel.style.background = ""; }); }); fileLabel.addEventListener("drop", (e) => { const dt = e.dataTransfer; const files = dt.files; if (files && files[0]) { fileInput.files = files; fileName.textContent = files[0].name; } }); // Form submission uploadForm.addEventListener("submit", async function (e) { e.preventDefault(); const formData = new FormData(this); const file = fileInput.files[0]; if (!file) { showResult("Please select a file", "error"); return; } // Check file size (200MB limit) const MAX_SIZE = 200 * 1024 * 1024; if (file.size > MAX_SIZE) { showResult("File too large. Maximum size is 200MB", "error"); return; } // Reset progress progressContainer.style.display = "block"; progressFill.style.width = "0%"; progressPercent.textContent = "0%"; progressStatus.textContent = "Uploading..."; progressBytes.textContent = ""; hideResult(); // Get current timestamp const timestamp = new Date().toISOString(); try { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener("progress", function (e) { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); progressFill.style.width = percent + "%"; progressPercent.textContent = percent + "%"; progressBytes.textContent = formatBytes(e.loaded) + " / " + formatBytes(e.total); } }); xhr.addEventListener("load", function () { if (xhr.status === 200) { const response = JSON.parse(xhr.responseText); progressStatus.textContent = "Complete!"; showResult( `Successfully uploaded ${response.filename} (${formatBytes(response.size)})`, "success", ); // Refresh file dates after upload checkFileDates(); } else { let errorMsg = "Upload failed"; try { const err = JSON.parse(xhr.responseText); errorMsg = err.error || errorMsg; } catch (e) {} progressStatus.textContent = "Failed"; showResult(errorMsg, "error"); } }); xhr.addEventListener("error", function () { progressStatus.textContent = "Failed"; showResult("Network error", "error"); }); xhr.open( "POST", `/api/upload?timestamp=${encodeURIComponent(timestamp)}`, ); xhr.send(formData); } catch (error) { progressStatus.textContent = "Failed"; showResult("Error: " + error.message, "error"); } }); // extlinux.conf buttons document .getElementById("load-extlinux") .addEventListener("click", loadExtlinux); document .getElementById("save-extlinux") .addEventListener("click", saveExtlinux); document .getElementById("clear-extlinux") .addEventListener("click", function () { extlinuxEditor.value = ""; showEditorStatus("Editor cleared", "success"); }); // Check mount status async function checkMountStatus() { try { const response = await fetch("/api/mount"); const data = await response.json(); const indicator = mountStatus.querySelector(".status-indicator"); const text = mountStatus.querySelector(".status-text"); if (data.mounted) { indicator.className = "status-indicator mounted"; text.textContent = "Mounted"; } else { indicator.className = "status-indicator not-mounted"; text.textContent = "Not Mounted"; } } catch (error) { const indicator = mountStatus.querySelector(".status-indicator"); const text = mountStatus.querySelector(".status-text"); indicator.className = "status-indicator"; text.textContent = "Error checking status"; } } // Load extlinux.conf async function loadExtlinux() { showEditorStatus("Loading...", ""); try { const response = await fetch("/api/extlinux"); const data = await response.json(); if (data.status === "success") { extlinuxEditor.value = data.content; showEditorStatus(`Loaded ${data.size} bytes`, "success"); } else { showEditorStatus(data.error || "Failed to load", "error"); } } catch (error) { showEditorStatus("Error: " + error.message, "error"); } } // Save extlinux.conf async function saveExtlinux() { const content = extlinuxEditor.value; if (!content.trim()) { showEditorStatus("Cannot save empty file", "error"); return; } showEditorStatus("Saving...", ""); // Get current timestamp from browser const timestamp = new Date().toISOString(); try { const response = await fetch( `/api/extlinux?timestamp=${encodeURIComponent(timestamp)}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: content }), }, ); const data = await response.json(); if (data.status === "success") { showEditorStatus("Successfully saved extlinux.conf", "success"); // Refresh file dates after save checkFileDates(); } else { showEditorStatus(data.error || "Failed to save", "error"); } } catch (error) { showEditorStatus("Error: " + error.message, "error"); } } // Reboot button const rebootBtn = document.getElementById("reboot-btn"); const rebootMessage = document.getElementById("reboot-message"); if (rebootBtn) { rebootBtn.addEventListener("click", async function () { // Confirm reboot if (!confirm("Are you sure you want to reboot the system?")) { return; } rebootBtn.disabled = true; rebootMessage.className = "reboot-message"; rebootMessage.style.display = "none"; try { const response = await fetch("/api/reboot", { method: "POST" }); const data = await response.json(); if (data.status === "success") { rebootMessage.textContent = "System rebooting... Connection will be lost."; rebootMessage.className = "reboot-message success"; rebootMessage.style.display = "block"; // Wait a bit then show reconnection message setTimeout(() => { rebootMessage.textContent = "Waiting for device to come back online..."; }, 5000); } else { rebootMessage.textContent = data.error || "Reboot failed"; rebootMessage.className = "reboot-message error"; rebootMessage.style.display = "block"; rebootBtn.disabled = false; } } catch (error) { rebootMessage.textContent = "Error: " + error.message; rebootMessage.className = "reboot-message error"; rebootMessage.style.display = "block"; rebootBtn.disabled = false; } }); } // Helper functions function showResult(message, type) { resultMessage.textContent = message; resultMessage.className = "result-message " + type; } function hideResult() { resultMessage.className = "result-message"; resultMessage.style.display = "none"; } function showEditorStatus(message, type) { editorStatus.textContent = message; editorStatus.className = "editor-status " + type; } function formatBytes(bytes) { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } // Temperature functions const temperatureStatus = document.getElementById("temperature-status"); const tempValue = temperatureStatus ? temperatureStatus.querySelector(".temp-value") : null; async function checkTemperature() { if (!tempValue) return; try { const response = await fetch("/api/temperature"); const data = await response.json(); if (data.success) { tempValue.textContent = data.temperature.toFixed(1); tempValue.style.color = getTempColor(data.temperature); } else { tempValue.textContent = "N/A"; tempValue.style.color = "#888"; } } catch (error) { tempValue.textContent = "Error"; tempValue.style.color = "#ff4757"; } } function getTempColor(temp) { if (temp < 50) return "#00ff88"; // Green - cool if (temp < 70) return "#00d9ff"; // Blue - normal if (temp < 80) return "#ffa500"; // Orange - warm return "#ff4757"; // Red - hot } // File dates functions const fileDates = { "Image.gz": document.getElementById("date-Image.gz"), "sun50i-h618-orangepi-zero2w.dtb": document.getElementById( "date-sun50i-h618-orangepi-zero2w.dtb", ), "uboot.env": document.getElementById("date-uboot.env"), "u-boot-sunxi-with-spl.bin": document.getElementById( "date-u-boot-sunxi-with-spl.bin", ), "extlinux.conf": document.getElementById("date-extlinux.conf"), }; async function checkFileDates() { for (const [filename, element] of Object.entries(fileDates)) { if (!element) continue; try { const response = await fetch( `/api/filedate?file=${encodeURIComponent(filename)}`, ); const data = await response.json(); if (data.status === "success" && data.date) { element.textContent = data.date; element.style.color = "#00ff88"; } else { element.textContent = "Not available"; element.style.color = "#888"; } } catch (error) { element.textContent = "Error"; element.style.color = "#ff4757"; } } } });