iobroker.lovelace
Version:
With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI
911 lines (828 loc) • 40.8 kB
HTML
<html>
<head>
<!-- Load ioBroker scripts and styles-->
<link rel="stylesheet" type="text/css" href="../../lib/css/fancytree/ui.fancytree.min.css" />
<link rel="stylesheet" type="text/css" href="../../css/adapter.css" />
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui.min.js"></script>
<script type="text/javascript" src="../../lib/js/jquery.fancytree-all.min.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<script type="text/javascript" src="../../lib/js/ace-1.2.0/ace.js"></script>
<script type="text/javascript" src="../../lib/js/ace-1.2.0/mode-yaml.js" charset="utf-8"></script>
<!-- Load our own files -->
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="js-yaml.min.js"></script>
<script type="text/javascript">
systemDictionary = lovelace_systemDictionary;
var parseYamlTimer;
var themes = [];
var editor = null;
var defaultTheme = '';
var defaultThemeDark = '';
var httpServer = {
port: 8091,
secure: false,
bind: '0.0.0.0'
};
function rebuildThemeSelects(_themes) {
var text = '<option value="default">' + _('default') + '</option>';
for (var i = 0; i < _themes.length; i++) {
text += '<option value="' + _themes[i] + '">' + _themes[i] + '</option>';
}
$('#defaultTheme').html(defaultTheme ? text.replace('>' + defaultTheme, 'selected>' + defaultTheme) : text).select().val(defaultTheme);
$('#defaultThemeDark').html(defaultThemeDark ? text.replace('>' + defaultThemeDark, 'selected>' + defaultThemeDark) : text).select().val(defaultThemeDark);
}
function parseYaml() {
parseYamlTimer && clearTimeout(parseYamlTimer);
parseYamlTimer = setTimeout(function () {
parseYamlTimer = null;
var yaml;
var $themes = $('#themes_fallback');
if (!editor) {
yaml = $themes.val();
} else {
yaml = editor.getValue();
}
var doc;
try {
doc = jsyaml.load(yaml);
} catch (e) {
!editor && $themes.addClass('error');
rebuildThemeSelects([]); //could not load themes, empty select boxes.
return;
}
!editor && $themes.removeClass('error');
var _themes = doc ? Object.keys(doc) : [];
if (!_themes.length || JSON.stringify(_themes) !== JSON.stringify(themes)) {
themes = _themes;
rebuildThemeSelects(themes);
}
});
}
function formatIds(obj) {
if (!obj) return '';
if (obj.getId && obj.setId && obj.getId !== obj.setId) {
return obj.getId + ' / ' + obj.setId;
} else if (obj.getId) {
return obj.getId;
} else if (obj.setId) {
return obj.setId;
} else {
return '';
}
}
function clippyCopy(e) {
var $input = $('<input>');
$(this).append($input);
$input.val($(this).text());
$input.trigger('select');
document.execCommand('copy');
$input.remove();
e.preventDefault();
e.stopPropagation();
showToast(null, _('Copied'));
}
function readEntities() {
var $entities = $('#entities');
sendTo(null, 'browse', null, function (list) {
var text = '';
list.sort(function (a, b) {
if (a.entity_id === b.entity_id) return 0;
if (a.entity_id > b.entity_id) return 1;
if (a.entity_id < b.entity_id) return -1;
});
var click2copy = _('Click to copy');
for (var i = 0; i < list.length; i++) {
text += '<tr>';
if (list[i].isManual) {
text += '<td><a class="btn-floating btn-small waves-effect waves-light open-custom" data-name="' + list[i].context.id + '"><i class="material-icons">build</i></a></td>';
} else {
text += '<td><a class="btn-floating btn-small waves-effect waves-light open-custom" data-name="' + list[i].context.id + '" disabled="true"><i class="material-icons">build</i></a></td>';
}
text += '<td class="entity-id" title="' + click2copy + '">' + list[i].entity_id + '</td>';
text += '<td>' + formatIds(list[i].context.STATE) + '</td><td class="attributes">';
if (list[i].attributes || list[i].context.ATTRIBUTES) {
var attrs = [];
var keysList = [];
if (list[i].context.ATTRIBUTES) {
list[i].context.ATTRIBUTES.forEach(function (attr) {
attrs.push('<b>' + attr.attribute + '</b>: ' + formatIds(attr));
keysList.push(attr.attribute);
});
}
if (list[i].attributes) {
for (var key in list[i].attributes) {
if (!keysList.includes(key)) {
attrs.push('<b>' + key + '</b>: ' + list[i].attributes[key]);
}
}
}
text += attrs.sort().join('<br>');
}
text += '</td></tr>';
}
$entities.html(text);
var invisible = $('.btn-attributes').data('invisible');
if (invisible === 'true') invisible = true;
if (invisible === 'false') invisible = false;
if (invisible) {
$('.attributes').hide();
} else {
$('.attributes').show();
}
$entities.find('.entity-id').on('click', clippyCopy);
$entities.find('.open-custom').off('click').on('click', function () {
let url = `${window.location.origin}/#tab-objects/customs/${$(this).data('name')}`;
window.open(url);
});
});
}
function formatTime(time) {
const date = new Date(time);
let result = 'unknown';
try {
result = date.toISOString();
} catch(e) {
//somehow does not work for folders?
console.log('Could not convert ' + time + ' to date object.');
}
return result;
}
function readFiles(subfolder) {
const path = subfolder ? subfolder + '/' : '/cards/';
var $dropZone = $('#tab-custom');
$dropZone.data('path', path);
socket.emit('readDir', 'lovelace.' + instance, path, function (err, list) {
var $files_list = $('#files_list');
var text = '<table>';
text += '<tr><th>' + _('File') + '</th><th>' + _('Size') + '</th><th>' + _('Modified') + '</th><th></th></tr>';
list.sort(function (a, b) {
if (a.file > b.file) return 1;
if (a.file < b.file) return -1;
return 0;
});
var server = httpServer.secure ? 'https://' : 'http://';
server += httpServer.bind === '0.0.0.0' ? window.location.hostname : httpServer.bind;
server += ':' + httpServer.port;
if (subfolder) {
text += `<tr><td><a href="#parent" id="parent">..</a></td><td> </td><td> </td><td></td></tr>`;
}
for (var i = 0; i < list.length; i++) {
const deleteButton = `<a class="btn-floating btn-small waves-effect waves-light red delete-file" data-name="${list[i].file}"><i class="material-icons">delete</i></a>`;
const fileLink = `<a href="${server}${path}${list[i].file}" target="_blank">${list[i].file}</a>`;
const folderButton = `<a href="#{list[i].file}" class="folder-button" data-folder="${list[i].isDir}" data-name="${path}${list[i].file}">${list[i].file}</a>`;
text += `<tr><td>${list[i].isDir ? folderButton : fileLink}</td>
<td>${list[i].isDir ? _('Folder') : list[i].stats.size}</td>
<td>${list[i].isDir ? ' ' : formatTime(list[i].modifiedAt)}</td>
<td>${deleteButton}</td>
</tr>`;
}
text += '</table>';
$files_list.html(text);
$files_list.find('.delete-file').off('click').on('click', function () {
console.log('Delete lovelace.' + instance + ' ' + path + $(this).data('name'));
let command = 'deleteFile';
if ($(this).data('folder') === "true") {
command = 'deleteFolder';
}
socket.emit(command, 'lovelace.' + instance, path + $(this).data('name'), function (err) {
if (err) {
showError(_('Cannot delete file/folder: ' + err));
} else {
setTimeout(() => readFiles(subfolder), 100);
}
});
});
$files_list.find('.folder-button').off('click').on('click', function () {
setTimeout(() => readFiles($(this).data('name')), 100);
});
if (subfolder) {
$('#parent').off('click').on('click', function (event) {
event.preventDefault();
const parts = subfolder.split('/');
parts.pop();
let newSubfolder = parts.join('/') || '/cards';
if (newSubfolder === '/cards' || newSubfolder === '/cards/') {
newSubfolder = undefined;
}
setTimeout(() => { readFiles(newSubfolder)}, 100);
});
}
const addFolderButton = $('#add-folder');
addFolderButton.off('click').on('click', function (event) {
event.preventDefault();
//ok user clicked ok button. Get Foldername.
const folderName = $('#foldername').val();
const newPath = path + folderName + (folderName.at(folderName.length -1) === '/' ? '' : '/');
socket.emit('mkdir', 'lovelace.' + instance, newPath, function (err) {
if (err) {
showError(_('Cannot create folder: ' + err));
} else {
setTimeout(() => readFiles(subfolder), 100);
}
});
});
});
}
function showHideSettings(id) {
var $secure = $('#secure');
var $auth = $('#auth');
if ($secure.prop('checked')) {
$('.col-certPublic').show();
$('.col-certPrivate').show();
$('.col-certChained').show();
$('.le-settings').removeClass('disabled');
if ($('#leEnabled').prop('checked')) {
$('.le-sub-settings').show();
if ($('#leUpdate').prop('checked')) {
$('.le-sub-settings-update').show();
} else {
$('.le-sub-settings-update').hide();
}
} else {
$('.le-sub-settings').hide();
}
} else {
$('.col-certPublic').hide();
$('.col-certPrivate').hide();
$('.col-certChained').hide();
$('.le-settings').addClass('disabled');
}
if ($auth.prop('checked')) {
$('.tab-login').removeClass('disabled');
$('#defaultUser').val('admin');
$('.col-defaultUser').hide();
$('.col-showUsersOnLoginScreen').show();
$('#showUsersOnLoginScreen').show();
$('.col-ttl').show();
if ((id === 'auth' || id === 'secure') && !$secure.prop('checked')) {
confirmMessage(_('Unsecure_Auth'), _('Warning!'), 'security', [_('Ignore warning'), _('Disable authentication')], function (result) {
if (result === 1) {
$('#auth').prop('checked', false).trigger('change');
showToast(null, _('Authentication was deactivated'));
}
});
}
} else {
$('.tab-login').addClass('disabled');
$('.col-defaultUser').show();
$('.col-showUsersOnLoginScreen').hide();
$('#showUsersOnLoginScreen').hide();
$('.col-ttl').hide();
}
if ($('#loginBackgroundImage').prop('checked')) {
$('.background').show();
} else {
$('.background').hide();
}
}
function getAllHistoryInstances(value) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, doc) {
if (!err && doc.rows.length) {
var $select = $('#history');
for (var i = 0; i < doc.rows.length; i++) {
if (doc.rows[i].value && doc.rows[i].value.common && doc.rows[i].value.common.getHistory) {
var id = doc.rows[i].id.replace(/^system\.adapter\./, '');
$select.append('<option value="' + id + '">' + id + '</option>');
}
}
}
$select.val(value || '');
$select.select();
});
}
function uploadFile(file, path, callback) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = function (e) {
//convert to base64 String here, because it fails for too big files in socket.io.js otherwise
var binary = '';
var bytes = new Uint8Array(e.target.result);
for (var i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes [i]);
}
socket.emit('writeFile', 'lovelace.' + instance, path + file.name, btoa(binary), function () {
callback && callback(file.name, path);
});
//if admin 6.2.16 is default or dependency, we can skip the whole block and do this instead:
//socket.emit('writeFile', 'lovelace.' + instance, '/cards/' + file.name, e.target.result, function () {
// callback && callback(file.name);
//});
};
// Read in the image file as a data URL.
reader.readAsArrayBuffer(file);
}
function fileHandler(event) {
event.preventDefault();
var files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
var $dz = $(this).find('.drop-zone');
var callback = $(this).data('drop-zone-cb');
var limit = $(this).data('limit');
var path = $(this).data('path');
var count = 0;
for (var f = 0; f < files.length; f++) {
var file = files[f];
if (file.size > (limit || 100000)) {
callback && callback(_('File is too big!'));
$dz.hide();
return false;
}
if (count === 0) {
$dz.show();
}
count++;
uploadFile(file, path, function (filename, path) {
if (!--count) {
$dz.hide();
callback && callback(null, path);
}
});
/*(function (_file) {
var reader = new FileReader();
reader.onload = function (evt) {
$dz.hide();
callback && callback(null, evt.target.result, _file.name);
};
reader.readAsDataURL(file);
})(file);*/
}
}
/**
* Install file upload on some div
* @param {object} $dropZone is jquery object of the div (DOM element) where the drop zone must be installed
* @param {number} limit is maximal size of file in bytes
* @param {function} callback is callback in form function (err, fileDataBase64) {}
*/
function installFileUpload($dropZone, limit, callback) {
if (typeof window.FileReader !== 'undefined' && !$dropZone.data('installed')) {
$dropZone.data('installed', true);
$dropZone.prepend('<div class="drop-zone" style="display: none"><input type="file" class="drop-file" style="display: none" /></div>');
var $dz = $dropZone.find('.drop-zone');
$dropZone[0].ondragover = function() {
$dz.off('click');
$dz.show();
return false;
};
$dz[0].ondragleave = function() {
$dz.hide();
return false;
};
$dz[0].ondrop = fileHandler.bind($dropZone[0]);
}
$dropZone.data('drop-zone-cb', callback);
$dropZone.data('limit', limit);
$dropZone.find('.drop-file').on('change', fileHandler.bind($dropZone[0]));
}
function load(settings, onChange) {
if (!settings) return;
if (!settings.lePort) settings.lePort = 80;
//initilize modals.
var elems = document.querySelectorAll('.modal');
M.Modal.init(elems);
//open modal by button:
const modal = $('.modal-trigger');
modal.modal();
getIPs(function (ips) {
for (var i = 0; i < ips.length; i++) {
$('#bind').append('<option value="' + ips[i].address + '">' + ips[i].name + '</option>');
}
$('#bind.value').select().val(settings.bind);
});
$('.value').each(function () {
var $key = $(this);
var 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])
.on('change', function () {
showHideSettings($(this).attr('id'));
onChange();
});
} else {
// do not call onChange direct, because onChange could expect some arguments
$key.val(settings[id])
.on('change', function () {
var id = $(this).attr('id');
showHideSettings(id);
if (id === 'defaultTheme') {
defaultTheme = $(this).val();
socket.emit('setState', 'lovelace.' + instance + '.control.theme', defaultTheme); //set state to new default
}
if (id === 'defaultThemeDark') {
defaultThemeDark = $(this).val();
socket.emit('setState', 'lovelace.' + instance + '.control.themeDark', defaultThemeDark); //set state to new default
}
onChange();
})
.on('keyup', function () {
onChange();
});
}
});
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
fillSelectCertificates('#certChained', 'chained', settings.certChained);
fillUsers('#defaultUser', settings.defaultUser);
showHideSettings();
getAllHistoryInstances(settings.history);
var $dropZone = $('#tab-custom');
installFileUpload($dropZone, 5242880, function (err, path) {
if (err) {
showError(_('Cannot upload file: ') + err);
} else {
readFiles(path.substring(0, path.length - 1));
}
});
onChange(false);
socket.emit('getObject', 'system.adapter.lovelace.' + instance, function (err, obj) {
if (obj && obj.common && obj.common.enabled) {
$('.btn-restart').on('click', function () {
socket.emit('getObject', 'system.adapter.lovelace.' + instance, function (err, obj) {
obj.common.enabled = false;
socket.emit('setObject', 'system.adapter.lovelace.' + instance, obj, function (err) {
obj.common.enabled = true;
socket.emit('setObject', 'system.adapter.lovelace.' + instance, obj, function (err) {
});
});
});
}).removeClass('disabled');
}
});
socket.emit('subscribe', 'lovelace.' + instance + '.info.entitiesUpdated', function (id, state) {
readEntities();
});
defaultTheme = settings.defaultTheme;
defaultThemeDark = settings.defaultThemeDark;
$('#themes_fallback').val(settings.themes);
$('.tab-themes').on('click', function () {
setTimeout(function () {
if (editor === null) {
try {
editor = ace.edit('themes');
var YamlMode = ace.require('ace/mode/yaml').Mode;
$('#themes').show();
editor.session.setMode(new YamlMode());
editor.setValue(settings.themes || ' ', -1);
$('#themes_fallback').hide();
editor.on('change', function () {
onChange();
parseYaml();
});
} catch (e) {
editor = false;
$('#themes').hide();
$('#themes_fallback').show().on('change', parseYaml).on('keyup', parseYaml).val(settings.themes);
}
parseYaml();
}
}, 100);
});
readFiles();
getIsAdapterAlive(function (isAlive) {
var $btnRefresh = $('.btn-refresh');
if (isAlive || common.enabled) {
$btnRefresh.removeClass('disabled');
readEntities();
$btnRefresh.on('click', function () {
readEntities();
});
} else {
$btnRefresh.addClass('disabled');
}
});
$('.btn-attributes').on('click', function () {
var invisible = $(this).data('invisible');
if (invisible === 'true') invisible = true;
if (invisible === 'false') invisible = false;
invisible = !invisible;
if (invisible) {
$('.attributes').hide();
$(this).find('.translate').text(_('Show attributes'));
} else {
$('.attributes').show();
$(this).find('.translate').text(_('Hide attributes'));
}
$(this).data('invisible', invisible);
});
httpServer.port = settings.port;
httpServer.secure = settings.secure;
httpServer.bind = settings.bind;
// reinitialize all the Materialize labels on the page if you are dynamically adding inputs:
if (M) M.updateTextFields();
}
// This will be called by the admin adapter when the user presses the save button
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if (!editor) {
obj.themes = $('#themes_fallback').val() || obj.themes;
} else {
obj.themes = editor.getValue() || obj.themes;
}
if ($('#secure').prop('checked') && (!$('#certPrivate').val() || !$('#certPublic').val())) {
showMessage(_('Set certificates or load it first in the system settings (right top).'));
return;
}
if (httpServer.port != obj.port || httpServer.secure !== obj.secure || httpServer.bind !== obj.bind) {
httpServer.port = obj.port;
httpServer.secure = obj.secure;
httpServer.bind = obj.bind;
readFiles();
}
callback(obj);
}
</script>
<style>
.drop-zone {
box-sizing: border-box;
width: 100%;
height: 100%;
position: absolute;
opacity: 0.8;
top: 0;
left: 0;
background: #eee;
border: 5px dashed #ccc;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
z-index: 1;
font-size: 32px;
font-weight: bold;
text-align: center;
}
.dropZone-error {
background: #faa ;
color: #f00;
}
.notice {
float: right;
margin-top: 15px;
}
.btn-restart {
margin-top: 5px;
}
#themes_fallback {
width: 100%;
resize: none;
height: calc(100vh - 300px);
font-family: monospace;
}
#themes {
width: 100%;
height: calc(100vh - 300px);
}
.row-narrow {
margin-bottom: 0 ;
}
.error {
border: 1px solid red;
}
.entity-id {
font-weight: bold;
vertical-align: top;
font-size: 14px;
cursor: pointer;
}
.div-entities {
width: 100%;
height: calc(100% - 50px);
overflow: auto;
}
.div-entities thead {
background: gray;
color: white;
}
.div-entities tbody {
font-size: 14px;
}
.div-entities tbody .attributes{
font-size: 10px;
}
.div-entities tbody td {
padding: 3px 5px;
}
</style>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div class="m adapter-container">
<div class="row">
<div class="col s12 m12 l12">
<ul class="tabs">
<li class="tab col s2 m2 l2"><a href="#tab-main" class="translate active">Main settings</a></li>
<li class="tab col s2 m2 l2 le-settings"><a href="#tab-le" class="translate">Let's Encrypt SSL</a></li>
<li class="tab col s2 m2 l2"><a href="#tab-custom" class="translate">Custom Cards</a></li>
<li class="tab col s2 m2 l2 tab-themes"><a href="#tab-themes" class="translate">Themes</a></li>
<li class="tab col s2 m2 l2 tab-entities"><a href="#tab-entities" class="translate">Entities</a></li>
</ul>
</div>
<div id="tab-main" class="col s12 m12 l12 page">
<div class="row">
<div class="col s6 m4 l2">
<img src="lovelace.png" class="logo">
</div>
</div>
<div class="row">
<div class="input-field col s12 m8 l5">
<select class="value" id="bind"></select>
<label class="translate" for="bind">IP</label>
</div>
<div class="input-field col s12 m4 l3">
<input class="value" id="port" size="5" maxlength="5" type="number"/>
<label class="translate" for="port">Port</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l2">
<input class="value" id="secure" type="checkbox" />
<label class="translate" for="secure">Secure(HTTPS)</label>
</div>
<div class="input-field col s12 m6 l2 col-certPublic">
<select id="certPublic" class="value"></select>
<label class="translate" for="certPublic">Public certificate</label>
</div>
<div class="input-field col s12 m6 l2 col-certPrivate">
<select id="certPrivate" class="value"></select>
<label class="translate" for="certPrivate">Private certificate</label>
</div>
<div class="input-field col s12 m6 l2 col-certChained">
<select id="certChained" class="value"></select>
<label class="translate" for="certChained">Chained certificate</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l2">
<select class="value" id="history" >
<option value="" class="translate">disabled</option>
</select>
<label class="translate" for="history">Historical instance</label>
</div>
<div class="input-field col s12 m6 l2">
<input class="value" type="number" id="historyMaxCount" />
<label class="translate" for="historyMaxCount">Maximum history points to fetch</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l2">
<input class="value" id="auth" type="checkbox" />
<label class="translate" for="auth">Authentication</label>
</div>
<div class="input-field col s12 m6 l2 col-defaultUser">
<select class="value" id="defaultUser"></select>
<label class="translate" for="defaultUser">Run as</label>
</div>
<div class="input-field col s12 m6 l2 col-ttl">
<input class="value" type="number" id="ttl" />
<label class="translate" for="ttl">Login timeout(sec)</label>
</div>
<div class="input-field col s12 m6 l2 col-showUsersOnLoginScreen">
<input class="value" id="showUsersOnLoginScreen" type="checkbox" />
<label class="translate" for="showUsersOnLoginScreen">showUsersOnLoginScreen</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l2 col-language">
<select class="value" id="language">
<option class="translate" value>Same as ioBroker</option>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="ru">русский</option>
<option value="pt">Portugues</option>
<option value="nl">Nederlands</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="es">Espanol</option>
<option value="pl">Polski</option>
<option value="zh-cn">简体中文</option>
</select>
<label class="translate" for="language">Language</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m9 l6">
<input class="value" id="aliasOnly" type="checkbox" />
<label class="translate" for="aliasOnly">AliasOnly</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m12 l10 long-text">
<input class="value" id="blindsInvert" type="checkbox" />
<label class="translate" for="blindsInvert">blindsInvert</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m8 l4">
<input class="value" id="alarmCheckCodeOnDisarmOnly" type="checkbox" />
<label class="translate" for="alarmCheckCodeOnDisarmOnly">alarmCheckCodeOnDisarmOnly</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m8 l4">
<select class="value" id="logbookSource">
<option class="translate" value="adapter">Adapter</option>
<option class="translate" value="user">User (works only for changes during adapter runtime)</option>
<option class="translate" value="none">Nothing</option>
</select>
<label class="translate" for="logbookSource">Logbook: Source should show</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m8 l4">
<input class="value" type="number" id="maxBrowserInstances" />
<label class="translate" for="maxBrowserInstances">Maximum browser instances to keep</label>
</div>
</div>
</div>
<div id="tab-le" class="col s12 m12 l12 page">
<div class="row">
<div class="col s12">
<img src="../../img/le.png" class="logo-le">
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l2">
<input class="value" id="leEnabled" type="checkbox" data-link="lets-encrypt-certificates"/>
<label for="leEnabled" class="translate">Use Lets Encrypt certificates</label>
</div>
</div>
<div class="row le-sub-settings">
<div class="input-field col s12 m6 l2">
<input class="value" id="leUpdate" type="checkbox" data-link="lets-encrypt-certificates"/>
<label for="leUpdate" class="translate">Use this instance for automatic update</label>
</div>
</div>
<div class="row le-sub-settings le-sub-settings-update">
<div class="input-field col s12 m6 l4">
<input class="value" id="lePort" type="number" size="5" maxlength="5" data-link="lets-encrypt-certificates"/>
<label for="lePort" class="translate">Port to check the domain</label>
</div>
</div>
</div>
<div id="tab-custom" class="col s12 m12 l12 page">
<a class="waves-effect waves-light btn btn-restart disabled" style="margin-top: 5px;"><i class="material-icons left">refresh</i><span class="translate">Restart adapter to update files</span></a>
<span class="translate notice" style="margin-top: 15px;">Drag and drop the custom cards files here</span>
<div id="files_list" class="s12 m12 l12"></div>
<div class="fixed-action-btn">
<a id="open-add-folder-modal" class="btn-floating btn-large waves-effect waves-light blue modal-trigger" href="#EnterFoldernameModal"><i class="material-icons">add</i></a>
</div>
<div id="EnterFoldernameModal" class="modal">
<div class="modal-content">
<h4 class="translate">Enter folder name</h4>
<div class="input-field">
<input id="foldername"/>
</div>
</div>
<div class="modal-footer">
<a id="add-folder" href="#!" class="modal-action modal-close waves-effect waves-blue blue btn-flat">Ok</a>
<a href="#!" class="modal-action modal-close waves-effect waves-red red btn-flat">Cancel</a>
</div>
</div>
</div>
<div id="tab-themes" class="col s12 m12 l12 page">
<div class="row row-narrow">
<div class="input-field col s12 m6 l4">
<select id="defaultTheme" class="value"></select>
<label class="translate" for="defaultTheme">Default theme</label>
</div>
<div class="input-field col s12 m6 l4">
<select id="defaultThemeDark" class="value"></select>
<label class="translate" for="defaultThemeDark">Default theme dark</label>
</div>
</div>
<div class="row row-narrow s12 m12 l12">
<label class="translate" for="defaultTheme">Place the themes as YAML here</label><br>
<div id="themes"></div>
<textarea id="themes_fallback"></textarea>
</div>
</div>
<div id="tab-entities" class="col s12 m12 l12 page">
<a class="waves-effect waves-light btn btn-refresh" style="margin-top: 5px;"><i class="material-icons left">refresh</i><span class="translate">Reload entities</span></a>
<a class="waves-effect waves-light btn btn-attributes" style="margin-top: 5px;" data-invisible="true"><i class="material-icons left">check</i><span class="translate">Show attributes</span></a>
<div class="div-entities">
<table>
<thead>
<tr><th></th><th>Entity ID</th><th>States</th><th class="attributes">Attributes</th></tr>
</thead>
<tbody id="entities">
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>