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.

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