<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cluster state</title>
    <script type="text/javascript" src="//yastatic.net/react/15.0.1/react.js"></script>
    <script type="text/javascript" src="//yastatic.net/react/15.0.1/react-dom.js"></script>
    <script type='text/javascript' src="{% static 'js/xmlhttp.js' %}"></script>
    <script type='text/javascript' src="{% static 'js/answer_set.js' %}"></script>
    <script type='text/javascript' src="{% static 'js/data_load.js' %}"></script>
    <script src="https://yandex.st/jquery/2.0.3/jquery.min.js" type="text/javascript"></script>
    <script src="{% static 'js/copying_link.js' %}" type="text/javascript"></script>
    <link href="https://yandex.st/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
</head>

<style>
    .b-ok {
        background-color: #CCFFCC;
    }
    .b-down {
        background-color: #FFCCCC;
    }
    .b-not-active {
        background-color: #FFDDCC;
    }
    .b-not-ok {
        background-color: #FFFFCC;
    }
    .b-si-disabled {
        background-color: #CCDDDD;
    }
    .b-not-actual {
        background-color: #EEEEAA;
    }
    .b-not-parsed {
        background-color: #FFFFFF;
    }
    .slot-selected {
        color: blue;
    }
    .b-zero:after {
        background-color: white;
        color: white;
        content: '*';
        font-size: 5px;
        float: right;
        position: relative;
        bottom: 2px;
        right: 2px;
        width: 10px;
        height: 10px;
        border-radius: 10px;
    }
    .endpointset {
        background-color: #FFFFFF;
    }
    td {
        border: 2px solid white;
    }
</style>

