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.

1640 lines
47 KiB

8 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
6 years ago
8 years ago
8 years ago
8 years ago
6 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
8 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
8 years ago
6 years ago
6 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
6 years ago
8 years ago
  1. var websock;
  2. var password = false;
  3. var maxNetworks;
  4. var maxSchedules;
  5. var messages = [];
  6. var free_size = 0;
  7. var urls = {};
  8. var numChanged = 0;
  9. var numReboot = 0;
  10. var numReconnect = 0;
  11. var numReload = 0;
  12. var litWhite = false;
  13. var litCCT = false;
  14. var now = 0;
  15. var ago = 0;
  16. <!-- removeIf(!rfm69)-->
  17. var packets;
  18. var filters = [];
  19. <!-- endRemoveIf(!rfm69)-->
  20. // -----------------------------------------------------------------------------
  21. // Messages
  22. // -----------------------------------------------------------------------------
  23. function initMessages() {
  24. messages[1] = "Remote update started";
  25. messages[2] = "OTA update started";
  26. messages[3] = "Error parsing data!";
  27. messages[4] = "The file does not look like a valid configuration backup or is corrupted";
  28. messages[5] = "Changes saved. You should reboot your board now";
  29. messages[7] = "Passwords do not match!";
  30. messages[8] = "Changes saved";
  31. messages[9] = "No changes detected";
  32. messages[10] = "Session expired, please reload page...";
  33. }
  34. <!-- removeIf(!sensor)-->
  35. function sensorName(id) {
  36. var names = [
  37. "DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15",
  38. "HLW8012", "V9261F", "ECH1560", "Analog", "Digital",
  39. "Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
  40. "SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
  41. "TMP3X", "Sonar", "SenseAir", "GeigerTicks", "GeigerCPM"
  42. ];
  43. if (1 <= id && id <= names.length) {
  44. return names[id - 1];
  45. }
  46. return null;
  47. }
  48. function magnitudeType(type) {
  49. var types = [
  50. "Temperature", "Humidity", "Pressure",
  51. "Current", "Voltage", "Active Power", "Apparent Power",
  52. "Reactive Power", "Power Factor", "Energy", "Energy (delta)",
  53. "Analog", "Digital", "Event",
  54. "PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO",
  55. "Local Dose Rate", "Local Dose Rate",
  56. "Count"
  57. ];
  58. if (1 <= type && type <= types.length) {
  59. return types[type - 1];
  60. }
  61. return null;
  62. }
  63. function magnitudeError(error) {
  64. var errors = [
  65. "OK", "Out of Range", "Warming Up", "Timeout", "Wrong ID",
  66. "Data Error", "I2C Error", "GPIO Error", "Calibration error"
  67. ];
  68. if (0 <= error && error < errors.length) {
  69. return errors[error];
  70. }
  71. return "Error " + error;
  72. }
  73. <!-- endRemoveIf(!sensor)-->
  74. // -----------------------------------------------------------------------------
  75. // Utils
  76. // -----------------------------------------------------------------------------
  77. $.fn.enterKey = function (fnc) {
  78. return this.each(function () {
  79. $(this).keypress(function (ev) {
  80. var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10);
  81. if (13 === keycode) {
  82. return fnc.call(this, ev);
  83. }
  84. });
  85. });
  86. };
  87. function keepTime() {
  88. $("span[name='ago']").html(ago);
  89. ago++;
  90. if (0 === now) { return; }
  91. var date = new Date(now * 1000);
  92. var text = date.toISOString().substring(0, 19).replace("T", " ");
  93. $("input[name='now']").val(text);
  94. $("span[name='now']").html(text);
  95. now++;
  96. }
  97. function zeroPad(number, positions) {
  98. var zeros = "";
  99. for (var i = 0; i < positions; i++) {
  100. zeros += "0";
  101. }
  102. return (zeros + number).slice(-positions);
  103. }
  104. function loadTimeZones() {
  105. var time_zones = [
  106. -720, -660, -600, -570, -540,
  107. -480, -420, -360, -300, -240,
  108. -210, -180, -120, -60, 0,
  109. 60, 120, 180, 210, 240,
  110. 270, 300, 330, 345, 360,
  111. 390, 420, 480, 510, 525,
  112. 540, 570, 600, 630, 660,
  113. 720, 765, 780, 840
  114. ];
  115. for (var i in time_zones) {
  116. var value = time_zones[i];
  117. var offset = value >= 0 ? value : -value;
  118. var text = "GMT" + (value >= 0 ? "+" : "-") +
  119. zeroPad(parseInt(offset / 60, 10), 2) + ":" +
  120. zeroPad(offset % 60, 2);
  121. $("select[name='ntpOffset']").append(
  122. $("<option></option>").
  123. attr("value",value).
  124. text(text));
  125. }
  126. }
  127. function validateForm(form) {
  128. // http://www.the-art-of-web.com/javascript/validate-password/
  129. // at least one lowercase and one uppercase letter or number
  130. // at least five characters (letters, numbers or special characters)
  131. var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
  132. // password
  133. var adminPass1 = $("input[name='adminPass']", form).first().val();
  134. if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
  135. alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
  136. return false;
  137. }
  138. var adminPass2 = $("input[name='adminPass']", form).last().val();
  139. if (adminPass1 !== adminPass2) {
  140. alert("Passwords are different!");
  141. return false;
  142. }
  143. // RFCs mandate that a hostname's labels may contain only
  144. // the ASCII letters 'a' through 'z' (case-insensitive),
  145. // the digits '0' through '9', and the hyphen.
  146. // Hostname labels cannot begin or end with a hyphen.
  147. // No other symbols, punctuation characters, or blank spaces are permitted.
  148. // Negative lookbehind does not work in Javascript
  149. // var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,32}(?<!-)$');
  150. var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,31}[A-Za-z0-9]$');
  151. var hostname = $("input[name='hostname']", form);
  152. var hasChanged = hostname.attr("hasChanged") || 0;
  153. if (0 === hasChanged) {
  154. return true;
  155. }
  156. if (!re_hostname.test(hostname.val())) {
  157. alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
  158. return false;
  159. }
  160. return true;
  161. }
  162. function getValue(element) {
  163. if ($(element).attr("type") === "checkbox") {
  164. return $(element).prop("checked") ? 1 : 0;
  165. } else if ($(element).attr("type") === "radio") {
  166. if (!$(element).prop("checked")) {
  167. return null;
  168. }
  169. }
  170. return $(element).val();
  171. }
  172. function addValue(data, name, value) {
  173. // These fields will always be a list of values
  174. var is_group = [
  175. "wifiName","wifiPass","wifiGW","wifiMask","wifiIP","wifiDNS",
  176. "schEnabled","schSwitch","schAction","schType","schHour","schMinute","schWDs","schUTC",
  177. "rlyBoot","rlyPulse","rlyTime","rlyOnDisc",
  178. "mqttGroup","mqttGroupInv",
  179. "dczRelayIdx","dczMagnitude",
  180. "tspkRelay","tspkMagnitude",
  181. "ledMode",
  182. "adminPass",
  183. "node", "key", "topic"
  184. ];
  185. if (name in data) {
  186. if (!Array.isArray(data[name])) {
  187. data[name] = [data[name]];
  188. }
  189. data[name].push(value);
  190. } else if (is_group.indexOf(name) >= 0) {
  191. data[name] = [value];
  192. } else {
  193. data[name] = value;
  194. }
  195. }
  196. function getData(form) {
  197. var data = {};
  198. // Populate data
  199. $("input,select", form).each(function() {
  200. var name = $(this).attr("name");
  201. var value = getValue(this);
  202. if (null !== value) {
  203. addValue(data, name, value);
  204. }
  205. });
  206. // Post process
  207. addValue(data, "schSwitch", 0xFF);
  208. delete data["filename"];
  209. delete data["rfbcode"];
  210. return data;
  211. }
  212. function randomString(length, chars) {
  213. var mask = "";
  214. if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; }
  215. if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
  216. if (chars.indexOf("#") > -1) { mask += "0123456789"; }
  217. if (chars.indexOf("@") > -1) { mask += "ABCDEF"; }
  218. if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
  219. var result = "";
  220. for (var i = length; i > 0; --i) {
  221. result += mask[Math.round(Math.random() * (mask.length - 1))];
  222. }
  223. return result;
  224. }
  225. function generateAPIKey() {
  226. var apikey = randomString(16, "@#");
  227. $("input[name='apiKey']").val(apikey);
  228. return false;
  229. }
  230. function getJson(str) {
  231. try {
  232. return JSON.parse(str);
  233. } catch (e) {
  234. return false;
  235. }
  236. }
  237. // -----------------------------------------------------------------------------
  238. // Actions
  239. // -----------------------------------------------------------------------------
  240. function sendAction(action, data) {
  241. websock.send(JSON.stringify({action: action, data: data}));
  242. }
  243. function sendConfig(data) {
  244. websock.send(JSON.stringify({config: data}));
  245. }
  246. function resetOriginals() {
  247. $("input,select").each(function() {
  248. $(this).attr("original", $(this).val());
  249. });
  250. numReboot = numReconnect = numReload = 0;
  251. }
  252. function doReload(milliseconds) {
  253. setTimeout(function() {
  254. window.location.reload();
  255. }, parseInt(milliseconds, 10));
  256. }
  257. /**
  258. * Check a file object to see if it is a valid firmware image
  259. * The file first byte should be 0xE9
  260. * @param {file} file File object
  261. * @param {Function} callback Function to call back with the result
  262. */
  263. function checkFirmware(file, callback) {
  264. var reader = new FileReader();
  265. reader.onloadend = function(evt) {
  266. if (FileReader.DONE === evt.target.readyState) {
  267. if (0xE9 !== evt.target.result.charCodeAt(0)) callback(false);
  268. if (0x03 !== evt.target.result.charCodeAt(2)) {
  269. var response = window.confirm("Binary image is not using DOUT flash mode. This might cause resets in some devices. Press OK to continue.");
  270. callback(response);
  271. } else {
  272. callback(true);
  273. }
  274. }
  275. };
  276. var blob = file.slice(0, 3);
  277. reader.readAsBinaryString(blob);
  278. }
  279. function doUpgrade() {
  280. var file = $("input[name='upgrade']")[0].files[0];
  281. if (typeof file === "undefined") {
  282. alert("First you have to select a file from your computer.");
  283. return false;
  284. }
  285. if (file.size > free_size) {
  286. alert("Image it too large to fit in the available space for OTA. Consider doing a two-step update.");
  287. return false;
  288. }
  289. checkFirmware(file, function(ok) {
  290. if (!ok) {
  291. alert("The file does not seem to be a valid firmware image.");
  292. return;
  293. }
  294. var data = new FormData();
  295. data.append("upgrade", file, file.name);
  296. $.ajax({
  297. // Your server script to process the upload
  298. url: urls.upgrade.href,
  299. type: "POST",
  300. // Form data
  301. data: data,
  302. // Tell jQuery not to process data or worry about content-type
  303. // You *must* include these options!
  304. cache: false,
  305. contentType: false,
  306. processData: false,
  307. success: function(data, text) {
  308. $("#upgrade-progress").hide();
  309. if ("OK" === data) {
  310. alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
  311. doReload(5000);
  312. } else {
  313. alert("There was an error trying to upload the new image, please try again (" + data + ").");
  314. }
  315. },
  316. // Custom XMLHttpRequest
  317. xhr: function() {
  318. $("#upgrade-progress").show();
  319. var myXhr = $.ajaxSettings.xhr();
  320. if (myXhr.upload) {
  321. // For handling the progress of the upload
  322. myXhr.upload.addEventListener("progress", function(e) {
  323. if (e.lengthComputable) {
  324. $("progress").attr({ value: e.loaded, max: e.total });
  325. }
  326. } , false);
  327. }
  328. return myXhr;
  329. }
  330. });
  331. });
  332. return false;
  333. }
  334. function doUpdatePassword() {
  335. var form = $("#formPassword");
  336. if (validateForm(form)) {
  337. sendConfig(getData(form));
  338. }
  339. return false;
  340. }
  341. function checkChanges() {
  342. if (numChanged > 0) {
  343. var response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
  344. if (response) {
  345. doUpdate();
  346. }
  347. }
  348. }
  349. function doAction(question, action) {
  350. checkChanges();
  351. if (question) {
  352. var response = window.confirm(question);
  353. if (false === response) {
  354. return false;
  355. }
  356. }
  357. sendAction(action, {});
  358. doReload(5000);
  359. return false;
  360. }
  361. function doReboot(ask) {
  362. var question = (typeof ask === "undefined" || false === ask) ?
  363. null :
  364. "Are you sure you want to reboot the device?";
  365. return doAction(question, "reboot");
  366. }
  367. function doReconnect(ask) {
  368. var question = (typeof ask === "undefined" || false === ask) ?
  369. null :
  370. "Are you sure you want to disconnect from the current WIFI network?";
  371. return doAction(question, "reconnect");
  372. }
  373. function doUpdate() {
  374. var form = $("#formSave");
  375. if (validateForm(form)) {
  376. // Get data
  377. sendConfig(getData(form));
  378. // Empty special fields
  379. $(".pwrExpected").val(0);
  380. $("input[name='pwrResetCalibration']").prop("checked", false);
  381. $("input[name='pwrResetE']").prop("checked", false);
  382. // Change handling
  383. numChanged = 0;
  384. setTimeout(function() {
  385. var response;
  386. if (numReboot > 0) {
  387. response = window.confirm("You have to reboot the board for the changes to take effect, do you want to do it now?");
  388. if (response) { doReboot(false); }
  389. } else if (numReconnect > 0) {
  390. response = window.confirm("You have to reconnect to the WiFi for the changes to take effect, do you want to do it now?");
  391. if (response) { doReconnect(false); }
  392. } else if (numReload > 0) {
  393. response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?");
  394. if (response) { doReload(0); }
  395. }
  396. resetOriginals();
  397. }, 1000);
  398. }
  399. return false;
  400. }
  401. function doBackup() {
  402. document.getElementById("downloader").src = urls.config.href;
  403. return false;
  404. }
  405. function onFileUpload(event) {
  406. var inputFiles = this.files;
  407. if (typeof inputFiles === "undefined" || inputFiles.length === 0) {
  408. return false;
  409. }
  410. var inputFile = inputFiles[0];
  411. this.value = "";
  412. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  413. if (!response) {
  414. return false;
  415. }
  416. var reader = new FileReader();
  417. reader.onload = function(e) {
  418. var data = getJson(e.target.result);
  419. if (data) {
  420. sendAction("restore", data);
  421. } else {
  422. window.alert(messages[4]);
  423. }
  424. };
  425. reader.readAsText(inputFile);
  426. return false;
  427. }
  428. function doRestore() {
  429. if (typeof window.FileReader !== "function") {
  430. alert("The file API isn't supported on this browser yet.");
  431. } else {
  432. $("#uploader").click();
  433. }
  434. return false;
  435. }
  436. function doFactoryReset() {
  437. var response = window.confirm("Are you sure you want to restore to factory settings?");
  438. if (response === false) {
  439. return false;
  440. }
  441. sendAction("factory_reset", {});
  442. doReload(5000);
  443. return false;
  444. }
  445. function doToggle(id, value) {
  446. sendAction("relay", {id: id, status: value ? 1 : 0 });
  447. return false;
  448. }
  449. function doScan() {
  450. $("#scanResult").html("");
  451. $("div.scan.loading").show();
  452. sendAction("scan", {});
  453. return false;
  454. }
  455. function doHAConfig() {
  456. $("#haConfig").html("");
  457. sendAction("haconfig", {});
  458. return false;
  459. }
  460. function doDebugCommand() {
  461. var el = $("input[name='dbgcmd']");
  462. var command = el.val();
  463. el.val("");
  464. sendAction("dbgcmd", {command: command});
  465. return false;
  466. }
  467. function doDebugClear() {
  468. $("#weblog").text("");
  469. return false;
  470. }
  471. <!-- removeIf(!rfm69)-->
  472. function doClearCounts() {
  473. sendAction("clear-counts", {});
  474. return false;
  475. }
  476. function doClearMessages() {
  477. packets.clear().draw(false);
  478. return false;
  479. }
  480. function doFilter(e) {
  481. var index = packets.cell(this).index();
  482. if (index == 'undefined') return;
  483. var c = index.column;
  484. var column = packets.column(c);
  485. if (filters[c]) {
  486. filters[c] = false;
  487. column.search("");
  488. $(column.header()).removeClass("filtered");
  489. } else {
  490. filters[c] = true;
  491. var data = packets.row(this).data();
  492. if (e.which == 1) {
  493. column.search('^' + data[c] + '$', true, false );
  494. } else {
  495. column.search('^((?!(' + data[c] + ')).)*$', true, false );
  496. }
  497. $(column.header()).addClass("filtered");
  498. }
  499. column.draw();
  500. return false;
  501. }
  502. function doClearFilters() {
  503. for (var i = 0; i < packets.columns()[0].length; i++) {
  504. if (filters[i]) {
  505. filters[i] = false;
  506. var column = packets.column(i);
  507. column.search("");
  508. $(column.header()).removeClass("filtered");
  509. column.draw();
  510. }
  511. }
  512. return false;
  513. }
  514. <!-- endRemoveIf(!rfm69)-->
  515. // -----------------------------------------------------------------------------
  516. // Visualization
  517. // -----------------------------------------------------------------------------
  518. function toggleMenu() {
  519. $("#layout").toggleClass("active");
  520. $("#menu").toggleClass("active");
  521. $("#menuLink").toggleClass("active");
  522. }
  523. function showPanel() {
  524. $(".panel").hide();
  525. if ($("#layout").hasClass("active")) { toggleMenu(); }
  526. $("#" + $(this).attr("data")).show();
  527. }
  528. // -----------------------------------------------------------------------------
  529. // Relays & magnitudes mapping
  530. // -----------------------------------------------------------------------------
  531. function createRelayList(data, container, template_name) {
  532. var current = $("#" + container + " > div").length;
  533. if (current > 0) { return; }
  534. var template = $("#" + template_name + " .pure-g")[0];
  535. for (var i in data) {
  536. var line = $(template).clone();
  537. $("label", line).html("Switch #" + i);
  538. $("input", line).attr("tabindex", 40 + i).val(data[i]);
  539. line.appendTo("#" + container);
  540. }
  541. }
  542. <!-- removeIf(!sensor)-->
  543. function createMagnitudeList(data, container, template_name) {
  544. var current = $("#" + container + " > div").length;
  545. if (current > 0) { return; }
  546. var template = $("#" + template_name + " .pure-g")[0];
  547. for (var i in data) {
  548. var magnitude = data[i];
  549. var line = $(template).clone();
  550. $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
  551. $("div.hint", line).html(magnitude.name);
  552. $("input", line).attr("tabindex", 40 + i).val(magnitude.idx);
  553. line.appendTo("#" + container);
  554. }
  555. }
  556. <!-- endRemoveIf(!sensor)-->
  557. // -----------------------------------------------------------------------------
  558. // RFM69
  559. // -----------------------------------------------------------------------------
  560. <!-- removeIf(!rfm69)-->
  561. function addMapping() {
  562. var template = $("#nodeTemplate .pure-g")[0];
  563. var line = $(template).clone();
  564. var tabindex = $("#mapping > div").length * 3 + 50;
  565. $(line).find("input").each(function() {
  566. $(this).attr("tabindex", tabindex++);
  567. });
  568. $(line).find("button").on('click', delMapping);
  569. line.appendTo("#mapping");
  570. }
  571. function delMapping() {
  572. var parent = $(this).parent().parent();
  573. $(parent).remove();
  574. }
  575. <!-- endRemoveIf(!rfm69)-->
  576. // -----------------------------------------------------------------------------
  577. // Wifi
  578. // -----------------------------------------------------------------------------
  579. function delNetwork() {
  580. var parent = $(this).parents(".pure-g");
  581. $(parent).remove();
  582. }
  583. function moreNetwork() {
  584. var parent = $(this).parents(".pure-g");
  585. $(".more", parent).toggle();
  586. }
  587. function addNetwork() {
  588. var numNetworks = $("#networks > div").length;
  589. if (numNetworks >= maxNetworks) {
  590. alert("Max number of networks reached");
  591. return null;
  592. }
  593. var tabindex = 200 + numNetworks * 10;
  594. var template = $("#networkTemplate").children();
  595. var line = $(template).clone();
  596. $(line).find("input").each(function() {
  597. $(this).attr("tabindex", tabindex);
  598. tabindex++;
  599. });
  600. $(line).find(".button-del-network").on("click", delNetwork);
  601. $(line).find(".button-more-network").on("click", moreNetwork);
  602. line.appendTo("#networks");
  603. return line;
  604. }
  605. // -----------------------------------------------------------------------------
  606. // Relays scheduler
  607. // -----------------------------------------------------------------------------
  608. function delSchedule() {
  609. var parent = $(this).parents(".pure-g");
  610. $(parent).remove();
  611. }
  612. function moreSchedule() {
  613. var parent = $(this).parents(".pure-g");
  614. $("div.more", parent).toggle();
  615. }
  616. function addSchedule(event) {
  617. var numSchedules = $("#schedules > div").length;
  618. if (numSchedules >= maxSchedules) {
  619. alert("Max number of schedules reached");
  620. return null;
  621. }
  622. var tabindex = 200 + numSchedules * 10;
  623. var template = $("#scheduleTemplate").children();
  624. var line = $(template).clone();
  625. var type = (1 === event.data.schType) ? "switch" : "light";
  626. template = $("#" + type + "ActionTemplate").children();
  627. var actionLine = template.clone();
  628. $(line).find("#schActionDiv").append(actionLine);
  629. $(line).find("input").each(function() {
  630. $(this).attr("tabindex", tabindex);
  631. tabindex++;
  632. });
  633. $(line).find(".button-del-schedule").on("click", delSchedule);
  634. $(line).find(".button-more-schedule").on("click", moreSchedule);
  635. $(line).find("input[name='schUTC']").prop("id", "schUTC" + (numSchedules + 1))
  636. .next().prop("for", "schUTC" + (numSchedules + 1));
  637. $(line).find("input[name='schEnabled']").prop("id", "schEnabled" + (numSchedules + 1))
  638. .next().prop("for", "schEnabled" + (numSchedules + 1));
  639. line.appendTo("#schedules");
  640. $(line).find("input[type='checkbox']").prop("checked", false);
  641. return line;
  642. }
  643. // -----------------------------------------------------------------------------
  644. // Relays
  645. // -----------------------------------------------------------------------------
  646. function initRelays(data) {
  647. var current = $("#relays > div").length;
  648. if (current > 0) { return; }
  649. var template = $("#relayTemplate .pure-g")[0];
  650. for (var i=0; i<data.length; i++) {
  651. // Add relay fields
  652. var line = $(template).clone();
  653. $(".id", line).html(i);
  654. $(":checkbox", line).prop('checked', data[i]).attr("data", i)
  655. .prop("id", "relay" + i)
  656. .on("change", function (event) {
  657. var id = parseInt($(event.target).attr("data"), 10);
  658. var status = $(event.target).prop("checked");
  659. doToggle(id, status);
  660. });
  661. $("label.toggle", line).prop("for", "relay" + i)
  662. line.appendTo("#relays");
  663. // Populate the relay SELECTs
  664. $("select.isrelay").append(
  665. $("<option></option>").attr("value",i).text("Switch #" + i));
  666. }
  667. }
  668. function createCheckboxes() {
  669. $("input[type='checkbox']").each(function() {
  670. if($(this).prop("name"))$(this).prop("id", $(this).prop("name"));
  671. $(this).parent().addClass("toggleWrapper");
  672. $(this).after('<label for="' + $(this).prop("name") + '" class="toggle"><span class="toggle__handler"></span></label>')
  673. });
  674. }
  675. function initRelayConfig(data) {
  676. var current = $("#relayConfig > div").length;
  677. if (current > 0) { return; }
  678. var template = $("#relayConfigTemplate").children();
  679. for (var i in data) {
  680. var relay = data[i];
  681. var line = $(template).clone();
  682. $("span.gpio", line).html(relay.gpio);
  683. $("span.id", line).html(i);
  684. $("select[name='rlyBoot']", line).val(relay.boot);
  685. $("select[name='rlyPulse']", line).val(relay.pulse);
  686. $("input[name='rlyTime']", line).val(relay.pulse_ms);
  687. $("input[name='mqttGroup']", line).val(relay.group);
  688. $("select[name='mqttGroupInv']", line).val(relay.group_inv);
  689. $("select[name='rlyOnDisc']", line).val(relay.on_disc);
  690. line.appendTo("#relayConfig");
  691. }
  692. }
  693. // -----------------------------------------------------------------------------
  694. // Sensors & Magnitudes
  695. // -----------------------------------------------------------------------------
  696. <!-- removeIf(!sensor)-->
  697. function initMagnitudes(data) {
  698. // check if already initialized
  699. var done = $("#magnitudes > div").length;
  700. if (done > 0) { return; }
  701. // add templates
  702. var template = $("#magnitudeTemplate").children();
  703. for (var i in data) {
  704. var magnitude = data[i];
  705. var line = $(template).clone();
  706. $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
  707. $("div.hint", line).html(magnitude.description);
  708. $("input", line).attr("data", i);
  709. line.appendTo("#magnitudes");
  710. }
  711. }
  712. <!-- endRemoveIf(!sensor)-->
  713. // -----------------------------------------------------------------------------
  714. // Lights
  715. // -----------------------------------------------------------------------------
  716. <!-- removeIf(!light)-->
  717. function initColorRGB() {
  718. // check if already initialized
  719. var done = $("#colors > div").length;
  720. if (done > 0) { return; }
  721. // add template
  722. var template = $("#colorRGBTemplate").children();
  723. var line = $(template).clone();
  724. line.appendTo("#colors");
  725. // init color wheel
  726. $("input[name='color']").wheelColorPicker({
  727. sliders: "wrgbp"
  728. }).on("sliderup", function() {
  729. var value = $(this).wheelColorPicker("getValue", "css");
  730. sendAction("color", {rgb: value});
  731. });
  732. // init bright slider
  733. $("#brightness").on("change", function() {
  734. var value = $(this).val();
  735. var parent = $(this).parents(".pure-g");
  736. $("span", parent).html(value);
  737. sendAction("color", {brightness: value});
  738. });
  739. }
  740. function initCCT() {
  741. // check if already initialized
  742. var done = $("#cct > div").length;
  743. if (done > 0) { return; }
  744. $("#miredsTemplate").children().clone().appendTo("#cct");
  745. $("#mireds").on("change", function() {
  746. var value = $(this).val();
  747. var parent = $(this).parents(".pure-g");
  748. $("span", parent).html(value);
  749. sendAction("mireds", {mireds: value});
  750. });
  751. }
  752. function initColorHSV() {
  753. // check if already initialized
  754. var done = $("#colors > div").length;
  755. if (done > 0) { return; }
  756. // add template
  757. var template = $("#colorHSVTemplate").children();
  758. var line = $(template).clone();
  759. line.appendTo("#colors");
  760. // init color wheel
  761. $("input[name='color']").wheelColorPicker({
  762. sliders: "whsvp"
  763. }).on("sliderup", function() {
  764. var color = $(this).wheelColorPicker("getColor");
  765. var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10);
  766. sendAction("color", {hsv: value});
  767. });
  768. }
  769. function initChannels(num) {
  770. // check if already initialized
  771. var done = $("#channels > div").length > 0;
  772. if (done) { return; }
  773. // does it have color channels?
  774. var colors = $("#colors > div").length > 0;
  775. // calculate channels to create
  776. var max = num;
  777. if (colors) {
  778. max = num % 3;
  779. if ((max > 0) & litWhite) {
  780. max--;
  781. if (litCCT) {
  782. max--;
  783. }
  784. }
  785. }
  786. var start = num - max;
  787. var onChannelSliderChange = function() {
  788. var id = $(this).attr("data");
  789. var value = $(this).val();
  790. var parent = $(this).parents(".pure-g");
  791. $("span", parent).html(value);
  792. sendAction("channel", {id: id, value: value});
  793. };
  794. // add templates
  795. var i = 0;
  796. var template = $("#channelTemplate").children();
  797. for (i=0; i<max; i++) {
  798. var channel_id = start + i;
  799. var line = $(template).clone();
  800. $("span.slider", line).attr("data", channel_id);
  801. $("input.slider", line).attr("data", channel_id).on("change", onChannelSliderChange);
  802. $("label", line).html("Channel #" + channel_id);
  803. line.appendTo("#channels");
  804. }
  805. for (i=0; i<num; i++) {
  806. $("select.islight").append(
  807. $("<option></option>").attr("value",i).text("Channel #" + i));
  808. }
  809. }
  810. <!-- endRemoveIf(!light)-->
  811. // -----------------------------------------------------------------------------
  812. // RFBridge
  813. // -----------------------------------------------------------------------------
  814. <!-- removeIf(!rfbridge)-->
  815. function rfbLearn() {
  816. var parent = $(this).parents(".pure-g");
  817. var input = $("input", parent);
  818. sendAction("rfblearn", {id: input.attr("data-id"), status: input.attr("data-status")});
  819. }
  820. function rfbForget() {
  821. var parent = $(this).parents(".pure-g");
  822. var input = $("input", parent);
  823. sendAction("rfbforget", {id: input.attr("data-id"), status: input.attr("data-status")});
  824. }
  825. function rfbSend() {
  826. var parent = $(this).parents(".pure-g");
  827. var input = $("input", parent);
  828. sendAction("rfbsend", {id: input.attr("data-id"), status: input.attr("data-status"), data: input.val()});
  829. }
  830. function addRfbNode() {
  831. var numNodes = $("#rfbNodes > legend").length;
  832. var template = $("#rfbNodeTemplate").children();
  833. var line = $(template).clone();
  834. var status = true;
  835. $("span", line).html(numNodes);
  836. $(line).find("input").each(function() {
  837. $(this).attr("data-id", numNodes);
  838. $(this).attr("data-status", status ? 1 : 0);
  839. status = !status;
  840. });
  841. $(line).find(".button-rfb-learn").on("click", rfbLearn);
  842. $(line).find(".button-rfb-forget").on("click", rfbForget);
  843. $(line).find(".button-rfb-send").on("click", rfbSend);
  844. line.appendTo("#rfbNodes");
  845. return line;
  846. }
  847. <!-- endRemoveIf(!rfbridge)-->
  848. // -----------------------------------------------------------------------------
  849. // Processing
  850. // -----------------------------------------------------------------------------
  851. function processData(data) {
  852. // title
  853. if ("app_name" in data) {
  854. var title = data.app_name;
  855. if ("app_version" in data) {
  856. title = title + " " + data.app_version;
  857. }
  858. $("span[name=title]").html(title);
  859. if ("hostname" in data) {
  860. title = data.hostname + " - " + title;
  861. }
  862. document.title = title;
  863. }
  864. Object.keys(data).forEach(function(key) {
  865. var i;
  866. var value = data[key];
  867. // ---------------------------------------------------------------------
  868. // Web mode
  869. // ---------------------------------------------------------------------
  870. if ("webMode" === key) {
  871. password = (1 === value);
  872. $("#layout").toggle(!password);
  873. $("#password").toggle(password);
  874. }
  875. // ---------------------------------------------------------------------
  876. // Actions
  877. // ---------------------------------------------------------------------
  878. if ("action" === key) {
  879. if ("reload" === data.action) { doReload(1000); }
  880. return;
  881. }
  882. // ---------------------------------------------------------------------
  883. // RFBridge
  884. // ---------------------------------------------------------------------
  885. <!-- removeIf(!rfbridge)-->
  886. if ("rfbCount" === key) {
  887. for (i=0; i<data.rfbCount; i++) { addRfbNode(); }
  888. return;
  889. }
  890. if ("rfbrawVisible" === key) {
  891. $("input[name='rfbcode']").attr("maxlength", 116);
  892. }
  893. if ("rfb" === key) {
  894. var nodes = data.rfb;
  895. for (i in nodes) {
  896. var node = nodes[i];
  897. $("input[name='rfbcode'][data-id='" + node.id + "'][data-status='" + node.status + "']").val(node.data);
  898. }
  899. return;
  900. }
  901. <!-- endRemoveIf(!rfbridge)-->
  902. // ---------------------------------------------------------------------
  903. // RFM69
  904. // ---------------------------------------------------------------------
  905. <!-- removeIf(!rfm69)-->
  906. if (key == "packet") {
  907. var packet = data.packet;
  908. var d = new Date();
  909. packets.row.add([
  910. d.toLocaleTimeString('en-US', { hour12: false }),
  911. packet.senderID,
  912. packet.packetID,
  913. packet.targetID,
  914. packet.key,
  915. packet.value,
  916. packet.rssi,
  917. packet.duplicates,
  918. packet.missing,
  919. ]).draw(false);
  920. return;
  921. }
  922. if (key == "mapping") {
  923. for (var i in data.mapping) {
  924. // add a new row
  925. addMapping();
  926. // get group
  927. var line = $("#mapping .pure-g")[i];
  928. // fill in the blanks
  929. var mapping = data.mapping[i];
  930. Object.keys(mapping).forEach(function(key) {
  931. var id = "input[name=" + key + "]";
  932. if ($(id, line).length) $(id, line).val(mapping[key]).attr("original", mapping[key]);
  933. });
  934. }
  935. return;
  936. }
  937. <!-- endRemoveIf(!rfm69)-->
  938. // ---------------------------------------------------------------------
  939. // Lights
  940. // ---------------------------------------------------------------------
  941. <!-- removeIf(!light)-->
  942. if ("rgb" === key) {
  943. initColorRGB();
  944. $("input[name='color']").wheelColorPicker("setValue", value, true);
  945. return;
  946. }
  947. if ("hsv" === key) {
  948. initColorHSV();
  949. // wheelColorPicker expects HSV to be between 0 and 1 all of them
  950. var chunks = value.split(",");
  951. var obj = {};
  952. obj.h = chunks[0] / 360;
  953. obj.s = chunks[1] / 100;
  954. obj.v = chunks[2] / 100;
  955. $("input[name='color']").wheelColorPicker("setColor", obj);
  956. return;
  957. }
  958. if ("brightness" === key) {
  959. $("#brightness").val(value);
  960. $("span.brightness").html(value);
  961. return;
  962. }
  963. if ("channels" === key) {
  964. var len = value.length;
  965. initChannels(len);
  966. for (i in value) {
  967. var ch = value[i];
  968. $("input.slider[data=" + i + "]").val(ch);
  969. $("span.slider[data=" + i + "]").html(ch);
  970. }
  971. return;
  972. }
  973. if ("mireds" === key) {
  974. $("#mireds").val(value);
  975. $("span.mireds").html(value);
  976. return;
  977. }
  978. if ("litWhite" === key) {
  979. litWhite = value;
  980. }
  981. if ("litCCT" === key) {
  982. initCCT();
  983. litCCT = value;
  984. }
  985. <!-- endRemoveIf(!light)-->
  986. // ---------------------------------------------------------------------
  987. // Sensors & Magnitudes
  988. // ---------------------------------------------------------------------
  989. <!-- removeIf(!sensor)-->
  990. if ("magnitudes" === key) {
  991. initMagnitudes(value);
  992. for (i in value) {
  993. var magnitude = value[i];
  994. var error = magnitude.error || 0;
  995. var text = (0 === error) ?
  996. magnitude.value + magnitude.units :
  997. magnitudeError(error);
  998. var element = $("input[name='magnitude'][data='" + i + "']");
  999. element.val(text);
  1000. $("div.hint", element.parent().parent()).html(magnitude.description);
  1001. }
  1002. return;
  1003. }
  1004. <!-- endRemoveIf(!sensor)-->
  1005. // ---------------------------------------------------------------------
  1006. // WiFi
  1007. // ---------------------------------------------------------------------
  1008. if ("maxNetworks" === key) {
  1009. maxNetworks = parseInt(value, 10);
  1010. return;
  1011. }
  1012. if ("wifi" === key) {
  1013. for (i in value) {
  1014. var wifi = value[i];
  1015. var nwk_line = addNetwork();
  1016. Object.keys(wifi).forEach(function(key) {
  1017. $("input[name='" + key + "']", nwk_line).val(wifi[key]);
  1018. });
  1019. }
  1020. return;
  1021. }
  1022. if ("scanResult" === key) {
  1023. $("div.scan.loading").hide();
  1024. $("#scanResult").show();
  1025. }
  1026. // -----------------------------------------------------------------------------
  1027. // Home Assistant
  1028. // -----------------------------------------------------------------------------
  1029. if ("haConfig" === key) {
  1030. $("#haConfig").show();
  1031. }
  1032. // -----------------------------------------------------------------------------
  1033. // Relays scheduler
  1034. // -----------------------------------------------------------------------------
  1035. if ("maxSchedules" === key) {
  1036. maxSchedules = parseInt(value, 10);
  1037. return;
  1038. }
  1039. if ("schedule" === key) {
  1040. for (i in value) {
  1041. var schedule = value[i];
  1042. var sch_line = addSchedule({ data: {schType: schedule["schType"] }});
  1043. Object.keys(schedule).forEach(function(key) {
  1044. var sch_value = schedule[key];
  1045. $("input[name='" + key + "']", sch_line).val(sch_value);
  1046. $("select[name='" + key + "']", sch_line).prop("value", sch_value);
  1047. $("input[type='checkbox'][name='" + key + "']", sch_line).prop("checked", sch_value);
  1048. });
  1049. }
  1050. return;
  1051. }
  1052. // ---------------------------------------------------------------------
  1053. // Relays
  1054. // ---------------------------------------------------------------------
  1055. if ("relayStatus" === key) {
  1056. initRelays(value);
  1057. for (i in value) {
  1058. $("input[name='relay'][data='" + i + "']").prop("checked", value[i]);
  1059. }
  1060. return;
  1061. }
  1062. // Relay configuration
  1063. if ("relayConfig" === key) {
  1064. initRelayConfig(value);
  1065. return;
  1066. }
  1067. // ---------------------------------------------------------------------
  1068. // Domoticz
  1069. // ---------------------------------------------------------------------
  1070. // Domoticz - Relays
  1071. if ("dczRelays" === key) {
  1072. createRelayList(value, "dczRelays", "dczRelayTemplate");
  1073. return;
  1074. }
  1075. // Domoticz - Magnitudes
  1076. <!-- removeIf(!sensor)-->
  1077. if ("dczMagnitudes" === key) {
  1078. createMagnitudeList(value, "dczMagnitudes", "dczMagnitudeTemplate");
  1079. return;
  1080. }
  1081. <!-- endRemoveIf(!sensor)-->
  1082. // ---------------------------------------------------------------------
  1083. // Thingspeak
  1084. // ---------------------------------------------------------------------
  1085. // Thingspeak - Relays
  1086. if ("tspkRelays" === key) {
  1087. createRelayList(value, "tspkRelays", "tspkRelayTemplate");
  1088. return;
  1089. }
  1090. // Thingspeak - Magnitudes
  1091. <!-- removeIf(!sensor)-->
  1092. if ("tspkMagnitudes" === key) {
  1093. createMagnitudeList(value, "tspkMagnitudes", "tspkMagnitudeTemplate");
  1094. return;
  1095. }
  1096. <!-- endRemoveIf(!sensor)-->
  1097. // ---------------------------------------------------------------------
  1098. // General
  1099. // ---------------------------------------------------------------------
  1100. // Messages
  1101. if ("message" === key) {
  1102. window.alert(messages[value]);
  1103. return;
  1104. }
  1105. // Web log
  1106. if ("weblog" === key) {
  1107. $("#weblog").append(new Text(value));
  1108. $("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
  1109. return;
  1110. }
  1111. // Enable options
  1112. var position = key.indexOf("Visible");
  1113. if (position > 0 && position === key.length - 7) {
  1114. var module = key.slice(0,-7);
  1115. $(".module-" + module).css("display", "inherit");
  1116. return;
  1117. }
  1118. if ("deviceip" === key) {
  1119. var a_href = $("span[name='" + key + "']").parent();
  1120. a_href.attr("href", "//" + value);
  1121. a_href.next().attr("href", "telnet://" + value);
  1122. }
  1123. if ("now" === key) {
  1124. now = value;
  1125. return;
  1126. }
  1127. if ("free_size" === key) {
  1128. free_size = parseInt(value, 10);
  1129. }
  1130. // Pre-process
  1131. if ("mqttStatus" === key) {
  1132. value = value ? "CONNECTED" : "NOT CONNECTED";
  1133. }
  1134. if ("ntpStatus" === key) {
  1135. value = value ? "SYNC'D" : "NOT SYNC'D";
  1136. }
  1137. if ("uptime" === key) {
  1138. ago = 0;
  1139. var uptime = parseInt(value, 10);
  1140. var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10);
  1141. var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10);
  1142. var hours = uptime % 24; uptime = parseInt(uptime / 24, 10);
  1143. var days = uptime;
  1144. value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s";
  1145. }
  1146. // ---------------------------------------------------------------------
  1147. // Matching
  1148. // ---------------------------------------------------------------------
  1149. var pre;
  1150. var post;
  1151. // Look for INPUTs
  1152. var input = $("input[name='" + key + "']");
  1153. if (input.length > 0) {
  1154. if (input.attr("type") === "checkbox") {
  1155. input.prop("checked", value);
  1156. } else if (input.attr("type") === "radio") {
  1157. input.val([value]);
  1158. } else {
  1159. pre = input.attr("pre") || "";
  1160. post = input.attr("post") || "";
  1161. input.val(pre + value + post);
  1162. }
  1163. }
  1164. // Look for SPANs
  1165. var span = $("span[name='" + key + "']");
  1166. if (span.length > 0) {
  1167. pre = span.attr("pre") || "";
  1168. post = span.attr("post") || "";
  1169. span.html(pre + value + post);
  1170. }
  1171. // Look for SELECTs
  1172. var select = $("select[name='" + key + "']");
  1173. if (select.length > 0) {
  1174. select.val(value);
  1175. }
  1176. });
  1177. // Auto generate an APIKey if none defined yet
  1178. if ($("input[name='apiKey']").val() === "") {
  1179. generateAPIKey();
  1180. }
  1181. resetOriginals();
  1182. }
  1183. function hasChanged() {
  1184. var newValue, originalValue;
  1185. if ($(this).attr("type") === "checkbox") {
  1186. newValue = $(this).prop("checked");
  1187. originalValue = ($(this).attr("original") === "true");
  1188. } else {
  1189. newValue = $(this).val();
  1190. originalValue = $(this).attr("original");
  1191. }
  1192. var hasChanged = $(this).attr("hasChanged") || 0;
  1193. var action = $(this).attr("action");
  1194. if (typeof originalValue === "undefined") { return; }
  1195. if ("none" === action) { return; }
  1196. if (newValue !== originalValue) {
  1197. if (0 === hasChanged) {
  1198. ++numChanged;
  1199. if ("reconnect" === action) { ++numReconnect; }
  1200. if ("reboot" === action) { ++numReboot; }
  1201. if ("reload" === action) { ++numReload; }
  1202. $(this).attr("hasChanged", 1);
  1203. }
  1204. } else {
  1205. if (1 === hasChanged) {
  1206. --numChanged;
  1207. if ("reconnect" === action) { --numReconnect; }
  1208. if ("reboot" === action) { --numReboot; }
  1209. if ("reload" === action) { --numReload; }
  1210. $(this).attr("hasChanged", 0);
  1211. }
  1212. }
  1213. }
  1214. // -----------------------------------------------------------------------------
  1215. // Init & connect
  1216. // -----------------------------------------------------------------------------
  1217. function initUrls(root) {
  1218. var paths = ["ws", "upgrade", "config", "auth"];
  1219. urls["root"] = root;
  1220. paths.forEach(function(path) {
  1221. urls[path] = new URL(path, root);
  1222. urls[path].protocol = root.protocol;
  1223. });
  1224. if (root.protocol == "https:") {
  1225. urls.ws.protocol = "wss:";
  1226. } else {
  1227. urls.ws.protocol = "ws:";
  1228. }
  1229. }
  1230. function connectToURL(url) {
  1231. initUrls(url);
  1232. $.ajax({
  1233. 'method': 'GET',
  1234. 'crossDomain': true,
  1235. 'url': urls.auth.href,
  1236. 'xhrFields': { 'withCredentials': true }
  1237. }).done(function(data) {
  1238. if (websock) { websock.close(); }
  1239. websock = new WebSocket(urls.ws.href);
  1240. websock.onmessage = function(evt) {
  1241. var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
  1242. if (data) {
  1243. processData(data);
  1244. }
  1245. };
  1246. }).fail(function() {
  1247. // Nothing to do, reload page and retry
  1248. });
  1249. }
  1250. function connect(host) {
  1251. if (!host.startsWith("http:") && !host.startsWith("https:")) {
  1252. host = "http://" + host;
  1253. }
  1254. connectToURL(new URL(host));
  1255. }
  1256. function connectToCurrentURL() {
  1257. connectToURL(new URL(window.location));
  1258. }
  1259. function getParameterByName(name) {
  1260. var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
  1261. return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
  1262. }
  1263. $(function() {
  1264. initMessages();
  1265. loadTimeZones();
  1266. createCheckboxes();
  1267. setInterval(function() { keepTime(); }, 1000);
  1268. $("#menuLink").on("click", toggleMenu);
  1269. $(".pure-menu-link").on("click", showPanel);
  1270. $("progress").attr({ value: 0, max: 100 });
  1271. $(".button-update").on("click", doUpdate);
  1272. $(".button-update-password").on("click", doUpdatePassword);
  1273. $(".button-reboot").on("click", doReboot);
  1274. $(".button-reconnect").on("click", doReconnect);
  1275. $(".button-wifi-scan").on("click", doScan);
  1276. $(".button-ha-config").on("click", doHAConfig);
  1277. $(".button-dbgcmd").on("click", doDebugCommand);
  1278. $("input[name='dbgcmd']").enterKey(doDebugCommand);
  1279. $(".button-dbg-clear").on("click", doDebugClear);
  1280. $(".button-settings-backup").on("click", doBackup);
  1281. $(".button-settings-restore").on("click", doRestore);
  1282. $(".button-settings-factory").on("click", doFactoryReset);
  1283. $("#uploader").on("change", onFileUpload);
  1284. $(".button-upgrade").on("click", doUpgrade);
  1285. $(".button-apikey").on("click", generateAPIKey);
  1286. $(".button-upgrade-browse").on("click", function() {
  1287. $("input[name='upgrade']")[0].click();
  1288. return false;
  1289. });
  1290. $("input[name='upgrade']").change(function (){
  1291. var file = this.files[0];
  1292. $("input[name='filename']").val(file.name);
  1293. });
  1294. $(".button-add-network").on("click", function() {
  1295. $(".more", addNetwork()).toggle();
  1296. });
  1297. $(".button-add-switch-schedule").on("click", { schType: 1 }, addSchedule);
  1298. <!-- removeIf(!light)-->
  1299. $(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule);
  1300. <!-- endRemoveIf(!light)-->
  1301. <!-- removeIf(!rfm69)-->
  1302. $(".button-add-mapping").on('click', addMapping);
  1303. $(".button-del-mapping").on('click', delMapping);
  1304. $(".button-clear-counts").on('click', doClearCounts);
  1305. $(".button-clear-messages").on('click', doClearMessages);
  1306. $(".button-clear-filters").on('click', doClearFilters);
  1307. $('#packets tbody').on('mousedown', 'td', doFilter);
  1308. packets = $('#packets').DataTable({
  1309. "paging": false
  1310. });
  1311. for (var i = 0; i < packets.columns()[0].length; i++) {
  1312. filters[i] = false;
  1313. }
  1314. <!-- endRemoveIf(!rfm69)-->
  1315. $(document).on("change", "input", hasChanged);
  1316. $(document).on("change", "select", hasChanged);
  1317. // don't autoconnect when opening from filesystem
  1318. if (window.location.protocol === "file:") { return; }
  1319. // Check host param in query string
  1320. if (host = getParameterByName('host')) {
  1321. connect(host);
  1322. } else {
  1323. connectToCurrentURL();
  1324. }
  1325. });