Browse Source

Unified web file to improve reliability

fastled
Xose Pérez 7 years ago
parent
commit
eed5acc346
11 changed files with 257 additions and 247 deletions
  1. +3
    -0
      code/espurna/config/general.h
  2. BIN
      code/espurna/data/index.html.gz
  3. BIN
      code/espurna/data/password.html.gz
  4. BIN
      code/espurna/data/script.js.gz
  5. BIN
      code/espurna/data/style.css.gz
  6. +159
    -144
      code/espurna/web.ino
  7. +1
    -1
      code/gulpfile.js
  8. +4
    -0
      code/html/custom.css
  9. +42
    -25
      code/html/custom.js
  10. +48
    -1
      code/html/index.html
  11. +0
    -76
      code/html/password.html

+ 3
- 0
code/espurna/config/general.h View File

@ -93,6 +93,9 @@
#define WEBSERVER_PORT 80 #define WEBSERVER_PORT 80
#define DNS_PORT 53 #define DNS_PORT 53
#define WEB_MODE_NORMAL 0
#define WEB_MODE_PASSWORD 1
#define AP_MODE AP_MODE_ALONE #define AP_MODE AP_MODE_ALONE
#define AP_MODE_IP "192.168.4.1" #define AP_MODE_IP "192.168.4.1"
#define AP_MODE_GW "192.168.4.1" #define AP_MODE_GW "192.168.4.1"


BIN
code/espurna/data/index.html.gz View File


BIN
code/espurna/data/password.html.gz View File


BIN
code/espurna/data/script.js.gz View File


BIN
code/espurna/data/style.css.gz View File


+ 159
- 144
code/espurna/web.ino View File

