CVE-2019-10765 (JavaScript, published 2019-11-20T16:15Z)
Description: value: iobroker.admin before 3.6.12 allows attacker to include file contents from outside the `/log/file1/` directory.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/ioBroker/ioBroker.admin
Files changed:
README.md
gulpfile.js
io-package.json
lib/socket.js
lib/web.js
main.js
package.json
src/js/admin.js
src/js/adminAdapters.js
src/login/index.html
Commit message: ### 3.6.8 (2019-10-09)
* (bluefox) Log paths were sanitized
* (bluefox) NPM packages were updated


Vulnerable code:
===================

User interface for configuration and administration.

* (bluefox) fix error with empty ID 
* (bluefox) rewrite interface with materialize 
            'transform-es2015-arrow-functions',
            'transform-es2015-block-scoping',
            'transform-es2015-classes',
            'transform-es2015-template-literals'
    .pipe(uglify())    
    "version": "3.6.7",
      "3.6.6": {
      },    
      "zh-cn": "docs/zh-cn/admin.md"            
function IOSocket(server, settings, adapter, objects, states, store) {
    if (!(this instanceof IOSocket)) return new IOSocket(server, settings, adapter, objects, states, store);
        if (!this.subscribes[type]) this.subscribes[type] = {};
            if (!socket._subscribe || !socket._subscribe[type]) return;
                    if (adapter.requireLog) adapter.requireLog(true);
                    callback(null, states);
                    callback(null, states[id]);
                adapter.getForeignStates(pattern, function (err, objs) {
                    if (typeof callback === 'function') {
                        callback(err, objs);
                    } else {
                        adapter.log.warn('[getForeignStates] Invalid callback')
                    }
                });
                adapter.setForeignState(id, state, {user: this._acl.user}, function (err, res) {
                    if (typeof callback === 'function') {
                        callback(err, res);
                    }
                });

                if (adapter.log.level === 'debug') showSubscribes(socket, 'stateChange');
                if (typeof callback === 'function') {
                    setImmediate(callback, null);
                }
                if (readAll) {
                }
                adapter.subscribeForeignStates('*');
                adapter.unsubscribeForeignStates('*');
                    if (req.body.origin) {
                        const parts = req.body.origin.match(/href=(.+)$/);
                        failureRedirect: '/login/index.html' + req.body.origin + (req.body.origin ? '&error' : '?error'),
                        res.status(500).send(err);
                let parts = req.url.split('/');
                            filename = path.join(parts.join('/'), filename);
                            filename = path.join('log/', filename) ;
                        if (filename[0] !== '/' && !filename.match(/^\W:/)) {
                            filename = path.normalize(__dirname + '/../../../') + filename;
                        if (fs.existsSync(filename)) {
                res.status(404).send('File ' + filename + ' not found');
                    tempFilePath: settings.tmpPath
                                res.status(500).send(err);

                        res.status(404).send('File ' + url + ' not found');
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
let socket      = null;
let webServer   = null;
let objects     = {};
let states      = {};
let secret      = 'Zgfr56gFe87jJOM'; // Will be generated by first start
        if (socket) {
            socket.objectChange(id, obj);
        }
    adapter.on('stateChange', (id, state) => {
        if (!state) {
            if (states[id]) {
                delete states[id];
            }
        } else {
            states[id] = state;
        }
        if (socket) {
            socket.stateChange(id, state);
        }
    });
        if (socket) {
            socket.sendCommand(obj);
        }
        if (socket) {
            // unsubscribe all
            socket.unsubscribeAll();
        }

}

// to do => remove it later, when all repositories patched.
function patchRepos(callback) {
    return callback && callback();
    // do not patch any more. Delete it later 2018.04.23
    /*
    adapter.getForeignObject('system.repositories', (err, obj) => {
        let changed = false;
        if (obj && obj.native && obj.native.repositories) {
            // default link should point to stable
            if (!obj.native.repositories.default || obj.native.repositories.default.link !== 'http://download.iobroker.net/sources-dist.json') {
                changed = true;
                obj.native.repositories.default = {
                    link: 'http://download.iobroker.net/sources-dist.json'
                };
            }
            // latest link should point to latest
            if (!obj.native.repositories.latest) {
                obj.native.repositories.latest = {
                    link: 'http://download.iobroker.net/sources-dist-latest.json'
                };
                changed = true;
            }

            // change URL of raw sources from ioBroker.js-controller to ioBroker.repositories
            for (let r in obj.native.repositories) {
                if (obj.native.repositories.hasOwnProperty(r) &&
                    obj.native.repositories[r].link === 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/conf/sources-dist.json') {
                    obj.native.repositories[r].link = 'https://raw.githubusercontent.com/ioBroker/ioBroker.repositories/master/sources-dist.json';
                    changed = true;
                }
            }
        }
        if (changed) {
            adapter.setForeignObject(obj._id, obj, function () {
                callback && callback();
            });
        } else {
            callback && callback();
        }
    });*/
    socket = new SocketIO(server, adapter.config, adapter, objects, states, store);
    patchRepos(() => {
        // By default update repository every 24 hours
        if (adapter.config.autoUpdate === undefined) {
            adapter.config.autoUpdate = 24;
        }
        adapter.config.autoUpdate = parseInt(adapter.config.autoUpdate, 10) || 0;
        if (adapter.config.autoUpdate) {
            setInterval(() => updateRegister(), adapter.config.autoUpdate * 3600000);
            updateRegister();
        }
    });
    let tasks = 0;
    });
    tasks++;
        !--tasks && callback && callback();
function updateRegister() {
    adapter.log.info('Request actual repository...');
    adapter.getForeignObject('system.config', (err, data) => {
        if (data && data.common) {
            adapter.sendToHost(adapter.host, 'getRepository', {
                repo:   data.common.activeRepo,
                update: true
            }, _repository => {
                if (_repository === 'permissionError') {
                    adapter.log.error('May not read "getRepository"');
                } else {
                    adapter.log.info('Repository received successfully.');
                    socket && socket.repoUpdated();
  "version": "3.6.7",
    "body-parser": "^1.18.2",
    "cookie-parser": "^1.4.3",
    "express": "^4.16.3",
    "express-fileupload": "https://github.com/screenmeet/express-fileupload/tarball/8d8f5d9b7cec2d7c17e6d85654edf1c0aaa5fa82",
    "express-session": "^1.15.6",
    "request": "^2.85.0",
    "xtend": "^4.0.1",
    "@iobroker/testing": "^1.2.0",
    "babel-core": "^6.26.3",
    "babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
    "babel-plugin-transform-es2015-block-scoping": "^6.26.0",
    "babel-plugin-transform-es2015-classes": "^6.24.1",
    "babel-plugin-transform-es2015-template-literals": "^6.22.0",
    "chai": "^4.1.2",
    "gulp": "^4.0.0",
    "gulp-babel": "^7.0.1",
    "gulp-clean-css": "^3.9.4",
    "gulp-htmlmin": "^4.0.0",
    "gulp-less": "^4.0.0",
    "gulp-sass": "^3.1.0",
    "gulp-sourcemaps": "^2.6.4",
    "gulp-terser": "^1.1.7",
    "ansi-colors": "^3.2.4",
    "mocha": "^6.1.4"
/* jslint vars: true */
    var what;
    var a = arguments;
    var L = a.length;
    var ax;
var $iframeDialog = null; // used in adapter settings window
var configNotSaved = null; // used in adapter settings window
var showConfig = null; // used in adapter settings window
var defaults = {};
var customPostInits = {};
var customPostOnSave = {};
var FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\\s?]/g;
var adapterRedirect = function (redirect, timeout) {
var gMain = null; // for google maps
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf('MSIE ');
    var trident = ua.indexOf('Trident/');
        var rv = ua.indexOf('rv:');
    var edge = ua.indexOf('Edge/');
    var path = location.pathname + 'socket.io';
    var allTabs = {};
    var main = {
                    var cb = $dialogConfirm.data('callback');
                var tButtons = '';
                for (var b = buttons.length - 1; b >= 0; b--) {
                    var cb = $dialogConfirm.data('callback');
            var text = typeof dateObj;
            var v;
            var $wizard = $('#button-wizard');
                        main._lastTimer = (new Date()).getTime();
                var obj = main.objects[main.currentUser];
                var name = '';
                    var objs = {};
                var groups = [];
                for (var i = 0; i < tabs.users.groups.length; i++) {
                    var group = main.objects[tabs.users.groups[i]];
            var id;
                    var obj = main.objects[id];
        },     
                var list = [];
                for (var id in main.objects) {
            var leaf = $tree ? $tree.selectId('getTreeInfo', id) : null;
    var tabs = {
    var stdout;
    var cmdCallback    = null;
    var activeCmdId    = null;
    var $stdout        = $('#stdout');
    var $dialogCommand = $('#dialog-command');
    var $dialogLicense = $('#dialog-license-main');
    var $dialogMessage = $('#dialog-message');
    var $dialogConfirm = $('#dialog-confirm');
    var $dialogCommandProgress = $dialogCommand.find('.progress div');
    var $adminSideMenu = $('#admin_sidemenu_menu');
    var $adminSideMain = $('#admin_sidemenu_main');
    var firstConnect   = true;
			var $versionBtn = $('.button-version');
            var $dialog = $('#admin_sidemenu_dialog');
            var html = $dialog.html();
            var $e = $(event.target);
            var offs = $e.offset();
            var text =
            var $lis = $adminSideMenu;
            for (var tid in allTabs) {
                var name = allTabs[tid];
                var found = $adminSideMenu.find('.admin-sidemenu-items[data-tab="' + tid + '"]').length;
                var id = 'chk-' + tid;
                var id = $(this).attr('id').substr(4);
                    var pos = main.systemConfig.common.tabs.indexOf(id);
                var $input = $(this).prev();
        
        
            var $menu = $adminSideMenu;
            var panelSelector = $menu.data('problem-link');
                var $panel = $(panelSelector);
                    var link = $panel.data('src');
                        var $iframe = $panel.find('iframe');
        var text     = '';
        var list     = [];
        var addTabs = [];
        for (var i = 0; i < main.instances.length; i++) {
            var instance = main.instances[i];
            var instanceObj = main.objects[instance];
                var isFound = false;
                var inst1 = instance.replace(/\.(\d+)$/, '.');
                for (var j = 0; j < addTabs.length; j++) {
                    var inst2 = addTabs[j].replace(/\.(\d+)$/, '.');
            var $this = $(this);
            var id = $this.attr('id');
        for (var a = 0; a < addTabs.length; a++) {
            var tab   = main.objects[addTabs[a]];
            var name  = 'tab-' + tab.common.name;
            var link  = tab.common.adminTab.link || '/adapter/' + tab.common.name + '/tab.html';
            var parts = addTabs[a].split('.');
            var buttonName;
                var isReplace = false;
                    var div = '<div id="' + name + '" data-name="' + buttonName + '" class="tab-custom ' + (isReplace ? 'link-replace' : '') + '" data-adapter="' + parts[2] + '" data-instance="' + parts[3] + '" data-src="' + link + '">' +
                        var elem = $ (this).contents ().find('body>header');
                        if (!elem || !elem.length) elem = $(this).contents ().find('head');
                        if (elem && elem.length) elem.append('<link rel="stylesheet" type="text/css" href="../../lib/css/iob/selectID.css"/>');
            var countLink = 0;
            var loadTimeout = setTimeout(function() {
        var $selHosts = $('#host-adapters');
        var lines = [];
        var color;
        var curId;
        for (var i = 0; i < main.tabs.hosts.list.length; i++) {
        var $selBtn = $('#host-adapters-btn').show();
            var val = $(this).data('value');
            var id  = 'system.host.' + val + '.alive';
            var val = $(this).prop('checked');
            var $input = $(this).prev();
                    var major = parseInt(result['Node.js'].split('.').shift().replace('v', ''), 10);
                var obj;
                for (var id in main.objects) {
                for (var dialog in main.dialogs) {
        //var changed = false;
        var oldObj = null;
        var action = 'update';

        oldObj = main.objects[id];
                var ignore = false;
                        var newObj = JSON.parse(JSON.stringify(obj));
        var ts = (new Date()).getTime();
        main._timer = setTimeout(function () {
        for (var pattern in main.subscribesStates) {
        for (var pattern in main.subscribesObjects) {
            for (var s = 0; s < patterns.length; s++) {
            for (var s = 0; s < patterns.length; s++) {
                    console.debug('Unsibscribe: ' + patterns[s]);
            for (var s = 0; s < patterns.length; s++) {
            for (var s = 0; s < patterns.length; s++) {
        var parts = decodeURI(window.location.hash).split('/');
            var parts   = decodeURI(window.location.hash).split('/');
                var parts  = main.currentHash.split('/');
                var tab    = parts[0].replace(/^#/, '').replace(/^tab-/, '');
                var dialog = parts[1];
                var params = decodeURIComponent(parts[2]);
                var $adminBody = $('.admin-sidemenu-body');
                var $actualTab = $adminBody.find('.admin-sidemenu-body-content');
                var $panel     = $('#tab-' + tab);
                    var link;
                        var $oldPanel = $('#tab-' + main.currentTab);
                            var $iframe_ = $oldPanel.find('>iframe');
                            var $iframe = $panel.find('>iframe');
                var  $tab = $adminSideMenu.find('.admin-sidemenu-items[data-tab="tab-' + tab + '"]');
        var icon;
        var alt;
        var isCommon = obj && obj.common;
                    var instance;
        var icon     = '';
        var alt      = '';
                var result = getIconHtml(obj);
        var icon     = '';
        var alt      = '';
            var result = getIconHtml(obj);
        var units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
        var u = -1;
        var r = parseInt(hex.slice(0, 2), 16),
    var tabsInfo = {
        'tab-node-red-2':       {order: 62,   icon: 'device_hub'},        
        var lines = '';
        var elements = [];
            var id = $(this).attr('id');
            var id = $(this).attr('id');
                var icon;
                    var _id = 'system.adapter.' + id.substring(4);
		    if (main.objects[_id] && main.objects[_id].common.adminTab && main.objects[_id].common.adminTab['fa-icon']) {
        for (var e = 0; e < elements.length; e++) {
            var m = text.match(/^upload \[(\d+)]/);
                var max = $dialogCommand.data('max');
                var value = parseInt(m[1], 10);
            var $backButton = $adminSideMain.find('.button-command');
                var error = $dialogCommand.data('error');
                    var m = error.match(/error: (.*)$/);
                        var vendor = data.native.vendor;
                                for (var r = 0; r < vendor.admin.css.rules.length; r++) {
                        var pos = main.systemConfig.common.tabs.indexOf('tab-log');
                                            var language = (main.systemConfig.common.language || window.navigator.userLanguage || window.navigator.language || '').substring(0, 2);
                                            if (language !== 'en' && language !== 'de' && language !== 'ru') language = 'en';
                                                var $input = $(this).prev();
                                for (var t in tabs) {
        if (main.waitForRestart) {
            location.reload();
        }
        if (main.waitForRestart) {
            location.reload();
        }
    var that = this;
            var tmp = version.split ('.');
                    var node = data.node;
                    var $tdList = $(node.tr).find('>td');
                    var obj = that.data[node.key];
                        for (var c = 0; c < that.tree.length; c++) {
                                var installed = 0;
                                for (var k = 0; k < that.tree[c].children.length; k++) {
                                    if (that.data[that.tree[c].children[k].key].installed) installed++;
                                var title;
                                //if (!that.onlyInstalled && !that.onlyUpdatable) {
                                title = '[<span title="' + _('Installed from group') + '">' + installed + '</span> / <span title="' + _('Total count in group') + '">' + that.tree[c].children.length + '</span>]';
                    var idx = obj.desc.indexOf('<div');
                    var desc = idx >= 0 ? obj.desc.substr(0, idx) : obj.desc;
                var $processAdapters = that.$tab.find('.process-adapters');
        var anyVisible = false;
                var $this = $(this);
                var data = {};
                var order = [];
                var url;
                for (var o = 0; o < order.length; o++) {
                    var user = that.urls[order[o]].match(/\.com\/([-_$§A-Za-z0-9]+)\/([-._$§A-Za-z0-9]+)\//);
                    var isCustom = !that.$installDialog.find('a[href="#tabs-install-github"]').hasClass('active');//!!that.$installDialog.find('#tabs-install').tabs('option', 'active');
                    var url;
                    var debug;
                    var adapter;
                        var parts = that.$installDialog.find('#install-github-link').val().split(' ');
                    var $input = $(this).prev();
             if (!that.data[node.key]) return false;
             var title = that.data[node.key].title;
             if (title && typeof title === 'object') {
             }
            var desc = that.data[node.key].desc;
                desc = desc[systemLang] || desc.en;
            if ((that.data[node.key].name      && that.data[node.key].name.toLowerCase().indexOf(that.currentFilter)     !== -1) ||
                 (title                        && title.toLowerCase().indexOf(that.currentFilter)                        !== -1) ||
                 (that.data[node.key].keywords && that.data[node.key].keywords.toLowerCase().indexOf(that.currentFilter) !== -1) ||
                 (desc                         && desc.toLowerCase().indexOf(that.currentFilter)                         !== -1)){
             } else {
                 return false;
             }
            this.main.socket.emit('sendToHost', host, 'getRepository', {repo: this.main.systemConfig.common.activeRepo, update: updateRepo}, function (_repository) {
                that.curRepository = _repository || {};
                if (that.curRepository && that.curInstalled && that.curRunning) {
                    that.curRepoLastUpdate = (new Date()).getTime();
                    setTimeout(function () {
                        for (var c = 0; c < that.curRunning.length; c++) {
                            that.curRunning[c](that.curRepository, that.curInstalled);
                        }
                        that.curRunning = null;
            this.main.socket.emit('sendToHost', host, 'getInstalled', null, function (_installed) {
                that.curInstalled = _installed || {};
                if (that.curRepository && that.curInstalled) {
                    that.curRepoLastUpdate = (new Date()).getTime();
                    setTimeout(function () {
                        for (var c = 0; c < that.curRunning.length; c++) {
                            that.curRunning[c](that.curRepository, that.curInstalled);
                        }
                        that.curRunning = null;
            setTimeout(function () {
                if (that.curRunning) {
                    for (var c = 0; c < that.curRunning.length; c++) {
                        that.curRunning[c](that.curRepository, that.curInstalled);
                    }
                    that.curRunning = null;
                if (callback) callback(that.curRepository, that.curInstalled);
        var text = '';
            for (var v in adapter.news) {
        var adapters;
            for (var a = 0; a < dependencies.length; a++) {
                for (var b in dependencies[a]) {
        for (var adapter in adapters) {
            //var d1 = that.data[c1.key], d2 = that.data[c1.key];
            var inst1 = c1.data.installed || 0, inst2 = c2.data.installed || 0;
            var ret = inst2 - inst1;
            var t1 = c1.titleLang || c1.title || '';
            var t2 = c2.titleLang || c2.title || '';
        var interval = now.getTime() - time.getTime();
        var days = Math.floor(interval / (24 * 3600000));
            var t  = days % 10;
            var tt = days % 100;
                var obj;
                var version;
                var rawVersion;
                var adapter;
                var adaptersToUpdate = 0;

                var listInstalled = [];
                var listNonInstalled = [];
                var nowObj = new Date();
                var localTexts = {
                    //var span = getVersionSpan(version);
                    var color = getVersionClass(version);
                    var title = color + '\n\r' + (news || '');
                for (var i = 0; i < listInstalled.length; i++) {
                    var installed = '';
                    var rawInstalled = '';
                    var icon = obj.icon;
                    var _instances = 0;
                    var _enabled   = 0;
                        var news = '';
                        var updatable = false;
                        var updatableError = '';
                        for (var z = 0; z < that.main.instances.length; z++) {
                    var group = (obj.type || that.types[adapter] || 'common adapters') + '_group';
                    var desc  = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc;
                    var title = obj.titleLang || obj.title;
                        var iGroup = -1;
                        for (var jj = 0; jj < that.tree.length; jj++) {
                    for (i = 0; i < listNonInstalled.length; i++) {
                        var group = (obj.type || that.types[adapter] || 'common adapters') + '_group';
                        var desc = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc;
                        title = obj.titleLang || obj.title;
                            var igroup = -1;
                            for (var j = 0; j < that.tree.length; j++){
                    var akeys = Object.keys(that.data);
                    var newData = {};
                    for (var u = 0; u < akeys.length; u++) {
                    var text = '';
                    var types = [];
                    for (var a in that.data) {
                        var ad = that.data[a];
                    for (var b in that.data) {
                    var tTypes = '<li class="main-toolbar-table-types-item" data-type=""><a>' + localTexts['all'] + '</a></li>\n';
                    for (var g = 0; g < types.length; g++) {
                    var $types = that.$tab.find('#main-toolbar-table-types');
                            var text = '<div class="icon-large" style="' +
                            var $big = $(text);
                            var h   = parseFloat($big.height());
                            var top = Math.round($(this).position().top - ((h - parseFloat($(this).height())) / 2));
                            var big = $(this).data('big');
                    var classes = [
                        var i = 0;
                var adaptersToUpdate = 0;
                for (var adapter in installedList) {
                    var obj = installedList ? installedList[adapter] : null;
                    var version = '';
                    if (repository[adapter] && repository[adapter].version) version = repository[adapter].version;
            var $updates = $('#updates-for-adapters');
        var $dialogAddInstance = $('#dialog-add-instance');
        var text = '';
        for (var h = 0; h < that.main.tabs.hosts.list.length; h++) {
            var host = that.main.tabs.hosts.list[h];
        var min = -1;
        var used = [];
        for (var i = 0; i < that.main.tabs.instances.list.length; i++) {
            var parts = that.main.tabs.instances.list[i].split('.');
                var index = parseInt(parts[parts.length - 1], 10);
        for (var m = 0; m < min; m++) {
        var $dialogLicense = $('#dialog-license');
        var timeout = setTimeout(function () {
            var adapter = $(this).attr('data-adapter-name');
            var desc = $(this).attr('data-adapter-desc');
                    var obj = repo[adapter];
                    if (!obj) obj = installed[adapter];
                    if (!obj) return;
                        showLicenseDialog(adapter, function (isAgree) {
                            if (isAgree) {
                                that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, function (exitCode) {
                                    if (!exitCode) that._postInit(true);
                                });
                            }
                        });
                        that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, function (exitCode) {
                            if (!exitCode) that._postInit(true);
                        });
            var name = $(this).attr('data-adapter-name');
            var aName = $(this).attr('data-adapter-name');
            var aName = $(this).attr('data-adapter-name');
        var $button = this.$tab.find('.adapter-update-custom-submit[data-adapter-name="' + adapter + '"]');
            var versions = [];
                var news = that.main.objects['system.adapter.' + adapter].common.news;
                for (var id in news) {
            var menu = '<div class="collection">';
            for (var v = 0; v < versions.length; v++) {
                var nnews = (news[versions[v]] ? news[versions[v]][systemLang] || news[versions[v]].en : '');
            var $adaptersMenu = $('#adapters-menu');
                var adapter = $(this).data('adapter-name');
                var version = $(this).data('version');
                    var $adaptersMenu = $('#adapters-menu');
                    var trigger = $adaptersMenu.data('trigger');
                var j = this.list.indexOf(id);
        var text = '';
        var opened;
            var adapter = id.match(/^system\.adapter\.([\w\d-]+)\.upload$/);
                var $adapter = this.$tab.find('.adapter-upload-progress[data-adapter-name="' + adapter[1] + '"]');
                var text = showUploadProgress(state.val);
            box-sizing:border-box;
        input:focus { outline: none; }

        input:focus ~ label, input.used ~ label {
        input:focus ~ .bar:before, input:focus ~ .bar:after { width: 50%; }
        .button:focus { outline: 0; }

        .buttonBlue:hover { background: #39a1f4; }
        <input type="text" name="username" id="username"><span class="highlight"></span><span class="bar"></span>
        <input type="password" name="password" id="password"><span class="highlight"></span><span class="bar"></span>
        for (var i = 0; i < inputs.length; i++) {
        }

Fixed code:
User interface for configuration and administration of ioBroker.
### 3.6.8 (2019-10-09)
* (bluefox) Log paths were sanitized
* (bluefox) NPM packages were updated

* (bluefox) fix error with empty ID
* (bluefox) rewrite interface with materialize
            '@babel/plugin-transform-arrow-functions',
            '@babel/plugin-transform-block-scoping',
            '@babel/plugin-transform-classes',
            '@babel/plugin-transform-template-literals'
    .pipe(uglify())
    "version": "3.6.8",
      "3.6.7": {
        "en": "Add Node.JS version check to popup messages",
        "de": "Node.JS-Versionsprüfung zu Popup-Nachrichten hinzufügen",
        "ru": "Добавить проверку версии Node.JS во всплывающие сообщения",
        "pt": "Adicione a verificação da versão do Node.JS às mensagens pop-up",
        "nl": "Voeg Node.JS versiecontrole toe aan pop-upberichten",
        "fr": "Ajouter la vérification de la version de Node.JS aux messages contextuels",
        "it": "Aggiungi il controllo versione Node.JS ai messaggi popup",
        "es": "Agregar verificación de versión de Node.JS a mensajes emergentes",
        "pl": "Dodaj sprawdzanie wersji Node.JS do wyskakujących wiadomości",
        "zh-cn": "将Node.JS版本检查添加到弹出消息"
      },"3.6.6": {
      },
      "zh-cn": "docs/zh-cn/admin.md"
function IOSocket(server, settings, adapter, objects, store) {
    if (!(this instanceof IOSocket)) {
        return new IOSocket(server, settings, adapter, objects, store);
    }
    let states = {};
        this.subscribes[type] = this.subscribes[type] || {};
            if (!socket._subscribe || !socket._subscribe[type]) {
                return;
            }

                    adapter.requireLog && adapter.requireLog(true);
                    adapter.getForeignStates('*', callback);
                    if (states[id]) {
                        callback(null, states[id]);
                    } else {
                        adapter.getForeignState(id, callback);
                    }
                if (typeof callback === 'function') {
                    adapter.getForeignStates(pattern, callback);
                } else {
                    adapter.log.warn('[getForeignStates] Invalid callback')
                }
                // clear cache
                if (states[id]) {
                    delete states[id];
                }

                adapter.setForeignState(id, state, {user: this._acl.user}, (err, res) =>
                    typeof callback === 'function' && callback(err, res));
                // clear cache
                if (states[id]) {
                    delete states[id];
                }

                // reset states cache on unsubscribe
                states = {};

                adapter.log.level === 'debug' && showSubscribes(socket, 'stateChange');

                typeof callback === 'function' && setImmediate(callback, null);
                /*if (readAll) {
                }*/

                Object.keys(that.subscribes.stateChange).forEach(pattern =>
                    adapter.subscribeForeignStates(pattern));


                Object.keys(that.subscribes.stateChange).forEach(pattern =>
                    adapter.unsubscribeForeignStates(pattern));

        if (!state) {
            if (states[id]) {
                delete states[id];
            }
        } else {
            states[id] = state;
        }

// copied from here: https://github.com/component/escape-html/blob/master/index.js
const matchHtmlRegExp = /["'&<>]/;
function escapeHtml (string) {
    const str = '' + string;
    const match = matchHtmlRegExp.exec(str);

    if (!match) {
        return str;
    }

    let escape;
    let html = '';
    let index = 0;
    let lastIndex = 0;

    for (index = match.index; index < str.length; index++) {
        switch (str.charCodeAt(index)) {
            case 34: // "
                escape = '&quot;';
                break;
            case 38: // &
                escape = '&amp;';
                break;
            case 39: // '
                escape = '&#39;';
                break;
            case 60: // <
                escape = '&lt;';
                break;
            case 62: // >
                escape = '&gt;';
                break;
            default:
                continue;
        }

        if (lastIndex !== index) {
            html += str.substring(lastIndex, index);
        }

        lastIndex = index + 1;
        html += escape;
    }

    return lastIndex !== index
        ? html + str.substring(lastIndex, index)
        : html;
}

                    const origin = (req.body && req.body.origin) || '?href=%2F';
                    if (origin) {
                        const parts = origin.match(/href=(.+)$/);
                            // if some invalid characters in redirect
                            if (redirect.match(/[^-_a-zA-Z0-9&%?.]/)) {
                                redirect = '/';
                            }
                    req.body.password = ((req.body && req.body.password) || '').toString();
                    req.body.username = ((req.body && req.body.username) || '').toString();

                        failureRedirect: '/login/index.html' + origin + (origin ? '&error' : '?error'),
                        res.status(500).send(escapeHtml(typeof err === 'string' ? err : JSON.stringify(err)));
                let parts = decodeURIComponent(req.url).split('/');
                        let logFolder;
                            logFolder = path.normalize(parts.join('/'));
                            logFolder = path.join(process.cwd(), 'log');
                        if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) {
                            logFolder = path.normalize(path.join(__dirname + '/../../../', logFolder));
                        filename = path.normalize(path.join(logFolder, filename));

                        if (filename.startsWith(logFolder) && fs.existsSync(filename)) {
                res.status(404).send('File ' + escapeHtml(filename) + ' not found');
                    tempFileDir: settings.tmpPath
                    // take first non empty file
                                res.status(500).send(escapeHtml(typeof err === 'string' ? err : JSON.stringify(err)));
                        res.status(404).send('File ' + escapeHtml(url) + ' not found');
const utils       = require('@iobroker/adapter-core'); // Get common adapter utils
const ONE_HOUR_MS = 3600000;
const ERROR_PERMISSION = 'permissionError';
let socket        = null;
let webServer     = null;

let objects       = {};
let secret        = 'Zgfr56gFe87jJOM'; // Will be generated by first start
        socket && socket.objectChange(id, obj);
    adapter.on('stateChange', (id, state) =>
        socket && socket.stateChange(id, state));
        socket && socket.sendCommand(obj);
        // unsubscribe all
        socket && socket.unsubscribeAll();
    socket = new SocketIO(server, adapter.config, adapter, objects, store);
    // By default update repository every 24 hours
    if (adapter.config.autoUpdate === undefined || adapter.config.autoUpdate === null) {
        adapter.config.autoUpdate = 24;
    }

    // interval in hours
    adapter.config.autoUpdate = parseInt(adapter.config.autoUpdate, 10) || 0;

    adapter.config.autoUpdate && updateRegister();
    /*

    });*/


        callback && callback();
function updateRegister(isForce) {
    adapter.getForeignObject('system.config', (err, systemConfig) => {
        err && adapter.log.error('May not read "system.config"');

        if (systemConfig && systemConfig.common) {
            adapter.getForeignObject('system.repositories', (err, repos) => {
                err && adapter.log.error('May not read "system.repositories"');
                // Check if repositories exists
                let exists = false;
                const active = systemConfig.common.activeRepo;

                // if repo is valid and actual
                if (!err &&
                    repos &&
                    repos.native &&
                    repos.native.repositories &&
                    repos.native.repositories[active] &&
                    Date.now() < repos.ts + adapter.config.autoUpdate * ONE_HOUR_MS) {
                    exists = true;
                }
                if (!exists || isForce) {
                    adapter.log.info('Request actual repository...');
                    // request repo from host
                    adapter.sendToHost(adapter.host, 'getRepository', {
                        repo:   active,
                        update: true
                    }, _repository => {
                        if (_repository === ERROR_PERMISSION) {
                            adapter.log.error('May not read "getRepository"');
                        } else {
                            adapter.log.info('Repository received successfully.');

                            socket && socket.repoUpdated();
                        }

                        // start next cycle
                        if (adapter.config.autoUpdate) {
                            adapter.timerRepo && clearInterval(adapter.timerRepo);
                            adapter.log.debug('Next repo update on ' + new Date(Date.now() + adapter.config.autoUpdate * ONE_HOUR_MS + 1).toLocaleString());
                            adapter.timerRepo = setTimeout(() => updateRegister(), adapter.config.autoUpdate * ONE_HOUR_MS + 1);
                        }
                    });
                } else if (adapter.config.autoUpdate) {
                    const interval = repos.ts + adapter.config.autoUpdate * ONE_HOUR_MS - Date.now() + 1;
                    adapter.log.debug('Next repo update on ' + new Date(Date.now() + interval).toLocaleString());
                    adapter.timerRepo && clearInterval(adapter.timerRepo);
                    adapter.timerRepo = setTimeout(() => updateRegister(), interval);
  "version": "3.6.8",
    "body-parser": "^1.19.0",
    "cookie-parser": "^1.4.4",
    "express": "^4.17.1",
    "express-fileupload": "^1.1.6-alpha.5",
    "express-session": "^1.16.2",
    "request": "^2.88.0",
    "xtend": "^4.0.2",
    "@iobroker/testing": "^1.2.5",
    "@babel/core": "^7.6.3",
    "@babel/plugin-transform-arrow-functions": "^7.2.0",
    "@babel/plugin-transform-block-scoping": "^7.6.3",
    "@babel/plugin-transform-classes": "^7.5.5",
    "@babel/plugin-transform-template-literals": "^7.4.4",
    "chai": "^4.2.0",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-clean-css": "^4.2.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-less": "^4.0.1",
    "gulp-sass": "^4.0.2",
    "gulp-sourcemaps": "^2.6.5",
    "gulp-terser": "^1.2.0",
    "ansi-colors": "^4.1.1",
    "mocha": "^6.2.1"
/* jslint consts: true */
    let what;
    const a = arguments;
    let L = a.length;
    let ax;
let $iframeDialog = null;  // used in adapter settings window
let configNotSaved = null; // used in adapter settings window
let showConfig = null;     // used in adapter settings window

const defaults = {};
const customPostInits = {};
const customPostOnSave = {};
const FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\\s?]/g;
const adapterRedirect = function (redirect, timeout) {
let gMain = null; // for google maps
    const ua = window.navigator.userAgent;
    const msie = ua.indexOf('MSIE ');
    const trident = ua.indexOf('Trident/');
        const rv = ua.indexOf('rv:');
    const edge = ua.indexOf('Edge/');
    let path = location.pathname + 'socket.io';
    let allTabs = {};
    const main = {
                    const cb = $dialogConfirm.data('callback');
                let tButtons = '';
                for (let b = buttons.length - 1; b >= 0; b--) {
                    const cb = $dialogConfirm.data('callback');
            let text = typeof dateObj;
            let v;
            const $wizard = $('#button-wizard');
                        main._lastTimer = Date.now();
                const obj = main.objects[main.currentUser];
                let name = '';
                    const objs = {};
                const groups = [];
                for (let i = 0; i < tabs.users.groups.length; i++) {
                    const group = main.objects[tabs.users.groups[i]];
            let id;
                    const obj = main.objects[id];
        },
                const list = [];
                for (const id in main.objects) {
            const leaf = $tree ? $tree.selectId('getTreeInfo', id) : null;
    const tabs = {
    let stdout;
    let cmdCallback      = null;
    let activeCmdId      = null;
    const $stdout        = $('#stdout');
    const $dialogCommand = $('#dialog-command');
    const $dialogLicense = $('#dialog-license-main');
    const $dialogMessage = $('#dialog-message');
    const $dialogConfirm = $('#dialog-confirm');
    const $dialogCommandProgress = $dialogCommand.find('.progress div');
    const $adminSideMenu = $('#admin_sidemenu_menu');
    const $adminSideMain = $('#admin_sidemenu_main');
    let firstConnect     = true;
			const $versionBtn = $('.button-version');
            const $dialog = $('#admin_sidemenu_dialog');
            const html = $dialog.html();
            const $e = $(event.target);
            const offs = $e.offset();
            let text =
            const $lis = $adminSideMenu;
            for (const tid in allTabs) {
                const name = allTabs[tid];
                const found = $adminSideMenu.find('.admin-sidemenu-items[data-tab="' + tid + '"]').length;
                const id = 'chk-' + tid;
                const id = $(this).attr('id').substr(4);
                    const pos = main.systemConfig.common.tabs.indexOf(id);
                const $input = $(this).prev();


            const $menu = $adminSideMenu;
            const panelSelector = $menu.data('problem-link');
                const $panel = $(panelSelector);
                    const link = $panel.data('src');
                        const $iframe = $panel.find('iframe');
        let text      = '';
        const list    = [];
        const addTabs = [];
        for (let i = 0; i < main.instances.length; i++) {
            const instance = main.instances[i];
            const instanceObj = main.objects[instance];
                let isFound = false;
                const inst1 = instance.replace(/\.(\d+)$/, '.');
                for (let j = 0; j < addTabs.length; j++) {
                    const inst2 = addTabs[j].replace(/\.(\d+)$/, '.');
            const $this = $(this);
            const id = $this.attr('id');
        for (let a = 0; a < addTabs.length; a++) {
            const tab = main.objects[addTabs[a]];
            let name  = 'tab-' + tab.common.name;
            let link  = tab.common.adminTab.link || '/adapter/' + tab.common.name + '/tab.html';
            const parts = addTabs[a].split('.');
            let buttonName;
                let isReplace = false;
                    const div = '<div id="' + name + '" data-name="' + buttonName + '" class="tab-custom ' + (isReplace ? 'link-replace' : '') + '" data-adapter="' + parts[2] + '" data-instance="' + parts[3] + '" data-src="' + link + '">' +
                        let elem = $ (this).contents ().find('body>header');
                        if (!elem || !elem.length) {
                            elem = $(this).contents ().find('head');
                        }
                        if (elem && elem.length) {
                            elem.append('<link rel="stylesheet" type="text/css" href="../../lib/css/iob/selectID.css"/>');
                        }
            let countLink = 0;
            let loadTimeout = setTimeout(function() {
        const $selHosts = $('#host-adapters');
        const lines = [];
        let color;
        let curId;
        for (let i = 0; i < main.tabs.hosts.list.length; i++) {
        const $selBtn = $('#host-adapters-btn').show();
            const val = $(this).data('value');
            const id  = 'system.host.' + val + '.alive';
            const val = $(this).prop('checked');
            const $input = $(this).prev();
                    const major = parseInt(result['Node.js'].split('.').shift().replace('v', ''), 10);
                let obj;
                for (const id in main.objects) {
                for (const dialog in main.dialogs) {
        //const changed = false;
        const oldObj = main.objects[id];
        let action = 'update';
                let ignore = false;
                        const newObj = JSON.parse(JSON.stringify(obj));
        const ts = Date.now();

        main._timer = setTimeout(() => {
        for (const pattern in main.subscribesStates) {
        for (const pattern in main.subscribesObjects) {
            for (let s = 0; s < patterns.length; s++) {
            for (let s = 0; s < patterns.length; s++) {
                    console.debug('Unsubscribe: ' + patterns[s]);
            for (let s = 0; s < patterns.length; s++) {
            for (let s = 0; s < patterns.length; s++) {
        const parts = decodeURI(window.location.hash).split('/');
            const parts   = decodeURI(window.location.hash).split('/');
                const parts  = main.currentHash.split('/');
                let tab      = parts[0].replace(/^#/, '').replace(/^tab-/, '');
                const dialog = parts[1];
                const params = decodeURIComponent(parts[2]);
                const $adminBody = $('.admin-sidemenu-body');
                let $actualTab   = $adminBody.find('.admin-sidemenu-body-content');
                const $panel     = $('#tab-' + tab);
                    let link;
                        const $oldPanel = $('#tab-' + main.currentTab);
                            const $iframe_ = $oldPanel.find('>iframe');
                            const $iframe = $panel.find('>iframe');
                const  $tab = $adminSideMenu.find('.admin-sidemenu-items[data-tab="tab-' + tab + '"]');
        let icon;
        let alt;
        const isCommon = obj && obj.common;
                    let instance;
        let icon     = '';
        let alt      = '';
                const result = getIconHtml(obj);
        let icon     = '';
        let alt      = '';
            const result = getIconHtml(obj);
        const units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
        let u = -1;
        const r = parseInt(hex.slice(0, 2), 16),
    const tabsInfo = {
        'tab-node-red-2':       {order: 62,   icon: 'device_hub'},
        let lines = '';
        const elements = [];
            const id = $(this).attr('id');
            const id = $(this).attr('id');
                let icon;
                    const _id = 'system.adapter.' + id.substring(4);
		            if (main.objects[_id] && main.objects[_id].common.adminTab && main.objects[_id].common.adminTab['fa-icon']) {
        for (let e = 0; e < elements.length; e++) {
            let m = text.match(/^upload \[(\d+)]/);
                const max = $dialogCommand.data('max');
                const value = parseInt(m[1], 10);
            const $backButton = $adminSideMain.find('.button-command');
                let error = $dialogCommand.data('error');
                    const m = error.match(/error: (.*)$/);
                        const vendor = data.native.vendor;
                                for (let r = 0; r < vendor.admin.css.rules.length; r++) {
                        const pos = main.systemConfig.common.tabs.indexOf('tab-log');
                                            let language = (main.systemConfig.common.language || window.navigator.userLanguage || window.navigator.language || '').substring(0, 2);
                                            if (language !== 'en' && language !== 'de' && language !== 'ru') {
                                                language = 'en';
                                            }
                                                const $input = $(this).prev();
                                for (const t in tabs) {

        main.waitForRestart && location.reload();
        main.waitForRestart && location.reload();
    const that = this;
            const tmp = version.split ('.');
                    const node = data.node;
                    const $tdList = $(node.tr).find('>td');
                    const obj = that.data[node.key];
                        for (let c = 0; c < that.tree.length; c++) {
                                let installed = 0;
                                for (let k = 0; k < that.tree[c].children.length; k++) {
                                    if (that.data[that.tree[c].children[k].key].installed) {
                                        installed++;
                                    }
                                let title = '[<span title="' + _('Installed from group') + '">' + installed + '</span> / <span title="' + _('Total count in group') + '">' + that.tree[c].children.length + '</span>]';
                    const idx = obj.desc.indexOf('<div');
                    const desc = idx >= 0 ? obj.desc.substr(0, idx) : obj.desc;
                const $processAdapters = that.$tab.find('.process-adapters');
        let anyVisible = false;
                const $this = $(this);
                const data = {};
                const order = [];
                let url;
                for (let o = 0; o < order.length; o++) {
                    const user = that.urls[order[o]].match(/\.com\/([-_$§A-Za-z0-9]+)\/([-._$§A-Za-z0-9]+)\//);
                    const isCustom = !that.$installDialog.find('a[href="#tabs-install-github"]').hasClass('active');//!!that.$installDialog.find('#tabs-install').tabs('option', 'active');
                    let url;
                    let debug;
                    let adapter;
                        const parts = that.$installDialog.find('#install-github-link').val().split(' ');
                    const $input = $(this).prev();
            if (!that.data[node.key]) return false;
            let title = that.data[node.key].title;
            if (title && typeof title === 'object') {
            }
            let desc = that.data[node.key].desc;
               desc = desc[systemLang] || desc.en;
            if ((that.data[node.key].name     && that.data[node.key].name.toLowerCase().indexOf(that.currentFilter)     !== -1) ||
                (title                        && title.toLowerCase().indexOf(that.currentFilter)                        !== -1) ||
                (that.data[node.key].keywords && that.data[node.key].keywords.toLowerCase().indexOf(that.currentFilter) !== -1) ||
                (desc                         && desc.toLowerCase().indexOf(that.currentFilter)                         !== -1)){
            } else {
                return false;
            }
            this.main.socket.emit('sendToHost', host, 'getRepository', {repo: this.main.systemConfig.common.activeRepo, update: updateRepo}, _repository => {
                this.curRepository = _repository || {};

                if (this.curRepository && that.curInstalled && that.curRunning) {
                    this.curRepoLastUpdate = Date.now();
                    setTimeout(() => {
                        this.curRunning.forEach(cb =>
                            cb(this.curRepository, this.curInstalled));

                        this.curRunning = null;
            this.main.socket.emit('sendToHost', host, 'getInstalled', null, _installed => {
                this.curInstalled = _installed || {};

                if (this.curRepository && that.curInstalled) {
                    this.curRepoLastUpdate = Date.now();
                    setTimeout(() => {
                        this.curRunning.forEach(cb =>
                            cb(this.curRepository, this.curInstalled));

                        this.curRunning = null;
            setTimeout(() => {
                if (this.curRunning) {
                    this.curRunning.forEach(cb =>
                        cb(this.curRepository, this.curInstalled));

                    this.curRunning = null;
                callback && callback(this.curRepository, this.curInstalled);
        let text = '';
            for (const v in adapter.news) {
        let adapters;
            for (let a = 0; a < dependencies.length; a++) {
                for (const b in dependencies[a]) {
        for (const adapter in adapters) {
            //const d1 = that.data[c1.key], d2 = that.data[c1.key];
            const inst1 = c1.data.installed || 0, inst2 = c2.data.installed || 0;
            const ret = inst2 - inst1;
            let t1 = c1.titleLang || c1.title || '';
            let t2 = c2.titleLang || c2.title || '';
        const interval = now.getTime() - time.getTime();
        const days = Math.floor(interval / (24 * 3600000));
            const t  = days % 10;
            const tt = days % 100;
                let obj;
                let version;
                let rawVersion;
                let adapter;
                let adaptersToUpdate = 0;

                const listInstalled = [];
                const listNonInstalled = [];
                const nowObj = new Date();
                const localTexts = {
                    //const span = getVersionSpan(version);
                    const color = getVersionClass(version);
                    const title = color + '\n\r' + (news || '');
                for (const i = 0; i < listInstalled.length; i++) {
                    let installed = '';
                    let rawInstalled = '';
                    let icon = obj.icon;
                    let _instances = 0;
                    let _enabled   = 0;
                        let news = '';
                        let updatable = false;
                        let updatableError = '';

                        for (const z = 0; z < that.main.instances.length; z++) {
                    const group = (obj.type || that.types[adapter] || 'common adapters') + '_group';
                    let desc  = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc;
                    let title = obj.titleLang || obj.title;
                        let iGroup = -1;
                        for (const jj = 0; jj < that.tree.length; jj++) {
                    for (let i = 0; i < listNonInstalled.length; i++) {
                        const group = (obj.type || that.types[adapter] || 'common adapters') + '_group';
                        let desc = (typeof obj.desc === 'object') ? (obj.desc[systemLang] || obj.desc.en) : obj.desc;
                        let title = obj.titleLang || obj.title;
                            let igroup = -1;
                            for (const j = 0; j < that.tree.length; j++){
                    const akeys = Object.keys(that.data);
                    const newData = {};
                    for (let u = 0; u < akeys.length; u++) {
                    let text = '';
                    const types = [];
                    for (const a in that.data) {
                        const ad = that.data[a];
                    for (const b in that.data) {
                    let tTypes = '<li class="main-toolbar-table-types-item" data-type=""><a>' + localTexts['all'] + '</a></li>\n';
                    for (let g = 0; g < types.length; g++) {
                    const $types = that.$tab.find('#main-toolbar-table-types');
                            const text = '<div class="icon-large" style="' +
                            const $big = $(text);
                            const h = parseFloat($big.height());
                            let top = Math.round($(this).position().top - ((h - parseFloat($(this).height())) / 2));
                            const big = $(this).data('big');
                    const classes = [
                        const i = 0;
                let adaptersToUpdate = 0;
                for (const adapter in installedList) {
                    const obj = installedList ? installedList[adapter] : null;
                    let version = '';
                    if (repository[adapter] && repository[adapter].version) {
                        version = repository[adapter].version;
                    }
            const $updates = $('#updates-for-adapters');
        const $dialogAddInstance = $('#dialog-add-instance');
        let text = '';
        for (const h = 0; h < that.main.tabs.hosts.list.length; h++) {
            const host = that.main.tabs.hosts.list[h];
        let min = -1;
        const used = [];
        for (const i = 0; i < that.main.tabs.instances.list.length; i++) {
            const parts = that.main.tabs.instances.list[i].split('.');
                const index = parseInt(parts[parts.length - 1], 10);
        for (const m = 0; m < min; m++) {
        const $dialogLicense = $('#dialog-license');
        let timeout = setTimeout(function () {
            const adapter = $(this).attr('data-adapter-name');
            const desc = $(this).attr('data-adapter-desc');
                    let obj = repo[adapter];
                    obj = obj || installed[adapter];
                    if (!obj) {
                        return;
                    }
                        showLicenseDialog(adapter, isAgree =>
                            isAgree && that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, exitCode =>
                                !exitCode && that._postInit(true)));
                        that.main.cmdExec(null, 'add ' + adapter + ' ' + index + ' --host ' + host, exitCode =>
                            !exitCode && that._postInit(true));
            const name = $(this).attr('data-adapter-name');
            const aName = $(this).attr('data-adapter-name');
            const aName = $(this).attr('data-adapter-name');
        const $button = this.$tab.find('.adapter-update-custom-submit[data-adapter-name="' + adapter + '"]');
            const versions = [];
                const news = that.main.objects['system.adapter.' + adapter].common.news;
                for (const id in news) {
            const menu = '<div class="collection">';
            for (const v = 0; v < versions.length; v++) {
                const nnews = (news[versions[v]] ? news[versions[v]][systemLang] || news[versions[v]].en : '');
            const $adaptersMenu = $('#adapters-menu');
                const adapter = $(this).data('adapter-name');
                const version = $(this).data('version');
                    const $adaptersMenu = $('#adapters-menu');
                    const trigger = $adaptersMenu.data('trigger');
                const j = this.list.indexOf(id);
        let text = '';
        let opened;
            const adapter = id.match(/^system\.adapter\.([\w\d-]+)\.upload$/);
                const $adapter = this.$tab.find('.adapter-upload-progress[data-adapter-name="' + adapter[1] + '"]');
                const text = showUploadProgress(state.val);
            box-sizing: border-box;
        input:focus {
            outline: none;
        }
        input:focus ~ label,
        input.used ~ label {
        input:focus ~ .bar:before,
        input:focus ~ .bar:after {
            width: 50%;
        }
        .button:focus {
            outline: 0;
        }
        .buttonBlue:hover {
            background: #39a1f4;
        }
        <input type="text" name="username" id="username" class="used"><span class="highlight"></span><span class="bar"></span>
        <input type="password" name="password" id="password" class="used"><span class="highlight"></span><span class="bar"></span>
        /*for (var i = 0; i < inputs.length; i++) {
        }*/