var debug = false; var websock; var password = false; var maxNetworks; var free_size = 0; var urls = {}; var numChanged = 0; var numReboot = 0; var numReconnect = 0; var numReload = 0; var configurationSaved = false; var ws_pingpong; //removeIf(!light) var colorPicker; var useWhite = false; var useCCT = false; //endRemoveIf(!light) var now = 0; var ago = 0; //removeIf(!rfm69) var packets; var filters = []; //endRemoveIf(!rfm69) //removeIf(!sensor) var Magnitudes = []; var MagnitudeErrors = {}; var MagnitudeNames = {}; var MagnitudeTypePrefixes = {}; var MagnitudePrefixTypes = {}; //endRemoveIf(!sensor) // ----------------------------------------------------------------------------- // Utils // ----------------------------------------------------------------------------- $.fn.enterKey = function (fnc) { return this.each(function () { $(this).keypress(function (ev) { var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10); if (13 === keycode) { return fnc.call(this, ev); } }); }); }; function followScroll(id, threshold) { if (threshold === undefined) { threshold = 90; } var elem = document.getElementById(id); var offset = (elem.scrollTop + elem.offsetHeight) / elem.scrollHeight * 100; if (offset > threshold) { elem.scrollTop = elem.scrollHeight; } } function fromSchema(source, schema) { if (schema.length !== source.length) { throw `Schema mismatch! Expected length ${schema.length} vs. ${source.length}`; } var target = {}; schema.forEach(function(key, index) { target[key] = source[index]; }); return target; } function keepTime() { $("span[name='ago']").html(ago); ago++; if (0 === now) { return; } var date = new Date(now * 1000); var text = date.toISOString().substring(0, 19).replace("T", " "); $("input[name='now']").val(text); $("span[name='now']").html(text); now++; } function zeroPad(number, positions) { return number.toString().padStart(positions, "0"); } function validatePassword(password) { // http://www.the-art-of-web.com/javascript/validate-password/ // at least one lowercase and one uppercase letter or number // at least eight characters (letters, numbers or special characters) // MUST be 8..63 printable ASCII characters. See: // https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution) // https://github.com/xoseperez/espurna/issues/1151 var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/; return ( (password !== undefined) && (typeof password === "string") && (password.length > 0) && re_password.test(password) ); } function validateFormPasswords(form) { var passwords = $("input[name='adminPass0'],input[name='adminPass1']", form); var adminPass1 = passwords.first().val(), adminPass2 = passwords.last().val(); var formValidity = passwords.first()[0].checkValidity(); if (formValidity && (adminPass1.length === 0) && (adminPass2.length === 0)) { return true; } var validPass1 = validatePassword(adminPass1), validPass2 = validatePassword(adminPass2); if (formValidity && validPass1 && validPass2) { return true; } if (!formValidity || (adminPass1.length > 0 && !validPass1)) { alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!"); } if (adminPass1 !== adminPass2) { alert("Passwords are different!"); } return false; } function validateFormHostname(form) { // RFCs mandate that a hostname's labels may contain only // the ASCII letters 'a' through 'z' (case-insensitive), // the digits '0' through '9', and the hyphen. // Hostname labels cannot begin or end with a hyphen. // No other symbols, punctuation characters, or blank spaces are permitted. // Negative lookbehind does not work in Javascript // var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,32}(?= 0; if ((haschanged || !changed) && !indexed) { changed_data.push(name); } // make sure to group keys from templates (or, manually flagged as such) var is_group = $(this).attr("data-settings-group") !== undefined; if (is_group) { if (name in data) { data[name].push(value); } else { data[name] = [value]; } } else { data[name] = value; } } }); // Finally, filter out only fields that had changed. // Note: We need to preserve dynamic lists like schedules, wifi etc. // so we don't accidentally break when user deletes entry in the middle var resulting_data = {}; for (var value in data) { if (changed_data.indexOf(value) >= 0) { resulting_data[value] = data[value]; } } // Hack: clean-up leftover arrays. // When empty, the receiving side will prune all keys greater than the current one. if (cleanup) { $(".settings-group").each(function() { var haschanged = ("true" === $(this).attr("hasChanged")); if (haschanged && !this.children.length) { var targets = this.dataset.settingsTarget; if (targets === undefined) return; targets.split(" ").forEach(function(target) { resulting_data[target] = []; }); } }); } return resulting_data; } function randomString(length, args) { if (typeof args === "undefined") { args = { lowercase: true, uppercase: true, numbers: true, special: true } } var mask = ""; if (args.lowercase) { mask += "abcdefghijklmnopqrstuvwxyz"; } if (args.uppercase) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } if (args.numbers || args.hex) { mask += "0123456789"; } if (args.hex) { mask += "ABCDEF"; } if (args.special) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; } var source = new Uint32Array(length); var result = new Array(length); window.crypto.getRandomValues(source).forEach(function(value, i) { result[i] = mask[value % mask.length]; }); return result.join(""); } function generateAPIKey() { var apikey = randomString(16, {hex: true}); $("input[name='apiKey']") .val(apikey) .attr("original", "-".repeat(16)) .attr("haschanged", "true"); return false; } function generatePassword() { var password = ""; do { password = randomString(10); } while (!validatePassword(password)); return password; } function toggleVisiblePassword() { var elem = this.previousElementSibling; if (elem.type === "password") { elem.type = "text"; } else { elem.type = "password"; } return false; } function doGeneratePassword() { var elems = $("input", $("#formPassword")); elems .val(generatePassword()) .attr("haschanged", "true") .each(function() { this.type = "text"; }); return false; } function moduleVisible(module) { if (module == "sch") { $("li.module-" + module).css("display", "inherit"); $("div.module-" + module).css("display", "flex"); return; } $(".module-" + module).css("display", "inherit"); } //removeIf(!thermostat) function checkTempRangeMin() { var min = parseInt($("#tempRangeMinInput").val(), 10); var max = parseInt($("#tempRangeMaxInput").val(), 10); if (min > max - 1) { $("#tempRangeMinInput").val(max - 1); } } function checkTempRangeMax() { var min = parseInt($("#tempRangeMinInput").val(), 10); var max = parseInt($("#tempRangeMaxInput").val(), 10); if (max < min + 1) { $("#tempRangeMaxInput").val(min + 1); } } function doResetThermostatCounters(ask) { var question = (typeof ask === "undefined" || false === ask) ? null : "Are you sure you want to reset burning counters?"; return doAction(question, "thermostat_reset_counters"); } //endRemoveIf(!thermostat) function initSelectGPIO(select) { // TODO: properly lock used GPIOs via locking and apply the mask here var mapping = [ [153, "NONE"], [0, "0 (FLASH)"], [1, "1 (U0TXD)"], [2, "2 (U1TXD)"], [3, "3 (U0RXD)"], [4, "4 (SDA)"], [5, "5 (SCL)"], [9, "9 (SDD2)"], [10, "10 (SDD3)"], [12, "12 (MTDI)"], [13, "13 (MTCK)"], [14, "14 (MTMS)"], [15, "15 (MTDO)"], [16, "16 (WAKE)"], ]; for (n in mapping) { var elem = $('").attr("value",i).text("Channel #" + i)); } // add brightness template var line = loadTemplate("brightnessTemplate"); line.appendTo("#channels"); // init bright slider $("#brightness").on("change", function() { var value = $(this).val(); var parent = $(this).parents(".pure-g"); $("span", parent).html(value); sendAction("brightness", {value: value}); }); } //endRemoveIf(!light) // ----------------------------------------------------------------------------- // RFBridge // ----------------------------------------------------------------------------- //removeIf(!rfbridge) function rfbLearn() { var parent = $(this).parents(".pure-g"); var input = $("input", parent); sendAction("rfblearn", {id: input.attr("data-id"), status: input.attr("data-status")}); } function rfbForget() { var parent = $(this).parents(".pure-g"); var input = $("input", parent); sendAction("rfbforget", {id: input.attr("data-id"), status: input.attr("data-status")}); } function rfbSend() { var parent = $(this).parents(".pure-g"); var input = $("input", parent); sendAction("rfbsend", {id: input.attr("data-id"), status: input.attr("data-status"), data: input.val()}); } function addRfbNode() { var numNodes = $("#rfbNodes > legend").length; var line = loadTemplate("rfbNodeTemplate"); $("span", line).html(numNodes); $(line).find("input").each(function() { this.dataset["id"] = numNodes; }); $(line).find(".button-rfb-learn").on("click", rfbLearn); $(line).find(".button-rfb-forget").on("click", rfbForget); $(line).find(".button-rfb-send").on("click", rfbSend); line.appendTo("#rfbNodes"); return line; } //endRemoveIf(!rfbridge) // ----------------------------------------------------------------------------- // LightFox // ----------------------------------------------------------------------------- //removeIf(!lightfox) function lightfoxLearn() { sendAction("lightfoxLearn", {}); } function lightfoxClear() { sendAction("lightfoxClear", {}); } //endRemoveIf(!lightfox) // ----------------------------------------------------------------------------- // Processing // ----------------------------------------------------------------------------- function processData(data) { if (debug) console.log(data); // title if ("app_name" in data) { var title = data.app_name; if ("app_version" in data) { $("span[name=title]").html(data.app_version); title = title + " " + data.app_version; } if ("hostname" in data) { title = data.hostname + " - " + title; } document.title = title; } Object.keys(data).forEach(function(key) { var i; var value = data[key]; // --------------------------------------------------------------------- // Web mode // --------------------------------------------------------------------- if ("webMode" === key) { password = (1 === value); $("#layout").toggle(!password); $("#password").toggle(password); } // --------------------------------------------------------------------- // Actions // --------------------------------------------------------------------- if ("action" === key) { if ("reload" === data.action) { doReload(1000); } return; } // --------------------------------------------------------------------- // RFBridge // --------------------------------------------------------------------- //removeIf(!rfbridge) if ("rfbCount" === key) { for (i=0; i 0)); var processOff = ((rfb.off !== undefined) && (rfb.off.length > 0)); for (let i = 0; i < size; ++i) { let selector = template`input[name='rfbcode'][data-id='${id}'][data-status='${status}']`; let id = i + start; if (processOn) { $(selector({"id": id, "status": 1})) .val(rfb.on[i]); } if (processOff) { $(selector({"id": id, "status": 0})) .val(rfb.off[i]); } } return; } //endRemoveIf(!rfbridge) // --------------------------------------------------------------------- // RFM69 // --------------------------------------------------------------------- //removeIf(!rfm69) if (key == "packet") { var packet = data.packet; var d = new Date(); packets.row.add([ d.toLocaleTimeString('en-US', { hour12: false }), packet.senderID, packet.packetID, packet.targetID, packet.key, packet.value, packet.rssi, packet.duplicates, packet.missing, ]).draw(false); return; } if (key == "mapping") { for (var i in data.mapping) { // add a new row addMapping(); // get group var line = $("#mapping .pure-g")[i]; // fill in the blanks var mapping = data.mapping[i]; Object.keys(mapping).forEach(function(key) { var id = "input[name=" + key + "]"; if ($(id, line).length) $(id, line).val(mapping[key]); }); setOriginalsFromValues($("input", line)); } return; } //endRemoveIf(!rfm69) // --------------------------------------------------------------------- // RPN Rules // --------------------------------------------------------------------- if (key == "rpnRules") { for (var i in data.rpnRules) { // add a new row addRPNRule(); // get group var line = $("#rpnRules .pure-g")[i]; // fill in the blanks var rule = data.rpnRules[i]; $("input", line).val(rule); setOriginalsFromValues($("input", line)); } return; } if (key == "rpnTopics") { for (var i in data.rpnTopics) { // add a new row addRPNTopic(); // get group var line = $("#rpnTopics .pure-g")[i]; // fill in the blanks var topic = data.rpnTopics[i]; var name = data.rpnNames[i]; $("input[name='rpnTopic']", line).val(topic); $("input[name='rpnName']", line).val(name); setOriginalsFromValues($("input", line)); } return; } if (key == "rpnNames") return; // --------------------------------------------------------------------- // Curtains // --------------------------------------------------------------------- //removeIf(!curtain) function applyCurtain(a, b) { $("#curtainGetPicture").css('background', 'linear-gradient(' + a + ', black ' + b + '%, #a0d6ff ' + b + '%)'); } if ("curtainState" === key) { initCurtain(); switch(value.type) { case '0': //Roller default: applyCurtain('180deg', value.get); break; case '1': //One side left to right applyCurtain('90deg', value.get); break; case '2': //One side right to left applyCurtain('270deg', value.get); break; case '3': //Two sides $("#curtainGetPicture").css('background', 'linear-gradient(90deg, black ' + value.get/2 + '%, #a0d6ff ' + value.get/2 + '% ' + (100 - value.get/2) + '%, black ' + (100 - value.get/2) + '%)'); break; } $("#curtainSet").val(value.set); if(!value.moving) { $("button.curtain-button").css('background', 'rgb(66, 184, 221)'); } else { if(!value.button) $("button.button-curtain-pause").css('background', 'rgb(192, 0, 0)'); else if(value.button == 1) { $("button.button-curtain-close").css('background', 'rgb(66, 184, 221)'); $("button.button-curtain-open").css('background', 'rgb(192, 0, 0)'); } else if(value.button == 2) { $("button.button-curtain-open").css('background', 'rgb(66, 184, 221)'); $("button.button-curtain-close").css('background', 'rgb(192, 0, 0)'); } } return; } //endRemoveIf(!curtain) // --------------------------------------------------------------------- // Lights // --------------------------------------------------------------------- //removeIf(!light) if ("lightstate" === key) { $("#color").toggle(value); return; } if (("rgb" === key) || ("hsv" === key)) { updateColor(key, value); return; } if ("brightness" === key) { $("#brightness").val(value); $("span.brightness").html(value); return; } if ("channels" === key) { var len = value.length; initChannels(len); for (i in value) { var ch = value[i]; $("input.slider[data=" + i + "]").val(ch); $("span.slider[data=" + i + "]").html(ch); } return; } if ("mireds" === key) { $("#mireds").attr("min", value["cold"]); $("#mireds").attr("max", value["warm"]); $("#mireds").val(value["value"]); $("span.mireds").html(value["value"]); return; } if ("useWhite" === key) { useWhite = value; } if ("useCCT" === key) { initCCT(); useCCT = value; } //endRemoveIf(!light) // --------------------------------------------------------------------- // Sensors & Magnitudes // --------------------------------------------------------------------- //removeIf(!sensor) if ("snsErrors" === key) { for (var index in value) { var type = value[index][0]; var name = value[index][1]; MagnitudeErrors[type] = name; } return; } if ("snsMagnitudes" === key) { for (var index in value) { var type = value[index][0]; var prefix = value[index][1]; var name = value[index][2]; MagnitudeNames[type] = name; MagnitudeTypePrefixes[type] = prefix; MagnitudePrefixTypes[prefix] = type; moduleVisible(prefix); } return; } if ("magnitudesConfig" === key) { initMagnitudes(value); return; } if ("magnitudes" === key) { for (var i=0; i { let cfg = fromSchema(entries, schema); addSchedule(cfg, value); }); return; } // --------------------------------------------------------------------- // Relays // --------------------------------------------------------------------- if ("relayConfig" === key) { if ($("#relays > div").length) { return; } if ($("#relayConfig > legend").length) { return; } let schema = value.schema; value["cfg"].forEach((entries, id) => { let cfg = fromSchema(entries, schema); var name = cfg["relayName"]; if (!cfg.relayName.length) { cfg.relayName = "Switch #" + id; } initRelay(id, cfg, value); initRelayConfig(id, cfg, value); }); return; } if ("relayState" === key) { updateRelays(value); return; } // --------------------------------------------------------------------- // Curtain(s) // --------------------------------------------------------------------- //removeIf(!curtain) // Relay configuration if ("curtainConfig" === key) { initCurtainConfig(value); return; } //endRemoveIf(!curtain) // --------------------------------------------------------------------- // LEDs // --------------------------------------------------------------------- if ("ledConfig" === key) { if ($("#ledConfig > div").length > 0) { return; } let schema = value["schema"]; value["leds"].forEach((entries, id) => { addLed(id, fromSchema(entries, schema), value); }); return; } // --------------------------------------------------------------------- // Domoticz // --------------------------------------------------------------------- // Domoticz - Relays if ("dczRelays" === key) { createRelayList(value, "dczRelays", "dczRelayTemplate"); return; } // Domoticz - Magnitudes //removeIf(!sensor) if ("dczMagnitudes" === key) { createMagnitudeList(value, "dczMagnitudes", "dczMagnitudeTemplate"); return; } //endRemoveIf(!sensor) // --------------------------------------------------------------------- // Thingspeak // --------------------------------------------------------------------- // Thingspeak - Relays if ("tspkRelays" === key) { createRelayList(value, "tspkRelays", "tspkRelayTemplate"); return; } // Thingspeak - Magnitudes //removeIf(!sensor) if ("tspkMagnitudes" === key) { createMagnitudeList(value, "tspkMagnitudes", "tspkMagnitudeTemplate"); return; } //endRemoveIf(!sensor) // --------------------------------------------------------------------- // HTTP API // --------------------------------------------------------------------- // Auto generate an APIKey if none defined yet if ("apiVisible" === key) { if (data.apiKey === undefined || data.apiKey === "") { generateAPIKey(); } } // --------------------------------------------------------------------- // General // --------------------------------------------------------------------- if ("saved" === key) { configurationSaved = value; return; } if ("message" === key) { window.alert(value); return; } // Web log if ("weblog" === key) { send("{}"); var msg = value["msg"]; var pre = value["pre"]; for (var i=0; i < msg.length; ++i) { if (pre[i]) { $("#weblog").append(new Text(pre[i])); } $("#weblog").append(new Text(msg[i])); } followScroll("weblog"); return; } // Enable options var position = key.indexOf("Visible"); if (position > 0 && position === key.length - 7) { var module = key.slice(0,-7); moduleVisible(module); return; } if ("deviceip" === key) { var a_href = $("span[name='" + key + "']").parent(); a_href.attr("href", "//" + value); a_href.next().attr("href", "telnet://" + value); } if ("now" === key) { now = parseInt(value, 10); return; } if ("free_size" === key) { free_size = parseInt(value, 10); } // Pre-process if ("mqttStatus" === key) { value = value ? "CONNECTED" : "NOT CONNECTED"; } if ("ntpStatus" === key) { value = value ? "SYNC'D" : "NOT SYNC'D"; } if ("uptime" === key) { ago = 0; var uptime = parseInt(value, 10); var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10); var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10); var hours = uptime % 24; uptime = parseInt(uptime / 24, 10); var days = uptime; value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s"; } //removeIf(!thermostat) if ("tmpUnits" == key) { $("span.tmpUnit").html(data[key] == 3 ? "ºF" : "ºC"); } //endRemoveIf(!thermostat) // --------------------------------------------------------------------- // Matching // --------------------------------------------------------------------- var elems = []; var pre; var post; // Look for INPUTs var input = $("input[name='" + key + "']"); if (input.length > 0) { if (input.attr("type") === "checkbox") { input.prop("checked", value); } else if (input.attr("type") === "radio") { input.val([value]); } else { pre = input.attr("pre") || ""; post = input.attr("post") || ""; input.val(pre + value + post); } elems.push(input); } // Look for SPANs var span = $("span[name='" + key + "']"); if (span.length > 0) { if (Array.isArray(value)) { value.forEach(function(elem) { span.append(elem); span.append('
'); elems.push(span); }); } else { pre = span.attr("pre") || ""; post = span.attr("post") || ""; span.html(pre + value + post); elems.push(span); } } // Look for SELECTs var select = $("select[name='" + key + "']"); if (select.length > 0) { if (select.attr("multiple") !== undefined) { select.val(bitsetToValues(value)); } else { select.val(value); } elems.push(select); } setOriginalsFromValues($(elems)); }); } function hasChanged() { var newValue, originalValue; if ($(this).attr("type") === "checkbox") { newValue = $(this).prop("checked"); originalValue = ($(this).attr("original") === "true"); } else { newValue = $(this).val(); originalValue = $(this).attr("original"); } if ($(this).attr("multiple") !== undefined) { newValue = newValue.join(","); } var hasChanged = ("true" === $(this).attr("hasChanged")); var action = $(this).attr("action"); if (typeof originalValue === "undefined") { return; } if ("none" === action) { return; } if (newValue !== originalValue) { if (!hasChanged) { ++numChanged; if ("reconnect" === action) { ++numReconnect; } if ("reboot" === action) { ++numReboot; } if ("reload" === action) { ++numReload; } } $(this).attr("hasChanged", true); } else { if (hasChanged) { --numChanged; if ("reconnect" === action) { --numReconnect; } if ("reboot" === action) { --numReboot; } if ("reload" === action) { --numReload; } } $(this).attr("hasChanged", false); } } // ----------------------------------------------------------------------------- // Init & connect // ----------------------------------------------------------------------------- function initUrls(root) { var paths = ["ws", "upgrade", "config", "auth"]; urls["root"] = root; paths.forEach(function(path) { urls[path] = new URL(path, root); urls[path].protocol = root.protocol; }); if (root.protocol == "https:") { urls.ws.protocol = "wss:"; } else { urls.ws.protocol = "ws:"; } } function connectToURL(url) { initUrls(url); fetch(urls.auth.href, { 'method': 'GET', 'cors': true, 'credentials': 'same-origin' }).then(function(response) { // Nothing to do, reload page and retry if (response.status != 200) { doReload(5000); return; } // update websock object if (websock) { websock.close(); } websock = new WebSocket(urls.ws.href); websock.onmessage = function(evt) { var data = {}; try { data = JSON.parse(evt.data .replace(/\n/g, "\\n") .replace(/\r/g, "\\r") .replace(/\t/g, "\\t")); } catch (e) { console.log(e); } processData(data); }; websock.onclose = function(evt) { clearInterval(ws_pingpong); if (window.confirm("Connection lost with the device, click OK to refresh the page")) { $("#layout").toggle(false); window.location.reload(); } } websock.onopen = function(evt) { ws_pingpong = setInterval(function() { sendAction("ping", {}); }, 5000); } }).catch(function(error) { console.log(error); doReload(5000); }); } function connect(host) { if (!host.startsWith("http:") && !host.startsWith("https:")) { host = "http://" + host; } connectToURL(new URL(host)); } function connectToCurrentURL() { connectToURL(new URL(window.location)); } $(function() { // most of the time, we want this unconditionally for all $("a.external") .attr("target", "_blank") .attr("rel", "noopener") .attr("tabindex", "-1"); createCheckboxes(); setInterval(function() { keepTime(); }, 1000); $(".password-reveal").on("click", toggleVisiblePassword); $("#menuLink").on("click", toggleMenu); $(".pure-menu-link").on("click", showPanel); $("progress").attr({ value: 0, max: 100 }); $("#button-wifi-scan").on("click", doScan); $(".button-update").on("click", doUpdate); $(".button-update-password").on("click", doUpdatePassword); $(".button-generate-password").on("click", doGeneratePassword); $(".button-reboot").on("click", doReboot); $(".button-reconnect").on("click", doReconnect); $(".button-dbgcmd").on("click", doDebugCommand); $("input[name='dbgcmd']").enterKey(doDebugCommand); $(".button-dbg-clear").on("click", doDebugClear); $(".button-settings-backup").on("click", doBackup); $(".button-settings-restore").on("click", doRestore); $(".button-settings-factory").on("click", doFactoryReset); $("#uploader").on("change", onFileUpload); $(".button-upgrade").on("click", doUpgrade); //removeIf(!garland) $(".checkbox-garland-enable").on("change", function() { sendAction("garland_switch", {status: $(this).prop("checked") ? 1 : 0}); }); $(".slider-garland-brightness").on("change", function() { sendAction("garland_set_brightness", {brightness: $(this)[0].value}); }); $(".slider-garland-speed").on("change", function() { sendAction("garland_set_speed", {speed: $(this)[0].value}); }); $(".button-garland-set-default").on("click", function() { sendAction("garland_set_default", {}); }); //endRemoveIf(!garland) //removeIf(!thermostat) $(".button-thermostat-reset-counters").on('click', doResetThermostatCounters); //endRemoveIf(!thermostat) $(".button-apikey").on("click", generateAPIKey); $(".button-upgrade-browse").on("click", function() { $("input[name='upgrade']")[0].click(); return false; }); $("input[name='upgrade']").change(function (){ var file = this.files[0]; $("input[name='filename']").val(file.name); }); $(".button-add-network").on("click", function() { $(".more", addNetwork()).toggle(); }); $(".button-add-switch-schedule").on("click", function() { addSchedule({schType: 1, schTarget: -1}); }); //removeIf(!light) $(".button-add-light-schedule").on("click", function() { addSchedule({schType: 2, schTarget: -1}); }); //endRemoveIf(!light) //removeIf(!curtain) $(".button-add-curtain-schedule").on("click", function() { addSchedule({schType: 3, schTarget: -1}); }); //endRemoveIf(!curtain) $(".button-add-rpnrule").on('click', addRPNRule); $(".button-add-rpntopic").on('click', addRPNTopic); $(".button-del-parent").on('click', delParent); //removeIf(!lightfox) $(".button-lightfox-learn") .off("click") .click(lightfoxLearn); $(".button-lightfox-clear") .off("click") .click(lightfoxClear); //endRemoveIf(!lightfox) //removeIf(!rfm69) $(".button-add-mapping").on('click', addMapping); $(".button-clear-counts").on('click', doClearCounts); $(".button-clear-messages").on('click', doClearMessages); $(".button-clear-filters").on('click', doClearFilters); $('#packets tbody').on('mousedown', 'td', doFilter); packets = $('#packets').DataTable({ "paging": false }); for (var i = 0; i < packets.columns()[0].length; i++) { filters[i] = false; } //endRemoveIf(!rfm69) $(".gpio-select").each(function(_, elem) { initSelectGPIO(elem) }); $(document).on("change", "input", hasChanged); $(document).on("change", "select", hasChanged); $("textarea").on("dblclick", function() { this.select(); }); resetOriginals(); $(".settings-group").each(function() { groupSettingsObserver.observe(this, {childList: true}); }); // don't autoconnect when opening from filesystem if (window.location.protocol === "file:") { processData({"webMode": 0}); return; } // Check host param in query string var search = new URLSearchParams(window.location.search), host = search.get("host"); if (host !== null) { connect(host); } else { connectToCurrentURL(); } });