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.

647 lines
18 KiB

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
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. tooltips: true,
  249. format: {
  250. to: function (value) { return parseInt(value); },
  251. from: function (value) { return value; }
  252. },
  253. orientation: "horizontal",
  254. range: { 'min': 0, 'max': 255}
  255. }).on("change", function() {
  256. var value = parseInt(this.get());
  257. websock.send(JSON.stringify({'action': 'brightness', 'data' : value}));
  258. });
  259. }
  260. function initChannels(num) {
  261. // check if already initialized
  262. var done = $("#channels > div").length > 0;
  263. if (done) return;
  264. // does it have color channels?
  265. var colors = $("#colors > div").length > 0;
  266. // calculate channels to create
  267. var max = colors ? num % 3 : num;
  268. var start = num - max;
  269. // add templates
  270. var template = $("#channelTemplate").children();
  271. for (var i=0; i<max; i++) {
  272. var channel_id = start + i;
  273. var line = $(template).clone();
  274. $(".slider", line).attr("data", channel_id);
  275. $("label", line).html("Channel " + (channel_id + 1));
  276. noUiSlider.create($(".slider", line).get(0), {
  277. start: 0,
  278. connect: [true, false],
  279. tooltips: true,
  280. format: {
  281. to: function (value) { return parseInt(value); },
  282. from: function (value) { return value; }
  283. },
  284. orientation: "horizontal",
  285. range: { 'min': 0, 'max': 255 }
  286. }).on("change", function() {
  287. var id = $(this.target).attr("data");
  288. var value = parseInt(this.get());
  289. websock.send(JSON.stringify({'action': 'channel', 'data': { 'id': id, 'value': value }}));
  290. });
  291. line.appendTo("#channels");
  292. }
  293. }
  294. function forgetCredentials() {
  295. $.ajax({
  296. 'method': 'GET',
  297. 'url': '/',
  298. 'async': false,
  299. 'username': "logmeout",
  300. 'password': "123456",
  301. 'headers': { "Authorization": "Basic xxx" }
  302. }).done(function(data) {
  303. return false;
  304. // If we don't get an error, we actually got an error as we expect an 401!
  305. }).fail(function(){
  306. // We expect to get an 401 Unauthorized error! In this case we are successfully
  307. // logged out and we redirect the user.
  308. return true;
  309. });
  310. }
  311. function processData(data) {
  312. // title
  313. if ("app" in data) {
  314. var title = data.app;
  315. if ("version" in data) {
  316. title = title + " " + data.version;
  317. }
  318. $(".pure-menu-heading").html(title);
  319. if ("hostname" in data) {
  320. title = data.hostname + " - " + title;
  321. }
  322. document.title = title;
  323. }
  324. Object.keys(data).forEach(function(key) {
  325. // Web Modes
  326. if (key == "webMode") {
  327. password = data.webMode == 1;
  328. $("#layout").toggle(data.webMode == 0);
  329. $("#password").toggle(data.webMode == 1);
  330. $("#credentials").hide();
  331. }
  332. // Actions
  333. if (key == "action") {
  334. if (data.action == "reload") {
  335. if (password) forgetCredentials();
  336. setTimeout(function() {
  337. window.location = "/";
  338. }, 1000);
  339. }
  340. return;
  341. }
  342. if (key == "color") {
  343. initColor();
  344. $("input[name='color']").wheelColorPicker('setValue', data[key], true);
  345. return;
  346. }
  347. if (key == "brightness") {
  348. var slider = $("#brightness");
  349. if (slider.length) slider.get(0).noUiSlider.set(data[key]);
  350. return;
  351. }
  352. if (key == "channels") {
  353. var len = data[key].length;
  354. initChannels(len);
  355. for (var i=0; i<len; i++) {
  356. var slider = $("div.channels[data=" + i + "]");
  357. if (slider.length) slider.get(0).noUiSlider.set(data[key][i]);
  358. }
  359. return;
  360. }
  361. if (key == "maxNetworks") {
  362. maxNetworks = parseInt(data.maxNetworks);
  363. return;
  364. }
  365. // Wifi
  366. if (key == "wifi") {
  367. var networks = data.wifi;
  368. for (var i in networks) {
  369. // add a new row
  370. var line = addNetwork();
  371. // fill in the blanks
  372. var wifi = data.wifi[i];
  373. Object.keys(wifi).forEach(function(key) {
  374. var element = $("input[name=" + key + "]", line);
  375. if (element.length) element.val(wifi[key]);
  376. });
  377. }
  378. return;
  379. }
  380. // Relay status
  381. if (key == "relayStatus") {
  382. var relays = data.relayStatus;
  383. createRelays(relays.length);
  384. for (var relayID in relays) {
  385. var element = $(".relayStatus[data=" + relayID + "]");
  386. if (element.length > 0) {
  387. element
  388. .prop("checked", relays[relayID])
  389. .iphoneStyle("refresh");
  390. }
  391. }
  392. return;
  393. }
  394. // Domoticz
  395. if (key == "dczRelayIdx") {
  396. var idxs = data.dczRelayIdx;
  397. createIdxs(idxs.length);
  398. for (var i in idxs) {
  399. var element = $(".dczRelayIdx[data=" + i + "]");
  400. if (element.length > 0) element.val(idxs[i]);
  401. }
  402. return;
  403. }
  404. // Messages
  405. if (key == "message") {
  406. window.alert(data.message);
  407. return;
  408. }
  409. // Enable options
  410. if (key.endsWith("Visible")) {
  411. var module = key.slice(0,-7);
  412. $(".module-" + module).show();
  413. return;
  414. }
  415. // Pre-process
  416. if (key == "network") {
  417. data.network = data.network.toUpperCase();
  418. }
  419. if (key == "mqttStatus") {
  420. data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
  421. }
  422. if (key == "ntpStatus") {
  423. data.ntpStatus = data.ntpStatus ? "SYNC'D" : "NOT SYNC'D";
  424. }
  425. if (key == "tmpUnits") {
  426. $("span#tmpUnit").html(data[key] == 1 ? "ºF" : "ºC");
  427. }
  428. // Look for INPUTs
  429. var element = $("input[name=" + key + "]");
  430. if (element.length > 0) {
  431. if (element.attr('type') == 'checkbox') {
  432. element
  433. .prop("checked", data[key])
  434. .iphoneStyle("refresh");
  435. } else if (element.attr('type') == 'radio') {
  436. element.val([data[key]]);
  437. } else {
  438. var pre = element.attr("pre") || "";
  439. var post = element.attr("post") || "";
  440. element.val(pre + data[key] + post);
  441. }
  442. return;
  443. }
  444. // Look for SPANs
  445. var element = $("span[name=" + key + "]");
  446. if (element.length > 0) {
  447. var pre = element.attr("pre") || "";
  448. var post = element.attr("post") || "";
  449. element.html(pre + data[key] + post);
  450. return;
  451. }
  452. // Look for SELECTs
  453. var element = $("select[name=" + key + "]");
  454. if (element.length > 0) {
  455. element.val(data[key]);
  456. return;
  457. }
  458. });
  459. // Auto generate an APIKey if none defined yet
  460. if ($("input[name='apiKey']").val() == "") {
  461. doGenerateAPIKey();
  462. }
  463. }
  464. function getJson(str) {
  465. try {
  466. return JSON.parse(str);
  467. } catch (e) {
  468. return false;
  469. }
  470. }
  471. function connect(h, p) {
  472. if (typeof h === 'undefined') {
  473. h = window.location.hostname;
  474. }
  475. if (typeof p === 'undefined') {
  476. p = location.port;
  477. }
  478. host = h;
  479. port = p;
  480. if (websock) websock.close();
  481. websock = new WebSocket('ws://' + host + ':' + port + '/ws');
  482. websock.onopen = function(evt) {
  483. console.log("Connected");
  484. };
  485. websock.onclose = function(evt) {
  486. console.log("Disconnected");
  487. };
  488. websock.onerror = function(evt) {
  489. console.log("Error: ", evt);
  490. };
  491. websock.onmessage = function(evt) {
  492. var data = getJson(evt.data);
  493. if (data) processData(data);
  494. };
  495. }
  496. function init() {
  497. $("#menuLink").on('click', toggleMenu);
  498. $(".button-update").on('click', doUpdate);
  499. $(".button-update-password").on('click', doUpdatePassword);
  500. $(".button-reset").on('click', doReset);
  501. $(".button-reconnect").on('click', doReconnect);
  502. $(".button-settings-backup").on('click', backupSettings);
  503. $(".button-settings-restore").on('click', restoreSettings);
  504. $('#uploader').on('change', onFileUpload);
  505. $(".button-apikey").on('click', doGenerateAPIKey);
  506. $(".button-upgrade").on('click', doUpgrade);
  507. $(".button-upgrade-browse").on('click', function() {
  508. $("input[name='upgrade']")[0].click();
  509. return false;
  510. });
  511. $("input[name='upgrade']").change(function (){
  512. var fileName = $(this).val();
  513. $("input[name='filename']").val(fileName.replace(/^.*[\\\/]/, ''));
  514. });
  515. $('progress').attr({ value: 0, max: 100 });
  516. $(".pure-menu-link").on('click', showPanel);
  517. $(".button-add-network").on('click', function() {
  518. $("div.more", addNetwork()).toggle();
  519. });
  520. var host = window.location.hostname;
  521. var port = location.port;
  522. $.ajax({
  523. 'method': 'GET',
  524. 'url': 'http://' + host + ':' + port + '/auth'
  525. }).done(function(data) {
  526. connect();
  527. }).fail(function(){
  528. $("#credentials").show();
  529. });
  530. }
  531. $(init);