iobroker.nuki-extended
Version:
323 lines (270 loc) • 10.1 kB
HTML
<html lang="en">
<head>
<title>ioBroker.nuki-extended</title>
<script src="../../lib/js/jquery-3.2.1.min.js"></script>
<script src="../../lib/js/socket.io.js"></script>
<script src="conn.js"></script>
<script src="modernizr-custom.js"></script>
<script src="vertical-timeline.js"></script>
<script src="LOCK.js"></script>
<script src="ICONS.js"></script>
<script src="LANGUAGE.js"></script>
<script>
<!--
var lang = (navigator.languages != undefined ? navigator.languages[0] : navigator.language).substr(0, 2);
console.log('LANGUAGE: ' + lang);
var STATES = ['logs', 'hex', 'name', 'type'];
var logIDs = [], timeline = [];
var timer = 0;
var socketConnected;
var tl;
// error
setTimeout(function()
{
$('#noNukiWebApi').text('Please activate Nuki Web API to retrieve log entries!');
}, 5*1000);
// add entries
function addTimeline(entries)
{
// compare payload to existing entries
let added = false;
let entry;
for (let i = 0; i < entries.length; i++)
{
entry = entries[i];
if (logIDs.indexOf(entry.id) === -1)
{
added = true;
logIDs.push(entry.id);
//
entry.date = new Date(entry.date);
entry.dateFormatted = entry.date.toLocaleDateString(lang, {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'});
entry.timeFormatted = entry.date.toLocaleTimeString(lang, {hour: '2-digit', minute:'2-digit', second:'2-digit'});
entry.action = ACTIONS[entry.action];
entry.deviceType = TYPES[entry.deviceType];
// add to timeline
timeline.push(entry);
}
else
break;
}
if (added)
{
// sort
timeline.sort(function(a, b)
{
return b.date - a.date
});
// show timeline
showTimeline(timeline);
}
}
function showTimeline(entries)
{
console.log("Show timeline with " + timeline.length + " entries.");
//console.log(JSON.stringify(timeline));
$('.cd-timeline-block').not('.template').remove();
entries.forEach(function(entry) {addLog(entry)});
tl.hideBlocks();
$(window).trigger('scroll');
}
// add log entry
function addLog(entry)
{
var tpl = $('.template').clone().removeClass('template hidden').addClass('device device_' + entry.smartlockId);
tpl.find('.cd-name').html(L(lang, entry.action).replace(/%person%/gi, entry.name ? L(lang, 'by') + ' ' + entry.name.replace('Nuki Web ()', 'Nuki Web API') : '<em>' + L(lang, 'manually') + '</em>'));
tpl.find('.cd-icon').addClass('action-'+entry.action).css('background-image', 'url('+ICONS[entry.action]+')');
tpl.find('.cd-date').html(entry.dateFormatted + ', ' + entry.timeFormatted);
tpl.find('.cd-description').removeClass('hidden').find('.nuki-device').html('<img style="vertical-align: middle" src="'+ICONS['DEVICE']+'" />' + ' ' + entry.deviceType);
if (entry.trigger != 0)
tpl.find('.cd-description').removeClass('hidden').find('.nuki-trigger').html('| <img style="vertical-align: middle" src="'+ICONS['TRIGGER']+'" />' + ' ' + L(lang, TRIGGER[entry.trigger]));
if (entry.state != 0)
tpl.find('.cd-description').removeClass('hidden').find('.nuki-state').html('| <img style="vertical-align: middle" src="'+ICONS['STATES']+'" />' + ' ' + L(lang, 'not executed') + ' ' + L(lang, STATES[entry.state]));
if (entry.autoUnlock)
tpl.find('.cd-description').removeClass('hidden').find('.nuki-autoUnlock').html('| <img style="vertical-align: middle" src="'+ICONS['AUTO_UNLOCK']+'" />' + ' ' + L(lang, 'AUTO_UNLOCK'));
if (entry.openerLog && (entry.openerLog.activeCm || entry.openerLog.activeRto || entry.openerLog.flagGeoFence || entry.openerLog.flagForce || entry.openerLog.flagDoorbellSuppression))
{
tpl.find('.cd-description').removeClass('hidden').find('.nuki-log').html(
(entry.openerLog.activeCm ? '| CONTINUOUS MODE' : '') +
(entry.openerLog.activeRto ? '| RING TO OPEN' : '') +
(entry.openerLog.flagGeoFence ? '| GEO FENCE' : '') +
(entry.openerLog.flagForce ? '| FORCED' : '') +
(entry.openerLog.flagDoorbellSuppression ? '| DOORBELL SUPPRESSED' : '')
);
}
$('#cd-timeline .cd-timeline-block').last().after(tpl);
$('#noNukiWebApi').remove();
}
// add locks
function addLock(lock)
{
var list = $('#cd-timeline .locks');
// add lock
if (list.find('[data-node="' + lock.nuki + '"]').length == 0)
list.append('<li class="menu" data-node="' + lock.nuki + '"><a class="filter">%name%</a></li>');
// show selection only with 2 devices or more
if (list.find('li').length > 2)
{
list.css('width', $('#cd-timeline').css('width'));
list.removeClass('hidden');
}
// set data-id and data-name with its values
if (!$('[data-node="' + lock.nuki + '"]').attr('data-' + lock.key))
$('[data-node="' + lock.nuki + '"]').attr('data-' + lock.key, lock.value);
// replace %name% with acutal name
if ($('[data-node="' + lock.nuki + '"]').html().indexOf('%') > -1)
$('[data-node="' + lock.nuki + '"]').html($('[data-node="' + lock.nuki + '"]').html().replace(RegExp('%' + lock.key + '%', 'gi'), lock.value));
}
// updater
function updater(deviceType, instance)
{
console.log('Retrieving ' + deviceType + ' logs from instance ' + instance + '..');
servConn.getChildren('nuki-extended.' + instance + '.' + deviceType, function(err, _devices)
{
if (err !== null)
{
alert(err);
return;
}
_devices.forEach(function(device)
{
servConn.getStates(STATES.map(state => device + '.' + state), function(err, _states)
{
if (err !== null)
{
alert(err);
return;
}
if (_states !== null && _states !== undefined)
{
for (var key in _states)
{
var node = key.substr(key.lastIndexOf('.')+1);
if (STATES.indexOf(node) === -1)
continue;
var content = _states[key].val;
// add log entries
if (node == 'logs')
{
let entries = JSON.parse(content);
console.log('Found ' + entries.length + ' entries in ' + deviceType + ' log.');
addTimeline(entries);
}
// add other information
else
{
addLock({'nuki': device, 'key': node.substr(node.lastIndexOf('.')+1), 'value': content});
$('[data-state="' + node + '"]').html(content);
}
}
// updater
setTimeout(updater, 10*1000, deviceType, instance); // every 10s
}
});
});
});
}
// page loaded
$(window).on('load', function()
{
// attach listener to filter
$('body').on('click', '.menu .filter', function(e)
{
let id = $(this).parent().data('id') || parseInt($(this).parent().data('type') + $(this).parent().data('hex'), 16);
// remove current selection
$('.menu .filter').removeClass('active');
// add new selection
$(this).addClass('active');
if (id != "ALL")
{
$('.device').addClass('hidden');
$('.device_' + id).removeClass('hidden');
}
else
$('.device').removeClass('hidden');
tl.hideBlocks();
$(window).trigger('scroll');
});
//
// Vertical Timeline
tl = new Timeline();
tl.bindScroll();
//
// Connect Socket
servConn.init({
connLink: window.location.href.substr(0, window.location.href.indexOf('/', 8))
}, {
onConnChange: function(socketConnected)
{
console.log('Socket ' + (socketConnected ? 'connected' : 'disconnected') + '!');
// retrieve states
if (socketConnected)
{
// get all instances of the adapter
console.log('Retrieving all adapter instances..');
for (let instance = 0; instance < 9; instance++)
{
servConn.getObject('nuki-extended.' + instance + '.info.connection', function(err, _instance)
{
if (_instance !== null)
{
// get logs from devices
['smartlocks', 'openers'].forEach(function(deviceType)
{
timer = timer + 1;
setTimeout(updater, timer*1000, deviceType, instance);
});
}
});
}
}
}
});
});
// -->
</script>
<link rel="stylesheet" type="text/css" href="vertical-timeline.css"/>
<link rel="stylesheet" type="text/css" href="nuki.css"/>
<style type="text/css">
<!--
.action-UNLOCK {background-color: #B22222;}
.action-LOCK {background-color: #22b322;}
.action-UNLATCH {background-color: #b38e22;}
.action-LOCK_N_GO {background-color: #b222b3;}
.action-LOCK_N_GO_WITH_UNLATCH {background-color: #b222b3;}
.action-DOORBELL_RECOGNITION {background-color: #b222b3;}
.action-DOOR_OPENED {background-color: #b34622;}
.action-DOOR_CLOSED {background-color: #6ab322;}
.action-INITIALIZATION {background-color: #666;}
// -->
</style>
</head>
<body>
<section id="cd-timeline" class="cd-container">
<ul class="locks hidden">
<li data-id="ALL" class="menu"><a class="filter active">Show all</a></li>
</ul>
<p id="noNukiWebApi">Loading log entries..</p>
<div class="cd-timeline-block template hidden">
<div class="cd-timeline-img cd-icon"></div>
<div class="cd-timeline-content">
<h2 class="cd-name"></h2>
<p class="cd-description hidden">
<span class="nuki-device"></span>
<span class="nuki-trigger"></span>
<span class="nuki-state"></span>
<span class="nuki-autoUnlock"></span>
<span class="nuki-log"></span>
</p>
<span class="cd-date"></span>
</div>
</div>
</section>
<div class="copyright">
Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> and <a href="https://www.freepik.com/" title="Freepik">Freepik</a>
from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a>
</div>
</body>
</html>