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.

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