Files
squeezelite-esp32-gronod/components/wifi-manager/webapp/src/js/custom.js

1920 lines
58 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import he from 'he';
import { Promise } from 'es6-promise';
if (!String.prototype.format) {
Object.assign(String.prototype, {
format() {
const args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== 'undefined' ? args[number] : match;
});
},
});
}
if (!String.prototype.encodeHTML) {
Object.assign(String.prototype, {
encodeHTML() {
return he.encode(this).replace(/\n/g, '<br />')
},
});
}
Object.assign(Date.prototype, {
toLocalShort() {
const opt = { dateStyle: 'short', timeStyle: 'short' };
return this.toLocaleString(undefined, opt);
},
});
const nvsTypes = {
NVS_TYPE_U8: 0x01,
/*! < Type uint8_t */
NVS_TYPE_I8: 0x11,
/*! < Type int8_t */
NVS_TYPE_U16: 0x02,
/*! < Type uint16_t */
NVS_TYPE_I16: 0x12,
/*! < Type int16_t */
NVS_TYPE_U32: 0x04,
/*! < Type uint32_t */
NVS_TYPE_I32: 0x14,
/*! < Type int32_t */
NVS_TYPE_U64: 0x08,
/*! < Type uint64_t */
NVS_TYPE_I64: 0x18,
/*! < Type int64_t */
NVS_TYPE_STR: 0x21,
/*! < Type string */
NVS_TYPE_BLOB: 0x42,
/*! < Type blob */
NVS_TYPE_ANY: 0xff /*! < Must be last */,
};
const btIcons = {
bt_playing: 'play-circle-fill',
bt_disconnected: 'bluetooth-fill',
bt_neutral: '',
bt_connected: 'bluetooth-connect-fill',
bt_disabled: '',
play_arrow: 'play-circle-fill',
pause: 'pause-circle-fill',
stop: 'stop-circle-fill',
'': '',
};
const btStateIcons = [
{ desc: 'Idle', sub: ['bt_neutral'] },
{ desc: 'Discovering', sub: ['bt_disconnected'] },
{ desc: 'Discovered', sub: ['bt_disconnected'] },
{ desc: 'Unconnected', sub: ['bt_disconnected'] },
{ desc: 'Connecting', sub: ['bt_disconnected'] },
{
desc: 'Connected',
sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'],
},
{ desc: 'Disconnecting', sub: ['bt_disconnected'] },
];
const pillcolors = {
MESSAGING_INFO: 'badge-success',
MESSAGING_WARNING: 'badge-warning',
MESSAGING_ERROR: 'badge-danger',
};
const connectReturnCode = {
UPDATE_CONNECTION_OK : 0,
UPDATE_FAILED_ATTEMPT : 1,
UPDATE_USER_DISCONNECT : 2,
UPDATE_LOST_CONNECTION : 3,
UPDATE_FAILED_ATTEMPT_AND_RESTORE : 4
}
const taskStates = {
0: 'eRunning',
/*! < A task is querying the state of itself, so must be running. */
1: 'eReady',
/*! < The task being queried is in a read or pending ready list. */
2: 'eBlocked',
/*! < The task being queried is in the Blocked state. */
3: 'eSuspended',
/*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
4: 'eDeleted',
};
const flash_status_codes = {
NONE : 0,
REBOOT_TO_RECOVERY: 2,
SET_FWURL: 5,
FLASHING: 6,
DONE: 7,
UPLOADING: 8,
ERROR: 9
};
let flash_state=flash_status_codes.FLASH_NONE;
let flash_ota_dsc='';
let flash_ota_pct=0;
let older_recovery=false;
function isFlashExecuting(data){
return (flash_state!=flash_status_codes.UPLOADING ) && (data.ota_dsc!='' || data.ota_pct>0);
}
function post_config(data){
let confPayload={
timestamp: Date.now(),
config : data
};
$.ajax({
url: '/config.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(confPayload),
error: handleExceptionResponse,
});
}
function process_ota_event(data){
if(data.ota_dsc){
flash_ota_dsc=data.ota_dsc;
}
if( data.ota_pct != undefined){
flash_ota_pct=data.ota_pct;
}
if(flash_state==flash_status_codes.ERROR){
return;
}
else if(isFlashExecuting(data)){
flash_state=flash_status_codes.FLASHING;
}
else if(flash_state==flash_status_codes.FLASHING ){
if(flash_ota_pct ==100){
// we were processing OTA, and we've reached 100%
flash_state=flash_status_codes.DONE;
$('#flashfilename').val('');
}
else if(flash_ota_pct<0 && older_recovery){
// we were processing OTA on an older recovery and we missed the
// end of flashing.
console.log('End of flashing from older recovery');
if(data.ota_dsc==''){
flash_ota_dsc = 'OTA Process Completed';
}
flash_state=flash_status_codes.DONE;
}
}
else if(flash_state ==flash_status_codes.UPLOADING){
if(flash_ota_pct ==100){
// we were processing OTA, and we've reached 100%
// reset the progress bar
flash_ota_pct = 0;
flash_state=flash_status_codes.FLASHING;
}
}
}
function set_ota_error(message){
flash_state=flash_status_codes.ERROR;
handle_flash_state({
ota_pct: 0,
ota_dsc: message,
event: flash_events.SET_ERROR
});
}
function show_update_dialog(){
$('#otadiv').modal();
if (flash_ota_pct >= 0) {
update_progress();
}
if (flash_ota_dsc !== '') {
$('span#flash-status').html(flash_ota_dsc);
}
}
const flash_events={
SET_ERROR: function(data){
if(data.ota_dsc){
flash_ota_dsc=data.ota_dsc;
}
else {
flash_ota_dsc = 'Error';
}
flash_ota_pct=data.ota_pct??0;
$('#fwProgressLabel').parent().addClass('bg-danger');
update_progress();
show_update_dialog();
},
START_OTA : function() {
if (flash_state == flash_status_codes.NONE || flash_state == flash_status_codes.ERROR || flash_state == undefined) {
$('#fwProgressLabel').parent().removeClass('bg-danger');
flash_state=flash_status_codes.REBOOT_TO_RECOVERY;
if(!recovery){
flash_ota_dsc = 'Starting recovery mode...';
// Reboot system to recovery mode
const data = {
timestamp: Date.now(),
};
$.ajax({
url: '/recovery.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
error: function(xhr, _ajaxOptions, thrownError){
set_ota_error(`Unexpected error while trying to restart to recovery. (status=${xhr.status??''}, error=${thrownError??''} ) `);
},
complete: function(response) {
console.log(response.responseText);
},
});
}
else {
flash_ota_dsc='Starting Update';
}
show_update_dialog();
}
else {
console.warn('Unexpected status while starting flashing');
}
},
FOUND_RECOVERY: function(data) {
console.log(JSON.stringify(data));
const url=$('#fw-url-input').val();
if(flash_state == flash_status_codes.REBOOT_TO_RECOVERY){
const fileInput = $('#flashfilename')[0].files;
if (fileInput.length > 0) {
flash_ota_dsc = 'Sending file to device.';
flash_state= flash_status_codes.UPLOADING;
const uploadPath = '/flash.json';
const xhttp = new XMLHttpRequest();
// xhrObj.upload.addEventListener("loadstart", loadStartFunction, false);
xhttp.upload.addEventListener("progress", progressFunction, false);
//xhrObj.upload.addEventListener("load", transferCompleteFunction, false);
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4) {
if(xhttp.status === 0 || xhttp.status === 404) {
set_ota_error(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);
$('#flashfilename').val('');
}
}
};
xhttp.open('POST', uploadPath, true);
xhttp.send(fileInput[0] );
}
else if(url==''){
flash_state= flash_status_codes.NONE;
}
else {
flash_ota_dsc = 'Saving firmware URL location.';
flash_state= flash_status_codes.SET_FWURL;
let confData= { fwurl: {
value: $('#fw-url-input').val(),
type: 33,
}
};
post_config(confData);
}
show_update_dialog();
}
},
PROCESS_OTA_UPLOAD: function(data){
flash_state= flash_status_codes.UPLOADING;
process_ota_event(data);
show_update_dialog();
},
PROCESS_OTA_STATUS: function(data){
if(data.ota_pct>0){
older_recovery = true;
}
if(flash_state == flash_status_codes.REBOOT_TO_RECOVERY){
data.event = flash_events.FOUND_RECOVERY;
handle_flash_state(data);
}
else if(flash_state==flash_status_codes.DONE && !recovery){
flash_state=flash_status_codes.NONE;
$('#rTable tr.release').removeClass('table-success table-warning');
$('#fw-url-input').val('');
}
else {
process_ota_event(data);
if(flash_state && (flash_state >flash_status_codes.NONE && flash_ota_pct>=0) ) {
show_update_dialog();
}
}
},
PROCESS_OTA: function(data) {
process_ota_event(data);
if(flash_state && (flash_state >flash_status_codes.NONE && flash_ota_pct>=0) ) {
show_update_dialog();
}
}
};
window.hideSurrounding = function(obj){
$(obj).parent().parent().hide();
}
function update_progress(){
$('.progress-bar')
.css('width', flash_ota_pct + '%')
.attr('aria-valuenow', flash_ota_pct)
.text(flash_ota_pct+'%')
$('.progress-bar').html((flash_state==flash_status_codes.DONE?100:flash_ota_pct) + '%');
}
function handle_flash_state(data) {
if(data.event) {
data.event(data);
}
else {
console.error('Unexpected error while processing handle_flash_state');
return;
}
}
window.hFlash = function(){
// reset file upload selection if any;
$('#flashfilename').val('');
handle_flash_state({ event: flash_events.START_OTA, url: $('#fw-url-input').val() });
}
window.handleReboot = function(link){
if(link=='reboot_ota'){
$('#reboot_ota_nav').removeClass('active').prop("disabled",true); delayReboot(500,'', 'reboot_ota');
}
else {
$('#reboot_nav').removeClass('active'); delayReboot(500,'',link);
}
}
function progressFunction(evt){
// if (evt.lengthComputable) {
// progressBar.max = evt.total;
// progressBar.value = evt.loaded;
// percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
// }
handle_flash_state({
ota_pct: ( Math.round(evt.loaded / evt.total * 100)),
ota_dsc: ('Uploading file to device'),
event: flash_events.PROCESS_OTA_UPLOAD
});
}
function handlebtstate(data) {
let icon = '';
let tt = '';
if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {
const iconsvg = btStateIcons[data.bt_status].sub[data.bt_sub_status];
if (iconsvg) {
icon = `#${btIcons[iconsvg]}`;
tt = btStateIcons[data.bt_status].desc;
} else {
icon = `#${btIcons.bt_connected}`;
tt = 'Output status';
}
}
$('#o_type').title = tt;
$('#o_bt').attr('xlink:href',icon);
}
function handleTemplateTypeRadio(outtype) {
if (outtype === 'bt') {
$('#bt').prop('checked', true);
$('#o_bt').attr('display', 'inline');
$('#o_spdif').attr('display', 'none');
$('#o_i2s').attr('display', 'none');
output = 'bt';
} else if (outtype === 'spdif') {
$('#spdif').prop('checked', true);
$('#o_bt').attr('display', 'none');
$('#o_spdif').attr('display', 'inline');
$('#o_i2s').attr('display', 'none');
output = 'spdif';
} else {
$('#i2s').prop('checked', true);
$('#o_bt').attr('display', 'none');
$('#o_spdif').attr('display', 'none');
$('#o_i2s').attr('display', 'inline');
output = 'i2s';
}
}
function handleExceptionResponse(xhr, _ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError !== '') {
showLocalMessage(thrownError, 'MESSAGING_ERROR');
}
}
function HideCmdMessage(cmdname) {
$('#toast_' + cmdname).css('display', 'none');
$('#toast_' + cmdname)
.removeClass('table-success')
.removeClass('table-warning')
.removeClass('table-danger')
.addClass('table-success');
$('#msg_' + cmdname).html('');
}
function showCmdMessage(cmdname, msgtype, msgtext, append = false) {
let color = 'table-success';
if (msgtype === 'MESSAGING_WARNING') {
color = 'table-warning';
} else if (msgtype === 'MESSAGING_ERROR') {
color = 'table-danger';
}
$('#toast_' + cmdname).css('display', 'block');
$('#toast_' + cmdname)
.removeClass('table-success')
.removeClass('table-warning')
.removeClass('table-danger')
.addClass(color);
let escapedtext = msgtext
.substring(0, msgtext.length - 1)
.encodeHTML()
.replace(/\n/g, '<br />');
escapedtext =
($('#msg_' + cmdname).html().length > 0 && append
? $('#msg_' + cmdname).html() + '<br/>'
: '') + escapedtext;
$('#msg_' + cmdname).html(escapedtext);
}
let releaseURL =
'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
let recovery = false;
const commandHeader = 'squeezelite -b 500:2000 -d all=info -C 30 -W';
let blockAjax = false;
//let blockFlashButton = false;
let apList = null;
//let selectedSSID = '';
//let checkStatusInterval = null;
let messagecount = 0;
let messageseverity = 'MESSAGING_INFO';
let StatusIntervalActive = false;
let LastRecoveryState = null;
let SystemConfig={};
let LastCommandsState = null;
var output = '';
let hostName = '';
let versionName='Squeezelite-ESP32';
let prevmessage='';
let project_name=versionName;
let platform_name=versionName;
let btSinkNamesOptSel='#cfg-audio-bt_source-sink_name';
let ConnectedToSSID={};
let ConnectingToSSID={};
let lmsBaseUrl;
let prevLMSIP='';
const ConnectingToActions = {
'CONN' : 0,'MAN' : 1,'STS' : 2,
}
Promise.prototype.delay = function(duration) {
return this.then(
function(value) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(value);
}, duration);
});
},
function(reason) {
return new Promise(function(_resolve, reject) {
setTimeout(function() {
reject(reason);
}, duration);
});
}
);
};
function startCheckStatusInterval() {
StatusIntervalActive = true;
setTimeout(checkStatus, 3000);
}
function RepeatCheckStatusInterval() {
if (StatusIntervalActive) {
startCheckStatusInterval();
}
}
function getConfigJson(slimMode) {
const config = {};
$('input.nvs').each(function(_index, entry) {
if (!slimMode) {
const nvsType = parseInt(entry.attributes.nvs_type.value, 10);
if (entry.id !== '') {
config[entry.id] = {};
if (
nvsType === nvsTypes.NVS_TYPE_U8 ||
nvsType === nvsTypes.NVS_TYPE_I8 ||
nvsType === nvsTypes.NVS_TYPE_U16 ||
nvsType === nvsTypes.NVS_TYPE_I16 ||
nvsType === nvsTypes.NVS_TYPE_U32 ||
nvsType === nvsTypes.NVS_TYPE_I32 ||
nvsType === nvsTypes.NVS_TYPE_U64 ||
nvsType === nvsTypes.NVS_TYPE_I64
) {
config[entry.id].value = parseInt(entry.value);
} else {
config[entry.id].value = entry.value;
}
config[entry.id].type = nvsType;
}
} else {
config[entry.id] = entry.value;
}
});
const key = $('#nvs-new-key').val();
const val = $('#nvs-new-value').val();
if (key !== '') {
if (!slimMode) {
config[key] = {};
config[key].value = val;
config[key].type = 33;
} else {
config[key] = val;
}
}
return config;
}
// eslint-disable-next-line no-unused-vars
function onFileLoad(elementId, event) {
let data = {};
try {
data = JSON.parse(elementId.srcElement.result);
} catch (e) {
alert('Parsing failed!\r\n ' + e);
}
$('input.nvs').each(function(_index, entry) {
if (data[entry.id]) {
if (data[entry.id] !== entry.value) {
console.log(
'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id]
);
$(this).val(data[entry.id]);
}
}
});
}
// eslint-disable-next-line no-unused-vars
function onChooseFile(event, onLoadFileHandler) {
if (typeof window.FileReader !== 'function') {
throw "The file API isn't supported on this browser.";
}
const input = event.target;
if (!input) {
throw 'The browser does not properly implement the event object';
}
if (!input.files) {
throw 'This browser does not support the `files` property of the file input.';
}
if (!input.files[0]) {
return undefined;
}
const file = input.files[0];
let fr = new FileReader();
fr.onload = onLoadFileHandler;
fr.readAsText(file);
input.value = '';
}
function delayReboot(duration, cmdname, ota = 'reboot') {
const url = '/'+ota+'.json';
$('tbody#tasks').empty();
$('#tasks_sect').css('visibility', 'collapse');
Promise.resolve({ cmdname: cmdname, url: url })
.delay(duration)
.then(function(data) {
if (data.cmdname.length > 0) {
showCmdMessage(
data.cmdname,
'MESSAGING_WARNING',
'System is rebooting.\n',
true
);
} else {
showLocalMessage('System is rebooting.\n', 'MESSAGING_WARNING');
}
console.log('now triggering reboot');
$("button[onclick*='handleReboot']").addClass('rebooting');
$.ajax({
url: data.url,
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
timestamp: Date.now(),
}),
error: handleExceptionResponse,
complete: function() {
console.log('reboot call completed');
Promise.resolve(data)
.delay(6000)
.then(function(rdata) {
if (rdata.cmdname.length > 0) {
HideCmdMessage(rdata.cmdname);
}
getCommands();
getConfig();
});
},
});
});
}
// eslint-disable-next-line no-unused-vars
window.saveAutoexec1 = function(apply) {
showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\n', false);
let commandLine = commandHeader + ' -n "' + $('#player').val() + '"';
if (output === 'bt') {
commandLine += ' -o "BT" -R -Z 192000';
showCmdMessage(
'cfg-audio-tmpl',
'MESSAGING_INFO',
'Remember to configure the Bluetooth audio device name.\n',
true
);
} else if (output === 'spdif') {
commandLine += ' -o SPDIF -Z 192000';
} else {
commandLine += ' -o I2S';
}
if ($('#optional').val() !== '') {
commandLine += ' ' + $('#optional').val();
}
const data = {
timestamp: Date.now(),
};
data.config = {
autoexec1: { value: commandLine, type: 33 },
autoexec: {
value: $('#disable-squeezelite').prop('checked') ? '0' : '1',
type: 33,
},
};
$.ajax({
url: '/config.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
error: handleExceptionResponse,
complete: function(response) {
if (
response.responseText.result &&
JSON.parse(response.responseText).result === 'OK'
) {
showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\n', true);
if (apply) {
delayReboot(1500, 'cfg-audio-tmpl');
}
} else if (response.responseText.result) {
showCmdMessage(
'cfg-audio-tmpl',
'MESSAGING_WARNING',
JSON.parse(response.responseText).Result + '\n',
true
);
} else {
showCmdMessage(
'cfg-audio-tmpl',
'MESSAGING_ERROR',
response.statusText + '\n'
);
}
console.log(response.responseText);
},
});
console.log('sent data:', JSON.stringify(data));
}
window.handleDisconnect = function(){
$.ajax({
url: '/connect.json',
dataType: 'text',
method: 'DELETE',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
timestamp: Date.now(),
}),
});
}
function setPlatformFilter(val){
if($('.upf').filter(function(){ return $(this).text().toUpperCase()===val.toUpperCase()}).length>0){
$('#splf').val(val).trigger('input');
return true;
}
return false;
}
window.handleConnect = function(){
ConnectingToSSID.ssid = $('#manual_ssid').val();
ConnectingToSSID.pwd = $('#manual_pwd').val();
ConnectingToSSID.dhcpname = $('#dhcp-name2').val();
$("*[class*='connecting']").hide();
$('#ssid-wait').text(ConnectingToSSID.ssid);
$('.connecting').show();
$.ajax({
url: '/connect.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
timestamp: Date.now(),
ssid: ConnectingToSSID.ssid,
pwd: ConnectingToSSID.pwd
}),
error: handleExceptionResponse,
});
// now we can re-set the intervals regardless of result
startCheckStatusInterval();
}
$(document).ready(function() {
$('#wifiTable').on('click','tr', function() {
});
$('#fw-url-input').on('input', function() {
if($(this).val().length>8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))){
$('#start-flash').show();
}
else {
$('#start-flash').hide();
}
});
$('.upSrch').on('input', function() {
const val = this.value;
$("#rTable tr").removeClass(this.id+'_hide');
if(val.length>0) {
$(`#rTable td:nth-child(${$(this).parent().index()+1})`).filter(function(){
return !$(this).text().toUpperCase().includes(val.toUpperCase());
}).parent().addClass(this.id+'_hide');
}
$('[class*="_hide"]').hide();
$('#rTable tr').not('[class*="_hide"]').show()
});
setTimeout(refreshAP,1500);
$('#otadiv').on('hidden.bs.modal', function () {
// reset flash status. This should stop the state machine from
// executing steps up to flashing itself.
flash_state=flash_status_codes.NONE;
});
$('#WifiConnectDialog').on('shown.bs.modal', function () {
$("*[class*='connecting']").hide();
if(ConnectingToSSID.Action!==ConnectingToActions.STS){
$('.connecting-init').show();
$('#manual_ssid').trigger('focus');
}
else {
handleWifiDialog();
}
})
$('#WifiConnectDialog').on('hidden.bs.modal', function () {
$('#WifiConnectDialog input').val('');
})
$('#uCnfrm').on('shown.bs.modal', function () {
$('#selectedFWURL').text($('#fw-url-input').val());
})
$('input#show-commands')[0].checked = LastCommandsState === 1;
$('a[href^="#tab-commands"]').hide();
$('#load-nvs').on('click', function() {
$('#nvsfilename').trigger('click');
});
$('#clear-syslog').on('click', function() {
messagecount = 0;
messageseverity = 'MESSAGING_INFO';
$('#msgcnt').text('');
$('#syslogTable').html('');
});
$('#wifiTable').on('click','tr', function() {
ConnectingToSSID.Action=ConnectingToActions.CONN;
if($(this).children('td:eq(1)').text() == ConnectedToSSID.ssid){
ConnectingToSSID.Action=ConnectingToActions.STS;
return;
}
if(!$(this).is(':last-child')){
ConnectingToSSID.ssid=$(this).children('td:eq(1)').text();
$('#manual_ssid').val(ConnectingToSSID.ssid);
}
else {
ConnectingToSSID.Action=ConnectingToActions.MAN;
ConnectingToSSID.ssid='';
$('#manual_ssid').val(ConnectingToSSID.ssid);
}
});
$('#ok-credits').on('click', function() {
$('#credits').slideUp('fast', function() {});
$('#app').slideDown('fast', function() {});
});
$('#acredits').on('click', function(event) {
event.preventDefault();
$('#app').slideUp('fast', function() {});
$('#credits').slideDown('fast', function() {});
});
$('input#show-commands').on('click', function() {
this.checked = this.checked ? 1 : 0;
if (this.checked) {
$('a[href^="#tab-commands"]').show();
LastCommandsState = 1;
} else {
LastCommandsState = 0;
$('a[href^="#tab-commands"]').hide();
}
});
$('input#show-nvs').on('click', function() {
this.checked = this.checked ? 1 : 0;
if (this.checked) {
$('*[href*="-nvs"]').show();
} else {
$('*[href*="-nvs"]').hide();
}
});
$('#save-as-nvs').on('click', function() {
const config = getConfigJson(true);
const a = document.createElement('a');
a.href = URL.createObjectURL(
new Blob([JSON.stringify(config, null, 2)], {
type: 'text/plain',
})
);
a.setAttribute(
'download',
'nvs_config_' + hostName + '_' + Date.now() + 'json'
);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
$('#save-nvs').on('click', function() {
post_config(getConfigJson(false));
});
$('#fwUpload').on('click', function() {
const fileInput = document.getElementById('flashfilename').files;
if (fileInput.length === 0) {
alert('No file selected!');
} else {
handle_flash_state({ event: flash_events.START_OTA, file: fileInput[0] });
}
});
$('[name=output-tmpl]').on('click', function() {
handleTemplateTypeRadio(this.id);
});
$('#chkUpdates').on('click', function() {
$('#rTable').html('');
$.getJSON(releaseURL, function(data) {
let i = 0;
const branches = [];
data.forEach(function(release) {
const namecomponents = release.name.split('#');
const branch = namecomponents[3];
if (!branches.includes(branch)) {
branches.push(branch);
}
});
let fwb='';
branches.forEach(function(branch) {
fwb += '<option value="' + branch + '">' + branch + '</option>';
});
$('#fwbranch').append(fwb);
data.forEach(function(release) {
let url = '';
release.assets.forEach(function(asset) {
if (asset.name.match(/\.bin$/)) {
url = asset.browser_download_url;
}
});
const namecomponents = release.name.split('#');
const ver = namecomponents[0];
const cfg = namecomponents[2];
const branch = namecomponents[3];
var bits = ver.substr(ver.lastIndexOf('-')+1);
bits = (bits =='32' || bits == '16')?bits:'';
let body = release.body;
body = body.replace(/'/gi, '"');
body = body.replace(
/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,
'$1'
);
body = body.replace(/- \(.+?\) /g, '- ');
$('#rTable').append(`<tr class='release ' fwurl='${url}'>
<td data-toggle='tooltip' title='${body}'>${ver}</td><td>${new Date(release.created_at).toLocalShort()}
</td><td class='upf'>${cfg}</td><td>${branch}</td><td>${bits}</td></tr>`
);
});
if (i > 7) {
$('#releaseTable').append(
"<tr id='showall'>" +
"<td colspan='6'>" +
"<input type='button' id='showallbutton' class='btn btn-info' value='Show older releases' />" +
'</td>' +
'</tr>'
);
$('#showallbutton').on('click', function() {
$('tr.hide').removeClass('hide');
$('tr#showall').addClass('hide');
});
}
$('#searchfw').css('display', 'inline');
if(!setPlatformFilter(platform_name)){
setPlatformFilter(project_name)
}
$('#rTable tr.release').on('click', function() {
var url=this.attributes['fwurl'].value;
if (lmsBaseUrl) {
url = url.replace(/.*\/download\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');
}
$('#fw-url-input').val(url);
$('#start-flash').show();
$('#rTable tr.release').removeClass('table-success table-warning');
$(this).addClass('table-success table-warning');
});
}).fail(function() {
alert('failed to fetch release history!');
});
});
$('#fwcheck').on('click', function() {
$('#releaseTable').html('');
$('#fwbranch').empty();
$.getJSON(releaseURL, function(data) {
let i = 0;
const branches = [];
data.forEach(function(release) {
const namecomponents = release.name.split('#');
const branch = namecomponents[3];
if (!branches.includes(branch)) {
branches.push(branch);
}
});
let fwb;
branches.forEach(function(branch) {
fwb += '<option value="' + branch + '">' + branch + '</option>';
});
$('#fwbranch').append(fwb);
data.forEach(function(release) {
let url = '';
release.assets.forEach(function(asset) {
if (asset.name.match(/\.bin$/)) {
url = asset.browser_download_url;
}
});
const namecomponents = release.name.split('#');
const ver = namecomponents[0];
const idf = namecomponents[1];
const cfg = namecomponents[2];
const branch = namecomponents[3];
let body = release.body;
body = body.replace(/'/gi, '"');
body = body.replace(
/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,
'$1'
);
body = body.replace(/- \(.+?\) /g, '- ');
const trclass = i++ > 6 ? ' hide' : '';
$('#releaseTable').append(
"<tr class='release" +
trclass +
"'>" +
"<td data-toggle='tooltip' title='" +
body +
"'>" +
ver +
'</td>' +
'<td>' +
new Date(release.created_at).toLocalShort() +
'</td>' +
'<td>' +
cfg +
'</td>' +
'<td>' +
idf +
'</td>' +
'<td>' +
branch +
'</td>' +
"<td><input type='button' class='btn btn-success' value='Select' data-url='" +
url +
"' onclick='setURL(this);' /></td>" +
'</tr>'
);
});
if (i > 7) {
$('#releaseTable').append(
"<tr id='showall'>" +
"<td colspan='6'>" +
"<input type='button' id='showallbutton' class='btn btn-info' value='Show older releases' />" +
'</td>' +
'</tr>'
);
$('#showallbutton').on('click', function() {
$('tr.hide').removeClass('hide');
$('tr#showall').addClass('hide');
});
}
$('#searchfw').css('display', 'inline');
}).fail(function() {
alert('failed to fetch release history!');
});
});
$('#updateAP').on('click', function() {
refreshAP();
console.log('refresh AP');
});
// first time the page loads: attempt to get the connection status and start the wifi scan
getConfig();
getCommands();
// start timers
startCheckStatusInterval();
});
// eslint-disable-next-line no-unused-vars
window.setURL = function(button) {
let url = button.dataset.url;
$('[data-url^="http"]')
.addClass('btn-success')
.removeClass('btn-danger');
$('[data-url="' + url + '"]')
.addClass('btn-danger')
.removeClass('btn-success');
// if user can proxy download through LMS, modify the URL
if (lmsBaseUrl) {
url = url.replace(/.*\/download\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');
}
$('#fwurl').val(url);
}
function rssiToIcon(rssi) {
if (rssi >= -55) {
return `#signal-wifi-fill`;
} else if (rssi >= -60) {
return `#signal-wifi-3-fill`;
} else if (rssi >= -65) {
return `#signal-wifi-2-fill`;
} else if (rssi >= -70) {
return `#signal-wifi-1-fill`;
} else {
return `#signal-wifi-line`;
}
}
function refreshAP() {
$.getJSON('/scan.json', async function() {
await sleep(2000);
$.getJSON('/ap.json', function(data) {
if (data.length > 0) {
// sort by signal strength
data.sort(function(a, b) {
const x = a.rssi;
const y = b.rssi;
// eslint-disable-next-line no-nested-ternary
return x < y ? 1 : x > y ? -1 : 0;
});
apList = data;
refreshAPHTML2(apList);
}
});
});
}
function formatAP(ssid, rssi, auth){
return `<tr data-toggle="modal" data-target="#WifiConnectDialog"><td></td><td>${ssid}</td><td>
<svg style="fill:white; width:1.5rem; height: 1.5rem;">
<use xlink:href="#${rssiToIcon(rssi)}"></use>
</svg>
</td><td>
<svg style="fill:white; width:1.5rem; height: 1.5rem;">
<use xlink:href="#lock${(auth == 0 ? '-unlock':'')}-fill"></use>
</svg>
</td></tr>`;
}
function refreshAPHTML2(data) {
let h = '';
$('#wifiTable tr td:first-of-type').text('');
$('#wifiTable tr').removeClass('table-success table-warning');
if(data){
data.forEach(function(e) {
h+=formatAP(e.ssid, e.rssi, e.auth);
});
$('#wifiTable').html(h);
}
if($('.manual_add').length == 0){
$('#wifiTable').append(formatAP('Manual add', 0,0));
$('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add');
}
if(ConnectedToSSID.ssid && ( ConnectedToSSID.urc === connectReturnCode.UPDATE_CONNECTION_OK || ConnectedToSSID.urc === connectReturnCode.UPDATE_FAILED_ATTEMPT_AND_RESTORE )){
const wifiSelector=`#wifiTable td:contains("${ConnectedToSSID.ssid}")`;
if($(wifiSelector).filter(function() {return $(this).text() === ConnectedToSSID.ssid; }).length==0){
$('#wifiTable').prepend(`${formatAP(ConnectedToSSID.ssid, ConnectedToSSID.rssi ?? 0, 0)}`);
}
$(wifiSelector).filter(function() {return $(this).text() === ConnectedToSSID.ssid; }).siblings().first().html('&check;').parent().addClass((ConnectedToSSID.urc === connectReturnCode.UPDATE_CONNECTION_OK?'table-success':'table-warning'));
$('span#foot-wifi').html(`SSID: <strong>${ConnectedToSSID.ssid}</strong>, IP: <strong>${ConnectedToSSID.ip}</strong>`);
$('#wifiStsIcon').attr('xlink:href',rssiToIcon(ConnectedToSSID.rssi));
}
else {
$('span#foot-wifi').html('');
}
}
function showTask(task) {
console.debug(
this.toLocaleString() +
'\t' +
task.nme +
'\t' +
task.cpu +
'\t' +
taskStates[task.st] +
'\t' +
task.minstk +
'\t' +
task.bprio +
'\t' +
task.cprio +
'\t' +
task.num
);
$('tbody#tasks').append(
'<tr class="table-primary"><th scope="row">' +
task.num +
'</th><td>' +
task.nme +
'</td><td>' +
task.cpu +
'</td><td>' +
taskStates[task.st] +
'</td><td>' +
task.minstk +
'</td><td>' +
task.bprio +
'</td><td>' +
task.cprio +
'</td></tr>'
);
}
function btExists(name){
return getBTSinkOpt(name).length>0;
}
function getBTSinkOpt(name){
return $(`${btSinkNamesOptSel} option:contains('${name}')`);
}
function getMessages() {
$.getJSON('/messages.json', async function(data) {
for (const msg of data) {
const msgAge = msg.current_time - msg.sent_time;
var msgTime = new Date();
msgTime.setTime(msgTime.getTime() - msgAge);
switch (msg.class) {
case 'MESSAGING_CLASS_OTA':
var otaData = JSON.parse(msg.message);
handle_flash_state({
ota_pct: (otaData.ota_pct ?? -1),
ota_dsc: (otaData.ota_dsc ??''),
event: flash_events.PROCESS_OTA
});
break;
case 'MESSAGING_CLASS_STATS':
// for task states, check structure : task_state_t
var statsData = JSON.parse(msg.message);
console.debug(
msgTime.toLocalShort() +
' - Number of running tasks: ' +
statsData.ntasks
);
console.debug(
msgTime.toLocalShort() +
'\tname' +
'\tcpu' +
'\tstate' +
'\tminstk' +
'\tbprio' +
'\tcprio' +
'\tnum'
);
if (statsData.tasks) {
if ($('#tasks_sect').css('visibility') === 'collapse') {
$('#tasks_sect').css('visibility', 'visible');
}
$('tbody#tasks').html('');
statsData.tasks
.sort(function(a, b) {
return b.cpu - a.cpu;
})
.forEach(showTask, msgTime);
} else if ($('#tasks_sect').css('visibility') === 'visible') {
$('tbody#tasks').empty();
$('#tasks_sect').css('visibility', 'collapse');
}
break;
case 'MESSAGING_CLASS_SYSTEM':
showMessage(msg, msgTime);
break;
case 'MESSAGING_CLASS_CFGCMD':
var msgparts = msg.message.split(/([^\n]*)\n(.*)/gs);
showCmdMessage(msgparts[1], msg.type, msgparts[2], true);
break;
case 'MESSAGING_CLASS_BT':
if($("#cfg-audio-bt_source-sink_name").is('input')){
var attr=$("#cfg-audio-bt_source-sink_name")[0].attributes;
var attrs='';
for (var j = 0; j < attr.length; j++) {
if(attr.item(j).name!="type"){
attrs+=`${attr.item(j).name } = "${attr.item(j).value}" `;
}
}
var curOpt=$("#cfg-audio-bt_source-sink_name")[0].value;
$("#cfg-audio-bt_source-sink_name").replaceWith(`<select id="cfg-audio-bt_source-sink_name" ${attrs}><option value="${curOpt}" data-description="${curOpt}">${curOpt}</option></select> `);
}
JSON.parse(msg.message).forEach(function(btEntry) {
//<input type="text" class="form-control bg-success" placeholder="name" hasvalue="true" longopts="sink_name" shortopts="n" checkbox="false" cmdname="cfg-audio-bt_source" id="cfg-audio-bt_source-sink_name" name="cfg-audio-bt_source-sink_name">
//<select hasvalue="true" longopts="jack_behavior" shortopts="j" checkbox="false" cmdname="cfg-audio-general" id="cfg-audio-general-jack_behavior" name="cfg-audio-general-jack_behavior" class="form-control "><option>--</option><option>Headphones</option><option>Subwoofer</option></select>
if(!btExists(btEntry.name)){
$("#cfg-audio-bt_source-sink_name").append(`<option>${btEntry.name}</option>`);
showMessage({ type:msg.type, message:`BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} `}, msgTime);
}
getBTSinkOpt(btEntry.name).attr('data-description', `${btEntry.name} (${btEntry.rssi}dB)`)
.attr('rssi',btEntry.rssi)
.attr('value',btEntry.name)
.text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change');
});
$(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function(a, b) {
console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt( $(b).attr('rssi'))} ? `);
return parseInt($(a).attr('rssi')) < parseInt( $(b).attr('rssi')) ? 1 : -1;
}));
break;
default:
break;
}
}
}).fail(function(xhr, ajaxOptions, thrownError){
if(xhr.status==404){
$('.orec').hide(); // system commands won't be available either
}
else {
handleExceptionResponse(xhr, ajaxOptions, thrownError);
}
}
);
/*
Minstk is minimum stack space left
Bprio is base priority
cprio is current priority
nme is name
st is task state. I provided a "typedef" that you can use to convert to text
cpu is cpu percent used
*/
}
function handleRecoveryMode(data) {
const locRecovery= data.recovery ??0;
if (LastRecoveryState !== locRecovery) {
LastRecoveryState = locRecovery;
$('input#show-nvs')[0].checked = LastRecoveryState === 1;
}
if ($('input#show-nvs')[0].checked) {
$('*[href*="-nvs"]').show();
} else {
$('*[href*="-nvs"]').hide();
}
if (locRecovery === 1) {
recovery = true;
$('.recovery_element').show();
$('.ota_element').hide();
$('#boot-button').html('Reboot');
$('#boot-form').attr('action', '/reboot_ota.json');
} else {
recovery = false;
$('.recovery_element').hide();
$('.ota_element').show();
$('#boot-button').html('Recovery');
$('#boot-form').attr('action', '/recovery.json');
}
}
function hasConnectionChanged(data){
// gw: "192.168.10.1"
// ip: "192.168.10.225"
// netmask: "255.255.255.0"
// ssid: "MyTestSSID"
return (data.urc !== ConnectedToSSID.urc ||
data.ssid !== ConnectedToSSID.ssid ||
data.gw !== ConnectedToSSID.gw ||
data.netmask !== ConnectedToSSID.netmask ||
data.ip !== ConnectedToSSID.ip || data.rssi !== ConnectedToSSID.rssi )
}
function handleWifiDialog(data){
if($('#WifiConnectDialog').is(':visible')){
if(ConnectedToSSID.ip) {
$('#ipAddress').text(ConnectedToSSID.ip);
}
if(ConnectedToSSID.ssid) {
$('#connectedToSSID' ).text(ConnectedToSSID.ssid);
}
if(ConnectedToSSID.gw) {
$('#gateway' ).text(ConnectedToSSID.gw);
}
if(ConnectedToSSID.netmask) {
$('#netmask' ).text(ConnectedToSSID.netmask);
}
if(ConnectingToSSID.Action===undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) {
$("*[class*='connecting']").hide();
$('.connecting-status').show();
}
if(SystemConfig.ap_ssid){
$('#apName').text(SystemConfig.ap_ssid);
}
if(SystemConfig.ap_pwd){
$('#apPass').text(SystemConfig.ap_pwd);
}
if(!data)
{
return;
}
else {
switch (data.urc) {
case connectReturnCode.UPDATE_CONNECTION_OK:
if(data.ssid && data.ssid===ConnectingToSSID.ssid){
$("*[class*='connecting']").hide();
$('.connecting-success').show();
ConnectingToSSID.Action = ConnectingToActions.STS;
}
break;
case connectReturnCode.UPDATE_FAILED_ATTEMPT:
//
if(ConnectingToSSID.Action !=ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid ){
$("*[class*='connecting']").hide();
$('.connecting-fail').show();
}
break;
case connectReturnCode.UPDATE_LOST_CONNECTION:
break;
case connectReturnCode.UPDATE_FAILED_ATTEMPT_AND_RESTORE:
if(ConnectingToSSID.Action !=ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid ){
$("*[class*='connecting']").hide();
$('.connecting-fail').show();
}
break;
case connectReturnCode.UPDATE_USER_DISCONNECT:
// that's a manual disconnect
// if ($('#wifi-status').is(':visible')) {
// $('#wifi-status').slideUp('fast', function() {});
// $('span#foot-wifi').html('');
// }
break;
default:
break;
}
}
}
}
function handleWifiStatus(data) {
if(hasConnectionChanged(data)){
ConnectedToSSID=data;
refreshAPHTML2();
}
handleWifiDialog(data);
}
function batteryToIcon(voltage) {
/* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated
as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely
following the graph here:
https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
using the 0.2C discharge profile for the rest of the values.
*/
if (voltage > 0) {
if (inRange(voltage, 5.8, 6.8) || inRange(voltage, 8.8, 10.2)) {
return `battery-low-line`;
} else if (inRange(voltage, 6.8, 7.4) || inRange(voltage, 10.2, 11.1)) {
return `battery-low-line`;
} else if (
inRange(voltage, 7.4, 7.5) ||
inRange(voltage, 11.1, 11.25)
) {
return `battery-low-line`;
} else if (
inRange(voltage, 7.5, 7.8) ||
inRange(voltage, 11.25, 11.7)
) {
return `battery-fill`;
} else {
return `battery-line`;
}
}
}
function checkStatus() {
RepeatCheckStatusInterval();
if (blockAjax) {
return;
}
blockAjax = true;
getMessages();
$.getJSON('/status.json', function(data) {
handleRecoveryMode(data);
handleWifiStatus(data);
handlebtstate(data);
handle_flash_state({
ota_pct: (data.ota_pct ?? -1),
ota_dsc: (data.ota_dsc ??''),
event: flash_events.PROCESS_OTA_STATUS
});
if (data.project_name && data.project_name !== '') {
project_name = data.project_name;
}
if(data.platform_name && data.platform_name!==''){
platform_name = data.platform_name;
}
if (data.version && data.version !== '') {
versionName=data.version;
$("#navtitle").html(`${project_name}${recovery?'<br>[recovery]':''}`);
$('span#foot-fw').html(`fw: <strong>${versionName}</strong>, mode: <strong>${recovery?"Recovery":project_name}</strong>`);
} else {
$('span#flash-status').html('');
}
if (data.Voltage) {
$('#battery').attr('xlink:href', `#${batteryToIcon(data.Voltage)}`);
$('#battery').show();
} else {
$('#battery').hide();
}
if((data.message??'')!='' && prevmessage != data.message){
// supporting older recovery firmwares - messages will come from the status.json structure
prevmessage = data.message;
showLocalMessage(data.message, 'MESSAGING_INFO')
}
$("button[onclick*='handleReboot']").removeClass('rebooting');
if (typeof lmsBaseUrl == "undefined" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {
const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;
prevLMSIP=data.lms_ip;
$.ajax({
url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',
type: 'HEAD',
dataType: 'text',
cache: false,
error: function() {
// define the value, so we don't check it any more.
lmsBaseUrl = '';
},
success: function() {
lmsBaseUrl = baseUrl;
}
});
}
$('#o_jack').attr('display', Number(data.Jack) ? 'inline' : 'none');
blockAjax = false;
}).fail(function(xhr, ajaxOptions, thrownError) {
handleExceptionResponse(xhr, ajaxOptions, thrownError);
blockAjax = false;
});
}
// eslint-disable-next-line no-unused-vars
window.runCommand = function(button, reboot) {
let cmdstring = button.attributes.cmdname.value;
showCmdMessage(
button.attributes.cmdname.value,
'MESSAGING_INFO',
'Executing.',
false
);
const fields = document.getElementById('flds-' + cmdstring);
cmdstring += ' ';
if (fields) {
const allfields = fields.querySelectorAll('select,input');
for (var i = 0; i < allfields.length; i++) {
const attr = allfields[i].attributes;
let qts = '';
let opt = '';
let isSelect = $(allfields[i]).is('select');
const hasValue=attr.hasvalue.value === 'true';
const validVal=(isSelect && allfields[i].value !== '--' ) || ( !isSelect && allfields[i].value !== '' );
if ( !hasValue|| hasValue && validVal) {
if (attr.longopts.value !== 'undefined') {
opt += '--' + attr.longopts.value;
} else if (attr.shortopts.value !== 'undefined') {
opt = '-' + attr.shortopts.value;
}
if (attr.hasvalue.value === 'true') {
if (allfields[i].value !== '') {
qts = /\s/.test(allfields[i].value) ? '"' : '';
cmdstring += opt + ' ' + qts + allfields[i].value + qts + ' ';
}
} else {
// this is a checkbox
if (allfields[i].checked) {
cmdstring += opt + ' ';
}
}
}
}
}
console.log(cmdstring);
const data = {
timestamp: Date.now(),
};
data.command = cmdstring;
$.ajax({
url: '/commands.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
error: function(xhr, _ajaxOptions, thrownError){
var cmd=JSON.parse(this.data ).command;
if(xhr.status==404){
showCmdMessage(
cmd.substr(0,cmd.indexOf(' ')),
'MESSAGING_ERROR',
`${recovery?'Limited recovery mode active. Unsupported action ':'Unexpected error while processing command'}`,
true
);
}
else {
handleExceptionResponse(xhr, _ajaxOptions, thrownError);
showCmdMessage(
cmd.substr(0,cmd.indexOf(' ')-1),
'MESSAGING_ERROR',
`Unexpected error ${(thrownError !== '')?thrownError:'with return status = '+xhr.status}`,
true
);
}
},
success: function(response) {
// var returnedResponse = JSON.parse(response.responseText);
$('.orec').show();
console.log(response.responseText);
if (
response.responseText &&
JSON.parse(response.responseText).Result === 'Success' &&
reboot
) {
delayReboot(2500, button.attributes.cmdname.value);
}
},
});
}
function getLongOps(data, name, longopts){
return data.values[name]!==undefined?data.values[name][longopts]:"";
}
function getCommands() {
$.getJSON('/commands.json', function(data) {
console.log(data);
$('.orec').show();
data.commands.forEach(function(command) {
if ($('#flds-' + command.name).length === 0) {
const cmdParts = command.name.split('-');
const isConfig = cmdParts[0] === 'cfg';
const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1];
let innerhtml = '';
// innerhtml+='<tr class="table-light"><td>'+(isConfig?'<h1>':'');
innerhtml +=
'<div class="card text-white mb-3"><div class="card-header">' +
command.help.encodeHTML().replace(/\n/g, '<br />') +
'</div><div class="card-body">';
innerhtml += '<fieldset id="flds-' + command.name + '">';
if (command.argtable) {
command.argtable.forEach(function(arg) {
let placeholder = arg.datatype || '';
const ctrlname = command.name + '-' + arg.longopts;
const curvalue = getLongOps(data,command.name,arg.longopts);
let attributes = 'hasvalue=' + arg.hasvalue + ' ';
// attributes +='datatype="'+arg.datatype+'" ';
attributes += 'longopts="' + arg.longopts + '" ';
attributes += 'shortopts="' + arg.shortopts + '" ';
attributes += 'checkbox=' + arg.checkbox + ' ';
attributes += 'cmdname="' + command.name + '" ';
attributes +=
'id="' +
ctrlname +
'" name="' +
ctrlname +
'" hasvalue="' +
arg.hasvalue +
'" ';
let extraclass = arg.mincount > 0 ? 'bg-success' : '';
if (arg.glossary === 'hidden') {
attributes += ' style="visibility: hidden;"';
}
if (arg.checkbox) {
innerhtml +=
'<div class="form-check"><label class="form-check-label">';
innerhtml +=
'<input type="checkbox" ' +
attributes +
' class="form-check-input ' +
extraclass +
'" value="" >' +
arg.glossary.encodeHTML() +
'<small class="form-text text-muted">Previous value: ' +
(curvalue ? 'Checked' : 'Unchecked') +
'</small></label>';
} else {
innerhtml +=
'<div class="form-group" ><label for="' +
ctrlname +
'">' +
arg.glossary.encodeHTML() +
'</label>';
if (placeholder.includes('|')) {
extraclass = placeholder.startsWith('+') ? ' multiple ' : '';
placeholder = placeholder
.replace('<', '')
.replace('=', '')
.replace('>', '');
innerhtml += `<select ${attributes} class="form-control ${extraclass}" >`;
placeholder = '--|' + placeholder;
placeholder.split('|').forEach(function(choice) {
innerhtml += '<option >' + choice + '</option>';
});
innerhtml += '</select>';
} else {
innerhtml +=
'<input type="text" class="form-control ' +
extraclass +
'" placeholder="' +
placeholder +
'" ' +
attributes +
'>';
}
innerhtml +=
'<small class="form-text text-muted">Previous value: ' +
(curvalue || '') +
'</small>';
}
innerhtml += '</div>';
});
}
innerhtml += '<div style="margin-top: 16px;">';
innerhtml +=
'<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true" style="display: none;" id="toast_' +
command.name +
'">';
innerhtml +=
'<div class="toast-header"><strong class="mr-auto">Result</strong><button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close" onclick="$(this).parent().parent().hide()">';
innerhtml +=
'<span aria-hidden="true">×</span></button></div><div class="toast-body" id="msg_' +
command.name +
'"></div></div>';
if (isConfig) {
innerhtml +=
'<button type="submit" class="btn btn-info" id="btn-save-' +
command.name +
'" cmdname="' +
command.name +
'" onclick="runCommand(this,false)">Save</button>';
innerhtml +=
'<button type="submit" class="btn btn-warning" id="btn-commit-' +
command.name +
'" cmdname="' +
command.name +
'" onclick="runCommand(this,true)">Apply</button>';
} else {
innerhtml +=
'<button type="submit" class="btn btn-success" id="btn-run-' +
command.name +
'" cmdname="' +
command.name +
'" onclick="runCommand(this,false)">Execute</button>';
}
innerhtml += '</div></fieldset></div></div>';
if (isConfig) {
$(targetDiv).append(innerhtml);
} else {
$('#commands-list').append(innerhtml);
}
}
});
data.commands.forEach(function(command) {
$('[cmdname=' + command.name + ']:input').val('');
$('[cmdname=' + command.name + ']:checkbox').prop('checked', false);
if (command.argtable) {
command.argtable.forEach(function(arg) {
const ctrlselector = '#' + command.name + '-' + arg.longopts;
const ctrlValue = getLongOps(data,command.name,arg.longopts);
if (arg.checkbox) {
$(ctrlselector)[0].checked = ctrlValue;
} else {
if (ctrlValue !== undefined) {
$(ctrlselector)
.val(ctrlValue)
.trigger('change');
}
if (
$(ctrlselector)[0].value.length === 0 &&
(arg.datatype || '').includes('|')
) {
$(ctrlselector)[0].value = '--';
}
}
});
}
});
}).fail(function(xhr, ajaxOptions, thrownError) {
if(xhr.status==404){
$('.orec').hide();
}
else {
handleExceptionResponse(xhr, ajaxOptions, thrownError);
}
$('#commands-list').empty();
blockAjax = false;
});
}
function getConfig() {
$.getJSON('/config.json', function(entries) {
$('#nvsTable tr').remove();
const data = (entries.config? entries.config : entries);
SystemConfig = data;
Object.keys(data)
.sort()
.forEach(function(key) {
let val = data[key].value;
if (key === 'autoexec') {
if (data.autoexec.value === '0') {
$('#disable-squeezelite')[0].checked = true;
} else {
$('#disable-squeezelite')[0].checked = false;
}
} else if (key === 'autoexec1') {
const re = /-o\s?(["][^"]*["]|[^-]+)/g;
const m = re.exec(val);
if (m[1].toUpperCase().startsWith('I2S')) {
handleTemplateTypeRadio('i2s');
} else if (m[1].toUpperCase().startsWith('SPDIF')) {
handleTemplateTypeRadio('spdif');
} else if (m[1].toUpperCase().startsWith('"BT')) {
handleTemplateTypeRadio('bt');
}
} else if (key === 'host_name') {
val = val.replaceAll('"', '');
$('input#dhcp-name1').val(val);
$('input#dhcp-name2').val(val);
$('#player').val(val);
document.title = val;
hostName = val;
} else if (key === 'rel_api') {
releaseURL = val;
}
$('tbody#nvsTable').append(
'<tr>' +
'<td>' +
key +
'</td>' +
"<td class='value'>" +
"<input type='text' class='form-control nvs' id='" +
key +
"' nvs_type=" +
data[key].type +
' >' +
'</td>' +
'</tr>'
);
$('input#' + key).val(data[key].value);
});
$('tbody#nvsTable').append(
"<tr><td><input type='text' class='form-control' id='nvs-new-key' placeholder='new key'></td><td><input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 ></td></tr>"
);
if (entries.gpio) {
$('#pins').show();
$('tbody#gpiotable tr').remove();
entries.gpio.forEach(function(gpioEntry) {
$('tbody#gpiotable').append(
'<tr class=' +
(gpioEntry.fixed ? 'table-secondary' : 'table-primary') +
'><th scope="row">' +
gpioEntry.group +
'</th><td>' +
gpioEntry.name +
'</td><td>' +
gpioEntry.gpio +
'</td><td>' +
(gpioEntry.fixed ? 'Fixed' : 'Configuration') +
'</td></tr>'
);
});
}
else {
$('#pins').hide();
}
}).fail(function(xhr, ajaxOptions, thrownError) {
handleExceptionResponse(xhr, ajaxOptions, thrownError);
blockAjax = false;
});
}
function showLocalMessage(message, severity) {
const msg = {
message: message,
type: severity,
};
showMessage(msg, new Date());
}
function showMessage(msg, msgTime) {
let color = 'table-success';
if (msg.type === 'MESSAGING_WARNING') {
color = 'table-warning';
if (messageseverity === 'MESSAGING_INFO') {
messageseverity = 'MESSAGING_WARNING';
}
} else if (msg.type === 'MESSAGING_ERROR') {
if (
messageseverity === 'MESSAGING_INFO' ||
messageseverity === 'MESSAGING_WARNING'
) {
messageseverity = 'MESSAGING_ERROR';
}
color = 'table-danger';
}
if (++messagecount > 0) {
$('#msgcnt').removeClass('badge-success');
$('#msgcnt').removeClass('badge-warning');
$('#msgcnt').removeClass('badge-danger');
$('#msgcnt').addClass(pillcolors[messageseverity]);
$('#msgcnt').text(messagecount);
}
$('#syslogTable').append(
"<tr class='" +
color +
"'>" +
'<td>' +
msgTime.toLocalShort() +
'</td>' +
'<td>' +
msg.message.encodeHTML() +
'</td>' +
'</tr>'
);
}
function inRange(x, min, max) {
return (x - min) * (x - max) <= 0;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}