<script type='text/javascript'>
    let current_states = {};
    let current_absents = {};
    const cluster_service = '{{ service }}';
    const cluster_ctype = '{{ ctype }}';
    const cluster_is_rty = {% if is_rty %}true{% else %}false{% endif %};
    const cluster_is_root = {% if is_root %}true{% else %}false{% endif %};

    const cluster_updates = {
        answer_id: 'cluster_answer',
        need_pause: function() {
            return (isHidden() && first_updates.hasOwnProperty('cluster_answer'));
        },
        hr_legend: 'cluster info',
        get_url: get_cluster_info_url,
        on_load: update_table,
        mark_update: function() { first_updates['cluster_answer'] = true; }
    };

    const cluster_unused_updates = {
        answer_id: 'free_slots_answer',
        need_pause: function() {
            return (isHidden() || document.getElementById('free_slots').hasAttribute('hidden'));
        },
        hr_legend: 'free slots info',
        url: '{% url "service_cluster_table" %}?service=unused_' + cluster_service + '&ctype=' + cluster_ctype + '&metrics=',
        on_load: update_unused_table,
        do_once_any_case: false
    };

    let selected_metrics = ['docs_count', 'result.server_uptime',
        'result.cpu_load_user', 'result.mem_size_real',
        'result.docs_in_final_indexes', 'result.docs_in_memory_indexes'
    ];

    let first_updates = {};

    const ctrl_commands = [
        {command: 'restart', short: 'Re', args: 'rigid_level=1', need_confirm: false},
        {command: 'abort', short: 'Ab', args: ''},
        {command: 'shutdown', short: 'Sh', args: '', need_confirm: false},
        {command: 'stop', short: 'St', args: ''},
        {command: 'clear_data_status', short: 'ClDS', args: '', need_confirm: false},
        {command: 'clear_index', short: 'Clear', args: ''},
        {command: 'enable_search', short: 'Es', args: '', need_confirm: false},
    ];

    const possible_metrics = [
        {"caption" : "sources", "filter":"result.search_sources_count", "default": true},
        {"caption" : "docs in final", "filter":"result.docs_in_final_indexes", "default": true, "rty_only": true},
        {"caption" : "docs in mem", "filter":"result.docs_in_memory_indexes", "default": true, "rty_only": true},
        {"caption" : "docs in disk", "filter":"result.docs_in_disk_indexers", "default": false, "rty_only": true},
        {"caption" : "doccount", "filter":"docs_count", "default": false, "rty_only": true},
        {"caption" : "size", "filter":"result.files_size.__SUM", "default": false},
        {"caption" : "age", "filter":"result.index_age", "default": false},
        {"caption" : "uptime", "filter":"result.controller_uptime", "default": true, "rty_only": false},
        {"caption" : "server_uptime", "filter":"result.server_uptime", "default": false},
        {"caption" : "mem", "filter":"result.mem_size_real", "default": false},
        {"caption" : "slot size", "filter":"result.slot.container.constraints.memory_limit", "default": false},
        {"caption" : "mem total", "filter":"result.total_mem_size", "default": false},
        {"caption" : "cpu", "filter":"result.cpu_load_user", "default": false, "rty_only": false},
        {"caption" : "cpu_policy", "filter":"result.slot.container.constraints.cpu_policy", "default": false, "rty_only": false},
        {"caption" : "cpu_limit", "filter":"result.slot.container.constraints.cpu_limit", "default": false, "rty_only": false},
        {"caption" : "la", "filter":"result.load_average", "default": false, "rty_only": false},
        {"caption" : "disk_index_rps", "filter":"result.disk_index_rps.30", "default": false},
        {"caption" : "memory_index_rps", "filter":"result.memory_index_rps.30", "default": false},
        {"caption" : "search_rps_failed", "filter":"result.search_rps_failed.30", "default": false},
        {"caption" : "search_rps_http", "filter":"result.search_rps_http.30", "default": false},
        {"caption" : "search_rps_neh", "filter":"result.search_rps_neh.30", "default": false},
        {"caption" : "search_rps_neh_base", "filter":"result.search_rps_neh_base.30", "default": false},
        {"caption" : "merger_tasks", "filter":"result.merger_tasks", "default": false},
        {"caption" : "current_sync", "filter":"result.current_sync_resource", "default": false},
        {"caption" : "last_sync", "filter":"result.last_sync_resource", "default": false},
        {"caption" : "nanny_service", "filter":"result.slot.properties.NANNY_SERVICE_ID", "default": false},
        {"caption" : "gencfg_group", "filter":"result.slot.dynamicProperties.GENCFG_GROUP", "default": false},
        {"caption" : "gencfg_rel", "filter":"result.slot.dynamicProperties.GENCFG_RELEASE", "default": false},
        {"caption" : "real_host", "filter":"$real_host$", "default": false}
    ];

    const state_fields = ['disable_indexing', 'disable_search',
        'result.indexing_enabled', 'result.search_enabled',
        'result.server_status_global.state', 'result.controller_status',
        'result.server_status_global.info', 'invalid_config_files', 'slot',
        'result.search_rps_neh.30', 'result.search_rps_http.30'
    ];

    const field2constraints = {
        'result.cpu_load_user': ['result.slot.container.constraints.cpu_limit', 'result.cpu_load_system'],
        'result.mem_size_real': ['result.slot.container.constraints.memory_limit'],
        'result.load_average': ['result.cpu_count'],
    };

    const fields2color = {
        'result.cpu_load_user': function(fields) {
            let climit = fields['result.slot.container.constraints.cpu_limit'].split('.')[0];
            if (isNaN(climit)) { return 'grey'; }
            let ratio = (fields['result.cpu_load_user'] + fields['result.cpu_load_system']) / (100*climit);
            if (ratio < 0.5) {
                return '#9beab3';
            } else if (ratio < 0.8) {
                return '#f7f795';
            } else {
                return '#f7b9b9';
            }
        },
        'result.mem_size_real': function(fields) {
            let mlimit = Number(fields['result.slot.container.constraints.memory_limit']);
            if (isNaN(mlimit)) { return 'grey'; }
            let ratio = 1000000 * fields['result.mem_size_real'] / mlimit;
            if (ratio < 0.7) {
                return '#9beab3';
            } else if (ratio < 0.85) {
                return '#f7f795';
            } else {
                return '#f7b9b9';
            }
        },
        'result.load_average': function(fields) {
            let cpus = fields['result.cpu_count'];
            let ratio = /*0.33 * */ fields['result.load_average'] / cpus;
            if (ratio < 0.5) {
                return '#9beab3';
            } else if (ratio < 0.75) {
                return '#f7f795';
            } else {
                return '#f7b9b9';
            }
        },
        'result.docs_in_final_indexes': function(fields) {
            let inconsist = fields['replics_consistance'];
            if (!!inconsist) {
                if (inconsist === 'shortage_docs') {
                    return '#f7b9b9';
                } else if (inconsist === 'overage_docs') {
                    return '#f5f756';
                }
            }
            return '';
        }
    };

    function get_itype() {
        if (cluster_is_rty) {
            return 'rtyserver';
        }
        return cluster_service.replace('_', '');
    }

    function isDataSuccess() {
        try {
            if (current_states['ans'].length > 0) {
                return current_states['success'];
            }
        } catch(err) { return false; }
    }

    function precompute_absent_slots() {
        current_absents = {};
        if (!cluster_is_rty) {
            return;
        }
        let max_cnts = {};
        let dc_cnts = {};
        for (let i=0; i<current_states['ans'].length; ++i) {
            let slots = current_states['ans'][i]['slots'];
            let id = current_states['ans'][i]['id'];
            let i_dc_cnts = {};
            for (let j=0; j<slots.length; ++j) {
                let dc = slots[j]['$datacenter$'];
                i_dc_cnts[dc] = (i_dc_cnts.hasOwnProperty(dc) ? i_dc_cnts[dc] : 0) + 1;
            }
            dc_cnts[id] = i_dc_cnts;
            for (let dc in i_dc_cnts) {
                if (i_dc_cnts.hasOwnProperty(dc)) {
                    if (!max_cnts.hasOwnProperty(dc) || i_dc_cnts[dc] > max_cnts[dc]) {
                        max_cnts[dc] = i_dc_cnts[dc];
                    }
                }
            }
       //     max_cnts['SAS'] += 1;
        }
  //      console.log(dc_cnts);
    //    console.log(max_cnts);
        for (let id in dc_cnts) {
            if (dc_cnts.hasOwnProperty(id)) {
                for (let dc in max_cnts) {
                    if (max_cnts.hasOwnProperty(dc)) {
                        let cur_cnt = dc_cnts[id].hasOwnProperty(dc) ? dc_cnts[id][dc] : 0;
                        if (cur_cnt < max_cnts[dc]) {
                            for (let rid=0; rid<(max_cnts[dc] - cur_cnt); ++rid) {
                                if (!current_absents.hasOwnProperty(id)) {
                                    current_absents[id] = [];
                                }
                                current_absents[id].push(dc + '_' + id + '_' + rid);
                            }
                        }
                    }
                }
            }
        }
    //    console.log(current_absents);
    }

    function getShowControls() {
        try {
            return document.getElementById('show-controls').checked;
        } catch(err) { return false; }
    }

    function get_cluster_info_url() {
        let url = '{% url "service_cluster_table" %}?service=' + cluster_service + '&ctype=' + cluster_ctype;
        let metrics_param = selected_metrics.join(',');
        for (let i=0; i<selected_metrics.length; ++i) {
            if (field2constraints.hasOwnProperty(selected_metrics[i])) {
                metrics_param += ',' + field2constraints[selected_metrics[i]].join(',');
            }
        }
        url += '&metrics=' + metrics_param;
        return url;
    }

    function do_command_on_slot(controller, command) {
        if (!command.hasOwnProperty('need_confirm') || command['need_confirm']) {
            if (!confirm('Are you sure to ' + command['command'] + ' slot ' + controller + '?')) {
                return;
            }
        }
        let url = '{% url "slot_command" %}?slot=' + controller + '&command=' + command['command'] + '&' + command['args']
            + '&service=' + service + '&ctype=' + ctype;

        console.log(url);
        let xmlhttp = getXmlHttp();
        xmlhttp.open('GET', url);
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState === 4) {
                console.log(xmlhttp.responseText);
            }
        };
        xmlhttp.send();
    }

    function parse_backup(ans_text) {
        try {
            let jsr = JSON.parse(ans_text);
            let conf = jsr['result']['config']['Server'][0]['ModulesConfig'][0];
            console.log(conf);
            if (!conf.hasOwnProperty('DOCFETCHER')) {
                document.getElementById('b-is-backup').innerText = "Is backup: NO (no docfetcher module)";
                return;
            }
            let dfc = conf['DOCFETCHER'][0];
            if (dfc['Enabled'] !== "1") {
                document.getElementById('b-is-backup').innerText = "Is backup: NO (docfetcher disabled)";
                return;
            }
            let dfss = dfc["Stream"];
            for (let sti=0; sti<dfss.length; ++sti) {
                if (dfss[sti].hasOwnProperty('Enabled') && dfss[sti]['Enabled']==='0') {
                    continue;
                }
                let fields = ['SyncPath', 'BackupTable'];
                for (let f=0; f<fields.length; ++f) {
                    if (dfss[sti].hasOwnProperty(fields[f]) && dfss[sti][fields[f]] !== '') {
                        if (fields[f] === 'SyncPath'
                            && (!dfss[sti].hasOwnProperty("SyncServer") || dfss[sti]["SyncServer"] === '')) {
                            document.getElementById('b-is-backup').innerText = "Is backup: No (no SyncServer)";
                        } else {
                            document.getElementById('b-is-backup').innerText = "Is backup: Yes (" + fields[f] + ")";
                        }
                        return;
                    }
                }
                let sn_field = 'SnapshotPath';
                if (dfss[sti].hasOwnProperty(sn_field) && dfss[sti][sn_field] !== '') {
                    if (dfss[sti].hasOwnProperty("ConsumeMode") && (dfss[sti]["ConsumeMode"] === 'replace' || dfss[sti]["ConsumeMode"] === 'hard_replace')
                        && dfss[sti].hasOwnProperty("StreamType") && dfss[sti]["StreamType"] === 'Snapshot'
                        && dfss[sti].hasOwnProperty("SnapshotServer")&& dfss[sti]["SnapshotServer"] !== '') {
                        document.getElementById('b-is-backup').innerText = "Is backup: Yes (SnapshotPath)";
                        return;
                    }
                }
            }
            document.getElementById('b-is-backup').innerText = "Is backup: NO (no conf fields)";
        } catch (err) {
            document.getElementById('b-is-backup').innerText = "Is backup: Error in json";
        }
    }

    function show_is_backup(ind=0) {
        document.getElementById('b-is-backup').innerText = "Is backup: Waiting...";
        let els = document.getElementsByClassName('slot-used');
        let a = els[ind].children[0];
        let url = '{% url "slot_proxy" %}/?h=' + a.href.split('?')[0] + '/&command=get_info_server';
     //   alert(url);
        let xmlhttp = getXmlHttp();
        xmlhttp.open('GET', url);
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState === 4) {
                if (xmlhttp.status === 200) {
                    parse_backup(xmlhttp.responseText);
                } else {
                    if (ind < 5) {
                        show_is_backup(ind+1);
                    } else {
                        console.log('error answer: ' + xmlhttp.responseText);
                        document.getElementById('b-is-backup').innerText = 'Is backup: Error';
                    }
                }
            }
        };
        xmlhttp.send();
    }

    function switch_select_slot(slot, is_unused=false) {
        document.getElementById('cell-' + slot).classList.toggle('slot-selected');
        let evt = new CustomEvent('slotselect', {detail: {slot: slot, is_unused: is_unused}});
        window.dispatchEvent(evt);
    }

    function is_searchind_disabled(slot_data) {
        if (!cluster_is_rty) { return false; }
        return (slot_data['disable_indexing'] || slot_data['disable_search']
            || !slot_data['result.indexing_enabled']
            || !slot_data['result.search_enabled']);
    }

    function get_controller_addr(slot, port) {
        let ctrl_port = Number(port) + 3;
        return slot.substring(0, slot.indexOf(':')+1) + ctrl_port;
    }

    function slot_style_class(slot_data) {
        let status = slot_data["result.controller_status"];
        let status_data = slot_data["result.server_status_global.state"];
        if (status === 'Down') {
            return 'b-down';
        }
        if (status !== 'Active') {
            return 'b-not-active';
        }
        if (status_data !== 'OK') {
            return 'b-not-ok';
        }
        if (is_searchind_disabled(slot_data)) {
            return 'b-si-disabled';
        }
        if (!!slot_data['invalid_config_files']) {
            return 'b-not-actual';
        }
        if (status === 'Active' && status_data === 'OK') {
            return 'b-ok';
        }
        return 'b-not-parsed';
    }

    function si_disabled_contents(slot_data) {
        let proxy_txt='', proxy_title='';
        let be_txt='', be_title='';
        if (slot_data['disable_search'] || slot_data['disable_indexing']) {
            proxy_title = 'Searchmap:';
            if (slot_data['disable_search']) {
                proxy_txt += 'S';
                proxy_title += ' search disabled,';
            }
            if (slot_data['disable_indexing']) {
                proxy_txt += 'I';
                proxy_title += ' indexing disabled';
            }
        }
        if (!slot_data['result.search_enabled'] || !slot_data['result.indexing_enabled']) {
            be_title = 'Backend:';
            be_txt = '(';
            if (!slot_data['result.search_enabled']) {
                be_txt += 'S';
                be_title += ' search disabled,';
            }
            if (!slot_data['result.indexing_enabled']) {
                be_txt += 'I';
                be_title += ' indexing disabled';
            }
            be_txt += ')';
        }
        return [[proxy_txt, proxy_title], [be_txt, be_title]];
    }

    function is_zero_rps(slot_data) {
        try {
            if (!slot_data.hasOwnProperty('result.search_rps_neh.30') || !slot_data.hasOwnProperty('result.search_rps_http.30')) {
                return false;
            }
            if (slot_data.hasOwnProperty('result.search_rps_neh.30') && slot_data['result.search_rps_neh.30'] > 0.01) {
                return false;
            }
            if (slot_data.hasOwnProperty('result.search_rps_http.30') && slot_data['result.search_rps_http.30'] > 2) {
                return false;
            }
            return true;
        } catch(err) {console.log(err);}
        return false;
    }

    class SlotLink extends React.Component {
        render () {
            let slot_short = '';
            try {
                slot_short = this.props.slot.split('.')[0];
                slot_short += ':' + this.props.port;
            } catch(err) {
                console.log(err);
                slot_short = this.props.slot;
            }
            return React.createElement('a',
                {
                    'target': '_blank',
                    'href': 'http://' + this.props.controller + '/?command=get_info_server'
                        // '{% url "slot_proxy" %}/?h=' + this.props.controller + '/&command=get_info_server'
                },
                slot_short + ' ' + this.props.dc);
        }
    }

    class SlotControls extends React.Component {
        render() {
            if (!cluster_is_root) {
                return null;
            }
            let ctrl_els = [];
            for (let i=0; i<ctrl_commands.length; ++i) {
                ctrl_els.push(React.createElement('button',
                    {
                        key: ctrl_commands[i]['short'] + '-' + this.key,
                        title: ctrl_commands[i]['command'],
                        onClick: () => {
                            do_command_on_slot(
                                this.props.controller,
                                ctrl_commands[i])
                        }
                    },
                    ctrl_commands[i]['short']
                ));
            }
            ctrl_els.push(React.createElement('a', {
                key: 'porto-' + this.key,
                title: 'Porto panel',
                target: '_blank',
                href: 'https://yasm.yandex-team.ru/template/panel/Host/hosts='
                    + this.props.slot.substr(0, this.props.slot.search(':')) + ';itype=' + get_itype()
            }, ' P'));
            ctrl_els.push(React.createElement('a', {
                key: 'copy-shell-' + this.key,
                id: 'copy-shell-' + this.key,
                title: 'Copy sky portoshell to clipboard',
                style: {cursor: 'pointer'},
                onClick: () => { copyTextToClipboard(
                    this.props.slot.length < 35 ?
                    'sky portoshell '
                    + this.props.port + '@' + this.props.slot.substr(0, this.props.slot.search(':'))
                    : 'ssh ' + this.props.slot.substr(0, this.props.slot.search(':'))
                    , 500);

                }
            }, ' C'));
            return React.createElement('div', {}, ctrl_els);
        }
    }

    class SlotMetrics extends React.Component {
        render() {
            let metric_els = [];
            for (let met in this.props.metrics) {
                let metr_props = {title: met, key: this.key + '-' + met};
                if (fields2color.hasOwnProperty(met)) {
                    try {
                        let b_color = fields2color[met](this.props.all_data);
                        if (!!b_color) {
                            metr_props['style'] = {'backgroundColor': b_color};
                        }
                    } catch(err) { console.log(err); }
                }
                metric_els.push(React.createElement(
                    'span',
                    metr_props,
                    this.props.metrics[met] + '/'
                ));
                metric_els.push(React.createElement(
                    'wbr',
                    {key: this.key + '-s-' + met},
                    null
                ));
            }
            return React.createElement('div', null, metric_els);
        }
    }

    class SlotState extends React.Component {
        render() {
            let elements = [];
            let is_down = (this.props.state_props["result.controller_status"] === 'Down');
            if (is_down) {
                let slot_short = this.props.real_host;
                try {
                    slot_short = slot_short.split('.')[0];
                } catch(err) {}
                let walle = React.createElement('a',
                    {
                        target: '_blank',
                        href: 'https://wall-e.yandex-team.ru/?hosts=' + slot_short,
                        title: 'Wall-e open'
                    },
                    'Down'
                );
                elements.push(React.createElement('div',
                    { key: 'st-' + this.key },
                    [walle]
                ));
            } else {
                elements.push(React.createElement('div',
                    {
                        key: 'st-' + this.key,
                        title: this.props.state_props['result.server_status_global.info']
                    },
                    this.props.state_props['result.controller_status']
                    + '/' + this.props.state_props['result.server_status_global.state']
                ));
            }
            if (is_searchind_disabled(this.props.state_props)) {
                let si_shorts = si_disabled_contents(this.props.state_props);
                elements.push(React.createElement(
                    'div',
                    {key: 'sind-p-' + this.key, title: si_shorts[0][1]},
                    si_shorts[0][0]
                ));
                elements.push(React.createElement(
                    'div',
                    {key: 'sind-b-' + this.key, title: si_shorts[1][1]},
                    si_shorts[1][0]
                ));
            }
            if (!!this.props.state_props['invalid_config_files'] && !is_down) {
                let err_files = '';
                for (let f=0; f<this.props.state_props['invalid_config_files'].length; ++f) {
                    if (f>1) {
                        err_files += 'and ' + (this.props.state_props['invalid_config_files'].length - f) + ' more';
                        break;
                    }
                    let icf = this.props.state_props['invalid_config_files'][f];
                    err_files += icf['id'] + ' ' + icf['error'] + ' ';
                }
                elements.push(React.createElement(
                    'div',
                    {key: 'invc-' + this.key, title: JSON.stringify(this.props.state_props['invalid_config_files'])},
                    err_files
                ))
            }
            return React.createElement('div', null, elements);
        }
    }

    class SlotCell extends React.Component {
        render() {
            let slot_data = current_states['ans'][this.props.interval_index]['slots'][this.props.slot_index];
            let key_part = this.props.interval_index + '' + this.props.slot_index;
            let controller = get_controller_addr(slot_data['slot'], slot_data['port']);
            let metrics = {};
            let state_props = {};
            for (let i=0; i<selected_metrics.length; ++i) {
                let metr = selected_metrics[i];
                if (slot_data.hasOwnProperty(metr)) {
                    metrics[metr] = slot_data[metr];
                }
            }
            for (let field in slot_data) {
                if (slot_data.hasOwnProperty(field) && state_fields.indexOf(field) !== -1) {
                    state_props[field] = slot_data[field];
                }
            }

            let elements = [];
            elements.push(React.createElement(
                SlotLink,
                {
                    slot: slot_data['slot'], dc: slot_data['$datacenter$'],
                    port: slot_data['port'], key: 'link-' + key_part,
                    controller: controller
                },
                null)
            );
            if (getShowControls()) {
                elements.push(React.createElement(
                    SlotControls,
                    {
                        slot: slot_data['slot'],
                        port: slot_data['port'],
                        controller: controller,
                        key: 'ctrl-' + key_part
                    },
                    null)
                );
            }
            elements.push(React.createElement(
                SlotMetrics,
                {metrics: metrics, all_data: slot_data, key: 'metr-' + key_part},
                null)
            );
            elements.push(React.createElement(
                SlotState,
                {state_props: state_props, real_host: slot_data['$real_host$'], key: 'state-' + key_part},
                null)
            );
            let b_style = slot_style_class(slot_data);
            if (is_zero_rps(slot_data)) {
                b_style += ' b-zero';
            }
            return React.createElement(
                'td',
                {
                    className: b_style + ' slot-used',
                    id: 'cell-' + slot_data['slot'],
                    onClick: () => { switch_select_slot(slot_data['slot'], false) }
                },
                elements
            );
        }
    }

    class EndpointSetCell extends React.Component {
        render() {
            let slot_data = current_states['ans'][this.props.interval_index]['slots'][this.props.slot_index];
            let key_part = this.props.interval_index + '' + this.props.slot_index;
            let elements = [];
            elements.push(React.createElement(
                'a',
                {
                    key: 'eps-' + key_part,
                    href: '{% url "endpoints_get" %}?eps=' + slot_data['slot'],
                    target: '_blank',
                    title: 'This is endpointset, show slots'
                },
                slot_data['slot'].replace(new RegExp('_', 'g'), ' '))
            );
            elements.push(React.createElement('span',
                {key: 'epspan-'+key_part},
                ' ' + slot_data['$datacenter$']));
            return React.createElement(
                'td',
                {
                    className: 'endpointset',
                    id: 'cell-' + slot_data['slot'],
                    onClick: () => { switch_select_slot(slot_data['slot'], false) }
                },
                elements
            );
        }
    }

    class IntervalRow extends React.Component {
        render() {
            let row_data = current_states['ans'][this.props.index];
            let cols = [];
            cols.push(React.createElement('td', {'key': 'cell-' + row_data['id']}, row_data['id']));
            for (let i=0; i<row_data['slots'].length; ++i) {
                let slot_key = current_states['ans'][this.props.index]['slots'][i]['slot'];
                if (slot_key.indexOf('@') === -1) {
                    cols.push(React.createElement(
                        SlotCell,
                        {interval_index: this.props.index, slot_index: i,
                        'key': 'slot-' + slot_key},
                        null
                    ));
                }
            }
            for (let i=0; i<row_data['slots'].length; ++i) {
                let slot_key = current_states['ans'][this.props.index]['slots'][i]['slot'];
                if (slot_key.indexOf('@') !== -1) {
                    cols.push(React.createElement(
                        EndpointSetCell,
                        {interval_index: this.props.index, slot_index: i,
                        'key': 'slot-' + slot_key},
                        null
                    ));
                }
            }
            if (current_absents.hasOwnProperty(row_data['id'])) {
                let id = row_data['id'];
                for (let j=0; j<current_absents[id].length; ++j) {
                    cols.push(React.createElement(
                        'td',
                        {key: current_absents[id][j],
                        id: 'cell-' + current_absents[id][j],
                        className: 'slot-absent',
                        onClick: () => { switch_select_slot(current_absents[id][j], false) }},
                        'Absent ' + current_absents[id][j].substr(0,3)
                    ));
                }
            }
            return React.createElement('tr', null, cols);
        }
    }

    class ClusterTable extends React.Component {
        render() {
            let rows = [];
            try {
                precompute_absent_slots();
            } catch(err) {
                console.log(err);
            }
            for (let i=0; i<current_states['ans'].length; ++i) {
                rows.push(
                    React.createElement(IntervalRow,
                    {
                        'key': 'int_' + current_states['ans'][i]['id'],
                        'index': i
                    },
                    null)
                );
            }
            return React.createElement(
                'table',
                null,
                React.createElement('tbody', null, rows)
            );
        }
    }

    class ClusterUnusedTable extends React.Component {
        render() {
            if (!this.props.unused  || this.props.unused.length === 0) {
                return React.createElement('div', null, 'No free slots');
            }
            let raws = [[]];
            let craw = 0;
            let dc_stat = {};
            for (let i=0; i<this.props.unused.length; ++i) {
                for (let j=0; j<this.props.unused[i]['slots'].length; ++j) {
                    let slot_data = this.props.unused[i]['slots'][j];
                    let controller = get_controller_addr(slot_data['slot'], slot_data['port']);
                    let dc = slot_data['$datacenter$'];
                    let real_host = slot_data['$real_host$'];
                    dc_stat[dc] = (dc_stat.hasOwnProperty(dc) ? dc_stat[dc] : 0) + 1;
                    let els = [React.createElement(
                        SlotLink,
                        {
                            slot: slot_data['slot'], dc: slot_data['$datacenter$'],
                            port: slot_data['port'], key: 'link-unused-' + slot_data['slot'],
                            controller: controller
                        },
                        null),
                        React.createElement(
                            'div',
                            {key: 'st-unused-' + slot_data['slot']},
                            slot_data['result.controller_status'] + ' (' + real_host + ')'
                        )
                    ];
                    let b_style = slot_style_class(slot_data);
                    raws[craw].push(React.createElement(
                        'td',
                        {
                            key: 'unused-td-' + slot_data['slot'],
                            className: b_style + ' slot-unused',
                            id: 'cell-' + slot_data['slot'],
                            onClick: () => { switch_select_slot(slot_data['slot'], true); }
                        },
                        els));
                }
            }
            let evt = new CustomEvent('freeslotstat', {detail: {data: dc_stat}});
            window.dispatchEvent(evt);

            let raw_els = [];
            for (let i=0; i<raws.length; ++i) {
                raw_els.push(React.createElement('tr', {key: 'unused-tr-' + i}, raws[i]));
            }
            return React.createElement('table', null, React.createElement('tbody', null, raw_els));
        }
    }

    function update_selected_metrics() {
        let els = document.getElementsByClassName('metric-chbox');
        let new_selected_metrics = [];
        for (let i=0; i<els.length; ++i) {
            if (els[i].checked) {
                new_selected_metrics.push(els[i].getAttribute('data-field'));
            }
        }
        selected_metrics = new_selected_metrics;
    }

    function fill_metrics_chooser() {
        let el = document.getElementById('metrics-chooser');
        for (let i=0; i<possible_metrics.length; ++i) {
            let met = possible_metrics[i];
            if (!cluster_is_rty && met.hasOwnProperty('rty_only') && met['rty_only']) {
                continue;
            }
            let chb_el = document.createElement('input');
            chb_el.setAttribute('type', 'checkbox');
            chb_el.setAttribute('title', met['filter']);
            chb_el.className = 'metric-chbox';
            chb_el.setAttribute('data-field', met['filter']);
            if (met['default']) {
                chb_el.checked = true;
            }
            chb_el.onchange = function() {update_selected_metrics()};
            let legend_el = document.createElement('span');
            legend_el.innerHTML = met['caption'];

            let label_el = document.createElement('label');
            label_el.appendChild(chb_el);
            label_el.appendChild(legend_el);
            el.appendChild(label_el);
        }
        update_selected_metrics();
    }

    function update_time() {
        let date = new Date();
        function padStr(d) { return d < 10 ? '0' + d : d;}
        let txt = padStr(date.getHours()) + ':' + padStr(date.getMinutes()) + ':' + padStr(date.getSeconds());
        document.getElementById('ts').innerHTML = 'Updated ' + txt;
    }

    function update_unused_table(res) {
        try {
            let el = React.createElement(ClusterUnusedTable, {unused: res['ans']}, null);
            ReactDOM.render(el,
                document.getElementById('free_slots_table'));
        } catch(err) { console.log(err); }
    }

    function update_table(res) {
        current_states = res;
        if (isDataSuccess()) {
            try {
                let el = React.createElement(ClusterTable, null, null);
                ReactDOM.render(el, document.getElementById('content'));
                update_time();
            } catch (err) { console.log(err); }
        }
    }

    $(document).ready(function() {
        fill_metrics_chooser();
        try {
            let charts_link = document.getElementById('charts-link');
            if (!!charts_link) {
                let chref = '{% url "monitor_gui" %}?ctype=' + cluster_ctype;
                if (cluster_is_rty) {
                    chref += '&service=' + cluster_service;
                }
                charts_link.href = chref;
            }
        } catch(err) { console.log(err); }
        try {
            let porto_link = document.getElementById('porto-link');
            if (!!porto_link) {
                let chref = 'https://yasm.yandex-team.ru/template/panel/Porto-container/hosts=ASEARCH';
                let itype = get_itype();
                let ctype_tag = 'prod';
                let prj = 'saas';
                if (cluster_ctype.search('stable') === 0) {
                    prj += cluster_ctype.replace('stable', '');
                }
                if (cluster_is_rty) {
                    if (cluster_ctype.search('stable') === 0) {
                        prj += '-' + cluster_service;
                    }
                }
                prj = prj.replace(new RegExp('_', 'g'), '-');
                if (cluster_ctype === 'prestable') { ctype_tag = 'hamster'; }
                else if (cluster_ctype === 'stable_hamster') { ctype_tag = 'priemka'; }
                else if (cluster_ctype === 'testing') { ctype_tag = 'test'; }
                chref += ';itype=' + itype;
               // chref += ';ctype=' + ctype_tag;
                chref += ';prj=' + prj;
                porto_link.href = chref;

                let volume_link = document.getElementById('volume-link');
                let vref = 'https://yasm.yandex-team.ru/chart/hosts=ASEARCH';
                vref += ';itype=' + itype;
         //       vref += ';ctype=' + ctype_tag;
                vref += ';prj=' + prj;
                vref += ';graphs=%7Bportoinst-capacity-perc_usage_*_txxx%7D,%7Bportoinst-volume_*_usage_perc_txxx%7D,%7Bportoinst-volume_*_usage_txxx%7D';

                volume_link.href = vref;

                let io_link = document.getElementById('io-link');
                let ihref = 'https://yasm.yandex-team.ru/template/panel/saas_io_usage/hosts=ASEARCH';
                ihref += ';itype=' + itype;
                ihref += ';prj=' + prj;
                io_link.href = ihref;
            }
        } catch(err) { console.log(err); }
        do_data_load(cluster_updates);
    });
</script>

<body>
<div id="metrics-chooser">
</div>
{% if is_root %}
<label style="color:#818c23;font-size:larger">
    <input type="checkbox" id="show-controls" onchange="update_table(current_states)">Show controls
</label>
{% endif %}
<div>
<div id="cluster_answer_dup" style="border-left: 5px solid #f0f5e8;float:left"></div>
    <div style="float:right">
        <a id="porto-link" href="https://yasm.yandex-team.ru/template/panel/Porto-container/" target="_blank">porto</a>
        <a id="io-link" href="https://yasm.yandex-team.ru/template/panel/saas_io_usage/" target="_blank">iops</a>
        <a id="volume-link" href="https://yasm.yandex-team.ru/" target="_blank">volumes</a>
        <a id="charts-link" href="{% url 'monitor_gui' %}?ctype={{ ctype }}" target="_blank">charts</a>
        {% if is_root %}
        <button id="b-is-backup" onclick="show_is_backup()">Is backup</button>
        {% endif %}
    </div>
</div>
<div id="content" style="clear: right">

</div>
{{ service }}
{{ ctype }}
<div id="ts"></div>
<div id="cluster_answer"></div>
<div id="raw_data" hidden></div>

</body>
</html>
