Compare commits

...

2 Commits

Author SHA1 Message Date
Sebastien
889b1097cc Reorganize configuration UI - release
The System tab is now hidden by default and can be enabled via a toggle
under the Credits tab, similar to how NVS tab works.  A new tab was
created to hold configurations, and display configuration was added.
2020-09-04 16:02:53 -04:00
Sebastien
41cdb8bcdd Allow saving/loading nvs from the nvs editor - release 2020-09-02 13:09:46 -04:00
6 changed files with 289 additions and 90 deletions

View File

@@ -396,3 +396,27 @@ const char *display_conf_get_driver_name(char * driver){
} }
return NULL; return NULL;
} }
/****************************************************************************************
*
*/
char * display_get_supported_drivers(){
int total_size = 1;
char * supported_drivers=NULL;
const char * separator = "|";
int separator_len = strlen(separator);
for(uint8_t i=0;known_drivers[i]!=NULL && strlen(known_drivers[i])>0;i++ ){
total_size += strlen(known_drivers[i])+separator_len;
}
total_size+=2;
supported_drivers = malloc(total_size);
memset(supported_drivers,0x00,total_size);
strcat(supported_drivers,"<");
for(uint8_t i=0;known_drivers[i]!=NULL && strlen(known_drivers[i])>0;i++ ){
supported_drivers = strcat(supported_drivers,known_drivers[i]);
supported_drivers = strcat(supported_drivers,separator);
}
strcat(supported_drivers,">");
return supported_drivers;
}

View File

