Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

637 lines
18 KiB

8 years ago
7 years ago
7 years ago
8 years ago
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. var websock;
  2. var password = false;
  3. var maxNetworks;
  4. var host;
  5. var port;
  6. // http://www.the-art-of-web.com/javascript/validate-password/
  7. function checkPassword(str) {
  8. // at least one number, one lowercase and one uppercase letter
  9. // at least eight characters that are letters, numbers or the underscore
  10. var re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{8,}$/;
  11. return re.test(str);
  12. }
  13. function validateForm(form) {
  14. // password
  15. var adminPass1 = $("input[name='adminPass1']", form).val();
  16. if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
  17. alert("The password you have entered is not valid, it must have at least 8 characters, 1 lower and 1 uppercase and 1 number!");
  18. return false;
  19. }
  20. var adminPass2 = $("input[name='adminPass2']", form).val();
  21. if (adminPass1 != adminPass2) {
  22. alert("Passwords are different!");
  23. return false;
  24. }
  25. return true;
  26. }
  27. function doUpdate() {
  28. var form = $("#formSave");
  29. if (validateForm(form)) {
  30. var data = form.serializeArray();
  31. delete(data['filename']);
  32. websock.send(JSON.stringify({'config': data}));
  33. $(".powExpected").val(0);
  34. $("input[name='powExpectedReset']")
  35. .prop("checked", false)
  36. .iphoneStyle("refresh");
  37. }
  38. return false;
  39. }
  40. function doUpgrade() {
  41. var contents = $("input[name='upgrade']")[0].files[0];
  42. if (typeof contents == 'undefined') {
  43. alert("First you have to select a file from your computer.");
  44. return false;
  45. }
  46. var filename = $("input[name='upgrade']").val().split('\\').pop();
  47. var data = new FormData();
  48. data.append('upgrade', contents, filename);
  49. $.ajax({
  50. // Your server script to process the upload
  51. url: 'http://' + host + ':' + port + '/upgrade',
  52. type: 'POST',
  53. // Form data
  54. data: data,
  55. // Tell jQuery not to process data or worry about content-type
  56. // You *must* include these options!
  57. cache: false,
  58. contentType: false,
  59. processData: false,
  60. success: function(data, text) {
  61. $("#upgrade-progress").hide();
  62. if (data == 'OK') {
  63. alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
  64. setTimeout(function() {
  65. window.location = "/";
  66. }, 5000);
  67. } else {
  68. alert("There was an error trying to upload the new image, please try again.");
  69. }
  70. },
  71. // Custom XMLHttpRequest
  72. xhr: function() {
  73. $("#upgrade-progress").show();
  74. var myXhr = $.ajaxSettings.xhr();
  75. if (myXhr.upload) {
  76. // For handling the progress of the upload
  77. myXhr.upload.addEventListener('progress', function(e) {
  78. if (e.lengthComputable) {
  79. $('progress').attr({ value: e.loaded, max: e.total });
  80. }
  81. } , false);
  82. }
  83. return myXhr;
  84. },
  85. });
  86. return false;
  87. }
  88. function doUpdatePassword() {
  89. var form = $("#formPassword");
  90. if (validateForm(form)) {
  91. var data = form.serializeArray();
  92. websock.send(JSON.stringify({'config': data}));
  93. }
  94. return false;
  95. }
  96. function doReset() {
  97. var response = window.confirm("Are you sure you want to reset the device?");
  98. if (response == false) return false;
  99. websock.send(JSON.stringify({'action': 'reset'}));
  100. return false;
  101. }
  102. function doReconnect() {
  103. var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
  104. if (response == false) return false;
  105. websock.send(JSON.stringify({'action': 'reconnect'}));
  106. return false;
  107. }
  108. function doToggle(element, value) {
  109. var relayID = parseInt(element.attr("data"));
  110. websock.send(JSON.stringify({'action': 'relay', 'data': { 'id': relayID, 'status': value ? 1 : 0 }}));
  111. return false;
  112. }
  113. function backupSettings() {
  114. document.getElementById('downloader').src = 'http://' + host + ':' + port + '/config';
  115. return false;
  116. }
  117. function onFileUpload(event) {
  118. var inputFiles = this.files;
  119. if (inputFiles == undefined || inputFiles.length == 0) return false;
  120. var inputFile = inputFiles[0];
  121. this.value = "";
  122. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  123. if (response == false) return false;
  124. var reader = new FileReader();
  125. reader.onload = function(e) {
  126. var data = getJson(e.target.result);
  127. if (data) {
  128. websock.send(JSON.stringify({'action': 'restore', 'data': data}));
  129. } else {
  130. alert("The file is not a configuration backup or is corrupted");
  131. }
  132. };
  133. reader.readAsText(inputFile);
  134. return false;
  135. }
  136. function restoreSettings() {
  137. if (typeof window.FileReader !== 'function') {
  138. alert("The file API isn't supported on this browser yet.");
  139. } else {
  140. $("#uploader").click();
  141. }
  142. return false;
  143. }
  144. function randomString(length, chars) {
  145. var mask = '';
  146. if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
  147. if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  148. if (chars.indexOf('#') > -1) mask += '0123456789';
  149. if (chars.indexOf('@') > -1) mask += 'ABCDEF';
  150. if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
  151. var result = '';
  152. for (var i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))];
  153. return result;
  154. }
  155. function doGenerateAPIKey() {
  156. var apikey = randomString(16, '@#');
  157. $("input[name=\"apiKey\"]").val(apikey);
  158. return false;
  159. }
  160. function showPanel() {
  161. $(".panel").hide();
  162. $("#" + $(this).attr("data")).show();
  163. if ($("#layout").hasClass('active')) toggleMenu();
  164. $("input[type='checkbox']").iphoneStyle("calculateDimensions").iphoneStyle("refresh");
  165. };
  166. function toggleMenu() {
  167. $("#layout").toggleClass('active');
  168. $("#menu").toggleClass('active');
  169. $("#menuLink").toggleClass('active');
  170. }
  171. function createRelays(count) {
  172. var current = $("#relays > div").length;
  173. if (current > 0) return;
  174. var template = $("#relayTemplate .pure-g")[0];
  175. for (var relayID=0; relayID<count; relayID++) {
  176. var line = $(template).clone();
  177. $(line).find("input").each(function() {
  178. $(this).attr("data", relayID);
  179. });
  180. if (count > 1) $(".relay_id", line).html(" " + (relayID+1));
  181. line.appendTo("#relays");
  182. $(":checkbox", line).iphoneStyle({
  183. onChange: doToggle,
  184. resizeContainer: true,
  185. resizeHandle: true,
  186. checkedLabel: 'ON',
  187. uncheckedLabel: 'OFF'
  188. });
  189. }
  190. }
  191. function createIdxs(count) {
  192. var current = $("#idxs > div").length;
  193. if (current > 0) return;
  194. var template = $("#idxTemplate .pure-g")[0];
  195. for (var id=0; id<count; id++) {
  196. var line = $(template).clone();
  197. $(line).find("input").each(function() {
  198. $(this).attr("data", id).attr("tabindex", 40+id);
  199. });
  200. if (count > 1) $(".id", line).html(" " + id);
  201. line.appendTo("#idxs");
  202. }
  203. }
  204. function delNetwork() {
  205. var parent = $(this).parents(".pure-g");
  206. $(parent).remove();
  207. }
  208. function moreNetwork() {
  209. var parent = $(this).parents(".pure-g");
  210. $("div.more", parent).toggle();
  211. }
  212. function addNetwork() {
  213. var numNetworks = $("#networks > div").length;
  214. if (numNetworks >= maxNetworks) {
  215. alert("Max number of networks reached");
  216. return;
  217. }
  218. var tabindex = 200 + numNetworks * 10;
  219. var template = $("#networkTemplate").children();
  220. var line = $(template).clone();
  221. $(line).find("input").each(function() {
  222. $(this).attr("tabindex", tabindex++);
  223. });
  224. $(line).find(".button-del-network").on('click', delNetwork);
  225. $(line).find(".button-more-network").on('click', moreNetwork);
  226. line.appendTo("#networks");
  227. return line;
  228. }
  229. function initColor() {
  230. // check if already initialized
  231. var done = $("#colors > div").length;
  232. if (done > 0) return;
  233. // add template
  234. var template = $("#colorTemplate").children();
  235. var line = $(template).clone();
  236. line.appendTo("#colors");
  237. // init color wheel
  238. $('input[name="color"]').wheelColorPicker({
  239. sliders: 'wrgbp'
  240. }).on('sliderup', function() {
  241. var value = $(this).wheelColorPicker('getValue', 'css');
  242. websock.send(JSON.stringify({'action': 'color', 'data' : value}));
  243. });
  244. // init bright slider
  245. noUiSlider.create($("#brightness").get(0), {
  246. start: 255,
  247. connect: [true, false],
  248. orientation: "horizontal",
  249. range: { 'min': 0, 'max': 255}
  250. }).on("change", function() {
  251. var value = parseInt(this.get());
  252. websock.send(JSON.stringify({'action': 'brightness', 'data' : value}));
  253. });
  254. }
  255. function initChannels(num) {
  256. // check if already initialized
  257. var done = $("#channels > div").length > 0;
  258. if (done) return;
  259. // does it have color channels?
  260. var colors = $("#colors > div").length > 0;
  261. // calculate channels to create
  262. var max = colors ? num % 3 : num;
  263. var start = num - max;
  264. // add templates
  265. var template = $("#channelTemplate").children();
  266. for (var i=0; i<max; i++) {
  267. var channel_id = start + i;
  268. var line = $(template).clone();
  269. $(".slider", line).attr("data", channel_id);
  270. $("label", line).html("Channel " + (channel_id + 1));
  271. noUiSlider.create($(".slider", line).get(0), {
  272. start: 0,
  273. connect: [true, false],
  274. orientation: "horizontal",
  275. range: { 'min': 0, 'max': 255 }
  276. }).on("change", function() {
  277. var id = $(this.target).attr("data");
  278. var value = parseInt(this.get());
  279. websock.send(JSON.stringify({'action': 'channel', 'data': { 'id': id, 'value': value }}));
  280. });
  281. line.appendTo("#channels");
  282. }
  283. }
  284. function forgetCredentials() {
  285. $.ajax({
  286. 'method': 'GET',
  287. 'url': '/',
  288. 'async': false,
  289. 'username': "logmeout",
  290. 'password': "123456",
  291. 'headers': { "Authorization": "Basic xxx" }
  292. }).done(function(data) {
  293. return false;
  294. // If we don't get an error, we actually got an error as we expect an 401!
  295. }).fail(function(){
  296. // We expect to get an 401 Unauthorized error! In this case we are successfully
  297. // logged out and we redirect the user.
  298. return true;
  299. });
  300. }
  301. function processData(data) {
  302. // title
  303. if ("app" in data) {
  304. var title = data.app;
  305. if ("version" in data) {
  306. title = title + " " + data.version;
  307. }
  308. $(".pure-menu-heading").html(title);
  309. if ("hostname" in data) {
  310. title = data.hostname + " - " + title;
  311. }
  312. document.title = title;
  313. }
  314. Object.keys(data).forEach(function(key) {
  315. // Web Modes
  316. if (key == "webMode") {
  317. password = data.webMode == 1;
  318. $("#layout").toggle(data.webMode == 0);
  319. $("#password").toggle(data.webMode == 1);
  320. $("#credentials").hide();
  321. }
  322. // Actions
  323. if (key == "action") {
  324. if (data.action == "reload") {
  325. if (password) forgetCredentials();
  326. setTimeout(function() {
  327. window.location = "/";
  328. }, 1000);
  329. }
  330. return;
  331. }
  332. if (key == "color") {
  333. initColor();
  334. $("input[name='color']").wheelColorPicker('setValue', data[key], true);
  335. return;
  336. }
  337. if (key == "brightness") {
  338. var slider = $("#brightness");
  339. if (slider.length) slider.get(0).noUiSlider.set(data[key]);
  340. return;
  341. }
  342. if (key == "channels") {
  343. var len = data[key].length;
  344. initChannels(len);
  345. for (var i=0; i<len; i++) {
  346. var slider = $("div.channels[data=" + i + "]");
  347. if (slider.length) slider.get(0).noUiSlider.set(data[key][i]);
  348. }
  349. return;
  350. }
  351. if (key == "maxNetworks") {
  352. maxNetworks = parseInt(data.maxNetworks);
  353. return;
  354. }
  355. // Wifi
  356. if (key == "wifi") {
  357. var networks = data.wifi;
  358. for (var i in networks) {
  359. // add a new row
  360. var line = addNetwork();
  361. // fill in the blanks
  362. var wifi = data.wifi[i];
  363. Object.keys(wifi).forEach(function(key) {
  364. var element = $("input[name=" + key + "]", line);
  365. if (element.length) element.val(wifi[key]);
  366. });
  367. }
  368. return;
  369. }
  370. // Relay status
  371. if (key == "relayStatus") {
  372. var relays = data.relayStatus;
  373. createRelays(relays.length);
  374. for (var relayID in relays) {
  375. var element = $(".relayStatus[data=" + relayID + "]");
  376. if (element.length > 0) {
  377. element
  378. .prop("checked", relays[relayID])
  379. .iphoneStyle("refresh");
  380. }
  381. }
  382. return;
  383. }
  384. // Domoticz
  385. if (key == "dczRelayIdx") {
  386. var idxs = data.dczRelayIdx;
  387. createIdxs(idxs.length);
  388. for (var i in idxs) {
  389. var element = $(".dczRelayIdx[data=" + i + "]");
  390. if (element.length > 0) element.val(idxs[i]);
  391. }
  392. return;
  393. }
  394. // Messages
  395. if (key == "message") {
  396. window.alert(data.message);
  397. return;
  398. }
  399. // Enable options
  400. if (key.endsWith("Visible")) {
  401. var module = key.slice(0,-7);
  402. $(".module-" + module).show();
  403. return;
  404. }
  405. // Pre-process
  406. if (key == "network") {
  407. data.network = data.network.toUpperCase();
  408. }
  409. if (key == "mqttStatus") {
  410. data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
  411. }
  412. if (key == "ntpStatus") {
  413. data.ntpStatus = data.ntpStatus ? "SYNC'D" : "NOT SYNC'D";
  414. }
  415. if (key == "tmpUnits") {
  416. $("span#tmpUnit").html(data[key] == 1 ? "ºF" : "ºC");
  417. }
  418. // Look for INPUTs
  419. var element = $("input[name=" + key + "]");
  420. if (element.length > 0) {
  421. if (element.attr('type') == 'checkbox') {
  422. element
  423. .prop("checked", data[key])
  424. .iphoneStyle("refresh");
  425. } else if (element.attr('type') == 'radio') {
  426. element.val([data[key]]);
  427. } else {
  428. var pre = element.attr("pre") || "";
  429. var post = element.attr("post") || "";
  430. element.val(pre + data[key] + post);
  431. }
  432. return;
  433. }
  434. // Look for SPANs
  435. var element = $("span[name=" + key + "]");
  436. if (element.length > 0) {
  437. var pre = element.attr("pre") || "";
  438. var post = element.attr("post") || "";
  439. element.html(pre + data[key] + post);
  440. return;
  441. }
  442. // Look for SELECTs
  443. var element = $("select[name=" + key + "]");
  444. if (element.length > 0) {
  445. element.val(data[key]);
  446. return;
  447. }
  448. });
  449. // Auto generate an APIKey if none defined yet
  450. if ($("input[name='apiKey']").val() == "") {
  451. doGenerateAPIKey();
  452. }
  453. }
  454. function getJson(str) {
  455. try {
  456. return JSON.parse(str);
  457. } catch (e) {
  458. return false;
  459. }
  460. }
  461. function connect(h, p) {
  462. if (typeof h === 'undefined') {
  463. h = window.location.hostname;
  464. }
  465. if (typeof p === 'undefined') {
  466. p = location.port;
  467. }
  468. host = h;
  469. port = p;
  470. if (websock) websock.close();
  471. websock = new WebSocket('ws://' + host + ':' + port + '/ws');
  472. websock.onopen = function(evt) {
  473. console.log("Connected");
  474. };
  475. websock.onclose = function(evt) {
  476. console.log("Disconnected");
  477. };
  478. websock.onerror = function(evt) {
  479. console.log("Error: ", evt);
  480. };
  481. websock.onmessage = function(evt) {
  482. var data = getJson(evt.data);
  483. if (data) processData(data);
  484. };
  485. }
  486. function init() {
  487. $("#menuLink").on('click', toggleMenu);
  488. $(".button-update").on('click', doUpdate);
  489. $(".button-update-password").on('click', doUpdatePassword);
  490. $(".button-reset").on('click', doReset);
  491. $(".button-reconnect").on('click', doReconnect);
  492. $(".button-settings-backup").on('click', backupSettings);
  493. $(".button-settings-restore").on('click', restoreSettings);
  494. $('#uploader').on('change', onFileUpload);
  495. $(".button-apikey").on('click', doGenerateAPIKey);
  496. $(".button-upgrade").on('click', doUpgrade);
  497. $(".button-upgrade-browse").on('click', function() {
  498. $("input[name='upgrade']")[0].click();
  499. return false;
  500. });
  501. $("input[name='upgrade']").change(function (){
  502. var fileName = $(this).val();
  503. $("input[name='filename']").val(fileName.replace(/^.*[\\\/]/, ''));
  504. });
  505. $('progress').attr({ value: 0, max: 100 });
  506. $(".pure-menu-link").on('click', showPanel);
  507. $(".button-add-network").on('click', function() {
  508. $("div.more", addNetwork()).toggle();
  509. });
  510. var host = window.location.hostname;
  511. var port = location.port;
  512. $.ajax({
  513. 'method': 'GET',
  514. 'url': 'http://' + host + ':' + port + '/auth'
  515. }).done(function(data) {
  516. connect();
  517. }).fail(function(){
  518. $("#credentials").show();
  519. });
  520. }
  521. $(init);