<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cluster hw {{ service }} {{ ctype }}</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>
    <link href="https://yandex.st/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
    <link href="{% static 'css/navigation.css' %}" rel="stylesheet" />
    <script src="https://yandex.st/jquery/2.0.3/jquery.min.js" type="text/javascript"></script>
    <script src="https://yandex.st/bootstrap/3.3.4/js/bootstrap.min.js" type="text/javascript"></script>
</head>

<style>
    .btn-main-link {
        background-color: #9ac16d;
    }
    .btn-main-link:after {
        border-left-color: #9ac16d;
    }
    .page-title {
        color: black;
        cursor: pointer;
    }
    .nav-service-link {
        color: #4d4d4d;
    }
    .nav-service-div {
        border-right: 2px solid #f3faeb;
    }
    .b-ok {
        background-color: #eef7e4;
    }
    .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;
    }
    td {
        border: 1px solid grey;
        vertical-align: top;
    }
    .slot-line {
        border-top: 1px solid #d8d9d8;
    }
</style>

<script type='text/javascript'>
    let current_states = {};
    let service = '{{ service }}';
    let ctype = '{{ ctype }}';
    let is_root = {% if is_root %}true{% else %}false{% endif %};

    let selected_metrics = ['result.controller_uptime',
        'result.cpu_load_user', 'result.mem_size_real'
    ];
    let selected_filters = [];

    let selected_slots = {};

    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; }
    };

    let first_updates = {};

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

    const possible_metrics = [
        {"caption" : "sources", "filter":"result.search_sources_count", "default": false},
        {"caption" : "docs in final", "filter":"result.docs_in_final_indexes", "default": false},
        {"caption" : "docs in mem", "filter":"result.docs_in_memory_indexes", "default": false},
        {"caption" : "docs in disk", "filter":"result.docs_in_disk_indexes", "default": false},
        {"caption" : "doccount", "filter":"docs_count", "default": false},
        {"caption" : "size", "filter":"result.files_size.__SUM", "default": false},
        {"caption" : "uptime", "filter":"result.controller_uptime", "default": true},
        {"caption" : "server_uptime", "filter":"result.server_uptime", "default": false},
        {"caption" : "mem", "filter":"result.mem_size_real", "default": true},
        {"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": true},
        {"caption" : "cpu_policy", "filter":"result.slot.container.constraints.cpu_policy", "default": false},
        {"caption" : "cpu_limit", "filter":"result.slot.container.constraints.cpu_limit", "default": false},
        {"caption" : "la", "filter":"result.load_average", "default": true},
        {"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" : "nanny_service", "filter":"result.slot.properties.NANNY_SERVICE_ID", "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'
    ];

    const field2constraints = {
        'result.cpu_load_user': ['result.slot.container.constraints.cpu_limit',
            'result.cpu_load_system', 'result.slot.container.constraints.cpu_guarantee'],
        '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';
            }
        },
    };

    const fields2format = {
        'result.cpu_load_user': function(fields) {
            try {
                let cpu_user = Number(fields['result.cpu_load_user']);
                let cpu_sys = Number(fields['result.cpu_load_system']);
                let cpu_sum = cpu_user + cpu_sys;
                let cpu_limit = fields['result.slot.container.constraints.cpu_limit'];
                if (isNaN(cpu_limit)) {
                    cpu_limit = Number(cpu_limit.slice(0, cpu_limit.length - 1))
                } else { cpu_limit = Numbet(cpu_limit); }
                let cpu_gar = fields['result.slot.container.constraints.cpu_guarantee'];
                if (isNaN(cpu_gar)) {
                    cpu_gar = Number(cpu_gar.slice(0, cpu_gar.length - 1))
                } else { cpu_gar = Number(cpu_gar); }
                cpu_limit = Math.ceil(100*cpu_limit);
                cpu_gar = Math.ceil(100*cpu_gar);
                return cpu_user + '/' + cpu_sum + '/' + cpu_limit + '(' + cpu_gar + ')';
            } catch(err) {
                console.log(err);
            }
            return fields['result.cpu_load_user'];
        },
        'result.mem_size_real': function(fields) {
            try {
                let mem_used = Math.ceil(Number(fields['result.mem_size_real']) / 1024);
                let mem_limit = Math.ceil(Number(fields['result.slot.container.constraints.memory_limit']) / (1024 * 1024 * 1024));
                return mem_used + '/' + mem_limit + 'Gb';
            } catch(err) {
                console.log(err);
            }
            return fields['result.mem_size_real'];
        },
        'result.load_average': function(fields) {
            return fields['result.load_average'];
        }
    };

    function isDataSuccess() {
        try {
            return current_states['success'];
        } catch(err) { return false; }
    }

    function set_answer(status, answer) {
        do_set_answer(status, answer, 'cluster_answer');
    }

    function get_cluster_info_url() {
        let url = '{% url "cluster_total_table" %}?service=' + service + '&ctype=' + ctype;
        let filters_param = selected_filters.join(',');
        url += '&filters=' + filters_param;
        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 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 (!!slot_data['invalid_config_files']) {
            return 'b-not-actual';
        }
        if (status === 'Active' && status_data === 'OK') {
            return 'b-ok';
        }
        return 'b-not-parsed';
    }


    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 {
                        metr_props['style'] = {'backgroundColor': fields2color[met](this.props.all_data)};
                    } catch(err) { console.log(err); }
                }
                let metric_text = this.props.metrics[met];
                if (fields2format.hasOwnProperty(met)) {
                    metric_text = fields2format[met](this.props.all_data);
                }
                metric_els.push(React.createElement(
                    'span',
                    metr_props,
                    metric_text
                ));
                metric_els.push(React.createElement(
                    'span',
                    {key: this.key + '-s-' + met},
                    ' '
                ));
            }
            return React.createElement('div', null, metric_els);
        }
    }

    class SlotLine extends React.Component {
        render() {
            let elements = [];
            let slot_data = this.props.slot_data;
            let key_part = slot_data['id'];

            let port = slot_data['slot'].slice(slot_data['slot'].lastIndexOf(':')+1);
            let controller = get_controller_addr(slot_data['slot'], port);
            elements.push(React.createElement(
                'a',
                {
                    key: 'link-' + key_part,
                    target: '_blank',
                    href: 'http://' + controller + '/?command=get_info_server'
                },
                port)
            );
            elements.push(React.createElement(
                'span',
                {key: 'serv-s-' + key_part},
                ' '
            ));
            let serv_props = {key: 'serv-' + key_part};
            if (slot_data['service'] === service) {
                serv_props['style'] = {'font-weight': 'bold'};
            }
            elements.push(React.createElement(
                'span',
                serv_props,
                slot_data['service']
            ));

            let metrics = {};
            for (let i=0; i<selected_metrics.length; ++i) {
                let metr = selected_metrics[i];
                if (slot_data.hasOwnProperty(metr)) {
                    metrics[metr] = slot_data[metr];
                }
            }

            elements.push(React.createElement(
                SlotMetrics,
                {metrics: metrics, all_data: slot_data, key: 'metr-' + key_part},
                null)
            );
            let b_style = slot_style_class(slot_data);
            return React.createElement(
                'div',
                {className: 'slot-line ' + b_style},
                elements
            );
        }
    }

    class HostHead extends React.Component {
        render() {
            let elements = [];
            let host = this.props.host_data['id'];
            let host_short = host.slice(0, host.indexOf('.search'));
            elements.push(React.createElement(
                'span',
                {key: 'name-' + this.props.host_data['id']},
                host_short
            ));
            elements.push(React.createElement(
                'span',
                {key: 'cpu-' + this.props.host_data['id']},
                ' ' + this.props.host_data['result.cpu_count']+'cpu'
            ));
            let mem_gb = Math.ceil(Number(this.props.host_data['result.total_mem_size']) / (1024 * 1024 * 1024));
            elements.push(React.createElement(
                'span',
                {key: 'mem-' + this.props.host_data['id']},
                ' ' + mem_gb +'Gb'
            ));
            elements.push(React.createElement(
                'span',
                {key: 'la-' + this.props.host_data['id']},
                ' la' + this.props.host_data['result.load_average']
            ));
            return React.createElement('div', {style: {fontWeight: 'bold'}}, elements);
        }
    }

    class HostCell extends React.Component {
        render() {
            let elements = [];
            let head_props = {};
            for (let prop in this.props.data) {
                if (this.props.data.hasOwnProperty(prop) && prop !== 'slots') {
                    head_props[prop] = this.props.data[prop];
                }
            }
            elements.push(React.createElement(
                HostHead,
                {host_data: head_props, key: 'host-' + head_props['id']},
                null)
            );
            for (let i=0; i<this.props.data['slots'].length; ++i) {
                elements.push(React.createElement(
                    SlotLine,
                    {
                        slot_data: this.props.data['slots'][i],
                        key: 'slot-' + this.props.data['slots'][i]['id']
                    },
                    null
                ))
            }
            return React.createElement(
                'td',
                {},
                elements
            );
        }
    }

    class IntervalRow extends React.Component {
        render() {
            let row_data = [];
            const ind = this.props.index;
            for (let i=0; i<current_states['ans'].length; ++i) {
                if (ind < current_states['ans'][i]['hosts'].length) {
                    row_data.push(current_states['ans'][i]['hosts'][ind]);
                } else {
                    row_data.push(null);
                }
            }
            let cols = [];
            for (let i=0; i<row_data.length; ++i) {
                if (row_data[i]) {
                    cols.push(React.createElement(
                        HostCell,
                        {data: row_data[i],
                        key: 'host-' + this.props.index + '-' + i},
                        null
                    ));
                } else {
                    cols.push(React.createElement(
                        'td',
                        {key: 'empty-' + this.props.index + '-' + i},
                        null));
                }
            }
            return React.createElement('tr', null, cols);
        }
    }

    class ClusterTable extends React.Component {
        render() {
            let rows = [];
            let max_len = 0;
            for (let i=0; i<current_states['ans'].length; ++i) {
                if (current_states['ans'][i]['hosts'].length > max_len) {
                    max_len = current_states['ans'][i]['hosts'].length;
                }
            }
            for (let i=0; i < max_len; ++i) {
                rows.push(
                    React.createElement(IntervalRow,
                    {
                        'key': 'host_number_' + i,
                        'index': i
                    },
                    null)
                );
            }
            return React.createElement(
                'table',
                null,
                React.createElement('tbody', null, rows)
            );
        }
    }

    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 update_selected_filters() {
        let els = document.getElementsByClassName('filter-chbox');
        let new_selected_filters = [];
        for (let i=0; i<els.length; ++i) {
            if (els[i].checked) {
                new_selected_filters.push(els[i].getAttribute('data-field'));
            }
        }
        if (new_selected_filters.indexOf('all_neighbors') !== -1
            && new_selected_filters.indexOf('neighbors') === -1) {
            new_selected_filters.push('neighbors');
        }
        selected_filters = new_selected_filters;
    }

    function fill_filters_chooser() {
        let el = document.getElementById('filters-chooser');
        const possible_filters = ['neighbors', 'all_neighbors', 'used', 'unused'];
        for (let i=0; i<possible_filters.length; ++i) {
            let chb_el = document.createElement('input');
            chb_el.setAttribute('type', 'checkbox');
            chb_el.className = 'filter-chbox';
            chb_el.setAttribute('data-field', possible_filters[i]);
            chb_el.onchange = function() {update_selected_filters()};

            let legend_el = document.createElement('span');
            legend_el.innerHTML = possible_filters[i];
            let label_el = document.createElement('label');
            label_el.appendChild(chb_el);
            label_el.appendChild(legend_el);
            el.appendChild(label_el);
        }
        update_selected_filters();
    }

    function fill_metrics_chooser() {
        let el = document.getElementById('metrics-chooser');
        for (let i=0; i<possible_metrics.length; ++i) {
            let met = possible_metrics[i];
            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();
        let txt = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
        document.getElementById('ts').innerHTML = 'Updated ' + txt;
    }

    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_filters_chooser();
        fill_metrics_chooser();
        do_data_load(cluster_updates);
    });