@ -101,6 +101,8 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
JsonArray& config = root["config"]; JsonArray& config = root["config"];
DEBUG_MSG("[WEBSOCKET] Parsing configuration data\n"); DEBUG_MSG("[WEBSOCKET] Parsing configuration data\n");
unsigned char webMode = WEB_MODE_NORMAL;
bool save = false; bool save = false;
bool changed = false; bool changed = false;
bool changedMQTT = false; bool changedMQTT = false;
@ -166,6 +168,11 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
} }
} }
if (key == "webMode") {
webMode = value.toInt();
continue;
}
// Check password // Check password
if (key == "adminPass1") { if (key == "adminPass1") {
adminPass = value; adminPass = value;
@ -222,36 +229,40 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
} }
// Checkboxes
if (apiEnabled != (getSetting("apiEnabled").toInt() == 1)) {
setSetting("apiEnabled", apiEnabled);
save = changed = true;
}
#if ENABLE_FAUXMO
if (fauxmoEnabled != (getSetting("fauxmoEnabled").toInt() == 1)) {
setSetting("fauxmoEnabled", fauxmoEnabled);
if (webMode == WEB_MODE_NORMAL) {
// Checkboxes
if (apiEnabled != (getSetting("apiEnabled").toInt() == 1)) {
setSetting("apiEnabled", apiEnabled);
save = changed = true; save = changed = true;
} }
#endif
#if ENABLE_FAUXMO
if (fauxmoEnabled != (getSetting("fauxmoEnabled").toInt() == 1)) {
setSetting("fauxmoEnabled", fauxmoEnabled);
save = changed = true;
}
#endif
// Clean wifi networks
for (int i = 0; i < network; i++) {
if (getSetting("pass" + String(i)).length() == 0) delSetting("pass" + String(i));
if (getSetting("ip" + String(i)).length() == 0) delSetting("ip" + String(i));
if (getSetting("gw" + String(i)).length() == 0) delSetting("gw" + String(i));
if (getSetting("mask" + String(i)).length() == 0) delSetting("mask" + String(i));
if (getSetting("dns" + String(i)).length() == 0) delSetting("dns" + String(i));
}
for (int i = network; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() > 0) {
save = changed = true;
// Clean wifi networks
for (int i = 0; i < network; i++) {
if (getSetting("pass" + String(i)).length() > 0) delSetting("pass" + String(i));
if (getSetting("ip" + String(i)).length() == 0) delSetting("ip" + String(i));
if (getSetting("gw" + String(i)).length() == 0) delSetting("gw" + String(i));
if (getSetting("mask" + String(i)).length() == 0) delSetting("mask" + String(i));
if (getSetting("dns" + String(i)).length() == 0) delSetting("dns" + String(i));
}
for (int i = network; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() > 0) {
save = changed = true;
}
delSetting("ssid" + String(i));
delSetting("pass" + String(i));
delSetting("ip" + String(i));
delSetting("gw" + String(i));
delSetting("mask" + String(i));
delSetting("dns" + String(i));
} }
delSetting("ssid" + String(i));
delSetting("pass" + String(i));
delSetting("ip" + String(i));
delSetting("gw" + String(i));
delSetting("mask" + String(i));
delSetting("dns" + String(i));
} }
// Save settings // Save settings
@ -297,130 +308,146 @@ void _wsStart(uint32_t client_id) {
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
root["buildDate"] = __DATE__;
root["buildTime"] = __TIME__;
root["manufacturer"] = String(MANUFACTURER);
root["chipid"] = chipid;
root["mac"] = WiFi.macAddress();
root["device"] = String(DEVICE);
root["hostname"] = getSetting("hostname", HOSTNAME);
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["mqttStatus"] = mqttConnected();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
relay.add(relayStatus(relayID));
}
root["relayMode"] = getSetting("relayMode", RELAY_MODE);
root["relayPulseMode"] = getSetting("relayPulseMode", RELAY_PULSE_MODE);
root["relayPulseTime"] = getSetting("relayPulseTime", RELAY_PULSE_TIME);
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
}
bool changePassword = false;
#if FORCE_CHANGE_PASS == 1
String adminPass = getSetting("adminPass", ADMIN_PASS);
if (adminPass.equals(ADMIN_PASS)) changePassword = true;
#endif
root["webPort"] = getSetting("webPort", WEBSERVER_PORT).toInt();
if (changePassword) {
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["webMode"] = WEB_MODE_PASSWORD;
root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt();
} else {
#if ENABLE_DOMOTICZ
root["webMode"] = WEB_MODE_NORMAL;
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
root["buildDate"] = __DATE__;
root["buildTime"] = __TIME__;
root["manufacturer"] = String(MANUFACTURER);
root["chipid"] = chipid;
root["mac"] = WiFi.macAddress();
root["device"] = String(DEVICE);
root["hostname"] = getSetting("hostname", HOSTNAME);
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["mqttStatus"] = mqttConnected();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
relay.add(relayStatus(relayID));
}
root["relayMode"] = getSetting("relayMode", RELAY_MODE);
root["relayPulseMode"] = getSetting("relayPulseMode", RELAY_PULSE_MODE);
root["relayPulseTime"] = getSetting("relayPulseTime", RELAY_PULSE_TIME);
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
}
root["dczVisible"] = 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
root["webPort"] = getSetting("webPort", WEBSERVER_PORT).toInt();
JsonArray& dczRelayIdx = root.createNestedArray("dczRelayIdx");
for (byte i=0; i<relayCount(); i++) {
dczRelayIdx.add(relayToIdx(i));
}
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiKey"] = getSetting("apiKey");
#if ENABLE_DHT
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
root["dczHumIdx"] = getSetting("dczHumIdx").toInt();
#endif
root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt();
#if ENABLE_DS18B20
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
#endif
#if ENABLE_DOMOTICZ
#if ENABLE_EMON
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#endif
root["dczVisible"] = 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& dczRelayIdx = root.createNestedArray("dczRelayIdx");
for (byte i=0; i<relayCount(); i++) {
dczRelayIdx.add(relayToIdx(i));
}
#if ENABLE_DHT
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
root["dczHumIdx"] = getSetting("dczHumIdx").toInt();
#endif
#if ENABLE_DS18B20
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
#endif
#if ENABLE_EMON
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#endif
#if ENABLE_POW
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#endif
#if ENABLE_POW
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#endif #endif
#endif
#if ENABLE_FAUXMO
root["fauxmoVisible"] = 1;
root["fauxmoEnabled"] = getSetting("fauxmoEnabled", FAUXMO_ENABLED).toInt() == 1;
#endif
#if ENABLE_FAUXMO
root["fauxmoVisible"] = 1;
root["fauxmoEnabled"] = getSetting("fauxmoEnabled", FAUXMO_ENABLED).toInt() == 1;
#endif
#if ENABLE_DS18B20
root["dsVisible"] = 1;
root["dsTmp"] = getDSTemperature();
#endif
#if ENABLE_DS18B20
root["dsVisible"] = 1;
root["dsTmp"] = getDSTemperature();
#endif
#if ENABLE_DHT
root["dhtVisible"] = 1;
root["dhtTmp"] = getDHTTemperature();
root["dhtHum"] = getDHTHumidity();
#endif
#if ENABLE_DHT
root["dhtVisible"] = 1;
root["dhtTmp"] = getDHTTemperature();
root["dhtHum"] = getDHTHumidity();
#endif
#if ENABLE_RF
root["rfVisible"] = 1;
root["rfChannel"] = getSetting("rfChannel", RF_CHANNEL);
root["rfDevice"] = getSetting("rfDevice", RF_DEVICE);
#endif
#if ENABLE_RF
root["rfVisible"] = 1;
root["rfChannel"] = getSetting("rfChannel", RF_CHANNEL);
root["rfDevice"] = getSetting("rfDevice", RF_DEVICE);
#endif
#if ENABLE_EMON
root["emonVisible"] = 1;
root["emonPower"] = getPower();
root["emonMains"] = getSetting("emonMains", EMON_MAINS_VOLTAGE);
root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO);
#endif
#if ENABLE_EMON
root["emonVisible"] = 1;
root["emonPower"] = getPower();
root["emonMains"] = getSetting("emonMains", EMON_MAINS_VOLTAGE);
root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO);
#endif
#if ENABLE_POW
root["powVisible"] = 1;
root["powActivePower"] = getActivePower();
root["powApparentPower"] = getApparentPower();
root["powReactivePower"] = getReactivePower();
root["powVoltage"] = getVoltage();
root["powCurrent"] = getCurrent();
root["powPowerFactor"] = getPowerFactor();
#endif
#if ENABLE_POW
root["powVisible"] = 1;
root["powActivePower"] = getActivePower();
root["powApparentPower"] = getApparentPower();
root["powReactivePower"] = getReactivePower();
root["powVoltage"] = getVoltage();
root["powCurrent"] = getCurrent();
root["powPowerFactor"] = getPowerFactor();
#endif
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid" + String(i));
network["pass"] = getSetting("pass" + String(i));
network["ip"] = getSetting("ip" + String(i));
network["gw"] = getSetting("gw" + String(i));
network["mask"] = getSetting("mask" + String(i));
network["dns"] = getSetting("dns" + String(i));
}
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid" + String(i));
network["pass"] = getSetting("pass" + String(i));
network["ip"] = getSetting("ip" + String(i));
network["gw"] = getSetting("gw" + String(i));
network["mask"] = getSetting("mask" + String(i));
network["dns"] = getSetting("dns" + String(i));
} }
String output; String output;
@ -650,21 +677,9 @@ void _onRPC(AsyncWebServerRequest *request) {
} }
void _onHome(AsyncWebServerRequest *request) { void _onHome(AsyncWebServerRequest *request) {
webLogRequest(request); webLogRequest(request);
if (!_authenticate(request)) return request->requestAuthentication(); if (!_authenticate(request)) return request->requestAuthentication();
#if FORCE_CHANGE_PASS == 1
String password = getSetting("adminPass", ADMIN_PASS);
if (password.equals(ADMIN_PASS)) {
request->send(SPIFFS, "/password.html");
} else {
request->send(SPIFFS, "/index.html");
}
#else
request->send(SPIFFS, "/index.html");
#endif
request->send(SPIFFS, "/index.html");
} }
void _onAuth(AsyncWebServerRequest *request) { void _onAuth(AsyncWebServerRequest *request) {


+ 1
- 1
code/gulpfile.js View File

@ -91,4 +91,4 @@ gulp.task('html', function() {
/* Build file system */ /* Build file system */
gulp.task('buildfs_split', ['clean', 'files', 'html']); gulp.task('buildfs_split', ['clean', 'files', 'html']);
gulp.task('buildfs_inline', ['clean', 'files', 'inline']); gulp.task('buildfs_inline', ['clean', 'files', 'inline']);
gulp.task('default', ['buildfs_split']);
gulp.task('default', ['buildfs_inline']);

+ 4
- 0
code/html/custom.css View File

@ -28,6 +28,7 @@
width: 100px; width: 100px;
margin: 5px auto; margin: 5px auto;
} }
.button-update-password,
.button-update { .button-update {
background: #1f8dd6; background: #1f8dd6;
} }
@ -86,3 +87,6 @@ div.hint {
.pure-form .center { .pure-form .center {
margin: .5em 0 .2em; margin: .5em 0 .2em;
} }
.webmode {
display: none;
}

+ 42
- 25
code/html/custom.js View File

@ -10,9 +10,7 @@ function checkPassword(str) {
return re.test(str); return re.test(str);
} }
function validateForm() {
var form = $("#formSave");
function validateForm(form) {
// password // password
var adminPass1 = $("input[name='adminPass1']", form).val(); var adminPass1 = $("input[name='adminPass1']", form).val();
@ -32,8 +30,9 @@ function validateForm() {
} }
function doUpdate() { function doUpdate() {
if (validateForm()) {
var data = $("#formSave").serializeArray();
var form = $("#formSave");
if (validateForm(form)) {
var data = form.serializeArray();
websock.send(JSON.stringify({'config': data})); websock.send(JSON.stringify({'config': data}));
$(".powExpected").val(0); $(".powExpected").val(0);
$("input[name='powExpectedReset']") $("input[name='powExpectedReset']")
@ -43,6 +42,15 @@ function doUpdate() {
return false; return false;
} }
function doUpdatePassword() {
var form = $("#formPassword");
if (validateForm(form)) {
var data = form.serializeArray();
websock.send(JSON.stringify({'config': data}));
}
return false;
}
function doReset() { function doReset() {
var response = window.confirm("Are you sure you want to reset the device?"); var response = window.confirm("Are you sure you want to reset the device?");
if (response == false) return false; if (response == false) return false;
@ -168,6 +176,24 @@ function addNetwork() {
} }
function forgetCredentials() {
$.ajax({
'method': 'GET',
'url': '/',
'async': false,
'username': "logmeout",
'password': "123456",
'headers': { "Authorization": "Basic xxx" }
}).done(function(data) {
return false;
// If we don't get an error, we actually got an error as we expect an 401!
}).fail(function(){
// We expect to get an 401 Unauthorized error! In this case we are successfully
// logged out and we redirect the user.
return true;
});
}
function processData(data) { function processData(data) {
// title // title
@ -185,31 +211,21 @@ function processData(data) {
Object.keys(data).forEach(function(key) { Object.keys(data).forEach(function(key) {
// Web Modes
if (key == "webMode") {
password = data.webMode == 1;
$("#layout").toggle(data.webMode == 0);
$("#password").toggle(data.webMode == 1);
}
// Actions // Actions
if (key == "action") { if (key == "action") {
if (data.action == "reload") { if (data.action == "reload") {
if (password) {
// Forget current authentication
$.ajax({
'method': 'GET',
'url': '/',
'async': false,
'username': "logmeout",
'password': "123456",
'headers': { "Authorization": "Basic xxx" }
}).done(function(data) {
// If we don't get an error, we actually got an error as we expect an 401!
}).fail(function(){
// We expect to get an 401 Unauthorized error! In this case we are successfully
// logged out and we redirect the user.
window.location = "/";
});
} else {
if (password) forgetCredentials();
setTimeout(function() {
window.location = "/"; window.location = "/";
}
}, 1000);
} }
return; return;
@ -368,6 +384,7 @@ function init() {
$("#menuLink").on('click', toggleMenu); $("#menuLink").on('click', toggleMenu);
$(".button-update").on('click', doUpdate); $(".button-update").on('click', doUpdate);
$(".button-update-password").on('click', doUpdatePassword);
$(".button-reset").on('click', doReset); $(".button-reset").on('click', doReset);
$(".button-reconnect").on('click', doReconnect); $(".button-reconnect").on('click', doReconnect);
$(".button-apikey").on('click', doGenerateAPIKey); $(".button-apikey").on('click', doGenerateAPIKey);


+ 48
- 1
code/html/index.html View File

@ -19,7 +19,52 @@
<body> <body>
<div id="layout">
<div id="password" class="webmode">
<div class="content">
<form id="formPassword" class="pure-form" action="/" method="post">
<input class="pure-u-1 pure-u-sm-3-4" type="hidden" name="webMode" value="1" />
<div class="panel" style="display: block;">
<div class="header">
<h1>SECURITY</h1>
<h2>Before using this device you have to change the default password for the user 'admin'. This password will be used for the <strong>AP mode hotspot</strong>, the <strong>web interface</strong> (where you are now) and the <strong>over-the-air updates</strong>.</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="1" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It should have at least <strong>eight characters</strong> (letters, numbers or the underscore) and at least <strong>one number</strong>, <strong>one lowercase</strong> and <strong>one uppercase</strong> letter.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="2" />
</div>
<button class="pure-button button-update-password">Update</button>
</fieldset>
</div>
</div>
</form>
</div> <!-- content -->
</div>
<div id="layout" class="webmode">
<a href="#menu" id="menuLink" class="menu-link"> <a href="#menu" id="menuLink" class="menu-link">
<span></span> <span></span>
@ -176,6 +221,8 @@
<form id="formSave" class="pure-form" action="/" method="post"> <form id="formSave" class="pure-form" action="/" method="post">
<input class="pure-u-1 pure-u-sm-3-4" type="hidden" name="webMode" value="0" />
<div class="panel" id="panel-general"> <div class="panel" id="panel-general">
<div class="header"> <div class="header">


+ 0
- 76
code/html/password.html View File

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ESPurna 0.0.0</title>
<meta charset="utf-8" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- build:css style.css -->
<link rel="stylesheet" href="pure-min.css" />
<link rel="stylesheet" href="side-menu.css" />
<link rel="stylesheet" href="grids-responsive-min.css" />
<link rel="stylesheet" href="checkboxes.css" />
<link rel="stylesheet" href="custom.css" />
<!-- endbuild -->
</head>
<body>
<div id="layout">
<div class="content">
<form id="formSave" class="pure-form" action="/" method="post">
<div class="panel" style="display: block;">
<div class="header">
<h1>SECURITY</h1>
<h2>Before using this device you have to change the default password for the user 'admin'. This password will be used for the <strong>AP mode hotspot</strong>, the <strong>web interface</strong> (where you are now) and the <strong>over-the-air updates</strong>.</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="1" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It should have at least <strong>eight characters</strong> (letters, numbers or the underscore) and at least <strong>one number</strong>, <strong>one lowercase</strong> and <strong>one uppercase</strong> letter.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="2" />
</div>
<button class="pure-button button-update">Update</button>
</fieldset>
</div>
</div>
</form>
</div> <!-- content -->
</div> <!-- layout -->
</body>
<!-- build:js script.js -->
<script src="jquery-1.12.3.min.js"></script>
<script src="checkboxes.js"></script>
<script src="custom.js"></script>
<!-- endbuild -->
<script>
password = true;
</script>
</html>

Loading…
Cancel
Save