<|start_filename|>src/js/treeTable.js<|end_filename|>
(function ($) {
    'use strict';

    if ($.fn.treeTable) return;

    function nodeExpand() {
        var id = this.id;
        var $table = this.row.parent().parent(); // table > tbody > tr
        var options = $table.data('options');

        if (options.name) {
            options.expanded = options.expanded || [];
            if (options.expanded.indexOf(id) === -1) {
                options.expanded.push(id);
                if (typeof Storage !== 'undefined') {
                    window.localStorage.setItem(options.name + '-treetable', JSON.stringify(options.expanded));
                }
            }
        }

        var parentNode = $table.treetable('node', id);
    }

    function nodeCollapse() {
        var id = this.id;
        var $table = this.row.parent().parent(); // table > tbody > tr
        var options = $table.data('options');

        if (options.name && options.expanded) {
            var pos = options.expanded.indexOf(id);
            if (pos !== -1) {
                options.expanded.splice(pos, 1);
                if (typeof Storage !== 'undefined') {
                    window.localStorage.setItem(options.name + '-treetable', JSON.stringify(options.expanded));
                }
            }
        }

        var parentNode = $table.treetable('node', id);
    }

    function filter($table, word) {
        if (word) {
            word = word.toLowerCase();
            var options = $table.data('options');
            $table.find('tr').each(function () {
                if ($(this).hasClass('tree-table-main-header')) return;

                if (!$(this).data('tt-branch') && $(this).find('td:first-child').text().indexOf(word) === -1) {
                    $(this).addClass('filtered-out');
                } else {
                    $(this).removeClass('filtered-out');
                }
            });
            var branch = '';
            var isOneVisible = false;
            // hide branches without children
            $table.find('tr').each(function () {
                if ($(this).data('tt-branch')) {
                    if (branch) {
                        if (!isOneVisible) {
                            $table.find('tr[data-tt-id="' + branch + '"]').addClass('filtered-out');
                        } else {
                            $table.find('tr[data-tt-id="' + branch + '"]').removeClass('filtered-out');
                        }
                    }
                    isOneVisible = false;
                    branch = $(this).data('tt-id');
                } else if (branch) {
                    if (!$(this).hasClass('filtered-out')) isOneVisible = true;
                }
            });
            if (branch) {
                if (!isOneVisible) {
                    $table.find('tr[data-tt-id="' + branch + '"]').addClass('filtered-out');
                } else {
                    $table.find('tr[data-tt-id="' + branch + '"]').removeClass('filtered-out');
                }
            }
        } else {
            $table.find('tr').removeClass('filtered-out');
        }
    }

    function processMoveTasks(options, tasks, callback) {
        if (!tasks || !tasks.length) {
            callback && callback();
            return;
        }
        var task = tasks.shift();

        options.moveId && options.moveId(task.oldId, task.newId, function (err) {
            setTimeout(function () {
                processMoveTasks(options, tasks, callback);
            }, 50);
        });
    }

    function buildList(options, noButtons) {
        var table = noButtons ? '' : '<div class="treetablelist-buttons"><button class="treetable-list-btn-ok"></button><button class="treetable-list-btn-cancel"></button></div>';
        table += '<ul class="treetable-list">';
        var rows = options.rows;
        for (var i = 0; i < rows.length; i++) {
            var parents = 0;
            var current = rows[i];
            while (current.parent) {
                var found = false;
                for (var j = 0; j < rows.length; j++) {
                    if (rows[j].id === current.parent) {
                        current = rows[j];
                        found = true;
                        break;
                    }
                }
                if (!found) break;
                parents++;
            }
            var title = rows[i].title;
            if (typeof title === 'object') {
                title = title[systemLang] || title.en;
            }
            var isNotFolder = rows[i].instance === undefined ? 0 : 1;
            table += '<li data-id="' + rows[i].id + '" class="' + (!isNotFolder ? 'treetable-list-folder' : 'treetable-list-item') + '" style="margin-left: ' + (parents * 19) + 'px; width: calc(100% - ' + (parents * 15 + 2 + isNotFolder * 7) + 'px);' +
            (rows[i].id === 'script.js.global' ? 'color: rgb(0, 128, 0);' : '') + '">' +
            (!isNotFolder ? '<span class="fancytree-expander"></span>' : '') + '<span class="fancytree-icon"></span>' + title + '</li>';
        }
        table += '</ul>';
        var $dlg = $(this);
        var $table = $(table);

        $dlg.find('.treetablelist-buttons').remove();
        $dlg.find('.treetable-list').remove();
        $dlg.find('.tree-table-buttons').remove();
        $dlg.find('.tree-table-main').remove();
        $dlg.prepend($table);

        var $buttons = $($table).find('.treetablelist-buttons');
        var $list    = $($table).find('.treetable-list');

        $list.sortable({
            cancel: '.treetable-list-folder',
            axis:   'y'
        }).data('options', options);

        var that = this;

        $buttons.find('.treetable-list-btn-ok').button({
            icons: {primary: 'ui-icon-check'},
            text: false
        })
        .css({width: 24, height: 24})
        .on('click', function () {
            // analyse new structure
            var currentFolder = '';
            var tasks = [];
            $list.find('li').each(function () {
                var id = $(this).data('id');
                if ($(this).hasClass('treetable-list-folder')) {
                    currentFolder = id;
                } else {
                    var parts = id.split('.');
                    var name = parts.pop();
                    if (parts.join('.') !== currentFolder) {
                        tasks.push({oldId: id, newId: currentFolder + '.' + name});
                    }
                }
            });
            processMoveTasks(options, tasks, function () {
                buildTable.call(that, options);
            });
        });
        $buttons.find('.treetable-list-btn-cancel').button({
            icons: {primary: 'ui-icon-cancel'},
            text: false
        })
        .css({width: 24, height: 24})
        .on('click', function () {
            buildTable.call(that, options);
        });
    }

    function getIconFromObj(obj, imgPath, classes) {
        var icon     = '';
        var alt      = '';
        var isCommon = obj && obj.common;

        if (isCommon) {
            if (isCommon.icon) {
                if (!isCommon.icon.match(/^data:image\//)) {
                    if (isCommon.icon.indexOf('.') !== -1) {
                        var instance;
                        if (obj.type === 'instance') {
                            icon = '/adapter/' + obj.common.name + '/' + obj.common.icon;
                        } else if (obj._id.match(/^system\.adapter\./)) {
                            instance = obj._id.split('.', 3);
                            if (obj.common.icon[0] === '/') {
                                instance[2] += obj.common.icon;
                            } else {
                                instance[2] += '/' + obj.common.icon;
                            }
                            icon = '/adapter/' + instance[2];
                        } else {
                            instance = obj._id.split('.', 2);
                            if (obj.common.icon[0] === '/') {
                                instance[0] += obj.common.icon;
                            } else {
                                instance[0] += '/' + obj.common.icon;
                            }
                            icon = '/adapter/' + instance[0];
                        }
                    } else {
                        return '<i class="material-icons ' + (classes || 'treetable-icon') + '">' + isCommon.icon + '</i>';
                    }

                } else {
                    icon = isCommon.icon;
                }
                alt = obj.type;
            } else {
                imgPath = imgPath || 'lib/css/fancytree/';
                if (obj.type === 'device') {
                    icon = imgPath + 'device.png';
                    alt  = 'device';
                } else if (obj.type === 'channel') {
                    icon = imgPath + 'channel.png';
                    alt  = 'channel';
                } else if (obj.type === 'state') {
                    icon = imgPath + 'state.png';
                    alt  = 'state';
                }
            }
        }

        if (icon) return '<img class="' + (classes || 'treetable-icon') + '" src="' + icon + '" alt="' + alt + '" />';
        return '';
    }

    // https://stackoverflow.com/questions/35969656/how-can-i-generate-the-opposite-color-according-to-current-color
    /*function invertColor(hex) {
        if (hex.indexOf('#') === 0) {
            hex = hex.slice(1);
        }
        // convert 3-digit hex to 6-digits.
        if (hex.length === 3) {
            hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
        }
        if (hex.length !== 6) {
            return false;
        }
        var r = parseInt(hex.slice(0, 2), 16),
            g = parseInt(hex.slice(2, 4), 16),
            b = parseInt(hex.slice(4, 6), 16);
        // http://stackoverflow.com/a/3943023/112731
        return (r * 0.299 + g * 0.587 + b * 0.114) <= 186;
    }*/
    function getUsersGroups(objects, groups) {
        var usersGroups = {};
        for (var g = 0; g < groups.length; g++) {
            if (objects[groups[g]] && objects[groups[g]].common && objects[groups[g]].common.members) {
                var users = objects[groups[g]].common.members;
                for (var u = 0; u < users.length; u++) {
                    usersGroups[users[u]] = usersGroups[users[u]] || [];
                    var name = objects[groups[g]].common.name;
                    if (name && typeof name === 'object') {
                        name = name[systemLang] || 'en';
                    }
                    usersGroups[users[u]].push({id: groups[g], name: name || id.replace('system.group.', '')});
                }
            }
        }
        return usersGroups;
    }

    function buildTable(options) {
        var table = '';
        // detect materialize
        var buttonTag = window.M && window.M.toast ? 'a' : 'button';
        if (options.panelButtons) {
            table += '<div class="row tree-table-buttons m">';
            for (var z = 0; z < options.panelButtons.length; z++) {
                table += '<' + buttonTag + ' class="btn-floating waves-effect waves-light blue btn-custom-' + z + '" title="' + (options.panelButtons[z].title || '') + '" ' + (options.panelButtons[z].id ? 'id="' + options.panelButtons[z].id + '"' : '') + '>';
                // detect materialize
                if (window.M && window.M.toast) {
                    table += '<i class="material-icons">' + (options.panelButtons[z].icon || '') + '</i>';
                }
                table += '</' + buttonTag + '>';
            }
            if (options.moveId) {
                table += '<' + buttonTag + ' class="btn-floating waves-effect waves-light blue treetable-sort" title="' + _('reorder') + '">';
                // detect materialize
                if (window.M && window.M.toast) {
                    table += '<i class="material-icons">import_export</i>';
                }
                table += '</' + buttonTag + '>';
            }
            table += '</div>';
        }

        // build header
        table += '<div class="row tree-table-body"><table class="tree-table-main">';
        table += '  <thead>';
        table += '      <tr class="tree-table-main-header">';
        for (var ch = 0; ch < options.columns.length; ch++) {
            if (options.columns[ch] === 'name') {
                table += '      <th' + (options.widths && options.widths[ch] ? ' class="treetable-th-name" style="width: ' + options.widths[ch] + '"' : '') + '>';
                table += '          <input placeholder="' + _('name') + '" class="filter_name treetable-filter" />';
                table += '          <button data-id="filter_name" role="button" class="filter-clear"></button>';
                table += '      </th>';
            } else {
                table += '      <th' + (options.widths && options.widths[ch] ? ' style="width: ' + options.widths[ch] + '"' : '') + '>' + _(options.columns[ch]) + '</th>';
            }
        }
        if (options.buttons) {
            table += '      <th' + (options.buttonsWidth ? ' style="width: ' + options.buttonsWidth + '"' : '') + '></th>';
        }
        table += '  </tr>';
        table += '</thead>';
        // build body
        table += '<tbody>';

        // <tr data-tt-id="system.adapter.0" data-tt-branch='true'>
        var rows        = [];
        var rootEx      = options.root ? new RegExp('^' + options.root.replace(/\./g, '\\.') + '\\.') : null;
        var instances   = options.columns.indexOf('instance') !== -1 ? [] : null;

        for (var id in options.objects) {
            if (!options.objects.hasOwnProperty(id)) continue;
            var m;
            if (instances && options.objects[id].type === 'instance' &&
                (m = id.match(/^system\.adapter\.javascript\.(\d+)$/))) {
                instances.push(m[1]);
            }

            if (rootEx && !rootEx.test(id)) continue;
            var common = options.objects[id].common;

            var obj = {
                id:     id,
                parent: null,
                _class: 'treetable-' + options.objects[id].type
            };

            if (options.objects[id].type === 'channel') {
                obj.folder = true;
            }
            for (var cb = 0; cb < options.columns.length; cb++) {
                if (options.columns[cb] === 'instance') {
                    if (options.objects[id].type === 'script') {
                        obj.instance = common ? common.engine.split('.').pop() : 0;
                    }
                } else if (common && options.columns[cb] !== 'id') {
                    var val = common[options.columns[cb]];
                    if (val !== undefined) {
                        obj[options.columns[cb]] = val;
                    }
                }
            }
            if (options.members) {
                obj.members = common.members;
            }
            if (options.colors) {
                obj.color = common.color;
            }

            rows.push(obj);
        }

        // sort
        rows.sort(function (a, b) {
            if (a.id > b.id) return 1;
            if (a.id < b.id) return -1;
            return 0;
        });

        // find parents and extend members
        for (var pp = 0; pp < rows.length; pp++) {
            // find parent:
            var parts  = rows[pp].id.split('.');
            var title  = parts.pop();
            var parent = parts.join('.');
            rows[pp].title = title;
            for (var p = 0; p < rows.length; p++) {
                if (rows[p].id === parent) {
                    rows[pp].parent = parent;
                    rows[p].children = rows[p].children || [];
                    rows[p].children.push(pp);
                    break;
                }
            }

            if (parts.length === 1) {
                rows[pp]._class += ' treetable-root'
            }

        }
        for (var ppp = 0; ppp < rows.length; ppp++) {
            rows[ppp].realChildren = rows[ppp].children ? !!rows[ppp].children.length : false;
        }

        if (options.groups) {
            var users = getUsersGroups(options.objects, options.groups);

            for (var g = rows.length - 1; g >= 0; g--) {
                rows[g].groups = users[rows[g].id] || [];
            }
        }

        if (options.members) {
            for (var k = rows.length - 1; k >= 0; k--) {
                // extend members
                if (rows[k].members) {
                    rows[k].children = rows[k].children || [];
                    var members = Object.assign([], rows[k].members);
                    members.sort();
                    for (var mm = 0; mm < members.length; mm++) {
                        obj = {
                            id:     members[mm],
                            title:  members[mm],
                            parent: rows[k].id,
                            _class: 'treetable-member'
                        };
                        rows[k].children.push(members[mm]);

                        if (options.objects[obj.id]) {
                            var ccommon = options.objects[obj.id].common;
                            if (ccommon) {
                                for (var ccb = 0; ccb < options.columns.length; ccb++) {
                                    var attr = options.columns[ccb];
                                    if (attr === 'members' || attr === 'id' || attr === 'title' || attr === 'icon') continue;
                                    var vval = ccommon[options.columns[ccb]];
                                    if (vval !== undefined) {
                                        obj[attr] = vval;
                                    }
                                }
                                if (options.colors) {
                                    obj.color = common.color;
                                }
                            }
                        }
                        rows.splice(k + 1, 0, obj);
                    }
                    delete rows[k].members;
                }
            }
        }

        var instSelect = '';

        for (var i = 0; i < rows.length; i++) {
            // title
            table += '<tr data-tt-id="' + rows[i].id + '"' + (!!rows[i].children ? ' data-tt-branch="true"' : '') + (rows[i].parent ? ' data-tt-parent-id="' + rows[i].parent + '"' : '') + ' class="';
            if (rows[i]._class) {
                table += rows[i]._class + ' ';
            }
            if (options.draggable) {
                table += options.draggable + ' ';
            }
            if (rows[i].children && rows[i].children.length) {
                table += 'not-empty';
            } else {
                table += 'is-empty';
            }

            table += '">';
            for (var c = 0; c < options.columns.length; c++) {
                var aattr = options.columns[c];
                var style = '';
                var _class = (options.classes && options.classes[c]) || '';

                if (aattr === 'groups') {
                    _class += ' m';
                }

                if (!c && rows[i].hasOwnProperty('children')) {
                    _class += ' treetable-folder fancytree-exp-c fancytree-has-children fancytree-ico-cf';
                    if (rows[i].id === 'script.js.global') {
                        style += ' color: rgb(0, 128, 0);'; // green
                    } else {
                        style += ' color: rgb(0, 0, 128);'
                    }
                    table += '<td style="' + style + '" class="' + _class + '">';
                    if (rows[i].children && rows[i].children.length) {
                        table += '<span class="treetable-counter">' + rows[i].children.length + '</span>';
                    }
                } else {
                    table += '<td style="' + style + '" class="' + _class + '">';
                }
                if (!c && options.colors && rows[i].color) {
                    table += '<div style="background: ' + rows[i].color + '" class="treetable-color"></div>';
                }
                if (!c && options.icons) {
                    table += getIconFromObj(options.objects[rows[i].id], options.imgPath) || '<div class="treetable-icon-empty">&nbsp;</div>';
                }
                if (aattr === 'enabled') {
                    table += '<input data-attr="' + aattr + '" data-id="' + rows[i].id + '" class="treetable-input" type="checkbox" ' + (rows[i][aattr] ? 'checked' : '') + ' ' + (options.readOnly && options.readOnly[c] !== false ? 'disabled' : '') + '>';
                } else
                if (aattr === 'groups') {
                    for (var gg = 0; gg < rows[i].groups.length; gg++) {
                        var gId = rows[i].groups[gg].id;
                        table += '<div class="chip">' + getIconFromObj(options.objects[gId], null, '') + rows[i].groups[gg].name + '</div>'; //<i class="close material-icons" data-group="' + rows[i].groups[gg].id + '" data-user="' + rows[i].id + '">close</i></div>';
                    }
                } else
                // edit instance
                if (aattr === 'instance') {
                    if (rows[i].instance !== undefined && instances.length > 1) {
                        instSelect = '<select class="treetable-instance" data-id="' + rows[i].id + '">';
                        for (var ii = 0; ii < instances.length; ii++) {
                            instSelect += '<option value="' + instances[ii] + '" ' + (instances[ii] === rows[i].instance ? 'selected' : '') + '>' + instances[ii] + '</option>';
                        }
                        instSelect += '</select>';

                        table += instSelect;
                    } else {
                        table += '<span>' + (rows[i].instance === undefined ? '' : rows[i].instance) + '</span>';
                    }
                } else if (aattr === 'icon') {
                    table += getIconFromObj(options.objects[rows[i].id], options.imgPath);
                } else {
                    var vall = rows[i][aattr] || '';
                    if (vall && typeof vall === 'object' && vall.en) {
                        vall = vall[systemLang] || vall.en;
                    }

                    table += '<span>' + vall + '</span>';
                }
                table += '</td>';
            }
            // add buttons
            if (options.buttons) {
                table += '<td class="treetable-buttons" style="' + (options.buttonsStyle || '') + '">';
                var text = '';
                for (var jj = 0; jj < options.buttons.length; jj++) {
                    if (options.buttons[jj].match && !options.buttons[jj].match(rows[i].id, rows[i].parent)) {
                        text += '<div class="treetable-button-empty">&nbsp;</div>';
                    } else {
                        text += '<' + buttonTag + ' data-id="' + rows[i].id + '" class="select-button-' + jj + ' select-button-custom td-button"  style="margin-right: 3px;'+ '" data-parent="' + rows[i].parent + '" data-children="' + !!rows[i].realChildren + '" title="' + (options.buttons[jj].title || '') + '">';
                        // detect materialize
                        if (window.M && window.M.toast) {
                            text += '<i class="material-icons">' + (options.buttons[jj].icon || '') + '</i>';
                        }
                        text += '</' + buttonTag + '>';
                    }
                }

                table += text + '</td>';
            }

            table += '</tr>';
        }
        table += '</body>';

        var $dlg = $(this);
        var $table = $(table);

        $dlg.find('span:first-child').remove();
        $dlg.find('.treetablelist-buttons').remove();
        $dlg.find('.treetable-list').remove();
        $dlg.find('.tree-table-buttons').remove();
        $dlg.find('.tree-table-main').remove();

        $dlg.prepend($table);
        options.rows = rows;
        var $treeTable = $($table[1]).find('>table');
        var $buttons   = $($table[0]);

        $treeTable.data('options', options);

        $treeTable.treetable({
            expandable:         true,
            clickableNodeNames: true,
            expanderTemplate:   '',
            indenterTemplate:   '<span class="fancytree-expander"></span><span class="fancytree-icon"></span>',
            onNodeExpand:       nodeExpand,
            onNodeCollapse:     nodeCollapse,
            stringCollapse:     _('collapse'),
            stringExpand:       _('expand')
        });

        var $tbody = $treeTable.find('tbody');

        $tbody.on('click', 'tr', function() {
            $('.selected').not(this).removeClass('selected');
            $(this).addClass('selected');
            var $table = $(this).parent().parent();
            var options = $table.data('options');
            var id = $(this).data('tt-id');
            options.onChange && options.onChange($(this).data('tt-id'), options.oldId);
            options.oldId = id;
        });

        if (options.buttons) {
            for (var b = 0; b < options.buttons.length; b++) {
                var $btn = $tbody.find('.select-button-' + b).button(options.buttons[b]).on('click', function () {
                    var cb = $(this).data('callback');
                    if (cb) {
                        cb.call($(this), $(this).data('id'), $(this).data('children'), $(this).data('parent'));
                    }
                }).data('callback', options.buttons[b].click).attr('title', options.buttons[b].title || '');

                if ($btn.length === 0) continue;
                if (options.buttons[b].width)  $btn.css({width:  options.buttons[b].width});
                if (options.buttons[b].height) $btn.css({height: options.buttons[b].height});
                /*if (options.buttons[b].match) {
                    $btn.each(function () {
                        options.buttons[b].match.call($(this), $(this).data('id'));
                    });
                }*/
            }
        }

        if (options.panelButtons) {
            for (var zz = 0; zz < options.panelButtons.length; zz++) {
                var $zz = $buttons.find('.btn-custom-' + zz);
                $zz
                    .on('click', options.panelButtons[zz].click)
                    .attr('title', options.panelButtons[zz].title || '');

                // detect materialize
                if (!window.M || !window.M.toast) {
                    $zz
                        .button(options.panelButtons[zz])
                        .css({width: 24, height: 24})
                }
            }
        }

        $treeTable.find('.filter_name').on('change', function () {
            var timer = $(this).data('timer');
            if (timer) {
                clearTimeout(timer);
            }
            var $this = $(this);
            $this.data('timer', setTimeout(function () {
                $this.data('timer', null);
                var val = $table.find('.filter_name').val();
                if (val) {
                    $this.addClass('input-not-empty');
                } else {
                    $this.removeClass('input-not-empty');
                }
                filter($($table[1]), $table.find('.filter_name').val());
            }));
        }).on('keyup', function () {
            $(this).trigger('change');
        });
        $treeTable.find('.filter-clear')
            .button({icons: {primary: 'ui-icon-close'}, text: false})
            .on('click', function () {
                var name = $(this).data('id');
                $table.find('.' + name).val('').trigger('change');
            });
        var that = this;
        $buttons.find('.treetable-sort')
            .button({icons: {primary: 'ui-icon-arrowthick-2-n-s'}, text: false})
            .css({width: 24, height: 24})
            .on('click', function () {
                buildList.call(that, options);
            });

        if (options.onEdit) {
            $treeTable.find('.treetable-instance').on('change', function () {
                options.onEdit($(this).data('id'), 'instance', $(this).val());
            });

            $treeTable.find('.treetable-input').on('change', function (e) {
                e.stopPropagation();
                var val;
                if ($(this).attr('type') === 'checkbox') {
                    val = $(this).prop('checked');
                } else {
                    val = $(this).val();
                }
                var id = $(this).data('id');
                if (options.onEdit) {
                    if (options.onEdit(id, $(this).data('attr'), val) === false) {
                        // todo support more types
                        $(this).prop('checked', true);
                    }
                }
            }).on('keyup', function () {
                $(this).trigger('change');
            });
        } else {
            $treeTable.find('.treetable-instance').prop('disabled', true)
        }
        if (typeof options.onReady === 'function') {
            options.onReady($treeTable);
        }
    }

    function reInit() {
        var $table = $(this).find('.tree-table-main');
        // remember selected
        var id = $table.find('.selected').data('tt-id');
        var expanded = $table.find('.expanded');
        var exIDs = [];
        expanded.each(function (el) {
            exIDs.push($(this).data('tt-id'));
        });
        var nameFilter = $table.find('.filter_name').val();
        var options = $table.data('options');
        buildTable.call(this, options);
        $table = $(this).find('.tree-table-main');
        for (var e = 0; e < exIDs.length; e++) {
			try {
                $table.treetable('expandNode', exIDs[e]);
			} catch (e) {
			}
        }
        if (id) {
            var node = $table.treetable('node', id);
            node && $table.treetable('reveal', id);
            node && node.row && node.row.addClass('selected');
        }
        if (nameFilter) $table.find('.filter_name').val(nameFilter).trigger('change');
    }

    var methods = {
        init: function (options) {
            options.imgPath = options.imgPath || 'lib/css/fancytree/';
            for (var i = 0; i < this.length; i++) {
                buildTable.call(this[i], options);

                if (typeof Storage !== 'undefined') {
                    var exIDs = window.localStorage.getItem(options.name + '-treetable');
                    if (exIDs) {
                        exIDs = JSON.parse(exIDs);
                        var $table = $(this[i]).find('.tree-table-main');
                        for (var e = 0; e < exIDs.length; e++) {
							try {
								$table.treetable('expandNode', exIDs[e]);
							} catch (e) {
							}
                        }
                    }
                }
            }
            return this;
        },
        destroy: function () {
            for (var i = 0; i < this.length; i++) {
                var $table = $(this[i]).find('.tree-table-main');
                if ($table.length && $table.data('options')) {
                    $table.data('options', null);
                    $(this[i]).html('');
                }
            }
        },
        expand: function (id) {
            for (var i = 0; i < this.length; i++) {
                var $table = $(this[i]).find('.tree-table-main');
                try {
                    if (id) {
                        $table.treetable('expandNode', id);
                    } else {
                        $table.treetable('expandAll', id);
                    }
                } catch (e) {
                }
            }
        },
        collapse: function (id) {
            for (var i = 0; i < this.length; i++) {
                var $table = $(this[i]).find('.tree-table-main');
                try {
                    if (id) {
                        $table.treetable('collapseNode', id);
                    } else {
                        $table.treetable('collapseAll');
                    }

                } catch (e) {
                }
            }
        },
        show: function (currentId, filter, onSuccess) {
            if (typeof filter === 'function') {
                onSuccess = filter;
                filter = undefined;
            }
            if (typeof currentId === 'function') {
                onSuccess = currentId;
                currentId = undefined;
            }
            for (var i = 0; i < this.length; i++) {
                var $table = $(this[i]).find('.tree-table-main');
                $table.find('.selected').removeClass('selected');
				try {
					$table.treetable('reveal', currentId);
				} catch (e) {
				}
                var node = $table.treetable('node', currentId);
                node && node.row.addClass('selected');
            }
            onSuccess && onSuccess();
            return this;
        },
        reinit: function () {
            for (var i = 0; i < this.length; i++) {
                reInit.call(this[i]);
            }
            return this;
        },
        'object': function (id, obj) {
            for (var i = 0; i < this.length; i++) {
                var $table = $(this[i]).find('.tree-table-main');
                if ($table.updateTimer) {
                    clearTimeout($table.updateTimer);
                }
                var options = $table.data('options');
                if (options && options.root && !id.match('^' + options.root.replace(/\./g, '\\.') + '\\.')) continue;

                var elem = this[i];
				// do not update too often. de-bounce it
                (function (_elem, _$table) {
                    _$table.updateTimer = setTimeout(function () {
                        reInit.call(_elem);
                    }, 300);
                })(elem, $table);
            }
            return this;
        }
    };

    $.fn.treeTable = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method "' +  method + '" not found in jQuery.treeTable');
        }
    };
})(jQuery);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigTimePicker.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import ConfigGeneric from './ConfigGeneric';
import { KeyboardTimePicker } from '@material-ui/pickers';

const styles = theme => ({
    indeterminate: {
        opacity: 0.5
    }
});

class ConfigTimePicker extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
        this.setState({ value });
    }

    renderItem(error, disabled, defaultValue) {
        return <KeyboardTimePicker
            fullWidth
            margin="normal"
            format="HH:mm:ss"
            error={!!error}
            disabled={!!disabled}
            value={this.state.value === null || this.state.value === undefined ? new Date() : this.state.value}
            onChange={value => {
                this.setState({ value }, () =>
                    this.onChange(this.props.attr, value));
            }}
            InputLabelProps={{
                shrink: true,
            }}
            placeholder={this.getText(this.props.schema.placeholder)}
            label={this.getText(this.props.schema.label)}
            helperText={this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}
        />
    }
}

ConfigTimePicker.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigTimePicker);

<|start_filename|>tmp/ws.js<|end_filename|>
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
/* jshint -W061 */
'use strict';

const SocketIO = require('../lib/ws');

const fs = require('fs');
const http = require('http');

const server = http.createServer(function (req, res) {
    if (req.url.endsWith('socket.io.js')) {
        res.setHeader('Content-type', 'text/javascript');
        res.write(fs.readFileSync(__dirname + '/../src-rx/public/lib/js/socket.io.js'));
        res.end();
    } else {
        res.setHeader('Content-type', 'text/html');
        res.write(fs.readFileSync(__dirname + '/public/index.html'));
        res.end();
    }
});

server.listen(8080, () => console.log('Started on 8080'));
const wsServer = new SocketIO(server);
wsServer.on('connect', socket => {
    console.log('connected');
    socket.on('disconnect', () => console.log('disconnected'));
    socket.on('withCallback', (data, cb) => {
        console.log(data);
        cb(data + 1);
    });
});
wsServer.on('error', error => console.log(error));



<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigObjectId.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import { Button, TextField } from '@material-ui/core';

import DialogSelectID from '@iobroker/adapter-react/Dialogs/SelectID';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    },
    flex: {
        display: 'flex'
    },
    button: {
        height: 48,
        marginLeft: 4,
        minWidth: 48,
    }
});

class ConfigObjectId extends ConfigGeneric {
    async componentDidMount() {
        super.componentDidMount();
        const { data, attr } = this.props;
        const value = ConfigGeneric.getValue(data, attr) || '';
        this.setState({ value, initialized: true});
    }

    renderItem(error, disabled, defaultValue) {
        if (!this.state.initialized) {
            return null;
        }
        const { classes, schema, socket, attr } = this.props;
        const { value, showSelectId } = this.state;

        return <FormControl className={classes.fullWidth}>
            <InputLabel shrink>{this.getText(schema.label)}</InputLabel>
            <div className={classes.flex}>
                <TextField
                    fullWidth
                    value={value}
                    error={!!error}
                    disabled={disabled}
                    placeholder={this.getText(schema.placeholder)}
                    label={this.getText(schema.label)}
                    helperText={this.renderHelp(schema.help, schema.helpLink, schema.noTranslation)}
                    onChange={e => {
                        const value = e.target.value;
                        this.setState({ value }, () =>
                            this.onChange(attr, value))
                    }}
                />
                <Button
                    className={this.props.classes.button}
                    size="small"
                    variant="outlined"
                    onClick={() => this.setState({ showSelectId: true })}
                >...</Button>
            </div>
            {showSelectId ? <DialogSelectID
                imagePrefix="../.."
                dateFormat={this.props.dateFormat}
                isFloatComma={this.props.isFloatComma}
                dialogName={'admin.' + this.props.adapterName}
                themeType={this.props.themeType}
                socket={socket}
                statesOnly={schema.all === undefined ? true : schema.all}
                selected={value}
                onClose={() => this.setState({ showSelectId: false })}
                onOk={value =>
                    this.setState({ showSelectId: false, value }, () =>
                        this.onChange(attr, value))}
            /> : null}
        </FormControl>;
    }
}

ConfigObjectId.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
    dateFormat: PropTypes.string,
    isFloatComma: PropTypes.bool,
};

export default withStyles(styles)(ConfigObjectId);

<|start_filename|>src-rx/src/components/Wizard/WizardPortForwarding.js<|end_filename|>
import { createRef, Component } from 'react';
import {withStyles} from '@material-ui/core/styles';
import withWidth from "@material-ui/core/withWidth";
import PropTypes from 'prop-types';
import Grid from '@material-ui/core/Grid';
import Toolbar from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Paper from  '@material-ui/core/Paper';

import IconCloud from '@material-ui/icons/Public';
import IconCloudPro from '@material-ui/icons/Language';
import IconCheck from "@material-ui/icons/Check";

const TOOLBAR_HEIGHT = 64;

const styles = theme => ({
    paper: {
        height: '100%',
        maxHeight: '100%',
        maxWidth: '100%',
        overflow: 'hidden',
    },
    title: {
        color: theme.palette.secondary.main,
    },
    form: {
        height: 'calc(100% - ' + (TOOLBAR_HEIGHT + theme.spacing(1)) + 'px)',
        overflow: 'auto',
    },
    input: {
        width: 400,
        marginBottom: theme.spacing(2)
    },
    grow: {
        flexGrow: 1,
    },
    toolbar: {
        height: TOOLBAR_HEIGHT,
        lineHeight: TOOLBAR_HEIGHT + 'px',
    },
    text: {
        fontSize: 16,
    },

    error: {
        fontSize: 24,
        color: '#c61f1f'
    },
    warning: {
        fontSize: 20,
        color: '#c6891f'
    },
    information: {
        fontSize: 18,
        color: '#429c1b'
    },
    button: {
        marginRight: theme.spacing(2),
    },
});

class WizardPortForwarding extends Component {
    constructor(props) {
        super(props);

        this.state = {
            auth: true,
            secure: false
        };

        this.focusRef = createRef();
    }

    componentDidMount() {
        this.focusRef.current && this.focusRef.current.focus();
    }

    render() {
        return <Paper className={ this.props.classes.paper }>
            <form className={ this.props.classes.form} noValidate autoComplete="off">
                <Grid container direction="column">
                    <Grid item>
                        <h2 className={ this.props.classes.title }>{ this.props.t('Important information about port forwarding') }</h2>
                    </Grid>
                    <Grid item>
                        {!this.props.auth ? <div className={this.props.classes.error}>{this.props.t('Warning!')}</div> : null}
                        {this.props.auth && !this.props.secure ? <div className={this.props.classes.warning}>{this.props.t('Be aware!')}</div> : null}
                        {this.props.auth && this.props.secure ? <div className={this.props.classes.information}>{this.props.t('Information')}</div> : null}

                        <div className={this.props.classes.text}>{this.props.t('Do not expose iobroker Admin or Web interfaces to the internet directly via the port forwarding!')}</div>
                    </Grid>
                    <Grid item style={{marginTop: 16}}>
                        <div className={this.props.classes.text}>{this.props.t('The Cloud services from iobroker.net/pro can help here to do that securely:')}</div>
                    </Grid>
                    <Grid item style={{marginTop: 16}}>
                        <Button
                            className={this.props.classes.button}
                            color="secondary"
                            variant="contained"
                            onClick={ () => window.open('https://iobroker.pro', 'help') }
                            startIcon={<IconCloudPro />}
                        >ioBroker.pro</Button>
                        <Button
                            color="secondary"
                            variant="contained"
                            onClick={ () => window.open('https://iobroker.net', 'help') }
                            startIcon={<IconCloud />}
                        >ioBroker.net</Button>
                    </Grid>
                </Grid>
            </form>
            <Toolbar className={ this.props.classes.toolbar }>
                <div className={ this.props.classes.grow }/>
                <Button color="primary" variant="contained" onClick={ () => this.props.onDone(this.state.password) } startIcon={<IconCheck/>}>{ this.props.t('Understand') }</Button>
            </Toolbar>
        </Paper>;
    }
}

WizardPortForwarding.propTypes = {
    auth: PropTypes.bool,
    secure: PropTypes.bool,
    t: PropTypes.func,
    socket: PropTypes.object,
    onDone: PropTypes.func.isRequired,
};

export default withWidth()(withStyles(styles)(WizardPortForwarding));


<|start_filename|>src-rx/src/components/ContextWrapper.js<|end_filename|>
import React, {
    createContext,
    useEffect,
    useState,
} from 'react';

import Adapters from '../tabs/Adapters';

export const ContextWrapper = createContext();

export const ContextWrapperProvider = ({ children }) => {
    const [stateContext, setState] = useState({
        hostsUpdate:    0,
        adaptersUpdate: 0,

        hosts:          null,
        repository:     null,
        installed:      null
    });

    const setStateContext = obj => {
        setState(prevState =>
            Object.keys(prevState).length === Object.keys(obj).length ?
                { ...obj }
                :
                { ...prevState, ...obj }
        );
    };

    useEffect(() => {
        if (stateContext.hosts) {
            const jsControllerVersion = stateContext.repository['js-controller'].version;
            let count = 0;
            stateContext.hosts.forEach(element => {
                if (Adapters.updateAvailable(element.common.installedVersion, jsControllerVersion)) {
                    count++;
                }
            });
            setStateContext({ hostsUpdate: count });
        }

        if (stateContext.installed) {
            let count = 0;
            Object.keys(stateContext.installed).forEach(element => {
                const _installed = stateContext.installed[element];
                const adapter = stateContext.repository[element];
                if (element !== 'js-controller' &&
                    element !== 'hosts' &&
                    _installed?.version &&
                    adapter?.version &&
                    _installed.ignoreVersion !== adapter.version &&
                    Adapters.updateAvailable(_installed.version, adapter.version)
                ) {
                    count++;
                }
            });

            setStateContext({ adaptersUpdate: count });
        }

    }, [stateContext.hosts, stateContext.installed, stateContext.repository]);

    return <ContextWrapper.Provider value={{ stateContext, setStateContext }}>
        {children}
    </ContextWrapper.Provider>;
};

<|start_filename|>src-rx/src/dialogs/SystemSettingsTabs/SSLDialog.js<|end_filename|>
// SSLDialog.js
import {Component} from 'react';
import PropTypes from 'prop-types';

import withWidth from '@material-ui/core/withWidth';
import {withStyles} from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import FormControl from '@material-ui/core/FormControl';
import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper';

const styles = theme => ({
    tabPanel: {
        width: '100%',
        height: '100% ',
        overflow: 'auto',
        overflowX: 'hidden',
        padding: 15,
        //backgroundColor: blueGrey[ 50 ]
    },
    buttonPanel: {
        paddingBottom: 40,
        display: 'flex'
    },
    descriptionPanel: {
        width: '100%',
        backgroundColor: 'transparent',
        marginLeft: 40,
        border: 'none',
        display: 'flex',
        alignItems: 'center',
        '& a': {
            paddingLeft: 3,
            color: theme.palette.type === 'dark' ? '#EEE' : '#111',

        }
    },
    formControl: {
        margin: theme.spacing(1),
        minWidth: '100%',
    },
});

class SSLDialog extends Component {
    render() {
        const {classes, data} = this.props;
        const {letsEncrypt} = data.native || {};
        return <div className={classes.tabPanel}>
            <div className={classes.buttonPanel}>
                <Paper
                    variant="outlined"
                    className={classes.descriptionPanel}
                    dangerouslySetInnerHTML={{__html: this.props.t('letsnecrypt_help')}}
                />
            </div>
            <Grid container spacing={6}>
                <Grid item md={3} xs={12}>
                    <FormControl className={classes.formControl}>
                        <TextField
                            id="email"
                            label={this.props.t('Email for account:')}
                            value={letsEncrypt ? letsEncrypt.email : ''}
                            InputLabelProps={{
                                readOnly: false,
                                shrink: true,
                            }}
                            onChange={evt => this.onChangeText(evt, 'email')}
                        />
                    </FormControl>
                </Grid>
                <Grid item md={3} xs={12}>
                    <FormControl className={classes.formControl}>
                        <TextField
                            id="domains"
                            label={this.props.t('Domains:')}
                            value={letsEncrypt ? letsEncrypt.domains : ''}
                            InputLabelProps={{
                                readOnly: false,
                                shrink: true,
                            }}
                            onChange={evt => this.onChangeText(evt, 'domains')}
                        />
                    </FormControl>
                </Grid>
                <Grid item md={3} xs={12}>
                    <FormControl className={classes.formControl}>
                        <TextField
                            id="path"
                            label={this.props.t('Path to storage:')}
                            value={letsEncrypt ? letsEncrypt.path : ''}
                            InputLabelProps={{
                                readOnly: false,
                                shrink: true,
                            }}
                            onChange={evt => this.onChangeText(evt, 'path')}
                        />
                    </FormControl>
                </Grid>
            </Grid>
        </div>;
    }

    onChangeText = (evt, id) => {
        const value = evt.target.value;
        this.doChange(id, value);
    }

    doChange = (name, value) => {
        let newData = JSON.parse(JSON.stringify(this.props.data))
        newData.native.letsEncrypt[name] = value;
        this.props.onChange(newData);
    }
}

SSLDialog.propTypes = {
    t: PropTypes.func,
    data: PropTypes.object,
    onChange: PropTypes.func,
};

export default withWidth()(withStyles(styles)(SSLDialog));





<|start_filename|>src-rx/src/components/Hosts/HostEdit.js<|end_filename|>
import { Component } from 'react';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';

import IconClose from '@material-ui/icons/Close';
import IconCheck from '@material-ui/icons/Check';
import AddIcon from '@material-ui/icons/Add';

import { Tooltip } from '@material-ui/core';
import UploadImage from '../UploadImage';

const styles = theme => ({
    error: {
        border: '2px solid #FF0000',
    },
    id: {
        fontStyle: 'italic',
    },
    dialog: {
        height: 'calc(100% - 64px)'
    },
    button: {
        marginTop: 20,
        marginLeft: theme.spacing(1),
    },
    funcDivEdit: {
        width: '100%'
    },
    funcEditName: {
        display: 'inline-block',
        width: 85
    },
    funcEdit: {
        width: 400,
    },
    marginTop: {
        marginTop: 20,
    },
    commonTabWrapper: {
        flexFlow: 'wrap',
        display: 'flex'
    },
    commonWrapper: {
        width: 500,
        minWidth: 300
    },
    flexDrop: {
        width: '100%',
        maxWidth: 500,
        margin: 'auto',
        display: 'flex'
    },
    marginBlock: {
        marginTop: 20
    },
    buttonAdd: {
        minWidth: 150
    },
    textField: {
        width: '100%'
    },
    flex: {
        display: 'flex'
    },
    close: {
        width: '20px',
        height: '20px',
        opacity: '0.9',
        cursor: 'pointer',
        position: 'relative',
        top: 20,
        transition: 'all 0.6s ease',
        '&:hover': {
            transform: 'rotate(90deg)'
        },
        '&:before': {
            position: 'absolute',
            left: '9px',
            content: '""',
            height: '20px',
            width: '3px',
            backgroundColor: '#ff4f4f',
            transform: 'rotate(45deg)'
        },
        '&:after': {
            position: 'absolute',
            left: '9px',
            content: '""',
            height: '20px',
            width: '3px',
            backgroundColor: '#ff4f4f',
            transform: 'rotate(-45deg)'
        },
    },
    color: {
        width: 70
    },
    buttonRemoveWrapper: {
        position: 'absolute',
        zIndex: 222,
        right: 0
    },
    tabsPadding: {
        padding: '0px 24px'
    }
});

class HostEdit extends Component {
    constructor(props) {
        super(props);

        this.state = {
            text: JSON.stringify(this.props.obj, null, 2),
            error: false,
            changed: false,
            tab: window.localStorage.getItem((this.props.dialogName || 'App') + '.editTab') || 'object',
        };

        this.originalObj = JSON.stringify(this.props.obj, null, 2);
    }

    prepareObject(value) {
        value = value || this.state.text;
        try {
            const obj = JSON.parse(value);
            obj._id = this.props.obj._id; // do not allow change of id
            return obj;
        } catch (e) {
            return null;
        }
    }

    onChange(value) {
        const newState = { text: value };
        const json = this.prepareObject(value);
        if (json) {
            newState.changed = this.originalObj !== JSON.stringify(json, null, 2);
            if (this.state.error) {
                newState.error = false;
            }
        } else {
            newState.error = true;
        }
        this.setState(newState);
    }

    onUpdate() {
        const obj = JSON.parse(this.state.text);
        obj._id = this.props.obj._id; // do not allow change of id
        this.props.onClose(obj);
    }
    setCommonItem(json, name, value) {
        json.common[name] = value;
        this.onChange(JSON.stringify(json, null, 2));
    }
    removeCommonItem(json, name) {
        delete json.common[name]
        this.onChange(JSON.stringify(json, null, 2));
    }
    buttonAddKey(nameKey, cb) {
        const { t, classes } = this.props;
        return <div
            className={classes.marginBlock}>
            <Button
                className={classes.buttonAdd}
                variant="contained"
                color="secondary"
                onClick={cb}
                startIcon={<AddIcon />}
            >{t('Add ' + nameKey)}</Button>
        </div>
    }
    buttonRemoveKey(nameKey, cb) {
        const { t, classes } = this.props;
        return <Tooltip title={t('Remove ' + nameKey)}><div className={classes.close} onClick={cb} /></Tooltip>
    }
    renderCommonEdit() {
        try {
            const json = JSON.parse(this.state.text);
            const disabled = false;
            const { classes, t } = this.props;
            return <div className={classes.commonTabWrapper}>
                <div className={classes.commonWrapper}>
                    {typeof json.common.title !== 'undefined' ?
                        <TextField
                            disabled={disabled}
                            label={t('title')}
                            className={clsx(classes.marginBlock, classes.textField)}
                            fullWidth
                            value={json.common.title}
                            onChange={(el) => this.setCommonItem(json, 'title', el.target.value)}
                        /> :
                        this.buttonAddKey('title', () => this.setCommonItem(json, 'title', ''))
                    }
                    {typeof json.common.color !== 'undefined' ?
                        <div className={classes.flex}>
                            <TextField
                                disabled={disabled}
                                className={clsx(classes.marginBlock, classes.color)}
                                label={t('Color')}
                                type="color"
                                value={json.common.color}
                                onChange={el => this.setCommonItem(json, 'color', el.target.value)} />
                            {this.buttonRemoveKey('color', () => this.removeCommonItem(json, 'color'))}
                        </div> :
                        this.buttonAddKey('color', () => this.setCommonItem(json, 'color', ''))
                    }
                </div>
                {typeof json.common.icon !== 'undefined' ?
                    <div className={classes.flexDrop}>
                        <UploadImage
                            disabled={disabled}
                            crop
                            maxSize={256 * 1024}
                            icon={json.common.icon}
                            removeIconFunc={() => this.setCommonItem(json, 'icon', '')}
                            onChange={(base64) => this.setCommonItem(json, 'icon', base64)}
                            t={t}
                        />
                        {this.buttonRemoveKey('icon', () => this.removeCommonItem(json, 'icon'))}
                    </div> :
                    <div className={classes.flexDrop}>
                        {this.buttonAddKey('icon', () => this.setCommonItem(json, 'icon', ''))}
                    </div>
                }
            </div>
        } catch (e) {
            return <div>{this.props.t('Cannot parse JSON!')}</div>;
        }
    }

    render() {
        // const withAlias = this.props.obj._id.startsWith('alias.0') && this.props.obj.type === 'state';

        return <Dialog
            classes={{ paper: this.props.classes.dialog }}
            open={true}
            maxWidth="lg"
            fullWidth={this.state.type !== 'number' && this.state.type !== 'boolean'}
            fullScreen={false}
            onClose={() => this.props.onClose()}
            aria-labelledby="edit-value-dialog-title"
            aria-describedby="edit-value-dialog-description"
        >
            <DialogTitle id="edit-value-dialog-title">{this.props.t('Edit host settings')}: <span className={this.props.classes.id}>{this.props.obj._id}</span></DialogTitle>
            <DialogContent>
                {
                    this.renderCommonEdit()
                }
            </DialogContent>
            <DialogActions>
                <Button variant="contained" disabled={this.state.error || !this.state.changed} onClick={() => this.onUpdate()} color="primary" startIcon={<IconCheck />}>{this.props.t('Write')}</Button>
                <Button variant="contained" onClick={() => this.props.onClose()} startIcon={<IconClose />}>{this.props.t('Cancel')}</Button>
            </DialogActions>
        </Dialog>;
    }
}

HostEdit.propTypes = {
    classes: PropTypes.object,
    socket: PropTypes.object,
    obj: PropTypes.object,
    expertMode: PropTypes.bool,
    onClose: PropTypes.func.isRequired,
    dialogName: PropTypes.string,
    objects: PropTypes.object,

    t: PropTypes.func,
};

export default withStyles(styles)(HostEdit);


<|start_filename|>src/lib/js/jqGrid/grid.locale-all.js<|end_filename|>
function initGridLanguage(lang) {
    $.jgrid = $.jgrid || {};

    lang = lang || 'en';

    var langs = {
        'en': /**
         * jqGrid English Translation
         * <NAME> <EMAIL>
         * http://trirand.com/blog/
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
                {
                    defaults: {
                        recordtext: "View {0} - {1} of {2}",
                        emptyrecords: "No records to view",
                        loadtext: "Loading...",
                        pgtext: "Page {0} of {1}"
                    },
                    search: {
                        caption: "Search...",
                        Find: "Find",
                        Reset: "Reset",
                        odata: [
                            {oper: 'eq', text: 'equal'},
                            {oper: 'ne', text: 'not equal'},
                            {oper: 'lt', text: 'less'},
                            {oper: 'le', text: 'less or equal'},
                            {oper: 'gt', text: 'greater'},
                            {oper: 'ge', text: 'greater or equal'},
                            {oper: 'bw', text: 'begins with'},
                            {oper: 'bn', text: 'does not begin with'},
                            {oper: 'in', text: 'is in'},
                            {oper: 'ni', text: 'is not in'},
                            {oper: 'ew', text: 'ends with'},
                            {oper: 'en', text: 'does not end with'},
                            {oper: 'cn', text: 'contains'},
                            {oper: 'nc', text: 'does not contain'}
                        ],
                        groupOps: [
                            {op: "AND", text: "all"},
                            {op: "OR", text: "any"}
                        ]
                    },
                    edit: {
                        addCaption: "Add Record",
                        editCaption: "Edit Record",
                        bSubmit: "Submit",
                        bCancel: "Cancel",
                        bClose: "Close",
                        saveData: "Data has been changed! Save changes?",
                        bYes: "Yes",
                        bNo: "No",
                        bExit: "Cancel",
                        msg: {
                            required: "Field is required",
                            number: "Please, enter valid number",
                            minValue: "value must be greater than or equal to ",
                            maxValue: "value must be less than or equal to",
                            email: "is not a valid e-mail",
                            integer: "Please, enter valid integer value",
                            date: "Please, enter valid date value",
                            url: "is not a valid URL. Prefix required ('http://' or 'https://')",
                            nodefined: " is not defined!",
                            novalue: " return value is required!",
                            customarray: "Custom function should return array!",
                            customfcheck: "Custom function should be present in case of custom checking!"

                        }
                    },
                    view: {
                        caption: "View Record",
                        bClose: "Close"
                    },
                    del: {
                        caption: "Delete",
                        msg: "Delete selected record(s)?",
                        bSubmit: "Delete",
                        bCancel: "Cancel"
                    },
                    nav: {
                        edittext: "",
                        edittitle: "Edit selected row",
                        addtext: "",
                        addtitle: "Add new row",
                        deltext: "",
                        deltitle: "Delete selected row",
                        searchtext: "",
                        searchtitle: "Find records",
                        refreshtext: "",
                        refreshtitle: "Reload Grid",
                        alertcap: "Warning",
                        alerttext: "Please, select row",
                        viewtext: "",
                        viewtitle: "View selected row"
                    },
                    col: {
                        caption: "Select columns",
                        bSubmit: "Ok",
                        bCancel: "Cancel"
                    },
                    errors: {
                        errcap: "Error",
                        nourl: "No url is set",
                        norecords: "No records to process",
                        model: "Length of colNames <> colModel!"
                    },
                    formatter: {
                        integer: {thousandsSeparator: ",", defaultValue: '0'},
                        number: {decimalSeparator: ".", thousandsSeparator: ",", decimalPlaces: 2, defaultValue: '0.00'},
                        currency: {decimalSeparator: ".", thousandsSeparator: ",", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0.00'},
                        date: {
                            dayNames: [
                                "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat",
                                "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
                            ],
                            monthNames: [
                                "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
                                "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
                            ],
                            AmPm: ["am", "pm", "AM", "PM"],
                            S: function (j) {
                                return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th';
                            },
                            srcformat: 'Y-m-d',
                            newformat: 'n/j/Y',
                            parseRe: /[Tt\\\/:_;.,\t\s-]/,
                            masks: {
                                // see http://php.net/manual/en/function.date.php for PHP format used in jqGrid
                                // and see http://docs.jquery.com/UI/Datepicker/formatDate
                                // and https://github.com/jquery/globalize#dates for alternative formats used frequently
                                // one can find on https://github.com/jquery/globalize/tree/master/lib/cultures many
                                // information about date, time, numbers and currency formats used in different countries
                                // one should just convert the information in PHP format
                                ISO8601Long: "Y-m-d H:i:s",
                                ISO8601Short: "Y-m-d",
                                // short date:
                                //    n - Numeric representation of a month, without leading zeros
                                //    j - Day of the month without leading zeros
                                //    Y - A full numeric representation of a year, 4 digits
                                // example: 3/1/2012 which means 1 March 2012
                                ShortDate: "n/j/Y", // in jQuery UI Datepicker: "M/d/yyyy"
                                // long date:
                                //    l - A full textual representation of the day of the week
                                //    F - A full textual representation of a month
                                //    d - Day of the month, 2 digits with leading zeros
                                //    Y - A full numeric representation of a year, 4 digits
                                LongDate: "l, F d, Y", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy"
                                // long date with long time:
                                //    l - A full textual representation of the day of the week
                                //    F - A full textual representation of a month
                                //    d - Day of the month, 2 digits with leading zeros
                                //    Y - A full numeric representation of a year, 4 digits
                                //    g - 12-hour format of an hour without leading zeros
                                //    i - Minutes with leading zeros
                                //    s - Seconds, with leading zeros
                                //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                                FullDateTime: "l, F d, Y g:i:s A", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy h:mm:ss tt"
                                // month day:
                                //    F - A full textual representation of a month
                                //    d - Day of the month, 2 digits with leading zeros
                                MonthDay: "F d", // in jQuery UI Datepicker: "MMMM dd"
                                // short time (without seconds)
                                //    g - 12-hour format of an hour without leading zeros
                                //    i - Minutes with leading zeros
                                //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                                ShortTime: "g:i A", // in jQuery UI Datepicker: "h:mm tt"
                                // long time (with seconds)
                                //    g - 12-hour format of an hour without leading zeros
                                //    i - Minutes with leading zeros
                                //    s - Seconds, with leading zeros
                                //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                                LongTime: "g:i:s A", // in jQuery UI Datepicker: "h:mm:ss tt"
                                SortableDateTime: "Y-m-d\\TH:i:s",
                                UniversalSortableDateTime: "Y-m-d H:i:sO",
                                // month with year
                                //    Y - A full numeric representation of a year, 4 digits
                                //    F - A full textual representation of a month
                                YearMonth: "F, Y" // in jQuery UI Datepicker: "MMMM, yyyy"
                            },
                            reformatAfterEdit: false
                        },
                        baseLinkUrl: '',
                        showAction: '',
                        target: '',
                        checkbox: {disabled: true},
                        idName: 'id'
                    }
                },
        /**
         * jqGrid German Translation
         * Version 1.0.0 (developed for jQuery Grid 3.3.1)
         * <NAME> <EMAIL>
         * http://blue-hit.de/
         *
         * Updated for jqGrid 3.8
         * <NAME>
         * http://www.contentcontrol-berlin.de
         *
         * Updated for jQuery 4.4
         * <NAME> <EMAIL>
         * the format corresponds now the format from
         * https://github.com/jquery/globalize/blob/master/lib/cultures/globalize.culture.de.js
         *
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "de": {
            defaults: {
                recordtext: "Zeige {0} - {1} von {2}",
                emptyrecords: "Keine Datensätze vorhanden",
                loadtext: "Lädt...",
                pgtext: "Seite {0} von {1}"
            },
            search: {
                caption: "Suche...",
                Find: "Suchen",
                Reset: "Zurücksetzen",
                odata: [
                    {oper: 'eq', text: "gleich"},
                    {oper: 'ne', text: "ungleich"},
                    {oper: 'lt', text: "kleiner"},
                    {oper: 'le', text: "kleiner gleich"},
                    {oper: 'gt', text: "größer"},
                    {oper: 'ge', text: "größer gleich"},
                    {oper: 'bw', text: "beginnt mit"},
                    {oper: 'bn', text: "beginnt nicht mit"},
                    {oper: 'in', text: "ist in"},
                    {oper: 'ni', text: "ist nicht in"},
                    {oper: 'ew', text: "endet mit"},
                    {oper: 'en', text: "endet nicht mit"},
                    {oper: 'cn', text: "enthält"},
                    {oper: 'nc', text: "enthält nicht"}
                ],
                groupOps: [
                    {op: "AND", text: "alle"},
                    {op: "OR", text: "mindestens eine"}
                ]
            },
            edit: {
                addCaption: "Datensatz hinzufügen",
                editCaption: "Datensatz bearbeiten",
                bSubmit: "Speichern",
                bCancel: "Abbrechen",
                bClose: "Schließen",
                saveData: "Daten wurden geändert! Änderungen speichern?",
                bYes: "ja",
                bNo: "nein",
                bExit: "abbrechen",
                msg: {
                    required: "Feld ist erforderlich",
                    number: "Bitte geben Sie eine Zahl ein",
                    minValue: "Wert muss größer oder gleich sein, als ",
                    maxValue: "Wert muss kleiner oder gleich sein, als ",
                    email: "ist keine gültige E-Mail-Adresse",
                    integer: "Bitte geben Sie eine Ganzzahl ein",
                    date: "Bitte geben Sie ein gültiges Datum ein",
                    url: "ist keine gültige URL. Präfix muss eingegeben werden ('http://' oder 'https://')",
                    nodefined: " ist nicht definiert!",
                    novalue: " Rückgabewert ist erforderlich!",
                    customarray: "Benutzerdefinierte Funktion sollte ein Array zurückgeben!",
                    customfcheck: "Benutzerdefinierte Funktion sollte im Falle der benutzerdefinierten Überprüfung vorhanden sein!"
                }
            },
            view: {
                caption: "Datensatz anzeigen",
                bClose: "Schließen"
            },
            del: {
                caption: "Löschen",
                msg: "Ausgewählte Datensätze löschen?",
                bSubmit: "Löschen",
                bCancel: "Abbrechen"
            },
            nav: {
                edittext: " ",
                edittitle: "Ausgewählte Zeile editieren",
                addtext: " ",
                addtitle: "Neue Zeile einfügen",
                deltext: " ",
                deltitle: "Ausgewählte Zeile löschen",
                searchtext: " ",
                searchtitle: "Datensatz suchen",
                refreshtext: "",
                refreshtitle: "Tabelle neu laden",
                alertcap: "Warnung",
                alerttext: "Bitte Zeile auswählen",
                viewtext: "",
                viewtitle: "Ausgewählte Zeile anzeigen"
            },
            col: {
                caption: "Spalten auswählen",
                bSubmit: "Speichern",
                bCancel: "Abbrechen"
            },
            errors: {
                errcap: "Fehler",
                nourl: "Keine URL angegeben",
                norecords: "Keine Datensätze zu bearbeiten",
                model: "colNames und colModel sind unterschiedlich lang!"
            },
            formatter: {
                integer: {thousandsSeparator: ".", defaultValue: '0'},
                number: {decimalSeparator: ",", thousandsSeparator: ".", decimalPlaces: 2, defaultValue: '0,00'},
                currency: {decimalSeparator: ",", thousandsSeparator: ".", decimalPlaces: 2, prefix: "", suffix: " €", defaultValue: '0,00'},
                date: {
                    dayNames: [
                        "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa",
                        "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"
                    ],
                    monthNames: [
                        "Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez",
                        "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"
                    ],
                    AmPm: ["", "", "", ""],
                    S: function (j) {
                        return '.';
                    }, // one can also use 'er' instead of '.' but one have to use additional word like 'der' or 'den' before
                    srcformat: 'Y-m-d',
                    newformat: 'd.m.Y',
                    parseRe: /[Tt\\\/:_;.,\t\s-]/,
                    masks: {
                        // see http://php.net/manual/en/function.date.php for PHP format used in jqGrid
                        // and see http://docs.jquery.com/UI/Datepicker/formatDate
                        // and https://github.com/jquery/globalize#dates for alternative formats used frequently
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        // short date:
                        //    d - Day of the month, 2 digits with leading zeros
                        //    m - Numeric representation of a month, with leading zeros
                        //    Y - A full numeric representation of a year, 4 digits
                        ShortDate: "d.m.Y", // in jQuery UI Datepicker: "dd.MM.yyyy"
                        // long date:
                        //    l - A full textual representation of the day of the week
                        //    j - Day of the month without leading zeros
                        //    F - A full textual representation of a month
                        //    Y - A full numeric representation of a year, 4 digits
                        LongDate: "l, j. F Y", // in jQuery UI Datepicker: "dddd, d. MMMM yyyy"
                        // long date with long time:
                        //    l - A full textual representation of the day of the week
                        //    j - Day of the month without leading zeros
                        //    F - A full textual representation of a month
                        //    Y - A full numeric representation of a year, 4 digits
                        //    H - 24-hour format of an hour with leading zeros
                        //    i - Minutes with leading zeros
                        //    s - Seconds, with leading zeros
                        FullDateTime: "l, j. F Y H:i:s", // in jQuery UI Datepicker: "dddd, d. MMMM yyyy HH:mm:ss"
                        // month day:
                        //    d - Day of the month, 2 digits with leading zeros
                        //    F - A full textual representation of a month
                        MonthDay: "d F", // in jQuery UI Datepicker: "dd MMMM"
                        // short time (without seconds)
                        //    H - 24-hour format of an hour with leading zeros
                        //    i - Minutes with leading zeros
                        ShortTime: "H:i", // in jQuery UI Datepicker: "HH:mm"
                        // long time (with seconds)
                        //    H - 24-hour format of an hour with leading zeros
                        //    i - Minutes with leading zeros
                        //    s - Seconds, with leading zeros
                        LongTime: "H:i:s", // in jQuery UI Datepicker: "HH:mm:ss"
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        // month with year
                        //    F - A full textual representation of a month
                        //    Y - A full numeric representation of a year, 4 digits
                        YearMonth: "F Y" // in jQuery UI Datepicker: "MMMM yyyy"
                    },
                    reformatAfterEdit: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            }
        },
        /**
         * jqGrid Russian Translation v1.0 02.07.2009 (based on translation by <NAME> v1.1 21.01.2009, http://softcore.com.ru)
         * <NAME>
         * http://d.sumy.ua
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "ru": {
            defaults: {
                recordtext: "Просмотр {0} - {1} из {2}",
                emptyrecords: "Нет записей для просмотра",
                loadtext: "Загрузка...",
                pgtext: "Стр. {0} из {1}"
            },
            search: {
                caption: "Поиск...",
                Find: "Найти",
                Reset: "Сброс",
                odata: [
                    {oper: 'eq', text: "равно"},
                    {oper: 'ne', text: "не равно"},
                    {oper: 'lt', text: "меньше"},
                    {oper: 'le', text: "меньше или равно"},
                    {oper: 'gt', text: "больше"},
                    {oper: 'ge', text: "больше или равно"},
                    {oper: 'bw', text: "начинается с"},
                    {oper: 'bn', text: "не начинается с"},
                    {oper: 'in', text: "находится в"},
                    {oper: 'ni', text: "не находится в"},
                    {oper: 'ew', text: "заканчивается на"},
                    {oper: 'en', text: "не заканчивается на"},
                    {oper: 'cn', text: "содержит"},
                    {oper: 'nc', text: "не содержит"}
                ],
                groupOps: [
                    {op: "AND", text: "все"},
                    {op: "OR", text: "любой"}
                ]
            },
            edit: {
                addCaption: "Добавить запись",
                editCaption: "Редактировать запись",
                bSubmit: "Сохранить",
                bCancel: "Отмена",
                bClose: "Закрыть",
                saveData: "Данные были измененны! Сохранить изменения?",
                bYes: "Да",
                bNo: "Нет",
                bExit: "Отмена",
                msg: {
                    required: "Поле является обязательным",
                    number: "Пожалуйста, введите правильное число",
                    minValue: "значение должно быть больше либо равно",
                    maxValue: "значение должно быть меньше либо равно",
                    email: "некорректное значение e-mail",
                    integer: "Пожалуйста, введите целое число",
                    date: "Пожалуйста, введите правильную дату",
                    url: "неверная ссылка. Необходимо ввести префикс ('http://' или 'https://')",
                    nodefined: " не определено!",
                    novalue: " возвращаемое значение обязательно!",
                    customarray: "Пользовательская функция должна возвращать массив!",
                    customfcheck: "Пользовательская функция должна присутствовать в случаи пользовательской проверки!"
                }
            },
            view: {
                caption: "Просмотр записи",
                bClose: "Закрыть"
            },
            del: {
                caption: "Удалить",
                msg: "Удалить выбранную запись(и)?",
                bSubmit: "Удалить",
                bCancel: "Отмена"
            },
            nav: {
                edittext: " ",
                edittitle: "Редактировать выбранную запись",
                addtext: " ",
                addtitle: "Добавить новую запись",
                deltext: " ",
                deltitle: "Удалить выбранную запись",
                searchtext: " ",
                searchtitle: "Найти записи",
                refreshtext: "",
                refreshtitle: "Обновить таблицу",
                alertcap: "Внимание",
                alerttext: "Пожалуйста, выберите запись",
                viewtext: "",
                viewtitle: "Просмотреть выбранную запись"
            },
            col: {
                caption: "Показать/скрыть столбцы",
                bSubmit: "Сохранить",
                bCancel: "Отмена"
            },
            errors: {
                errcap: "Ошибка",
                nourl: "URL не установлен",
                norecords: "Нет записей для обработки",
                model: "Число полей не соответствует числу столбцов таблицы!"
            },
            formatter: {
                integer: {thousandsSeparator: " ", defaultValue: '0'},
                number: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0,00'},
                currency: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0,00'},
                date: {
                    dayNames: [
                        "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб",
                        "Воскресение", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"
                    ],
                    monthNames: [
                        "Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек",
                        "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"
                    ],
                    AmPm: ["am", "pm", "AM", "PM"],
                    S: function (j) {
                        return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th'
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'd.m.Y',
                    parseRe: /[Tt\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "n.j.Y",
                        LongDate: "l, F d, Y",
                        FullDateTime: "l, F d, Y G:i:s",
                        MonthDay: "F d",
                        ShortTime: "G:i",
                        LongTime: "G:i:s",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F, Y"
                    },
                    reformatAfterEdit: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            }

        },
        /**
         * jqGrid Spanish Translation
         * Traduccion jqGrid en Español por <NAME>
         * Traduccion corregida y ampliada por Faserline, S.L. 
         * http://www.faserline.com
         * Traduccion corregida y ampliada por <NAME>
         * Traducción corregida y ampliada por <NAME>
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "es": {
            defaults: {
                recordtext: "Mostrando {0} - {1} de {2}",
                emptyrecords: "Sin registros que mostrar",
                loadtext: "Cargando...",
                savetext: "Guardando...",
                pgtext: "Página {0} de {1}",
                pgfirst: "Primera Página",
                pglast: "Última Página",
                pgnext: "Página Siguiente",
                pgprev: "Página Anterior",
                pgrecs: "Registros por página",
                showhide: "Alternar Contraer Expandir Grid",
                // mobile
                pagerCaption: "Grid::Configurar página",
                pageText: "Página:",
                recordPage: "Registros por página",
                nomorerecs: "No más registros...",
                scrollPullup: "Arrastrar arriba para cargar más...",
                scrollPulldown: "Arrastrar arriba para refrescar...",
                scrollRefresh: "Soltar para refrescar..."
            },
            search: {
                caption: "Búsqueda...",
                Find: "Buscar",
                Reset: "Limpiar",
                odata: [{oper: 'eq', text: "igual "}, {oper: 'ne', text: "no igual a"}, {oper: 'lt', text: "menor que"}, {oper: 'le', text: "menor o igual que"}, {oper: 'gt', text: "mayor que"}, {oper: 'ge', text: "mayor o igual a"}, {oper: 'bw', text: "empiece por"}, {oper: 'bn', text: "no empiece por"}, {oper: 'in', text: "está en"}, {oper: 'ni', text: "no está en"}, {oper: 'ew', text: "termina por"}, {oper: 'en', text: "no termina por"}, {oper: 'cn', text: "contiene"}, {oper: 'nc', text: "no contiene"}, {oper: 'nu', text: 'es nulo'}, {oper: 'nn', text: 'no es nulo'}, {oper: 'bt', text: 'entre'}],
                groupOps: [{op: "AND", text: "todo"}, {op: "OR", text: "cualquier"}],
                operandTitle: "Clic para seleccionar la operación de búsqueda.",
                resetTitle: "Reiniciar valores de búsqueda",
                addsubgrup: "Agregar subgrupo",
                addrule: "Agregar regla",
                delgroup: "Borrar grupo",
                delrule: "Borrar regla"
            },
            edit: {
                addCaption: "Agregar registro",
                editCaption: "Modificar registro",
                bSubmit: "Guardar",
                bCancel: "Cancelar",
                bClose: "Cerrar",
                saveData: "Se han modificado los datos, ¿guardar cambios?",
                bYes: "Si",
                bNo: "No",
                bExit: "Cancelar",
                msg: {
                    required: "Campo obligatorio",
                    number: "Introduzca un número",
                    minValue: "El valor debe ser mayor o igual a ",
                    maxValue: "El valor debe ser menor o igual a ",
                    email: "no es una dirección de correo válida",
                    integer: "Introduzca un valor entero",
                    date: "Introduzca una fecha correcta ",
                    url: "no es una URL válida. Prefijo requerido ('http://' or 'https://')",
                    nodefined: " no está definido.",
                    novalue: " valor de retorno es requerido.",
                    customarray: "La función personalizada debe devolver un array.",
                    customfcheck: "La función personalizada debe estar presente en el caso de validación personalizada."
                }
            },
            view: {
                caption: "Consultar registro",
                bClose: "Cerrar"
            },
            del: {
                caption: "Eliminar",
                msg: "¿Desea eliminar los registros seleccionados?",
                bSubmit: "Eliminar",
                bCancel: "Cancelar"
            },
            nav: {
                edittext: " ",
                edittitle: "Modificar fila seleccionada",
                addtext: " ",
                addtitle: "Agregar nueva fila",
                deltext: " ",
                deltitle: "Eliminar fila seleccionada",
                searchtext: " ",
                searchtitle: "Buscar información",
                refreshtext: "",
                refreshtitle: "Recargar datos",
                alertcap: "Aviso",
                alerttext: "Seleccione una fila",
                viewtext: "",
                viewtitle: "Ver fila seleccionada",
                savetext: "",
                savetitle: "Guardar fila",
                canceltext: "",
                canceltitle: "Cancelar edición de fila",
                selectcaption: "Acciones..."
            },
            col: {
                caption: "Mostrar/ocultar columnas",
                bSubmit: "Enviar",
                bCancel: "Cancelar"
            },
            errors: {
                errcap: "Error",
                nourl: "No se ha especificado una URL",
                norecords: "No hay datos para procesar",
                model: "Las columnas de nombres son diferentes de las columnas del modelo"
            },
            formatter: {
                integer: {thousandsSeparator: ".", defaultValue: '0'},
                number: {decimalSeparator: ",", thousandsSeparator: ".", decimalPlaces: 2, defaultValue: '0,00'},
                currency: {decimalSeparator: ",", thousandsSeparator: ".", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0,00'},
                date: {
                    dayNames: [
                        "Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa",
                        "Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"
                    ],
                    monthNames: [
                        "Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic",
                        "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
                    ],
                    AmPm: ["am", "pm", "AM", "PM"],
                    S: function (j) {
                        return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th'
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'd-m-Y',
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "n/j/Y",
                        LongDate: "l, F d, Y",
                        FullDateTime: "l, F d, Y g:i:s A",
                        MonthDay: "F d",
                        ShortTime: "g:i A",
                        LongTime: "g:i:s A",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F, Y"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            },
            colmenu: {
                sortasc: "Orden Ascendente",
                sortdesc: "Orden Descendente",
                columns: "Columnas",
                filter: "Filtrar",
                grouping: "Agrupar por",
                ungrouping: "Desagrupar",
                searchTitle: "Obtener elementos con un valor que:",
                freeze: "Inmovilizar",
                unfreeze: "Movilizar",
                reorder: "Mover para reordenar"
            }
        },
        /**
         * jqGrid French Translation
         * <NAME> <EMAIL>
         * http://trirand.com/blog/ 
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "fr": {
            defaults: {
                recordtext: "Enregistrements {0} - {1} sur {2}",
                emptyrecords: "Aucun enregistrement à afficher",
                loadtext: "Chargement...",
                savetext: "Sauvegarde en cours...",
                pgtext: "Page {0} de {1}",
                pgfirst: "Première page",
                pglast: "Dernière page",
                pgnext: "Page suivante",
                pgprev: "Page précédente",
                pgrecs: "Enregistrements par page",
                showhide: "Réduire/Agrandir la grille",
                // mobile
                pagerCaption: "Grille::Options de pagination",
                pageText: "Page:",
                recordPage: "Enregistrements par page",
                nomorerecs: "Plus de données...",
                scrollPullup: "Glisser vers le haut pour charger plus de données...",
                scrollPulldown: "Glisser vers le bas pour rafraîchir...",
                scrollRefresh: "Relâcher pour rafraîchir..."
            },
            search: {
                caption: "Recherche...",
                Find: "Chercher",
                Reset: "Réinitialiser",
                odata: [{oper: 'eq', text: "égal"}, {oper: 'ne', text: "différent"}, {oper: 'lt', text: "inférieur"}, {oper: 'le', text: "inférieur ou égal"}, {oper: 'gt', text: "supérieur"}, {oper: 'ge', text: "supérieur ou égal"}, {oper: 'bw', text: "commence par"}, {oper: 'bn', text: "ne commence pas par"}, {oper: 'in', text: "est dans"}, {oper: 'ni', text: "n'est pas dans"}, {oper: 'ew', text: "finit par"}, {oper: 'en', text: "ne finit pas par"}, {oper: 'cn', text: "contient"}, {oper: 'nc', text: "ne contient pas"}, {oper: 'nu', text: 'is null'}, {oper: 'nn', text: 'is not null'}, {oper: 'bt', text: 'entre'}],
                groupOps: [{op: "AND", text: "tous"}, {op: "OR", text: "au moins un"}],
                operandTitle: "Cliquer pour sélectionner l'opérateur.",
                resetTitle: "Réinitialiser la valeur de recherche",
                addsubgrup: "Add subgroup",
                addrule: "Add rule",
                delgroup: "Delete group",
                delrule: "Delete rule"
            },
            edit: {
                addCaption: "Ajouter",
                editCaption: "Éditer",
                bSubmit: "Valider",
                bCancel: "Annuler",
                bClose: "Fermer",
                saveData: "Les données ont changé ! Enregistrer les modifications ?",
                bYes: "Oui",
                bNo: "Non",
                bExit: "Annuler",
                msg: {
                    required: "Champ obligatoire",
                    number: "Saisissez un nombre correct",
                    minValue: "La valeur doit être supérieure ou égale à",
                    maxValue: "La valeur doit être inférieure ou égale à",
                    email: "n'est pas un email valide",
                    integer: "Saisissez un entier valide",
                    url: "n'est pas une adresse valide. Préfixe requis ('http://' or 'https://')",
                    nodefined: " n'est pas défini!",
                    novalue: " la valeur de retour est requise!",
                    customarray: "Une fonction personnalisée devrait retourner un tableau (array)!",
                    customfcheck: "Une fonction personnalisée devrait être présente dans le cas d'une vérification personnalisée!"
                }
            },
            view: {
                caption: "Voir les enregistrements",
                bClose: "Fermer"
            },
            del: {
                caption: "Supprimer",
                msg: "Supprimer les enregistrements sélectionnés ?",
                bSubmit: "Supprimer",
                bCancel: "Annuler"
            },
            nav: {
                edittext: " ",
                edittitle: "Editer la ligne sélectionnée",
                addtext: " ",
                addtitle: "Ajouter une ligne",
                deltext: " ",
                deltitle: "Supprimer la ligne sélectionnée",
                searchtext: " ",
                searchtitle: "Chercher un enregistrement",
                refreshtext: "",
                refreshtitle: "Recharger le tableau",
                alertcap: "Avertissement",
                alerttext: "Veuillez sélectionner une ligne",
                viewtext: "",
                viewtitle: "Afficher la ligne sélectionnée",
                savetext: "",
                savetitle: "Sauvegarder la ligne",
                canceltext: "",
                canceltitle: "Annuler l'édition de la ligne",
                selectcaption: "Actions..."
            },
            col: {
                caption: "Afficher/Masquer les colonnes",
                bSubmit: "Valider",
                bCancel: "Annuler"
            },
            errors: {
                errcap: "Erreur",
                nourl: "Aucune adresse n'est paramétrée",
                norecords: "Aucun enregistrement à traiter",
                model: "Nombre de titres (colNames) <> Nombre de données (colModel)!"
            },
            formatter: {
                integer: {thousandsSeparator: " ", defaultValue: '0'},
                number: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0,00'},
                currency: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0,00'},
                date: {
                    dayNames: [
                        "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam",
                        "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"
                    ],
                    monthNames: [
                        "Jan", "Fév", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Déc",
                        "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Décembre"
                    ],
                    AmPm: ["am", "pm", "AM", "PM"],
                    S: function (j) {
                        return j == 1 ? 'er' : 'e';
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'd/m/Y',
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "n/j/Y",
                        LongDate: "l, F d, Y",
                        FullDateTime: "l, F d, Y g:i:s A",
                        MonthDay: "F d",
                        ShortTime: "g:i A",
                        LongTime: "g:i:s A",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F, Y"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            },
            colmenu: {
                sortasc: "Trier en ordre croissant",
                sortdesc: "Trier en ordre décroissant",
                columns: "Colonnes",
                filter: "Filtrer",
                grouping: "Grouper par",
                ungrouping: "Séparer",
                searchTitle: "Prendre les items avec la valeur:",
                freeze: "Figer",
                unfreeze: "Relâcher",
                reorder: "Déplacer pour changer l'ordre"
            }
        },
        /**
         * jqGrid Portuguese Translation
         * Traduçã da jqGrid em Portugues por <NAME>, http://www.eyeviewdesign.pt
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "pt": {
            defaults: {
                recordtext: "View {0} - {1} of {2}",
                emptyrecords: "No records to view",
                loadtext: "A carregar...",
                pgtext: "Página {0} de {1}",
                savetext: "Saving...",
                pgfirst: "First Page",
                pglast: "Last Page",
                pgnext: "Next Page",
                pgprev: "Previous Page",
                pgrecs: "Records per Page",
                showhide: "Toggle Expand Collapse Grid",
                // mobile
                pagerCaption: "Grid::Page Settings",
                pageText: "Page:",
                recordPage: "Records per Page",
                nomorerecs: "No more records...",
                scrollPullup: "Pull up to load more...",
                scrollPulldown: "Pull down to refresh...",
                scrollRefresh: "Release to refresh..."
            },
            search: {
                caption: "Busca...",
                Find: "Procurar",
                Reset: "Limpar",
                odata: [{oper: 'eq', text: 'igual'}, {oper: 'ne', text: 'desigual'}, {oper: 'lt', text: 'menor'}, {oper: 'le', text: 'menor ou igual'}, {oper: 'gt', text: 'maior'}, {oper: 'ge', text: 'maior ou igual'}, {oper: 'bw', text: 'comecacom'}, {oper: 'bn', text: 'nao comeca com'}, {oper: 'in', text: 'estadentro'}, {oper: 'ni', text: 'nao esta dentro'}, {oper: 'ew', text: 'finalizacom'}, {oper: 'en', text: 'nao finaliza com'}, {oper: 'cn', text: 'contem'}, {oper: 'nc', text: 'nao contem'}],
                groupOps: [{op: "AND", text: "tudo"}, {op: "OR", text: "qualquer"}],
                operandTitle: "Click to select search operation.",
                resetTitle: "Reset Search Value",
                addsubgrup: "Add subgroup",
                addrule: "Add rule",
                delgroup: "Delete group",
                delrule: "Delete rule"
            },
            edit: {
                addCaption: "Adicionar Registo",
                editCaption: "Modificar Registo",
                bSubmit: "Submeter",
                bCancel: "Cancelar",
                bClose: "Fechar",
                saveData: "Dados foram alterados. Guardar?",
                bYes: "Sim",
                bNo: "Nao",
                bExit: "Cancelar",
                msg: {
                    required: "Campo obrigatório",
                    number: "Por favor, introduza um numero",
                    minValue: "O valor deve ser maior ou igual que",
                    maxValue: "O valor deve ser menor ou igual a",
                    email: "Não é um email válid",
                    integer: "Por favor, introduza um numero inteiro",
                    date: "Introduza una fecha correcta ",
                    url: "nao e um URL valido. Requerido prefixo ('http://' or 'https://')",
                    nodefined: " nao esta definido!",
                    novalue: " valor requerido!",
                    customarray: "Funcao customizada deve entrar!",
                    customfcheck: "Funcao customizada deve estar presente em caso deconfirmar customizacao!"
                }
            },
            view: {
                caption: "View Record",
                bClose: "Close"
            },
            del: {
                caption: "Eliminar",
                msg: "Deseja eliminar o(s) registo(s) seleccionado(s)?",
                bSubmit: "Eliminar",
                bCancel: "Cancelar"
            },
            nav: {
                edittext: " ",
                edittitle: "Modificar registo seleccionado",
                addtext: " ",
                addtitle: "Adicionar novo registo",
                deltext: " ",
                deltitle: "Eliminar registo seleccionado",
                searchtext: " ",
                searchtitle: "Procurar",
                refreshtext: "",
                refreshtitle: "Actualizar",
                alertcap: "Aviso",
                alerttext: "Por favor, seleccione um registo",
                viewtext: "",
                viewtitle: "Ver coluna selecionada",
                savetext: "",
                savetitle: "Save row",
                canceltext: "",
                canceltitle: "Cancel row editing",
                selectcaption: "Actions..."
            },
            col: {
                caption: "Mostrar/Ocultar Colunas",
                bSubmit: "Enviar",
                bCancel: "Cancelar"
            },
            errors: {
                errcap: "Erro",
                nourl: "Não especificou um url",
                norecords: "Não existem dados para processar",
                model: "Tamanho do colNames <> colModel!"
            },
            formatter: {
                integer: {thousandsSeparator: " ", defaultValue: '0'},
                number: {decimalSeparator: ".", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0.00'},
                currency: {decimalSeparator: ".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0.00'},
                date: {
                    dayNames: [
                        "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab",
                        "Domingo", "Segunda-Feira", "Terça-Feira", "Quarta-Feira", "Quinta-Feira", "Sexta-Feira", "Sábado"
                    ],
                    monthNames: [
                        "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez",
                        "Janeiro", "Fevereiro", "Mar�o", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"
                    ],
                    AmPm: ["am", "pm", "AM", "PM"],
                    S: function (j) {
                        return j < 11 || j > 13 ? ['�', '�', '�', '�'][Math.min((j - 1) % 10, 3)] : '�'
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'd/m/Y',
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "n/j/Y",
                        LongDate: "l, F d, Y",
                        FullDateTime: "l, F d, Y g:i:s A",
                        MonthDay: "F d",
                        ShortTime: "g:i A",
                        LongTime: "g:i:s A",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F, Y"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            },
            colmenu: {
                sortasc: "Sort Ascending",
                sortdesc: "Sort Descending",
                columns: "Columns",
                filter: "Filter",
                grouping: "Group By",
                ungrouping: "Ungroup",
                searchTitle: "Get items with value that:",
                freeze: "Freeze",
                unfreeze: "Unfreeze",
                reorder: "Move to reorder"
            }
        },
        /**
         * jqGrid Polish Translation
         * <NAME> <EMAIL>
         * http://FreeTree.pl
         *
         * Updated names, abbreviations, currency and date/time formats for Polish norms (also corresponding with CLDR v21.0.1 --> http://cldr.unicode.org/index) 
         * <NAME> <EMAIL>
         * http://tpeczek.blogspot.com; http://tpeczek.codeplex.com
         *
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html
         **/
        "pl": {
            defaults: {
                recordtext: "Pokaż {0} - {1} z {2}",
                emptyrecords: "Brak rekordów do pokazania",
                loadtext: "Ładowanie...",
                pgtext: "Strona {0} z {1}",
                savetext: "Saving...",
                pgfirst: "First Page",
                pglast: "Last Page",
                pgnext: "Next Page",
                pgprev: "Previous Page",
                pgrecs: "Records per Page",
                showhide: "Toggle Expand Collapse Grid",
                // mobile
                pagerCaption: "Grid::Page Settings",
                pageText: "Page:",
                recordPage: "Records per Page",
                nomorerecs: "No more records...",
                scrollPullup: "Pull up to load more...",
                scrollPulldown: "Pull down to refresh...",
                scrollRefresh: "Release to refresh..."
            },
            search: {
                caption: "Wyszukiwanie...",
                Find: "Szukaj",
                Reset: "Czyść",
                odata: [{oper: 'eq', text: "dokładnie"}, {oper: 'ne', text: "różne od"}, {oper: 'lt', text: "mniejsze od"}, {oper: 'le', text: "mniejsze lub równe"}, {oper: 'gt', text: "większe od"}, {oper: 'ge', text: "większe lub równe"}, {oper: 'bw', text: "zaczyna się od"}, {oper: 'bn', text: "nie zaczyna się od"}, {oper: 'in', text: "jest w"}, {oper: 'ni', text: "nie jest w"}, {oper: 'ew', text: "kończy się na"}, {oper: 'en', text: "nie kończy się na"}, {oper: 'cn', text: "zawiera"}, {oper: 'nc', text: "nie zawiera"}, {oper: 'nu', text: 'is null'}, {oper: 'nn', text: 'is not null'}, {oper: 'bt', text: 'between'}],
                groupOps: [{op: "AND", text: "oraz"}, {op: "OR", text: "lub"}],
                operandTitle: "Click to select search operation.",
                resetTitle: "Reset Search Value",
                addsubgrup: "Add subgroup",
                addrule: "Add rule",
                delgroup: "Delete group",
                delrule: "Delete rule"
            },
            edit: {
                addCaption: "Dodaj rekord",
                editCaption: "Edytuj rekord",
                bSubmit: "Zapisz",
                bCancel: "Anuluj",
                bClose: "Zamknij",
                saveData: "Dane zostały zmienione! Zapisać zmiany?",
                bYes: "Tak",
                bNo: "Nie",
                bExit: "Anuluj",
                msg: {
                    required: "Pole jest wymagane",
                    number: "Proszę wpisać poprawną liczbę",
                    minValue: "wartość musi być większa lub równa od",
                    maxValue: "wartość musi być mniejsza lub równa od",
                    email: "nie jest poprawnym adresem e-mail",
                    integer: "Proszę wpisać poprawną liczbę",
                    date: "Proszę podaj poprawną datę",
                    url: "jest niewłaściwym adresem URL. Pamiętaj o prefiksie ('http://' lub 'https://')",
                    nodefined: " niezdefiniowane!",
                    novalue: " wymagana jest wartość zwracana!",
                    customarray: "Funkcja niestandardowa powinna zwracać tablicę!",
                    customfcheck: "Funkcja niestandardowa powinna być obecna w przypadku niestandardowego sprawdzania!"
                }
            },
            view: {
                caption: "Pokaż rekord",
                bClose: "Zamknij"
            },
            del: {
                caption: "Usuń",
                msg: "Czy usunąć wybrany rekord(y)?",
                bSubmit: "Usuń",
                bCancel: "Anuluj"
            },
            nav: {
                edittext: "",
                edittitle: "Edytuj wybrany wiersz",
                addtext: "",
                addtitle: "Dodaj nowy wiersz",
                deltext: "",
                deltitle: "Usuń wybrany wiersz",
                searchtext: "",
                searchtitle: "Wyszukaj rekord",
                refreshtext: "",
                refreshtitle: "Przeładuj",
                alertcap: "Uwaga",
                alerttext: "Proszę wybrać wiersz",
                viewtext: "",
                viewtitle: "Pokaż wybrany wiersz",
                savetext: "",
                savetitle: "Save row",
                canceltext: "",
                canceltitle: "Cancel row editing",
                selectcaption: "Actions..."
            },
            col: {
                caption: "Pokaż/Ukryj kolumny",
                bSubmit: "Zatwierdź",
                bCancel: "Anuluj"
            },
            errors: {
                errcap: "Błąd",
                nourl: "Brak adresu url",
                norecords: "Brak danych",
                model: "Długość colNames <> colModel!"
            },
            formatter: {
                integer: {thousandsSeparator: " ", defaultValue: '0'},
                number: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0,00'},
                currency: {decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix: " zł", defaultValue: '0,00'},
                date: {
                    dayNames: [
                        "niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob.",
                        "niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"
                    ],
                    monthNames: [
                        "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru",
                        "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"
                    ],
                    AmPm: ["", "", "", ""],
                    S: function (j) {
                        return '';
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'd.m.Y',
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "d.m.y",
                        LongDate: "l, j F Y",
                        FullDateTime: "l, j F Y H:i:s",
                        MonthDay: "j F",
                        ShortTime: "H:i",
                        LongTime: "H:i:s",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F Y"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            },
            colmenu: {
                sortasc: "Sort Ascending",
                sortdesc: "Sort Descending",
                columns: "Columns",
                filter: "Filter",
                grouping: "Group By",
                ungrouping: "Ungroup",
                searchTitle: "Get items with value that:",
                freeze: "Freeze",
                unfreeze: "Unfreeze",
                reorder: "Move to reorder"
            }
        },
        "nl": {
            defaults:
                    {
                        recordtext: "regels {0} - {1} van {2}",
                        emptyrecords: "Geen data gevonden.",
                        loadtext: "Laden...",
                        pgtext: "pagina  {0}  van {1}",
                        savetext: "Saving...",
                        pgfirst: "Eerste Pagina",
                        pglast: "Laatste Pagina",
                        pgnext: "Volgende Pagina",
                        pgprev: "Vorige Pagina",
                        pgrecs: "Records per Pagina",
                        showhide: "Schakelen Uitklappen Inklappen Grid",
                        // mobile
                        pagerCaption: "Grid::Page Settings",
                        pageText: "Page:",
                        recordPage: "Records per Page",
                        nomorerecs: "No more records...",
                        scrollPullup: "Pull up to load more...",
                        scrollPulldown: "Pull down to refresh...",
                        scrollRefresh: "Release to refresh..."
                    },
            search:
                    {
                        caption: "Zoeken...",
                        Find: "Zoek",
                        Reset: "Herstellen",
                        odata: [{oper: 'eq', text: "gelijk aan"}, {oper: 'ne', text: "niet gelijk aan"}, {oper: 'lt', text: "kleiner dan"}, {oper: 'le', text: "kleiner dan of gelijk aan"}, {oper: 'gt', text: "groter dan"}, {oper: 'ge', text: "groter dan of gelijk aan"}, {oper: 'bw', text: "begint met"}, {oper: 'bn', text: "begint niet met"}, {oper: 'in', text: "is in"}, {oper: 'ni', text: "is niet in"}, {oper: 'ew', text: "eindigt met"}, {oper: 'en', text: "eindigt niet met"}, {oper: 'cn', text: "bevat"}, {oper: 'nc', text: "bevat niet"}, {oper: 'nu', text: 'is null'}, {oper: 'nn', text: 'is not null'}, {oper: 'bt', text: 'between'}],
                        groupOps: [{op: "AND", text: "alle"}, {op: "OR", text: "een van de"}],
                        operandTitle: "Klik om de zoekterm te selecteren.",
                        resetTitle: "Herstel zoekterm",
                        addsubgrup: "Add subgroup",
                        addrule: "Add rule",
                        delgroup: "Delete group",
                        delrule: "Delete rule"
                    },
            edit:
                    {
                        addCaption: "Nieuw",
                        editCaption: "Bewerken",
                        bSubmit: "Opslaan",
                        bCancel: "Annuleren",
                        bClose: "Sluiten",
                        saveData: "Er is data aangepast! Wijzigingen opslaan?",
                        bYes: "Ja",
                        bNo: "Nee",
                        bExit: "Sluiten",
                        msg:
                                {
                                    required: "Veld is verplicht",
                                    number: "Voer a.u.b. geldig nummer in",
                                    minValue: "Waarde moet groter of gelijk zijn aan ",
                                    maxValue: "Waarde moet kleiner of gelijk zijn aan",
                                    email: "is geen geldig e-mailadres",
                                    integer: "Voer a.u.b. een geldig getal in",
                                    date: "Voer a.u.b. een geldige waarde in",
                                    url: "is geen geldige URL. Prefix is verplicht ('http://' or 'https://')",
                                    nodefined: " is niet gedefineerd!",
                                    novalue: " return waarde is verplicht!",
                                    customarray: "Aangepaste functie moet array teruggeven!",
                                    customfcheck: "Aangepaste function moet aanwezig zijn in het geval van aangepaste controle!"
                                }
                    },
            view:
                    {
                        caption: "Tonen",
                        bClose: "Sluiten"
                    },
            del:
                    {
                        caption: "Verwijderen",
                        msg: "Verwijder geselecteerde regel(s)?",
                        bSubmit: "Verwijderen",
                        bCancel: "Annuleren"
                    },
            nav:
                    {
                        edittext: "",
                        edittitle: "Bewerken",
                        addtext: "",
                        addtitle: "Nieuw",
                        deltext: "",
                        deltitle: "Verwijderen",
                        searchtext: "",
                        searchtitle: "Zoeken",
                        refreshtext: "",
                        refreshtitle: "Vernieuwen",
                        alertcap: "Waarschuwing",
                        alerttext: "Selecteer a.u.b. een regel",
                        viewtext: "",
                        viewtitle: "Openen",
                        savetext: "",
                        savetitle: "Save row",
                        canceltext: "",
                        canceltitle: "Cancel row editing",
                        selectcaption: "Actions..."
                    },
            col:
                    {
                        caption: "Tonen/verbergen kolommen",
                        bSubmit: "OK",
                        bCancel: "Annuleren"
                    },
            errors:
                    {
                        errcap: "Fout",
                        nourl: "Er is geen URL gedefinieerd",
                        norecords: "Geen data om te verwerken",
                        model: "Lengte van 'colNames' is niet gelijk aan 'colModel'!"
                    },
            formatter:
                    {
                        integer:
                                {
                                    thousandsSeparator: ".",
                                    defaultValue: "0"
                                },
                        number:
                                {
                                    decimalSeparator: ",",
                                    thousandsSeparator: ".",
                                    decimalPlaces: 2,
                                    defaultValue: "0.00"
                                },
                        currency:
                                {
                                    decimalSeparator: ",",
                                    thousandsSeparator: ".",
                                    decimalPlaces: 2,
                                    prefix: "EUR ",
                                    suffix: "",
                                    defaultValue: "0.00"
                                },
                        date:
                                {
                                    dayNames: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag"],
                                    monthNames: ["Jan", "Feb", "Maa", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "October", "November", "December"],
                                    AmPm: ["am", "pm", "AM", "PM"],
                                    S: function (b) {
                                        return b < 11 || b > 13 ? ["st", "nd", "rd", "th"][Math.min((b - 1) % 10, 3)] : "th"
                                    },
                                    srcformat: "Y-m-d",
                                    newformat: "d/m/Y",
                                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                                    masks:
                                            {
                                                ISO8601Long: "Y-m-d H:i:s",
                                                ISO8601Short: "Y-m-d",
                                                ShortDate: "n/j/Y",
                                                LongDate: "l, F d, Y",
                                                FullDateTime: "l d F Y G:i:s",
                                                MonthDay: "d F",
                                                ShortTime: "G:i",
                                                LongTime: "G:i:s",
                                                SortableDateTime: "Y-m-d\\TH:i:s",
                                                UniversalSortableDateTime: "Y-m-d H:i:sO",
                                                YearMonth: "F, Y"
                                            },
                                    reformatAfterEdit: false,
                                    userLocalTime: false
                                },
                        baseLinkUrl: "",
                        showAction: "",
                        target: "",
                        checkbox:
                                {
                                    disabled: true
                                },
                        idName: "id"
                    },
            colmenu: {
                sortasc: "Sort Ascending",
                sortdesc: "Sort Descending",
                columns: "Columns",
                filter: "Filter",
                grouping: "Group By",
                ungrouping: "Ungroup",
                searchTitle: "Get items with value that:",
                freeze: "Freeze",
                unfreeze: "Unfreeze",
                reorder: "Move to reorder"
            }
        },
        /**
         * jqGrid Chinese Translation
         * 咖啡兔 <EMAIL> 
         * http://www.kafeitu.me 
         * 
         * 花岗岩 <EMAIL>
         * 
         * Dual licensed under the MIT and GPL licenses:
         * http://www.opensource.org/licenses/mit-license.php
         * http://www.gnu.org/licenses/gpl.html 
         **/
        "zh-cn": {
            defaults: {
                recordtext: "第{0}到第{1}条\u3000共 {2} 条", // 共字前是全角空格
                emptyrecords: "没有记录！",
                loadtext: "读取中...",
                savetext: "保存中...",
                pgtext: "第{0}页\u3000共{1}页",
                pgfirst: "第一页",
                pglast: "最后一页",
                pgnext: "下一页",
                pgprev: "上一页",
                pgrecs: "每页记录数",
                showhide: "切换 展开 折叠 表格",
                // mobile
                pagerCaption: "表格::页面设置",
                pageText: "Page:",
                recordPage: "每页记录数",
                nomorerecs: "没有更多记录...",
                scrollPullup: "加载更多...",
                scrollPulldown: "刷新...",
                scrollRefresh: "滚动刷新..."
            },
            search: {
                caption: "搜索...",
                Find: "查找",
                Reset: "重置",
                odata: [{oper: 'eq', text: '等于\u3000\u3000'}, {oper: 'ne', text: '不等于\u3000'}, {oper: 'lt', text: '小于\u3000\u3000'}, {oper: 'le', text: '小于等于'}, {oper: 'gt', text: '大于\u3000\u3000'}, {oper: 'ge', text: '大于等于'}, {oper: 'bw', text: '开头是'}, {oper: 'bn', text: '开头不是'}, {oper: 'in', text: '属于\u3000\u3000'}, {oper: 'ni', text: '不属于'}, {oper: 'ew', text: '结尾是'}, {oper: 'en', text: '结尾不是'}, {oper: 'cn', text: '包含\u3000\u3000'}, {oper: 'nc', text: '不包含'}, {oper: 'nu', text: '为空'}, {oper: 'nn', text: '不为空'}, {oper: 'bt', text: '区间'}],
                groupOps: [{op: "AND", text: "满足所有条件"}, {op: "OR", text: "满足任一条件"}],
                operandTitle: "单击进行搜索。",
                resetTitle: "重置搜索条件",
                addsubgrup: "添加条件组",
                addrule: "添加条件",
                delgroup: "删除条件组",
                delrule: "删除条件"
            },
            edit: {
                addCaption: "添加记录",
                editCaption: "编辑记录",
                bSubmit: "提交",
                bCancel: "取消",
                bClose: "关闭",
                saveData: "数据已修改，是否保存？",
                bYes: "是",
                bNo: "否",
                bExit: "取消",
                msg: {
                    required: "此字段必需",
                    number: "请输入有效数字",
                    minValue: "输值必须大于等于 ",
                    maxValue: "输值必须小于等于 ",
                    email: "这不是有效的e-mail地址",
                    integer: "请输入有效整数",
                    date: "请输入有效时间",
                    url: "无效网址。前缀必须为 ('http://' 或 'https://')",
                    nodefined: " 未定义！",
                    novalue: " 需要返回值！",
                    customarray: "自定义函数需要返回数组！",
                    customfcheck: "必须有自定义函数!"
                }
            },
            view: {
                caption: "查看记录",
                bClose: "关闭"
            },
            del: {
                caption: "删除",
                msg: "删除所选记录？",
                bSubmit: "删除",
                bCancel: "取消"
            },
            nav: {
                edittext: "",
                edittitle: "编辑所选记录",
                addtext: "",
                addtitle: "添加新记录",
                deltext: "",
                deltitle: "删除所选记录",
                searchtext: "",
                searchtitle: "查找",
                refreshtext: "",
                refreshtitle: "刷新表格",
                alertcap: "注意",
                alerttext: "请选择记录",
                viewtext: "",
                viewtitle: "查看所选记录",
                savetext: "",
                savetitle: "保存记录",
                canceltext: "",
                canceltitle: "取消编辑记录",
                selectcaption: "操作..."
            },
            col: {
                caption: "选择列",
                bSubmit: "确定",
                bCancel: "取消"
            },
            errors: {
                errcap: "错误",
                nourl: "没有设置url",
                norecords: "没有需要处理的记录",
                model: "colNames 和 colModel 长度不等！"
            },
            formatter: {
                integer: {thousandsSeparator: ",", defaultValue: '0'},
                number: {decimalSeparator: ".", thousandsSeparator: ",", decimalPlaces: 2, defaultValue: '0.00'},
                currency: {decimalSeparator: ".", thousandsSeparator: ",", decimalPlaces: 2, prefix: "", suffix: "", defaultValue: '0.00'},
                date: {
                    dayNames: [
                        "日", "一", "二", "三", "四", "五", "六",
                        "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六",
                    ],
                    monthNames: [
                        "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二",
                        "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"
                    ],
                    AmPm: ["am", "pm", "上午", "下午"],
                    S: function (j) {
                        return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th';
                    },
                    srcformat: 'Y-m-d',
                    newformat: 'Y-m-d',
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        // see http://php.net/manual/en/function.date.php for PHP format used in jqGrid
                        // and see http://docs.jquery.com/UI/Datepicker/formatDate
                        // and https://github.com/jquery/globalize#dates for alternative formats used frequently
                        // one can find on https://github.com/jquery/globalize/tree/master/lib/cultures many
                        // information about date, time, numbers and currency formats used in different countries
                        // one should just convert the information in PHP format
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        // short date:
                        //    n - Numeric representation of a month, without leading zeros
                        //    j - Day of the month without leading zeros
                        //    Y - A full numeric representation of a year, 4 digits
                        // example: 3/1/2012 which means 1 March 2012
                        ShortDate: "n/j/Y", // in jQuery UI Datepicker: "M/d/yyyy"
                        // long date:
                        //    l - A full textual representation of the day of the week
                        //    F - A full textual representation of a month
                        //    d - Day of the month, 2 digits with leading zeros
                        //    Y - A full numeric representation of a year, 4 digits
                        LongDate: "l, F d, Y", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy"
                        // long date with long time:
                        //    l - A full textual representation of the day of the week
                        //    F - A full textual representation of a month
                        //    d - Day of the month, 2 digits with leading zeros
                        //    Y - A full numeric representation of a year, 4 digits
                        //    g - 12-hour format of an hour without leading zeros
                        //    i - Minutes with leading zeros
                        //    s - Seconds, with leading zeros
                        //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                        FullDateTime: "l, F d, Y g:i:s A", // in jQuery UI Datepicker: "dddd, MMMM dd, yyyy h:mm:ss tt"
                        // month day:
                        //    F - A full textual representation of a month
                        //    d - Day of the month, 2 digits with leading zeros
                        MonthDay: "F d", // in jQuery UI Datepicker: "MMMM dd"
                        // short time (without seconds)
                        //    g - 12-hour format of an hour without leading zeros
                        //    i - Minutes with leading zeros
                        //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                        ShortTime: "g:i A", // in jQuery UI Datepicker: "h:mm tt"
                        // long time (with seconds)
                        //    g - 12-hour format of an hour without leading zeros
                        //    i - Minutes with leading zeros
                        //    s - Seconds, with leading zeros
                        //    A - Uppercase Ante meridiem and Post meridiem (AM or PM)
                        LongTime: "g:i:s A", // in jQuery UI Datepicker: "h:mm:ss tt"
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        // month with year
                        //    Y - A full numeric representation of a year, 4 digits
                        //    F - A full textual representation of a month
                        YearMonth: "F, Y" // in jQuery UI Datepicker: "MMMM, yyyy"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: '',
                showAction: '',
                target: '',
                checkbox: {disabled: true},
                idName: 'id'
            },
            colmenu: {
                sortasc: "升序排序",
                sortdesc: "降序排序",
                columns: "列",
                filter: "筛选",
                grouping: "分类",
                ungrouping: "取消分类",
                searchTitle: "查找:",
                freeze: "冻结",
                unfreeze: "取消冻结",
                reorder: "重新排序"
            }
        },
        "it": {
            defaults: {
                recordtext: "Mostra {0} - {1} di {2}",
                emptyrecords: "Non ci sono record da mostrare",
                loadtext: "Caricamento...",
                savetext: "Salvataggio...",
                pgtext: "Pagina {0} di {1}",
                pgfirst: "Prima Pagina",
                pglast: "Ultima Pagina",
                pgnext: "Pagina Successiva",
                pgprev: "Pagina Precedente",
                pgrecs: "Records per Pagina",
                showhide: "Espandi o collassa griglia",
                // mobile
                pagerCaption: "Griglia::Impostaioni della pagina",
                pageText: "Pagina:",
                recordPage: "Records per Pagina",
                nomorerecs: "Non ci sono altri record...",
                scrollPullup: "Trascina verso l'alto per altri...",
                scrollPulldown: "Trascina verso il basso per aggiornare...",
                scrollRefresh: "Rilascia per aggiornare..."
            },
            search: {
                caption: "Cerca...",
                Find: "Trova",
                Reset: "Reset",
                odata: [{oper: 'eq', text: 'uguale'}, {oper: 'ne', text: 'diverso'}, {oper: 'lt', text: 'minore'}, {oper: 'le', text: 'minore o uguale'}, {oper: 'gt', text: 'maggiore'}, {oper: 'ge', text: 'maggiore o uguale'}, {oper: 'bw', text: 'inizia per'}, {oper: 'bn', text: 'non inizia per'}, {oper: 'in', text: 'è in'}, {oper: 'ni', text: 'non è in'}, {oper: 'ew', text: 'finisce per'}, {oper: 'en', text: 'non finisce per'}, {oper: 'cn', text: 'contiene'}, {oper: 'nc', text: 'non contiene'}, {oper: 'nu', text: 'è null'}, {oper: 'nn', text: 'non è null'}, {oper: 'bt', text: 'between'}],
                groupOps: [{op: "AND", text: "tutti"}, {op: "OR", text: "ciascuno"}],
                operandTitle: "Clicca sull'opzione di ricerca scelta.",
                resetTitle: "Resetta valori di ricerca",
                addsubgrup: "Add subgroup",
                addrule: "Add rule",
                delgroup: "Delete group",
                delrule: "Delete rule"
            },
            edit: {
                addCaption: "Aggiungi Record",
                editCaption: "Modifica Record",
                bSubmit: "Invia",
                bCancel: "Annulla",
                bClose: "Chiudi",
                saveData: "I dati sono stati modificati! Salvare le modifiche?",
                bYes: "Si",
                bNo: "No",
                bExit: "Annulla",
                msg: {
                    required: "Campo obbligatorio",
                    number: "Per favore, inserisci un numero valido",
                    minValue: "il valore deve essere maggiore o uguale a ",
                    maxValue: "il valore deve essere minore o uguale a ",
                    email: "non è una e-mail valida",
                    integer: "Per favore, inserisci un intero valido",
                    date: "Per favore, inserisci una data valida",
                    url: "non è un URL valido. Prefissi richiesti ('http://' o 'https://')",
                    nodefined: " non è definito!",
                    novalue: " valore di ritorno richiesto!",
                    customarray: "La funzione personalizzata deve restituire un array!",
                    customfcheck: "La funzione personalizzata deve essere presente in caso di controlli personalizzati!"

                }
            },
            view: {
                caption: "Visualizza Record",
                bClose: "Chiudi"
            },
            del: {
                caption: "Cancella",
                msg: "Cancellare i record selezionati?",
                bSubmit: "Canella",
                bCancel: "Annulla"
            },
            nav: {
                edittext: "",
                edittitle: "Modifica riga selezionata",
                addtext: "",
                addtitle: "Aggiungi riga",
                deltext: "",
                deltitle: "Cancella riga",
                searchtext: "",
                searchtitle: "Trova record",
                refreshtext: "",
                refreshtitle: "Ricarica tabella",
                alertcap: "Attenzione",
                alerttext: "Per favore, seleziona un record",
                viewtext: "",
                viewtitle: "Visualizza riga selezionata",
                savetext: "",
                savetitle: "Salva riga",
                canceltext: "",
                canceltitle: "Annulla modifica riga",
                selectcaption: "Actions..."
            },
            col: {
                caption: "Seleziona colonne",
                bSubmit: "Ok",
                bCancel: "Annulla"
            },
            errors: {
                errcap: "Errore",
                nourl: "Nessun url impostato",
                norecords: "Non ci sono record da elaborare",
                model: "Lunghezza dei colNames <> colModel!"
            },
            formatter: {
                integer: {
                    thousandsSeparator: ".",
                    defaultValue: "0"
                },
                number: {
                    decimalSeparator: ",",
                    thousandsSeparator: ".",
                    decimalPlaces: 2,
                    defaultValue: "0,00"
                },
                currency: {
                    decimalSeparator: ",",
                    thousandsSeparator: ".",
                    decimalPlaces: 2,
                    prefix: "€ ",
                    suffix: "",
                    defaultValue: "0,00"
                },
                date: {
                    dayNames: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"],
                    monthNames: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic", "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
                    AmPm: ["am", "pm", "AM", "PM"],
                    S: function (b) {
                        return b < 11 || b > 13 ? ["st", "nd", "rd", "th"][Math.min((b - 1) % 10, 3)] : "th"
                    },
                    srcformat: "Y-m-d",
                    newformat: "d/m/Y",
                    parseRe: /[#%\\\/:_;.,\t\s-]/,
                    masks: {
                        ISO8601Long: "Y-m-d H:i:s",
                        ISO8601Short: "Y-m-d",
                        ShortDate: "d/m/Y",
                        LongDate: "l d F Y",
                        FullDateTime: "l d F Y G:i:s",
                        MonthDay: "F d",
                        ShortTime: "H:i",
                        LongTime: "H:i:s",
                        SortableDateTime: "Y-m-d\\TH:i:s",
                        UniversalSortableDateTime: "Y-m-d H:i:sO",
                        YearMonth: "F, Y"
                    },
                    reformatAfterEdit: false,
                    userLocalTime: false
                },
                baseLinkUrl: "",
                showAction: "",
                target: "",
                checkbox: {disabled: true},
                idName: "id"
            },
            colmenu: {
                sortasc: "Sort Ascending",
                sortdesc: "Sort Descending",
                columns: "Columns",
                filter: "Filter",
                grouping: "Group By",
                ungrouping: "Ungroup",
                searchTitle: "Get items with value that:",
                freeze: "Freeze",
                unfreeze: "Unfreeze",
                reorder: "Move to reorder"
            }
        }
    };

    $.extend($.jgrid, langs[lang]);
}

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigDatePicker.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import { KeyboardDatePicker } from '@material-ui/pickers';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    indeterminate: {
        opacity: 0.5
    }
});

class ConfigDatePicker extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
        this.setState({ value });
    }

    renderItem(error, disabled, defaultValue) {
        return <KeyboardDatePicker
            fullWidth
            margin="normal"
            format={this.props.systemConfig.dateFormat.toLowerCase().replace('mm','MM')}
            error={!!error}
            disabled={!!disabled}
            value={this.state.value === null || this.state.value === undefined ? new Date() : this.state.value}
            KeyboardButtonProps={{
                'aria-label': 'change date',
            }}
            inputProps={{ maxLength: this.props.schema.maxLength || this.props.schema.max || undefined }}
            onChange={value => {
                this.setState({ value }, () =>
                    this.onChange(this.props.attr, value));
            }}
            InputLabelProps={{
                shrink: true,
            }}
            placeholder={this.getText(this.props.schema.placeholder)}
            label={this.getText(this.props.schema.label)}
            helperText={this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}
        />;
    }
}

ConfigDatePicker.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigDatePicker);

<|start_filename|>src-rx/src/assets/rooms/parseNames.js<|end_filename|>
// this is service script that extracts names from names.txt and places them into list.json
/*
const fs = require('fs');

function readTxt(fileName) {
    const text = fs.readFileSync(fileName).toString('utf8');
    const lines = text.split('\n');
    const langs = lines.shift().split('\t').map(l => l.toLowerCase().trim());

    const result = {};
    lines.forEach(line => {
        const words = line.split('\t').map(w => w.trim());
        const word = {};
        result[words[0]] = word;
        langs.forEach((lang, i) => {
            word[lang] = words[i];
        });
    });
    return result;
}

function mergeTexts(jsonFileName, words) {
    const file = require(jsonFileName);
    file.forEach(item => {
        if (typeof item.name === 'string' && words[item.name]) {
            item.name = words[item.name];
        }
    });
    fs.writeFileSync(jsonFileName, JSON.stringify(file, null, 2));
}

const words = readTxt(__dirname + '/names.txt');
mergeTexts(__dirname + '/list.json', words);
*/

<|start_filename|>test/mocha.setup.js<|end_filename|>
// Don't silently swallow unhandled rejections
process.on('unhandledRejection', (e) => {
    throw e;
});

// enable the should interface with sinon
// and load chai-as-promised and sinon-chai by default
const sinonChai = require('sinon-chai');
const chaiAsPromised = require('chai-as-promised');
const { should, use } = require('chai');

should();
use(sinonChai);
use(chaiAsPromised);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigCustomEasyAccess.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Checkbox from '@material-ui/core/Checkbox';

import I18n from '@iobroker/adapter-react/i18n';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    table: {
        minWidth: 400
    },
    header: {
        fontSize: 16,
        fontWeight: 'bold'
    }
});

class ConfigCustomEasyAccess extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();

        this.props.socket.getAdapterInstances()
            .then(instances => {
                instances = instances
                    .filter(instance =>
                        instance?.common?.adminUI && (instance.common.adminUI.config !== 'none' || instance.common.adminUI.tab))
                    .map(instance => ({
                        id: instance._id.replace(/^system\.adapter\./, ''),
                        config: instance.common.adminUI.config !== 'none',
                        adminTab: instance.common.adminTab
                    }))
                    .sort((a, b) => a.id > b.id ? 1 : (a.id < b.id ? -1 : 0));

                this.setState({instances});
            });
    }

    renderItem(error, disabled, defaultValue) {
        if (!this.state.instances) {
            return null;
        } else {
            const accessAllowedConfigs = ConfigGeneric.getValue(this.props.data, 'accessAllowedConfigs') || [];
            const accessAllowedTabs    = ConfigGeneric.getValue(this.props.data, 'accessAllowedTabs')    || [];

            return <TableContainer>
                <Table className={this.props.classes.table} size="small">
                    <TableHead>
                        <TableRow>
                            <TableCell className={this.props.classes.header}>{I18n.t('Instance')}</TableCell>
                            <TableCell className={this.props.classes.header}>{I18n.t('Config')}</TableCell>
                            <TableCell className={this.props.classes.header}>{I18n.t('Tab')}</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        {this.state.instances.map((row) => (
                            <TableRow key={row.id}>
                                <TableCell component="th" scope="row">{row.id}</TableCell>
                                <TableCell>
                                    {row.config ?
                                        <Checkbox checked={accessAllowedConfigs.includes(row.id)}
                                            onClick={() => {
                                                const _accessAllowedConfigs = [...accessAllowedConfigs];
                                                const pos = _accessAllowedConfigs.indexOf(row.id);
                                                if (pos !== -1) {
                                                    _accessAllowedConfigs.splice(pos, 1);
                                                } else {
                                                    _accessAllowedConfigs.push(row.id);
                                                    _accessAllowedConfigs.sort();
                                                }
                                                this.onChange('accessAllowedConfigs', _accessAllowedConfigs);
                                            }}
                                        />
                                    : null}</TableCell>
                                <TableCell>
                                    {row.adminTab ?
                                        <Checkbox
                                            checked={accessAllowedTabs.includes(row.id)}
                                             onClick={() => {
                                                 const _accessAllowedTabs = [...accessAllowedTabs];
                                                 const pos = _accessAllowedTabs.indexOf(row.id);
                                                 if (pos !== -1) {
                                                     _accessAllowedTabs.splice(pos, 1);
                                                 } else {
                                                     _accessAllowedTabs.push(row.id);
                                                     _accessAllowedTabs.sort();
                                                 }
                                                 this.onChange('accessAllowedTabs', _accessAllowedTabs);
                                             }}
                                        /> : null}
                                </TableCell>
                            </TableRow>
                        ))}
                    </TableBody>
                </Table>
            </TableContainer>;
        }
    }
}

ConfigCustomEasyAccess.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigCustomEasyAccess);

<|start_filename|>src-rx/src/dialogs/AdapterInfoDialog.js<|end_filename|>
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import { AppBar } from '@material-ui/core';
import { Button } from '@material-ui/core';
import { Grid } from '@material-ui/core';
import { Toolbar } from '@material-ui/core';

import CloseIcon from '@material-ui/icons/Close';
import LinkIcon from '@material-ui/icons/Link';

import Router from '@iobroker/adapter-react/Components/Router';
import I18n from '@iobroker/adapter-react/i18n';
import Loader from '@iobroker/adapter-react/Components/Loader';

import Markdown from '../components/Markdown';

const styles = {
    root: {
        height: '100%'
    },
    scroll: {
        height: '100%',
        overflowY: 'auto',
        '& img': {
            maxWidth: '100%'
        },
        fontSize: 14,
    }
};

class AdapterInfoDialog extends Component {

    constructor(props) {

        super(props);

        const uri = `https://www.iobroker.net/${I18n.getLanguage()}/adapterref/iobroker.${props.adapter}/README.md`;
        //this.props.link.replace('https://github.com', 'https://raw.githubusercontent.com').replace('blob/', '');
        const uriGithub = this.props.link.replace('https://github.com', 'https://raw.githubusercontent.com').replace('blob/', '');
        const rawUri = uriGithub.replace('blob/', '').substring(0, uriGithub.lastIndexOf('/') + 1);

        this.state = {
            tab: 0,
            readme: '',
            uri,
            rawUri
        };

        this.t = props.t;
    }

    async componentDidMount() {
        try {
            const data = await fetch(this.state.uri);
            let readme = await data.text();
            /*const lines = readme.split('\n');
            if (lines[0].trim() === '---') {
                let i = 1;
                while(lines[i] !== '---') i++;
                lines.splice(0, i + 1);
                readme = lines.join('\n');
                lines.unshift(`![Logo](admin/${this.props.adapter}.png)`);
                lines.unshift('# ioBroker.' + this.props.adapter);
                lines.unshift('');
            }
            const split = this.splitReadMe(readme);*/
            readme = readme.replace(/\(([-\w]+)\/adapterref\//g, '(https://www.iobroker.net/$1/adapterref/');
            /*readme = readme.replace(/\(en\/adapterref\//g, '(https://www.iobroker.net/en/adapterref/');
            readme = readme.replace(/\(de\/adapterref\//g, '(https://www.iobroker.net/de/adapterref/');
            readme = readme.replace(/\(zh-cn\/adapterref\//g, '(https://www.iobroker.net/zh-cn/adapterref/');
            readme = readme.replace(/\(zh-cn\/adapterref\//g, '(https://www.iobroker.net/zh-cn/adapterref/');*/
            readme = readme.replace(/src="([-\w]+)\/adapterref\//g, 'src="https://www.iobroker.net/zh-cn/adapterref/');

            this.setState({text: readme});
        } catch(error) {
            window.alert(error);
        }
    }

    trimArr(lines) {

        let j = lines.length - 1;

        while (j >= 0 && !lines[j]) {
            j--;
        }

        if (j !== lines.length - 1) {
            lines.splice(j + 1);
        }

        return lines;
    }

    splitReadMe(html, link) {

        const result = {logo: '', readme: [], changelog: [], license: []};
        let lines = html.trim().split(/\r\n|\n/);

        // second line is main title
        if (lines[2].match(/^#\sio/)) {
            lines.splice(2, 1);
        }

        if (lines[1].match(/^#\sio/)) {
            lines.splice(1, 1);
        }
        // first line is logo
        if (lines[0].match(/!\[[-_\w\d]*]\([-._\w\d/]+\.png\)/)) {
            result.logo = link + lines[0].match(/\((.+)\)/)[1];
            lines.splice(0, 1);
        }

        let part = 'readme';

        for (let i = 0; i < lines.length; i++) {

            if (lines[i].match(/^====/)) {
                continue;
            }

            if (lines[i].match(/^###?\s+Changelog/)) {
                part = 'changelog';
                continue;

            } else if (lines[i].match(/^###?\s+License/)) {
                part = 'license';
                continue;
            } else if (lines[i].match(/^##?\s+.+/)) {
                part = 'readme';
            }

            if (!result[part].length && !lines[i]) {
                continue;
            }

            result[part].push(lines[i]);
        }



        if (result.logo) {
           // that.$divLogo.html('<img src="' + result.logo + '" />').show();
        } else {
           // that.$divLogo.html('').hide();
        }

        this.trimArr(result.readme);
        this.trimArr(result.changelog);
        this.trimArr(result.license);

        if (result.readme.length) {
            result.readme = result.readme.join('\n');
        } else {
            result.readme = '';
        }
        if (result.changelog.length) {
            result.changelog = result.changelog.join('\n');
        } else {
            delete result.changelog;
        }
        if (result.license.length) {
            result.license[0] = '## ' + result.license[0];
            result.license = result.license.join('\n');
        } else {
            delete result.license;
        }

        return result;
    }

    changeTab(event, newValue) {
        this.setState({
            tab: newValue
        });
    }

    openTab(path) {
        const tab = window.open(path, '_blank');
        tab.focus();
    }

    closeDialog() {
        Router.doNavigate('tab-adapters');
    }

    transformUri(uri) {
        return (uri && uri.startsWith('http') ? '' : this.state.rawUri) + uri;
    }

    render() {
        if (!this.state.text) {
            return <Loader theme={this.props.theme}/>;
        }
        const { classes } = this.props;
        //const { tab } = this.state;

        return <Grid
            item
            container
            direction="column"
            wrap="nowrap"
            className={ classes.root }
        >
            {/*<AppBar color="default" position="static">
                <Tabs value={ this.state.tab } onChange={ (event, newValue) => this.changeTab(event, newValue) }>
                    <Tab label="README" disabled={ !this.state.readme }/>
                    <Tab label="Changelog" disabled={ !this.state.changelog }/>
                    <Tab label="License" disabled={ !this.state.license }/>
                </Tabs>
            </AppBar>
            <Box p={ 3 } className={ classes.scroll }>
                <ReactMarkdown
                    source={ this.state[tab === 1 ? 'changelog' : tab === 2 ? 'license' : 'readme'] }
                    linkTarget="_blank"
                    transformLinkUri={ (uri) => this.transformUri(uri) }
                    transformImageUri={ (uri) => this.transformUri(uri) }
                    escapeHtml={ false }
                />
            </Box>*/}
            <Markdown className={ classes.scroll }
                text={this.state.text}
                language={I18n.getLanguage()}
                theme={this.props.theme}
                themeName={this.props.themeName}
                themeType={this.props.themeType}
                mobile={this.props.mobile}
                editMode={false}
                socket={this.props.socket}
                adapter={this.props.adapter}
                //                https://github.com/ioBroker/ioBroker.admin/blob/master/README.md =>
                // https://raw.githubusercontent.com/ioBroker/ioBroker.admin/master/README.md
                link={this.props.link.replace('https://github.com/', 'https://raw.githubusercontent.com/').replace('/blob/', '/')}
                //onNavigate={(language, tab, page, chapter) => this.onNavigate(language, tab, page, chapter)}
            />
            <AppBar  color="default" position="static">
                <Toolbar>
                    <Grid container spacing={ 1 }>
                        <Grid item>
                            <Button
                                variant="contained"
                                color="primary"
                                onClick={ () => this.openTab(this.props.link) }
                                startIcon={<LinkIcon />}
                            >
                                { this.t('Open original') }
                            </Button>
                        </Grid>
                        <Grid item>
                            <Button
                                variant="contained"
                                color="default"
                                onClick={ () => this.closeDialog() }
                                startIcon={<CloseIcon />}
                            >
                                { this.t('Close') }
                            </Button>
                        </Grid>
                    </Grid>
                </Toolbar>
            </AppBar>
        </Grid>;
    }
}

AdapterInfoDialog.propTypes = {
    adapter: PropTypes.string,
    link: PropTypes.string,
    t: PropTypes.func,
    theme: PropTypes.object,
    themeName: PropTypes.string,
    themeType: PropTypes.string,
    socket: PropTypes.object
};

export default withStyles(styles)(AdapterInfoDialog);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigTextSendTo.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';

import copy from '@iobroker/adapter-react/Components/copy-to-clipboard';
import CopyIcon from '@iobroker/adapter-react/icons/IconCopy';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    }
});

class ConfigTextSendTo extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();

        this.askInstance();
    }

    askInstance() {
        if (this.props.alive) {
            let data = this.props.schema.data;
            if (data === undefined && this.props.schema.jsonData) {
                data = this.getPattern(this.props.schema.jsonData);
                try {
                    data = JSON.parse(data);
                } catch (e) {
                    console.error('Cannot parse json data: ' + data);
                }
            }

            if (data === undefined) {
                data = null;
            }

            this.props.socket.sendTo(this.props.adapterName + '.' + this.props.instance, this.props.schema.command || 'send', data)
                .then(text => this.setState({text: text || '', context: this.getContext()}));
        }
    }

    getContext() {
        const context = {};
        if (Array.isArray(this.props.schema.alsoDependsOn)) {
            this.props.schema.alsoDependsOn.forEach(attr =>
                context[attr] = ConfigGeneric.getValue(this.props.data, attr));
        }
        return JSON.stringify(context);
    }

    renderItem(error, disabled, defaultValue) {
        if (this.state.text === undefined) {
            return null;
        }

        if (this.props.alive) {
            const context = this.getContext();
            if (context !== this.state.context) {
                setTimeout(() => {
                    this.askInstance();
                }, 300);
            }
        }

        if (this.props.schema.container === 'text') {
            return <TextField
                fullWidth
                InputProps={{
                    endAdornment: this.props.schema.copyToClipboard ?
                        <IconButton
                            size="small"
                            onClick={e => {
                                copy(this.state.text);
                                window.alert('Copied');
                            }}>
                            <CopyIcon/>
                        </IconButton>
                        : undefined,
                }}
                value={this.state.text}
                label={this.getText(this.props.schema.label)}
                helperText={this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}
            />;
        } else {
            return <div className={this.props.classes.fullWidth}>{this.state.text}</div>;
        }
    }
}

ConfigTextSendTo.propTypes = {
    socket: PropTypes.object.isRequired,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    adapterName: PropTypes.string,
    instance: PropTypes.number,
};

export default withStyles(styles)(ConfigTextSendTo);

<|start_filename|>src/lib/js/cron/jquery.cron.words.js<|end_filename|>
/*global jQueryCronWords:true */
'use strict';

var jQueryCronWords = {
    "April": {
		"en": "April",
		"de": "April",
		"ru": "Апр",
		"pt": "Abril",
		"nl": "April",
		"fr": "Avril",
		"it": "Aprile",
		"es": "Abril",
                "pl": "kwiecień",
                "zh-cn": "四月"
	},
    "Aug": {
		"en": "Aug",
		"de": "Aug",
		"ru": "Авг",
		"pt": "Ago",
		"nl": "Aug",
		"fr": "Août",
		"it": "Ago",
		"es": "Ago",
                "pl": "Sier",
                "zh-cn": "八月"
	},
    "CRON Every": {
		"en": "Every",
		"de": "Alle",
		"ru": "Каждые",
		"pt": "Cada",
		"nl": "Elk",
		"fr": "Chaque",
		"it": "Ogni",
		"es": "Cada",
                "pl": "Każdy",
                "zh-cn": "一切"
	},
    "CRON Every day": {
		"en": "Every day",
		"de": "<NAME>",
		"ru": "Каждый день",
		"pt": "Todo dia",
		"nl": "Elke dag",
		"fr": "Tous les jours",
		"it": "Ogni giorno",
		"es": "Cada día",
                "pl": "Codzienny",
                "zh-cn": "每天"
	},
    "CRON Every hour": {
		"en": "Every hour",
		"de": "Jede Stunde",
		"ru": "Каждый час",
		"pt": "Toda hora",
		"nl": "Elk uur",
		"fr": "Toutes les heures",
		"it": "Ogni ora",
		"es": "Cada hora",
                "pl": "Co godzinę",
                "zh-cn": "每隔一小时"
	},
    "CRON Every minute": {
		"en": "Every minute",
		"de": "Jede Minute",
		"ru": "Каждую минуту",
		"pt": "Todo minuto",
		"nl": "Elke minuut",
		"fr": "Chaque minute",
		"it": "Ogni minuto",
		"es": "Cada minuto",
                "pl": "Każda minuta",
                "zh-cn": "每一分钟"
	},
    "CRON Every month": {
		"en": "Every month",
		"de": "Jeden Monat",
		"ru": "Каждый месяц",
		"pt": "Todo mês",
		"nl": "Elke maand",
		"fr": "Chaque mois",
		"it": "Ogni mese",
		"es": "Cada mes",
                "pl": "Każdego miesiąca",
                "zh-cn": "每个月"
	},
    "CRON Every second": {
		"en": "Every second",
		"de": "Jede Sekunde",
		"ru": "Каждую секунду",
		"pt": "Todo segundo",
		"nl": "Elke seconde",
		"fr": "Chaque seconde",
		"it": "Ogni secondo",
		"es": "Cada segundo",
                "pl": "Każda sekunda",
                "zh-cn": "每一秒"
	},
    "CRON Every week day": {
		"en": "Every week day",
		"de": "<NAME>",
		"ru": "Каждый день недели",
		"pt": "Todos os dias da semana",
		"nl": "Elke weekdag",
		"fr": "Tous les jours de la semaine",
		"it": "Ogni giorno della settimana",
		"es": "Todos los días de la semana",
                "pl": "W każdy dzień roboczy",
                "zh-cn": "每个工作日"
	},
    "CRON day": {
		"en": "day",
		"de": "Tag",
		"ru": "день",
		"pt": "dia",
		"nl": "dag",
		"fr": "jour",
		"it": "giorno",
		"es": "día",
                "pl": "dzień",
                "zh-cn": "天"
	},
    "CRON days": {
		"en": "days",
		"de": "Tage",
		"ru": "дней",
		"pt": "dias",
		"nl": "Dagen",
		"fr": "jours",
		"it": "giorni",
		"es": "dias",
                "pl": "dni",
                "zh-cn": "天"
	},
    "CRON hour": {
		"en": "hour",
		"de": "Stunde",
		"ru": "час",
		"pt": "hora",
		"nl": "uur",
		"fr": "heure",
		"it": "ora",
		"es": "hora",
                "pl": "godzina",
                "zh-cn": "小时"
	},
    "CRON hours": {
		"en": "hours",
		"de": "Stunden",
		"ru": "часа(ов)",
		"pt": "horas",
		"nl": "Uren",
		"fr": "heures",
		"it": "ore",
		"es": "horas",
                "pl": "godziny",
                "zh-cn": "小时"
	},
    "CRON minute": {
		"en": "minute",
		"de": "Minute",
		"ru": "минуту",
		"pt": "minuto",
		"nl": "minuut",
		"fr": "minute",
		"it": "Minuto",
		"es": "minuto",
                "pl": "minuta",
                "zh-cn": "分钟"
	},
    "CRON minutes": {
		"en": "minutes",
		"de": "Minuten",
		"ru": "минут(ы)",
		"pt": "minutos",
		"nl": "notulen",
		"fr": "minutes",
		"it": "minuti",
		"es": "minutos",
                "pl": "minuty",
                "zh-cn": "分钟"
	},
    "CRON month": {
		"en": "month",
		"de": "Monat",
		"ru": "месяц",
		"pt": "mês",
		"nl": "maand",
		"fr": "mois",
		"it": "mese",
		"es": "mes",
                "pl": "miesiąc",
                "zh-cn": "月"
	},
    "CRON months": {
		"en": "months",
		"de": "Monate",
		"ru": "месяца",
		"pt": "meses",
		"nl": "maanden",
		"fr": "mois",
		"it": "mesi",
		"es": "meses",
                "pl": "miesięcy",
                "zh-cn": "个月"
	},
    "CRON second": {
		"en": "second",
		"de": "Sekunde",
		"ru": "секунду",
		"pt": "segundo",
		"nl": "tweede",
		"fr": "seconde",
		"it": "secondo",
		"es": "segundo",
                "pl": "druga",
                "zh-cn": "第二"
	},
    "CRON seconds": {
		"en": "seconds",
		"de": "Sekunden",
		"ru": "секунд(ы)",
		"pt": "segundos",
		"nl": "Seconden",
		"fr": "secondes",
		"it": "secondi",
		"es": "segundos",
                "pl": "sekundy",
                "zh-cn": "秒"
	},
    "CRON Clear": {
		"en": "Clear",
		"de": "Löschen",
		"ru": "Clear",
		"pt": "Limpar",
		"nl": "Duidelijk",
		"fr": "Effacer",
		"it": "Pulito",
		"es": "Limpio",
                "pl": "Jasny",
                "zh-cn": "明确"
	},
    "CRON Day of Month": {
		"en": "Day of Month",
		"de": "Monatstag",
		"ru": "День месяца",
		"pt": "Dia do mês",
		"nl": "Dag van de maand",
		"fr": "Jour du mois",
		"it": "Giorno del mese",
		"es": "Dia del mes",
                "pl": "",
                "zh-cn": ""
		},
    "CRON Day of Week": {
		"en": "Day of Week",
		"de": "Wochentag",
		"ru": "Дни недели",
		"pt": "Dia da semana",
		"nl": "Dag van de week",
		"fr": "Jour de la semaine",
		"it": "Giorno della settimana",
		"es": "Día de la semana",
                "pl": "Dzień miesiąca",
                "zh-cn": "一个月的一天"
	},
    "CRON dialog": {
		"en": "CRON dialog",
		"de": "CRON Dialog",
                "ru": "CRON диалог",
                "pt": "CRON diálogo",
                "nl": "CRON dialoog",
                "fr": "Dialogue CRON",
                "it": "CRON dialogo",
                "es": "CRON diálogo",
                "pl": "Okno dialogowe CRON",
                "zh-cn": "CRON对话框"
    },
    "CRON Type": {
                "en": "Type",
                "de": "Art",
                "ru": "Тип",
                "pt": "Tipo",
                "nl": "Type",
                "fr": "Type",
                "it": "Genere",
                "es": "Tipo",
                "pl": "Rodzaj",
                "zh-cn": "类型"
    },
    "CRON Apply": {
                "en": "Apply",
                "de": "Anwenden",
                "ru": "Перенять",
                "pt": "Aplique",
                "nl": "Toepassen",
                "fr": "Appliquer",
                "it": "Applicare",
                "es": "Aplicar",
                "pl": "Zastosować",
                "zh-cn": "应用"
    },
    "CRON Cancel": {
                "en": "Cancel",
                "de": "Abbrechen",
                "ru": "Отмена",
                "pt": "Cancelar",
                "nl": "Annuleer",
                "fr": "Annuler",
                "it": "Annulla",
                "es": "Cancelar",
                "pl": "Anuluj",
                "zh-cn": "取消"
    },
    "Dec": {
		"en": "Dec",
		"de": "Dez",
		"ru": "Дек",
		"pt": "Dec",
		"nl": "DEC",
		"fr": "Déc.",
		"it": "Dec",
		"es": "Dic",
                "pl": "Grud",
                "zh-cn": "十二月"
	},
    "Each day": {
		"en": "Each Day",
		"de": "Jeden Tag",
		"ru": "Каждый день",
		"pt": "Cada dia",
		"nl": "Elke dag",
		"fr": "Chaque jour",
		"it": "Ogni giorno",
		"es": "Cada día",
                "pl": "Każdego dnia",
                "zh-cn": "每天"
	},
    "Each month": {
		"en": "Each Month",
		"de": "<NAME>",
		"ru": "Каждый месяц",
		"pt": "Cada mês",
		"nl": "Elke maand",
		"fr": "Chaque mois",
		"it": "Ogni mese",
		"es": "Cada mes",
                "pl": "Każdego miesiąca",
                "zh-cn": "每月"
	},
    "CRON Each selected day": {
		"en": "Each selected Day",
		"de": "Jeden ausgewählten Tag",
		"ru": "Каждый выбранный день",
		"pt": "Cada dia selecionado",
		"nl": "Elke geselecteerde dag",
		"fr": "Chaque jour sélectionné",
		"it": "Ogni giorno selezionato",
		"es": "Cada día seleccionado",
                "pl": "Każdy wybrany dzień",
                "zh-cn": "每个选定日"
	},
    "CRON Each selected hour": {
		"en": "Each Selected Hour",
		"de": "Jede ausgewählte Stunde",
		"ru": "Каждый выбранный час",
		"pt": "Cada Hora Selecionada",
		"nl": "Elk geselecteerd uur",
		"fr": "Chaque heure sélectionnée",
		"it": "Ogni ora selezionata",
		"es": "Cada hora seleccionada",
                "pl": "Każda wybrana godzina",
                "zh-cn": "每个选定的小时"
	},
    "CRON Each selected minute": {
		"en": "Each selected minute",
		"de": "Jede ausgewählte Minute",
		"ru": "Каждую выбранную минуту",
		"pt": "Cada minuto selecionado",
		"nl": "Elke geselecteerde minuut",
		"fr": "Chaque minute sélectionnée",
		"it": "Ogni minuto selezionato",
		"es": "Cada minuto seleccionado",
                "pl": "Każda wybrana minuta",
                "zh-cn": "每个选定的分钟"
	},
    "CRON Each selected month": {
		"en": "Each selected month",
		"de": "Jeden ausgewählten Monat",
		"ru": "Каждый выбранный месяц",
		"pt": "Cada mês selecionado",
		"nl": "Elke geselecteerde maand",
		"fr": "Chaque mois sélectionné",
		"it": "Ogni mese selezionato",
		"es": "Cada mes seleccionado",
                "pl": "Każdy wybrany miesiąc",
                "zh-cn": "每个选定的月份"
	},
    "CRON Each selected second": {
		"en": "Each selected second",
		"de": "Jede ausgewählte Sekunde",
		"ru": "Каждую выбранную секунду",
		"pt": "Cada segundo selecionado",
		"nl": "Elke geselecteerde seconde",
		"fr": "Chaque seconde sélectionnée",
		"it": "Ogni secondo selezionato",
		"es": "Cada segundo seleccionado",
                "pl": "Każda wybrana sekunda",
                "zh-cn": "每个选定的秒"
	},
    "CRON Each selected week day": {
		"en": "Each selected week day",
		"de": "Jeden ausgewählten Wochentag",
		"ru": "Каждый выбранный день недели",
		"pt": "Cada dia da semana selecionado",
		"nl": "Elke geselecteerde weekdag",
		"fr": "Chaque jour de la semaine sélectionné",
		"it": "Ogni giorno della settimana selezionato",
		"es": "Cada día de la semana seleccionado",
                "pl": "Każdy wybrany dzień tygodnia",
                "zh-cn": "每个选定的工作日"
	},
    "CRON Every %s seconds": {
		"en": "Every %s seconds",
		"de": "Alle %s Sekunden",
		"ru": "Каждые %s секунд(ы)",
		"pt": "Todos os %s segundos",
		"nl": "Elke% s seconden",
		"fr": "Chaque %s seconde",
		"it": "Ogni %s secondi",
		"es": "Cada %s segundos",
                "pl": "Co %s sekund",
                "zh-cn": "每%s秒"
	},
    "CRON Every n hours": {
		"en": "Every n Hours",
		"de": "Alle N Stunden",
		"ru": "Каждые N часов",
		"pt": "Todas as horas",
		"nl": "Every n Hours",
		"fr": "Toutes les n heures",
		"it": "Ogni n ore",
		"es": "Cada n horas",
                "pl": "Co n godzin",
                "zh-cn": "每n小时一次"
	},
    "CRON Every n minutes": {
		"en": "Every n minutes",
		"de": "Alle N Minuten",
		"ru": "Каждые N минут",
		"pt": "A cada n minutos",
		"nl": "Elke n minuten",
		"fr": "Toutes les n minutes",
		"it": "Ogni n minuti",
		"es": "Cada n minutos",
                "pl": "Co n minut",
                "zh-cn": "每隔n分钟"
	},
    "CRON Every n seconds": {
		"en": "Every n seconds",
		"de": "Alle N Sekunden",
		"ru": "Каждые N секунд",
		"pt": "Cada n segundos",
		"nl": "Elke n seconden",
		"fr": "Toutes les n secondes",
		"it": "Ogni n secondi",
		"es": "Cada n segundos",
                "pl": "Co n sekund",
                "zh-cn": "每隔n秒"
	},
    "Feb": {
		"en": "Feb",
		"de": "Feb",
		"ru": "Фев",
		"pt": "Feb",
		"nl": "Feb",
		"fr": "Févr.",
		"it": "Feb",
		"es": "Feb",
                "pl": "Luty",
                "zh-cn": "二月"
	},
    "Friday": {
		"en": "Friday",
		"de": "Freitag",
		"ru": "Пятница",
		"pt": "Sexta-feira",
		"nl": "Vrijdag",
		"fr": "Vendredi",
		"it": "Venerdì",
		"es": "Viernes",
                "pl": "Piątek",
                "zh-cn": "星期五"
	},
    "CRON Hour": {
		"en": "Hour",
		"de": "Stunden",
		"ru": "Часы",
		"pt": "Hora",
		"nl": "Uur",
		"fr": "Heure",
		"it": "Ora",
		"es": "Hora",
                "pl": "Godzina",
                "zh-cn": "小时"
	},
    "Jan": {
		"en": "Jan",
		"de": "Jan",
		"ru": "Янв",
		"pt": "Jan",
		"nl": "Jan",
		"fr": "Janv.",
		"it": "Jan",
		"es": "Ene",
                "pl": "Styc",
                "zh-cn": "一月"
	},
    "July": {
		"en": "July",
		"de": "Juli",
		"ru": "Июль",
		"pt": "Julho",
		"nl": "Juli",
		"fr": "Juillet",
		"it": "Luglio",
		"es": "Julio",
                "pl": "Lipiec",
                "zh-cn": "七月"
	},
    "June": {
		"en": "June",
		"de": "Juni",
		"ru": "Июнь",
		"pt": "Junho",
		"nl": "Juni",
		"fr": "Juin",
		"it": "Giugno",
		"es": "Junio",
                "pl": "Czerw",
                "zh-cn": "六月"
	},
    "March": {
		"en": "March",
		"de": "März",
		"ru": "Март",
		"pt": "Marcha",
		"nl": "Maart",
		"fr": "Mars",
		"it": "Marzo",
		"es": "Marzo",
                "pl": "Marsz",
                "zh-cn": "游行"
	},
    "May": {
		"en": "May",
		"de": "Mai",
		"ru": "Mай",
		"pt": "Maio",
		"nl": "Mei",
		"fr": "Mai",
		"it": "Maggio",
		"es": "Mayo",
                "pl": "Może",
                "zh-cn": "可以"
	},
    "CRON Minute": {
		"en": "Minute",
		"de": "Minuten",
		"ru": "Минуты",
		"pt": "Minuto",
		"nl": "Minuut",
		"fr": "Minute",
		"it": "Minuto",
		"es": "Minuto",
                "pl": "Minuta",
                "zh-cn": "分钟"
	},
    "Monday": {
		"en": "Monday",
		"de": "Montag",
		"ru": "Понедельник",
		"pt": "Segunda-feira",
		"nl": "Maandag",
		"fr": "Lundi",
		"it": "Lunedi",
		"es": "Lunes",
                "pl": "Poniedziałek",
                "zh-cn": "星期一"
	},
    "CRON Month": {
		"en": "Month",
		"de": "Monate",
		"ru": "Месяцы",
		"pt": "Mês",
		"nl": "Maand",
		"fr": "Mois",
		"it": "Mese",
		"es": "Mes",
                "pl": "Miesiąc",
                "zh-cn": "月"
	},
    "Nov": {
		"en": "Nov",
		"de": "Nov",
		"ru": "Ноя",
		"pt": "Nov",
		"nl": "Nov",
		"fr": "Nov.",
		"it": "Nov",
		"es": "Nov",
                "pl": "Listo",
                "zh-cn": "十一月"
	},
    "Oct": {
		"en": "Oct",
		"de": "Okt",
		"ru": "Окт",
		"pt": "Oct",
		"nl": "Oct",
		"fr": "Oct.",
		"it": "Ottobre",
		"es": "Oct",
                "pl": "Paźdz",
                "zh-cn": "十月"
	},
    "Saturday": {
		"en": "Saturday",
		"de": "Samstag",
		"ru": "Суббота",
		"pt": "Sábado",
		"nl": "Zaterdag",
		"fr": "Samedi",
		"it": "Sabato",
		"es": "Sábado",
                "pl": "Sobota",
                "zh-cn": "星期六"
	},
    "CRON Second": {
		"en": "Second",
		"de": "Sekunden",
		"ru": "Секунды",
		"pt": "Segundo",
		"nl": "Tweede",
		"fr": "Seconde",
		"it": "Secondo",
		"es": "Segundo",
                "pl": "Druga",
                "zh-cn": "第二"
	},
    "Sept": {
		"en": "Sept",
		"de": "Sept",
		"ru": "Сен",
		"pt": "Sept",
		"nl": "Sept",
		"fr": "Sept.",
		"it": "Set",
		"es": "Sept",
                "pl": "Wrzes",
                "zh-cn": "九月"
	},
    "Sunday": {
		"en": "Sunday",
		"de": "Sonntag",
		"ru": "Воскресение",
		"pt": "Domingo",
		"nl": "Zondag",
		"fr": "Dimanche",
		"it": "Domenica",
		"es": "Domingo",
                "pl": "Niedziela",
                "zh-cn": "星期日"
	},
    "Thursday": {
		"en": "Thursday",
		"de": "Donnerstag",
		"ru": "Четверг",
		"pt": "Quinta-feira",
		"nl": "Donderdag",
		"fr": "Jeudi",
		"it": "Giovedi",
		"es": "Jueves",
                "pl": "Czwartek",
                "zh-cn": "星期四"
	},
    "Tuesday": {
		"en": "Tuesday",
		"de": "Dienstag",
		"ru": "Вторник",
		"pt": "Terça",
		"nl": "Dinsdag",
		"fr": "Mardi",
		"it": "Martedì",
		"es": "Martes",
                "pl": "Wtorek",
                "zh-cn": "星期二"
	},
    "CRON Use seconds": {
		"en": "Use seconds",
		"de": "Benutzen Sekunden",
		"ru": "Задействовать секунды",
		"pt": "Use segundos",
		"nl": "Gebruik seconden",
		"fr": "Utiliser les secondes",
		"it": "Usa secondi",
		"es": "Usar segundos",
                "pl": "Użyj sekund",
                "zh-cn": "使用秒"
	},
    "Wednesday": {
		"en": "Wednesday",
		"de": "Mittwoch",
		"ru": "Среда",
		"pt": "Quarta-feira",
		"nl": "Woensdag",
		"fr": "Mercredi",
		"it": "Mercoledì",
		"es": "Miércoles",
                "pl": "środa",
                "zh-cn": "星期三"
	},
    "never": {
		"en": "Never",
		"de": "Nie",
		"ru": "Никогда",
		"pt": "Nunca",
		"nl": "Nooit",
		"fr": "Jamais",
		"it": "Mai",
		"es": "Nunca",
                "pl": "Nigdy",
                "zh-cn": "决不"
	}
};

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigStaticDivider.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import Utils from '@iobroker/adapter-react/Components/Utils';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%',
        backgroundColor: theme.palette.type === 'dark' ? '#FFF' : '#000',
        borderStyle: 'hidden',
    },
    primary: {
        backgroundColor: theme.palette.primary.main
    },
    secondary: {
        backgroundColor: theme.palette.secondary.main
    }
});

class ConfigStaticDivider extends ConfigGeneric {
    renderItem() {
        return <hr
            className={Utils.clsx(
                this.props.classes.fullWidth,
                this.props.schema.color === 'primary' && this.props.classes.primary,
                this.props.schema.color === 'secondary' && this.props.classes.secondary
            )}
            style={{
                height: this.props.schema.color ? this.props.schema.height || 2 : this.props.schema.height || 1,
                backgroundColor: this.props.schema.color !== 'primary' && this.props.schema.color !== 'secondary' && this.props.schema.color ? this.props.schema.color : undefined,
            }}
        />;
    }
}

ConfigStaticDivider.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigStaticDivider);

<|start_filename|>src-rx/src/components/Wizard/WizardAuthSSLTab.js<|end_filename|>
import { createRef, Component } from 'react';
import {withStyles} from '@material-ui/core/styles';
import withWidth from "@material-ui/core/withWidth";
import PropTypes from 'prop-types';

import Grid from '@material-ui/core/Grid';
import Toolbar from '@material-ui/core/Grid';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Button from '@material-ui/core/Button';
import Paper from  '@material-ui/core/Paper';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import FormGroup from '@material-ui/core/FormGroup';

import IconCheck from '@material-ui/icons/Check';

const TOOLBAR_HEIGHT = 64;

const styles = theme => ({
    paper: {
        height: '100%',
        maxHeight: '100%',
        maxWidth: '100%',
        overflow: 'hidden',
    },
    title: {
        color: theme.palette.secondary.main,
    },
    form: {
        height: 'calc(100% - ' + (TOOLBAR_HEIGHT + theme.spacing(1)) + 'px)',
        overflow: 'auto',
    },
    input: {
        width: 400,
        textAlign: 'left',
    },
    inputLine: {
        width: 400,
        margin: 'auto',
        marginBottom: 50
    },
    grow: {
        flexGrow: 1,
    },
    toolbar: {
        height:     TOOLBAR_HEIGHT,
        lineHeight: TOOLBAR_HEIGHT + 'px',
    }
});

class WizardAuthSSLTab extends Component {
    constructor(props) {
        super(props);

        this.state = {
            auth: !!this.props.auth,
            secure: !!this.props.secure,
        };

        this.focusRef = createRef();
    }

    componentDidMount() {
        this.focusRef.current && this.focusRef.current.focus();
    }

    render() {
        return <Paper className={ this.props.classes.paper }>
            <form className={ this.props.classes.form} noValidate autoComplete="off">
                <Grid container direction="column">
                    <Grid item>
                        <h2 className={ this.props.classes.title }>{ this.props.t('It is suggested to enable the authentication in admin') }</h2>
                    </Grid>
                    <Grid item className={this.props.classes.inputLine}>
                        <FormGroup>
                            <FormControlLabel
                                className={this.props.classes.input}
                                control={
                                    <Checkbox
                                        checked={this.state.auth}
                                        onChange={() => this.setState({auth: !this.state.auth})}
                                    />
                                }
                                label={this.props.t('Authentication in Admin')}

                            />
                            <FormHelperText>{this.props.t('Activate the check of password in admin if you plan to access your ioBroker is not in "Demilitarized Zone"')}</FormHelperText>
                        </FormGroup>
                    </Grid>
                    <Grid item>
                        <FormControl className={this.props.classes.input}>
                            <InputLabel>{this.props.t('Certificates')}</InputLabel>
                            <Select
                                value={this.state.secure}
                                onChange={e => this.setState({secure: e.target.value})}
                            >
                                <MenuItem value={false}>{this.props.t('No SSL')}</MenuItem>
                                <MenuItem value={true}>{this.props.t('Use self signed certificates')}</MenuItem>
                            </Select>
                            <FormHelperText>{this.state.secure ?
                                this.props.t('Browsers will inform you about the problem with self-signed certificates, but the communication is encrypted.') :
                                this.props.t('Your communication with admin is not encrypted')}</FormHelperText>
                        </FormControl>
                    </Grid>
                </Grid>
            </form>
            <Toolbar className={ this.props.classes.toolbar }>
                <div className={ this.props.classes.grow }/>
                <Button color="primary" variant="contained" onClick={ () => this.props.onDone({auth: this.state.auth, secure: this.state.secure}) } startIcon={<IconCheck/>}>{ this.props.t('Apply') }</Button>
            </Toolbar>
        </Paper>;
    }
}

WizardAuthSSLTab.propTypes = {
    auth: PropTypes.bool,
    secure: PropTypes.bool,
    t: PropTypes.func,
    socket: PropTypes.object,
    onDone: PropTypes.func.isRequired,
};

export default withWidth()(withStyles(styles)(WizardAuthSSLTab));


<|start_filename|>src-rx/src/dialogs/ObjectEditOfAccessControl.js<|end_filename|>
import React, { useEffect, useState } from 'react';
import { Checkbox, FormControl, InputLabel, LinearProgress, MenuItem, Select, Switch } from '@material-ui/core';

import Icon from '@iobroker/adapter-react/Components/Icon';
import I18n from '@iobroker/adapter-react/i18n';

import Utils from '../components/Utils';
import CustomModal from '../components/CustomModal';

const readWriteArray = [
    {
        Owner: [
            { name: 'read', valueNum: 0x400, title: 'read owner' },
            { name: 'write', valueNum: 0x200, title: 'write owner' }
        ]
    },
    {
        Group: [
            { name: 'read', valueNum: 0x40, title: 'read group' },
            { name: 'write', valueNum: 0x20, title: 'write group' }
        ]
    },
    {
        Everyone: [
            { name: 'read', valueNum: 0x4, title: 'read everyone' },
            { name: 'write', valueNum: 0x2, title: 'write everyone' }
        ]
    },
];

const newValueAccessControl = (value, newValue, mask) => {
    value |= newValue & mask;
    value &= newValue | (~mask & 0xFFFF);
    return value;
}

const ObjectRights = ({ value, disabled, setValue, t, differentValues, applyToChildren, mask, setMask }) => {
    useEffect(() => {
        if (applyToChildren) {
            let _checkDifferent = 0;
            for (let e = 0; e < differentValues.length; e++) {
                _checkDifferent |= value ^ differentValues[e];
            }
            setMask(_checkDifferent);
        } else {
            setMask(0);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [differentValues, applyToChildren]);

    let newSelected = value;

    return <div style={{
        display: 'flex',
        width: 'fit-content',
        margin: 20,
        border: '1px solid',
        borderLeft: 0
    }}>
        {readWriteArray.map(el => {
            const name = Object.keys(el)[0];
            return <div style={{
                width: 150,
                height: 150,
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                borderLeft: '1px solid'
            }} key={name} >
                <div style={{
                    height: 50,
                    display: 'flex',
                    alignItems: 'center',
                    fontSize: 18,
                    borderBottom: '1px solid silver',
                    width: '100%',
                    justifyContent: 'center'
                }}>{t(name)}</div>
                <div style={{
                    display: 'flex',
                    width: '100%'
                }}>
                    {el[name].map((obj, idx) => {
                        let bool = false;
                        if (newSelected - obj.valueNum >= 0) {
                            newSelected = newSelected - obj.valueNum;
                            bool = true;
                        }
                        return <div style={{
                            display: 'flex',
                            flexDirection: 'column',
                            flex: 1,
                            alignItems: 'center',
                            borderRight: idx === 0 ? '1px solid' : 0
                        }} key={obj.valueNum}>
                            <div style={{
                                height: 50,
                                borderBottom: '1px solid',
                                width: '100%',
                                justifyContent: 'center',
                                display: 'flex',
                                alignItems: 'center',
                                color: 'silver'
                            }}>{t(obj.name)}</div>
                            <div style={{ height: 50, display: 'flex' }}>
                                <Checkbox
                                    disabled={disabled}
                                    checked={bool}
                                    color={mask & obj.valueNum ? 'primary' : 'secondary'}
                                    indeterminate={!!(mask & obj.valueNum)}
                                    style={mask & obj.valueNum ? { opacity: 0.5 } : null}
                                    onChange={e => {
                                        if (mask & obj.valueNum) {
                                            mask &= (~obj.valueNum) & 0xFFFF;
                                            setMask(mask);
                                        }
                                        let newValue = value;
                                        if (!e.target.checked) {
                                            newValue &= (~obj.valueNum) & 0xFFFF;
                                        } else {
                                            newValue |= obj.valueNum;
                                        }
                                        setValue(newValue);
                                    }}
                                />
                            </div>
                        </div>
                    })}
                </div>
            </div>
        })}
    </div>;
}

const ObjectEditOfAccessControl = ({ onClose, onApply, open, selected, extendObject, objects, t, modalEmptyId, themeType }) => {
    const [stateOwnerUser, setStateOwnerUser] = useState(null);
    const [stateOwnerGroup, setStateOwnerGroup] = useState(null);
    const [ownerUsers, setOwnerUsers] = useState([]);
    const [ownerGroups, setOwnerGroups] = useState([]);
    const [applyToChildren, setApplyToChildren] = useState(false);
    const [checkState, setCheckState] = useState(false);
    const [childrenCount, setChildrenCount] = useState(0);
    const [valueObjectAccessControl, setValueObjectAccessControl] = useState(null);
    const [valueStateAccessControl, setValueStateAccessControl] = useState(null);
    const [differentOwner, setDifferentOwner] = useState(false);
    const [differentGroup, setDifferentGroup] = useState(false);
    const [differentState, setDifferentState] = useState([]);
    const [differentObject, setDifferentObject] = useState([]);
    const [maskState, setMaskState] = useState(0);
    const [maskObject, setMaskObject] = useState(0);
    const [ids, setIds] = useState([]);
    const [progress, setProgress] = useState(false);

    const [disabledButton, setDisabledButton] = useState(true);

    const different = t('different');

    useEffect(() => {
        let count = 0
        let _differentState = [];
        let _differentObject = [];

        let id = selected || modalEmptyId;
        let idWithDot = id + '.';
        const keys = Object.keys(objects).sort();
        let _checkState = false;
        let groups = [];
        let users = [];
        const lang = I18n.getLanguage();

        let _differentOwner = false;
        let _differentGroup = false;
        let _stateOwnerUser = null;
        let _stateOwnerGroup = null;
        let _valueObjectAccessControl = null;
        let _valueStateAccessControl = null;
        const _ids = [];

        for (let k = 0; k < keys.length; k++) {
            const key = keys[k];
            const obj = objects[key];
            if (obj && (key === id || key.startsWith(idWithDot))) {
                count++;
                _ids.push(key);
                if (!obj.acl) {
                    continue;
                }

                if (_valueObjectAccessControl === null && obj.acl.object !== undefined) {
                    _valueObjectAccessControl = obj.acl.object;
                }
                if (_valueStateAccessControl === null && obj.acl.state !== undefined) {
                    _valueStateAccessControl = obj.acl.state;
                }
                if (_stateOwnerUser === null && obj.acl.owner !== undefined) {
                    _stateOwnerUser = obj.acl.owner;
                }
                if (_stateOwnerGroup === null && obj.acl.ownerGroup !== undefined) {
                    _stateOwnerGroup = obj.acl.ownerGroup;
                }

                if (!differentOwner && _stateOwnerUser !== obj.acl.owner && obj.acl.owner !== undefined) {
                    _differentOwner = true;
                }
                if (!differentGroup && _stateOwnerGroup !== obj.acl.ownerGroup && obj.acl.ownerGroup !== undefined) {
                    _differentGroup = true;
                }
                if (obj.acl.state !== undefined && _valueStateAccessControl !== obj.acl.state && !_differentState.includes(obj.acl.state)) {
                    _differentState.push(obj.acl.state);
                }
                if (obj.acl.object !== undefined && _valueObjectAccessControl !== obj.acl.object && !_differentObject.includes(obj.acl.object)) {
                    _differentObject.push(obj.acl.object);
                }
                if (obj.type === 'state') {
                    _checkState = true;
                }
            }

            if (key.startsWith('system.group.') && obj?.type === 'group') {
                groups.push({
                    name: Utils.getObjectNameFromObj(obj, lang).replace('system.group.', ''),
                    value: key,
                    icon: obj.common?.icon,
                    color: obj.common?.color,
                });
            } else
                if (key.startsWith('system.user.') && obj?.type === 'user') {
                    users.push({
                        name: Utils.getObjectNameFromObj(obj, lang).replace('system.user.', ''),
                        value: key,
                        icon: obj.common?.icon,
                        color: obj.common?.color,
                    });
                }
        }

        _stateOwnerUser = _stateOwnerUser || objects['system.config'].common.defaultNewAcl.owner;
        _stateOwnerGroup = _stateOwnerGroup || objects['system.config'].common.defaultNewAcl.ownerGroup;
        _valueObjectAccessControl = _valueObjectAccessControl || objects['system.config'].common.defaultNewAcl.object;
        setValueObjectAccessControl(isNaN(_valueObjectAccessControl) ? objects['system.config'].common.defaultNewAcl.object : _valueObjectAccessControl);

        if (_checkState) {
            _valueStateAccessControl = _valueStateAccessControl || objects['system.config'].common.defaultNewAcl.state;
            setValueStateAccessControl(isNaN(_valueStateAccessControl) ? objects['system.config'].common.defaultNewAcl.state : _valueStateAccessControl);
        }

        const userItem = users.find(item => item.value === _stateOwnerUser);
        const groupItem = groups.find(item => item.value === _stateOwnerGroup);

        setStateOwnerUser(userItem);
        setStateOwnerGroup(groupItem);

        setDifferentOwner(_differentOwner);
        setDifferentGroup(_differentGroup);

        setOwnerUsers(users);
        setOwnerGroups(groups);

        _checkState && setCheckState(true);
        modalEmptyId && setApplyToChildren(true);
        setChildrenCount(count);

        setDifferentState(_differentState);
        setDifferentObject(_differentObject);

        setIds(_ids);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [objects, selected]);

    useEffect(() => {
        if (applyToChildren) {
            if (differentGroup) {
                stateOwnerGroup.value !== 'different' && setStateOwnerGroup({ name: different, value: 'different' });
                if (!ownerGroups.find(item => item.value === 'different')) {
                    setOwnerGroups(el => ([{
                        name: different,
                        value: 'different'
                    }, ...el]));
                }
            }

            if (differentOwner) {
                stateOwnerUser.value !== 'different' && setStateOwnerUser({ name: different, value: 'different' });
                if (!ownerUsers.find(item => item.value === 'different')) {
                    setOwnerUsers(el => ([{
                        name: different,
                        value: 'different'
                    }, ...el]));
                }
            }
        } else {
            if (stateOwnerUser && stateOwnerUser.value === 'different') {
                setStateOwnerUser(objects[selected].acl.owner);
            }
            if (stateOwnerGroup && stateOwnerGroup.value === 'different') {
                setStateOwnerGroup(objects[selected].acl.ownerGroup);
            }
            // remove different from list
            setOwnerGroups(el => el.filter(({ value }) => value !== 'different'));
            setOwnerUsers(el => el.filter(({ value }) => value !== 'different'));
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [applyToChildren, /*stateOwnerUser, stateOwnerGroup,*/ differentOwner, differentGroup]);

    if (!ids.length) {
        return <LinearProgress />;
    } else {
        return <CustomModal
            open={open}
            titleButtonApply="apply"
            overflowHidden
            applyDisabled={disabledButton}
            progress={progress}
            onClose={onClose}
            onApply={() => {
                setProgress(true);
                setTimeout(async () => {
                    if (!applyToChildren) {
                        let newAcl = JSON.parse(JSON.stringify(objects[selected].acl || {}));
                        newAcl.object = valueObjectAccessControl;
                        newAcl.owner = stateOwnerUser.value;
                        newAcl.ownerGroup = stateOwnerGroup.value;

                        if (objects[selected].type === 'state') {
                            newAcl.state = valueStateAccessControl;
                        }
                        extendObject(selected, { acl: newAcl });
                    }
                    else {
                        //let maskState = Object.keys(differentHexState).reduce((sum, key) => sum | (differentHexState[key] ? parseInt(key, 16) : 0), 0);
                        let _maskState = ~maskState & 0xFFFF;

                        //let maskObject = Object.keys(differentHexObject).reduce((sum, key) => sum | (differentHexObject[key] ? parseInt(key, 16) : 0), 0);
                        let _maskObject = ~maskObject & 0xFFFF;

                        for (let i = 0; i < ids.length; i++) {
                            const key = ids[i];
                            const obj = objects[key];
                            let newAcl = JSON.parse(JSON.stringify(obj.acl || {}));
                            newAcl.object = newValueAccessControl(obj.acl.object, valueObjectAccessControl, _maskState);
                            if (stateOwnerUser.value !== 'different') {
                                newAcl.owner = stateOwnerUser.value;
                            }
                            if (stateOwnerGroup.value !== 'different') {
                                newAcl.ownerGroup = stateOwnerGroup.value;
                            }
                            if (obj.type === 'state') {
                                newAcl.state = newValueAccessControl(obj.acl.state, valueStateAccessControl, _maskObject);
                            }
                            await extendObject(key, { acl: newAcl });
                        }
                    }

                    setProgress(false);
                    onApply();
                }, 200);
            }}>

            <div style={{ display: 'flex', flexDirection: 'column' }}>
                <div style={{
                    margin: 10,
                    fontSize: 20
                }}>{t('Access control list: %s', selected || modalEmptyId)}</div>
                <div style={{ display: 'flex' }}>
                    <FormControl fullWidth style={{ marginRight: 10 }}>
                        <InputLabel>{t('Owner user')}</InputLabel>
                        <Select
                            disabled={progress}
                            value={stateOwnerUser.value}
                            renderValue={value => <span>{stateOwnerUser.icon ? <Icon src={stateOwnerUser.icon} style={{ width: 16, height: 16, marginRight: 8 }} /> : null}{stateOwnerUser.name}</span>}
                            style={stateOwnerUser.value === 'different' ? { opacity: 0.5 } : { color: stateOwnerUser.color || undefined, backgroundColor: Utils.getInvertedColor(stateOwnerUser.color, themeType) }}
                            onChange={el => {
                                const userItem = ownerUsers.find(item => item.value === el.target.value);
                                setStateOwnerUser(userItem);
                                setDisabledButton(false);
                            }}
                        >
                            {ownerUsers.map(el => <MenuItem style={el.value === 'different' ? { opacity: 0.5 } : { color: el.color || undefined, backgroundColor: Utils.getInvertedColor(el.color, themeType) }} key={el.value} value={el.value}>
                                {el.icon ? <Icon src={el.icon} style={{ width: 16, height: 16, marginRight: 8 }} /> : null}
                                {el.name}
                            </MenuItem>)}
                        </Select>
                    </FormControl>
                    <FormControl fullWidth>
                        <InputLabel>{t('Owner group')}</InputLabel>
                        <Select
                            disabled={progress}
                            value={stateOwnerGroup.value}
                            renderValue={value => <span>{stateOwnerGroup.icon ? <Icon src={stateOwnerGroup.icon} style={{ width: 16, height: 16, marginRight: 8 }} /> : null}{stateOwnerGroup.name}</span>}
                            style={stateOwnerGroup.value === 'different' ? { opacity: 0.5 } : { color: stateOwnerGroup.color || undefined, backgroundColor: Utils.getInvertedColor(stateOwnerGroup.color, themeType) }}
                            onChange={el => {
                                const groupItem = ownerGroups.find(item => item.value === el.target.value);
                                setStateOwnerGroup(groupItem);
                                setDisabledButton(false);
                            }}
                        >
                            {ownerGroups.map(el => <MenuItem key={el.value} value={el.value} style={el.value === 'different' ? { opacity: 0.5 } : { color: el.color || undefined, backgroundColor: Utils.getInvertedColor(el.color, themeType) }}>
                                {el.icon ? <Icon src={el.icon} style={{ width: 16, height: 16, marginRight: 8 }} /> : null}
                                {el.name}
                            </MenuItem>)}
                        </Select>
                    </FormControl>
                </div>

                <div style={{
                    display: 'flex',
                    margin: 10,
                    alignItems: 'center',
                    fontSize: 10,
                    marginLeft: 0,
                    color: 'silver'
                }}>
                    <div style={!applyToChildren ? { color: 'green' } : null}>{t('to apply one item')}</div>
                    <Switch
                        disabled={progress || !!modalEmptyId || childrenCount === 1}
                        checked={!!modalEmptyId || applyToChildren}
                        onChange={e => {
                            setApplyToChildren(e.target.checked);
                            setDisabledButton(false);
                        }}
                        color="primary"
                    />
                    <div style={applyToChildren ? { color: 'green' } : null}>{t('to apply with children')} {(modalEmptyId || childrenCount > 1) ? `(${childrenCount} ${t('object(s)')})` : ''}</div>
                </div>

                {progress && <LinearProgress />}

                <div style={{ overflowY: 'auto' }}>
                    <div>
                        <h2>{t('Object rights')}</h2>
                        <ObjectRights
                            mask={maskObject}
                            disabled={progress}
                            setMask={setMaskObject}
                            applyToChildren={applyToChildren}
                            differentValues={differentObject}
                            t={t}
                            setValue={e => {
                                setValueObjectAccessControl(e);
                                setDisabledButton(false);
                            }}
                            value={valueObjectAccessControl} />
                    </div>
                    {checkState && <div>
                        <h2>{t('States rights')}</h2>
                        <ObjectRights
                            mask={maskState}
                            disabled={progress}
                            setMask={setMaskState}
                            applyToChildren={applyToChildren}
                            differentValues={applyToChildren ? differentState : []}
                            t={t}
                            setValue={e => {
                                setValueStateAccessControl(e);
                                setDisabledButton(false);
                            }}
                            value={valueStateAccessControl} />
                    </div>}
                </div>
            </div>
        </CustomModal>;
    }
}

export default ObjectEditOfAccessControl;

<|start_filename|>src-rx/src/components/TabHeader.js<|end_filename|>
import PropTypes from 'prop-types';

import { Grid } from '@material-ui/core';

const TabHeader = props => {
    return <Grid
        item
        container
        alignItems="center"
    >
        { props.children }
    </Grid>;
}

TabHeader.propTypes = {
    /**
     * The content of the component.
     */
    children: PropTypes.node
};

export default TabHeader;

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigColor.js<|end_filename|>
import PropTypes from 'prop-types';
import {withStyles} from '@material-ui/core/styles';
import { ChromePicker } from 'react-color'

import IconButton from '@material-ui/core/IconButton';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';

import ClearIcon from '@material-ui/icons/Close';

import ConfigGeneric from './ConfigGeneric';

import Utils from '@iobroker/adapter-react/Components/Utils';
import ColorPicker from '@iobroker/adapter-react/Components/ColorPicker';

const styles = theme => ({});

class ConfigColor extends ConfigGeneric {
    renderColorDialog() {
        return !!this.state.showColorDialog && <Dialog
            onClose={() => {
                this.setState({showColorDialog: false});
            }}
            open={this.state.showColorDialog}>
            <ChromePicker
                color={this.state.colorDialogValue}
                onChange={value => {
                    this.setState({colorDialogValue: value}, () =>
                        this.onChange(this.props.attr, ColorPicker.getColor(this.state.colorDialogValue, true)));
                }}
            />
        </Dialog>;
    }

    renderItem(error, disabled, defaultValue) {
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
        let textColor = Utils.isUseBright(value, null);
        if (textColor === null) {
            textColor = undefined;
        }
        return <>
            {this.renderColorDialog()}
            <TextField
                disabled={!!disabled}
                style={{minWidth: 100, width: 'calc(100% - 8px)'}}
                label={this.getText(this.props.schema.label)}
                value={value || ''}
                onClick={() => this.setState({showColorDialog: true, colorDialogValue: value})}
                onChange={e => {
                    const color = e.target.value;
                    this.onChange(this.props.attr, color);
                }}
                inputProps={{
                    style: {
                        //paddingLeft: noPadding ? 0 : 8,
                        backgroundColor: value,
                        color: textColor ? '#FFF' : '#000'
                    }
                }}
                InputProps={{
                    endAdornment: value ?
                        <IconButton
                            disabled={!!this.props.onPaste}
                            size="small"
                            onClick={e => {
                                e.stopPropagation();
                                this.onChange(this.props.attr, '');
                            }}>
                            <ClearIcon/>
                        </IconButton>
                        : undefined,
                }}
                InputLabelProps={{shrink: true}}
            />
        </>;
    }
}

ConfigColor.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigColor);

<|start_filename|>src-admin/src/Components/Toast.js<|end_filename|>
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { IconButton, Snackbar } from '@material-ui/core';
import PropTypes from 'prop-types';
import I18n from '@iobroker/adapter-react/i18n';
import { MdClose as IconClose } from 'react-icons/md';

const useStyles = makeStyles((theme) => ({
    close: {

    }
}));

const Toast = ({ message, onClose }) => {
    const classes = useStyles();
    if (!message) {
        return null;
    }
    return <Snackbar
        anchorOrigin={{
            vertical: 'bottom',
            horizontal: 'center',
        }}
        open={true}
        autoHideDuration={6000}
        onClose={onClose}
        ContentProps={{'aria-describedby': 'message-id',}}
        message={<span id="message-id">{I18n.t(message)}</span>}
        action={[
            <IconButton
                key="close"
                aria-label="Close"
                color="inherit"
                className={classes.close}
                onClick={onClose}
            >
                <IconClose />
            </IconButton>,
        ]}
    />;
}

Toast.defaultProps = {
    message: '',
    onClose: () => { }
};

Toast.propTypes = {
    onClose: PropTypes.func,
    message: PropTypes.string
};

export default Toast;

<|start_filename|>src-admin/src/Components/CustomCheckbox.js<|end_filename|>
import { Checkbox, FormControlLabel, withStyles } from '@material-ui/core';
import React from 'react';
import clsx from 'clsx';
import I18n from '@iobroker/adapter-react/i18n';
import PropTypes from 'prop-types';

const styles = theme => ({
    input: {
        minWidth: 300
    }
});

const CustomCheckbox = ({ title, attr, style, classes, native, onChange, className, table, checked }) => {
    return <FormControlLabel
        key={attr}
        style={Object.assign({ paddingTop: 5 }, style)}
        className={clsx(classes.controlElement, className)}
        control={
            <Checkbox
                checked={table ? checked : native[attr]}
                onChange={el => {
                    if (table) {
                        onChange(el.target.checked);
                    } else {
                        onChange(attr, !native[attr]);
                    }
                }}
                color="primary"
            />
        }
        label={I18n.t(title)}
    />;
}

CustomCheckbox.defaultProps = {
    table: false,
    className: null
};

CustomCheckbox.propTypes = {
    title: PropTypes.string,
    attr: PropTypes.string.isRequired,
    style: PropTypes.object,
    native: PropTypes.object.isRequired,
    onChange: PropTypes.func,
    table: PropTypes.bool
};

export default withStyles(styles)(CustomCheckbox);

<|start_filename|>src-admin/src/i18n/ru.json<|end_filename|>
{
  "Access to instances": "Доступ к экземплярам",
  "Admin adapter settings": "Настройки для адаптера admin",
  "Allow access only to specific instances": "Разрешить доступ только к конкретным настройкам",
  "Apply access rights for selected instances": "Применить права доступа для выбранных экземпляров",
  "Authentication was deactivated": "Аутентификация была отключена",
  "Authentication": "Аутентификация",
  "Auto update": "Проверка обновлений",
  "Background": "Фон",
  "Background color of the login screen": "Цвет фона экрана входа в систему",
  "Background image": "Фоновое изображение",
  "Cache": "Кэш",
  "Chained certificate": "'Chained' сертификат",
  "Disable authentication": "Отключить аутентификацию",
  "Enabled": "Включено",
  "Events threshold value": "Порог отключения обновлений",
  "Hide logo": "Скрыть логотип",
  "IP": "IP",
  "Ignore warning": "Игнорировать предупреждение",
  "Let's Encrypt settings": "Настройкт Let's Encrypt",
  "Listen on all IPs": "Слушать на всех адресах",
  "Login timeout(sec)": "Logout через(сек)",
  "Own motto": "Собственный девиз",
  "Port to check the domain": "Порт для проверки доменного имени",
  "Port": "Порт",
  "Private certificate": "'Private' сертификат",
  "Public certificate": "'Public' сертификат",
  "Run as": "Запустить от пользователя",
  "Secure(HTTPS)": "Шифрование(HTTPS)",
  "Set certificates or load it first in the system settings (right top).": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).",
  "Unsecure_Auth": "Пароль будет отправлен через незащищенное соединение. Для защиты ваших паролей активируйте безопасное соединение (HTTPS)!",
  "Use Lets Encrypt certificates": "Использовать сертификаты Let's Encrypt",
  "Use this instance for automatic update": "Обновлять сертификаты в этом драйвере",
  "Warning!": "Предупреждение!",
  "every 12 hours": "каждые 12 часов",
  "every 2 days": "каждые 2 дня",
  "every 2 weeks": "каждые 2 недели",
  "every 3 days": "каждые 3 дня",
  "every day": "каждый день",
  "every week": "каждую неделю",
  "explain_access_instance": "Существует возможность разрешить доступ к определенным страницам конфигурации выбранных экземпляров.",
  "manually": "вручную",
  "monthly": "раз в месяц",
  "place here": "разместите файлы здесь",
  "tooltip_auth": "Активировать проверку имени и пароля.",
  "tooltip_autoUpdate": "Регулярно проверять на наличие обновлений драйверов",
  "tooltip_bind": "IP Адрес интерфейса на котором будет запущен админ",
  "tooltip_cache": "Активировать кеш браузера",
  "tooltip_certChained": "Chained сертификат. Настройте сначала сертификаты в системных настройках.",
  "tooltip_certPrivate": "Приватный (private) сертификат. Настройте сначала сертификаты в системных настройках.",
  "tooltip_certPublic": "Публичный (public) сертификат. Настройте сначала сертификаты в системных настройках.",
  "tooltip_defaultUser": "Автоматический логин с этим пользователем.",
  "tooltip_leEnabled": "Использовать сертификаты Let's Encrypt",
  "tooltip_lePort": "Порт для challenge сервера Let's Encrypt. Сервер должен быть доступен из интернета на порту 80",
  "tooltip_leUpdate": "Обновлять в этой инстанции Let's Encrypt сертификаты раз в 3 месяца. Обновлять их может только одна инстанция",
  "tooltip_port": "Порт для интерфейса.",
  "tooltip_secure": "Активировать протокол SSL. Сайт будет доступен по https:// , а не по http://",
  "tooltip_thresholdValue": "если браузер получает слишком много событий за 3 секунды, обновление будет остановлено",
  "tooltip_ttl": "По прошествии этого времени при бездействии пользователь будет разлогинен.",
  "users permissions": "Права доступа"
}


<|start_filename|>src-admin/src/Components/CustomButtonUpload.js<|end_filename|>
import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import { TextField } from '@material-ui/core';
import I18n from '@iobroker/adapter-react/i18n';

const useStyles = makeStyles(theme => ({
    root: {
        '& > *': {
            margin: theme.spacing(1),
        },
    },
    input: {
        display: 'none',
    },
}));

const CustomButtonUpload = ({ title, onChange }) => {
    const classes = useStyles();
    const [valueFileUpload, setValueFileUpload] = useState('');
    return <div className={classes.root}>
        <input
            accept="image/*"
            className={classes.input || ''}
            id="contained-button-file"
            multiple
            type="file"
            onChange={(e) => {
                onChange(e.target.files[0] || e.dataTransfer.files[0], name =>
                    setValueFileUpload(name));
            }}
        />
        <label htmlFor="contained-button-file">
            <Button variant="contained" color="primary" component="span">
                {I18n.t(title)}
            </Button>
            <TextField style={{ marginLeft: 20 }} value={valueFileUpload} />
        </label>
    </div>;
}

export default CustomButtonUpload;

<|start_filename|>src-admin/src/i18n/nl.json<|end_filename|>
{
  "Access to instances": "Toegang tot instanties",
  "Admin adapter settings": "Admin adapter instellingen",
  "Allow access only to specific instances": "Sta alleen toegang toe tot specifieke instanties",
  "Apply access rights for selected instances": "Pas toegangsrechten toe voor geselecteerde instanties",
  "Authentication was deactivated": "Verificatie was gedeactiveerd",
  "Authentication": "Authenticatie",
  "Auto update": "Updates controleren",
  "Background": "Achtergrond",
  "Background color of the login screen": "Achtergrondkleur van het inlogscherm",
  "Background image": "Achtergrond afbeelding",
  "Cache": "Cache",
  "Chained certificate": "Ketencertificaat",
  "Disable authentication": "Schakel verificatie uit",
  "Enabled": "Ingeschakeld",
  "Events threshold value": "Drempelwaarde voor gebeurtenissen",
  "Hide logo": "Logo verbergen",
  "IP": "IP",
  "Ignore warning": "Negeer waarschuwing",
  "Let's Encrypt settings": "Let's Encrypt Instellingen",
  "Listen on all IPs": "Luister op alle IP's",
  "Login timeout(sec)": "Aanmelding Time-out (sec)",
  "Own motto": "Eigen motto",
  "Port to check the domain": "Poort om het domein te controleren",
  "Port": "Poort",
  "Private certificate": "Privé certificaat",
  "Public certificate": "Openbaar certificaat",
  "Run as": "Uitvoeren als",
  "Secure(HTTPS)": "Encryptie (HTTPS)",
  "Set certificates or load it first in the system settings (right top).": "Stel certificaten in of installeer deze eerst in de systeeminstellingen (rechtsboven).",
  "Unsecure_Auth": "Het wachtwoord wordt verzonden via onbeveiligde verbinding. Ter beveiliging van uw wachtwoorden schakelt u de beveiligde verbinding (HTTPS) in!",
  "Use Lets Encrypt certificates": "Gebruik Let's Encrypt-certificaten",
  "Use this instance for automatic update": "Gebruik deze instantie voor automatische update",
  "Warning!": "Waarschuwing!",
  "every 12 hours": "Elke 12 uur",
  "every 2 days": "om de 2 dagen",
  "every 2 weeks": "elke 2 weken",
  "every 3 days": "om de 3 dagen",
  "every day": "elke dag",
  "every week": "elke week",
  "explain_access_instance": "Er is een optie om toegang te verlenen tot de specifieke configuratiepagina's van geselecteerde exemplaren.",
  "manually": "handmatig",
  "monthly": "maandelijks",
  "place here": "plaats de bestanden hier",
  "tooltip_auth": "Controleer of aanmelding en wachtwoord toegang hebben tot de beheerderspagina. U kunt gebruikers beheren op het tabblad Gebruikers.",
  "tooltip_autoUpdate": "Controleer regelmatig of er updates zijn",
  "tooltip_bind": "IP-adres welke de admin adapter gebruikt",
  "tooltip_cache": "Browser cache ingeschakeld.",
  "tooltip_certChained": "Ketencertificaat. Beheer deze in de systeemconfiguratie.",
  "tooltip_certPrivate": "Privé certificaat. Beheer deze in de systeemconfiguratie.",
  "tooltip_certPublic": "Openbaar certificaat. Beheer deze in de systeemconfiguratie.",
  "tooltip_defaultUser": "Automatisch inloggen met deze gebruiker",
  "tooltip_leEnabled": "Gebruik Let's Encrypt-certificaten",
  "tooltip_lePort": "Poort voor uitgaande server. Let's Encrypt ondersteunt alleen poort 80, maar met een router kun je elke poort gebruiken",
  "tooltip_leUpdate": "Deze instantie werkt Let's Encrypt-certificaten elke 3 maanden bij",
  "tooltip_port": "Poort voor beheerdersinterface, deze moet beschikbaar zijn. Als u op Linux draait zonder root  kunt u geen poorten onder 1000 gebruiken.",
  "tooltip_secure": "Schakel SSL-protocol in. Beheerpagina is beschikbaar via https: // en niet http: //",
  "tooltip_thresholdValue": "Als de browser te veel gebeurtenissen binnen 3 seconden ontvangt, wordt de update gestopt",
  "tooltip_ttl": "Time-out aanmelding. Na deze tijd zal de gebruiker automatisch uitloggen.",
  "users permissions": "gebruikersrechten"
}


<|start_filename|>src-admin/src/i18n/en.json<|end_filename|>
{
  "Access to instances": "Access to instances",
  "Admin adapter settings": "The settings for the admin adapter",
  "Allow access only to specific instances": "Allow access only to specific instances",
  "Apply access rights for selected instances": "Apply access rights for selected instances",
  "Authentication was deactivated": "Authentication was deactivated",
  "Authentication": "Authentication",
  "Auto update": "Check for updates",
  "Background": "Background",
  "Background color of the login screen": "Background color of the login screen",
  "Background image": "Background image",
  "Cache": "Cache",
  "Chained certificate": "Chained certificate",
  "Disable authentication": "Disable authentication",
  "Enabled": "Enabled",
  "Events threshold value": "Events threshold value",
  "Hide logo": "Hide logo",
  "IP": "IP",
  "Ignore warning": "Ignore warning",
  "Let's Encrypt settings": "Let's Encrypt settings",
  "Listen on all IPs": "Listen on all IPs",
  "Login timeout(sec)": "Login timeout(sec)",
  "Own motto": "Own motto",
  "Port to check the domain": "Port to check the domain",
  "Port": "Port",
  "Private certificate": "Private certificate",
  "Public certificate": "Public certificate",
  "Run as": "Run as",
  "Secure(HTTPS)": "Secure(HTTPS)",
  "Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).",
  "Unsecure_Auth": "The password will be sent via unsecure connection. To protect your passwords enable the secure connection (HTTPS)!",
  "Use Lets Encrypt certificates": "Use Let's Encrypt certificates",
  "Use this instance for automatic update": "Use this instance for automatic update",
  "Warning!": "Warning!",
  "every 12 hours": "every 12 hours",
  "every 2 days": "every 2 days",
  "every 2 weeks": "every 2 weeks",
  "every 3 days": "every 3 days",
  "every day": "every day",
  "every week": "every week",
  "explain_access_instance": "There is a option to allow access to the specific configuration pages of selected instances.",
  "manually": "manually",
  "monthly": "monthly",
  "place here": "place the files here",
  "tooltip_auth": "Require a login and a password to access the admin page. This can be managed on the users tab.",
  "tooltip_autoUpdate": "Check periodically for updates of adapter",
  "tooltip_bind": "IP address where the admin will listen on",
  "tooltip_cache": "Browser cache enabled.",
  "tooltip_certChained": "Chained certificate. Root certificate + Public certificate. Please manage it in the system configs.",
  "tooltip_certPrivate": "Private certificate. Please manage it in the system configs.",
  "tooltip_certPublic": "Public certificate. Please manage it in the system configs.",
  "tooltip_defaultUser": "Auto-login with this user",
  "tooltip_leEnabled": "Use Let's Encrypt certificates",
  "tooltip_lePort": "Port for challenge server. Let's Encrypt supports only port 80, but with corresponding settings in your router, any port can be used",
  "tooltip_leUpdate": "This instance will update Let's Encrypt certificates every 3 months",
  "tooltip_port": "Port for admin interface. It must be available. If running on linux with no root you cannot use ports below 1000.",
  "tooltip_secure": "Enable SSL protocol. Admin will be available via https:// and not http://",
  "tooltip_thresholdValue": "If the browser receives too many events in 3 seconds, refresh will be stopped",
  "tooltip_ttl": "Login timeout. After this time the user will be automatically logged off.",
  "users permissions": "Users permissions"
}


<|start_filename|>src-rx/src/components/Instances/InstanceCategory.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import {
    Accordion, AccordionDetails, AccordionSummary,
} from '@material-ui/core';

import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import MaterialDynamicIcon from '../../helpers/MaterialDynamicIcon';

const boxShadow = '0 2px 2px 0 rgba(0, 0, 0, .14),0 3px 1px -2px rgba(0, 0, 0, .12),0 1px 5px 0 rgba(0, 0, 0, .2)';
const boxShadowHover = '0 8px 17px 0 rgba(0, 0, 0, .2),0 6px 20px 0 rgba(0, 0, 0, .19)';

const styles = theme => ({
    root: {
        position: 'relative',
        margin: 10,
        width: 300,
        minHeight: 200,
        background: theme.palette.background.default,
        boxShadow,
        display: 'flex',
        flexDirection: 'column',
        transition: 'box-shadow 0.5s',
        '&:hover': {
            boxShadow: boxShadowHover
        }
    },
    row: {
        paddingLeft: 8,
        flexDirection: 'row-reverse',
        minHeight: '48px !important',
        height: 48,
        fontWeight: 'bold',
        fontSize: 16
    },
    iconStyle: {
        marginRight: 10
    },
    wrapperName: {
        margin: '0 10px',
        display: 'flex',
        alignItems: 'center'
    },
    wrapperChildren: {
        width: '100%'
    },
    accordionDetails: {
        padding: 0
    },
    accordion: {
        margin: '1px 0 !important'
    }
});

const InstanceCategory = ({
    name,
    key,
    children,
    classes,
    expanded,
    onExpand,
}) => {
    return <Accordion
        expanded={!!expanded}
        onChange={() => onExpand(!expanded)}
        classes={{ root: classes.accordion }} key={key} square>
        <AccordionSummary
            classes={{ root: classes.row }}
            expandIcon={<ExpandMoreIcon />}>
            <div className={classes.wrapperName}>
                <MaterialDynamicIcon objIconBool iconName={name} className={classes.iconStyle} />{name}
            </div>
        </AccordionSummary>
        <AccordionDetails className={classes.accordionDetails}>
            <div className={classes.wrapperChildren}>
                {children}
            </div>
        </AccordionDetails>
    </Accordion>;
}

InstanceCategory.propTypes = {
    t: PropTypes.func,
    themeType: PropTypes.string,
    adminInstance: PropTypes.string,
    hosts: PropTypes.array,
    setHost: PropTypes.func,
    host: PropTypes.string,
    instanceId: PropTypes.string,
};


export default withStyles(styles)(InstanceCategory);

<|start_filename|>src-admin/src/Tabs/Options.js<|end_filename|>
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';

import Security from '@material-ui/icons/Security';

import Logo from '@iobroker/adapter-react/Components/Logo';
import I18n from '@iobroker/adapter-react/i18n';

import CustomSelect from '../Components/CustomSelect';
import CustomInput from '../Components/CustomInput';
import CustomCheckbox from '../Components/CustomCheckbox';
import CustomModal from '../Components/CustomModal';
import Toast from '../Components/Toast';

const styles = theme => ({
    blockWrapper: {
        display: 'flex',
        flexDirection: 'column',
        marginRight: 20,
        '@media screen and (max-width: 360px)': {
            marginRight: 0
        }
    },
    displayNone: {
        display: 'none !important'
    },
    tab: {
        width: '100%',
        minHeight: '100%'
    },
    column: {
        display: 'inline-block',
        verticalAlign: 'top',
        marginRight: 20
    },
    columnSettings: {
        width: 'calc(100% - 10px)',
    },
    blockWrapperCheckbox: {
        display: 'flex',
        flexFlow: 'wrap'
    },
    ipInputStyle: {
        marginTop: 10,
        width: 900,
        marginRight: 20,
        '@media screen and (max-width: 940px)': {
            width: '100%'
        }
    },
    blockWarning: {
        background: '#2196f3',
        color: '#fff',
        margin: '20px 2px',
        padding: 8,
        fontSize: 20
    },
    blockWarningContent: {
        marginBottom: 200,
        flexFlow: 'wrap',
        display: 'flex',
        alignItems: 'flex-end'
    }
});

class Options extends Component {
    constructor(props) {
        super(props);
        this.state = {
            toast: '',
            ipAddressOptions: [],
            certificatesOptions: [],
            usersOptions: [],
            openModal: false
        };
    }

    componentDidMount() {
        const { socket, common: { host } } = this.props;

        socket.getRawSocket().emit('getHostByIp', host, (err, data) => {
            if (data) {
                let IPs4 = [{ title: `[IPv4] 0.0.0.0 - ${I18n.t('open_ip')}`, value: '0.0.0.0', family: 'ipv4' }];
                let IPs6 = [{ title: '[IPv6] ::', value: '::', family: 'ipv6' }];
                if (data.native.hardware && data.native.hardware.networkInterfaces) {
                    for (let eth in data.native.hardware.networkInterfaces) {
                        if (!data.native.hardware.networkInterfaces.hasOwnProperty(eth)) {
                            continue;
                        }
                        for (let num = 0; num < data.native.hardware.networkInterfaces[eth].length; num++) {
                            if (data.native.hardware.networkInterfaces[eth][num].family !== 'IPv6') {
                                IPs4.push({ title: `[${data.native.hardware.networkInterfaces[eth][num].family}] ${data.native.hardware.networkInterfaces[eth][num].address} - ${eth}`, value: data.native.hardware.networkInterfaces[eth][num].address, family: 'ipv4' });
                            } else {
                                IPs6.push({ title: `[${data.native.hardware.networkInterfaces[eth][num].family}] ${data.native.hardware.networkInterfaces[eth][num].address} - ${eth}`, value: data.native.hardware.networkInterfaces[eth][num].address, family: 'ipv6' });
                            }
                        }
                    }
                }
                for (let i = 0; i < IPs6.length; i++) {
                    IPs4.push(IPs6[i]);
                }
                this.setState({ ipAddressOptions: IPs4 });
            }
        })

        socket.getCertificates()
            .then(list =>
                this.setState({ certificatesOptions: list }));

        socket.getUsers()
            .then(list =>
                this.setState({ usersOptions: list }));
    }

    componentDidUpdate(prevProps) {
        const { native: { auth, secure } } = prevProps;
        if (!this.props.native.secure && this.props.native.auth && !this.state.openModal && ((auth !== this.props.native.auth) || (secure !== this.props.native.secure))) {
            this.setState({ openModal: true });
        }
    }

    renderConfirmDialog() {
        return <CustomModal
            open={this.state.openModal}
            buttonClick={() => {
                this.props.onChange('auth', false, () =>
                    this.setState({ openModal: false, toast: 'Authentication was deactivated' }));
            }}
            close={() => this.setState({ openModal: false })}
            titleOk={I18n.t('Disable authentication')}
            titleCancel={I18n.t('Ignore warning')}>
            <div className={classes.blockWarning}>{I18n.t('Warning!')}</div>
            <div className={classes.blockWarningContent}><Security style={{ width: 100, height: 100 }} />{I18n.t('Unsecure_Auth')}</div>
        </CustomModal>;
    }

    render() {
        const { instance, common, classes, native, onLoad, onChange } = this.props;
        const { certificatesOptions, ipAddressOptions, usersOptions, toast } = this.state;
        let newCommon = JSON.parse(JSON.stringify(common));

        newCommon.icon = newCommon.extIcon;

        return <form className={classes.tab}>
            <Toast message={toast} onClose={() => this.setState({ toast: '' })} />
            {this.renderConfirmDialog()}
            <Logo
                instance={instance}
                classes={undefined}
                common={newCommon}
                native={native}
                onError={text => this.setState({ errorText: text })}
                onLoad={onLoad}
            />
            <div className={`${classes.column} ${classes.columnSettings}`}>
                <div>
                    <CustomSelect
                        title='IP'
                        attr='bind'
                        className={classes.ipInputStyle}
                        options={ipAddressOptions}
                        native={native}
                        onChange={onChange}
                    />
                    <CustomInput
                        title='Port'
                        attr='port'
                        type='number'
                        style={{ marginTop: 5 }}
                        native={native}
                        onChange={onChange}
                    />
                </div>
                <div className={classes.blockWrapperCheckbox}>
                    <div className={classes.blockWrapper}>
                        <CustomCheckbox
                            title='Secure(HTTPS)'
                            attr='secure'
                            style={{ marginTop: 10 }}
                            native={native}
                            onChange={onChange}
                        />
                        <CustomCheckbox
                            title='Authentication'
                            attr='auth'
                            style={{ marginTop: 10 }}
                            native={native}
                            onChange={onChange}
                        />
                    </div>
                    <div className={classes.blockWrapper}>
                        <div className={`${classes.blockWrapperCheckbox} ${native['secure'] ? null : classes.displayNone}`} >
                            <CustomSelect
                                title='Public certificate'
                                attr='certPublic'
                                options={[
                                    { title: I18n.t('nothing'), value: '' }, ...certificatesOptions.filter(({ type }) => type === 'public').map(({ name }) => ({ title: name, value: name }))
                                ]}
                                style={{ marginTop: 10, marginRight: 20 }}
                                native={native}
                                onChange={onChange}
                            />
                            <CustomSelect
                                title='Private certificate'
                                attr='certPrivate'
                                options={[
                                    { title: I18n.t('nothing'), value: '' }, ...certificatesOptions.filter(({ type }) => type === 'private').map(({ name }) => ({ title: name, value: name }))
                                ]}
                                style={{ marginTop: 10, marginRight: 20 }}
                                native={native}
                                onChange={onChange}
                            />
                            <CustomSelect
                                title='Chained certificate'
                                attr='certChained'
                                options={[
                                    { title: I18n.t('nothing'), value: '' }, ...certificatesOptions.filter(({ type }) => type === 'chained').map(({ name }) => ({ title: name, value: name }))
                                ]}
                                style={{ marginTop: 10 }}
                                native={native}
                                onChange={onChange}
                            />
                        </div>
                        <CustomSelect
                            className={!native['auth'] ? null : classes.displayNone}
                            title='Run as'
                            attr='defaultUser'
                            options={usersOptions.map(({ _id, common: { name } }) => ({ title: name, value: _id.replace('system.user.', '') }))}
                            style={{ marginTop: 10, width: 300 }}
                            native={native}
                            onChange={onChange}
                        />
                        <CustomInput
                            className={native['auth'] ? null : classes.displayNone}
                            title='Login timeout(sec)'
                            attr='ttl'
                            type='number'
                            style={{ marginTop: -1, width: 300 }}
                            native={native}
                            onChange={onChange}
                        />
                    </div>
                    <div className={classes.blockWrapper}>
                        <CustomSelect
                            title='Auto update'
                            attr='autoUpdate'
                            style={{ marginTop: 10 }}
                            native={native}
                            options={[
                                {value: 0, title: 'manually'},
                                {value: 12, title: 'every 12 hours'},
                                {value: 24, title: 'every day'},
                                {value: 48, title: 'every 2 days'},
                                {value: 72, title: 'every 3 days'},
                                {value: 168, title: 'every week'},
                                {value: 336, title: 'every 2 weeks'},
                                {value: 720, title: 'monthly'},
                            ]}
                            onChange={onChange}
                        />
                        <CustomInput
                            title='Events threshold value'
                            attr='thresholdValue'
                            style={{ marginTop: 10 }}
                            native={native}
                            onChange={onChange}
                        />
                    </div>
                </div>
            </div>
        </form>;
    }
}

Options.propTypes = {
    common: PropTypes.object.isRequired,
    native: PropTypes.object.isRequired,
    instance: PropTypes.number.isRequired,
    adapterName: PropTypes.string.isRequired,
    onError: PropTypes.func,
    onLoad: PropTypes.func,
    onChange: PropTypes.func,
    changed: PropTypes.bool,
    socket: PropTypes.object.isRequired,
};

export default withStyles(styles)(Options);

<|start_filename|>src-admin/src/Components/CustomSelect.js<|end_filename|>
import { FormControl, FormHelperText, Input, MenuItem, Select, withStyles } from '@material-ui/core';
import React from 'react';
import I18n from '@iobroker/adapter-react/i18n';
import PropTypes from 'prop-types';
import clsx from 'clsx';

const styles = theme => ({
    input: {
        minWidth: 300
    },
    inputNumber: {
        minWidth: 150
    }
});

const CustomSelect = ({ table, value, title, attr, options, style, classes, native, onChange, className }) => {
    return <FormControl
        className={clsx(classes.input, classes.controlElement, className)}
        style={Object.assign({ paddingTop: 5 }, style)}
    >
        <Select
            value={table ? value : native[attr] || '_'}
            onChange={e => {
                if (table) {
                    onChange(e.target.value);
                } else {
                    onChange(attr, e.target.value === '_' ? '' : e.target.value);
                }
            }}
            input={<Input name={attr} id={attr + '-helper'} />}
        >
            {options.map(item => <MenuItem key={'key-' + item.value} value={item.value || '_'}>{I18n.t(item.title)}</MenuItem>)}
        </Select>
        <FormHelperText>{I18n.t(title)}</FormHelperText>
    </FormControl>;
}

CustomSelect.defaultProps = {
    value: '',
    className: null,
    table: false
};

CustomSelect.propTypes = {
    title: PropTypes.string,
    attr: PropTypes.string,
    options: PropTypes.array.isRequired,
    style: PropTypes.object,
    native: PropTypes.object.isRequired,
    onChange: PropTypes.func
};

export default withStyles(styles)(CustomSelect);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigAlive.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import ConfigGeneric from './ConfigGeneric';

import I18n from '@iobroker/adapter-react/i18n';
import Utils from '@iobroker/adapter-react/Components/Utils';

const styles = theme => ({
    root: {
        width: '100%',
    },
    notAlive: {
        color: '#a30000'
    }
});

class ConfigAlive extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();

        const instance = this.getInstance();

        this.props.socket.getState(instance + '.alive')
            .then(state => this.setState({alive: !!(state && state.val), instance}));
    }

    getInstance() {
        let instance = this.props.schema.instance || (this.props.adapterName + '.' + this.props.instance);
        if (instance.includes('${')) {
            instance = this.getPattern(instance);
        }
        if (instance && !instance.startsWith('system.adapter.')) {
            instance = 'system.adapter.' + instance;
        }
        return instance;
    }

    renderItem() {
        if (this.getInstance() !== this.state.instance) {
            setTimeout(() => {
                const instance = this.getInstance();
                if (instance) {
                    this.props.socket.getState(instance + '.alive')
                        .then(state => this.setState({alive: !!(state && state.val), instance}));
                } else {
                    this.setState({alive: null, instance})
                }
            }, 200);
        }

        if (this.state.alive !== false && this.state.alive !== true) {
            return null;
        }

        const instance = this.state.instance.replace(/^system.adapter./, '');
        return <div className={Utils.clsx(this.props.classes.root, !this.state.alive && this.props.classes.notAlive)}>
            {this.state.alive ?
                this.props.schema.textAlive !== undefined ? (this.props.schema.textAlive ? I18n.t(this.props.schema.textAlive, instance) : '') : I18n.t('Instance %s is alive', instance)
                :
                this.props.schema.textNotAlive !== undefined ? (this.props.schema.textNotAlive ? I18n.t(this.props.schema.textNotAlive, instance) : '') : I18n.t('Instance %s is not alive', instance)
            }
        </div>;
    }
}

ConfigAlive.propTypes = {
    socket: PropTypes.object.isRequired,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    adapterName: PropTypes.string,
    instance: PropTypes.number,
};

export default withStyles(styles)(ConfigAlive);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigStaticHeader.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import {Typography} from '@material-ui/core';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    header: {
        width: '100%',
        background: theme.palette.primary.main,
        color: theme.palette.primary.contrastText,
        padding: 4,
        borderRadius: 3
    },
});

class ConfigStaticHeader extends ConfigGeneric {
    renderItem() {
        return <Typography
            className={this.props.classes.header}
            component={this.props.schema.size ? 'h' + this.props.schema.size : 'h5'}
        >
            {this.getText(this.props.schema.text, this.props.schema.noTranslation)}
        </Typography>;
    }
}

ConfigStaticHeader.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigStaticHeader);

<|start_filename|>src-rx/src/dialogs/WizardDialog.js<|end_filename|>
import { Component } from 'react';
import {withStyles} from '@material-ui/core/styles';
import withWidth from "@material-ui/core/withWidth";
import PropTypes from 'prop-types';
import clsx from 'clsx';

import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';

import ToggleThemeMenu from '../components/ToggleThemeMenu';

// Icons
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
import CheckIcon from '@material-ui/icons/Check';

import WizardPasswordTab from '../components/Wizard/WizardPasswordTab';
import WizardLicenseTab from '../components/Wizard/WizardLicenseTab';
import WizardFinishImage from '../assets/wizard-finish.jpg';
import WizardWelcomeImage from '../assets/wizard-welcome.jpg';
import WizardSettingsTab from '../components/Wizard/WizardSettingsTab';
import WizardAuthSSLTab from '../components/Wizard/WizardAuthSSLTab';
import WizardPortForwarding from '../components/Wizard/WizardPortForwarding';
import Logo from '../assets/logo.png';
import LongLogo from '../assets/longLogo.svg';
import Router from "@iobroker/adapter-react/Components/Router";

const TOOLBAR_HEIGHT = 64;

const styles = theme => ({
    dialog: {
        height: '100%',
        maxHeight: '100%',
        maxWidth: '100%',
    },
    paper: {
        width: '100%',
        height: '100%',
        overflow: 'hidden'
    },
    content: {
        textAlign: 'center',
    },
    tabPanel: {
        width: '100%',
        overflow: 'hidden',
        height: 'calc(100% - ' + 72 + 'px)',
    },
    fullHeightWithoutToolbar: {
        height: 'calc(100% - ' + TOOLBAR_HEIGHT + 'px)',
        width: '100%',
        overflow: 'auto',
    },
    finishBackground: {
        backgroundImage: 'url(' + WizardFinishImage + ')',
        backgroundRepeat: 'no-repeat',
        backgroundSize: 'cover',
        backgroundPosition: 'center',
    },
    welcomeBackground: {
        backgroundImage: 'url(' + WizardWelcomeImage + ')',
        backgroundRepeat: 'no-repeat',
        backgroundSize: 'cover',
        backgroundPosition: 'center',
    },
    grow: {
        flexGrow: 1,
    },
    playIcon: {
        marginLeft: theme.spacing(1),
    },
    toolbar: {
        height: TOOLBAR_HEIGHT,
    },
    logo: {
        width: 32,
        height: 32,
        float: 'right',
        borderRadius: '50%',
        background: 'white',
    },
    themeButton: {
        float: 'right',
        display: 'inline-block',
        marginTop: -1,
        marginRight: theme.spacing(1)
    },

    finalText: {
        fontSize: 48,
        marginTop: 80,
        color: '#265063',
        fontWeight: 'bold'
    },
    finalLongLogo: {
        width: 500,
    }
});

class WizardDialog extends Component {
    constructor(props) {
        super(props);

        this.state = {
            exitAvailable: false,
            activeStep: 0,
            auth: false,
            secure: false,
        };
        this.adminInstance = null;

        this.password = '';

        this.lastPage = 1;
    }

    componentDidMount() {
        this.props.socket.getCurrentInstance()
            .then(namespace =>
                this.props.socket.getObject('system.adapter.' + namespace)
                    .then(obj => {
                        this.adminInstance = obj;
                        this.setState({auth: obj.native.auth, secure: obj.native.secure})
                    }));
    }

    renderWelcome() {
        // shutterstock Standard commercial license on ioBroker GmbH: https://www.shutterstock.com/de/image-vector/welcome-neon-text-vector-sign-design-1186433386
        return <div className={ clsx(this.props.classes.paper, this.props.classes.welcomeBackground) }>
            <div className={ this.props.classes.fullHeightWithoutToolbar }>

            </div>
            <Toolbar className={ this.props.classes.toolbar }>
                <div className={ this.props.classes.grow }/>
                <Button
                    variant="contained"
                    color="primary"
                    onClick={ () =>
                        this.props.socket.getCompactSystemConfig(true)
                            .then(obj =>
                                this.setState( {activeStep: this.state.activeStep + 1 + (obj.common.licenseConfirmed ? 0 : 0) }))
                    }>
                    { this.props.t('Start wizard') } <PlayArrowIcon className={ this.props.classes.playIcon }/></Button>
                <div className={ this.props.classes.grow }/>
            </Toolbar>
        </div>;
    }

    renderLicense() {
        return <WizardLicenseTab
            t={ this.props.t }
            socket={ this.props.socket }
            themeName={ this.props.themeName }
            onDone={ () => {
                this.props.socket.getSystemConfig(true)
                    .then(obj => {
                        obj.common.licenseConfirmed = true;
                        return this.props.socket.setSystemConfig(obj);
                    })
                    .then(() => this.setState( {activeStep: this.state.activeStep + 1 }));
            } }
        />;
    }

    renderPassword() {
        return <WizardPasswordTab
            t={ this.props.t }
            socket={ this.props.socket }
            themeName={ this.props.themeName }
            onDone={ pass =>
                this.props.socket.changePassword('admin', pass)
                    .then(() =>
                        this.setState( {activeStep: this.state.activeStep + 1 }))}
        />;
    }

    renderSettings() {
        return <WizardSettingsTab
            t={ this.props.t }
            socket={ this.props.socket }
            themeName={ this.props.themeName }
            onDone={settings =>
                this.props.socket.getSystemConfig(true)
                    .then(obj => {
                        Object.assign(obj.common, settings);
                        return this.props.socket.setSystemConfig(obj);
                    })
                    .then(() => this.setState({activeStep: this.state.activeStep + 1}))
            }
        />;
    }

    renderAuthentication() {
        return <WizardAuthSSLTab
            t={ this.props.t }
            auth={this.state.auth}
            secure={this.state.secure}
            socket={ this.props.socket }
            themeName={ this.props.themeName }
            onDone={settings =>
                this.setState(settings, () => this.setState({activeStep: this.state.activeStep + 1}))}
        />;
    }

    renderPortForwarding() {
        return <WizardPortForwarding
            t={ this.props.t }
            socket={ this.props.socket }
            themeName={ this.props.themeName }
            auth={this.state.auth}
            secure={this.state.secure}
            onDone={() =>
                this.setState({activeStep: this.state.activeStep + 1})}
        />;
    }

    async onClose() {
        // read if discovery is available
        const discovery = await this.props.socket.getState('system.adapter.discovery.0.alive');

        if (this.adminInstance) {
            let certPublic;
            let certPrivate;
            if (this.adminInstance.native.secure !== this.state.secure || this.adminInstance.native.auth !== this.state.auth) {
                if (this.state.secure && (!this.adminInstance.certPublic || !!this.adminInstance.certPrivate)) {
                    // get certificates
                    try {
                        const certs = await this.props.socket.getCertificates();
                        certPublic  = certs && certs.find(c => c.type === 'public');
                        certPrivate = certs && certs.find(c => c.type === 'private');
                    } catch (e) {
                        window.alert('Cannot read certificates: ' + e);
                    }
                }
                this.adminInstance.native.auth = this.state.auth;

                if (this.state.secure) {
                    if (!(this.adminInstance.native.certPublic  || certPublic) ||
                        !(this.adminInstance.native.certPrivate || certPrivate))
                    {
                        window.alert(this.props.t('Cannot enable authentication as no certificates found!'));
                        this.adminInstance.native.secure = false;

                        await this.props.socket.setObject(this.adminInstance._id, this.adminInstance);
                        setTimeout(() => window.location = `http://${window.location.host}/#tab-adapters${discovery ? '/discovery' : ''}`, 1000);
                        return this.props.onClose();
                    } else {
                        this.adminInstance.native.secure      = this.state.secure;
                        this.adminInstance.native.certPublic  = this.adminInstance.native.certPublic  || certPublic.name;
                        this.adminInstance.native.certPrivate = this.adminInstance.native.certPrivate || certPrivate.name;
                    }
                }

                await this.props.socket.setObject(this.adminInstance._id, this.adminInstance);

                // redirect to https or http
                setTimeout(() => {
                    if (this.adminInstance.native.secure) {
                        window.location = `https://${window.location.host}/#tab-adapters${discovery ? '/discovery' : ''}`;
                    } else {
                        window.location = `http://${window.location.host}/#tab-adapters${discovery ? '/discovery' : ''}`;
                    }
                }, 1000);

                this.props.onClose();
            } else {
                Router.doNavigate('tab-adapters', discovery ? 'discovery' : undefined);
                this.props.onClose();
            }
        } else {
            Router.doNavigate('tab-adapters', discovery ? 'discovery' : undefined);
            this.props.onClose();
        }
    }

    renderFinish() {
        // Free Image license: https://pixabay.com/illustrations/road-sky-mountains-clouds-black-908176/
        return <div className={ clsx(this.props.classes.paper, this.props.classes.finishBackground) }>
            <div className={ this.props.classes.fullHeightWithoutToolbar }>
                <div className={this.props.classes.finalText}>{this.props.t('Have fun automating your home with')}</div>
                <img src={LongLogo} alt="ioBroker" className={this.props.classes.finalLongLogo}/>
            </div>
            <Toolbar className={ this.props.classes.toolbar }>
                <div className={ this.props.classes.grow }/>
                <Button variant="contained" color="primary" onClick={ async () => await this.onClose() } startIcon={<CheckIcon/>}>{ this.props.t('Finish') }</Button>
                <div className={ this.props.classes.grow }/>
            </Toolbar>
        </div>;
    }

    render() {
        return <Dialog
            className={ this.props.classes.dialog }
            open={ true }
            onClose={ () => {} }
            fullWidth={ true }
            fullScreen={ true }
            aria-labelledby="wizard-dialog-title"
        >
            <DialogTitle id="wizard-dialog-title">{ this.props.t('Initial ioBroker setup') } <img src={Logo} className={this.props.classes.logo} alt="logo"/><ToggleThemeMenu className={this.props.classes.themeButton} t={this.props.t} toggleTheme={this.props.toggleTheme} themeName={this.props.themeName} size="small"/></DialogTitle>
            <DialogContent className={ this.props.classes.content }>
                <AppBar position="static">
                    <Stepper activeStep={ this.state.activeStep }>
                        <Step><StepLabel>{ this.props.t('Welcome') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('License agreement') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('Password') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('Authentication') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('Port forwarding') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('Settings') }</StepLabel></Step>
                        <Step><StepLabel>{ this.props.t('Finish') }</StepLabel></Step>
                    </Stepper>
                </AppBar>
                {this.state.activeStep === 0 ? <div className={ this.props.classes.tabPanel }>{ this.renderWelcome()        }</div> : null }
                {this.state.activeStep === 1 ? <div className={ this.props.classes.tabPanel }>{ this.renderLicense()        }</div> : null }
                {this.state.activeStep === 2 ? <div className={ this.props.classes.tabPanel }>{ this.renderPassword()       }</div> : null }
                {this.state.activeStep === 3 ? <div className={ this.props.classes.tabPanel }>{ this.renderAuthentication() }</div> : null }
                {this.state.activeStep === 4 ? <div className={ this.props.classes.tabPanel }>{ this.renderPortForwarding() }</div> : null }
                {this.state.activeStep === 5 ? <div className={ this.props.classes.tabPanel }>{ this.renderSettings()       }</div> : null }
                {this.state.activeStep === 6 ? <div className={ this.props.classes.tabPanel }>{ this.renderFinish()         }</div> : null }
            </DialogContent>
        </Dialog>;
    }
}

WizardDialog.propTypes = {
    t: PropTypes.func,
    lang: PropTypes.string,
    socket: PropTypes.object,
    onClose: PropTypes.func,
    toggleTheme: PropTypes.func,
    themeName: PropTypes.string,
};

export default withWidth()(withStyles(styles)(WizardDialog));


<|start_filename|>src/lib/js/cron/jquery.cron.js<|end_filename|>
function translateCron() {
    if (typeof systemDictionary !== 'undefined' && !systemDictionary['CRON Every %s seconds']) {
        for (var w in jQueryCronWords) {
            if (jQueryCronWords.hasOwnProperty(w)) {
                systemDictionary[w] = jQueryCronWords[w];
            }
        }
        //translateAll('#dialog-cron');
    }
}

function setupCron(value, callback) {
    var $el      = $('#dialog-cron');
    var $input   = $el.find('.cron-input');
    var $seconds = $el.find('.cron-checkbox-seconds');
    $el.data('callback', callback);

    if (value !== undefined) {
        $input.val(value);
    }

    $el.find('.btn-clear').off('click').click(function (e) {
        e.stopPropagation();
        e.preventDefault();
        $input.val('').trigger('change');
    });
    $el.find('.btn-apply').off('click').click(function () {
        var cb = $el.data('callback');
        $el.data('callback', null);
        cb($input.val());
    });
    $el.find('.btn-cancel').off('click').click(function () {
        var cb = $el.data('callback');
        $el.data('callback', null);
        cb();
    });
    var types = [
        'second',
        'minute',
        'hour',
        'day',
        'month',
        'week'
    ];

    var everyText = [
        'CRON Every %s seconds',
        'CRON Every %s minutes',
        'CRON Every %s hours',
        'CRON Every %s days',
        'CRON Every %s months'
    ];

    var cronArr;
    var updateInput = false;

    var value = $input.val();
    if (!value) {
        $seconds.prop('checked', false);
        cronArr = null;
    } else {
        cronArr = value.split(' ');
        if (cronArr.length === 5) {
            $seconds.prop('checked', false);
            cronArr.unshift('*');
        } else {
            $seconds.prop('checked', true);
        }
    }

    var drawEach = {
        second: drawEachSecond,
        minute: drawEachMinute,
        hour:   drawEachHour,
        day:    drawEachDay,
        month:  drawEachMonth,
        week:   drawEachWeekday
    };

    function enableSeconds(isEnabled) {
        if (isEnabled === undefined) {
            isEnabled =  $seconds.prop('checked');
        }

        if (isEnabled) {
            $el.find('.cron-tab-second').show();
        } else {
            $el.find('.cron-tab-second').hide();
            if (getActiveTab() === 'cron-tabs-second') {
                $el.find('.tabs').mtabs('select', 'cron-tabs-minute');
            }
        }
    }

    function getActiveTab() {
        return M.Tabs.getInstance($el.find('.tabs')).$activeTabLink.attr('href').replace(/^#/, '');
    }

    $el.find('.tabs').mtabs();
    $el.find('.cron-type-selector').select().off('change').change(function () {
        if ($input.is(':focus') || updateInput) return;
        cronArr = cronArr || ['*', '*', '*', '*', '*', '*'];
        var type = $(this).data('type');
        var val  = $(this).val();
        var cronVal;
        var $tab = $el.find('.page[data-type="' + type + '"]');
        if (val === 'every') {
            cronVal = '*';
            $tab.find('.n').hide();
            $tab.find('.each').hide();
        } else if (val === 'n') {
            var sliderVal = $tab.find('.cron-slider').val();
            cronVal = '*/' + sliderVal;
            $tab.find('.n').show();
            $tab.find('.each').hide();
            updateSliderText(type, sliderVal);
        } else if (val === 'each') {
            $tab.find('.n').hide();
            $tab.find('.each').show();
            drawEach[type]();
            cronVal = '*';
        }
        cronArr[types.indexOf(type)] = cronVal;

        drawCron();
    });
    $el.find('.cron-slider').change(processSlider);
    M.Range.init($el.find('.cron-slider'));

    // workaround for materialize checkbox problem
    $el.find('.cron-checkbox-seconds+span').off('click').click(function () {
        var $input = $(this).prev();
        if (!$input.prop('disabled')) {
            $input.prop('checked', !$input.prop('checked')).trigger('change');
        }
    });

    $seconds.off('change').change(function () {
        enableSeconds($(this).prop('checked'));
        drawCron();
    });

    // init of seconds (whithout drawCron)
    enableSeconds();

    $input.off('change').change(function () {
        $(this).focus();
        cronArr = text2cron($(this).val());
        detectSettings($(this).val());
    }).off('keyup').keyup(function () {
        $(this).trigger('change');
    });

    function text2cron(value) {
        if (value === undefined) {
            value = $input.val();
        }
        value = value.trim();
        if (!value) {
            enableSeconds(false);
            return null;
        }

        var arr = value.split(' ');

        if (arr.length === 5) {
            arr.unshift('*');
            enableSeconds(false);
        } else {
            enableSeconds(true);
        }

        return arr;
    }

    function cron2text(arr) {
        if (!arr) arr = cronArr;

        if (!arr) {
            return '';
        }

        arr = JSON.parse(JSON.stringify(arr || ['*', '*', '*', '*', '*', '*']));
        if (!$seconds.prop('checked')) {
            arr.shift();
        }
        for (var a = 0; a < arr.length; a++) {
            if (arr[a] === '*/1') arr[a] = '*';
        }

        return arr.join(' ');
    }

    function correctCasus(text, seconds) {
        text = text.replace('Каждую(ый) минуту',    'Каждую минуту');
        text = text.replace('Каждую(ый) минут(у)',  'Каждую минуту');
        text = text.replace('Каждую(ый) час',       'Каждый час');
        text = text.replace('Каждую(ый) секунду',   'Каждую секунду');
        text = text.replace(/ (\d{1,2}) числа/,     ' $1го числа');

        text = text.replace(/ (\d{1,2}) в Январе/,  ' $1го числа в Январе');
        text = text.replace(/ (\d{1,2}) в Феврале/, ' $1го числа в Феврале');
        text = text.replace(/ (\d{1,2}) в Марте/,   ' $1го числа в Марте');
        text = text.replace(/ (\d{1,2}) в Апреле/,  ' $1го числа в Апреле');
        text = text.replace(/ (\d{1,2}) в Майе/,    ' $1го числа в Майе');
        text = text.replace(/ (\d{1,2}) в Июне/,    ' $1го числа в Июне');
        text = text.replace(/ (\d{1,2}) в Июле/,    ' $1го числа в Июле');
        text = text.replace(/ (\d{1,2}) в Августе/,  ' $1го числа в Августе');
        text = text.replace(/ (\d{1,2}) в Сентябре/,  ' $1го числа в Сентябре');
        text = text.replace(/ (\d{1,2}) в Октябре/,  ' $1го числа в Октябре');
        text = text.replace(/ (\d{1,2}) в Ноябре/,  ' $1го числа в Ноябре');
        text = text.replace(/ (\d{1,2}) в Декабре/,  ' $1го числа в Декабре');

        text = text.replace('Каждую(ый) 0 минуту',   'Каждые ноль минут');
        text = text.replace(/Каждую\(ый\) ([\d\sи,]+) минуту/, 'Каждую $1 минуту');

        text = text.replace(/каждой\(го\) ([\d\sи,]+) минуту/, 'каждой $1 минуты');
        text = text.replace('каждой(го) минут(у)',  'каждой минуты');

        text = text.replace(' 0 часа(ов)',           ' 0 часов');
        text = text.replace(' 1 часа(ов)',           ' 1 час');
        text = text.replace(' 2 часа(ов)',           ' 2 часа');
        text = text.replace(' 3 часа(ов)',           ' 3 часа');
        text = text.replace(' 4 часа(ов)',           ' 4 часа');
        text = text.replace(/ (\d{1,2}) часа\(ов\)/, ' $1 часов');

        text = text.replace('Jede(r) Sekunde',      'Jede Sekunde');
        text = text.replace(/Jede\(r\) ([\d\sund,]+) Sekunde/, 'Jede $1 Sekunde');
        text = text.replace('Jede(r) Minute',       'Jede Minute');
        text = text.replace('Jede Minuten',         'Jede Minute');
        text = text.replace('Jede Stunde',          'Jede Stunde');
        text = text.replace('Jede(r) Stunde',       'Jede Stunde');
        text = text.replace(/Jede\(r\) ([\d\sund,]+) Minute/, 'Jede $1 Minute');
        text = text.replace('Jede Sekunde in Minuten', 'Jede Sekunde in jeder Minute');
        
        return text
    }

    function drawCron() {
        var newCron = cron2text();
        $input.val(newCron);
        updateDescription(newCron);
    }

    function updateDescription(value) {
        if (!value) {
            $el.find('.cron-text').html(_('never'));
            return;
        }
        var isSeconds = $seconds.prop('checked');
        var text = cronToText(value, isSeconds, JQUERY_CRON_LOCALE[systemLang]);
        text = correctCasus(text, isSeconds ? cronArr[0] : null);

        $el.find('.cron-text').html(text);
    }

    function detectSettings(value) {
        updateInput = true;
        cronArr = text2cron(value);

        for (var c = 0; c < (cronArr ? cronArr.length : 6); c++) {
            detect(cronArr, c);
        }

        updateDescription(value);
        updateInput = false;
    }

    // 5-7,9-11 => 5,6,7,9,10,11
    function convertMinusIntoArray(value) {
        var parts = value.toString().split(',');
        for (var p = 0; p < parts.length; p++) {
            var items = parts[p].trim().split('-');
            if (items.length > 1) {
                parts[p] = [];
                for (var i = parseInt(items[0], 10); i <= parseInt(items[1], 10); i++) {
                    parts[p].push(i);
                }
                parts[p] = parts[p].join(',');
            }
        }
        var value = parts.join(',');
        var values = value.split(',');
        values.sort(function (a, b) {
            a = parseInt(a, 10);
            b = parseInt(b, 10);
            return a - b;
        });
        // remove double entries
        for (p = values.length - 1; p >= 0; p--) {
            if (values[p] === values[p + 1]) {
                values.splice(p + 1, 1);
            }
        }

        return values.join(',');
    }

    // 5,6,7,9,10,11 => 5-7,9-11
    function convertArrayIntoMinus(value) {
        value = convertMinusIntoArray(value);

        var parts = value.split(',');
        var newParts = [];
        var start = parts[0];
        var end   = parts[0];
        for (var p = 1; p < parts.length; p++) {
            if (parts[p] - 1 !== parseInt(parts[p - 1], 10)) {
                if (start === end) {
                    newParts.push(start)
                } else if (end - 1 == start) {
                    newParts.push(start + ',' + end);
                }else {
                    newParts.push(start + '-' + end);
                }
                start = parts[p];
                end   = parts[p];
            } else {
                end = parts[p];
            }
        }

        if (start === end) {
            newParts.push(start)
        } else if (end - 1 == start) {
            newParts.push(start + ',' + end);
        } else {
            newParts.push(start + '-' + end);
        }

        return newParts.join(',');
    }

    function detect(values, index) {
        var $tab = $el.find('.page[data-type="' + types[index] + '"]');

        if (!values) {
            if ($tab.find('.cron-type-selector').val() !== 'every') {
                $tab.find('.cron-type-selector').val('every');
                console.log($tab.find('.n').length);
                $tab.find('.n').hide();
                $tab.find('.each').hide();
                changed = true;
            } else {
                $tab.find('.n').hide();
                $tab.find('.each').hide();
            }
            $tab.find('.cron-slider').val(1);
            return;
        }

        values[index] = values[index] || '*';
        var changed   = true;
        var $selector = $tab.find('.cron-type-selector');
        var cronType  = $selector.val();

        if (values[index].indexOf('/') !== -1) {
            var parts_ = values[index].split('/');
            var value  = parseInt(parts_[1], 10) || 1;
            if ($tab.find('.cron-slider').val() !== value.toString()) {
                $tab.find('.cron-slider').val(value);
                changed = true;
            }
            if (cronType !== 'n') {
                $selector.val('n');
                changed = true;
            }
            updateSliderText(types[index], value);

        } else if (values[index].indexOf('*') !== -1) {
            $tab.find('.cron-slider').val(1);
            if ($selector.val() !== 'every') {
                $selector.val('every');
                changed = true;
            }
        } else {
            $tab.find('.cron-slider').val(1);
            // each
            var parts = convertMinusIntoArray(values[index]).split(',');
            if ($selector.val() !== 'each') {
                $selector.val('each');
                changed = true;
            }
            var selected = false;

            $tab.find('.cron-number').each(function () {
                var index = $(this).data('index').toString();
                var value = parts.indexOf(index.toString()) !== -1;
                if (value && !$(this).hasClass('selected')) {
                    $(this).addClass('selected');
                    changed = true;
                } else if (!value && $(this).hasClass('selected')) {
                    $(this).removeClass('selected');
                    changed = true;
                }
                if (value) selected = true;
            });

            if (!selected) {
                if ($selector.val() !== 'every') {
                    $selector.val('every');
                    changed = true;
                }
            }
            if (changed) {
                $el.find('.tabs').mtabs('select', 'cron-tabs-' + types[index]);
            }
        }
        cronType = $selector.val();

        if (cronType === 'n') {
            $tab.find('.n').show();
            $tab.find('.each').hide();
        } else if (cronType === 'each') {
            $tab.find('.n').hide();
            $tab.find('.each').show();
        } else {
            $tab.find('.n').hide();
            $tab.find('.each').hide();
        }

        $selector.select();
    }

    function updateSliderText(type, val) {
        var $selector = $el.find('.page[data-type="' + type + '"] .cron-type-selector');
        $selector.find('option[value="n"]').html(val === 1 ? _('CRON Every ' + type) : _('CRON Every') + ' ' + val + ' ' + _('CRON ' + type + 's'));
        $selector.select();

    }

    function processSlider() {
        var arg  = $(this).data('arg');
        var type = $(this).data('type');
        var val  = $(this).val();
        if (!cronArr) {
            cronArr = ['*', '*', '*', '*', '*', '*'];
        }

        cronArr[arg] = '*/' + val;
        updateSliderText(type, val);
        drawCron();
    }

    function processEachChange() {
        var val;
        if ($(this).hasClass('selected')) {
            $(this).removeClass('selected');
            val = false;
        } else {
            $(this).addClass('selected');
            val = true;
        }

        var newItem = $(this).data('index').toString();
        var arg     = $(this).data('arg');

        if (!cronArr) {
            cronArr = ['*', '*', '*', '*', '*', '*'];
        }

        if (cronArr[arg] === '*') {
            cronArr[arg] = newItem;
        } else {
            // if value already in list, toggle it off
            var list = convertMinusIntoArray(cronArr[arg]).split(',');
            var pos = list.indexOf(newItem);
            if (!val) {
                if (pos !== -1) {
                    list.splice(pos, 1);
                }
                cronArr[arg] = list.join(',');
            } else {
                // else toggle it on
                if (pos === -1) {
                    cronArr[arg] = cronArr[arg] + ',' + newItem;
                }
            }
            cronArr[arg] = convertArrayIntoMinus(cronArr[arg]);
            if(cronArr[arg] === '') cronArr[arg] = '*';
        }
        drawCron();
    }

    function padded(val) {
        if (typeof val === 'string' && val.length === 1) {
            val = '0' + val;
        } else if (val < 10) {
            val = '0' + val;
        } else {
            val = val.toString();
        }
        return val;
    }

    function draw(type, drawFunc) {
        var $format = $el.find('.page[data-type="' + type + '"] .cron-tabs-format');
        $format.html(drawFunc());

        if (!cronArr) {
            cronArr = ['*', '*', '*', '*', '*', '*'];
        }
        var arg = types.indexOf(type);
        var list = convertMinusIntoArray(cronArr[arg]).split(',');

        $format.find('.cron-number').off('click').click(processEachChange).each(function () {
            if (list.indexOf($(this).data('index').toString()) !== -1) {
                $(this).addClass('selected')
            }
        });
    }

    function drawEachSecond() {
        draw('second', function () {
            var text = '';
            // seconds
            for (var i = 0; i < 60; i++) {
                text += '<div data-index="' + i + '" data-type="second" data-arg="0" class="cron-number">' + padded(i) + '</div>';
                if (i !== 0 && ((i + 1) % 10 === 0)) text += '<br/>';
            }
            return text;
        });
    }

    function drawEachMinute () {
        draw('minute', function () {
            var text = '';
            // minutes
            for (var i = 0; i < 60; i++) {
                text += '<div data-index="' + i + '" data-type="minute" data-arg="1" class="cron-number">' + padded(i) + '</div>';
                if (i !== 0 && (((i + 1) % 10) === 0)) text += '<br/>';
            }
            return text;
        });
    }

    function drawEachHour() {
        draw('hour', function () {
            var text = '';
            // hours
            for (var i = 0; i < 24; i++) {
                text += '<div data-index="' + i + '" data-type="hour" data-arg="2" class="cron-number">' + padded(i) + '</div>';
                if (i !== 0 && (((i + 1) % 12) === 0)) text += '<br/>';
            }
            return text;
        });
    }

    function drawEachDay () {
        draw('day', function () {
            var text = '';
            // days
            for (var i = 1; i < 36; i++) {
                if (i > 31) {
                    text += '<div class="cron-number-empty">&nbsp;</div>';
                } else {
                    text += '<div data-index="' + i + '" data-type="day" data-arg="3" class="cron-number">' + padded(i) + '</div>';
                }

                if (i !== 0 && ((i % 7) === 0)) text += '<br/>';
            }
            return text;
        });
    }
    
    function drawEachMonth () {
        draw('month', function () {
            var text = '';
            // months
            var months = ['Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];

            for (var i = 0; i < months.length; i++) {
                text += '<div data-index="' + (i + 1) + '" data-type="month" data-arg="4" class="cron-number">' + _(months[i]) + '</div>';
            }
            return text;
        });
    }

    function drawEachWeekday () {
        draw('week', function () {
            var text = '';
            // weeks
            var days = [
                {id: 1, name: 'Monday'},
                {id: 2, name: 'Tuesday'},
                {id: 3, name: 'Wednesday'},
                {id: 4, name: 'Thursday'},
                {id: 5, name: 'Friday'},
                {id: 6, name: 'Saturday'},
                {id: 0, name: 'Sunday'}
            ];

            for (var i = 0; i < days.length; i++) {
                text += '<div data-index="' + days[i].id + '" data-type="week" data-arg="5" class="cron-number">' + _(days[i].name) + '</div>';
            }
            return text;
        });
    }

    drawEachSecond();
    drawEachMinute();
    drawEachHour();
    drawEachDay();
    drawEachMonth();
    drawEachWeekday();
    drawCron();
    detectSettings($input.val());
    $el.modal({
        dismissible: false
    }).modal('open');
}

<|start_filename|>src/js/adminFileUtils.js<|end_filename|>
function fileHandler(event) {
    event.preventDefault();
    var file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0];

    var $dz      = $(this).find('.drop-zone');
    var callback = $(this).data('drop-zone-cb');
    var limit    = $(this).data('limit');
    if (file.size > (limit || 10000)) {
        callback && callback(_('File is too big!'));
        $dz.hide();
        return false;
    }
    $dz.show();
    var reader = new FileReader();
    reader.onload = function (evt) {
        $dz.hide();
        callback && callback(null, evt.target.result);
    };
    reader.readAsDataURL(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]));
}

<|start_filename|>src-rx/src/helpers/cardSort.js<|end_filename|>
import _ from 'lodash';

const funcSet = _.throttle(
    (setTabs, tabs) => setTabs(tabs)
    , 200);

const moveCard = (id, atIndex, tabs, setTabs, hoverClientY, hoverMiddleY) => {
    const { card, index } = findCard(id, tabs);
    if (index === atIndex) {
        return;
    }
    if (index < atIndex && hoverClientY < hoverMiddleY) {
        return;
    }
    if (index > atIndex && hoverClientY > hoverMiddleY) {
        return;
    }
    if (card && index !== atIndex) {
        const copyCard = JSON.parse(JSON.stringify(tabs));
        copyCard.splice(index, 1);
        copyCard.splice(atIndex, 0, card);
        funcSet(setTabs,copyCard);
    }
};

const findCard = (id, cards) => {
    const card = cards.find(c => c.name === id);
    return {
        card,
        index: cards.indexOf(card),
    };
};

export { moveCard, findCard };

<|start_filename|>src/js/adminReadme.js<|end_filename|>
/* global systemLang, showdown */

function Readme(main) {
    'use strict';

    var that           = this;
    this.$dialog       = $('#dialog-readme');
    this.$divReadme    = this.$dialog.find('.result-readme');
    this.$divChangeLog = this.$dialog.find('.result-changelog');
    this.$divLicense   = this.$dialog.find('.result-license');
    this.$divLogo      = this.$dialog.find('.result-logo');
    this.$tabs         = this.$dialog.find('.tabs');
    this.mainUrl       = '';
    this.main          = main;

    this.prepare = function () {
    };

    this.init = function () {
        if (this.inited) {
            return;
        }

        this.inited = true;

        if (!this.$tabs.data('inited')) {
            this.$tabs.data('inited', true);
            this.$tabs.mtabs();
        }

        showdown.setFlavor('github');

        var adapterName = this.main.navigateGetParams();

        var url = that.main.tabs.adapters.$tab.find('.adapter-readme-submit[data-adapter-name="' + adapterName + '"]').data('adapter-url');
        if (!url) {
            url = localStorage.getItem('original-md-url');
        }
        if (url) {
            localStorage.setItem('original-md-url', url);
            url = url.replace('https://github.com', 'https://raw.githubusercontent.com').replace('blob/', '');
            var tmp = url.split('/');
            that.mainUrl = 'https://raw.githubusercontent.com/' + tmp[3] + '/' + tmp[4] + '/master/';
            that.$dialog.find('.title').html(adapterName);
            that.$divReadme.empty();
            that.fillDiv(url);
        }

        that.$dialog.find('.dialog-system-buttons .btn-cancel').off('click').on('click', function (e) {
            e.stopPropagation();
            e.preventDefault();
            localStorage.removeItem('original-md-url');
            that.main.navigate();
        });
    };

    function md2html(data, link) {
        var html = new showdown.Converter().makeHtml(data);
        html = html.replace(/id="/g, 'id="mdid-');
        html = html.replace(/src="(?!http)/g, 'class="responsive-img" src="' + link);
        html = html.replace(/href="#/g, 'href="" class="goto-link" data-goto="#mdid-');
        html = html.replace(/href="(\S*)\.md/g, function (match) {
            return 'href="" class="md-link" data-url="' + that.mainUrl + match.replace('href="', '');
        });
        html = html.replace(/<!--[^>]*-->/g, '\n');
        return html.replace(/href="http/g, 'target="_blank" href="http');
    }

    function trimArr(lines) {
        var j = lines.length - 1;
        while (j >= 0 && !lines[j]) {
            j--;
        }
        if (j !== lines.length - 1) {
            lines.splice(j);
        }
        return lines;
    }

    function splitReadMe(html, link) {
        var result = {logo: '', readme: [], changeLog: [], license: []};
        var lines = html.trim().split(/\r\n|\n/);

        // second line is main title
        if (lines[2].match(/^#\sio/)) {
            lines.splice(2, 1);
        }
        if (lines[1].match(/^#\sio/)) {
            lines.splice(1, 1);
        }
        // first line is logo
        if (lines[0].match(/!\[[-_\w\d]*]\([-._\w\d\/]+\.png\)/)) {
            result.logo = link + lines[0].match(/\((.+)\)/)[1];
            lines.splice(0, 1);
        }
        var part = 'readme';
        var i = 0;
        while (i < lines.length) {
            if (lines[i].match(/^====/)) {
                i++;
                continue;
            }
            if (lines[i].match(/^###?\s+Changelog/)) {
                part = 'changeLog';
                i++;
                continue;
            } else if (lines[i].match(/^###?\s+License/)) {
                part = 'license';
                i++;
                continue;
            }
            if (!result[part].length && !lines[i]) {
                i++;
                continue;
            }
            result[part].push(lines[i]);
            i++;
        }

        if (result.logo) {
            that.$divLogo.html('<img src="' + result.logo + '" />').show();
        } else {
            that.$divLogo.html('').hide();
        }
        trimArr(result.readme);
        trimArr(result.changeLog);
        trimArr(result.license);

        if (result.readme.length) {
            result.readme = md2html(result.readme.join('\n'), link);
        } else {
            result.readme = '';
        }
        if (result.changeLog.length) {
            result.changeLog = md2html(result.changeLog.join('\n'), link);
        } else {
            delete result.changeLog;
        }
        if (result.license.length) {
            result.license[0] = '## ' + result.license[0];
            result.license = md2html(result.license.join('\n'), link);
        } else {
            delete result.license;
        }

        return result;
    }

    this.fillDiv = function (url) {
        $.get(url, function (data) {
            var orgUrl = url.replace('https://raw.githubusercontent.com', 'https://github.com').replace('/master/', '/blob/master/');
            that.$dialog.find('.dialog-system-buttons .btn-open-org').attr('href', orgUrl);
            // Split data into 3 parts: Readme, ChangeLog and License
            var parts = splitReadMe(data, url.substring(0, url.lastIndexOf('/') + 1));
            that.$divReadme.html(parts.readme);
            if (parts.changeLog) {
                that.$divChangeLog.html(parts.changeLog);
                that.$tabs.find('.tab-changelog').show();
            } else {
                that.$divChangeLog.html('');
                that.$tabs.find('.tab-changelog').hide();
            }
            if (parts.license) {
                that.$divLicense.html(parts.license);
                that.$tabs.find('.tab-license').show();
            } else {
                that.$divLicense.html('');
                that.$tabs.find('.tab-license').hide();
            }
        }).done(function () {
            that.$divReadme.on('click', '.md-link', function (e) {
                e.stopPropagation();
                e.preventDefault();
                that.fillDiv($(this).data('url'));
            });
            that.$divReadme.on('click', '.goto-link', function (e) {
                e.stopPropagation();
                e.preventDefault();
                var $elemId = $($(this).data('goto'));
                if ($elemId.length) {
                    that.$divReadme.animate({
                        scrollTop: that.$divReadme.scrollTop() - that.$divReadme.offset().top + $elemId.offset().top
                    }, 2000);
                }
            });
        });
    };

    this.destroy = function () {
        if (this.inited) {
            // this.$dialog.find('.collapsible').collapsible('destroy');
            localStorage.removeItem('original-md-url');
            this.inited = false;
        }
    };
}


<|start_filename|>src-rx/src/components/Object/ObjectBrowserEditRole.js<|end_filename|>
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';

import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import TextField from '@material-ui/core/TextField';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import Autocomplete from '@material-ui/lab/Autocomplete';

import IconCancel from '@material-ui/icons/Close';
import IconCheck from '@material-ui/icons/Check';

const styles = theme => ({
    input: {
        marginBottom: theme.spacing(2),
    },
    inputText: {
        width: 400,
        height: 300,
        marginBottom: theme.spacing(2),
    },
    formControl: {
        marginBottom: theme.spacing(2),
        minWidth: 150,
    },
    quality: {
        width: '100%'
    }
});

class ObjectBrowserEditRole extends Component {
    constructor(props) {
        super(props);

        this.state = {
            role: null,
            initRole: null,
            roleInput: null
        };
    }

    componentDidMount() {
        this.props.socket.getObject(this.props.id)
            .then(obj => {
                this.object = obj;
                const value = obj?.common?.role || null;
                this.setState({ role: value, initRole: value, roleInput: value });
            })
            .catch(e => console.error(e));
    }

    onUpdate() {
        this.object.common = this.object.common || {};
        this.object.common.role = this.state.roleInput;
        this.props.socket.setObject(this.object._id, this.object)
            .then(() => this.props.onClose(this.object));
    }

    render() {
        return <Dialog
            key="objectBrowserEditRole"
            open={true}
            maxWidth="sm"
            fullWidth={true}
            onClose={() => this.props.onClose()}
            aria-labelledby="edit-role-dialog-title"
            aria-describedby="edit-role-dialog-description"
        >
            <DialogTitle id="edit-role-dialog-title">{this.object ? this.props.t('Update role for %s', this.object._id) : null}</DialogTitle>
            <DialogContent>
                <Autocomplete
                    freeSolo
                    options={this.props.roles}
                    style={{ width: '100%' }}
                    value={this.state.role}
                    onChange={(event, role) => this.setState({ role, roleInput: role })}
                    onInputChange={(event, role) => this.setState({ roleInput: role })}
                    renderInput={params => <TextField
                        {...params}
                        label={this.props.t('Role')}
                        variant="outlined"
                    />}
                />
            </DialogContent>
            <DialogActions>
                <Button disabled={this.state.initRole === this.state.roleInput} variant="contained" onClick={() => this.onUpdate()} color="primary" startIcon={<IconCheck />}>{this.props.t('Apply')}</Button>
                <Button variant="contained" onClick={() => this.props.onClose()} color="default" startIcon={<IconCancel />}>{this.props.t('Cancel')}</Button>
            </DialogActions>
        </Dialog>;
    }
}

ObjectBrowserEditRole.propTypes = {
    classes: PropTypes.object,
    roles: PropTypes.array,
    id: PropTypes.string,
    socket: PropTypes.object,
    onClose: PropTypes.func.isRequired,

    t: PropTypes.func,
};

export default withStyles(styles)(ObjectBrowserEditRole);


<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigLanguage.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

import I18n from '@iobroker/adapter-react/i18n';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    }
});

const LANGUAGES =[
    {
        value: 'en',
        label: 'English'
    },
    {
        value: 'de',
        label: 'Deutsch'
    },
    {
        value: 'ru',
        label: 'русский'
    },
    {
        value: 'pt',
        label: 'Portugues'
    },
    {
        value: 'nl',
        label: 'Nederlands'
    },
    {
        value: 'fr',
        label: 'français'
    },
    {
        value: 'it',
        label: 'Italiano'
    },
    {
        value: 'es',
        label: 'Espanol'
    },
    {
        value: 'pl',
        label: 'Polski'
    },
    {
        value: 'zh-ch',
        label: '简体中文'
    }
];

class ConfigLanguage extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
        const languages = [...LANGUAGES];
        if (this.props.schema.system) {
            languages.unshift({value: '', label: I18n.t('System language')});
        }

        this.setState({value: value || I18n.getLanguage(), selectOptions: languages});
    }

    renderItem(error, disabled, defaultValue) {
        if (!this.state.selectOptions) {
            return null;
        }

        const item = this.state.selectOptions?.find(item => item.value === this.state.value || (!item.value && !this.state.value));

        return <FormControl className={this.props.classes.fullWidth}>
            <InputLabel>{this.getText(this.props.schema.label)}</InputLabel>
            <Select
                error={!!error}
                disabled={!!disabled}
                value={this.state.value || '_'}
                renderValue={val => this.getText(item?.label, this.props.schema.noTranslation)}
                onChange={e => {
                    const value = e.target.value === '_' ? '' : e.target.value;
                    this.setState({value}, () =>
                        this.onChange(this.props.attr, value));
                }}
            >
                {this.state.selectOptions?.map(item =>
                    <MenuItem key={item.value} value={item.value}>{item.label}</MenuItem>)}
            </Select>
            {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
        </FormControl>;
    }
}

ConfigLanguage.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigLanguage);

<|start_filename|>src-admin/public/blockly.js<|end_filename|>
'use strict';

goog.provide('Blockly.JavaScript.Sendto');

goog.require('Blockly.JavaScript');

// --- ifttt --------------------------------------------------
Blockly.Words['ifttt_iot']           = {'en': 'Send text to IFTTT via iot',  'de': 'Sende Text zu IFTTT über iot',       'ru': 'Послать текст в IFTTT через iot'};
Blockly.Words['ifttt_event']         = {'en': 'event',                       'de': 'event',                              'ru': 'event'};
Blockly.Words['ifttt_value1']        = {'en': 'value1',                      'de': 'value1',                             'ru': 'value1'};
Blockly.Words['ifttt_value2']        = {'en': 'value2',                      'de': 'value2',                             'ru': 'value2'};
Blockly.Words['ifttt_value3']        = {'en': 'value3',                      'de': 'value3',                             'ru': 'value3'};
Blockly.Words['ifttt_tooltip']       = {'en': 'Send to IFTTT',               'de': 'Sende zu IFTTT',                     'ru': 'Послать в IFTTT'};
Blockly.Words['ifttt_help']          = {'en': 'https://github.com/ioBroker/ioBroker.cloud/blob/master/README.md', 'de': 'http://www.iobroker.net/?page_id=178&lang=de', 'ru': 'http://www.iobroker.net/?page_id=4262&lang=ru'};

Blockly.Words['ifttt_log']           = {'en': 'log level',                   'de': 'Loglevel',                           'ru': 'Протокол'};
Blockly.Words['ifttt_log_none']      = {'en': 'none',                        'de': 'keins',                              'ru': 'нет'};
Blockly.Words['ifttt_log_info']      = {'en': 'info',                        'de': 'info',                               'ru': 'инфо'};
Blockly.Words['ifttt_log_debug']     = {'en': 'debug',                       'de': 'debug',                              'ru': 'debug'};
Blockly.Words['ifttt_log_warn']      = {'en': 'warning',                     'de': 'warning',                            'ru': 'warning'};
Blockly.Words['ifttt_log_error']     = {'en': 'error',                       'de': 'error',                              'ru': 'ошибка'};


// this is copy of engines.js
// Blockly.Sendto is global variable and defined in javascript/admin/google-blockly/own/blocks_sendto.js

Blockly.Sendto.blocks['ifttt_iot'] =
    '<block type="ifttt_iot">'
    + '     <value name="INSTANCE">'
    + '     </value>'
    + '     <value name="EVENT">'
    + '         <shadow type="text">'
    + '             <field name="TEXT">state</field>'
    + '         </shadow>'
    + '     </value>'
    + '     <value name="VALUE1">'
    + '         <shadow type="text">'
    + '             <field name="TEXT">value1</field>'
    + '         </shadow>'
    + '     </value>'
    + '     <value name="VALUE2">'
    + '         <shadow type="text">'
    + '             <field name="TEXT">value2</field>'
    + '         </shadow>'
    + '     </value>'
    + '     <value name="VALUE3">'
    + '         <shadow type="text">'
    + '             <field name="TEXT">value3</field>'
    + '         </shadow>'
    + '     </value>'
    + '     <value name="LOG">'
    + '     </value>'
    + '</block>';

Blockly.Blocks['ifttt_iot'] = {
    init: function() {
        var options = [];
        if (typeof main !== 'undefined' && main.instances) {
            for (var i = 0; i < main.instances.length; i++) {
                var m = main.instances[i].match(/^system.adapter.iot.(\d+)$/);
                if (m) {
                    var n = parseInt(m[1], 10);
                    options.push(['iot.' + n, '.' + n]);
                }
            }
            if (!options.length) {
                for (var k = 0; k <= 4; k++) {
                    options.push(['iot.' + k, '.' + k]);
                }
            }
        } else {
            for (var u = 0; u <= 4; u++) {
                options.push(['iot.' + u, '.' + u]);
            }
        }

        this.appendDummyInput('INSTANCE')
            .appendField(Blockly.Words['ifttt_iot'][systemLang])
            .appendField(new Blockly.FieldDropdown(options), 'INSTANCE');

        this.appendValueInput('EVENT')
            .appendField(Blockly.Words['ifttt_event'][systemLang]);

        var input = this.appendValueInput('VALUE1')
            .appendField(Blockly.Words['ifttt_value1'][systemLang]);
        if (input.connection) input.connection._optional = true;

        input = this.appendValueInput('VALUE2')
            .appendField(Blockly.Words['ifttt_value2'][systemLang]);
        if (input.connection) input.connection._optional = true;

        input = this.appendValueInput('VALUE3')
            .appendField(Blockly.Words['ifttt_value3'][systemLang]);
        if (input.connection) input.connection._optional = true;

        this.appendDummyInput('LOG')
            .appendField(Blockly.Words['ifttt_log'][systemLang])
            .appendField(new Blockly.FieldDropdown([
                [Blockly.Words['ifttt_log_none'][systemLang],  ''],
                [Blockly.Words['ifttt_log_info'][systemLang],  'log'],
                [Blockly.Words['ifttt_log_debug'][systemLang], 'debug'],
                [Blockly.Words['ifttt_log_warn'][systemLang],  'warn'],
                [Blockly.Words['ifttt_log_error'][systemLang], 'error']
            ]), 'LOG');

        this.setInputsInline(false);
        this.setPreviousStatement(true, null);
        this.setNextStatement(true, null);

        this.setColour(Blockly.Sendto.HUE);
        this.setTooltip(Blockly.Words['ifttt_tooltip'][systemLang]);
        this.setHelpUrl(Blockly.Words['ifttt_help'][systemLang]);
    }
};

Blockly.JavaScript['ifttt_iot'] = function(block) {
    var dropdown_instance = block.getFieldValue('INSTANCE');
    var event = Blockly.JavaScript.valueToCode(block, 'EVENT', Blockly.JavaScript.ORDER_ATOMIC);
    var logLevel = block.getFieldValue('LOG');
    var value1  = Blockly.JavaScript.valueToCode(block, 'VALUE1', Blockly.JavaScript.ORDER_ATOMIC);
    var value2  = Blockly.JavaScript.valueToCode(block, 'VALUE2', Blockly.JavaScript.ORDER_ATOMIC);
    var value3  = Blockly.JavaScript.valueToCode(block, 'VALUE3', Blockly.JavaScript.ORDER_ATOMIC);

    var logText;
    if (logLevel) {
        logText = 'console.' + logLevel + '("ifttt_iot: " + ' + event + ');\n'
    } else {
        logText = '';
    }

    return 'sendTo("iot' + dropdown_instance + '", "ifttt", {event: ' + event  + ', value1: ' + value1 + ', value2: ' + value2 + ', value3: ' + value3 + '});\n' +
        logText;
};


<|start_filename|>src/materialize-css/js/parallax.js<|end_filename|>
(function($) {
  'use strict';

  let _defaults = {
    responsiveThreshold: 0, // breakpoint for swipeable
  };

  class Parallax extends Component {

    constructor(el, options) {
      super(Parallax, el, options);

      this.el.M_Parallax = this;

      /**
       * Options for the Parallax
       * @member Parallax#options
       * @prop {Number} responsiveThreshold
       */
      this.options = $.extend({}, Parallax.defaults, options);
      this._enabled = window.innerWidth > this.options.responsiveThreshold;

      this.$img = this.$el.find('img').first();
      this.$img.each(function() {
        let el = this;
        if (el.complete) $(el).trigger("load");
      });

      this._updateParallax();
      this._setupEventHandlers();
      this._setupStyles();

      Parallax._parallaxes.push(this);
    }

    static get defaults() {
      return _defaults;
    }

    static init(els, options) {
      return super.init(this, els, options);
    }

    /**
     * Get Instance
     */
    static getInstance(el) {
      let domElem = !!el.jquery ? el[0] : el;
      return domElem.M_Parallax;
    }

    /**
     * Teardown component
     */
    destroy() {
      Parallax._parallaxes.splice(Parallax._parallaxes.indexOf(this), 1);
      this.$img[0].style.transform = '';
      this._removeEventHandlers();

      this.$el[0].M_Parallax = undefined;
    }

    static _handleScroll() {
      for (let i = 0; i < Parallax._parallaxes.length; i++) {
        let parallaxInstance = Parallax._parallaxes[i];
        parallaxInstance._updateParallax.call(parallaxInstance);
      }
    }

    static _handleWindowResize() {
      for (let i = 0; i < Parallax._parallaxes.length; i++) {
        let parallaxInstance = Parallax._parallaxes[i];
        parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold;
      }
    }

    _setupEventHandlers() {
      this._handleImageLoadBound = this._handleImageLoad.bind(this);
      this.$img[0].addEventListener('load', this._handleImageLoadBound);

      if (Parallax._parallaxes.length === 0) {
        Parallax._handleScrollThrottled = M.throttle(Parallax._handleScroll, 5);
        window.addEventListener('scroll', Parallax._handleScrollThrottled);

        Parallax._handleWindowResizeThrottled = M.throttle(Parallax._handleWindowResize, 5);
        window.addEventListener('resize', Parallax._handleWindowResizeThrottled);
      }
    }

    _removeEventHandlers() {
      this.$img[0].removeEventListener('load', this._handleImageLoadBound);

      if (Parallax._parallaxes.length === 0) {
        window.removeEventListener('scroll', Parallax._handleScrollThrottled);
        window.removeEventListener('resize', Parallax._handleWindowResizeThrottled);
      }
    }

    _setupStyles() {
      this.$img[0].style.opacity = 1;
    }

    _handleImageLoad() {
      this._updateParallax();
    }

    _updateParallax() {
      let containerHeight = this.$el.height() > 0 ? this.el.parentNode.offsetHeight : 500;
      let imgHeight = this.$img[0].offsetHeight;
      let parallaxDist = imgHeight - containerHeight;
      let bottom = this.$el.offset().top + containerHeight;
      let top = this.$el.offset().top;
      let scrollTop = M.getDocumentScrollTop();
      let windowHeight = window.innerHeight;
      let windowBottom = scrollTop + windowHeight;
      let percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
      let parallax = parallaxDist * percentScrolled;

      if (!this._enabled) {
        this.$img[0].style.transform = '';

      } else if (bottom > scrollTop && top < scrollTop + windowHeight) {
        this.$img[0].style.transform = `translate3D(-50%, ${parallax}px, 0)`;
      }
    }
  }

  /**
   * @static
   * @memberof Parallax
   */
  Parallax._parallaxes = [];

  M.Parallax = Parallax;

  if (M.jQueryLoaded) {
    M.initializeJqueryWrapper(Parallax, 'parallax', 'M_Parallax');
  }

})(cash);


<|start_filename|>src-rx/src/tabs/Events.js<|end_filename|>
// Do not implement this tab!
// It could be to many events and users do not understand that

<|start_filename|>src-rx/src/components/DragWrapper.js<|end_filename|>
import React, { useEffect, useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { findCard, moveCard } from '../helpers/cardSort';
const style = {
    cursor: 'move',
};
const DragWrapper = ({ canDrag, setEndDrag, iconJSX, selected, compact, badgeContent, badgeColor, tab, tabs, setTabs, _id, children, name }) => {
    const ref = useRef(null);
    const [{ handlerId }, drop] = useDrop({
        accept: 'box',
        collect(monitor) {
            return {
                handlerId: monitor.getHandlerId(),
            };
        },
        // canDrop: () => false,
        hover({ _id: draggedId }, monitor) {
            if (!ref.current) {
                return;
            }
            const { index: overIndexActions } = findCard(_id, tabs);
            const hoverBoundingRect = ref.current?.getBoundingClientRect();
            const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
            const clientOffset = monitor.getClientOffset();
            const hoverClientY = clientOffset.y - hoverBoundingRect.top;
            moveCard(draggedId,
                overIndexActions,
                tabs,
                setTabs,
                hoverClientY,
                hoverMiddleY
            );
        }
    });

    const [{ isDragging }, drag, preview] = useDrag({
        type: 'box',
        item: { _id: tab.name, ...tab, iconJSX, selected, compact, badgeContent, badgeColor },
        canDrag: () => canDrag,
        end: () => setEndDrag(),
        collect: monitor => ({ isDragging: monitor.isDragging() }),
    });

    useEffect(() => {
        preview(getEmptyImage(), { captureDraggingState: true });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    const opacity = isDragging ? 0 : 1;
    drag(drop(ref));

    return <div ref={ref} style={{ ...style, opacity }} data-handler-id={handlerId}>
        <a type="box" data-handler-id={handlerId} onClick={event => event.preventDefault()} href={`/#${name}`} style={{ ...style, opacity, color: 'inherit', textDecoration: 'none' }} >
            {children}
        </a>
    </div>;
}

export default DragWrapper;

<|start_filename|>src-admin/src/i18n/fr.json<|end_filename|>
{
  "Access to instances": "Accès aux instances",
  "Admin adapter settings": "Configuration de l'adaptateur d'administration",
  "Allow access only to specific instances": "Autoriser l'accès uniquement à des instances spécifiques",
  "Apply access rights for selected instances": "Appliquer les droits d'accès pour les instances sélectionnées",
  "Authentication was deactivated": "L'authentification a été désactivée",
  "Authentication": "Authentification",
  "Auto update": "Vérifier les mises à jour",
  "Background": "Contexte",
  "Background color of the login screen": "Couleur d'arrière-plan de l'écran de connexion",
  "Background image": "Image de fond",
  "Cache": "Cache",
  "Chained certificate": "Certificat chaîné",
  "Disable authentication": "Désactiver l'authentification",
  "Enabled": "Activée",
  "Events threshold value": "Valeur seuil pour les événements",
  "Hide logo": "Masquer le logo",
  "IP": "IP",
  "Ignore warning": "Ignorer l'avertissement",
  "Let's Encrypt settings": "Paramètres Let's Encrypt",
  "Listen on all IPs": "Écouter sur toutes les adresses IP",
  "Login timeout(sec)": "Délai d'attente de connexion (s)",
  "Own motto": "Propre devise",
  "Port to check the domain": "Port pour vérifier le domaine",
  "Port": "Port",
  "Private certificate": "Certificat privé",
  "Public certificate": "Certificat public",
  "Run as": "Executer en tant que",
  "Secure(HTTPS)": "Connexion sécurisée (HTTPS)",
  "Set certificates or load it first in the system settings (right top).": "Définissez des certificats ou chargez-les d'abord dans les paramètres du système (en haut à droite).",
  "Unsecure_Auth": "Le mot de passe sera envoyé via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS)!",
  "Use Lets Encrypt certificates": "Utiliser les certificats Let's Encrypt",
  "Use this instance for automatic update": "Utiliser cette instance pour la mise à jour automatique",
  "Warning!": "Attention!",
  "every 12 hours": "toutes les 12 heures",
  "every 2 days": "tous les 2 jours",
  "every 2 weeks": "toutes les 2 semaines",
  "every 3 days": "tous les 3 jours",
  "every day": "tous les jours",
  "every week": "toutes les semaines",
  "explain_access_instance": "Il existe une option pour autoriser l'accès aux pages de configuration spécifiques des instances sélectionnées.",
  "manually": "manuellement",
  "monthly": "mensuel",
  "place here": "Placez les fichiers ici",
  "tooltip_auth": "Utiliser un identifiant et un mot de passe pour accéder à la page d'administration. Vous pouvez les gérer sur l'onglet des utilisateurs.",
  "tooltip_autoUpdate": "Vérifier periodiquement les mises à jour",
  "tooltip_bind": "Adresse IP, où l'administrateur s'exécutera",
  "tooltip_cache": "Activer la cache du navigateur",
  "tooltip_certChained": "Certificat chaîné. Certificat racine + Certificat public. Veuillez le gérer dans les configs du système.",
  "tooltip_certPrivate": "Certificat privé Veuillez le gérer dans les configs du système.",
  "tooltip_certPublic": "Certificat public. Veuillez le gérer dans les configs du système.",
  "tooltip_defaultUser": "Connexion automatique avec cet utilisateur",
  "tooltip_leEnabled": "Utiliser les certificats Let's Encrypt",
  "tooltip_lePort": "Port pour le serveur de challenge. Let's Encrypt ne supporte que le port 80, mais avec certaines configurations du routeur, vous pouvez utiliser n'importe quel port",
  "tooltip_leUpdate": "Cette instance mettra à jour les certificats Let's Encrypt tous les 3 mois",
  "tooltip_port": "Port pour l'interface d'administration. Il doit être disponible. Les utilisateurs non-adminstratifs ne peuvent pas utiliser les ports inférieurs à 1000.",
  "tooltip_secure": "Activer le protocole SSL. L'administration sera disponible uniquement via https:// et non http://",
  "tooltip_thresholdValue": "Si le navigateur reçoit trop d'événements en 3 secondes, la mise à jour sera arrêtée",
  "tooltip_ttl": "Passé ce délai, l'utilisateur sera automatiquement déconnecté.",
  "users permissions": "Autorisations des utilisateurs"
}


<|start_filename|>src-rx/src/components/Instances/InstanceInfo.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import {
    Grid,
    Tooltip,
    Typography
} from '@material-ui/core';
const styles = theme => ({
    nowrap:{
        flexFlow: 'nowrap',
        overflow: 'hidden'
    },
    width:{
        width:'100%',
        overflow: 'hidden'

    }
})
const InstanceInfo = props => {
    return <Grid
        item
        container
        title={props.icon ? '' : props.tooltip || ''}
        alignItems="center"
        direction="row"
        spacing={ 1 }
        className={props.classes.nowrap}
    >
        <Grid item>
            { props.icon &&
                <Tooltip title={ props.tooltip || '' }>
                    { props.icon }
                </Tooltip>
            }
        </Grid>
        <Grid 
        className={props.classes.width}
         item>
            <Tooltip title={ props.tooltip || '' }>
                <Typography component="div">
                    { props.children }
                </Typography>
            </Tooltip>
        </Grid>
    </Grid>;
}

InstanceInfo.propTypes = {
    children: PropTypes.node,
    icon: PropTypes.node,
    tooltip: PropTypes.string
};

export default withStyles(styles)(InstanceInfo);

<|start_filename|>src-admin/src/Tabs/EasyAdmin.js<|end_filename|>
import React, {Component} from 'react';
import {withStyles} from '@material-ui/core/styles';
import PropTypes from 'prop-types';



const styles = theme => ({
    tab: {
        width: '100%',
        minHeight: '100%'
    },
});

class EasyAdmin extends Component {
    constructor(props) {
        super(props);

        this.state = {

        };
    }

    render() {
        return <form className={this.props.classes.tab}>
            <div>{I18n.t('explain_access_instance')}</div>
            <div></div>
        </form>;
    }
}

EasyAdmin.propTypes = {
    common: PropTypes.object.isRequired,
    native: PropTypes.object.isRequired,
    instance: PropTypes.number.isRequired,
    adapterName: PropTypes.string.isRequired,
    onError: PropTypes.func,
    onLoad: PropTypes.func,
    onChange: PropTypes.func,
    socket: PropTypes.object.isRequired,
    onShowError: PropTypes.func,
};

export default withStyles(styles)(EasyAdmin);


<|start_filename|>src-rx/src/dialogs/ObjectEditDialog.js<|end_filename|>
import { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import withWidth from "@material-ui/core/withWidth";
import PropTypes from 'prop-types';

import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import MonacoEditor from 'react-monaco-editor';

// Icons
import CloseIcon from '@material-ui/icons/Close';
import CheckIcon from "@material-ui/icons/Check";

const styles = theme => ({
    dialog: {
        height: '100%',
        maxHeight: '100%',
        maxWidth: '100%',
    },
    content: {
        textAlign: 'center',
    },
    tabPanel: {
        width: '100%',
        overflow: 'hidden',
        height: 'calc(100% - ' + theme.mixins.toolbar.minHeight + 'px)',
    }
});

export const EXTENSIONS = {
    images: ['png', 'jpg', 'svg', 'jpeg'],
    code: ['js', 'json'],
    txt: ['log', 'txt', 'html', 'css', 'xml'],
};

class ObjectEditDialog extends Component {
    constructor(props) {
        super(props);

        this.state = {
            code: JSON.stringify(this.props.obj._id, null, 2),
            changed: false,
        };
    }
    editorDidMount(editor, monaco) {
        console.log('editorDidMount', editor);
        editor.focus();
    }
    onChange(newValue, e) {
        console.log('onChange', newValue, e);
    }

    onSave() {
        this.props.onClose();
    }

    render() {
        return <Dialog
            className={this.props.classes.dialog}
            open={true}
            onClose={() => this.props.onClose()}
            fullWidth={true}
            fullScreen={true}
            aria-labelledby="object-edit-dialog-title"
        >
            <DialogTitle id="object-edit-dialog-title">{
                this.props.t('Edit object: %s', this.props.obj._id)
            }</DialogTitle>
            <DialogContent className={this.props.classes.content}>
                <MonacoEditor
                    width="100%"
                    height="100%"
                    language="json"
                    theme={this.props.themeName === 'dark' ? 'vs-dark' : 'vs-light'}
                    value={this.state.code}
                    options={{
                        selectOnLineNumbers: true
                    }}
                    onChange={(newValue, e) => this.onChange(newValue, e)}
                    editorDidMount={(editor, monaco) => this.editorDidMount(editor, monaco)}
                />
            </DialogContent>
            <DialogActions>
                <Button
                    variant="contained"
                    color="primary"
                    disabled={!this.state.changed}
                    onClick={() => this.onSave()}
                    startIcon={<CheckIcon />}
                >
                    {this.props.t('Save')}
                </Button>
                <Button
                    variant="contained"
                    onClick={() => this.props.onClose()}
                    startIcon={<CloseIcon />}
                >
                    {this.props.t('Close')}
                </Button>
            </DialogActions>
        </Dialog>;
    }
}

ObjectEditDialog.propTypes = {
    t: PropTypes.func,
    lang: PropTypes.string,
    expertMode: PropTypes.bool,
    socket: PropTypes.object,
    themeName: PropTypes.string,
    obj: PropTypes.object,
    onClose: PropTypes.func.isRequired,
};

export default withWidth()(withStyles(styles)(ObjectEditDialog));


<|start_filename|>src/lib/css/cron/jquery.cron.css<|end_filename|>
#cron-input {
    margin-bottom: 10px;
}
.cron-preview-every {
    padding-bottom: 10px;
}
.cron-tab-page {
    padding-top: 0em !important;
    padding-left: 0em !important;
    padding-right: 0em !important;
    padding-bottom: 0em !important;
}
.cron-tabs {
    background: white;
}

<|start_filename|>src/js/adminIntro.js<|end_filename|>
function Intro(main) {
    'use strict';

    var that = this;
    var isCloud = document.location.hostname === 'iobroker.pro' || document.location.hostname === 'iobroker.net';


    this.$tab      = $('#tab-intro');
    this.$tiles    = this.$tab.find('.tab-intro-cards');
    this.main      = main;
    this.inited    = false;
    this.$template = $('#intro-template');

    function readInstances(callback) {
        that.main.socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, doc) {
            if (err) {
                if (callback) callback (err, []);
            } else {
                if (doc.rows.length === 0) {
                    if (callback) callback (err, []);
                } else {
                    that.main.instances = [];
                    for (var i = 0; i < doc.rows.length; i++) {
                        that.main.instances.push(doc.rows[i].id);
                        that.main.objects[doc.rows[i].id] = doc.rows[i].value;
                    }
                    if (callback) callback(err, that.main.instances);
                }
            }
        });
    }

    /**
     * Format number in seconds to time text
     * @param {!number} seconds
     * @returns {String}
     */
    function formatSeconds(seconds) {
        var days = Math.floor(seconds / (3600 * 24));
        seconds %= 3600 * 24;
        var hours = Math.floor(seconds / 3600);
        if (hours < 10) {
            hours = '0' + hours;
        }
        seconds %= 3600;
        var minutes = Math.floor(seconds / 60);
        if (minutes < 10) {
            minutes = '0' + minutes;
        }
        seconds %= 60;
        seconds = Math.floor(seconds);
        if (seconds < 10) {
            seconds = '0' + seconds;
        }
        var text = '';
        if (days) {
            text += days + ' ' + _('daysShortText') + ' ';
        }
        text += hours + ':' + minutes + ':' + seconds;

        return text;
    }

    /**
     * Format bytes to MB or GB
     * @param {!number} bytes
     * @returns {String}
     */
    function formatRam(bytes) {
        var GB = Math.floor(bytes / (1024 * 1024 * 1024) * 10) / 10;
        bytes %= (1024 * 1024 * 1024);
        var MB = Math.floor(bytes / (1024 * 1024) * 10) / 10;
        var text = '';
        if (GB > 1) {
            text += GB + ' GB ';
        } else {
            text += MB + ' MB ';
        }

        return text;
    }

    function formatSpeed(mhz) {
        return mhz + ' MHz';
    }

    /**
     * FormatObject for host informations
     * @type type
     */
    var formatInfo = {
        'Uptime':        formatSeconds,
        'System uptime': formatSeconds,
        'RAM':           formatRam,
        'Speed':         formatSpeed,
        'Disk size':     that.main.formatBytes,
        'Disk free':     that.main.formatBytes
    };

    function copyToClipboard(e) {
        var $input = $('<textarea>');
        $(this).append($input);
        $input.val($(this).data('clippy'));
        $input.trigger('select');
        document.execCommand('copy');
        $input.remove();
        e.preventDefault();
        e.stopPropagation();
        that.main.showToast(that.$tiles, _('copied'))
    }

    function buildInfoCard(host) {
        var $card = that.$template.clone();
        $card.removeAttr('id');
        $card.addClass('card-system-info');
        $card.find('.btn-small').addClass('disabled');
        $card.find('.card-titles').text(host.name);
        $card.find('.btn-card-enabled').data('host', host.id);

        // button enabled
        if (that.main.systemConfig.common.intro[host.id] === false) {
            if (that.$tab.hasClass('edit-active')) {
                $card.addClass('card-disabled').find('.btn-card-enabled').removeClass('blue').addClass('gray').find('i').text('close');
            } else {
                return null;
            }
        }
		var icon;
        if (that.main.objects[host.id] && that.main.objects[host.id].common) {
            icon = that.main.objects[host.id].common.icon;
        }
        $card.find('.card-image-img').attr('src', icon || 'img/no-image.png');
        $card.find('.card-content-text').html('<div class="preloader-wrapper small active">\n' +
            '    <div class="spinner-layer spinner-green-only">\n' +
            '      <div class="circle-clipper left">\n' +
            '        <div class="circle"></div>\n' +
            '      </div><div class="gap-patch">\n' +
            '        <div class="circle"></div>\n' +
            '      </div><div class="circle-clipper right">\n' +
            '        <div class="circle"></div>\n' +
            '      </div>\n' +
            '    </div>\n' +
            '  </div>');

        var timeout = setTimeout(function () {
            if (timeout) {
                timeout = null;
                $card.find('.btn-small').addClass('disabled');
                $card.find('.card-content-text').html(_('offline'));
            }
        }, 6000);

        formatInfo['Disk size'] = formatInfo['Disk size'] || that.main.formatBytes;
        formatInfo['Disk free'] = formatInfo['Disk size'] || that.main.formatBytes;

        that.main.socket.emit('sendToHost', host.id, 'getHostInfo', null, function (data) {
            clearTimeout(timeout);
            timeout = null;
            if (data === 'permissionError') {
                console.error('May not read "getHostInfo"');
            } else if (!data) {
                console.error('Cannot read "getHostInfo"');
            } else {
                $card.find('.btn-small').removeClass('disabled');
            }

            var diskWarning = that.main.states['system.host.' + that.main.currentHost + '.diskWarning'];
            if (diskWarning) {
                diskWarning = parseFloat(diskWarning.val);
            } else {
                diskWarning = 5;
            }
            var hasWarning = data['Disk size'] > 0 && data['Disk free'] && Math.round((data['Disk free'] / data['Disk size']) * 100) < diskWarning;

            var text = '<div class="card-content-text">';
            if (data) {
                text += '<ul>';
                for (var item in data) {
                    if (data.hasOwnProperty(item) && (item === 'Platform' || item === 'NPM' || item === 'RAM' || item === 'Node.js')) {
                        text += '<li><b>' + _(item) + ': </b>';
                        text += '<span class="system-info" data-attribute="' + item + '">' + (formatInfo[item] ? formatInfo[item](data[item]) : data[item] || ' --') + '</span></li>';
                    }
                }
                if (hasWarning) {
                    text += '<li><b>' + _('Disk free') + ': </b>';
                    text += '<span class="system-info system-warning" data-attribute="Disk free">' + (formatInfo['Disk free'] ? formatInfo['Disk free'](data['Disk free']) : data['Disk free'] || ' --') + '</span></li>';
                    text += '<li><b>' + _('Disk size') + ': </b>';
                    text += '<span class="system-info" data-attribute="Disk size">' + (formatInfo['Disk size'] ? formatInfo['Disk size'](data['Disk size']) : data['Disk size'] || ' --') + '</span></li>';
                }

                text += '</ul>';
            }
            text += '</div>';
            $card.find('.card-content-text').replaceWith($(text));

            text = '<div class="card-reveal"><h5>' + _('Info') + '</h5><a class="btn-info" title="' + _('Copy to clipboard') + '"><i class="material-icons">content_copy</i></a><span class="card-title grey-text text-darken-4"><i class="material-icons right">close</i></span>';
            var clippy = [];

            if (data) {
                text += '<ul>';
                for (var item_ in data) {
                    if (data.hasOwnProperty(item_)) {
                        text += '<li><b>' + _(item_) + ': </b>';
                        var formatted = (formatInfo[item_] ? formatInfo[item_](data[item_]) : data[item_]);
                        clippy.push(item_ + ': ' + formatted);
                        if (item_ === 'Disk free' && hasWarning) {
                            text += '<span class="system-info system-warning" data-attribute="' + item_ + '">' + formatted + '</span></li>';
                        } else {
                            text += '<span class="system-info" data-attribute="' + item_ + '">' + formatted + '</span></li>';
                        }
                    }
                }
                text += '</ul>';
            }
            text += '</div>';
            $card.find('.card-reveal').replaceWith($(text));
            if (that.$tab.hasClass('edit-active')) {
                $card.find('.btn-small').hide();
            } else {
                $card.find('.btn-info').data('clippy', clippy.join('\r\n')).on('click', copyToClipboard);
            }
        });
        return $card;
    }

    function buildOneCard(adapter, instance, welcomeScreen, common, url, web, enabled) {
        var $card = that.$template.clone();
        if (typeof url === 'object') {
            url = url.link;
        }

        $card.removeAttr('id');
        var urlText = url.replace(/^https?:\/\//, '');
        var pos = urlText.indexOf('/');
        if (pos !== -1) {
            urlText = urlText.substring(0, pos);
        }
        if (adapter === 'admin' && urlText === location.host) return null;
        if (adapter === 'web') return null;
        if (adapter !== 'vis-web-admin' && adapter.match(/^vis-/)) return null; // no widgets
        if (adapter.match(/^icons-/)) return null; // no icons
        if (common && common.noIntro) return null;

        $card.find('.btn-card-enabled').data('instance', adapter + '.' + instance + (welcomeScreen && welcomeScreen.name ? '.' + welcomeScreen.name : '')).data('web', web);

        // button enabled
        if (!enabled) {
            $card.addClass('card-disabled').find('.btn-card-enabled').removeClass('blue').addClass('gray').find('i').text('close');
        } else {
            $card.on('click', function () {
                var editActive = that.$tab.hasClass('edit-active');
                if (editActive) return;
                window.open($(this).find('.url').attr('href'));
            })
        }

        url = typeof url === 'object' ? url._first : url || '';
        if (isCloud) {
            url = url.replace(/^https?:\/\/iobroker\.\w\w\w(:\d+)?/, '');
        }
        // link
        $card.find('.url').attr('href', url).text(urlText + (web ? ' (' + web + ')' : ''));
        // icon
        $card.find('.card-image-img').attr('src', common.icon ? 'adapter/' + adapter + '/' + common.icon : 'img/no-image.png');
        if (welcomeScreen && welcomeScreen.color) {
            $card.find('.card-image').css('background', welcomeScreen.color);
        }

        // title
        var title = (welcomeScreen && welcomeScreen.name) || common.titleLang || common.title;
        if (typeof title === 'object') {
            title = title[systemLang] || title.en;
        }
        $card.find('.card-titles').text(title || adapter);

        var desc = common.desc;
        if (typeof desc === 'object') {
            desc = desc[systemLang] || desc.en;
        }
        $card.find('.card-content-text').text(desc || '');
        return $card;
    }

    function getCards(instances, callback) {
        var list = JSON.parse(JSON.stringify(instances));
        var a;
        var $cards = [];
        var enabled;
        var $card;
        list.sort(function (a, b) {
            a = that.main.objects[a] && that.main.objects[a].common;
            b = that.main.objects[b] && that.main.objects[b].common;
            a = a || {};
            b = b || {};
            if (a.order === undefined && b.order === undefined) {
                if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
                if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
                return 0;
            } else if (a.order === undefined) {
                return -1;
            } else if (b.order === undefined) {
                return 1;
            } else {
                if (a.order > b.order) return 1;
                if (a.order < b.order) return -1;
                if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
                if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
                return 0;
            }
        });

        var editActive = that.$tab.hasClass('edit-active');

        var urls = [];

        for (a = 0; a < list.length; a++) {
            var obj = that.main.objects[list[a]];
            var common = obj && obj.common;

            if (common && (common.enabled || common.onlyWWW) && (common.localLinks || common.localLink)) {
                var link = common.localLinks || common.localLink || '';
                var adapter = list[a].substring('system.adapter.'.length).split('.');
                var instance = adapter[1];
                adapter = adapter[0];
                var url = link ? that.main.tabs.instances.replaceInLink(link, adapter, instance) : '';
                $card = null;
                var intro = that.main.systemConfig.common.intro[adapter + '.' + instance];
                if (typeof url === 'object') {
                    var first = true;
                    for (var inst in url) {
                        if (url.hasOwnProperty(inst)) {
                            enabled = true;
                            if (intro !== undefined) {
                                if (first && typeof intro !== 'object') {
                                    var val = intro;
                                    intro = {};
                                    that.main.systemConfig.common.intro[adapter + '.' + instance] = intro;
                                    intro[inst] = val;
                                }
                                if (intro[inst] === false) {
                                    enabled = false;
                                }
                            }
                            first = false;

                            if (!editActive && !enabled) continue;
                            if (urls.indexOf(url[inst]) === -1) {
                                $card = buildOneCard(adapter, instance, null, common, url[inst], inst, enabled);
                                $card && $cards.push($card);
                                urls.push(url[inst]);
                            }
                        }
                    }
                } else {
                    enabled = true;
                    if (!editActive && intro !== undefined) {
                        if (typeof intro === 'object') {
                            for (var aa in intro) {
                                if (intro.hasOwnProperty(aa)) {
                                    intro = intro[aa];
                                    that.main.systemConfig.common.intro[adapter + '.' + instance] = intro;
                                    break;
                                }
                            }
                        }
                        if (intro === false) {
                            enabled = false;
                        }
                    }
                    var welcomeScreen = common.welcomeScreen; //common.welcomeScreenPro || common.welcomeScreen;
                    while (welcomeScreen) {
                        intro = that.main.systemConfig.common.intro[adapter + '.' + instance + '.' + welcomeScreen.name];
                        if (!editActive && intro !== undefined) {
                            if (typeof intro === 'object') {
                                for (var aaa in intro) {
                                    if (intro.hasOwnProperty(aaa)) {
                                        intro = intro[aaa];
                                        that.main.systemConfig.common.intro[adapter + '.' + instance + '.' + welcomeScreen.name] = intro;
                                        break;
                                    }
                                }
                            }
                            if (intro === false) {
                                enabled = false;
                            }
                        }
                        that.main.systemConfig.common.intro[adapter + '.' + instance + '.' + welcomeScreen.name] = intro;
                        if (editActive || enabled) {
                            var welcomeUrl = url;
                            var m = welcomeUrl.match(/https?:\/\/[.\w\d-]+:\d+\/(.*)$/);
                            if (m) {
                                welcomeUrl = welcomeUrl.replace(m[1], welcomeScreen.link);
                            } else {
                                welcomeUrl += welcomeScreen.link;
                            }
                            if (welcomeUrl !== url && urls.indexOf(welcomeUrl) === -1) {
                                $card = buildOneCard(adapter, instance, welcomeScreen, common, welcomeUrl, null, enabled, welcomeScreen);
                                $card && $cards.push($card);
                                urls.push(welcomeUrl);
                            }
                        }

                        if (welcomeScreen === common.welcomeScreenPro) {
                            welcomeScreen = common.welcomeScreen;
                        } else {
                            welcomeScreen = null;
                        }
                    }

                    if (!editActive && !enabled) continue;

                    if (urls.indexOf(url) === -1) {
                        $card = buildOneCard(adapter, instance, null, common, url, null, enabled);
                        $card && $cards.push($card);
                        urls.push(url);
                    }
                }
            }
        }

        for (var i = 0; i < that.main.tabs.hosts.list.length; i++) {
            $card = buildInfoCard(that.main.tabs.hosts.list[i]);
            $card && $cards.push($card);
        }

        callback(null, $cards);
    }

    function updateConfig(callback) {
        var values = [];
        that.$tiles.find('.btn-card-enabled').each(function () {
            var inst = $(this).data('instance');
            if (inst) {
                values.push({
                    id: inst,
                    enabled: !$(this).hasClass('gray'),
                    web: $(this).data('web')
                });
            } else {
                values.push({
                    id: $(this).data('host'),
                    enabled: !$(this).hasClass('gray')
                });
            }
        });

        // update all values
        var intro = that.main.systemConfig.common.intro;
        var changed = true;
        for (var i = 0; i < values.length; i++) {
            var actual;
            var id = values[i].id;
            if (values[i].web) {
                if (intro[id] !== undefined && typeof intro[id] !== 'object') {
                    var val = intro[id];
                    intro[id] = {};
                    intro[id][values[i].web] = val;
                }
                actual = intro[id] && intro[id][values[i].web] !== undefined ? intro[id][values[i].web] : true;
            } else {
                if (intro[id] !== undefined && typeof intro[id] === 'object') {
                    for (var aa in intro[id]) {
                        if (intro[id].hasOwnProperty(aa)) {
                            intro[id] = intro[id][aa];
                            break;
                        }
                    }
                }
                actual = intro[id] !== undefined ? intro[id] : true;
            }
            if (values[i].enabled !== actual) {
                changed = true;
                if (values[i].web) {
                    intro[id] = intro[id] || {};
                    intro[id][values[i].web] = values[i].enabled;
                } else {
                    intro[id] = values[i].enabled;
                }
            }
        }

        if (changed) {
            that.main.socket.emit('getObject', 'system.config', function (err, obj) {
                if (obj) {
                    obj.common.intro = that.main.systemConfig.common.intro;
                    that.main.socket.emit('setObject', obj._id, obj, function (err) {
                        callback && callback();
                    });
                } else {
                    callback && callback();
                }
            });
        } else {
            callback && callback();
        }
    }

    this.prepare = function () {
        this.$tab.find('.btn-edit').off('click').on('click', function () {
            that.$tab.addClass('edit-active');
            showTiles();
        });
        this.$tab.find('.btn-edit-ok').off('click').on('click', function () {
            updateConfig(function () {
                that.$tab.removeClass('edit-active');
                showTiles();
                that.main.showToast(that.$tiles, _('Updated'));
            });
        });
        this.$tab.find('.btn-edit-cancel').off('click').on('click', function () {
            that.$tab.removeClass('edit-active');
            showTiles();
        });
    };

    function showTiles(instances, callback) {
        instances = instances || that.main.instances;

        getCards(instances, function (err, $cards) {
            that.$tiles.html('');
            for (var c = 0; c < $cards.length; c++) {
                that.$tiles.append($cards[c]);
            }

            if (that.$tab.hasClass('edit-active')) {
                that.$tiles.find('.btn-card-enabled').on('click', function () {
                    var enabled = !$(this).hasClass('gray');
                    if (enabled) {
                        $(this).removeClass('blue').addClass('gray').find('i').text('close');
                        $(this).parent().addClass('card-disabled');
                    } else {
                        $(this).addClass('blue').removeClass('gray').find('i').text('check');
                        $(this).parent().removeClass('card-disabled')
                    }
                });
            }
            callback && callback();
        });
    }
    // ----------------------------- Site cards show and Edit ------------------------------------------------
    this.init = function (update) {
        if (this.inited && !update) {
            return;
        }

        if (!this.main.objectsLoaded) {
            setTimeout(function () {
                that.init(update);
            }, 250);
            return;
        }

        that.main.systemConfig.common.intro = that.main.systemConfig.common.intro || {};

        // update info
        readInstances(function (err, instances) {
            showTiles(instances, function () {
                that.restoreScroll();
            });
        });

        // Required is list of hosts and repository (done in getAdaptersInfo)
        if (!this.inited) {
            this.inited = true;
            this.main.subscribeObjects('system.adapter.*');
            this.main.subscribeObjects('system.host.*');
        }
    };
    this.saveScroll         = function () {
        this.scrollTop = this.$tiles.scrollTop();
    };
    this.restoreScroll         = function () {
        if (that.scrollTop) {
            that.$tiles.scrollTop(that.scrollTop);
        }
    };
    this.destroy = function () {
        if (this.inited) {
            this.saveScroll();
            this.inited = false;
            this.main.unsubscribeObjects('system.adapter.*');
            this.main.unsubscribeObjects('system.host.*');
        }
    };

    this.objectChange = function (id /*, obj, action*/) {
        // Update Adapter Table
        if (this.inited && (id.match(/^system\.adapter\.[a-zA-Z0-9-_]+\.\d+$/) || id.match(/^system\.host\./))) {
            if (this.updateTimeout) {
                this.updateTimeout = clearTimeout(this.updateTimeout);
            }
            this.updateTimeout = setTimeout(function () {
                showTiles();
            }, 1000);
        }
    };
}


<|start_filename|>src/materialize-css/js/slider.js<|end_filename|>
(function ($, anim) {
  'use strict';

  let _defaults = {
    indicators: true,
    height: 400,
    duration: 500,
    interval: 6000
  };


  /**
   * @class
   *
   */
  class Slider extends Component {
    /**
     * Construct Slider instance and set up overlay
     * @constructor
     * @param {Element} el
     * @param {Object} options
     */
    constructor(el, options) {
      super(Slider, el, options);

      this.el.M_Slider = this;

      /**
       * Options for the modal
       * @member Slider#options
       * @prop {Boolean} [indicators=true] - Show indicators
       * @prop {Number} [height=400] - height of slider
       * @prop {Number} [duration=500] - Length in ms of slide transition
       * @prop {Number} [interval=6000] - Length in ms of slide interval
       */
      this.options = $.extend({}, Slider.defaults, options);

      // setup
      this.$slider = this.$el.find('.slides');
      this.$slides = this.$slider.children('li');
      this.activeIndex = this.$slider.find('.active').index();
      if (this.activeIndex != -1) {
        this.$active = this.$slides.eq(this.activeIndex);
      }

      this._setSliderHeight();

      // Set initial positions of captions
      this.$slides.find('.caption').each((el) => {
        this._animateCaptionIn(el, 0);
      });

      // Move img src into background-image
      this.$slides.find('img').each((el) => {
        let placeholderBase64 = 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
        if ($(el).attr('src') !== placeholderBase64) {
          $(el).css('background-image', 'url("' + $(el).attr('src') + '")');
          $(el).attr('src', placeholderBase64);
        }
      });

      this._setupIndicators();

      // Show active slide
      if (this.$active) {
        this.$active.css('display', 'block');
      } else {
        this.$slides.first().addClass('active');
        anim({
          targets: this.$slides.first()[0],
          opacity: 1,
          duration: this.options.duration,
          easing: 'easeOutQuad'
        });

        this.activeIndex = 0;
        this.$active = this.$slides.eq(this.activeIndex);

        // Update indicators
        if (this.options.indicators) {
          this.$indicators.eq(this.activeIndex).addClass('active');
        }
      }

      // Adjust height to current slide
      this.$active.find('img').each((el) => {
        anim({
          targets: this.$active.find('.caption')[0],
          opacity: 1,
          translateX: 0,
          translateY: 0,
          duration: this.options.duration,
          easing: 'easeOutQuad'
        });
      });

      this._setupEventHandlers();

      // auto scroll
      this.start();
    }

    static get defaults() {
      return _defaults;
    }

    static init(els, options) {
      return super.init(this, els, options);
    }

    /**
     * Get Instance
     */
    static getInstance(el) {
      let domElem = !!el.jquery ? el[0] : el;
      return domElem.M_Slider;
    }

    /**
     * Teardown component
     */
    destroy() {
      this.pause();
      this._removeIndicators();
      this._removeEventHandlers();
      this.el.M_Slider = undefined;
    }

    /**
     * Setup Event Handlers
     */
    _setupEventHandlers() {
      this._handleIntervalBound = this._handleInterval.bind(this);
      this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this);

      if (this.options.indicators) {
        this.$indicators.each((el) => {
          el.addEventListener('click', this._handleIndicatorClickBound);
        });
      }
    }

    /**
     * Remove Event Handlers
     */
    _removeEventHandlers() {
      if (this.options.indicators) {
        this.$indicators.each((el) => {
          el.removeEventListener('click', this._handleIndicatorClickBound);
        });
      }
    }

    /**
     * Handle indicator click
     * @param {Event} e
     */
    _handleIndicatorClick(e) {
      let currIndex = $(e.target).index();
      this.set(currIndex);
    }

    /**
     * Handle Interval
     */
    _handleInterval() {
      let newActiveIndex = this.$slider.find('.active').index();
      if (this.$slides.length === newActiveIndex + 1) newActiveIndex = 0; // loop to start
      else newActiveIndex += 1;

      this.set(newActiveIndex);
    }

    /**
     * Animate in caption
     * @param {Element} caption
     * @param {Number} duration
     */
    _animateCaptionIn(caption, duration) {
      let animOptions = {
        targets: caption,
        opacity: 0,
        duration: duration,
        easing: 'easeOutQuad'
      };

      if ($(caption).hasClass('center-align')) {
        animOptions.translateY = -100;

      } else if ($(caption).hasClass('right-align')) {
        animOptions.translateX = 100;

      } else if ($(caption).hasClass('left-align')) {
        animOptions.translateX = -100;
      }

      anim(animOptions);
    }

    /**
     * Set height of slider
     */
    _setSliderHeight() {
      // If fullscreen, do nothing
      if (!this.$el.hasClass('fullscreen')) {
        if (this.options.indicators) {
          // Add height if indicators are present
          this.$el.css('height', (this.options.height + 40) + 'px');
        } else {
          this.$el.css('height', this.options.height + 'px');
        }
        this.$slider.css('height', this.options.height + 'px');
      }
    }

    /**
     * Setup indicators
     */
    _setupIndicators() {
      if (this.options.indicators) {
        this.$indicators = $('<ul class="indicators"></ul>');
        this.$slides.each((el, index) => {
          let $indicator = $('<li class="indicator-item"></li>');
          this.$indicators.append($indicator[0]);
        });
        this.$el.append(this.$indicators[0]);
        this.$indicators = this.$indicators.children('li.indicator-item');
      }
    }

    /**
     * Remove indicators
     */
    _removeIndicators() {
      this.$el.find('ul.indicators').remove();
    }

    /**
     * Cycle to nth item
     * @param {Number} index
     */
    set(index) {
      // Wrap around indices.
      if (index >= this.$slides.length) index = 0;
      else if (index < 0) index = this.$slides.length - 1;

      // Only do if index changes
      if (this.activeIndex != index) {
        this.$active = this.$slides.eq(this.activeIndex);
        let $caption = this.$active.find('.caption');
        this.$active.removeClass('active');

        anim({
          targets: this.$active[0],
          opacity: 0,
          duration: this.options.duration,
          easing: 'easeOutQuad',
          complete: () => {
            this.$slides.not('.active').each((el) => {
              anim({
                targets: el,
                opacity: 0,
                translateX: 0,
                translateY: 0,
                duration: 0,
                easing: 'easeOutQuad'
              });
            });
          }
        });

        this._animateCaptionIn($caption[0], this.options.duration);

        // Update indicators
        if (this.options.indicators) {
          this.$indicators.eq(this.activeIndex).removeClass('active');
          this.$indicators.eq(index).addClass('active');
        }

        anim({
          targets: this.$slides.eq(index)[0],
          opacity: 1,
          duration: this.options.duration,
          easing: 'easeOutQuad'
        });

        anim({
          targets: this.$slides.eq(index).find('.caption')[0],
          opacity: 1,
          translateX: 0,
          translateY: 0,
          duration: this.options.duration,
          delay: this.options.duration,
          easing: 'easeOutQuad'
        });

        this.$slides.eq(index).addClass('active');
        this.activeIndex = index;

        // Reset interval
        this.start();
      }
    }

    /**
     * Pause slider interval
     */
    pause() {
      clearInterval(this.interval);
    }

    /**
     * Start slider interval
     */
    start() {
      clearInterval(this.interval);
      this.interval = setInterval(
        this._handleIntervalBound, this.options.duration + this.options.interval
      );
    }

    /**
     * Move to next slide
     */
    next() {
      let newIndex = this.activeIndex + 1;

      // Wrap around indices.
      if (newIndex >= this.$slides.length) newIndex = 0;
      else if (newIndex < 0) newIndex = this.$slides.length - 1;

      this.set(newIndex);
    }

    /**
     * Move to previous slide
     */
    prev() {
      let newIndex = this.activeIndex - 1;

      // Wrap around indices.
      if (newIndex >= this.$slides.length) newIndex = 0;
      else if (newIndex < 0) newIndex = this.$slides.length - 1;

      this.set(newIndex);
    }
  }

  M.Slider = Slider;

  if (M.jQueryLoaded) {
    M.initializeJqueryWrapper(Slider, 'slider', 'M_Slider');
  }

}(cash, M.anime));


<|start_filename|>src-rx/src/tabs/Files.js<|end_filename|>
import { Component } from 'react';

import PropTypes from 'prop-types';
import LinearProgress from '@material-ui/core/LinearProgress';

import FileBrowser from '../components/FileBrowser';

import TabContainer from '../components/TabContainer';
import TabContent from '../components/TabContent';
import FileEditOfAccessControl from '../dialogs/FileEditOfAccessControl';

class Files extends Component {
    constructor(props) {
        super(props);
        this.t = this.translate;
        this.wordCache = {};
        this.objects = {};
    }

    componentDidMount() {
        this.props.socket.getObjects(true, true)
            .then(objects =>
                this.objects = objects)
    }

    translate = (word, arg1, arg2) => {
        if (arg1 !== undefined) {
            return this.props.t(word, arg1, arg2);
        }

        if (!this.wordCache[word]) {
            this.wordCache[word] = this.props.t(word);
        }

        return this.wordCache[word];
    }

    render() {
        if (!this.props.ready) {
            return <LinearProgress />;
        }

        return <TabContainer>
            <TabContent overflow="auto">
                <FileBrowser
                    showViewTypeButton={true}
                    ready={this.props.ready}
                    socket={this.props.socket}
                    themeName={this.props.themeName}
                    lang={this.props.lang}
                    t={this.props.t}
                    showToolbar={true}
                    allowUpload={true}
                    allowView={true}
                    allowDownload={true}
                    allowCreateFolder={true}
                    allowDelete={true}
                    expertMode={this.props.expertMode}
                    modalEditOfAccessControl={(context, objData) =>
                        <FileEditOfAccessControl
                            open={context.state.modalEditOfAccess}
                            themeType={this.props.themeType}
                            extendObject={async (adapter, file, data) => {
                                let promise;
                                if (file && typeof file === 'object') {
                                    const obj = file;
                                    // it is setObject
                                    promise = this.props.socket.getObject(obj._id)
                                        .then(oldObj => {
                                            oldObj.acl = obj.acl;
                                            return this.props.socket.setObject(oldObj._id, oldObj)
                                                .then(() => oldObj);
                                        });
                                } else if (file) {
                                    if ((data.owner || data.ownerGroup) && data.permissions) {
                                        promise = this.props.socket.chownFile(adapter, file, {owner: data.owner, ownerGroup: data.ownerGroup})
                                            .then(() => this.props.socket.chmodFile(adapter, file, {mode: data.permissions}));
                                    } else
                                    if (data.permissions) {
                                        promise = this.props.socket.chmodFile(adapter, file, {mode: data.permissions});
                                    } else if (data.owner || data.ownerGroup) {
                                        promise = this.props.socket.chownFile(adapter, file, {owner: data.owner, ownerGroup: data.ownerGroup});
                                    }
                                }

                                if (promise) {
                                    const result = await promise;
                                    if (result.entries) {
                                        if (result?.entries) {
                                            for ( let i = 0; i < result?.entries.length; i++) {
                                                await context.updateItemsAcl([{id: adapter + '/' + (result.entries[i].path ? result.entries[i].path + '/' : '') + result.entries[i].file, acl: result.entries[i].acl}]);
                                            }
                                        }
                                    } else if (result) {
                                        await context.updateItemsAcl([{id: result._id, acl: result.acl}]);
                                    }
                                }
                            }}
                            selected={context.state.selected}
                            folders={context.state.folders}
                            objects={this.objects}
                            socket={this.props.socket}
                            t={this.t}
                            onClose={() => context.setState({ modalEditOfAccess: false, modalEditOfAccessObjData: null })}
                            onApply={() => context.setState({ modalEditOfAccess: false, modalEditOfAccessObjData: null })} />
                    }
                />
            </TabContent>
        </TabContainer>;
    }
}

Files.propTypes = {
    t: PropTypes.func,
    lang: PropTypes.string,
    socket: PropTypes.object,
    ready: PropTypes.bool,
    expertMode: PropTypes.bool,
    themeType: PropTypes.string,
};

export default Files;

<|start_filename|>src/js/tableEditor.js<|end_filename|>
/**
 * Create edit table from javascript array.
 *
 * This function creates a html edit table.
 *
 * <pre><code>
 *   <div id="values" style="width: 100%; height: calc(100% - 205px)">
 *      <button class="table-button-add" style="margin-left: 10px"></button>
 *      <div style="width: 100%; height: calc(100% - 30px); overflow: auto;">
 *          <table class="table-values" style="width: 100%;">
 *              <thead>
 *                  <tr>
 *                      <th data-name="_index" style="30px" data-style="width: 100%; text-align: right">Context</th>
 *                      <th data-name="regex"     class="translate" style="width: 30%" data-style="text-align: right">Context</th>
 *                      <th data-name="room"      class="translate" data-type="select">Room</th>
 *                      <th data-name="aaa"       class="translate" data-options="1/A;2/B;3/C;4" data-type="select">Room</th>
 *                      <th data-name="enabled"   class="translate" data-type="checkbox">Enabled</th>
 *                      <th data-buttons="delete up down" style="width: 32px"></th>
 *                  </tr>
 *              </thead>
 *          </table>
 *      </div>
 *   </div>
 * </pre></code>
 *
 * @param {string} divId name of the html element (or empty).
 * @param {string} values data array
 * @param {object} options settings
 *  <pre><code>
 *      {
 *          onChange: this function will be called if something changed. function (attr, index) {}
 *          onReady: called, when the table is ready (may be to modify some elements of it),
 *          maxRaw: maximal number of rows
 *      }
 *  </pre></code>
 * @return {object} array with values
 */
function values2table(divId, values, options) {
    if (typeof divId === 'object') {
        options = values;
        values = divId;
        divId    = '';
    }
    var maxRaw   = (options && options.maxRaw) || null;
    var onChange = (options && options.onChange) || null;
    var onReady  = (options && options.onReady) || null;

    values = values || [];
    var names = [];
    var $div;
    if (!divId) {
        $div = $('body');
    } else {
        $div = $('#' + divId);
    }
    var $add = $div.find('.table-button-add');
    $add.data('raw', values.length);

    if (maxRaw) {
        $add.data('maxRaw', maxRaw);
    }

    if (!$add.data('inited')) {
        $add.data('inited', true);

        // var addText = $add.text();

        $add.on('click', function () {
            if (!$add.data('maxRaw') || ($add.data('raw') < $add.data('maxRaw'))) {
                var $table = $div.find('.table-values');
                var values = $table.data('values');
                var names  = $table.data('names');
                var maxRaw = $table.data('maxRaw');
                var obj = {};
                for (var i = 0; i < names.length; i++) {
                    if (!names[i]) continue;
                    obj[names[i].name] = names[i].def;
                }
                values.push(obj);
                onChange && onChange();
                setTimeout(function () {
                    values2table(divId, values, onChange, onReady, maxRaw);
                }, 100);
                $add.data('raw', $add.data('raw') + 1);
            } else {
                confirmMessage(_('maxTableRaw') + ': ' + $add.data('maxRaw'), _('maxTableRawInfo'), 'alert', ['Ok']);
            }
        });
    }

    if (values) {
        var buttons = [];
        var $table = $div.find('.table-values');
        $table.data('values', values);

        // load rooms
        if (!$table.data('rooms') && $table.find('th[data-name="room"]').length) {
            getEnums('rooms', function (err, list) {
                var result = {};
                var trRooms = _('nonerooms');
                if (trRooms !== 'nonerooms') {
                    result[_('none')] = trRooms;
                } else {
                    result[_('none')] = '';
                }
                var nnames = [];
                for (var n in list) {
                    if (list.hasOwnProperty(n)) {
                        nnames.push(n);
                    }
                }
                nnames.sort(function (a, b) {
                    a = a.toLowerCase();
                    b = b.toLowerCase();
                    if (a > b) return 1;
                    if (a < b) return -1;
                    return 0;
                });

                for (var l = 0; l < nnames.length; l++) {
                    result[nnames[l]] = list[nnames[l]].common.name || l;
                }
                $table.data('rooms', result);
                values2table(divId, values, onChange, onReady, maxRaw);
            });
            return;
        }
        // load functions
        if (!$table.data('functions') && $table.find('th[data-name="func"]').length) {
            getEnums('functions', function (err, list) {
                var result = {};
                var trFuncs = _('nonefunctions');
                if (trFuncs !== 'nonefunctions') {
                    result[_('none')] = trFuncs;
                } else {
                    result[_('none')] = '';
                }

                var nnames = [];
                for (var n in list) {
                    if (list.hasOwnProperty(n)) {
                        nnames.push(n);
                    }
                }
                nnames.sort(function (a, b) {
                    a = a.toLowerCase();
                    b = b.toLowerCase();
                    if (a > b) return 1;
                    if (a < b) return -1;
                    return 0;
                });

                for (var l = 0; l < nnames.length; l++) {
                    result[nnames[l]] = list[nnames[l]].common.name || l;
                }
                $table.data('functions', result);
                values2table(divId, values, onChange, onReady, maxRaw);
            });
            return;
        }
        $table.find('th').each(function () {
            var name = $(this).data('name');
            if (name) {
                var obj = {
                    name:    name,
                    type:     $(this).data('type') || 'text',
                    def:      $(this).data('default'),
                    'class':  ($(this).attr('class') || '').replace('translate', ''),
                    style:    $(this).data('style'),
                    readOnly: $(this).data('readOnly'),
                    tdstyle:  $(this).data('tdstyle')
                };
                if (obj.type === 'checkbox') {
                    if (obj.def === 'false') obj.def = false;
                    if (obj.def === 'true')  obj.def = true;
                    obj.def = !!obj.def;
                } else if (obj.type === 'select' || obj.type === 'select multiple') {
                    var vals = ($(this).data('options') || '').split(';');
                    obj.options = {};
                    for (var v = 0; v < vals.length; v++) {
                        var parts = vals[v].split('/');
                        obj.options[parts[0]] = _(parts[1] || parts[0]);
                        if (v === 0) obj.def = (obj.def === undefined) ? parts[0] : obj.def;
                    }
                } else {
                    obj.def = obj.def || '';
                }
                names.push(obj);
            } else {
                names.push(null);
            }

            name = $(this).data('buttons');

            if (name) {
                buttons.push({
                    btn:        name.split(' '),
                    'class':    ($(this).attr('class') || '').replace('translate', ''),
                    style:      $(this).data('style'),
                    tdstyle:    $(this).data('tdstyle')
                });
            } else {
                buttons.push(null);
            }
        });

        $table.data('names', names);

        var text = '';
        for (var v = 0; v < values.length; v++) {
            var idName = values[v] && values[v].id;
            if (!idName && values[v]) {
                if (names[0] === '_index') {
                    idName = values[v][names[1]];
                } else {
                    idName = values[v][names[0]];
                }
            }
            text += '<tr data-id="' + idName + '" data-index="' + v + '">';

            for (var i = 0; i < names.length; i++) {
                text += '<td';
                var line    = '';
                var style   = '';
                var tdstyle = '';
                if (names[i]) {
                    if (names[i]['class']) {
                        text += ' class="' + names[i]['class'] + '" ';
                    }
                    if (names[i].name !== '_index') {
                        tdstyle = names[i].tdstyle || '';
                        if (tdstyle && tdstyle[0] !== ';') tdstyle = ';' + tdstyle;
                    }
                    if (names[i].name === '_index') {
                        style = (names[i].style ? names[i].style : 'text-align: right;');
                        line += (v + 1);
                    } else if (names[i].type === 'checkbox') {
                        line += '<input style="' + (names[i].style || '') + '" class="values-input" type="checkbox" data-index="' + v + '" data-name="' + names[i].name + '" ' + (values[v][names[i].name] ? 'checked' : '') + '" data-old-value="' + (values[v][names[i].name] === undefined ? '' : values[v][names[i].name]) + '"/>';
                    } else if (names[i].type.substring(0, 6) === 'select') {
                        line += (names[i].type.substring(7, 16) === 'multiple' ? '<select multiple style="' : '<select style="') + (names[i].style ? names[i].style : 'width: 100%') + '" class="values-input" data-index="' + v + '" data-name="' + names[i].name + '">';
                        var options;
                        if (names[i].name === 'room') {
                            options = $table.data('rooms');
                        } else if (names[i].name === 'func') {
                            options = $table.data('functions');
                            if (names[i].type === 'select multiple') delete options[_('none')];
                        } else {
                            options = names[i].options;
                        }

                        var val = (values[v][names[i].name] === undefined ? '' : values[v][names[i].name]);
                        if (typeof val !== 'object') val = [val];
                        for (var p in options) {
                            line += '<option value="' + p + '" ' + (val.indexOf(p) !== -1 ? ' selected' : '') + '>' + options[p] + '</option>';
                        }
                        line += '</select>';
                    } else {
                        line += '<input class="values-input" style="' + (names[i].style ? names[i].style : 'width: 100%') + '" type="' + names[i].type + '" data-index="' + v + '" data-name="' + names[i].name + '"/>';
                    }
                }

                if (buttons[i]) {
                    style = 'text-align: center; ' + (buttons[i].style || '') + (buttons[i].tdstyle || '');
                    for (var b = 0; b < buttons[i].btn.length; b++) {
                        if ((!v && buttons[i].btn[b] === 'up') || v === values.length - 1 && buttons[i].btn[b] === 'down') {
                            line += '<a class="btn-floating disabled" data-command="' + buttons[i].btn[b] + '" class="values-buttons"><i class="material-icons"></i></a>';
                            continue;
                        }
                        line += '<a class="btn-floating" data-index="' + v + '" data-command="' + buttons[i].btn[b] + '" class="values-buttons"><i class="material-icons"></i></a>';
                    }
                    if (buttons[i]['class']) {
                        text += ' class="' + buttons[i]['class'] + '" ';
                    }
                }
                if (style.length || tdstyle.length) {
                    text += ' style="' + style + tdstyle + '">' + line + '</td>';
                } else {
                    text += '>' + line + '</td>';
                }
            }

            text += '</tr>';
        }
        var $lines = $table.find('.table-lines');
        if (!$lines.length) {
            $table.append('<tbody class="table-lines"></tbody>');
            $lines = $table.find('.table-lines');
        }

        $lines.html(text);

        $lines.find('.values-input').each(function () {
            var $this = $(this);
            var type = $this.attr('type');
            var name = $this.data('name');
            var id   = $this.data('index');
            $this.data('old-value', values[id][name]);
            if (type === 'checkbox') {
                $this.prop('checked', values[id][name]);
            } else {
                $this.val(values[id][name]);
            }
        });
        $lines.find('a[data-command]').each(function () {
            var command = $(this).data('command');
            if (command === 'delete') {
                $(this).on('click', function () {
                    var id = $(this).data('index');
                    var elem = values[id];
                    values.splice(id, 1);
                    onChange && onChange();

                    setTimeout(function () {
                        if (typeof tableEvents === 'function') {
                            tableEvents(id, elem, 'delete');
                        }

                        values2table(divId, values, onChange, onReady, maxRaw);
                    }, 100);

                    if ($add.data('maxRaw')) {
                        $add.data('raw', $add.data('raw') - 1);
                    }
                })
                .addClass('red')
                    .find('.material-icons')
                    .html('delete');
            } else if (command === 'up') {
                $(this).on('click', function () {
                        var id = $(this).data('index');
                        var elem = values[id];
                        values.splice(id, 1);
                        values.splice(id - 1, 0, elem);
                        onChange && onChange();
                        setTimeout(function () {
                            values2table(id, values, onChange, onReady, maxRaw);
                        }, 100);
                    }).find('i').html('arrow_upward');
            } else if (command === 'down') {
                $(this).on('click', function () {
                        var id = $(this).data('index');
                        var elem = values[id];
                        values.splice(id, 1);
                        values.splice(id + 1, 0, elem);
                        onChange && onChange();
                        setTimeout(function () {
                            values2table(id, values, onChange, onReady, maxRaw);
                        }, 100);
                    }).find('i').html('arrow_downward');
            } else if (command === 'pair') {
                $(this).on('click', function () {
                        if (typeof tableEvents === 'function') {
                            var id = $(this).data('index');
                            var elem = values[id];
                            tableEvents(id, elem, 'pair');
                        }
                    }).attr('title', _('pair')).find('i').html('insert_link');
            } else if (command === 'unpair') {
                $(this).on('click', function () {
                        if (typeof tableEvents === 'function') {
                            var id = $(this).data('index');
                            var elem = values[id];
                            tableEvents(id, elem, 'unpair');
                        }
                    }).attr('title', _('unpair')).find('i').html('not_interested');
            }
        });

        $lines.find('.values-input').on('change.adaptersettings', function () {
            var index = $(this).data('index');
            var name  = $(this).data('name');
            if ($(this).attr('type') === 'checkbox') {
                if ($(this).prop('checked').toString() !== $(this).data('old-value') && onChange) onChange(name, index);
                values[index][name] = $(this).prop('checked');
            } else {
                if ($(this).val() !== $(this).data('old-value') && onChange) onChange(name, index);
                values[index][name] = $(this).val();

            }
        }).on('keyup', function () {
            $(this).trigger('change.adaptersettings');
        });
    }
    if (typeof onReady === 'function') onReady();
}

/**
 * Extract the values from table.
 *
 * This function extracts the values from edit table, that was generated with values2table function.
 *
 * @param {string} divId name of the html element (or nothing).
 * @return {object} array with values
 */
function table2values(divId) {
    var $div;
    if (!divId) {
        $div = $('body');
    } else {
        $div = $('#' + divId);
    }
    var names = [];
    $div.find('.table-values th').each(function () {
        var name = $(this).data('name');
        if (name) {
            names.push(name);
        } else {
            names.push('___ignore___');
        }
    });

    var values = [];
    var j = 0;
    $div.find('.table-lines tr').each(function () {
        values[j] = {};

        $(this).find('td').each(function () {
            var $input = $(this).find('input');
            if ($input.length) {
                var name = $input.data('name');
                if ($input.attr('type') === 'checkbox') {
                    values[j][name] = $input.prop('checked');
                } else {
                    values[j][name] = $input.val();
                }
            }
            var $select = $(this).find('select');
            if ($select.length) {
                var name = $select.data('name');
                values[j][name] = $select.val() || '';
            }
        });
        j++;
    });

    return values;
}

<|start_filename|>src-rx/src/components/ToggleThemeMenu.js<|end_filename|>
import React from 'react';
// import Menu from '@material-ui/core/Menu';
// import MenuItem from '@material-ui/core/MenuItem';
import { IconButton, Tooltip } from '@material-ui/core';
import Brightness4Icon from '@material-ui/icons/Brightness4';
import Brightness5Icon from '@material-ui/icons/Brightness5';
import Brightness6Icon from '@material-ui/icons/Brightness6';
import Brightness7Icon from '@material-ui/icons/Brightness7';

// const themeNames = ['dark', 'blue', 'colored', 'light'];

export default function ToggleThemeMenu({ themeName, toggleTheme, t, className, style, size }) {
    // const [anchorEl, setAnchorEl] = useState(null);
    // const handleClose = (name) => {
    //     setAnchorEl(null);
    //     toggleTheme(name);
    // };

    return <div className={className || undefined} style={style || undefined}>
        <Tooltip title={t('Change color theme')}>
            <IconButton onClick={() => toggleTheme()} size={size || 'medium'}>
                {themeName === 'dark'    && <Brightness4Icon />}
                {themeName === 'blue'    && <Brightness5Icon />}
                {themeName === 'colored' && <Brightness6Icon />}
                {themeName === 'light'   && <Brightness7Icon />}
            </IconButton>
        </Tooltip>
        {/* <Menu
            id="simple-menu"
            anchorEl={anchorEl}
            keepMounted
            open={Boolean(anchorEl)}
            onClose={() => setAnchorEl(null)}
        >
            {themeNames.map(name => <MenuItem
                key={name}
                selected={name === themeName}
                onClick={() => handleClose(name)}>
                {t(name)}
            </MenuItem>)}
        </Menu> */}
    </div>;
}

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigUser.js<|end_filename|>
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

import Icon from '@iobroker/adapter-react/Components/Icon';
import I18n from '@iobroker/adapter-react/i18n';
import Utils from '../Utils';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    },
    icon: {
        width: 16,
        height: 16,
        marginRight: 8
    }
});

class ConfigUser extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();
        this.props.socket.getUsers()
            .then(users => {
                const _users = {};
                const lang = I18n.getLanguage();

                if (this.props.schema.short) {
                    users.forEach(user => _users[user._id] = {
                        color: user.common?.color,
                        icon: user.common?.icon,
                        name: Utils.getObjectNameFromObj(user, lang)
                    });
                } else {
                    users.forEach(user => _users[user._id.replace(/^system\.user\./, '')] = {
                        color: user.common?.color,
                        icon: user.common?.icon,
                        name: Utils.getObjectNameFromObj(user, lang)
                    });
                }

                this.setState({users: _users});
            });
    }

    renderItem(error, disabled, defaultValue) {
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);

        return <FormControl className={this.props.classes.fullWidth}>
            <InputLabel>{this.getText(this.props.schema.label)}</InputLabel>
            <Select
                error={!!error}
                disabled={!!disabled}
                value={value}
                renderValue={val => <span>{this.state.users && this.state.users[val]?.icon ? <Icon src={this.state.users && this.state.users[val]?.icon} className={this.props.classes.icon} /> : null}{(this.state.users && this.state.users[val]?.name) || val || ''}</span>}
                style={{ color: (this.state.users && this.state.users[value]?.color) || undefined, backgroundColor: Utils.getInvertedColor(this.state.users && this.state.users[value]?.color, this.props.themeType) }}
                onChange={e => this.onChange(this.props.attr, e.target.value)}
            >
                {this.state.users && Object.keys(this.state.users).map(id => <MenuItem style={{ color: this.state.users[id].color || undefined, backgroundColor: Utils.getInvertedColor(this.state.users[id].color, this.props.themeType) }} key={id} value={id}>
                    {this.state.users[id].icon ? <Icon src={this.state.users[id].icon} className={this.props.classes.icon} /> : null}
                    {this.state.users[id].name}
                </MenuItem>)}
            </Select>
            {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
        </FormControl>;
    }
}

ConfigUser.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigUser);

<|start_filename|>src/js/adminHosts.js<|end_filename|>
/* global systemLang, M, FORBIDDEN_CHARS, showdown */

function Hosts(main) {
    'use strict';

    var that      = this;
    this.main     = main;
    this.list     = [];
    this.$tab     = $('#tab-hosts');
    this.$grid    = this.$tab.find('#hosts');
    this.$table   = this.$tab.find('#grid-hosts');
    this.inited   = false;
    this.isTiles  = true;
    this.words    = {};
    
    showdown.setFlavor('github');

    this.prepare  = function () {
        this.isTiles = (this.main.config.hostsIsTiles !== undefined && this.main.config.hostsIsTiles !== null) ? this.main.config.hostsIsTiles : true;

        // fix for IE
        if (this.main.browser === 'ie' && this.main.browserVersion <= 10) {
            this.isTiles = false;
            this.$tab.find('.btn-switch-tiles').hide();
        }

        this.$tab.find('.btn-reload')
            .attr('title', _('Update'))
            .on('click', function () {
                that.init(true);
            });

        this.$tab.find('.btn-switch-tiles').off('click').on('click', function () {
            that.isTiles = !that.isTiles;

            if (that.isTiles) {
                $(this).find('i').text('view_list');
            } else {
                $(this).find('i').text('view_module');
            }

            that.main.saveConfig('hostsIsTiles', that.isTiles);

            setTimeout(function () {
                that._postInit();
            }, 50);
        });

        if (this.isTiles) {
            this.$tab.find('.btn-switch-tiles').find('i').text('view_list');
        } else {
            this.$tab.find('.btn-switch-tiles').find('i').text('view_module');
        }

        this.$tab.find('.filter-clear').on('click', function () {
            that.$tab.find('.filter-input').val('').trigger('change');
        });

        var $hostsFilter = this.$tab.find('.filter-input');
        $hostsFilter.on('change', function () {
            var filter = $(this).val();
            if (filter) {
                $(this).addClass('input-not-empty');
                that.$tab.find('.filter-clear').show();
            } else {
                that.$tab.find('.filter-clear').hide();
                $(this).removeClass('input-not-empty');
            }

            that.main.saveConfig('hostsFilter', filter);
            applyFilter(filter);
        }).on('keyup', function () {
            if (that.filterTimeout) clearTimeout(that.filterTimeout);
            that.filterTimeout = setTimeout(function () {
                that.$tab.find('.filter-input').trigger('change');
            }, 300);
        });

        if (this.main.config.hostsFilter && this.main.config.hostsFilter[0] !== '{') {
            $hostsFilter.val(that.main.config.hostsFilter).addClass('input-not-empty');
            this.$tab.find('.filter-clear').show();
        } else {
            this.$tab.find('.filter-clear').hide();
        }

        // cache translations
        this.words['Title']     = _('Title');
        this.words['OS']        = _('OS');
        this.words['Available'] = _('Available');
        this.words['Installed'] = _('Installed');
        this.words['Events']    = _('Events');
        this.words['Title']     = _('Title');
        that.words['Type']      = _('Type');
    };

    // ----------------------------- Hosts show and Edit ------------------------------------------------
    this.initButtons = function (id) {
        var selector = id ? '[data-host-id="' + id + '"]' : '';

        this.$tab.find('.host-update-submit' + selector).off('click').on('click', function () {
            that.main.cmdExec($(this).attr('data-host-name'), 'upgrade self', function (exitCode) {
                if (!exitCode) that.init(true);
            });
        });

        this.$tab.find('.host-restart-submit' + selector).off('click').on('click', function () {
            that.main.waitForRestart = true;
            that.main.cmdExec($(this).attr('data-host-name'), 'restart');
        });
        this.$tab.find('.host-delete' + selector).off('click').on('click', function () {
            that.main.cmdExec(that.main.currentHost, 'host remove ' + $(this).attr('data-host-name'));
        });

        this.$tab.find('.host-edit' + selector).off('click').on('click', function () {
            editHost($(this).attr('data-host-id'));
        });

        this.$tab.find('.host-update-hint-submit' + selector).off('click').on('click', function () {
            var infoTimeout = setTimeout(function () {
                showUpdateInfo();
                infoTimeout = null;
            }, 1000);

            that.main.socket.emit('sendToHost', $(this).attr('data-host-name'), 'getLocationOnDisk', null, function (data) {
                if (infoTimeout) clearTimeout(infoTimeout);
                infoTimeout = null;
                showUpdateInfo(data);
            });
        });

    };
    
    async function showUpdateInfo(data) {
        const $dialog = $('#dialog-host-update');

        let updateInfo;
        await fetch("https://raw.githubusercontent.com/ioBroker/ioBroker.docs/master/admin/" + systemLang + "/controller-upgrade.md").then(async (resp) => {
            if(resp.ok){
                updateInfo = await resp.text();
            }else{
                updateInfo = _("update_info_no_net");
            }
        });
        let html = new showdown.Converter().makeHtml(updateInfo);
        html = html.replace(/<img/g, '<img class="img-responsive"');
        html = html.replace(/<h1/g, '<h4');
        html = html.replace(/<h2/g, '<h5');
        html = html.replace(/<\/h1>/g, '</h4>');
        html = html.replace(/<\/h2>/g, '</h5>');
        
        $dialog.find('#dialog-host-update-instructions').html(html);

        if (!$dialog.data('inited')) {
            $dialog.data('inited', true);
            $dialog.modal();
        }
        
        $dialog.modal('open');
    }

    function applyFilter(filter) {
        filter = (filter || '').toLowerCase().trim();

        if (!filter) {
            that.$tab.find('.hosts-host').show();
            that.$tab.find('.hosts-host-filtered-out').hide();
        } else {
            var someVisible = false;
            that.$tab.find('.hosts-host').each(function () {
                var text = $(this).data('host-filter');
                if (text.toLowerCase().indexOf(filter) !== -1) {
                    $(this).show();
                    someVisible = true;
                } else {
                    $(this).hide();
                }
            });
            if (!someVisible) {
                that.$tab.find('.hosts-host-filtered-out').show();
            } else {
                that.$tab.find('.hosts-host-filtered-out').hide();
            }
        }
    }

    function showOneHostRow(index) {
       var obj   = that.main.objects[that.list[index].id];
       var alive = that.main.states[obj._id + '.alive'] && that.main.states[obj._id + '.alive'].val && that.main.states[obj._id + '.alive'].val !== 'null';
       obj.common = obj.common || {};
       obj.native = obj.native || {};

       var text = '<tr class="hosts-host" data-host-id="' + obj._id + '" data-host-filter="' + (obj.common.title || '') + ' ' + (obj.common.hostname || '') + ' ' + (obj.common.name || '') + '">';
       //LED
       text += '<td class="tab-hosts-header-led"><div class="hosts-led ' + (alive ? 'led-green' : 'led-red') + '" data-host-id="' + obj._id + '"></div></td>';
        // icon
        text += '<td class="hosts-icon">' + that.main.getHostIcon(obj) + '</td>';
       // name
       text += '<td class="hosts-name" style="font-weight: bold">' + obj.common.hostname + '</td>';
       // type
       text += '<td class="tab-hosts-header-type">' + obj.common.type + '</td>';
       var title = obj.common.titleLang || obj.common.title;
       if (typeof title === 'object') {
           title = title[systemLang] || title.en;
       }
       // description
       text += '<td class="tab-hosts-header-title">' + title + '</td>';
       // platform
       // text += '<td>' + obj.common.platform + '</td>'; // actually only one platform
       // OS
       text += '<td class="tab-hosts-header-os">' + (obj.native.os ? obj.native.os.platform : _('unknown')) + '</td>';
       // Available
       text += '<td class="tab-hosts-header-available"><span data-host-id="' + obj._id + '" data-type="' + obj.common.type + '" class="hosts-version-available"></span>' +
           '<button class="small-button host-update-submit"      data-host-name="' + obj.common.hostname + '" style="display: none; opacity: 0;" title="' + _('update') + '"><i class="material-icons">refresh</i></button>' +
           '<button class="small-button host-update-hint-submit" data-host-name="' + obj.common.hostname + '" style="display: none;"             title="' + _('update') + '"><i class="material-icons">refresh</i></button>' +
           '</td>';

       // installed
       text += '<td class="hosts-version-installed tab-hosts-header-installed" data-host-id="' + obj._id + '">' + obj.common.installedVersion + '</td>';

       // event rates
       if (that.main.states[obj._id + '.inputCount']) {
           text += '<td class="tab-hosts-header-events" style="text-align: center"><span title="in" data-host-id="' + obj._id + '" class="host-in">&#x21E5;' + that.main.states[obj._id + '.inputCount'].val + '</span> / <span title="out" data-host-id="' + obj._id + '"  class="host-out">&#x21A6;' + that.main.states[obj._id + '.outputCount'].val + '</span></td>';
       } else {
           text += '<td class="tab-hosts-header-events" style="text-align: center"><span title="in" data-host-id="' + obj._id + '" class="host-in"></span> / <span title="out" data-host-id="' + obj._id + '" class="host-out"></span></td>';
       }

       // restart button
       text += '<td class="tab-hosts-header-restart"><button class="small-button host-restart-submit" style="' + (alive ? '' : 'display: none') + '" data-host-id="' + obj._id + '" title="' + _('restart') + '"><i class="material-icons">autorenew</i></button></td>';

       text += '</tr>';

       return text;
   }

    function showOneHostTile(index) {
        var obj   = that.main.objects[that.list[index].id];
        var alive = that.main.states[obj._id + '.alive'] && that.main.states[obj._id + '.alive'].val && that.main.states[obj._id + '.alive'].val !== 'null';
        obj.common = obj.common || {};
        obj.native = obj.native || {};

        var color;
        if (obj.common.color) {
            color = that.main.invertColor(obj.common.color);
        }

        var text =  '  <div class="col s12 hosts-host" data-host-id="' + obj._id + '" data-host-filter="' + (obj.common.title || '') + ' ' + (obj.common.hostname || '') + ' ' + (obj.common.name || '') + '">'+
                    '      <div class="host z-depth-1 hoverable">' +
                    '          <div class="image center">'+
                    '              ' + that.main.getHostIcon(obj, ' ') +
                    '              <div class="hosts-led ' + (alive ? 'led-green' : 'led-red') +'" data-host-id="' + obj._id + '"></div>' +
                    '          </div>'+
                    '          <div class="system" style="' + (obj.common.color ? 'color: ' + (color ? 'white' : 'black') + '; background: ' + obj.common.color : '') + '">' +
                    '              <span class="nameHost" title="name">' + obj.common.hostname + '</span>' +
                    '              <ul>'+
                    '                  <li class="tab-hosts-header-title"><span class="title-val">' + that.words['Type'] + ':</span> <span class="type">' + obj.common.type + '</span></li>' +
                    '                  <li><span class="title-val">' + that.words['Title']     + ':</span> <span class="title">'      + obj.common.title + '</span></li>' +
                    '                  <li><span class="title-val">' + that.words['OS']        + ':</span> <span class="os">'         + (obj.native.os ? obj.native.os.platform : _('unknown')) + '</span></li>' +
                    '                  <li><span class="title-val">' + that.words['Available'] + ':</span> <span class="available hosts-version-available"></span></li>' +
                    '                  <li><span class="title-val">' + that.words['Installed'] + ':</span> <span class="installed"> ' + obj.common.installedVersion + '</span></li>';

        if (that.main.states[obj._id + '.inputCount']) {
            text += '<li class="tab-hosts-header-events"><span class="title-val">' + that.words['Events'] + ':</span> <span title="in" data-host-id="' + obj._id + '" class="host-in">&#x21E5;' + that.main.states[obj._id + '.inputCount'].val + '</span> / <span title="out" data-host-id="' + obj._id + '"  class="host-out">&#x21A6;' + that.main.states[obj._id + '.outputCount'].val + '</span></li>';
        } else {
            text += '<li class="tab-hosts-header-events"><span class="title-val">' + that.words['Events'] + ':</span> <span title="in" data-host-id="' + obj._id + '" class="host-in"></span> / <span title="out" data-host-id="' + obj._id + '" class="host-out"></span></li>';
        }

        text +=    '</ul>'+
        '          </div>'+
        '          <div class="icon center">'+
        '              <i class="material-icons host-edit"               data-host-id="' + obj._id + '">edit</i>' +
        '              <i class="material-icons host-restart-submit"     data-host-name="' + obj.common.hostname + '" title="' + _('restart') + '">autorenew</i>';
        if (obj.common.hostname !== that.main.currentHost) {
            text += '  <i class="material-icons host-delete"             data-host-name="' + obj.common.hostname + '" title="' + _('remove') + '">delete</i>';
        }
        text +=    '   <i class="material-icons host-update-hint-submit" data-host-name="' + obj.common.hostname + '" style="display: none">refresh</i>' +
        '              <i class="material-icons host-update-submit"      data-host-name="' + obj.common.hostname + '" style="display: none; opacity: 0">refresh</i>' +
        '          </div>'+
        '      </div>'+
        '  </div>';

        return text;
    }

    function editHost(id) {
        var $dialog = $('#tab-host-dialog-edit');

        var titleVal  = '';
        var iconVal  = '';
        var colorVal = '';

        installFileUpload($dialog, 50000, function (err, text) {
            if (err) {
                that.main.showToast($dialog, err);
            } else {
                if (!text.match(/^data:image\//)) {
                    that.main.showToast($dialog, _('Unsupported image format'));
                    return;
                }
                $dialog.find('.tab-host-dialog-ok').removeClass('disabled');
                iconVal   = text;

                $dialog.find('.tab-host-dialog-edit-icon').show().html('<img class="" />');
                $dialog.find('.tab-host-dialog-edit-icon img').attr('src', text);
                $dialog.find('.tab-host-dialog-edit-icon-clear').show();
            }
        });

        if (that.main.objects[id] && that.main.objects[id].common) {
            titleVal      = that.main.objects[id].common.title;
            if (typeof titleVal === 'object') {
                titleVal = titleVal[systemLang] || titleVal.en;
            }
            iconVal      = that.main.objects[id].common.icon;
            colorVal     = that.main.objects[id].common.color;
        }

        $dialog.find('#tab-host-dialog-edit-title')
            .val(titleVal)
            .off('change')
            .on('change', function () {
                $dialog.find('.tab-host-dialog-ok').removeClass('disabled');
            }).off('keyup').on('keyup', function () {
                $(this).trigger('change');
            });

        $dialog.find('.tab-host-dialog-ok')
            .addClass('disabled')
            .off('click')
            .on('click', function () {
                var obj = JSON.parse(JSON.stringify(that.main.objects[id]));
                obj.common.title = $dialog.find('#tab-host-dialog-edit-title').val();
                obj.common.icon =  iconVal;
                obj.common.color = colorVal;
                if (JSON.stringify(obj) !== JSON.stringify(that.main.objects[id])) {
                    that.main.socket.emit('setObject', obj._id, obj, function (err) {
                        that.main.showToast($dialog, _('Updated'));
                    });
                } else {
                    that.main.showToast($dialog, _('Nothing changed'));
                }
            });

        if (iconVal) {
            $dialog.find('.tab-host-dialog-edit-icon').show().html(that.main.getIcon(id));
            $dialog.find('.tab-host-dialog-edit-icon-clear').show();
        } else {
            $dialog.find('.tab-host-dialog-edit-icon').hide();
            $dialog.find('.tab-host-dialog-edit-icon-clear').hide();
        }

        colorVal = colorVal || false;

        if (colorVal) {
            $dialog.find('.tab-host-dialog-edit-color').val(colorVal);
        } else {
            $dialog.find('.tab-host-dialog-edit-color').val();
        }

        M.updateTextFields('#tab-host-dialog-edit');
        that.main.showToast($dialog, _('Drop the icons here'));

        $dialog.find('.tab-host-dialog-edit-upload').off('click').on('click', function () {
            $dialog.find('.drop-file').trigger('click');
        });

        $dialog.find('.tab-host-dialog-edit-icon-clear').off('click').on('click', function () {
            if (iconVal) {
                iconVal = '';
                $dialog.find('.tab-host-dialog-edit-icon').hide();
                $dialog.find('.tab-host-dialog-ok').removeClass('disabled');
                $dialog.find('.tab-host-dialog-edit-icon-clear').hide();
            }
        });
        $dialog.find('.tab-host-dialog-edit-color-clear').off('click').on('click', function () {
            if (colorVal) {
                $dialog.find('.tab-host-dialog-ok').removeClass('disabled');
                $dialog.find('.tab-host-dialog-edit-color-clear').hide();
                $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker({
                    component: '.btn',
                    color: colorVal,
                    container: $dialog.find('.tab-host-dialog-edit-colorpicker')
                }).colorpicker('setValue', '');
                colorVal = '';
            }
        });
        var time = Date.now();
        try {
            $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker('destroy');
        } catch (e) {

        }
        $dialog.find('.tab-host-dialog-edit-colorpicker').colorpicker({
            component: '.btn',
            color: colorVal,
            container: $dialog.find('.tab-host-dialog-edit-colorpicker')
        }).colorpicker('setValue', colorVal).on('showPicker.colorpicker', function (/* event */) {
            //$dialog.find('.tab-host-dialog-edit-colorpicker')[0].scrollIntoView(false);
            var $modal = $dialog.find('.modal-content');
            $modal[0].scrollTop = $modal[0].scrollHeight;
        }).on('changeColor.colorpicker', function (event){
            if (Date.now() - time > 100) {
                colorVal = event.color.toHex();
                $dialog.find('.tab-host-dialog-ok').removeClass('disabled');
                $dialog.find('.tab-host-dialog-edit-icon-clear').show();
            }
        });
        if (colorVal) {
            $dialog.find('.tab-host-dialog-edit-color-clear').show();
        } else {
            $dialog.find('.tab-host-dialog-edit-color-clear').hide();
        }

        $dialog.modal().modal('open');
    }

    function showHostsTile() {
        var text = '';
        for (var i = 0; i < that.list.length; i++) {
            text += showOneHostTile(i);
        }
        that.$table.html('');
        that.$tab.find('.hosts-table').hide();
        that.$grid.html(text).show();
        that.$grid.append('<div class="col s12 hosts-host-filtered-out"><div class="host z-depth-1 hoverable">' + _('Filtered out') + '</div></div>');
    }

    function showHostsTable() {
        var text = '';
        for (var i = 0; i < that.list.length; i++) {
            text += showOneHostRow(i);
        }
        that.$grid.html('').hide();
        that.$table.html(text);
        that.$tab.find('.hosts-table').show();
        that.$table.append('<tr class="hosts-host-filtered-out"><td colspan="9">' + _('Filtered out') + '</td></tr>');
    }

    this.updateCounter = function (counter) {
        if (counter === undefined) {
            this.main.tabs.adapters.getAdaptersInfo(this.main.currentHost, false, false, function (repository, installedList) {
                var hostsToUpdate = 0;
                if (!installedList || !installedList.hosts) return;

                for (var id in installedList.hosts) {
                    if (!installedList.hosts.hasOwnProperty(id)) continue;
                    var obj = that.main.objects['system.host.' + id];
                    if (!obj || !obj.common) continue;
                    var installedVersion = obj.common.installedVersion;
                    var availableVersion = obj.common ? (repository && repository[obj.common.type] ? repository[obj.common.type].version : '') : '';

                    if (installedVersion && availableVersion && !that.main.upToDate(availableVersion, installedVersion)) {
                        id = 'system.host.' + id.trim().replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_');
                        if (that.main.states[id + '.alive'] && that.main.states[id + '.alive'].val && that.main.states[id + '.alive'].val !== 'null') {
                            hostsToUpdate++;
                        }
                    }
                }

                that.updateCounter(hostsToUpdate);
            });
        } else if (counter) {
            var $updates = $('#updates-for-hosts');
            if ($updates.length) {
                $updates.text(counter);
            } else {
                $('<span id="updates-for-hosts" title="' + _('updates') + '" class="new badge updates-for-hosts" data-badge-caption="">' + counter + '</span>').appendTo('.admin-sidemenu-items[data-tab="tab-hosts"] a');
            }
        } else {
            $('#updates-for-hosts').remove();
        }
    };

    this._postInit = function () {
        if (typeof that.$grid !== 'undefined') {
            if (this.isTiles) {
                showHostsTile();
            } else {
                showHostsTable();
            }
            applyFilter(this.$tab.find('.filter-input').val());

            var timer = setTimeout(function () {
                console.warn('Timeout for repository');
                timer = null;
                that.initButtons();
            }, 2000);

            var host = that.main.currentHost;
            if (!host) {
                // find alive host
                for (var i = 0; i < that.list.length; i++) {
                    if (that.main.states[that.list[i].id + '.alive'] && that.main.states[that.list[i].id + '.alive'].val) {
                        host = that.list[i].id;
                        break;
                    }
                }
            }

            that.main.tabs.adapters.getAdaptersInfo(host, true, false, function (repository, installedList) {
                if (!installedList || !installedList.hosts) return;

                for (var id in installedList.hosts) {
                    if (!installedList.hosts.hasOwnProperty(id)) continue;
                    var obj = that.main.objects['system.host.' + id];
                    var installed = installedList.hosts[id].version;
                    if (installed !== installedList.hosts[id].runningVersion)   installed += '(' + _('Running: ') + installedList.hosts[id].runningVersion + ')';
                    if (!installed && obj.common && obj.common.installedVersion) installed = obj.common.installedVersion;

                    id = 'system.host.' + id.trim().replace(FORBIDDEN_CHARS, '_').replace(/\./g, '_');
                    that.$tab.find('.hosts-version-installed[data-host-id="' + id + '"]').html(installed);
                }

                that.$tab.find('.hosts-host').each(function () {
                    var id = $(this).data('host-id');
                    var obj = that.main.objects[id];
                    var installedVersion = obj.common.installedVersion;
                    var availableVersion = obj.common ? (repository && repository[obj.common.type] ? repository[obj.common.type].version : '') : '';
                    if (installedVersion && availableVersion) {
                        if (!that.main.upToDate(availableVersion, installedVersion)) {
                            // show button
                            if (that.main.states[id + '.alive'] && that.main.states[id + '.alive'].val && that.main.states[id + '.alive'].val !== 'null') {
                                $(this).find('.host-update-submit').show();
                                $(this).find('.host-update-hint-submit').show();
                                $(this).find('.hosts-version-installed').addClass('updateReady');
                                $(this).find('.hosts-version-available').addClass('hosts-version-available-updatable');
                            }
                        }
                    }
                    if (availableVersion) {
                        $(this).find('.hosts-version-available').html(availableVersion);
                    }
                });

                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
                that.initButtons();
            });
        }
    };

    this.init = function (update) {
        if (this.inited && !update) {
            return;
        }

        this.getHosts(function () {
            that._postInit();
        });
        if (!this.inited) {
            this.inited = true;
            this.main.subscribeObjects('system.host.*');
            this.main.subscribeStates('system.host.*');
        }
    };

    this.destroy = function () {
        if (this.inited) {
            this.inited = false;
            this.main.unsubscribeObjects('system.host.*');
            this.main.unsubscribeStates('system.host.*');
        }
    };

    this.addHost = function (obj) {
        var addr = null;
        // Find first non internal IP and use it as identifier
        if (obj.native.hardware && obj.native.hardware.networkInterfaces) {
            for (var eth in obj.native.hardware.networkInterfaces) {
                if (!obj.native.hardware.networkInterfaces.hasOwnProperty(eth)) continue;
                for (var num = 0; num < obj.native.hardware.networkInterfaces[eth].length; num++) {
                    if (!obj.native.hardware.networkInterfaces[eth][num].internal) {
                        addr = obj.native.hardware.networkInterfaces[eth][num].address;
                        break;
                    }
                }
                if (addr) break;
            }
        }
        if (addr) {
            this.list.push({name: obj.common.hostname, address: addr,        id: obj._id});
        } else {
            this.list.push({name: obj.common.hostname, address: '127.0.0.1', id: obj._id});
        }
    };

    this.getHosts = function (callback) {
        this.main.socket.emit('getForeignObjects', 'system.host.*',  'state', function (err, res) {
            for (var id in res) {
                if (!res.hasOwnProperty(id)) continue;
                that.main.objects[id] = res[id];
            }
            that.main.socket.emit('getForeignStates', 'system.host.*', function (err, res) {
                for (var id in res) {
                    if (!res.hasOwnProperty(id)) continue;
                    that.main.states[id] = res[id];
                }
                that.main.socket.emit('getForeignObjects', 'system.host.*', 'host', function (err, res) {
                    that.list = [];
                    for (var id in res) {
                        if (!res.hasOwnProperty(id)) continue;
                        var obj = res[id];

                        that.main.objects[id] = obj;

                        if (obj.type === 'host') {
                            that.addHost(obj);
                        }
                    }
                    main.initHostsList();
                    if (callback) callback();
                });
            });
        });
    };

    this.objectChange = function (id, obj, action) {
        // Update hosts
        if (id.match(/^system\.host\.[-\w]+$/)) {
            var found = false;
            var i;
            for (i = 0; i < this.list.length; i++) {
                if (this.list[i].id === id) {
                    found = true;
                    break;
                }
            }

            if (obj) {
                if (!found) this.list.push({id: id, address: obj.common.address ? obj.common.address[0] : '', name: obj.common.name});
            } else {
                if (found) this.list.splice(i, 1);
            }
            
            if (this.updateTimer) clearTimeout(this.updateTimer);

            this.updateTimer = setTimeout(function () {
                that.updateTimer = null;
                that._postInit();
            }, 200);
        }
    };

    this.stateChange = function (id, state) {
        if (id.match(/^system\.host\..+\.alive$/)) {
            id = id.substring(0, id.length - 6);
            if (state && state.val) {
                this.$tab.find('.hosts-led[data-host-id="' + id + '"]').removeClass('led-red').addClass('led-green');
            } else {
                this.$tab.find('.hosts-led[data-host-id="' + id + '"]').removeClass('led-green').addClass('led-red');
                this.$tab.find('.host-update-submit[data-host-id="' + id + '"]').hide();
                this.$tab.find('.host-update-hint-submit[data-host-id="' + id + '"]').hide();
                this.$tab.find('.host-restart-submit[data-host-id="' + id + '"]').hide();
                this.$tab.find('.hosts-version-available[data-host-id="' + id + '"]').removeClass('hosts-version-available-updatable');
            }
        } else if (id.match(/^system\.host\..+\.outputCount$/)) {
            id = id.substring(0, id.length - 12);

            this.$tab.find('.host-out[data-host-id="' + id + '"]').html('<span class="highlight">&#x21A6;' + state.val + '</span>');
        } else if (id.match(/^system\.host\..+\.inputCount$/)) {
            id = id.substring(0, id.length - 11);

            this.$tab.find('.host-in[data-host-id="' + id + '"]').html('<span class="highlight">&#x21A6;' + state.val + '</span>');
        }
    };
}



<|start_filename|>src-admin/src/Tabs/Certificates.js<|end_filename|>
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';

import CustomCheckbox from '../Components/CustomCheckbox';
import CustomInput from '../Components/CustomInput';
import HintComponent from '../Components/HintComponent';
import logo from '../assets/le.png';

import I18n from '@iobroker/adapter-react/i18n';

const styles = theme => ({
    tab: {
        width: '100%',
        minHeight: '100%'
    },
    column: {
        display: 'inline-block',
        verticalAlign: 'top',
        marginRight: 20
    },
    columnSettings: {
        width: 'calc(100% - 10px)'
    },
    logoWidth: {
        width: 200
    },
    fontSize: {
        '@media screen and (max-width: 460px)': {
            '& > *': {
                fontSize: '3.2vw',
            }
        }
    }

});

class Certificates extends Component {
    render() {
        const { classes, native, onChange, common: { readme } } = this.props;
        return <form className={classes.tab}>
            <img className={classes.logoWidth} alt='logo' src={logo} />
            <div className={`${classes.column} ${classes.columnSettings}`}>
                <div>
                    <CustomCheckbox
                        title='Use Lets Encrypt certificates'
                        attr='leEnabled'
                        className={classes.fontSize}
                        native={native}
                        onChange={onChange}
                    />
                    <HintComponent openLink={() =>
                        window.open(`${readme}#lets-encrypt-certificates`, '_blank')} />
                </div>
                <div style={native['leEnabled'] ? { display: 'block' } : { display: 'none' }}>
                    <CustomCheckbox
                        title='Use this instance for automatic update'
                        attr='leUpdate'
                        className={classes.fontSize}
                        native={native}
                        onChange={onChange}
                    />
                </div>
                <div style={native['leUpdate'] && native['leEnabled'] ? { display: 'block' } : { display: 'none' }}>
                    <CustomInput
                        title='Port to check the domain'
                        attr='lePort'
                        type='number'
                        style={{ marginTop: -1 }}
                        native={native}
                        onChange={onChange}
                    />
                </div>
            </div>
        </form>;
    }
}

Certificates.propTypes = {
    common: PropTypes.object.isRequired,
    native: PropTypes.object.isRequired,
    instance: PropTypes.number.isRequired,
    adapterName: PropTypes.string.isRequired,
    onError: PropTypes.func,
    onLoad: PropTypes.func,
    onChange: PropTypes.func,
    changed: PropTypes.bool,
    socket: PropTypes.object.isRequired,
};

export default withStyles(styles)(Certificates);


<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigCertificateSelect.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

import ConfigGeneric from './ConfigGeneric';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    }
});

class ConfigCertificateSelect extends ConfigGeneric {
    async componentDidMount() {
        super.componentDidMount();
        const value = ConfigGeneric.getValue(this.props.data, this.props.attr);
        let selectOptions = await this.props.socket.getCertificates();

        selectOptions = selectOptions
            .filter(el => this.props.attr.toLowerCase().endsWith(el.type))
            .map(el => ({label: el.name, value: el.name}));

        selectOptions.unshift({label: ConfigGeneric.NONE_LABEL, value: ConfigGeneric.NONE_VALUE});

        this.setState({value, selectOptions});
    }

    renderItem(error, disabled, defaultValue) {
        if (!this.state.selectOptions) {
            return null;
        }
        // eslint-disable-next-line
        const item = this.state.selectOptions?.find(item => item.value === this.state.value);

        return <FormControl className={this.props.classes.fullWidth}>
            <InputLabel shrink>{this.getText(this.props.schema.label)}</InputLabel>
            <Select
                error={!!error}
                displayEmpty
                disabled={!!disabled}
                value={this.state.value}
                renderValue={val => this.getText(item?.label, this.props.schema.noTranslation !== false)}
                onChange={e =>
                    this.setState({ value: e.target.value }, () =>
                        this.onChange(this.props.attr, this.state.value))}
            >
                {this.state.selectOptions?.map(item =>
                    <MenuItem
                        key={item.value}
                        value={item.value}
                        style={item.value === ConfigGeneric.NONE_VALUE ? { opacity: 0.5 } : {}}>{
                            this.getText(item.label, this.props.schema.noTranslation !== false)
                        }</MenuItem>)}
            </Select>
            {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
        </FormControl>;
    }
}

ConfigCertificateSelect.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigCertificateSelect);

<|start_filename|>src-admin/src/Components/CustomModal.js<|end_filename|>
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import { Dialog } from '@material-ui/core';
import PropTypes from 'prop-types';

const useStyles = makeStyles((theme) => ({
    modalContentWrapper: {
        margin: '20px 0',
        padding: '0 35px',
        // background: '#f6f6f6',
        overflowX: 'hidden',
        minWidth: '320px'
    },
    modalButtonBlock: {
        display: 'flex',
        justifyContent: 'flex-end',
        marginTop: '20px',
        flexFlow: 'wrap',
        borderTop: '1px solid silver'
    },
    modalButtonBlockTwo: {
        justifyContent: 'space-around',
        flexFlow: 'wrap-reverse',
        '& button': {
            margin: '5px'
        }
    },
    modalWrapper: {
        position: 'relative',
        '[class*="MuiPaper-root MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthXl MuiPaper-elevation24 MuiPaper-rounded"]': {
            background: '#f6f6f6'
        }
    },
    close: {
        position: 'absolute',
        right: '8px',
        top: '6px',
        width: '32px',
        height: '32px',
        opacity: '0.9',
        cursor: 'pointer',
        transition: 'all 0.6s ease',
        '&:hover': {
            transform: 'rotate(90deg)'
        },
        '&:before': {
            position: 'absolute',
            left: '15px',
            content: '""',
            height: '33px',
            width: '4px',
            backgroundColor: '#ff4f4f',
            transform: 'rotate(45deg)'
        },
        '&:after': {
            position: 'absolute',
            left: '15px',
            content: '""',
            height: '33px',
            width: '4px',
            backgroundColor: '#ff4f4f',
            transform: 'rotate(-45deg)'
        },
    },
    '@media screen and (max-width: 460px)': {
        modalContentWrapper: {
            minWidth: 'auto'
        }
    }

}));

const CustomModal = ({ open, close, children, titleOk, titleCancel, buttonClick }) => {
    const classes = useStyles();
    return <Dialog
        open={open}
        maxWidth="md"
        disableEscapeKeyDown={true}
        onClose={close}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
        className={classes.modalWrapper || ''}
    >
        <div className={classes.modalContentWrapper}>
            <div className={classes.close} onClick={close} />
            {children}
            <div className={`${classes.modalButtonBlock} ${titleOk ? classes.modalButtonBlockTwo : ''}`}>
                {titleOk && <Button variant="contained" onClick={buttonClick} color="primary" autoFocus={}>{titleOk}</Button>}
                {titleCancel && <Button variant="contained" onClick={close}>{titleCancel}</Button>}
            </div>
        </div>
    </Dialog>;
}

CustomModal.defaultProps = {
    open: false,
    buttonClick: () => { },
    close: () => { }
};

CustomModal.propTypes = {
    open: PropTypes.bool,
    close: PropTypes.func,
    children: PropTypes.any,
    titleOk: PropTypes.string,
    titleCancel: PropTypes.string,
    buttonClick: PropTypes.func
};

export default CustomModal;

<|start_filename|>src-rx/src/helpers/hooks/useStateLocal.js<|end_filename|>
import * as React from 'react';

export function useStateLocal(defaultValue, key) {
    const [state, setState] = React.useState(
        window.localStorage.getItem(key) ?
            JSON.parse(window.localStorage.getItem(key))
            :
            defaultValue
    );

    const eventsToInstall = newValue => {
        window.localStorage.setItem(key, JSON.stringify(newValue));
        setState(newValue);
    };

    return [state, eventsToInstall, !!window.localStorage.getItem(key)];
}

<|start_filename|>src-rx/src/tabs/Hosts.js<|end_filename|>
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import semver from 'semver';
import clsx from 'clsx';

import withWidth from '@material-ui/core/withWidth';
import { withStyles } from '@material-ui/core/styles';

import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import { InputAdornment, LinearProgress, TextField } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';

import RefreshIcon from '@material-ui/icons/Refresh';
import ViewListIcon from '@material-ui/icons/ViewList';
import ViewModuleIcon from '@material-ui/icons/ViewModule';
import CloseIcon from '@material-ui/icons/Close';
import BugReportIcon from '@material-ui/icons/BugReport';
import InfoIcon from '@material-ui/icons/Info';
import WarningIcon from '@material-ui/icons/Warning';
import ErrorIcon from '@material-ui/icons/Error';

import TabContainer from '../components/TabContainer';
import TabContent from '../components/TabContent';
import TabHeader from '../components/TabHeader';
import { useStateLocal } from '../helpers/hooks/useStateLocal';
import HostCard from '../components/Hosts/HostCard';
import HostRow from '../components/Hosts/HostRow';
import HostEdit from '../components/Hosts/HostEdit';
import { JsControllerDialogFunc } from '../dialogs/JsControllerDialog';
import Utils from '../Utils';
import BaseSettingsDialog from '../dialogs/BaseSettingsDialog';
import SlowConnectionWarningDialog from '../dialogs/SlowConnectionWarningDialog';
import AdapterUpdateDialog from '../dialogs/AdapterUpdateDialog';

const styles = theme => ({
    grow: {
        flexGrow: 1
    },
    cards: {
        display: 'flex',
        flexFlow: 'wrap',
        justifyContent: 'center',
    },
    tabHeaderWrapper: {
        height: 30,
        display: 'flex',
        margin: 7,
    },
    tabHeaderFirstItem: {
        width: 312,
        paddingLeft: 30,
        fontSize: 14,
        fontWeight: 600,
        alignSelf: 'center',
    },
    tabHeaderItem: {
        flex: 1,
        fontSize: 14,
        fontWeight: 600,
        alignSelf: 'center'
    },
    tabHeaderItemButton: {
        width: 144,
        fontSize: 14,
        fontWeight: 600,
        alignSelf: 'center'
    },
    widthButtons: {
        width: 240,
    },
    tabFlex: {
        display: 'flex',
        flex: 1,
        padding: '0 10px'
    },
    bold: {
        fontWeight: 'bold'
    },
    wrapperInfo: {
        display: 'flex',
        flexFlow: 'wrap',
        width: '100%'
    },
    marginRight: {
        marginRight: 'auto'
    },
    wrapperBlockItem: {
        display: 'flex',
        flexFlow: 'nowrap',
        whiteSpace: 'nowrap',
        margin: 10,
    },
    ul: {
        paddingTop: 0,
        paddingBottom: 0,
        paddingLeft: 16,
        paddingRight: 8
    },
    nowrap: {
        display: 'flex',
        flexFlow: 'nowrap',
        flex: 1,
        whiteSpace: 'nowrap',
        marginRight: 5
    },
    '@media screen and (max-width: 1100px)': {
        hidden1100: {
            display: 'none !important'
        },
    },
    '@media screen and (max-width: 800px)': {
        hidden800: {
            display: 'none !important'
        },
    },
    '@media screen and (max-width: 600px)': {
        hidden600: {
            display: 'none !important'
        },
    },
});

let wordCache = {};

const formatInfo = {
    'Uptime': Utils.formatSeconds,
    'System uptime': Utils.formatSeconds,
    'RAM': Utils.formatRam,
    'Speed': Utils.formatSpeed,
    'Disk size': Utils.formatBytes,
    'Disk free': Utils.formatBytes
};

const getHostDescriptionAll = (id, t, classes, hostsData) => {
    const hostData = hostsData ? hostsData[id] : null;
    if (!hostData) {
        return [<Skeleton />];
    }
    if (typeof hostData === 'string') {
        return hostData;
    }
    return [
        <ul className={classes.ul}>
            {
                hostData && typeof hostData === 'object' && Object.keys(hostData).map(value =>
                    <li key={value}>
                        {hostData && typeof hostData === 'object' ?
                            <span className={classes.black}>
                                <span className={classes.bold}>{t(value)}: </span>
                                {(formatInfo[value] ? formatInfo[value](hostData[value], t) : hostData[value] || '--')}
                            </span>
                            :
                            <Skeleton />
                        }
                    </li>)
            }
        </ul>,
        <div className={classes.wrapperInfo}>
            <div className={classes.marginRight}>
                {hostData && typeof hostData === 'object' && Object.keys(hostData).map((value, idx) => idx < 5 &&
                    <div
                        className={classes.wrapperBlockItem} key={value}>
                        {hostData && typeof hostData === 'object' ?
                            <>
                                <span className={clsx(classes.bold, classes.nowrap)}>{t(value)}: </span>
                                {(formatInfo[value] ? formatInfo[value](hostData[value], t) : hostData[value] || '--')}
                            </>
                            :
                            <Skeleton />
                        }
                    </div>)}
            </div>
            <div className={classes.marginRight}>
                {hostData && typeof hostData === 'object' && Object.keys(hostData).map((value, idx) => idx > 4 && idx < 10 &&
                    <div
                        className={classes.wrapperBlockItem} key={value}>
                        {hostData && typeof hostData === 'object' ?
                            <>
                                <span className={clsx(classes.bold, classes.nowrap)}>{t(value)}: </span>
                                {(formatInfo[value] ? formatInfo[value](hostData[value], t) : hostData[value] || '--')}
                            </>
                            :
                            <Skeleton />
                        }
                    </div>)}
            </div>
            <div className={classes.marginRight}>
                {hostData && typeof hostData === 'object' && Object.keys(hostData).map((value, idx) => idx > 10 &&
                    <div
                        className={classes.wrapperBlockItem} key={value}>
                        {hostData && typeof hostData === 'object' ?
                            <>
                                <span className={clsx(classes.bold, classes.nowrap)}>{t(value)}: </span>
                                {(formatInfo[value] ? formatInfo[value](hostData[value], t) : hostData[value] || '--')}
                            </>
                            :
                            <Skeleton />
                        }
                    </div>)}
            </div>
        </div>
    ];
}


const getLogLevelIcon = (level) => {
    if (level === 'debug') {
        return <BugReportIcon />;
    } else if (level === 'info') {
        return <InfoIcon />;
    } else if (level === 'warn') {
        return <WarningIcon />;
    } else if (level === 'error') {
        return <ErrorIcon />;
    }
    return null;
}
// every tab should get their data itself from server
const Hosts = ({
    classes,
    disabled,
    socket,
    currentHost,
    expertMode,
    executeCommand,
    systemConfig,
    navigate,
    themeName,
    lang,
    hostsWorker,
    showAdaptersWarning,
    ...props
}) => {
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const t = (word, arg1) => {
        if (arg1 !== undefined && !wordCache[`${word} ${arg1}`]) {
            wordCache[`${word} ${arg1}`] = props.t(word, arg1);
        } else if (!wordCache[word]) {
            wordCache[word] = props.t(word);
        }
        return arg1 !== undefined ? wordCache[`${word} ${arg1}`] : wordCache[word];
    }

    const [hosts, setHosts] = useState([]);
    const [alive, setAlive] = useState({});
    const [repository, setRepository] = useState({});
    const [hostsData, setHostsData] = useState({});
    const [refresh, setRefresh] = useState(false);
    const [viewMode, setViewMode] = useStateLocal(false, 'Hosts.viewMode');
    const [filterText, setFilterText] = useStateLocal('', 'Hosts.filterText');
    const [showSlowConnectionWarning, setShowSlowConnectionWarning] = useState(false);
    const [readTimeoutMs, setReadTimeoutMs] = useState(SlowConnectionWarningDialog.getReadTimeoutMs());

    const getHostsData = (hosts, _alive) => {
        const promises = hosts.map(obj => {
            if (_alive[obj._id]) {
                return socket.getHostInfo(obj._id, null, readTimeoutMs)
                    .catch(error => {
                        console.error('Cannot get getHostInfo:' + error);
                        error.toString().includes('timeout') && setShowSlowConnectionWarning(true);
                        return error;
                    })
                    .then(data =>
                        ({ id: obj._id, data }));
            } else {
                return { id: obj._id, data: 'offline' };
            }
        });

        return new Promise(resolve =>
            Promise.all(promises)
                .then(results => {
                    const _hostsData = {};
                    results.forEach(res => _hostsData[res.id] = res.data);
                    resolve(_hostsData);
                }));
    };

    const updateHosts = (hostId, state) => {
        setHosts(prevHostsArray => {
            const newHosts = JSON.parse(JSON.stringify(prevHostsArray));
            if (Array.isArray(hostId)) {
                hostId.forEach(event => {
                    const elementFind = prevHostsArray.find(host => host._id === event.id);
                    if (elementFind) {
                        const index = prevHostsArray.indexOf(elementFind);
                        if (event.obj) {
                            newHosts[index] = event.obj;
                        } else {
                            newHosts.splice(index, 1);
                        }
                    } else {
                        newHosts.push(event.obj);
                    }
                });
            } else {
                const elementFind = prevHostsArray.find(({ _id }) => _id === hostId);
                if (elementFind) {
                    const index = prevHostsArray.indexOf(elementFind);
                    if (state) {
                        newHosts[index] = state;
                    } else {
                        newHosts.splice(index, 1);
                    }
                } else {
                    newHosts.push(state);
                }
            }

            filterText && newHosts.length <= 2 && setFilterText('');

            return newHosts;
        });
    }

    const readInfo = () => {
        return socket.getHosts(true, false, readTimeoutMs)
            .then(hostsArray => socket.getRepository(currentHost, { update: false }, false, readTimeoutMs)
                .then(async repositoryProm => {
                    const _alive = JSON.parse(JSON.stringify(alive));

                    for (let h = 0; h < hostsArray.length; h++) {
                        let aliveValue = await socket.getState(`${hostsArray[h]._id}.alive`);
                        _alive[hostsArray[h]._id] = !aliveValue ? false : !!aliveValue.val;
                    }

                    setAlive(_alive);

                    setRepository(repositoryProm);
                    setHosts(hostsArray);
                    filterText && hostsArray.length <= 2 && setFilterText('');
                    const hostDataObj = await getHostsData(hostsArray, _alive);
                    setHostsData(hostDataObj);

                    // simulation
                    // setTimeout(() => setShowSlowConnectionWarning(true), 5000);
                })
                .catch(e => {
                    window.alert('Cannot getRepository: ' + e);
                    e.toString().includes('timeout') && setShowSlowConnectionWarning(true);
                }));
    };

    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(() => {
        readInfo()
            .then(() =>
                hostsWorker.registerHandler(updateHosts));

        return () => hostsWorker.unregisterHandler(updateHosts);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [refresh]);

    const getAllArrayHosts = useMemo(() => hosts.map(({
        _id,
        common: { name, icon, color, title, installedVersion },
        native: { os: { platform } },
    }, idx
    ) => ({
        renderCard: viewMode ? <HostCard
            systemConfig={systemConfig}
            key={_id}
            setEditDialog={() => setEditDialog({ index: idx, dialogName: name })}
            setBaseSettingsDialog={() => setBaseSettingsDialog({ index: idx, dialogName: name })}
            hostsWorker={hostsWorker}
            expertMode={expertMode}
            socket={socket}
            name={name}
            getLogLevelIcon={getLogLevelIcon}
            alive={alive[_id]}
            color={color}
            image={icon}
            title={title}
            os={platform}
            openHostUpdateDialog={() => openHostUpdateDialog(name)}
            description={getHostDescriptionAll(_id, t, classes, hostsData)[0]}
            available={repository['js-controller']?.version || '-'}
            executeCommandRemove={() => executeCommand(`host remove ${name}`)}
            dialogUpgrade={() => JsControllerDialogFunc(socket, _id)}
            currentHost={currentHost === _id}
            installed={installedVersion}
            events={'- / -'}
            t={t}
            _id={_id}
            showAdaptersWarning={showAdaptersWarning}
        /> : null,
        renderRow: !viewMode ? <HostRow
            systemConfig={systemConfig}
            key={_id}
            setEditDialog={() => setEditDialog({
                index: idx,
                dialogName: name
            })}
            setBaseSettingsDialog={() => setBaseSettingsDialog({
                index: idx,
                dialogName: name
            })}
            hostsWorker={hostsWorker}
            expertMode={expertMode}
            socket={socket}
            name={name}
            alive={alive[_id]}
            color={color}
            image={icon}
            title={title}
            os={platform}
            getLogLevelIcon={getLogLevelIcon}
            openHostUpdateDialog={() => openHostUpdateDialog(name)}
            executeCommandRemove={() => executeCommand(`host remove ${name}`)}
            dialogUpgrade={() => JsControllerDialogFunc(socket, _id)}
            currentHost={currentHost === _id}
            description={getHostDescriptionAll(_id, t, classes, hostsData)[1]}
            available={repository['js-controller']?.version || '-'}
            installed={installedVersion}
            events={'- / -'}
            t={t}
            _id={_id}
            showAdaptersWarning={showAdaptersWarning}
        /> : null,
        name
    })
        // eslint-disable-next-line react-hooks/exhaustive-deps
    ), [hosts, alive, repository, hostsData, classes, expertMode, viewMode]);

    const [editDialog, setEditDialog] = useState({ index: 0, dialogName: '' });

    const [baseSettingsDialog, setBaseSettingsDialog] = useState({ index: 0, dialogName: '' });

    const getPanels = useCallback(() => {
        const items = getAllArrayHosts.filter(el => filterText ? el.name.toLowerCase().includes(filterText.toLowerCase()) : true).map(el => viewMode ? el.renderCard : el.renderRow);
        return items.length ? items : t('All items are filtered out');
    }, [getAllArrayHosts, t, filterText, viewMode,]);

    const baseSettingsSettingsDialog = () => {
        if (!baseSettingsDialog.dialogName) {
            return null;
        }
        return <BaseSettingsDialog
            currentHost={baseSettingsDialog.dialogName}
            hosts={hosts}
            themeName={themeName}
            currentHostName={baseSettingsDialog.dialogName}
            key="base"
            onClose={() => setBaseSettingsDialog({
                index: 0,
                dialogName: ''
            })}
            lang={lang}
            // showAlert={(message, type) => this.showAlert(message, type)}
            socket={socket}
            // currentTab={currentTab}
            t={t}
        />
    };

    const renderEditObjectDialog = () => {
        if (!editDialog.dialogName) {
            return null;
        }
        return <HostEdit
            obj={hosts[editDialog.index]}
            socket={socket}
            dialogName={editDialog.dialogName}
            t={t}
            expertMode={expertMode}
            onClose={obj => {
                setEditDialog({
                    index: 0,
                    dialogName: ''
                })
                if (obj) {
                    socket.setObject(obj._id, obj)
                        .then(_ => setRefresh((el) => !el))
                        .catch(e => alert('Cannot write object: ' + e));
                }
            }}
        />
    };

    const renderSlowConnectionWarning = () => {
        if (!showSlowConnectionWarning) {
            return null;
        } else {
            return <SlowConnectionWarningDialog
                readTimeoutMs={readTimeoutMs}
                t={t}
                onClose={async _readTimeoutMs => {
                    setShowSlowConnectionWarning(false);
                    if (_readTimeoutMs) {
                        setReadTimeoutMs(_readTimeoutMs);
                        await readInfo();
                    }
                }}
            />;
        }
    }

    const [hostUpdateDialog, setHostUpdateDialog] = useState(false);
    const [hostUpdate, setHostUpdate] = useState(null);

    const closeHostUpdateDialog = (cb) => {
        setHostUpdateDialog(false);
        setHostUpdate(null);
        cb && cb();
    }

    const openHostUpdateDialog = (hostName, cb) => {
        setHostUpdateDialog(true);
        setHostUpdate(hostName);
        cb && cb();
    }

    const getNews = (value, all = false) => {
        const adapter = repository['js-controller'];
        const installed = hosts.find(el => el.common.name === hostUpdate).common.installedVersion;
        const news = [];

        if (installed && adapter && adapter.news) {
            Object.keys(adapter.news).forEach(version => {
                try {
                    if (semver.gt(version, installed) || all) {
                        news.push({
                            version: version,
                            news: adapter.news[version][lang] || adapter.news[version].en
                        });
                    }
                } catch (e) {
                    // ignore it
                    console.warn(`Cannot compare "${version}" and "${installed}"`);
                }
            });
        }

        return news;
    }
    const hostUpdateDialogCb = () => {
        if (!hostUpdateDialog) {
            return null;
        } else {
            return <AdapterUpdateDialog
                open={hostUpdateDialog}
                adapter={hostUpdate}
                adapterObject={repository['js-controller']}
                t={props.t}
                textUpdate={t('Show instructions')}
                rightDependencies
                news={getNews()}
                onUpdate={() =>
                    closeHostUpdateDialog(() =>
                        JsControllerDialogFunc(socket, hostUpdate))}
                onClose={() => closeHostUpdateDialog()}
            />
        }
    }

    if (!hosts.length) {
        return <LinearProgress />;
    }

    return <TabContainer>
        {renderEditObjectDialog()}
        {baseSettingsSettingsDialog()}
        {renderSlowConnectionWarning()}
        {hostUpdateDialogCb()}
        <TabHeader>
            <Tooltip title={t('Show / hide List')}>
                <IconButton onClick={() => setViewMode(!viewMode)}>
                    {viewMode ? <ViewModuleIcon /> : <ViewListIcon />}
                </IconButton>
            </Tooltip>
            <Tooltip title={t('Reload')}>
                <IconButton onClick={() => setRefresh(el => !el)}>
                    <RefreshIcon />
                </IconButton>
            </Tooltip>
            <div className={classes.grow} />
            {hosts.length > 2 ? <TextField
                label={t('Filter')}
                style={{ margin: '5px 0' }}
                value={filterText}
                onChange={event => setFilterText(event.target.value)}
                InputProps={{
                    endAdornment: (
                        filterText ? <InputAdornment position="end">
                            <IconButton
                                size="small"
                                onClick={() => setFilterText('')}
                            >
                                <CloseIcon />
                            </IconButton>
                        </InputAdornment> : null
                    ),
                }}
            /> : null}
            <div className={classes.grow} />
        </TabHeader>
        <TabContent overflow="auto">
            <div className={viewMode ? classes.cards : ''}>
                {!viewMode &&
                    <div className={classes.tabHeaderWrapper}>
                        <div className={classes.tabHeaderFirstItem}>
                            {t('Name:')}
                        </div>
                        <div className={classes.tabFlex}>
                            {/*<div className={clsx(classes.tabHeaderItem, classes.hidden600)}>{t('Title:')}</div>*/}
                            <div className={clsx(classes.tabHeaderItem, classes.hidden800)}>CPU</div>
                            <div className={clsx(classes.tabHeaderItem, classes.hidden800)}>RAM</div>
                            <div className={clsx(classes.tabHeaderItem, classes.hidden800)}>{t('Uptime')}</div>
                            <div className={clsx(classes.tabHeaderItem, classes.hidden1100)}>{t('Available')}</div>
                            <div className={clsx(classes.tabHeaderItem, classes.hidden1100)}>{t('Installed')}</div>
                            <div className={clsx(classes.tabHeaderItem, classes.hidden600)}>{t('Events')}</div>
                            <div className={clsx(classes.tabHeaderItemButton, expertMode && classes.widthButtons)} />
                        </div>
                    </div>}
                {getPanels()}
            </div>
        </TabContent>
    </TabContainer>;
}

Hosts.propTypes = {
    t: PropTypes.func,
    expertMode: PropTypes.bool,
    socket: PropTypes.object,
    systemConfig: PropTypes.object,
    hostsWorker: PropTypes.object,
    showAdaptersWarning: PropTypes.func,
};

export default withWidth()(withStyles(styles)(Hosts));

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigSetState.js<|end_filename|>
import React from "react";
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import Button from '@material-ui/core/Button';

import I18n from '@iobroker/adapter-react/i18n';
import Icon from '@iobroker/adapter-react/Components/Icon';

import ConfigGeneric from './ConfigGeneric';
import IconWarning from "@material-ui/icons/Warning";
import IconError from "@material-ui/icons/Error";
import IconInfo from "@material-ui/icons/Info";
import ConfirmDialog from "@iobroker/adapter-react/Dialogs/Confirm";

const styles = theme => ({
    fullWidth: {
        width: '100%'
    },
    icon: {
        width: 24,
        height: 24,
        marginRight: 4
    }
});

class ConfigSetState extends ConfigGeneric {
    async _onClick() {
        let val = this.props.schema.val;
        if (typeof val === 'string' && val.includes('${')) {
            val = this.getPattern(val);
            const obj = await this.props.socket.getObject(this.props.schema.id);
            if (obj?.common?.type === 'number') {
                val = parseFloat(val);
            } else if (obj?.common?.type === 'boolean') {
                val = val === 'true' || val === true || val === '1' || val === 1;
            }
        }

        try {
            await this.props.socket.setState(this.props.schema.id, {val, ack: !!this.props.schema.ack});
            this.props.schema.okText && window.alert(this.getText(this.props.schema.okText));
        } catch (e) {
            if (this.props.schema.error && this.props.schema.error[e.toString()]) {
                window.alert(this.getText(this.props.schema.error[e.toString()]));
            } else {
                window.alert(I18n.t(e.toString()) || I18n.t('Error'));
            }
        }
    }

    renderConfirmDialog() {
        if (!this.state.confirmDialog) {
            return null;
        }
        const confirm = this.state.confirmData || this.props.schema.confirm;
        let icon = null;
        if (confirm.type === 'warning') {
            icon = <IconWarning />;
        } else if (confirm.type === 'error') {
            icon = <IconError />;
        } else if (confirm.type === 'info') {
            icon = <IconInfo />;
        }

        return <ConfirmDialog
            title={ this.getText(confirm.title) || I18n.t('Please confirm') }
            text={ this.getText(confirm.text) }
            ok={ this.getText(confirm.ok) || I18n.t('Ok') }
            cancel={ this.getText(confirm.cancel) || I18n.t('Cancel') }
            icon={icon}
            onClose={isOk =>
                this.setState({ confirmDialog: false}, async () =>
                    isOk && (await this._onClick()))
            }
        />;
    }

    renderItem(error, disabled, defaultValue) {
        return <Button
            variant={this.props.schema.variant || undefined}
            color={this.props.schema.color || undefined}
            className={this.props.classes.fullWidth}
            disabled={disabled}
            onClick={async () => {
                if (this.props.schema.confirm) {
                    this.setState({confirmDialog: true});
                } else {
                    await this._onClick();
                }
            }}
        >
            {this.props.schema.icon ? <Icon src={this.props.schema.icon} className={this.props.classes.icon}/> : null}
            {this.getText(this.props.schema.label, this.props.schema.noTranslation)}
        </Button>;
    }
}

ConfigSetState.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
    adapterName: PropTypes.string,
    instance: PropTypes.number,
    commandRunning: PropTypes.bool,
    onCommandRunning: PropTypes.func,
};

export default withStyles(styles)(ConfigSetState);

<|start_filename|>src-rx/src/components/JsonConfigComponent/ConfigChip.js<|end_filename|>
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';

import ConfigGeneric from './ConfigGeneric';
import ChipInput from 'material-ui-chip-input';

const styles = theme => ({
    fullWidth: {
        width: '100%'
    }
});

class ConfigLanguage extends ConfigGeneric {
    componentDidMount() {
        super.componentDidMount();
        const { data, attr } = this.props;
        const value = ConfigGeneric.getValue(data, attr);
        this.setState({ value: value || [] });
    }

    renderItem(error, disabled, defaultValue) {
        const { attr,schema } = this.props;
        const { value } = this.state;
        return <FormControl className={this.props.classes.fullWidth}>
            <ChipInput
                value={value}
                disabled={!!disabled}
                label={this.getText(schema.label)}
                error={!!error}
                onAdd={chip => {
                        const newValue = JSON.parse(JSON.stringify(value));
                        newValue.push(chip);
                        this.setState({ value: newValue, prevValue: '' }, () => {
                            this.onChange(attr, newValue);
                        })
                }}
                onDelete={(chip, index) => {
                    const newValue = JSON.parse(JSON.stringify(value));
                    newValue.splice(index, 1);;
                    this.setState({ value: newValue, prevValue: '' }, () => {
                        this.onChange(attr, newValue);
                    })
                }}
            />
            {this.props.schema.help ? <FormHelperText>{this.renderHelp(this.props.schema.help, this.props.schema.helpLink, this.props.schema.noTranslation)}</FormHelperText> : null}
        </FormControl>;
    }
}

ConfigLanguage.propTypes = {
    socket: PropTypes.object.isRequired,
    themeType: PropTypes.string,
    themeName: PropTypes.string,
    style: PropTypes.object,
    className: PropTypes.string,
    data: PropTypes.object.isRequired,
    schema: PropTypes.object,
    onError: PropTypes.func,
    onChange: PropTypes.func,
};

export default withStyles(styles)(ConfigLanguage);

<|start_filename|>src-rx/src/components/Enums/DragObjectBrowser.js<|end_filename|>
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';

import { useDrag } from 'react-dnd'
import { getEmptyImage } from 'react-dnd-html5-backend';

import ObjectBrowser from '../../components/ObjectBrowser';

import Card from '@material-ui/core/Card';
import Icon from '@material-ui/core/Icon';

import ListIcon from '@material-ui/icons/List';

const DragObjectBrowser = props => {
    let browserProps = props;

    const [wrapperState, setWrapperState] = useState({DragWrapper: null});

    useEffect(() => {
        const DragWrapper = props => {
            let onDragEnd = (item, monitor) => {
                const dropResult = monitor.getDropResult();
                if (item.data && dropResult) {
                    if (item.data.obj) {
                        browserProps.addItemToEnum(item.data.obj._id, dropResult.enumId);
                    } else {
                        // all children ??
                        window.alert('TODO: Add all direct children of ' + item.data.id);
                    }
                }
            };

            let dragSettings = {
                type: 'object',
                end: onDragEnd,
            };

            dragSettings.item = {
                data: props.item.data,
                preview: (props.item.data && props.item.data.obj ? <Card
                    key={props.item.data.obj._id}
                    variant="outlined"
                    className={browserProps.classesParent.enumGroupMember}
                >
                    {
                        props.item.data.obj.common?.icon
                            ?
                            <Icon
                                className={ browserProps.classesParent.icon }
                                src={props.item.data.obj.common.icon}
                            />
                            :
                            <ListIcon className={browserProps.classesParent.icon} />
                    }
                    {props.item.data.obj.common?.name ? browserProps.getName(props.item.data.obj.common?.name) : props.item.data.obj._id}
                </Card> : null)
            };

            const [{ isDragging }, dragRef, preview] = useDrag(dragSettings);

            useEffect(() => {
                preview(getEmptyImage(), { captureDraggingState: true });
                // eslint-disable-next-line react-hooks/exhaustive-deps
            }, []);

            return <div
                key={props.item.data.id}
                className={props.className || ''}
                ref={dragRef}
                style={{ backgroundColor: isDragging ? 'rgba(100,152,255,0.1)' : undefined }}
            >
                {props.children}
            </div>;
        }
        setWrapperState({DragWrapper: DragWrapper});
    // eslint-disable-next-line
    }, []); // react-hooks/exhaustive-deps

    return wrapperState ? <ObjectBrowser
        t={props.t}
        socket={props.socket}
        types={['state', 'channel', 'device']}
        lang={props.lang}
        dragEnabled
        DragWrapper={wrapperState.DragWrapper}
        levelPadding={10}
    /> : null;
}

DragObjectBrowser.propTypes = {
    addItemToEnum: PropTypes.func,
    getName: PropTypes.func,
    classesParent: PropTypes.object,
    t: PropTypes.func,
    lang: PropTypes.string,
    socket: PropTypes.object,
};

export default DragObjectBrowser;

<|start_filename|>src/materialize-css/js/tapTarget.js<|end_filename|>
(function ($) {
  'use strict';

  let _defaults = {
    onOpen: undefined,
    onClose: undefined,
  };


  /**
   * @class
   *
   */
  class FeatureDiscovery extends Component {
    /**
     * Construct FeatureDiscovery instance
     * @constructor
     * @param {Element} el
     * @param {Object} options
     */
    constructor(el, options) {
      super(FeatureDiscovery, el, options);

      this.el.M_FeatureDiscovery = this;

      /**
       * Options for the select
       * @member FeatureDiscovery#options
       * @prop {Function} onOpen - Callback function called when feature discovery is opened
       * @prop {Function} onClose - Callback function called when feature discovery is closed
       */
      this.options = $.extend({}, FeatureDiscovery.defaults, options);

      this.isOpen = false;

      // setup
      this.$origin = $('#' + this.$el.attr('data-target'));
      this._setup();

      this._calculatePositioning();
      this._setupEventHandlers();
    }

    static get defaults() {
      return _defaults;
    }

    static init(els, options) {
      return super.init(this, els, options);
    }

    /**
     * Get Instance
     */
    static getInstance(el) {
      let domElem = !!el.jquery ? el[0] : el;
      return domElem.M_FeatureDiscovery;
    }

    /**
     * Teardown component
     */
    destroy() {
      this._removeEventHandlers();
      this.el.FeatureDiscovery = undefined;
    }

    /**
     * Setup Event Handlers
     */
    _setupEventHandlers() {
      this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
      this._handleTargetClickBound = this._handleTargetClick.bind(this);
      this._handleOriginClickBound = this._handleOriginClick.bind(this);

      this.el.addEventListener('click', this._handleTargetClickBound);
      this.originEl.addEventListener('click', this._handleOriginClickBound);

      // Resize
      let throttledResize = M.throttle(this._handleResize, 200);
      this._handleThrottledResizeBound = throttledResize.bind(this);

      window.addEventListener('resize', this._handleThrottledResizeBound);
    }

    /**
     * Remove Event Handlers
     */
    _removeEventHandlers() {
      this.el.removeEventListener('click', this._handleTargetClickBound);
      this.originEl.removeEventListener('click', this._handleOriginClickBound);
      window.removeEventListener('resize', this._handleThrottledResizeBound);
    }

    /**
     * Handle Target Click
     * @param {Event} e
     */
    _handleTargetClick(e) {
      this.open();
    }

    /**
     * Handle Origin Click
     * @param {Event} e
     */
    _handleOriginClick(e) {
      this.close();
    }

    /**
     * Handle Resize
     * @param {Event} e
     */
    _handleResize(e) {
      this._calculatePositioning();
    }

    /**
     * Handle Resize
     * @param {Event} e
     */
    _handleDocumentClick(e) {
      if (!$(e.target).closest('.tap-target-wrapper').length) {
        this.close();
        e.preventDefault();
        e.stopPropagation();
      }
    }

    /**
     * Setup feature discovery
     */
    _setup() {
      // Creating tap target
      this.wrapper = this.$el.parent()[0];
      this.waveEl = $(this.wrapper).find('.tap-target-wave')[0];
      this.originEl = $(this.wrapper).find('.tap-target-origin')[0];
      this.contentEl = this.$el.find('.tap-target-content')[0];

      // Creating wrapper
      if (!$(this.wrapper).hasClass('.tap-target-wrapper')) {
        this.wrapper = document.createElement('div');
        this.wrapper.classList.add('tap-target-wrapper');
        this.$el.before($(this.wrapper));
        this.wrapper.append(this.el);
      }

      // Creating content
      if (!this.contentEl) {
        this.contentEl = document.createElement('div');
        this.contentEl.classList.add('tap-target-content');
        this.$el.append(this.contentEl);
      }

      // Creating foreground wave
      if (!this.waveEl) {
        this.waveEl = document.createElement('div');
        this.waveEl.classList.add('tap-target-wave');

        // Creating origin
        if (!this.originEl) {
          this.originEl = this.$origin.clone(true, true);
          this.originEl.addClass('tap-target-origin');
          this.originEl.removeAttr('id');
          this.originEl.removeAttr('style');
          this.originEl = this.originEl[0];
          this.waveEl.append(this.originEl);
        }

        this.wrapper.append(this.waveEl);
      }
    }

    /**
     * Calculate positioning
     */
    _calculatePositioning() {
      // Element or parent is fixed position?
      let isFixed = this.$origin.css('position') === 'fixed';
      if (!isFixed) {
        let parents = this.$origin.parents();
        for (let i = 0; i < parents.length; i++) {
          isFixed = $(parents[i]).css('position') == 'fixed';
          if (isFixed) {
            break;
          }
        }
      }

      // Calculating origin
      let originWidth = this.$origin.outerWidth();
      let originHeight = this.$origin.outerHeight();
      let originTop = isFixed ? this.$origin.offset().top - M.getDocumentScrollTop() : this.$origin.offset().top;
      let originLeft = isFixed ? this.$origin.offset().left - M.getDocumentScrollLeft() : this.$origin.offset().left;

      // Calculating screen
      let windowWidth = window.innerWidth;
      let windowHeight = window.innerHeight;
      let centerX = windowWidth / 2;
      let centerY = windowHeight / 2;
      let isLeft = originLeft <= centerX;
      let isRight = originLeft > centerX;
      let isTop = originTop <= centerY;
      let isBottom = originTop > centerY;
      let isCenterX = originLeft >= windowWidth * 0.25 && originLeft <= windowWidth * 0.75;

      // Calculating tap target
      let tapTargetWidth = this.$el.outerWidth();
      let tapTargetHeight = this.$el.outerHeight();
      let tapTargetTop = originTop + originHeight / 2 - tapTargetHeight / 2;
      let tapTargetLeft = originLeft + originWidth / 2 - tapTargetWidth / 2;
      let tapTargetPosition = isFixed ? 'fixed' : 'absolute';

      // Calculating content
      let tapTargetTextWidth = isCenterX ? tapTargetWidth : tapTargetWidth / 2 + originWidth;
      let tapTargetTextHeight = tapTargetHeight / 2;
      let tapTargetTextTop = isTop ? tapTargetHeight / 2 : 0;
      let tapTargetTextBottom = 0;
      let tapTargetTextLeft = isLeft && !isCenterX ? tapTargetWidth / 2 - originWidth : 0;
      let tapTargetTextRight = 0;
      let tapTargetTextPadding = originWidth;
      let tapTargetTextAlign = isBottom ? 'bottom' : 'top';

      // Calculating wave
      let tapTargetWaveWidth = originWidth > originHeight ? originWidth * 2 : originWidth * 2;
      let tapTargetWaveHeight = tapTargetWaveWidth;
      let tapTargetWaveTop = tapTargetHeight / 2 - tapTargetWaveHeight / 2;
      let tapTargetWaveLeft = tapTargetWidth / 2 - tapTargetWaveWidth / 2;

      // Setting tap target
      let tapTargetWrapperCssObj = {};
      tapTargetWrapperCssObj.top = isTop ? tapTargetTop + 'px' : '';
      tapTargetWrapperCssObj.right = isRight ? (windowWidth - tapTargetLeft - tapTargetWidth) + 'px' : '';
      tapTargetWrapperCssObj.bottom = isBottom ? (windowHeight - tapTargetTop - tapTargetHeight) + 'px' : '';
      tapTargetWrapperCssObj.left = isLeft ? tapTargetLeft + 'px' : '';
      tapTargetWrapperCssObj.position = tapTargetPosition;
      $(this.wrapper).css(tapTargetWrapperCssObj);

      // Setting content
      $(this.contentEl).css({
        width: tapTargetTextWidth + 'px',
        height: tapTargetTextHeight + 'px',
        top: tapTargetTextTop + 'px',
        right: tapTargetTextRight + 'px',
        bottom: tapTargetTextBottom + 'px',
        left: tapTargetTextLeft + 'px',
        padding: tapTargetTextPadding + 'px',
        verticalAlign: tapTargetTextAlign
      });

      // Setting wave
      $(this.waveEl).css({
        top: tapTargetWaveTop + 'px',
        left: tapTargetWaveLeft + 'px',
        width: tapTargetWaveWidth + 'px',
        height: tapTargetWaveHeight + 'px'
      });
    }

    /**
     * Open Feature Discovery
     */
    open() {
      if (this.isOpen) {
        return;
      }

      // onOpen callback
      if (typeof (this.options.onOpen) === 'function') {
        this.options.onOpen.call(this, this.$origin[0]);
      }

      this.isOpen = true;
      this.wrapper.classList.add('open');

      document.body.addEventListener('click', this._handleDocumentClickBound, true);
      document.body.addEventListener('touchend', this._handleDocumentClickBound);
    }

    /**
     * Close Feature Discovery
     */
    close() {
      if (!this.isOpen) {
        return;
      }

      // onClose callback
      if (typeof (this.options.onClose) === 'function') {
        this.options.onClose.call(this, this.$origin[0]);
      }

      this.isOpen = false;
      this.wrapper.classList.remove('open');

      document.body.removeEventListener('click', this._handleDocumentClickBound, true);
      document.body.removeEventListener('touchend', this._handleDocumentClickBound);
    }
  }

  M.FeatureDiscovery = FeatureDiscovery;

  if (M.jQueryLoaded) {
    M.initializeJqueryWrapper(FeatureDiscovery, 'featureDiscovery', 'M_FeatureDiscovery');
  }

}(cash));


<|start_filename|>src/js/adminInfo.js<|end_filename|>
/* global socket, systemLang */

function InfoAdapter(main) {

    var that = this;
    
    this.systemData = {node: null, npm: null, os: null, uuid: null};
    this.main = main;

    this.checkVersion = function (smaller, bigger) {
        if (smaller === undefined || bigger === undefined) {
            return false;
        }
        smaller = smaller.split('.');
        bigger = bigger.split('.');
        smaller[0] = parseInt(smaller[0], 10);
        bigger[0] = parseInt(bigger[0], 10);

        if (smaller[0] > bigger[0]) {
            return false;
        } else if (smaller[0] === bigger[0]) {
            smaller[1] = parseInt(smaller[1], 10);
            bigger[1] = parseInt(bigger[1], 10);
            if (smaller[1] > bigger[1]) {
                return false;
            } else if (smaller[1] === bigger[1]) {
                smaller[2] = parseInt(smaller[2], 10);
                bigger[2] = parseInt(bigger[2], 10);
                return (smaller[2] < bigger[2]);
            } else {
                return true;
            }
        } else {
            return true;
        }
    };

    this.checkVersionBetween = function (inst, vers1, vers2) {
        return inst === vers1 || inst === vers2 || (that.checkVersion(vers1, inst) && that.checkVersion(inst, vers2));
    };

    this.showPopup = function (obj) {
        if (sessionStorage.getItem('ioBroker.info.lastPopup')) {
            that.checkAndSetData(obj, sessionStorage.getItem('ioBroker.info.lastPopup'));
        } else {
            that.main.socket.emit('getState', 'info.0.last_popup', function (err, dateObj) {
                if (!err && dateObj) {
                    sessionStorage.setItem('ioBroker.info.lastPopup', dateObj.val);
                    that.checkAndSetData(obj, dateObj.val);
                }
            });
        }
    };

    this.checkAndSetData = async function (messagesObj, date) {
        const messages = await that.checkMessages(messagesObj, date);
        if (messages.length === 1) {
            const message = messages[0];
            that.main.showMessage(message.content, message.title, message.class);
        } else if (messages.length > 1) {
            let content = '<ol>';
            const idArray = [];
            messages.forEach(function (message) {
                if (idArray.indexOf(message.id) === -1) {
                    content += '<li>';
                    content += '<h5>' + message.title + '</h5>';
                    content += '<p>' + message.content + '</p>';
                    content += '</li>';
                    idArray.push(message.id);
                }
            });
            content += '</ol>';
            that.main.showMessage(content, _('Please read these important notes:'), 'error');
        }
        if (messages.length > 0) {
            sessionStorage.setItem('ioBroker.info.lastPopup', new Date().toISOString());
            that.main.socket.emit('setState', 'info.0.last_popup', {val: new Date().toISOString(), ack: true});
        }
    };
    
    this.checkActive = function (adapterName) {
        const instances = that.main.instances;
        if (!instances) {
            return false;
        }
        const instCreated = instances.filter(function (str) {
            return str.includes('.' + adapterName + '.');
        });
        if (instCreated.length === 0) {
            return false;
        }
        let i;
        for (i = 0; i < instCreated.length; i++) {
            if (that.main.objects[instCreated[i]].common.enabled) {
                return true;
            }
        }
        return false;
    }

    this.checkConditions = function (condition, installedVersion) {
        if (condition.startsWith('equals')) {
            const vers = condition.substring(7, condition.length - 1).trim();
            return (installedVersion === vers);
        } else if (condition.startsWith('bigger')) {
            const vers = condition.substring(7, condition.length - 1).trim();
            return that.checkVersion(vers, installedVersion);
        } else if (condition.startsWith('smaller')) {
            const vers = condition.substring(8, condition.length - 1).trim();
            return that.checkVersion(installedVersion, vers);
        } else if (condition.startsWith('between')) {
            const vers1 = condition.substring(8, condition.indexOf(',')).trim();
            const vers2 = condition.substring(condition.indexOf(',') + 1, condition.length - 1).trim();
            return that.checkVersionBetween(installedVersion, vers1, vers2);
        } else {
            return true;
        }
    };

    this.checkMessages = async function (obj, date) {
        const messagesToShow = [];

        try {
            const messages = JSON.parse(obj);
            const today = new Date().getTime();
            let lastMessage = 0;
            if (date) {
                lastMessage = new Date(date).getTime();
            }
            if (messages.length > 0) {
                await asyncForEach(messages, async function (message) {
                    let showIt = true;

                    if (showIt && message['created'] && new Date(message['created']).getTime() < lastMessage) {
                        showIt = false;
                    } else if (showIt && message['date-start'] && new Date(message['date-start']).getTime() > today) {
                        showIt = false;
                    } else if (showIt && message['date-end'] && new Date(message['date-end']).getTime() < today) {
                        showIt = false;
                    } else if (showIt && message.conditions && Object.keys(message.conditions).length > 0) {
                        const adapters = that.main.tabs.adapters.curInstalled;
                        await asyncForEach(Object.keys(message.conditions), function (key) {
                            if (showIt) {
                                const adapter = adapters[key];
                                const condition = message.conditions[key];

                                if (!adapter && condition !== '!installed') {
                                    showIt = false;
                                } else if (adapter && condition === '!installed') {
                                    showIt = false;
                                } else if (adapter && condition === 'active') {
                                    showIt = that.checkActive(key);
                                } else if (adapter && condition === '!active') {
                                    showIt = !that.checkActive(key);
                                } else if (adapter) {
                                    showIt = that.checkConditions(condition, adapter.version);
                                }
                            }
                        });
                    }

                    if (showIt && message['node-version']) {
                        const condition = message['node-version'];
                        showIt = that.systemData.node !== null && that.checkConditions(condition, that.systemData.node);
                    }
                    if (showIt && message['npm-version']) {
                        const condition = message['npm-version'];
                        showIt = that.systemData.npm !== null && that.checkConditions(condition, that.systemData.npm);
                    }
                    if (showIt && message['os']) {
                        showIt = that.systemData.os !== null && that.systemData.os === message['os'];
                    }
                    if (showIt && message['repo']) {
                        showIt = that.main.systemConfig.common.activeRepo === message['repo'];
                    }                    
                    if (showIt && message['uuid']) {
                        if (Array.isArray(message['uuid'])) {
                            var oneMustBe = false;
                            if (that.systemData.uuid){
                                await asyncForEach(message['uuid'], function (uuid){
                                    if (!oneMustBe) {
                                        oneMustBe = that.systemData.uuid === uuid;
                                    }
                                });
                            }
                            showIt = oneMustBe;
                        } else {
                            showIt = that.systemData.uuid && that.systemData.uuid === message['uuid'];
                        }
                    }

                    if (showIt) {
                        messagesToShow.push({
                            id: message.id,
                            title: message.title[systemLang],
                            content: message.content[systemLang],
                            'class': message.class,
                            icon: message['fa-icon'],
                            created: message.created
                        });
                    }
                });
            }
        } catch (err) {

        }

        return messagesToShow;
    };

    this.init = function () {

        that.main.socket.emit('getState', 'info.0.sysinfo.os.versions.node', function (err, data) {
            if (!err && data) {
                that.systemData.node = data.val;
            }
        });
        that.main.socket.emit('getState', 'info.0.sysinfo.os.versions.npm', function (err, data) {
            if (!err && data) {
                that.systemData.npm = data.val;
            }
        });
        that.main.socket.emit('getState', 'info.0.sysinfo.os.info.platform', function (err, data) {
            if (!err && data) {
                that.systemData.os = data.val;
            }
        });
        that.main.socket.emit('getState', 'info.0.uuid', function (err, data) {
            if (!err && data) {
                that.systemData.uuid = data.val;
            }
        });

        if (that.main.objects['info.0.newsfeed'] && that.main.objects['info.0.last_popup']) {
            that.main.socket.emit('subscribe', 'info.0.newsfeed');

            that.main.socket.on('stateChange', function (id, obj) {
                if (id === 'info.0.newsfeed') {
                    that.showPopup(obj.val);
                }
            });

            that.main.socket.emit('getState', 'info.0.newsfeed', async function (err, obj) {
                if (!err && obj) {
                    that.showPopup(obj.val);
                }
            });
        } else if (!that.main.systemConfig.common.infoAdapterInstall) {

            if (that.main.objects['system.adapter.info.0']) {
                // if installed version too old
                that.main.confirmMessage(_('<p>Your version of the info adapter is outdated. It is strongly recommended to update this to get on the enjoyment of the innovations. Only with the new version, for example, messages from the ioBroker team can be displayed directly.</p><p>Would you like to update the info adapter?</p>'), _('Update info adapter'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'upgrade info', function (exitCode) {
                            if (!exitCode) {
                                location.reload(true);
                            }
                        });
                    }
                });
            } else {
                // if info adapter is not installed
                that.main.confirmMessage(_('<p>You have not installed an Info Adapter. The adapter shows you information about the system and is required to display important messages from the ioBroker team.</p><p>Do you want to install the info adapter?</p>'), _('Info adapter not found'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'add info 0', function (exitCode) {
                            if (!exitCode) {
                                location.reload(true);
                            }
                        });
                    }
                });
            }

            main.socket.emit('getObject', 'system.config', function (err, obj) {
                //ask only one time
                if (err || !obj) {
                    main.showError(_('Cannot confirm: ' + err));
                    return;
                }
                obj.common = obj.common || {};
                obj.common.infoAdapterInstall = true;
                main.socket.emit('setObject', 'system.config', obj, function (err) {
                    err && main.showError(err);
                });
            });
        }
    };
}


<|start_filename|>src-rx/src/helpers/MaterialDynamicIcon.js<|end_filename|>
import { useState, useEffect } from 'react';
import * as Icons from '@material-ui/icons/';

const ICON_CACHE = {};

let objIcon = {
    'All': 'AllInclusive',
    'messaging': 'Message',
    'communication': 'WifiTethering',
    'general': 'Apps',
    'logic': 'VpnKey',
    'alarm': 'NotificationsActive',
    'iot-systems': 'SystemUpdateAlt',
    'misc-data': 'EmojiSymbols',
    'multimedia': 'PermMedia',
    'network': 'SettingsEthernet',
    'storage': 'Storage',
    'visualization': 'Visibility',
    'climate-control': 'SettingsBrightness',
    'date-and-time': 'DateRange',
    'energy': 'BatteryChargingFull',
    'garden': 'FilterVintage',
    'geoposition': 'LocationOn',
    'hardware': 'Phonelink',
    'health': 'Favorite',
    'household': 'House',
    'infrastructure': 'Apartment',
    'lighting': 'EmojiObjects',
    'protocols': 'DialerSip',
    'utility': 'PhonelinkSetup',
    'vehicle': 'AirportShuttle',
    'visualization-icons': 'BubbleChart',
    'visualization-widgets': 'Widgets',
    'weather': 'Brightness6',
    'metering': 'LinearScale'
};

const MaterialDynamicIcon = ({ iconName, className, adapter, socket, onClick, objIconBool }) => {
    let [url, setUrl] = useState('');

    useEffect(() => {
        if (adapter && socket) {
            ICON_CACHE[adapter] = ICON_CACHE[adapter] || socket.getObject(`system.adapter.${adapter}`);
            ICON_CACHE[adapter].then(obj =>
                obj?.common?.icon && setUrl(`../../adapter/${adapter}/${obj.common.icon}`));
        }
    }, [adapter, socket]);

    if (adapter) {
        return <img onClick={e => onClick && onClick(e)} src={url || ''} className={className} alt="" />;
    } else {
        const Element = Icons[objIconBool ? objIcon[iconName] || 'Help' : (iconName || 'Help')];
        return <Element
            className={className}
            onClick={e => onClick && onClick(e)}
        />;
    }
}

MaterialDynamicIcon.defaultProps = {
    className: null,
    iconName: 'Help',
    objIconBool: false
};

export default MaterialDynamicIcon;

<|start_filename|>src-rx/src/assets/css/style.css<|end_filename|>
#diagSample {
    background: #fff;
}

#tabs-system, #diagSample {
    overflow: auto;
}

.m pre {
    font-family: monospace, monospace;
    font-size: 1em;
}

.drop-container {
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    border: 5px dashed transparent;
    margin-top: 5px;
    height: calc(100% - 10px)
}

.drop-container:focus {
    outline: none;
}

.drop-dop {
    background-color: #FFFFFF40;
    border: 5px dashed #000000A0;
    z-index: 100;
}

.drop-container input {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1000;
}

.tables {
    width: 33.3%;
}

.dialog-setting {
    width: 100% !important;
    height: 100% !important;
    max-width: calc(100% - 64px) !important;
    -webkit-transition: all 400ms ease-out !important;
    -moz-transition: all 400ms ease-out !important;
    -ms-transition: all 400ms ease-out !important;
    -o-transition: all 400ms ease-out !important;
    transition: all 400ms ease-out !important;
}

.dialogName {
    white-space: nowrap;
    font-size: 1.1rem !important;
    padding-right: 10px;
}

.float_row {
    display: flex !important;
}

tbody > .float_row:nth-of-type(2n+1) {
    background-color: #00000008;
}

.float_cell {
    display: table-cell;
}

.grow_cell {
    flex-grow: 100;
}

.small_size {
    width: 40px !important;
    height: 40px !important;
    min-width: 40px !important;
    min-height: 40px !important;
    max-width: 40px !important;
    max-height: 40px !important;
}
@media screen and (max-width: 993px) {
    .sendData-grid {
        height: calc(100% - 1rem);
    }

    #diagSample {
        height: calc(100% - 6rem);
    }

    .dialog-setting {
        width: 100% !important;
        height: 100% !important;
        max-width: calc(100% - 64px) !important;
    }
}

@media (max-width: 760px) {
    .tables {
        width: 50%;
    }

    .dialog-setting {
        width: 100% !important;
        height: 100% !important;
        max-width: calc(100% - 64px) !important;
    }

    .float_row {
        display: flex !important;
        flex-direction: column;
        margin-bottom: 30px;
        border-bottom: 5px solid #00000029;
    }

    .float_cell {
        display: block !important;
        width: 100% !important;
        text-align: center !important;
    }
}

@media (max-width: 540px) {
    tables {
        width: 100%;
    }

    .dialog-setting {
        width: 100% !important;
        height: 100% !important;
        max-width: 100% !important;
        max-height: 100% !important;
        margin: 0 !important;
    }

    .dialogName {
        font-size: .7rem !important;
    }

    .float_row {
        display: flex !important;
        flex-direction: column;
        margin-bottom: 30px;
        border-bottom: 5px solid #00000029;
    }

    .float_cell {
        display: block !important;
        width: 100% !important;
        text-align: center !important;
    }

    .xs-centered {
        text-align: center !important;
    }

    .xs-centered input {
        text-align: center !important;
    }
}

@media (max-width: 420px) {
    .tables {
        width: 100%;
    }

    .dialog-setting {
        width: 100% !important;
        height: 100% !important;
        max-width: 100% !important;
        max-height: 100% !important;
        margin: 0 !important;
    }

    .dialogName {
        font-size: .7rem !important;
    }

    .float_row {
        display: flex !important;
        flex-direction: column;
        margin-bottom: 30px;
        border-bottom: 5px solid #00000029;
    }

    .float_cell {
        display: block !important;
        width: 100% !important;
        text-align: center !important;
    }

    .xs-centered {
        text-align: center !important;
    }

    .xs-centered input {
        text-align: center !important;
    }
}