</script>
{% include "nav_ctype.html" %}
{% include "navigation.html" %}
<body>
<div style="border-bottom: 1px solid #e3f2d3">
    <button class="btn-main-link"><a href="{% url 'main' %}" class="main-link">SAAS</a></button>
    <span class="dropdown"><span class="page-title" data-toggle="dropdown">Cluster hardware<span class="caret"></span></span>
        <ul id="nav-screens-menu" class="dropdown-menu">
            <li><a href="{% url 'deploy_page' %}?service={{ service }}&ctype={{ ctype }}">Cluster & deploy</a></li>
            <li><a href="{% url 'cluster_total' %}?service={{ service }}&ctype={{ ctype }}">Cluster hardware</a></li>
            <li><a href="{% url 'service_info' %}?service={{ service }}&ctype={{ ctype }}">Service conf</a></li>
            <li><a href="{% url 'monitor_hosts' %}?service={{ service }}&ctype={{ ctype }}">Monitor hosts</a></li>
            <li><a href="{% url 'monitor_gui' %}?service={{ service }}&ctype={{ ctype }}">Monitor</a></li>
        </ul></span>
    <span id="ctype_selector" style="font-weight:bold">{{ ctype }}</span><span class="caret"></span>
    <span class="nav-service-link" style="margin-left:10px;font-weight:bold;color:#4CAF50">{{ service }}</span>
</div>
<table>
    <tr>
        <td id="nav-services-list" style="vertical-align: top;border:none"></td>
        <td style="vertical-align: top;border:none">

<div id="filters-chooser">

</div>
<div id="metrics-chooser" hidden>
</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 id="cluster_answer_dup" style="border-left: 5px solid #eff5e8"></div>
<div id="content">

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

        </td>
    </tr>
</table>
</body>
</html>
