import "./style.css"; import { initMenus } from "./menu"; let isConsoleExpanded = false; let isAutoScroll = true; let consoleContentList = []; let consoleContent; let isFileSystemExpanded = true; let isFirmwareUpdateExpanded = false; let consolePanel; let fileSystem; let firmware; let consoleHeader; let consoleBody; let fileSystemHeader; let fileSystemBody; let filecontentFooter; let firmwareHeader; let firmwareBody; let filesInput; let fwInput; let filesButton; let firmwareButton; let refreshButton; let createDirButton; let message; let messageLimited; let websocketStarted = false; let wsSource; let wsMsg = ""; let logOff = false; let pageId = ""; let currentPath = "/"; const version = "3.0.0.a5"; let xmlhttpupload; let prgfiletext; let prgfile; let uploadType = 0; let restartTime; let loginLink; let loginModal; let loginUser = ""; let loginMsg; let fspath="/files"; let hostpath="/"; window.onload = function () { consolePanel = document.getElementById("consolePanel"); fileSystem = document.getElementById("fileSystem"); firmware = document.getElementById("firmware"); consoleHeader = document.getElementById("consoleHeader"); consoleBody = document.getElementById("consoleBody"); fileSystemHeader = document.getElementById("fileSystemHeader"); fileSystemBody = document.getElementById("fileSystemBody"); filecontentFooter = document.getElementById("filecontentFooter"); firmwareHeader = document.getElementById("firmwareHeader"); firmwareBody = document.getElementById("firmwareBody"); consoleContent = document.getElementById("consoleContent"); fwInput = document.getElementById("filefw"); filesInput = document.getElementById("files"); message = document.getElementById("MSG"); messageLimited = document.getElementById("MSGLimited"); prgfiletext = document.getElementById("prgfiletext"); prgfile = document.getElementById("prgfile"); document.getElementById("cmdBtn").addEventListener("click", function () { let input = document.getElementById("customCmdTxt"); if (input.value.trim().length == 0) return; let url = new URL("http://" + window.location.host + "/command"); url.searchParams.append("cmd", input.value.trim()); httpGet(url, processCmdJson); consoleContentUpdate( "" + input.value.trim() + "\n" ); input.value = ""; }); document .getElementById("loginInput") .addEventListener("keydown", function (event) { if (event.keyCode === 13) { document.getElementById("passwordInput").focus(); } }); document .getElementById("passwordInput") .addEventListener("keydown", function (event) { if (event.keyCode === 13) { document.getElementById("loginbutton").click(); } }); document .getElementById("customCmdTxt") .addEventListener("keyup", function (event) { if (event.keyCode === 13) { document.getElementById("cmdBtn").click(); } }); consoleHeader.addEventListener("click", function () { isConsoleExpanded = !isConsoleExpanded; if (isConsoleExpanded) consoleBody.style.display = "block"; else consoleBody.style.display = "none"; }); loginMsg = document.getElementById("loginMsg"); loginLink = document.getElementById("loginLink"); loginModal = document.getElementById("loginpage"); loginLink.addEventListener("click", function () { loginModal.classList.remove("hide"); }); document .getElementById("disconnectbutton") .addEventListener("click", function () { document.getElementById("loginInput").value = ""; document.getElementById("loginbutton").click(); }); document.getElementById("loginbutton").addEventListener("click", function () { loginModal.classList.add("hide"); let user = document.getElementById("loginInput").value.trim(); loginUser = user; let password = document.getElementById("passwordInput").value.trim(); let url = new URL("http://" + window.location.host + "/login"); url.searchParams.append("USER", user); url.searchParams.append("PASSWORD", password); url.searchParams.append("SUBMIT", "yes"); let xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4) { if (xmlhttp.status != 200) { if (xmlhttp.status == 401) { handle401(); } else { console.log(xmlhttp.status); ErrorMSG( "Error: board does not answered properly " + xmlhttp.status ); } } else { getFWData(); } } }; xmlhttp.open("GET", url, true); xmlhttp.send(); document.getElementById("passwordInput").value = ""; }); refreshButton = document.getElementById("refresh"); refreshButton.addEventListener("click", function () { SendFileCommand("list", "all"); }); firmwareButton = document.getElementById("uploapFW"); firmwareButton.addEventListener("click", function () { prgupdatetext.innerHTML = ""; fwInput.click(); }); filesButton = document.getElementById("uploadFiles"); filesButton.innerHTML = fileIcon(true); filesButton.addEventListener("click", function () { filesInput.click(); }); fwInput.addEventListener("change", function () { if (fwInput.value) { if (!confirm("Confirm Firmware Update ?")) { console.log("abort"); fwInput.value = ""; return; } uploadFirmware(); } }); filesInput.addEventListener("change", function () { console.log("upload"); if (filesInput.value) { uploadFiles(); } }); document .getElementById("monitor_enable_autoscroll") .addEventListener("click", function () { if (document.getElementById("monitor_enable_autoscroll").checked) { isAutoScroll = true; autoscroll(); } else { isAutoScroll = false; } }); consoleBody.style.display = "none"; fileSystemHeader.addEventListener("click", function () { isFileSystemExpanded = !isFileSystemExpanded; if (isFileSystemExpanded) { fileSystemBody.style.display = "block"; fileSystemBody.scrollIntoView(); } else { fileSystemBody.style.display = "none"; } }); firmwareHeader.addEventListener("click", function () { isFirmwareUpdateExpanded = !isFirmwareUpdateExpanded; if (isFirmwareUpdateExpanded) { firmwareBody.style.display = "block"; firmwareBody.scrollIntoView(); } else firmwareBody.style.display = "none"; }); firmwareBody.style.display = "none"; createDirButton = document.getElementById("createdir"); createDirButton.innerHTML = dirIcon(true); createDirButton.addEventListener("click", function () { let filename = prompt("Directory name", ""); if (filename != null && filename.trim().length > 0) { SendFileCommand("createdir", filename.trim()); } }); getFWData(); initMenus(); }; function handle401() { loginModal.classList.remove("hide"); loginLink.classList.remove("hide"); if (loginUser.length > 0) { loginMsg.classList.remove("hide"); } else { loginMsg.classList.add("hide"); } firmware.classList.add("hide"); fileSystem.classList.add("hide"); consolePanel.classList.add("hide"); } function padNumber(num, size) { let s = num.toString().padStart(size, "0"); return s; } function getPCTime() { let d = new Date(); return ( d.getFullYear() + "-" + padNumber(d.getMonth() + 1, 2) + "-" + padNumber(d.getDate(), 2) + "-" + padNumber(d.getHours(), 2) + "-" + padNumber(d.getMinutes(), 2) + "-" + padNumber(d.getSeconds(), 2) ); } function isLimitedEnvironment(wifiMode) { let sitesList = [ "clients3.google.com", //Android Captive Portal Detection "connectivitycheck.", //Apple iPhone, iPad with iOS 6 Captive Portal Detection "apple.com", ".akamaitechnologies.com", //Apple iPhone, iPad with iOS 7, 8, 9 and recent versions of OS X "www.appleiphonecell.com", "www.itools.info", "www.ibook.info", "www.airport.us", "www.thinkdifferent.us", ".akamaiedge.net", //Windows ".msftncsi.com", "microsoft.com", ]; if (wifiMode != "AP") return false; for (let i = 0; i < sitesList.length; i++) { if (document.location.host.indexOf(sitesList[i]) != -1) return true; } return false; } function autoscroll() { if (isAutoScroll) consoleContent.scrollTop = consoleContent.scrollHeight; } function consoleContentUpdate(message) { if (message) { consoleContentList = consoleContentList.concat(message); consoleContentList = consoleContentList.slice(-300); let output = ""; for (let i = 0; i < consoleContentList.length; i++) { output += consoleContentList[i]; } document.getElementById("consoleContent").innerHTML = output; autoscroll(); } } function processCmdJson(text) { let json; try { json = JSON.parse(text); consoleContentUpdate(JSON.stringify(json, null, " ") + "\n"); } catch (e) { consoleContentUpdate(text + "\n"); } } function processFWJson(text) { let json; try { json = JSON.parse(text); } catch (e) { ErrorMSG("Error: json data are incorrect!
" + text); consoleContentUpdate(text); return; } if (!json.status || !json.cmd || !json.data) { ErrorMSG("Wrong version of webUI"); consoleContentUpdate(text); return; } json = json.data; if (json.FWVersion) { let verLink = document.getElementById("verLink"); verLink.innerHTML = "v" + json.FWVersion; verLink.addEventListener("click", function () { let url = new URL("http://" + window.location.host + "/config"); window.open(url, "_blank"); }); } else { ErrorMSG( "Error: json data are incorrect!
" + JSON.stringify(json, null, "
") ); return; } document.getElementById("espLink").addEventListener("click", function () { let url = new URL("http://" + window.location.host); window.open(url); }); consolePanel.classList.remove("hide"); if ((json.FlashFileSystem && json.FlashFileSystem != "none")||(json.SDConnection && json.SDConnection != "none")){ fileSystem.classList.remove("hide"); if (json.FlashFileSystem && json.FlashFileSystem == "none"){ fspath="/sdfiles"; } } if (json.WebUpdate == "Enabled") firmware.classList.remove("hide"); hostpath = json.HostPath; if (json.WiFiMode && json.WebSocketIP) { if (isLimitedEnvironment(json.WiFiMode)) { let address = "http://" + json.WebSocketIP + (window.location.port == 80 ? "" : ":" + window.location.port); InfoMSGLimited( "It seems you are in limited environment,
please open a browser using
" + address + "
to get all features working" ); } } if (json.Hostname) document.title = json.Hostname; startSocket(json.WebSocketIP, json.WebSocketPort, json.WebCommunication, json.WebSocketSubProtocol); SendFileCommand("list", "all"); } function startSocket(ip, port, sync, protocol) { if (websocketStarted) { wsSource.close(); } wsSource = new WebSocket( "ws://" + ip + ":" + port + (sync == "Asynchronous" ? "/ws" : ""), [protocol] ); wsSource.binaryType = "arraybuffer"; wsSource.onopen = function (e) { websocketStarted = true; }; wsSource.onclose = function (e) { websocketStarted = false; //seems sometimes it disconnect so wait 3s and reconnect //if it is not a log off if (!logOff) setTimeout(() => { startSocket(ip, port, sync, protocol); }, 3000); }; wsSource.onerror = function (e) { ErrorMSG("Error: websocket error!
" ); console.log("Error: websocket error!"); }; wsSource.onmessage = function (e) { let msg = ""; //bin if (e.data instanceof ArrayBuffer) { let bytes = new Uint8Array(e.data); for (let i = 0; i < bytes.length; i++) { msg += String.fromCharCode(bytes[i]); if (bytes[i] == 10 || bytes[i] == 13) { wsMsg += msg; consoleContentUpdate(wsMsg); wsMsg = ""; msg = ""; } } wsMsg += msg; } else { msg = e.data; let tval = msg.split(":"); if (tval.length >= 2) { if (tval[0] == "currentID") { pageId = tval[1]; } if (tval[0] == "activeID") { if (pageId != tval[1]) { logOff = true; wsSource.close(); InfoMSG( "

It seems you are connect from another location, you are now disconnected." ); document.title = document.title + "(disconnected)"; consolePanel.classList.add("hide"); firmware.classList.add("hide"); fileSystem.classList.add("hide"); document.getElementById("verLink").classList.add("disabled"); loginModal.classList.add("hide"); loginUser = ""; document.getElementById("loginInput").value = ""; document.getElementById("passwordInput").value = ""; document.cookie = ""; } } if (tval[0] == "ERROR") { xmlhttpupload.abort(); uploadError(tval[2], tval[1]); } } } }; } function uploadError(msg, nb) { let status = "Upload failed"; if (nb != 0 && typeof nb != "undefined") { status = msg + "(" + nb + ")"; } alert(status + "!"); if (uploadType == 1) { let currentstatus = filecontentFooter.innerHTML; if (currentstatus.length > 0) { filecontentFooter.innerHTML = "Status: " + status + "  " + currentstatus.substring(currentstatus.indexOf("|")); } else { filecontentFooter.innerHTML = status; } } else if (uploadType == 2) { prgupdatetext.innerHTML = status; } //location.reload(); } function ErrorMSG(msg) { message.innerHTML = msg; message.className = "text-error"; } function InfoMSGLimited(msg) { messageLimited.innerHTML = msg; messageLimited.className = "text-error"; } function InfoMSG(msg) { message.innerHTML = msg; message.className = "text-primary"; } function getFWData() { let url = new URL("http://" + window.location.host + "/command"); url.searchParams.append("cmd", "[ESP800]json=YES version="+version+" time=" + getPCTime()); httpGet(url, processFWJson); } function SendFileCommand(action, filename) { let url = new URL("http://" + window.location.host + fspath); url.searchParams.append("action", action); url.searchParams.append("filename", filename); url.searchParams.append("path", currentPath); httpGet(url, dispatchFileStatus); } function trashIcon() { return ""; } function backIcon() { return ""; } function fileIcon(plus = false) { return ( "" + (plus ? "" : "") + "" ); } function dirIcon(plus = false) { return ( "" + (plus ? "" : "") + "" ); } function dispatchFileStatus(jsonresponse) { let json; let eventslisteners = []; let showESP3Dbutton = false; try { json = JSON.parse(jsonresponse); //ugly but code is smaller filecontentFooter.innerHTML = "Status: " + json.status + "  |  Total space: " + json.total + "  |  Used space: " + json.used + "  |  Occupation: " + " " + json.occupation + "%"; json.files.sort(function (a, b) { return compareStrings(a.name, b.name); }); let content = ""; document.getElementById("path").innerHTML = currentPath; if (currentPath != "/") { let pos = currentPath.lastIndexOf("/"); let newPath; if (pos == 0) newPath = "/"; else newPath = currentPath.substring(0, pos); console.log("newpath:" + newPath); content += "
" + "
" + "
" + backIcon() + "
Up..
"; eventslisteners.push({ action: "updir", id: "updir", target: newPath }); } for (let i1 = 0; i1 < json.files.length; i1++) { if (String(json.files[i1].size) == "-1") { content += "
" + "
" + "
" + dirIcon() + "
" + json.files[i1].name + "
" + trashIcon() + "
"; eventslisteners.push({ action: "dir", id: "Dir" + i1, target: json.files[i1].name, }); eventslisteners.push({ action: "dirdel", id: "DirDel" + i1, target: json.files[i1].name, }); } } for (let i1 = 0; i1 < json.files.length; i1++) { if (String(json.files[i1].size) != "-1") { if ( currentPath == hostpath && (json.files[i1].name == "index.html.gz" || json.files[i1].name == "index.html") ) { showESP3Dbutton = true; } content += "
" + "
" + "
" + fileIcon() + "
" + json.files[i1].name + "
" + json.files[i1].size + "
" + trashIcon() + "
"; eventslisteners.push({ action: "file", id: "File" + i1, target: json.files[i1].name, }); eventslisteners.push({ action: "filedel", id: "FileDel" + i1, target: json.files[i1].name, }); } } document.getElementById("fileList").innerHTML = content; for (let i = 0; i < eventslisteners.length; i++) { switch (eventslisteners[i].action) { case "updir": document .getElementById(eventslisteners[i].id) .addEventListener("click", function () { currentPath = eventslisteners[i].target; console.log(currentPath); SendFileCommand("list", "all"); }); break; case "file": document .getElementById(eventslisteners[i].id) .addEventListener("click", function () { let url = new URL( "http://" + window.location.host + currentPath + "/" + eventslisteners[i].target ); window.open(url, "_blank"); }); break; case "filedel": document .getElementById(eventslisteners[i].id) .addEventListener("click", function () { if ( confirm( "Confirm deletion of file: " + eventslisteners[i].target ) ) SendFileCommand("delete", eventslisteners[i].target); }); break; case "dir": document .getElementById(eventslisteners[i].id) .addEventListener("click", function () { currentPath += (currentPath == "/" ? "" : "/") + eventslisteners[i].target; console.log(currentPath); SendFileCommand("list", "all"); }); break; case "dirdel": document .getElementById(eventslisteners[i].id) .addEventListener("click", function () { if ( confirm( "Confirm deletion of directory: " + eventslisteners[i].target ) ) SendFileCommand("deletedir", eventslisteners[i].target); }); break; } } } catch (e) { ErrorMSG("Error: json data are incorrect!
" + jsonresponse); return; } finally { if (showESP3Dbutton) { document.getElementById("espLink").classList.remove("hide"); } else { document.getElementById("espLink").classList.add("hide"); } } } function httpGet(url, processfn) { let xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4) { if (xmlhttp.status == 200) { ErrorMSG(""); processfn(xmlhttp.responseText); } else if (xmlhttp.status == 401) { handle401(); } else { console.log(xmlhttp.status); ErrorMSG("Error: board does not answered properly " + xmlhttp.status); } } }; xmlhttp.open("GET", url, true); xmlhttp.send(); } function compareStrings(a, b) { // case-insensitive comparison a = a.toLowerCase(); b = b.toLowerCase(); return a < b ? -1 : a > b ? 1 : 0; } function uploadFiles() { let files = filesInput.files; if (files.length == 0) return; let formData = new FormData(); uploadType = 1; filesButton.classList.add("hide"); createDirButton.classList.add("hide"); refreshButton.classList.add("hide"); prgfiletext.classList.remove("hide"); prgfile.classList.remove("hide"); prgfile.value = 0; prgfiletext.innerHTML = "Uploading ...0%"; formData.append("path", currentPath); let currentpath = currentPath; if (!currentpath.endsWith("/")) currentpath += "/"; for (let i3 = 0; i3 < files.length; i3++) { let file = files[i3]; let arg = currentpath + file.name + "S"; //append file size first to check updload is complete formData.append(arg, file.size); formData.append("myfiles", file, currentpath + file.name); } xmlhttpupload = new XMLHttpRequest(); xmlhttpupload.open("POST", fspath, true); //progress upload event xmlhttpupload.upload.addEventListener("progress", updateProgress, false); //progress function function updateProgress(oEvent) { if (oEvent.lengthComputable) { let percentComplete = (oEvent.loaded / oEvent.total) * 100; prgfile.value = percentComplete; prgfiletext.innerHTML = "Uploading ..." + percentComplete.toFixed(0) + "%"; } else { // Impossible because size is unknown console.log("oops"); } } xmlhttpupload.onloadend = function () { filesButton.classList.remove("hide"); createDirButton.classList.remove("hide"); refreshButton.classList.remove("hide"); prgfile.classList.add("hide"); prgfiletext.classList.add("hide"); filesInput.value = ""; if (xmlhttpupload.status === 200) { dispatchFileStatus(xmlhttpupload.responseText); } else if (xmlhttp.status == 401) { handle401(); } else uploadError("Error", xmlhttpupload.status); }; xmlhttpupload.send(formData); } function uploadFirmware() { let files = fwInput.files; if (files.length == 0) return; let formData = new FormData(); uploadType = 2; firmwareButton.classList.add("hide"); prgupdatetext.classList.remove("hide"); prgupdate.classList.remove("hide"); prgupdate.value = 0; prgupdatetext.innerHTML = "Uploading ...0%"; formData.append("path", currentPath); let currentpath = currentPath; if (!currentpath.endsWith("/")) currentpath += "/"; for (let i3 = 0; i3 < files.length; i3++) { let file = files[i3]; let arg = currentpath + file.name + "S"; //append file size first to check updload is complete formData.append(arg, file.size); formData.append("myfiles", file, currentpath + file.name); } xmlhttpupload = new XMLHttpRequest(); xmlhttpupload.open("POST", "/updatefw", true); //progress upload event xmlhttpupload.upload.addEventListener("progress", updateProgress, false); //progress function function updateProgress(oEvent) { if (oEvent.lengthComputable) { let percentComplete = (oEvent.loaded / oEvent.total) * 100; prgupdate.value = percentComplete; prgupdatetext.innerHTML = "Uploading ..." + percentComplete.toFixed(0) + "%"; } else { // Impossible because size is unknown console.log("oops"); } } xmlhttpupload.onloadend = function () { firmwareButton.classList.remove("hide"); prgupdate.classList.add("hide"); fwInput.value = ""; if (xmlhttpupload.status === 200) { prgupdatetext.classList.add("hide"); firmware.classList.add("hide"); consolePanel.classList.add("hide"); fileSystem.classList.add("hide"); document.getElementById("verLink").classList.add("hide"); document.getElementById("espLink").classList.add("hide"); restartTime = 40; InfoMSG("
Restarting... please wait 40s"); setInterval(function () { restartTime--; InfoMSG("
Restarting... please wait " + restartTime + "s"); if (restartTime == 0) location.reload(); }, 1000); } else if (xmlhttp.status == 401) { handle401(); } else uploadError("Error", xmlhttpupload.status); }; xmlhttpupload.send(formData); }