node-red-nd-zigbee2mqtt
Version:
Zigbee2mqtt connectivity nodes for node-red
468 lines (402 loc) • 24 kB
HTML
<style>
.node-input-devices-friendly_name, .node-input-groups-friendly_name {
width:100% ;
min-width: 200px ;
}
table.z2m_table {
border-spacing: 5px;
border-collapse: separate;
}
table.z2m_table th {
text-align: left;
}
table.z2m_table td {
vertical-align: top;
}
</style>
<script type='text/javascript'>
$.getScript('zigbee2mqtt/static/tokeninput/jquery.tokeninput.js');
</script>
<script type="text/x-red" data-template-name="zigbee2mqtt-bridge">
<link rel="stylesheet" href="zigbee2mqtt/static/css/multiple-select.css" type="text/css" />
<link rel="stylesheet" href="zigbee2mqtt/static/css/common.css" type="text/css" />
<link rel="stylesheet" href="zigbee2mqtt/static/tokeninput/token-input-facebook.css" type="text/css" />
<div class="form-row">
<label for="node-input-name" class="l-width"><i class="fa fa-bookmark"></i> <span data-i18n="label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]placeholder.name">
</div>
<div class="form-row">
<label for="node-input-server" class="l-width"><i class="fa fa-globe"></i> <span data-i18n="label.server"></span></label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="refreshBtn" class="l-width"><i class="fa fa-refresh"></i> <span data-i18n="label.refresh"></span></label>
<a class="red-ui-button s-width" id="refreshBtn"><span data-i18n="label.refresh_all"></span></a>
</div>
<div id="config_wr" style="display:none;">
<div class="form-row">
<!-- Tabsheets -->
<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-z2m-tabs"></ul>
</div>
<div id="node-z2m-tabs-content" style="min-height: 150px">
<!-- Content of all tabsheets -->
<div id="node-z2m-tab-bridge" class="node-z2m-tab-bridge" style="position: relative; margin-top: 30px;">
<div class="form-row">
<label for="permit_join_btn" class="l-width"><i class="fa fa-share-alt"></i> <span data-i18n="label.permit_join"></span></label>
<button style="width: 70%" class="red-ui-button permit_join_btn" id="permit_join_btn"><span data-i18n="label.permit_join_help"></span></button>
<button style="width: 70%" class="red-ui-button permit_join_btn" style="display:none;" id="permit_join_cancel_btn"><span data-i18n="label.permit_join_cancel_help"></span></button>
</div>
<div class="form-row">
<label for="log_level_info_btn" class="l-width"><i class="fa fa-commenting-o"></i> <span data-i18n="label.log_level"></span></label>
<button style="width: 17%" class="red-ui-button log_level_btn" data-level="info" id="log_level_info_btn"><span data-i18n="label.log_level_info"></span></button>
<button style="width: 17%" class="red-ui-button log_level_btn" data-level="debug" id="log_level_debug_btn"><span data-i18n="label.log_level_debug"></span></button>
<button style="width: 17%" class="red-ui-button log_level_btn" data-level="warn" id="log_level_warn_btn"><span data-i18n="label.log_level_warn"></span></button>
<button style="width: 17%" class="red-ui-button log_level_btn" data-level="error" id="log_level_error_btn"><span data-i18n="label.log_level_error"></span></button>
</div>
<div class="form-row">
<label for="config_version" class="l-width"><span data-i18n="label.version"></span></label>
<span id="config_version" style="font-weight:bold"> - </span>
</div>
</div>
<div id="node-z2m-tab-devices" class="node-z2m-tab-devices" style="position: relative; margin-top: 30px;">
<table class="z2m_table" id="node-z2m-devices_table">
<thead>
<th style="width:150px"><span data-i18n="devices.friendly_name"></span></th>
<th style="width:95%;"><span data-i18n="devices.device"></span></th>
<th style="width:50px;"></th>
<th style="width:50px;"></th>
</thead>
<tbody>
</tbody>
</table>
<div class="form-row form-row-auto-height">
<ol id="node-input-devices-container"></ol>
</div>
</div>
<div id="node-z2m-tab-groups" class="node-z2m-tab-groups" style="position: relative; margin-top: 30px;">
<a href="#" class="red-ui-item-add red-ui-button"><i class="fa fa-plus"></i> <span data-i18n="groups.add"></span></a>
<table class="z2m_table" id="node-z2m-groups_table">
<thead>
<th style="width:95%;"><span data-i18n="groups.friendly_name"></span></th>
<th style="width:50px;"></th>
<th style="width:50px;"></th>
</thead>
<tbody>
</tbody>
</table>
<div class="form-row form-row-auto-height">
<ol id="node-input-groups-container"></ol>
</div>
</div>
<div id="node-z2m-tab-map" class="node-z2m-tab-map" style="position: relative; margin-top: 30px;">
<select id="node-input-engine" style="width:100px;display:none;">
<option value="circo" selected>circo</option>
<option value="dot">dot</option>
<option value="fdp">fdp</option>
<option value="neato">neato</option>
<option value="osage">osage</option>
<option value="twopi">twopi</option>
</select>
<a href="#" id="z2m_map_refresh_btn" class="red-ui-item-add red-ui-button"><i class="fa fa-refresh"></i> <span data-i18n="map.refresh"></span></a>
<a href="#" id="z2m_map_fullscreen_btn" style="display:none;" class="red-ui-item-add red-ui-button"><i class="fa fa-external-link"></i> <span data-i18n="map.fullscreen"></span></a>
<div id="z2m_map_img"></div>
</div>
</div>
</div>
</script>
<script type='text/javascript'>
RED.nodes.registerType('zigbee2mqtt-bridge', {
category: 'Zigbee2mqtt',
color: '#FDBF48',
defaults: {
name: {
value: ""
},
server: {
type: "zigbee2mqtt-server",
required: true
},
topic: {
value: null,
required: false
}
},
inputs: 1,
outputs: 1,
outputLabels: ["value"],
paletteLabel: 'bridge',
icon: "icon.png",
label: function () {
var label = 'Zigbee2mqtt Bridge';
if (this.name) {
label = this.name;
}
return label;
},
oneditprepare: function () {
var node = this;
node.tabs = RED.tabs.create({
id: "node-z2m-tabs",
onchange: function (tab) {
$("#node-z2m-tabs-content").children().hide();
$("#" + tab.id).show();
}
});
node.tabs.addTab({
id: "node-z2m-tab-bridge",
label: RED._("node-red-nd-zigbee2mqtt/bridge:tabs.bridge")
});
node.tabs.addTab({
id: "node-z2m-tab-devices",
label: RED._("node-red-nd-zigbee2mqtt/bridge:tabs.devices")
});
node.tabs.addTab({
id: "node-z2m-tab-groups",
label: RED._("node-red-nd-zigbee2mqtt/bridge:tabs.groups")
});
node.tabs.addTab({
id: "node-z2m-tab-map",
label: RED._("node-red-nd-zigbee2mqtt/bridge:tabs.map")
});
$('#refreshBtn').on("click", function(){
init(true);
});
init(false);
function init (force = false) {
var $permitJoinBtn = $('#permit_join_btn');
//get config
$.getJSON('zigbee2mqtt/getConfig', {
controllerID: node.server,
permit_join: true
}).done(function (data, textStatus, jqXHR) {
$('#config_wr').show();
$('#config_version').text(data.version);
$('.log_level_btn').each(function () {
$(this).removeClass('active');
});
$('.log_level_btn#log_level_' + data.log_level + '_btn').addClass('active');
$('.permit_join_btn').each(function () {
$(this).hide();
});
if (data.permit_join) {
$('#permit_join_cancel_btn').show()
} else {
$permitJoinBtn.show();
}
$('.log_level_btn#log_level_' + data.log_level + '_btn').addClass('active');
});
//log level
$('.log_level_btn').off('click').on('click', function () {
$('.log_level_btn').each(function () {
$(this).removeClass('active');
});
$(this).addClass('active');
$.getJSON('zigbee2mqtt/setLogLevel', {
controllerID: node.server,
log_level: $(this).data('level')
}).done(function (data, textStatus, jqXHR) {
});
});
var timeout = null;
//permit join
$('#permit_join_cancel_btn').off('click').on('click', function () {
$permitJoinBtn.show();
$('#permit_join_cancel_btn').hide();
$.getJSON('zigbee2mqtt/setPermitJoin', {
controllerID: node.server,
permit_join: false
});
});
$permitJoinBtn.off('click').on('click', function () {
clearTimeout(timeout);
$permitJoinBtn.hide();
$('#permit_join_cancel_btn').show();
$.getJSON('zigbee2mqtt/setPermitJoin', {
controllerID: node.server,
permit_join: true
}).done(function (data, textStatus, jqXHR) {
timeout = setTimeout(function () {
$permitJoinBtn.show();
$('#permit_join_cancel_btn').hide();
$.getJSON('zigbee2mqtt/setPermitJoin', {
controllerID: node.server,
permit_join: false
});
}, 60000 * 3);
});
});
$('#z2m_map_refresh_btn').off('click').on('click', function () {
var $btn = $(this);
$('#z2m_map_img').html('');
$btn.find('.fa-refresh').addClass('fa-spin');
$btn.find('span').text(RED._("node-red-nd-zigbee2mqtt/bridge:map.loading"));
$('#z2m_map_fullscreen_btn').hide();
$.getJSON('zigbee2mqtt/refreshMap', {
controllerID: node.server,
engine: $('#node-input-engine').val()
}).done(function (data, textStatus, jqXHR) {
$btn.find('.fa-refresh').removeClass('fa-spin');
$btn.find('span').text(RED._("node-red-nd-zigbee2mqtt/bridge:map.refresh"));
$('#z2m_map_fullscreen_btn').show();
$('#z2m_map_img').html(data.svg).find('svg').attr('width','100%').attr('height','100%');
});
});
$('#z2m_map_fullscreen_btn').off('click').on('click', function () {
var url = '/zigbee2mqtt/showMap?controllerID='+node.server;
var win = window.open(url, '_blank');
if (win) {
//Browser has allowed it to be opened
win.focus();
} else {
//Browser has blocked it
alert('Please allow popups for this website');
}
});
$.getJSON('zigbee2mqtt/getDevices', {
controllerID: node.server,
forceRefresh: force
}).done(function (data, textStatus, jqXHR) {
var tokeninputArr = [];
var tokeninputArrPrePopulate = [];
var devices = data[0]; //devices
if (devices) {
$('#node-z2m-devices_table tbody tr').remove();
devices.forEach(function (device, index) {
if ("Coordinator" === device.type) return;
tokeninputArr.push({'id':device.ieee_address, 'name':device.friendly_name, 'model': device.model_id});
var input = '<input class="node-input-devices-friendly_name" type="text" value="' + device.friendly_name + '">';
var info = '<div class="device_wr"><span><b>' + device.manufacturer + ':' + device.model_id + (device.supported ? ' supported ' : ' not supported ') + '</b>' + device.type + '</span><div class="help-tips">' + (device.definition ? device.definition.description : '') + '</div></div>';
var removeBtn = '<a href="#" class="red-ui-item-remove red-ui-button" data-id="' + device.ieee_address + '"><i class="fa fa-remove"></i></a>'
var saveBtn = '<a href="#" class="red-ui-item-save red-ui-button" data-ieeeaddr="' + device.ieee_address + '"><i class="fa fa-check"></i> ' + RED._("node-red-nd-zigbee2mqtt/bridge:devices.set") + '</a>'
var configBtn = '<a href="#" class="red-ui-item-config red-ui-button" data-ieeeaddr="' + device.ieee_address + '"><i class="fa fa-check"></i> config</a>'
$('#node-z2m-devices_table tbody').append('<tr><td>' + input + '</td><td>' + info + '</td><td>' + configBtn + '</td><td>' + removeBtn + '</td><</tr>');
});
$('#node-z2m-devices_table .red-ui-item-save').on('click', function () {
var ieeeAddr = $(this).attr('data-ieeeaddr');
var newName = $(this).closest('tr').find('.node-input-devices-friendly_name').val();
$.getJSON('zigbee2mqtt/renameDevice', {
controllerID: node.server,
ieeeAddr: ieeeAddr,
newName: newName
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
});
$('#node-z2m-devices_table .red-ui-item-config').on('click', function () {
var ieeeAddr = $(this).attr('data-ieeeaddr');
$.getJSON('zigbee2mqtt/configDevice', {
controllerID: node.server,
ieeeAddr: ieeeAddr,
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
});
$('#node-z2m-devices_table .red-ui-item-remove').on('click', function () {
if (confirm(RED._("node-red-nd-zigbee2mqtt/bridge:devices.sure_remove"))) {
var id = $(this).attr('data-id');
$(this).closest('tr').remove();
$.getJSON('zigbee2mqtt/removeDevice', {
controllerID: node.server,
id: id
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
}
});
}
var groups = data[1]; //groups
if (groups) {
$('#node-z2m-groups_table tbody tr').remove();
groups.forEach(function (group, index) {
var input = '<input class="node-input-groups-friendly_name" type="text" value="' + group.friendly_name + '">';
// var info = '<div class="device_wr"><span><b>'+device.ieeeAddr+'</b>: '+device.modelID+'</span><div class="help-tips">'+device.description+'</div></div>';
var removeBtn = '<a href="#" class="red-ui-item-remove red-ui-button" data-id="' + group.ID + '"><i class="fa fa-remove"></i></a>'
var saveBtn = '<a href="#" class="red-ui-item-save red-ui-button" data-id="' + group.ID + '"><i class="fa fa-check"></i> ' + RED._("node-red-nd-zigbee2mqtt/bridge:groups.set") + '</a>'
$('#node-z2m-groups_table tbody').append('<tr class="group_row_' + group.ID + '"><td>' + input + '</td><td>' + saveBtn + '</td><td>' + removeBtn + '</td></tr><tr class="group_row_' + group.ID + '"><td colspan="4" style="padding-bottom:25px;"><input type="hidden" data-group_id="' + group.ID + '" id="tokeninput_'+group.ID+'" class="tokeninput"/></td></tr>');
tokeninputArrPrePopulate = [];
group.devices.forEach(function (key, value) {
tokeninputArrPrePopulate.push({"id":key, "name":key});
});
$("#tokeninput_"+group.ID).tokenInput(tokeninputArr, {
theme:'facebook',
prePopulate:tokeninputArrPrePopulate,
processPrePopulate:true,
hintText:RED._("node-red-nd-zigbee2mqtt/bridge:tokeninput.type_to_search"),
noResultsText:RED._("node-red-nd-zigbee2mqtt/bridge:tokeninput.no_results"),
searchingText:RED._("node-red-nd-zigbee2mqtt/bridge:tokeninput.searching"),
deleteText:'×',
animateDropdown:true,
resultsFormatter:function (item) {
if(item.id!=0){
return '<li>' + item.name + ' : '+item.model+'</li>'
}
},
onAdd:function (item) {
var groupId = this.attr('data-group_id');
var deviceId = item.id;
$.getJSON('zigbee2mqtt/addDeviceToGroup', {
controllerID: node.server,
groupId: groupId,
deviceId: deviceId,
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
},
onDelete:function (item) {
var groupId = this.attr('data-group_id');
var deviceId = item.id;
$.getJSON('zigbee2mqtt/removeDeviceFromGroup', {
controllerID: node.server,
groupId: groupId,
deviceId: deviceId,
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
},
});
});
$('#node-z2m-groups_table .red-ui-item-save').on('click', function () {
var id = $(this).attr('data-id');
var newName = $(this).closest('tr').find('.node-input-groups-friendly_name').val();
$.getJSON('zigbee2mqtt/renameGroup', {
controllerID: node.server,
id: id,
newName: newName
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
});
$('#node-z2m-groups_table .red-ui-item-remove').on('click', function () {
if (confirm(RED._("node-red-nd-zigbee2mqtt/bridge:groups.sure_remove"))) {
var id = $(this).attr('data-id');
$('.group_row_' + id).remove();
$.getJSON('zigbee2mqtt/removeGroup', {
controllerID: node.server,
id: id
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
});
}
});
$('#node-z2m-tab-groups .red-ui-item-add').on('click', function () {
var name = prompt(RED._("node-red-nd-zigbee2mqtt/bridge:groups.enter_group_name"), "");
if (name) {
$.getJSON('zigbee2mqtt/addGroup', {
controllerID: node.server,
name: name
}).done(function (data, textStatus, jqXHR) {
if ("error" in data) alert(data.description);
setTimeout(function(){
init();
}, 1000);
});
}
});
}
});
}
},
oneditsave: function () {
}
});
</script>