UNPKG

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> <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>&nbsp</td><td>&nbsp</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 ? '&nbsp' : 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 !important; 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 !important; } .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>