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.

487 lines
13 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
  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 doColor() {
  28. var color = $(this).wheelColorPicker('getColor');
  29. var data = [];
  30. if ((color.r == color.g) && (color.g == color.b)) {
  31. data.push(0);
  32. data.push(0);
  33. data.push(0);
  34. data.push(parseInt(color.r * 255));
  35. } else {
  36. data.push(parseInt(color.r * 255));
  37. data.push(parseInt(color.g * 255));
  38. data.push(parseInt(color.b * 255));
  39. data.push(0);
  40. }
  41. websock.send(JSON.stringify({'action': 'color', 'data' : data}));
  42. }
  43. function doUpdate() {
  44. var form = $("#formSave");
  45. if (validateForm(form)) {
  46. var data = form.serializeArray();
  47. websock.send(JSON.stringify({'config': data}));
  48. $(".powExpected").val(0);
  49. $("input[name='powExpectedReset']")
  50. .prop("checked", false)
  51. .iphoneStyle("refresh");
  52. }
  53. return false;
  54. }
  55. function doUpdatePassword() {
  56. var form = $("#formPassword");
  57. if (validateForm(form)) {
  58. var data = form.serializeArray();
  59. websock.send(JSON.stringify({'config': data}));
  60. }
  61. return false;
  62. }
  63. function doReset() {
  64. var response = window.confirm("Are you sure you want to reset the device?");
  65. if (response == false) return false;
  66. websock.send(JSON.stringify({'action': 'reset'}));
  67. return false;
  68. }
  69. function doReconnect() {
  70. var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
  71. if (response == false) return false;
  72. websock.send(JSON.stringify({'action': 'reconnect'}));
  73. return false;
  74. }
  75. function doToggle(element, value) {
  76. var relayID = parseInt(element.attr("data"));
  77. websock.send(JSON.stringify({'action': value ? 'on' : 'off', 'relayID': relayID}));
  78. return false;
  79. }
  80. function backupSettings() {
  81. document.getElementById('downloader').src = 'http://' + host + ':' + port + '/config';
  82. return false;
  83. }
  84. function onFileUpload(event) {
  85. var inputFiles = this.files;
  86. if (inputFiles == undefined || inputFiles.length == 0) return false;
  87. var inputFile = inputFiles[0];
  88. this.value = "";
  89. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  90. if (response == false) return false;
  91. var reader = new FileReader();
  92. reader.onload = function(e) {
  93. var data = getJson(e.target.result);
  94. if (data) {
  95. websock.send(JSON.stringify({'action': 'restore', 'data': data}));
  96. } else {
  97. alert("The file is not a configuration backup or is corrupted");
  98. }
  99. };
  100. reader.readAsText(inputFile);
  101. return false;
  102. }
  103. function restoreSettings() {
  104. if (typeof window.FileReader !== 'function') {
  105. alert("The file API isn't supported on this browser yet.");
  106. } else {
  107. $("#uploader").click();
  108. }
  109. return false;
  110. }
  111. function randomString(length, chars) {
  112. var mask = '';
  113. if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
  114. if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  115. if (chars.indexOf('#') > -1) mask += '0123456789';
  116. if (chars.indexOf('@') > -1) mask += 'ABCDEF';
  117. if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
  118. var result = '';
  119. for (var i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))];
  120. return result;
  121. }
  122. function doGenerateAPIKey() {
  123. var apikey = randomString(16, '@#');
  124. $("input[name=\"apiKey\"]").val(apikey);
  125. return false;
  126. }
  127. function showPanel() {
  128. $(".panel").hide();
  129. $("#" + $(this).attr("data")).show();
  130. if ($("#layout").hasClass('active')) toggleMenu();
  131. $("input[type='checkbox']").iphoneStyle("calculateDimensions").iphoneStyle("refresh");
  132. };
  133. function toggleMenu() {
  134. $("#layout").toggleClass('active');
  135. $("#menu").toggleClass('active');
  136. $("#menuLink").toggleClass('active');
  137. }
  138. function createRelays(count) {
  139. var current = $("#relays > div").length;
  140. if (current > 0) return;
  141. var template = $("#relayTemplate .pure-g")[0];
  142. for (var relayID=0; relayID<count; relayID++) {
  143. var line = $(template).clone();
  144. $(line).find("input").each(function() {
  145. $(this).attr("data", relayID);
  146. });
  147. if (count > 1) $(".relay_id", line).html(" " + (relayID+1));
  148. line.appendTo("#relays");
  149. $(":checkbox", line).iphoneStyle({
  150. onChange: doToggle,
  151. resizeContainer: true,
  152. resizeHandle: true,
  153. checkedLabel: 'ON',
  154. uncheckedLabel: 'OFF'
  155. });
  156. }
  157. }
  158. function createIdxs(count) {
  159. var current = $("#idxs > div").length;
  160. if (current > 0) return;
  161. var template = $("#idxTemplate .pure-g")[0];
  162. for (var id=0; id<count; id++) {
  163. var line = $(template).clone();
  164. $(line).find("input").each(function() {
  165. $(this).attr("data", id).attr("tabindex", 40+id);
  166. });
  167. if (count > 1) $(".id", line).html(" " + id);
  168. line.appendTo("#idxs");
  169. }
  170. }
  171. function delNetwork() {
  172. var parent = $(this).parents(".pure-g");
  173. $(parent).remove();
  174. }
  175. function moreNetwork() {
  176. var parent = $(this).parents(".pure-g");
  177. $("div.more", parent).toggle();
  178. }
  179. function addNetwork() {
  180. var numNetworks = $("#networks > div").length;
  181. if (numNetworks >= maxNetworks) {
  182. alert("Max number of networks reached");
  183. return;
  184. }
  185. var tabindex = 200 + numNetworks * 10;
  186. var template = $("#networkTemplate").children();
  187. var line = $(template).clone();
  188. $(line).find("input").each(function() {
  189. $(this).attr("tabindex", tabindex++);
  190. });
  191. $(line).find(".button-del-network").on('click', delNetwork);
  192. $(line).find(".button-more-network").on('click', moreNetwork);
  193. line.appendTo("#networks");
  194. return line;
  195. }
  196. function forgetCredentials() {
  197. $.ajax({
  198. 'method': 'GET',
  199. 'url': '/',
  200. 'async': false,
  201. 'username': "logmeout",
  202. 'password': "123456",
  203. 'headers': { "Authorization": "Basic xxx" }
  204. }).done(function(data) {
  205. return false;
  206. // If we don't get an error, we actually got an error as we expect an 401!
  207. }).fail(function(){
  208. // We expect to get an 401 Unauthorized error! In this case we are successfully
  209. // logged out and we redirect the user.
  210. return true;
  211. });
  212. }
  213. function processData(data) {
  214. // title
  215. if ("app" in data) {
  216. var title = data.app;
  217. if ("version" in data) {
  218. title = title + " " + data.version;
  219. }
  220. $(".pure-menu-heading").html(title);
  221. if ("hostname" in data) {
  222. title = data.hostname + " - " + title;
  223. }
  224. document.title = title;
  225. }
  226. Object.keys(data).forEach(function(key) {
  227. // Web Modes
  228. if (key == "webMode") {
  229. password = data.webMode == 1;
  230. $("#layout").toggle(data.webMode == 0);
  231. $("#password").toggle(data.webMode == 1);
  232. $("#credentials").hide();
  233. }
  234. // Actions
  235. if (key == "action") {
  236. if (data.action == "reload") {
  237. if (password) forgetCredentials();
  238. setTimeout(function() {
  239. window.location = "/";
  240. }, 1000);
  241. }
  242. return;
  243. }
  244. if (key == "color") {
  245. var color = data[key].split(",");
  246. if (color[3] > 0) color[0] = color[1] = color[2] = color[3];
  247. $("input[name='color']").wheelColorPicker('setRgb', color[0] / 255, color[1] / 255, color[2] / 255, true);
  248. return;
  249. }
  250. if (key == "maxNetworks") {
  251. maxNetworks = parseInt(data.maxNetworks);
  252. return;
  253. }
  254. // Wifi
  255. if (key == "wifi") {
  256. var networks = data.wifi;
  257. for (var i in networks) {
  258. // add a new row
  259. var line = addNetwork();
  260. // fill in the blanks
  261. var wifi = data.wifi[i];
  262. Object.keys(wifi).forEach(function(key) {
  263. var element = $("input[name=" + key + "]", line);
  264. if (element.length) element.val(wifi[key]);
  265. });
  266. }
  267. return;
  268. }
  269. // Relay status
  270. if (key == "relayStatus") {
  271. var relays = data.relayStatus;
  272. createRelays(relays.length);
  273. for (var relayID in relays) {
  274. var element = $(".relayStatus[data=" + relayID + "]");
  275. if (element.length > 0) {
  276. element
  277. .prop("checked", relays[relayID])
  278. .iphoneStyle("refresh");
  279. }
  280. }
  281. return;
  282. }
  283. // Domoticz
  284. if (key == "dczRelayIdx") {
  285. var idxs = data.dczRelayIdx;
  286. createIdxs(idxs.length);
  287. for (var i in idxs) {
  288. var element = $(".dczRelayIdx[data=" + i + "]");
  289. if (element.length > 0) element.val(idxs[i]);
  290. }
  291. return;
  292. }
  293. // Messages
  294. if (key == "message") {
  295. window.alert(data.message);
  296. return;
  297. }
  298. // Enable options
  299. if (key.endsWith("Visible")) {
  300. var module = key.slice(0,-7);
  301. $(".module-" + module).show();
  302. return;
  303. }
  304. // Pre-process
  305. if (key == "network") {
  306. data.network = data.network.toUpperCase();
  307. }
  308. if (key == "mqttStatus") {
  309. data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
  310. }
  311. if (key == "tmpUnits") {
  312. $("span#tmpUnit").html(data[key] == 1 ? "ºF" : "ºC");
  313. }
  314. // Look for INPUTs
  315. var element = $("input[name=" + key + "]");
  316. if (element.length > 0) {
  317. if (element.attr('type') == 'checkbox') {
  318. element
  319. .prop("checked", data[key])
  320. .iphoneStyle("refresh");
  321. } else if (element.attr('type') == 'radio') {
  322. element.val([data[key]]);
  323. } else {
  324. element.val(data[key]);
  325. }
  326. return;
  327. }
  328. // Look for SELECTs
  329. var element = $("select[name=" + key + "]");
  330. if (element.length > 0) {
  331. element.val(data[key]);
  332. return;
  333. }
  334. });
  335. // Auto generate an APIKey if none defined yet
  336. if ($("input[name='apiKey']").val() == "") {
  337. doGenerateAPIKey();
  338. }
  339. }
  340. function getJson(str) {
  341. try {
  342. return JSON.parse(str);
  343. } catch (e) {
  344. return false;
  345. }
  346. }
  347. function connect(h, p) {
  348. if (typeof h === 'undefined') {
  349. h = window.location.hostname;
  350. }
  351. if (typeof p === 'undefined') {
  352. p = location.port;
  353. }
  354. host = h;
  355. port = p;
  356. if (websock) websock.close();
  357. websock = new WebSocket('ws://' + host + ':' + port + '/ws');
  358. websock.onopen = function(evt) {
  359. console.log("Connected");
  360. };
  361. websock.onclose = function(evt) {
  362. console.log("Disconnected");
  363. };
  364. websock.onerror = function(evt) {
  365. console.log("Error: ", evt);
  366. };
  367. websock.onmessage = function(evt) {
  368. var data = getJson(evt.data);
  369. if (data) processData(data);
  370. };
  371. }
  372. function init() {
  373. $("#menuLink").on('click', toggleMenu);
  374. $(".button-update").on('click', doUpdate);
  375. $(".button-update-password").on('click', doUpdatePassword);
  376. $(".button-reset").on('click', doReset);
  377. $(".button-reconnect").on('click', doReconnect);
  378. $(".button-settings-backup").on('click', backupSettings);
  379. $(".button-settings-restore").on('click', restoreSettings);
  380. $('#uploader').on('change', onFileUpload);
  381. $(".button-apikey").on('click', doGenerateAPIKey);
  382. $(".pure-menu-link").on('click', showPanel);
  383. $(".button-add-network").on('click', function() {
  384. $("div.more", addNetwork()).toggle();
  385. });
  386. $('input[name="color"]').wheelColorPicker({
  387. sliders: 'wsvp'
  388. }).on('sliderup', doColor);
  389. var host = window.location.hostname;
  390. var port = location.port;
  391. $.ajax({
  392. 'method': 'GET',
  393. 'url': 'http://' + host + ':' + port + '/auth'
  394. }).done(function(data) {
  395. connect();
  396. }).fail(function(){
  397. $("#credentials").show();
  398. });
  399. }
  400. $(init);