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.a6'; 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) + 'T' + 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'); if (typeof json.HostPath != 'undefined') hostpath = json.HostPath; console.log('HostPath: ' + 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); SendFileCommand('list', 'all'); } function startSocket(ip, port, sync) { if (websocketStarted) { wsSource.close(); } wsSource = new WebSocket( 'ws://' + ip + ':' + port + (sync == 'Asynchronous' ? '/ws' : ''), ['webui-v3'] ); 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); }, 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; if (!wsMsg.startsWith('ESP3D says: command forwarded')) { 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 currentpath = currentPath; if (!currentpath.endsWith('/')) currentpath += '/'; currentpath += hostpath; if (!currentpath.endsWith('/')) currentpath += '/'; let currentpath2 = currentpath.replaceAll('//', '/'); currentpath = currentpath2; console.log('currentpath: ' + currentpath); 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 == '/' && (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 += '/'; currentpath += hostpath; if (!currentpath.endsWith('/')) currentpath += '/'; let currentpath2 = currentpath.replaceAll('//', '/'); currentpath = currentpath2; console.log(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); }