var debug = false;
var websock;
var password = false;
var maxNetworks;
var messages = [];
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)
// -----------------------------------------------------------------------------
// Messages
// -----------------------------------------------------------------------------
function initMessages() {
messages[1] = "Remote update started";
messages[2] = "OTA update started";
messages[3] = "Error parsing data!";
messages[4] = "The file does not look like a valid configuration backup or is corrupted";
messages[5] = "Changes saved. You should reboot your board now";
messages[7] = "Passwords do not match!";
messages[8] = "Changes saved";
messages[9] = "No changes detected";
messages[10] = "Session expired, please reload page...";
}
// -----------------------------------------------------------------------------
// 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!";
}
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='adminPass1'],input[name='adminPass2']", 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;
}
function getValue(element) {
if ($(element).attr("type") === "checkbox") {
return $(element).prop("checked") ? 1 : 0;
} else if ($(element).attr("type") === "radio") {
if (!$(element).prop("checked")) {
return null;
}
}
return $(element).val();
}
function addValue(data, name, value) {
if (name in data) {
if (!Array.isArray(data[name])) {
data[name] = [data[name]];
}
data[name].push(value);
} else if (isGroupValue(name)) {
data[name] = [value];
} else {
data[name] = value;
}
}
function getData(form, changed, cleanup) {
// Populate two sets of data, ones that had been changed and ones that stayed the same
var data = {};
var changed_data = [];
if (cleanup === undefined) {
cleanup = true;
}
if (changed === undefined) {
changed = true;
}
$("input,select", form).each(function() {
if ($(this).attr("data-settings-ignore") === "true") {
return;
}
var name = $(this).attr("name");
var real_name = $(this).attr("data-settings-real-name");
if (real_name !== undefined) {
name = real_name;
}
var value = getValue(this);
if (null !== value) {
var haschanged = ("true" === $(this).attr("hasChanged"));
var indexed = changed_data.indexOf(name) >= 0;
if ((haschanged || !changed) && !indexed) {
changed_data.push(name);
}
addValue(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) {
$(".group-settings").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 getJson(str) {
try {
return JSON.parse(str);
} catch (e) {
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 template = $("#brightnessTemplate").children();
var line = $(template).clone();
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 template = $("#rfbNodeTemplate").children();
var line = $(template).clone();
$("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", {});
}
function initLightfox(data, relayCount) {
var numNodes = data.length;
var template = $("#lightfoxNodeTemplate").children();
var i, j;
for (i=0; i span").text(data[i]["id"]);
$line.find("select").each(function() {
$(this).attr("name", "btnRelay" + data[i]["id"]);
for (j=0; j < relayCount; j++) {
$(this).append($("
").attr("value", j).text("Switch #" + j));
}
$(this).val(data[i]["relay"]);
status = !status;
});
setOriginalsFromValues($("input,select", $line));
$line.appendTo("#lightfoxNodes");
}
var $panel = $("#panel-lightfox")
$(".button-lightfox-learn").off("click").click(lightfoxLearn);
$(".button-lightfox-clear").off("click").click(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 (var i=0; i 0 && position === key.length - 10) {
var template = $("#magnitudeCorrectionTemplate > div")[0];
var elem = $(template).clone();
var prefix = key.slice(0, position);
$("label", elem).html(MagnitudeNames[MagnitudePrefixTypes[prefix]]);
$("input", elem).attr("name", key).val(value);
setOriginalsFromValues($("input", elem));
elem.appendTo("#magnitude-corrections");
moduleVisible("magnitude-corrections");
return;
}
}
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 div").length > 0) return;
var schema = value["schema"];
value["list"].forEach(function(led_data, index) {
var line = $($("#ledConfigTemplate").children()).clone();
$("span.id", line).html(index);
$("select", line).attr("data", index);
$("input", line).attr("data", index);
var led = fromSchema(led_data, schema);
$("select[name='ledGPIO']", line).val(led.GPIO);
// XXX: checkbox implementation depends on unique id
// $("input[name='ledInv']", line).val(led.Inv);
$("select[name='ledMode']", line).val(led.Mode);
$("input[name='ledRelay']", line).val(led.Relay);
setOriginalsFromValues($("input,select", line));
line.appendTo("#ledConfig");
});
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
// ---------------------------------------------------------------------
// Messages
if ("message" === key) {
if (value == 8) {
configurationSaved = true;
}
window.alert(messages[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 = value;
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) {
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");
}
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 = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
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() {
initMessages();
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-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-wifi-scan").on("click", doScan);
$(".button-ha-config").on("click", doHAConfig);
$(".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, schSwitch: -1});
});
//removeIf(!light)
$(".button-add-light-schedule").on("click", function() {
addSchedule({schType: 2, schSwitch: -1});
});
//endRemoveIf(!light)
//removeIf(!curtain)
$(".button-add-curtain-schedule").on("click", function() {
addSchedule({schType: 3, schSwitch: -1});
});
//endRemoveIf(!curtain)
$(".button-add-rpnrule").on('click', addRPNRule);
$(".button-add-rpntopic").on('click', addRPNTopic);
$(".button-del-parent").on('click', delParent);
//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();
$(".group-settings").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();
}
});