@@ -10,6 +10,7 @@
#include "gds.h" #include "gds.h"
/* /*
The displayer is not thread-safe and the caller must ensure use its own The displayer is not thread-safe and the caller must ensure use its own
mutexes if it wants something better. Especially, text() line() and draw() mutexes if it wants something better. Especially, text() line() and draw()
@@ -38,3 +39,4 @@ void displayer_scroll(char *string, int speed, int pause);
void displayer_control(enum displayer_cmd_e cmd, ...); void displayer_control(enum displayer_cmd_e cmd, ...);
void displayer_metadata(char *artist, char *album, char *title); void displayer_metadata(char *artist, char *album, char *title);
void displayer_timer(enum displayer_time_e mode, int elapsed, int duration); void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
char * display_get_supported_drivers();

View File

@@ -82,15 +82,17 @@ static struct {
} i2ccheck_args; } i2ccheck_args;
static struct { static struct {
struct arg_lit *clear;
struct arg_lit *hflip;
struct arg_lit *vflip;
struct arg_lit *rotate;
struct arg_int *address;
struct arg_int *width;
struct arg_int *height;
struct arg_str *name; struct arg_str *name;
struct arg_str *driver; struct arg_str *driver;
struct arg_int *width;
struct arg_int *height;
struct arg_int *address;
struct arg_lit *rotate;
struct arg_lit *hflip;
struct arg_lit *vflip;
struct arg_int *speed;
struct arg_int *back;
struct arg_lit *clear;
struct arg_end *end; struct arg_end *end;
} i2cdisp_args; } i2cdisp_args;
@@ -368,7 +370,7 @@ static int do_i2c_show_display(int argc, char **argv){
static int do_i2c_set_display(int argc, char **argv) static int do_i2c_set_display(int argc, char **argv)
{ {
int width=0, height=0, address=60; int width=0, height=0, address=60, back=-1, speed=8000000 ;
int result = 0; int result = 0;
char * name = NULL; char * name = NULL;
char * driver= NULL; char * driver= NULL;
@@ -426,6 +428,7 @@ static int do_i2c_set_display(int argc, char **argv)
fprintf(f,"Missing parameter: --height\n"); fprintf(f,"Missing parameter: --height\n");
nerrors ++; nerrors ++;
} }
/* Check "--name" option */ /* Check "--name" option */
if (i2cdisp_args.name->count) { if (i2cdisp_args.name->count) {
name=strdup(i2cdisp_args.name->sval[0]); name=strdup(i2cdisp_args.name->sval[0]);
@@ -436,6 +439,21 @@ static int do_i2c_set_display(int argc, char **argv)
driver=strdup(i2cdisp_args.driver->sval[0]); driver=strdup(i2cdisp_args.driver->sval[0]);
} }
/* Check "--speed" option */
if (i2cdisp_args.speed->count) {
speed=i2cdisp_args.speed->ival[0];
}
/* Check "--back" option */
if (i2cdisp_args.back->count) {
back=i2cdisp_args.back->ival[0];
if(!GPIO_IS_VALID_OUTPUT_GPIO(back)){
fprintf(f,"Invalid GPIO for back light: %d %s\n", back, GPIO_IS_VALID_GPIO(back)?"has input capabilities only":"is not a GPIO");
back=-1;
nerrors ++;
}
}
if(!name) name = strdup("I2C"); if(!name) name = strdup("I2C");
if(!driver) driver = strdup("SSD1306"); if(!driver) driver = strdup("SSD1306");
@@ -456,8 +474,8 @@ static int do_i2c_set_display(int argc, char **argv)
bool rotate = i2cdisp_args.rotate->count>0; bool rotate = i2cdisp_args.rotate->count>0;
if(nerrors==0){ if(nerrors==0){
snprintf(config_string, sizeof(config_string),"%s:width=%i,height=%i,address=%i,driver=%s%s%s", snprintf(config_string, sizeof(config_string),"%s:back=%i,speed=%i,width=%i,height=%i,address=%i,driver=%s%s%s",
name,width,height,address,driver,rotate || i2cdisp_args.hflip->count?",HFlip":"",rotate || i2cdisp_args.vflip->count?",VFlip":"" ); name,back,speed,width,height,address,driver,rotate || i2cdisp_args.hflip->count?",HFlip":"",rotate || i2cdisp_args.vflip->count?",VFlip":"" );
fprintf(f,"Updating display configuration string configuration to :\n" fprintf(f,"Updating display configuration string configuration to :\n"
"display_config = \"%s\"",config_string ); "display_config = \"%s\"",config_string );
result = config_set_value(NVS_TYPE_STR, "display_config", config_string)!=ESP_OK; result = config_set_value(NVS_TYPE_STR, "display_config", config_string)!=ESP_OK;
@@ -897,15 +915,19 @@ cJSON * i2c_set_display_cb(){
} }
static void register_i2c_set_display(){ static void register_i2c_set_display(){
char * supported_drivers = display_get_supported_drivers();
i2cdisp_args.address = arg_int0("a", "address", "<n>", "Set the device address, default 60"); i2cdisp_args.address = arg_int0("a", "address", "<n>", "Set the device address, default 60");
i2cdisp_args.width = arg_int0("w", "width", "<n>", "Set the display width"); i2cdisp_args.width = arg_int0("w", "width", "<n>", "Set the display width");
i2cdisp_args.height = arg_int0("h", "height", "<n>", "Set the display height"); i2cdisp_args.height = arg_int0("h", "height", "<n>", "Set the display height");
i2cdisp_args.name = arg_str0("t", "type", "<I2C|SPI>", "Display type, I2C or SPI. Default I2C"); i2cdisp_args.name = arg_str0("t", "type", "<I2C|SPI>", "Display type, I2C or SPI. Default I2C");
i2cdisp_args.driver = arg_str0("d", "driver", "<string>", "Set the display driver name. Default SSD1306"); i2cdisp_args.driver = arg_str0("d", "driver", supported_drivers?supported_drivers:"<string>", "Set the display driver name. Default SSD1306");
i2cdisp_args.clear = arg_lit0(NULL, "clear", "clear configuration and return"); i2cdisp_args.clear = arg_lit0(NULL, "clear", "clear configuration and return");
i2cdisp_args.hflip = arg_lit0(NULL, "hf", "Flip picture horizontally"); i2cdisp_args.hflip = arg_lit0(NULL, "hf", "Flip picture horizontally");
i2cdisp_args.vflip = arg_lit0(NULL, "vf", "Flip picture vertically"); i2cdisp_args.vflip = arg_lit0(NULL, "vf", "Flip picture vertically");
i2cdisp_args.rotate = arg_lit0("r", "rotate", "Rotate the picture 180 deg"); i2cdisp_args.rotate = arg_lit0("r", "rotate", "Rotate the picture 180 deg");
i2cdisp_args.back = arg_int0("b", "back", "<n>","Backlight GPIO (if applicable)");
i2cdisp_args.speed = arg_int0("s", "speed", "<n>","Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz");
i2cdisp_args.end = arg_end(8); i2cdisp_args.end = arg_end(8);
const esp_console_cmd_t i2c_set_display= { const esp_console_cmd_t i2c_set_display= {
.command = "setdisplay", .command = "setdisplay",

View File

@@ -63,6 +63,7 @@ var checkStatusInterval = null;
var StatusIntervalActive = false; var StatusIntervalActive = false;
var RefreshAPIIntervalActive = false; var RefreshAPIIntervalActive = false;
var LastRecoveryState=null; var LastRecoveryState=null;
var LastCommandsState=null;
var output = ''; var output = '';
function stopCheckStatusInterval(){ function stopCheckStatusInterval(){
@@ -100,8 +101,98 @@ function RepeatRefreshAPInterval(){
if(RefreshAPIIntervalActive) if(RefreshAPIIntervalActive)
startRefreshAPInterval(); startRefreshAPInterval();
} }
function getConfigJson(slimMode){
var config = {};
$("input.nvs").each(function() {
var key = $(this)[0].id;
var val = $(this).val();
if(!slimMode){
var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10);
if (key != '') {
config[key] = {};
if(nvs_type == nvs_type_t.NVS_TYPE_U8
|| nvs_type == nvs_type_t.NVS_TYPE_I8
|| nvs_type == nvs_type_t.NVS_TYPE_U16
|| nvs_type == nvs_type_t.NVS_TYPE_I16
|| nvs_type == nvs_type_t.NVS_TYPE_U32
|| nvs_type == nvs_type_t.NVS_TYPE_I32
|| nvs_type == nvs_type_t.NVS_TYPE_U64
|| nvs_type == nvs_type_t.NVS_TYPE_I64) {
config[key].value = parseInt(val);
}
else {
config[key].value = val;
}
config[key].type = nvs_type;
}
}
else {
config[key] = val;
}
});
var key = $("#nvs-new-key").val();
var 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;
}
$(document).ready(function(){
function onFileLoad(elementId, event) {
var data={};
try{
data = JSON.parse(elementId.srcElement.result);
}
catch (e){
alert('Parsing failed!\r\n '+ e);
}
$("input.nvs").each(function() {
var key = $(this)[0].id;
var val = $(this).val();
if(data[key]){
if(data[key] != val){
console.log("Changed "& key & " " & val & "==>" & data[key]);
$(this).val(data[key]);
}
}
else {
console.log("Value " & key & " missing from file");
}
});
}
function onChooseFile(event, onLoadFileHandler) {
if (typeof window.FileReader !== 'function')
throw ("The file API isn't supported on this browser.");
let 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;
let file = input.files[0];
let fr = new FileReader();
fr.onload = onLoadFileHandler;
fr.readAsText(file);
input.value="";
}
$(document).ready(function(){
$("input#show-commands")[0].checked=LastCommandsState==1?true:false;
$('a[href^="#tab-commands"]').hide();
$("#load-nvs").click(function () {
$("#nvsfilename").trigger('click');
});
$("#wifi-status").on("click", ".ape", function() { $("#wifi-status").on("click", ".ape", function() {
$( "#wifi" ).slideUp( "fast", function() {}); $( "#wifi" ).slideUp( "fast", function() {});
$( "#connect-details" ).slideDown( "fast", function() {}); $( "#connect-details" ).slideDown( "fast", function() {});
@@ -210,6 +301,17 @@ $(document).ready(function(){
$( "#wifi" ).slideDown( "fast", function() {}) $( "#wifi" ).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() { $("input#show-nvs").on("click", function() {
this.checked=this.checked?1:0; this.checked=this.checked?1:0;
if(this.checked){ if(this.checked){
@@ -334,42 +436,27 @@ $(document).ready(function(){
console.log('sent config JSON with data:', JSON.stringify(data)); console.log('sent config JSON with data:', JSON.stringify(data));
}); });
$("#save-as-nvs").on("click", function() {
var data = { 'timestamp': Date.now() };
var 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" + Date.now() +"json");
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
console.log('sent config JSON with headers:', JSON.stringify(headers));
console.log('sent config JSON with data:', JSON.stringify(data));
});
$("#save-nvs").on("click", function() { $("#save-nvs").on("click", function() {
var headers = {}; var headers = {};
var data = { 'timestamp': Date.now() }; var data = { 'timestamp': Date.now() };
var config = {}; var config = getConfigJson(false);
$("input.nvs").each(function() {
var key = $(this)[0].id;
var val = $(this).val();
var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10);
if (key != '') {
config[key] = {};
if(nvs_type == nvs_type_t.NVS_TYPE_U8
|| nvs_type == nvs_type_t.NVS_TYPE_I8
|| nvs_type == nvs_type_t.NVS_TYPE_U16
|| nvs_type == nvs_type_t.NVS_TYPE_I16
|| nvs_type == nvs_type_t.NVS_TYPE_U32
|| nvs_type == nvs_type_t.NVS_TYPE_I32
|| nvs_type == nvs_type_t.NVS_TYPE_U64
|| nvs_type == nvs_type_t.NVS_TYPE_I64) {
config[key].value = parseInt(val);
}
else {
config[key].value = val;
}
config[key].type = nvs_type;
}
});
var key = $("#nvs-new-key").val();
var val = $("#nvs-new-value").val();
if (key != '') {
// headers["X-Custom-" +key] = val;
config[key] = {};
config[key].value = val;
config[key].type = 33;
}
data['config'] = config; data['config'] = config;
$.ajax({ $.ajax({
url: '/config.json', url: '/config.json',
@@ -421,7 +508,6 @@ $(document).ready(function(){
fwurl : { fwurl : {
value : url, value : url,
type : 33 type : 33
} }
}; };
@@ -935,7 +1021,8 @@ function checkStatus(){
blockAjax = false; blockAjax = false;
}); });
} }
function runCommand(button) {
function runCommand(button,reboot) {
pardiv = button.parentNode.parentNode; pardiv = button.parentNode.parentNode;
fields=document.getElementById("flds-"+button.value); fields=document.getElementById("flds-"+button.value);
cmdstring=button.value+' '; cmdstring=button.value+' ';
@@ -984,7 +1071,32 @@ function runCommand(button) {
console.log(xhr.status); console.log(xhr.status);
console.log(thrownError); console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
},
complete: function(response) {
//var returnedResponse = JSON.parse(response.responseText);
console.log(response.responseText);
if(reboot){
showMessage('Applying. Please wait for the ESP32 to reboot', 'MESSAGING_WARNING');
console.log('now triggering reboot');
$.ajax({
url: '/reboot.json',
dataType: 'text',
method: 'POST',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ 'timestamp': Date.now()}),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
},
complete: function(response) {
console.log('reboot call completed');
}
});
}
}
}); });
enableStatusTimer = true; enableStatusTimer = true;
} }
@@ -993,60 +1105,82 @@ function runCommand(button) {
function getCommands() { function getCommands() {
$.getJSON("/commands.json", function(data) { $.getJSON("/commands.json", function(data) {
console.log(data); console.log(data);
innerhtml=''; var advancedtabhtml='';
data.commands.forEach(function(command) { data.commands.forEach(function(command) {
innerhtml+='<tr><td>'; isConfig=($('#'+command.name+'-list').length>0);
innerhtml+=escapeHTML(command.help).replace(/\n/g, '<br />')+'<br>'; innerhtml='';
innerhtml+='<tr><td>'+(isConfig?'<h1>':'');
innerhtml+=escapeHTML(command.help).replace(/\n/g, '<br />')+(isConfig?'</h1>':'<br>');
innerhtml+='<div >'; innerhtml+='<div >';
if(command.hasOwnProperty("argtable")){ if(command.hasOwnProperty("argtable")){
innerhtml+='<table class="table table-hover" id="flds-'+command.name+'"><tbody>'; innerhtml+='<table class="table table-hover" id="flds-'+command.name+'"><tbody>';
command.argtable.forEach(function (arg){ command.argtable.forEach(function (arg){
innerhtml+="<tr>";
ctrlname=command.name+'-'+arg.longopts;
innerhtml+='<td><label for="'+ctrlname+'">'+ arg.glossary+'</label></td>';
ctrltype="text";
if(arg.checkbox){
ctrltype="checkbox";
}
curvalue=data.values?.[command.name]?.[arg.longopts] || '';
placeholder=arg?.datatype || ''; placeholder=arg?.datatype || '';
innerhtml+='<td><input type="'+ctrltype+'" id="'+ctrlname+'" name="'+ctrlname+'" placeholder="'+placeholder+'" hasvalue="'+arg.hasvalue+'" '; ctrlname=command.name+'-'+arg.longopts;
curvalue=data.values?.[command.name]?.[arg.longopts] || '';
innerhtml+="<tr>";
var attributes ='datatype="'+arg.datatype+'" ';
attributes+='hasvalue='+arg.hasvalue+' ';
attributes+='longopts="'+arg.longopts+'" ';
attributes+='shortopts="'+arg.shortopts+'" ';
attributes+='checkbox='+arg.checkbox+' ';
innerhtml+='datatype="'+arg.datatype+'" '; if(placeholder.includes('|')){
innerhtml+='hasvalue='+arg.hasvalue+' '; placeholder = placeholder.replace('<','').replace('>','');
innerhtml+='longopts="'+arg.longopts+'" '; innerhtml+='<td><select name="'+ctrlname+'" ';
innerhtml+='shortopts="'+arg.shortopts+'" '; innerhtml+=attributes;
innerhtml+='checkbox='+arg.checkbox+' '; innerhtml+=' class="custom-select">';
innerhtml+='<option '+(curvalue.length>0?'value':'selected')+'>'+arg.glossary+'</option>'
placeholder.split('|').forEach(function(choice){
innerhtml+='<option '+(curvalue.length>0&&curvalue==choice?'selected':'value')+'="'+choice+'">'+choice+'</option>';
if(arg.checkbox){ });
if(curvalue=data.values?.[command.name]?.[arg.longopts] ){ innerhtml+='</select></td>';
innerhtml+='checked=true ';
}
else{
innerhtml+='checked=false ';
}
innerhtml+='></input></td>';
} }
else { else {
innerhtml+='value="'+curvalue+'" '; ctrltype="text";
innerhtml+='></input></td>'+ curvalue.length>0?'<td>last: '+curvalue+'</td>':''; if(arg.checkbox){
} ctrltype="checkbox";
}
innerhtml+='<td><label for="'+ctrlname+'">'+ arg.glossary+'</label></td>';
innerhtml+='<td><input type="'+ctrltype+'" id="'+ctrlname+'" name="'+ctrlname+'" placeholder="'+placeholder+'" hasvalue="'+arg.hasvalue+'" ';
innerhtml+=attributes;
if(arg.checkbox){
if(data.values?.[command.name]?.[arg.longopts] ){
innerhtml+='checked=true ';
}
else{
innerhtml+='checked=false ';
}
innerhtml+='></input></td>';
}
else {
innerhtml+='value="'+curvalue+'" ';
innerhtml+='></input></td>'+ curvalue.length>0?'<td>last: '+curvalue+'</td>':'';
}
}
innerhtml+="</tr>"; innerhtml+="</tr>";
}); });
innerhtml+='</tbody></table><br>'; innerhtml+='</tbody></table>';
} }
innerhtml+='<div class="buttons"><input id="btn-'+ command.name + '" type="button" class="btn btn-danger btn-sm" value="'+command.name+'" onclick="runCommand(this);"></div></div><td></tr>'; if(isConfig){
innerhtml+='<div class="buttons"><input id="btn-'+ command.name + '" type="button" class="btn btn-success" value="Save" onclick="runCommand(this,false);">';
innerhtml+='<input id="btn-'+ command.name + '-apply" type="button" class="btn btn-success" value="Apply" onclick="runCommand(this,true);"></div></div><td></tr>';
$('#'+command.name+'-list').append(innerhtml);
}
else {
advancedtabhtml+='<br>'+innerhtml;
advancedtabhtml+='<div class="buttons"><input id="btn-'+ command.name + '" type="button" class="btn btn-danger btn-sm" value="'+command.name+'" onclick="runCommand(this);"></div></div><td></tr>';
}
});
$("#commands-list").append(advancedtabhtml);
});
$("#commands-list").append(innerhtml);
}) })
.fail(function(xhr, ajaxOptions, thrownError) { .fail(function(xhr, ajaxOptions, thrownError) {

View File

@@ -721,6 +721,7 @@ esp_err_t config_post_handler(httpd_req_t *req){
if(err==ESP_OK){ if(err==ESP_OK){
httpd_resp_sendstr(req, "{ \"result\" : \"OK\" }"); httpd_resp_sendstr(req, "{ \"result\" : \"OK\" }");
messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"Save Success");
} }
cJSON_Delete(root); cJSON_Delete(root);
if(bOTA) { if(bOTA) {

View File

@@ -67,11 +67,14 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a> <a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-setdisplay">Display</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-syslog">Syslog</a> <a class="nav-link" data-toggle="tab" href="#tab-syslog">Syslog</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-commands">System</a> <a class="nav-link" data-toggle="tab" href="#tab-commands">Advanced</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a> <a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a>
@@ -195,6 +198,12 @@
</div> </div>
</div> </div>
</div> <!-- wifi --> </div> <!-- wifi -->
<div class="tab-pane fade" id="tab-setdisplay">
<table class="table table-hover" id="setdisplay-table">
<tbody id="setdisplay-list">
</tbody>
</table>
</div> <!-- display -->
<div class="tab-pane fade" id="tab-audio"> <div class="tab-pane fade" id="tab-audio">
<div id="audioout"> <div id="audioout">
@@ -304,7 +313,6 @@
</tbody> </tbody>
</table> </table>
</div> <!-- system --> </div> <!-- system -->
<div class="tab-pane fade" id="tab-syslog"> <div class="tab-pane fade" id="tab-syslog">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
@@ -332,13 +340,16 @@
<tbody id="nvsTable"> <tbody id="nvsTable">
</tbody> </tbody>
</table> </table>
<div class="buttons"> <div class="buttons">
<div id="boot-div"> <div id="boot-div">
<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe"> <form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button> <button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
</form> </form>
</div> </div>
<input id="save-nvs" type="button" class="btn btn-success" value="Save" /> <input id="save-nvs" type="button" class="btn btn-success" value="Commit">
<input id="save-as-nvs" type="button" class="btn btn-success" value="Download config">
<input id="load-nvs" type="button" class="btn btn-success" value="Load File">
<input aria-describedby="fileHelp" onchange="onChooseFile(event, onFileLoad.bind(this))" id="nvsfilename" type="file" style="display:none">
</div> </div>
</div> <!-- nvs --> </div> <!-- nvs -->
@@ -363,6 +374,11 @@
<input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked"> <input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked">
<label class="custom-control-label" for="show-nvs"></label> <label class="custom-control-label" for="show-nvs"></label>
</div> </div>
<h2>Show Advanced Commands</h2>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="show-commands" checked="checked">
<label class="custom-control-label" for="show-commands"></label>
</div>
</div> <!-- credits --> </div> <!-- credits -->
</div> </div>
<footer class="footer"><span id="foot-fw"></span><span id="foot-wifi"></span></footer> <footer class="footer"><span id="foot-fw"></span><span id="foot-wifi"></span></footer>