iobroker.unifi
Version:
UniFi Adapter for ioBroker
436 lines (371 loc) • 17 kB
JavaScript
/**
* Chips
*/
function list2chips(selector, list, onChange) {
const data = [];
list.sort();
for (let i = 0; i < list.length; i++) {
if (list[i] && list[i].trim()) {
data.push({ tag: list[i].trim() });
}
}
$(selector).chips({
data: data,
placeholder: _('Add'),
secondaryPlaceholder: _('Add'),
onChipAdd: onChange,
onChipDelete: onChange
});
}
function chips2list(selector) {
const data = $(selector).chips('getData');
const list = [];
for (let i = 0; i < data.length; i++) {
list.push(data[i].tag);
}
list.sort();
return list;
}
/**
* the function loadSettings has to exist ...
* @param {*} settings
* @param {*} onChange
*/
function loadHelper(settings, onChange) {
// example: select elements with id=key and class=value and insert value
if (!settings) return;
// Update settings < v0.5.3
if (Object.prototype.hasOwnProperty.call(settings, 'blacklist')) {
settings['objectsFilter'] = settings['blacklist'];
}
if (Object.prototype.hasOwnProperty.call(settings, 'whitelist')) {
settings['statesFilter'] = settings['whitelist'];
}
// Update fields
$('.value').each(function () {
const $key = $(this);
const id = $key.attr('id');
if ($key.attr('type') === 'checkbox') {
// do not call onChange direct, because onChange could expect some arguments
$key.prop('checked', settings[id]).change(function () {
onChange();
});
} else {
// do not call onChange direct, because onChange could expect some arguments
$key.val(settings[id]).change(function () {
onChange();
}).keyup(function () {
onChange();
});
}
});
onChange(false);
M.updateTextFields(); // function Materialize.updateTextFields(); to reinitialize all the Materialize labels on the page if you are dynamically adding inputs.
}
/**
* Is called by the admin adapter when the settings page loads
* @param {*} settings
* @param {*} onChange
*/
async function load(settings, onChange) {
console.log('Loading settings');
loadHelper(settings, onChange);
await createChips(settings, onChange);
await createTreeViews(settings, onChange);
onChange(false);
console.log('Loading settings done');
}
/**
* Is called by the admin adapter when the user presses the save button
* @param {*} callback
*/
function save(callback) {
// example: select elements with class=value and build settings object
const obj = {};
$('.value').each(function () {
const $this = $(this);
const id = $this.attr('id');
if ($this.attr('type') === 'checkbox') {
obj[id] = $this.prop('checked');
} else {
let value = $this.val();
value = value.trim();
obj[id] = value;
}
});
// Process objectsFilter
obj.objectsFilter = {};
$('[id*=chips_]').each(function () {
const settingsName = $(this).attr('id').replace('chips_', '');
obj.objectsFilter[settingsName] = chips2list(`#chips_${settingsName}`);
});
//Process statesFilter
obj.statesFilter = {};
$('[id*=tree_]').each(function () {
// store selected nodes of tree
const settingsName = $(this).attr('id').replace('tree_', '');
const selected = $.ui.fancytree.getTree(`#tree_${settingsName}`).getSelectedNodes();
const selectedIds = $.map(selected, function (node) {
return node.data.id;
});
const retVal = [];
for (const id of selectedIds) {
const dummy = id.split('.');
let dummy2 = [];
for (let i = 0; i < dummy.length; i++) {
if (i === 0) {
dummy2 = [];
}
dummy2.push(dummy[i]);
if (!retVal.includes(dummy2.join('.'))) {
retVal.push(dummy2.join('.'));
}
}
}
obj.statesFilter[settingsName] = retVal;
});
// Delete settings < v0.5.3
obj.blacklist = undefined;
obj.whitelist = undefined;
callback(obj);
}
//#region Functions
/**
* @param {*} settings
* @param {*} onChange
*/
async function createChips(settings, onChange) {
for (const key of Object.keys(settings.objectsFilter)) {
try {
list2chips(`#chips_${key}`, settings.objectsFilter[key], onChange);
M.updateTextFields(); // function Materialize.updateTextFields(); to reinitialize all the Materialize labels on the page if you are dynamically adding inputs.
} catch (err) {
console.error(`[createTreeViews] key: ${key} error: ${err.message}, stack: ${err.stack}`);
}
}
}
/**
* @param {*} settings
* @param {*} onChange
*/
async function createTreeViews(settings, onChange) {
for (const key of Object.keys(settings.statesFilter)) {
try {
// get json data from file
const obj = await getUnifiObjects(key);
// convert json to tree object
const tree = {
title: key,
key: key,
folder: true,
expanded: true,
children: []
};
await convertJsonToTreeObject(key, obj[key].logic.has, tree, settings);
$(`#tree_${key}`).fancytree({
activeVisible: true, // Make sure, active nodes are visible (expanded)
aria: true, // Enable WAI-ARIA support
autoActivate: true, // Automatically activate a node when it is focused using keyboard
autoCollapse: false, // Automatically collapse all siblings, when a node is expanded
autoScroll: false, // Automatically scroll nodes into visible area
clickFolderMode: 2, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
checkbox: true, // Show check boxes
checkboxAutoHide: false, // Display check boxes on hover only
debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
disabled: false, // Disable control
focusOnSelect: false, // Set focus when node is checked by a mouse click
escapeTitles: false, // Escape `node.title` content for display
generateIds: false, // Generate id attributes like <span id='fancytree-id-KEY'>
keyboard: true, // Support keyboard navigation
keyPathSeparator: '/', // Used by node.getKeyPath() and tree.loadKeyPath()
minExpandLevel: 1, // 1: root node is not collapsible
quicksearch: false, // Navigate to next node by typing the first letters
rtl: false, // Enable RTL (right-to-left) mode
selectMode: 3, // 1:single, 2:multi, 3:multi-hier
tabindex: '0', // Whole tree behaves as one single control
titlesTabbable: false, // Node titles can receive keyboard focus
tooltip: false, // Use title as tooltip (also a callback could be specified)
// icon: function (event, data) {
// if (data.node.isFolder()) {
// return "unifi.png";
// }
// },
click: function (event, data) {
if (data.targetType === 'title' && !data.node.folder) {
data.node.setSelected(!data.node.isSelected());
}
},
source: [
tree
],
select: function (event, data) {
if (data.node.key === 'clients.client.is_online') {
var isOnlineNode = $.map(data.tree.getSelectedNodes(), function (node) {
if (node.data.id === 'clients.client.is_online') {
return node;
}
});
var nodeLastSeenByUap = data.tree.getNodeByKey('clients.client.last_seen_by_uap');
var nodeLastSeenByUsw = data.tree.getNodeByKey('clients.client.last_seen_by_usw');
if (isOnlineNode && isOnlineNode.length === 1) {
// is_online is selected
nodeLastSeenByUap.setSelected(true);
nodeLastSeenByUap.unselectable = true;
nodeLastSeenByUsw.setSelected(true);
nodeLastSeenByUsw.unselectable = true;
} else {
nodeLastSeenByUap.unselectable = false;
nodeLastSeenByUsw.unselectable = false;
}
nodeLastSeenByUap.applyPatch(nodeLastSeenByUap);
nodeLastSeenByUsw.applyPatch(nodeLastSeenByUsw);
}
if (data.node.key === 'devices.device.port_table.port.port_poe_enabled') {
var portPoeEnabled = $.map(data.tree.getSelectedNodes(), function (node) {
if (node.data.id === 'devices.device.port_table.port.port_poe_enabled') {
return node;
}
});
let nodePortPoe = data.tree.getNodeByKey('devices.device.port_table.port.port_poe');
let treePortOverrides = data.tree.getNodeByKey('devices.device.port_overrides');
let treePortOverridesPort = data.tree.getNodeByKey('devices.device.port_overrides.port');
if (portPoeEnabled && portPoeEnabled.length === 1) {
// port_poe_enabled is selected
nodePortPoe.setSelected(true);
nodePortPoe.unselectable = true;
for (const child of treePortOverridesPort.children) {
child.setSelected(true);
child.unselectable = true;
child.applyPatch(child);
}
treePortOverridesPort.setSelected(true);
treePortOverridesPort.unselectable = true;
treePortOverrides.setSelected(true);
treePortOverrides.unselectable = true;
} else {
nodePortPoe.unselectable = false;
for (const child of treePortOverridesPort.children) {
child.unselectable = false;
child.applyPatch(child);
}
treePortOverridesPort.unselectable = false;
treePortOverrides.unselectable = false;
}
nodePortPoe.applyPatch(nodePortPoe);
}
if (data.node.key === 'devices.device.led_override') {
var ledOverrideEnabled = $.map(data.tree.getSelectedNodes(), function (node) {
if (node.data.id === 'devices.device.led_override') {
return node;
}
});
let nodeDeviceId = data.tree.getNodeByKey('devices.device.device_id');
if (ledOverrideEnabled && ledOverrideEnabled.length === 1) {
// port_poe_enabled is selected
nodeDeviceId.setSelected(true);
nodeDeviceId.unselectable = true;
} else {
nodeDeviceId.unselectable = false;
}
nodeDeviceId.applyPatch(nodeDeviceId);
}
// Funktion um alle title auszulesen, kann für Übersetzung verwendet werden -> bitte drin lassen!
// var selKeys = $.map(data.tree.getSelectedNodes(), function (node) {
// if (node.children === null) {
// return node.title;
// }
// });
// console.log(selKeys.join('\n').replace(/_/g, " "));
onChange();
}
});
M.updateTextFields(); // function Materialize.updateTextFields(); to reinitialize all the Materialize labels on the page if you are dynamically adding inputs.
} catch (err) {
console.error(`[createTreeViews] key: ${key} error: ${err.message}, stack: ${err.stack}`);
}
}
}
/**
* @param {*} name
* @param {*} obj
* @param {*} tree
* @param {*} settings
*/
async function convertJsonToTreeObject(name, obj, tree, settings) {
for (const [id, value] of Object.entries(obj)) {
try {
let idReadable = id.split('.');
idReadable = idReadable[idReadable.length - 1];
const title = value.common.name ? `${idReadable} | ${_(value.common.name)}` : `${idReadable}`;
if (value && value.type === 'state') {
if (settings.statesFilter[name] && settings.statesFilter[name].includes(id)) {
if ((id === 'clients.client.last_seen_by_uap' || id === 'clients.client.last_seen_by_usw') && settings.statesFilter[name].includes('clients.client.is_online')) {
tree.children.push({
title: title,
key: id,
id: id,
selected: true,
unselectable: true
});
} else if ((id === 'devices.device.port_table.port.port_poe' || id.includes('devices.device.port_overrides')) && settings.statesFilter[name].includes('devices.device.port_table.port.port_poe_enabled')) {
tree.children.push({
title: title,
key: id,
id: id,
selected: true,
unselectable: true
});
} else if (id === 'devices.device.device_id' && settings.statesFilter[name].includes('devices.device.led_override')) {
tree.children.push({
title: title,
key: id,
id: id,
selected: true,
unselectable: true
});
} else {
tree.children.push({
title: title,
key: id,
id: id,
selected: true
});
}
} else {
tree.children.push({
title: title,
key: id,
id: id
});
}
} else if (value && value.type === 'channel' || value.type === 'device') {
const subtree = {
title: title,
key: id,
folder: true,
expanded: true,
children: []
};
await convertJsonToTreeObject(name, value.logic.has, subtree, settings);
tree.children.push(subtree);
}
} catch (err) {
console.error(`[convertJsonToTreeObject] error: ${err.message}, stack: ${err.stack}`);
}
}
}
/**
* @param {*} lib
*/
async function getUnifiObjects(lib) {
return new Promise((resolve, reject) => {
$.getJSON(`./lib/objects_${lib}.json`, function (json) {
if (json) {
resolve(json);
} else {
resolve(null);
}
});
});
}
//#endregion