<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cluster map {{ ctype }}</title>
    <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"></script>
    <script src="https://yandex.st/bootstrap/3.3.4/js/bootstrap.min.js"></script>
    <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>
</head>
<style type='text/css'>
.progress {
    margin-bottom: 0;
}
.warning {
    background-color: #f0ad4e;
    border-radius: 5px;
}
.danger {
    background-color: red;
    border-radius: 5px;
}
.normal {
    background-color: #d4ff33;
    border-radius: 5px;
}
.very_danger {
    background-color: brown;
    border-radius: 5px;
    color: white;
}
td.clock-cell {
    font: 16pt bolt;
    color: #880000;
    text-align: center;
}
table {
    border-spacing: 2px;
    border-collapse: separate;
}
    td {
        border: 1px solid #566145;
    }
    th {
        border: 1px solid #566145;
    }
    .td-interval {
        border: none;
    }
    .btn-main-link {
        background-color: #cd5f62;
    }
    .btn-main-link:after {
        border-left-color: #cd5f62;
    }
    .page-title {
        color: #205d20;
        cursor: pointer;
    }

</style>
<script type="text/javascript">
    const ctype = '{{ ctype }}';

    const proxy_updates = {
        'answer_id': 'answer_proxy',
        'on_load': update_proxies,
        'url': '{% url "cluster_map_proxies" %}?ctype=' + ctype
    };
    const cluster_updates = {
        'answer_id': 'answer_cluster',
        'on_load': update_cluster,
        'url': '{% url "cluster_map_repshards" %}?ctype=' + ctype
    };
    const queues_updates = {
        'answer_id': 'answer_queues',
        'on_load': update_queues,
        'url': '{% url "cluster_queues" %}?ctype=' + ctype
    };
    const queues_dailystat_updates = {
        'do_once': true,
        'answer_id': 'answer_yasm',
        'on_load': function(res) { queues_daily_stat = res; },
        'url': '{% url "index_rates" %}?ctype=' + ctype
    };
    const queue_bytes_limits = [1500000000000, 1500000000, 1500000, 1500];
    const queue_ratio_limits = [5, 1, 0.2, 0];

    let cluster_states = {};
    let proxies_states = {};
    let queues_states = {};
    let queues_daily_stat = {};

    let first_updates = {};

    function set_answer(status, answer, id) {
        do_set_answer(status, answer, id);
    }

    function isHidden() {
        let hidden = false;
        try {
            hidden = document.hidden;
        } catch(err) { hidden = false;}
        return hidden;
    }

    function data_load(callback_data) {
        let ans_id = callback_data['answer_id'];
        if (isHidden() && first_updates.hasOwnProperty(ans_id)) {
            setTimeout(data_load, 5000, callback_data);
            set_answer(statuses.hidden, 'window not visible, update paused', callback_data['answer_id']);
            return;
        }
        set_answer(statuses.loading, 'loading data...', ans_id);
        let xmlhttp = getXmlHttp();
        let url = callback_data['url'];
        url += '&skip_eps=' + document.getElementById('chb_skip_eps').checked;
        xmlhttp.open('GET', url);
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState === 4) {
                let ans_id = callback_data['answer_id'];
                if (xmlhttp.status === 200) {
                    try {
                        let res = JSON.parse(xmlhttp.responseText);
                        if (res['success']) {
                            callback_data['on_load'](res);
                            set_answer(statuses.ok, 'cluster info updated', ans_id);
                            if (callback_data.hasOwnProperty('do_once') && callback_data['do_once']) {
                                return;
                            }
                        } else {
                            set_answer(statuses.error, 'cluster info with error: ' + xmlhttp.responseText, ans_id);
                        }
                    } catch(err) {
                        console.log(err);
                        set_answer(statuses.error, 'error on answer parse: ' + err, ans_id);
                    }
                } else {
                    console.log('not 200');
                    set_answer(statuses.error, xmlhttp.responseText, ans_id);
                }
                first_updates[ans_id] = true;
                setTimeout(data_load, 5000, callback_data);
            }
        };
        xmlhttp.send();
    }

    function do_hosts_stat(inp) {
        let stat = {};
        stat['active'] = inp.hasOwnProperty('Active') ? inp['Active'] : 0;
        stat['downs'] = inp.hasOwnProperty('Down') ? inp['Down'] : 0;
        stat['flagged'] = inp.hasOwnProperty('Flagged') ? inp['Flagged'] : 0;
        let other = 0;
        for (let field in inp) {
            if (inp.hasOwnProperty(field)
                && field !== 'Active' && field !== 'Down' && field !== 'Flagged') {
                other += inp[field];
            }
        }
        stat['other'] = other;
        stat['all'] = stat['active'] + stat['downs'] + stat['other'] + stat['flagged'];
        stat['active_perc'] = 100 * stat['active'] / stat['all'];
        stat['downs_perc'] = 100 * stat['downs'] / stat['all'];
        stat['flagged_perc'] = 100 * stat['flagged'] / stat['all'];
        stat['other_perc'] = 100 * stat['other'] / stat['all'];
        stat['metric'] = stat['downs_perc'] + 0.5 * stat['other_perc'] + 0.25 * stat['flagged_perc'];
        return stat;
    }

    function compute_metrics(shards_stat, replicas_stat) {
        let metric = 0;
        if (shards_stat.hasOwnProperty('intervals')) {
            let intervals = shards_stat['intervals'];
            let cnt = 0;
            let max_perc = 0;
            for (let intrv in intervals) {
                if (intervals.hasOwnProperty(intrv)) {
                    let stat = do_hosts_stat(intervals[intrv]['result.controller_status']);
                    metric += stat['downs_perc'] + stat['other_perc'] + stat['flagged_perc'];
                    ++cnt;
                    max_perc = Math.max(max_perc, stat['metric']);
                }
            }
            metric = metric / cnt;
            metric += max_perc;
        }
        if (replicas_stat.hasOwnProperty('\\$datacenter_alias\\$s')) {
            let dcs = replicas_stat['\\$datacenter_alias\\$s'];
            let add_metric = 0;
            let cnt = 0;
            let max_perc = 0;
            for (let dc in dcs) {
                if (dcs.hasOwnProperty(dc) && dcs[dc].hasOwnProperty('replic_ids')) {
                    let repids = dcs[dc]['replic_ids'];
                    for (let repid in repids) {
                        if (repids.hasOwnProperty(repid)) {
                            let stat = do_hosts_stat(repids[repid]['result.controller_status']);
                            add_metric += stat['downs_perc'] + stat['other_perc'] + stat['flagged_perc'];
                            ++cnt;
                            max_perc = Math.max(max_perc, stat['metric']);
                        }
                    }
                }
            }
            metric += add_metric / cnt;
            metric += max_perc;
        }
        return metric;
    }

    class IntervalCell extends React.Component {
        render() {
            let stats = this.props.stat;
            let elements = [];
            elements.push(React.createElement(
                'div',
                {
                    key: 'danger',
                    className: 'progress-bar progress-bar-danger',
                    role: 'progressbar',
                    style: {width: stats['downs_perc'] + '%'}
                },
                stats['downs']
            ));
            elements.push(React.createElement(
                'div',
                {
                    key: 'warn',
                    className: 'progress-bar progress-bar-warning',
                    role: 'progressbar',
                    style: {width: stats['other_perc'] + '%'}
                },
                stats['other']
            ));
            elements.push(React.createElement(
                'div',
                {
                    key: 'info',
                    className: 'progress-bar progress-bar-info',
                    role: 'progressbar',
                    style: {width: stats['flagged_perc'] + '%'}
                },
                stats['flagged']
            ));
            elements.push(React.createElement(
                'div',
                {
                    key: 'active',
                    className: 'progress-bar progress-bar-success',
                    role: 'progressbar',
                    style: {width: stats['active_perc'] + '%'}
                },
                stats['active']
            ));
            return React.createElement('td', {className: 'td-interval'}, React.createElement(
                'div',
                {
                    className: 'progress',
                    title: this.props.title,
                },
                elements));
        }
    }

    class ServiceCell extends React.Component {
        render() {
            let intervals = cluster_states[this.props.kind]['ans'][this.props.service];
            let elements = [];
            let cells = [];
            const per_row = 15;
            if (this.props.kind === 'shards') {
                let shs = intervals['intervals'];
                for (let intv in shs) {
                    if (!shs.hasOwnProperty(intv)) {
                        continue;
                    }
                    let stat = do_hosts_stat(shs[intv]['result.controller_status']);
                    cells.push(React.createElement(
                        IntervalCell,
                        {
                            stat: stat,
                            key: this.key + '-' + intv,
                            title: 'interval: ' + intv
                        },
                        null));
                }
            } else {
                let dcs = intervals['\\$datacenter_alias\\$s'];
                for (let dc in dcs) {
                    if (!dcs.hasOwnProperty(dc)) {
                        continue;
                    }
                    for (let repid in dcs[dc]['replic_ids']) {
                        if (!dcs[dc]['replic_ids'].hasOwnProperty(repid)) {
                            continue;
                        }
                        let stat = do_hosts_stat(dcs[dc]['replic_ids'][repid]['result.controller_status']);
                        cells.push(React.createElement(
                            IntervalCell,
                            {
                                stat: stat,
                                key: this.key + '-' + repid,
                                title: 'replic_id: ' + repid
                            },
                            null));
                    }
                }
            }
            cells.sort(function(a, b) {return b.props.stat.metric - a.props.stat.metric});
            for (let i=0; i<cells.length; i+= per_row) {
                elements.push(React.createElement(
                    'tr',
                    {key: 'tr-' + i + '-' + this.key},
                    cells.slice(i, Math.min(i+per_row, cells.length))
                ));
            }
            return React.createElement(
                'td', null,
                React.createElement('table', null,
                    React.createElement('tbody', null,
                       elements
                    )
                )
            );
        }
    }

    class ClusterTable extends React.Component {
        render() {
            let elements = [];
            let serv_data = {};
            let serv_metrics = [];
            let services = {};
            let serv_shards = cluster_states['shards']['ans'];
            let serv_replicas = cluster_states['replicas']['ans'];
            for (let service in serv_shards) {
                if (serv_shards.hasOwnProperty(service)) {
                    services[service] = '';
                }
            }
            for (let service in serv_replicas) {
                if (serv_replicas.hasOwnProperty(service)) {
                    services[service] = '';
                }
            }
            for (let service in services) {
                if (!services.hasOwnProperty(service)) {
                    continue;
                }
                let metric = compute_metrics(
                    serv_shards.hasOwnProperty(service) ? serv_shards[service] : {},
                    serv_replicas.hasOwnProperty(service) ? serv_replicas[service] : {}
                );
                serv_metrics.push({service: service, metric: metric});
                let serv_tds = [];
                serv_tds.push(React.createElement(
                    'td',
                    {key: 'sn1-'+service},
                    React.createElement(
                        'a',
                        {'href': '{% url "deploy_page" %}?ctype={{ ctype }}&service=' + service,
                        'target': '_blank'},
                        service)));

                if (serv_replicas.hasOwnProperty(service)) {
                    let serv_el = React.createElement(
                        ServiceCell,
                        {key:'replicas-' + service, service: service, kind: 'replicas'},
                        null
                    );
                    serv_tds.push(serv_el);
                } else {
                    serv_tds.push(React.createElement('td', {key:'replicas-' + service}, null));
                }
                if (serv_shards.hasOwnProperty(service)) {
                    let serv_el = React.createElement(
                        ServiceCell,
                        {key: 'shards-' + service, service: service, kind: 'shards'},
                        null
                    );
                    serv_tds.push(serv_el);
                } else {
                    serv_tds.push(React.createElement('td', {key: 'shards-' + service}, null));
                }
                serv_data[service] = React.createElement('tr', {key: 'tr-'+service}, serv_tds);
            }
            serv_metrics.sort(function(a, b) { return b.metric - a.metric; });
            for (let i=0; i<serv_metrics.length; ++i) {
                let service = serv_metrics[i]['service'];
                elements.push(serv_data[service]);
            }
            let th_cells = [
                React.createElement('th', {key:'serv-th'}, ''),
                React.createElement('th', {key: 'serv-reps-th'}, 'Replicas'),
                React.createElement('th', {key: 'serv-shards-th'}, 'Shards')
            ];
            return React.createElement(
                'table', null,
                [
                    React.createElement('thead', {key: 'serv-thead'}, React.createElement('tr', null, th_cells)),
                    React.createElement('tbody', {key: 'serv-tbody'}, elements)
                ]);
        }
    }

    class ProxiesTable extends React.Component {
        render() {
            let th = [
                React.createElement(
                    'th',
                    {key: 'prx-th-spr'},
                    React.createElement('a', {
                        href: '{% url "cluster_info" %}?ctype=' + ctype + '&service=searchproxy',
                        target: '_blank'
                    }, 'searchproxy'))
            ];
            let spr_dcs = [React.createElement('td', {key: 'spr-emp'}, null)];
            for (let dc in proxies_states['searchproxy']['ans']) {
                if (!proxies_states['searchproxy']['ans'].hasOwnProperty(dc)) {
                    continue;
                }
                th.push(React.createElement('th', {key: 'spr-dc-' + dc}, dc));
                spr_dcs.push(React.createElement(
                    IntervalCell,
                    {
                        stat: do_hosts_stat(proxies_states['searchproxy']['ans'][dc]['result.controller_status']),
                        key: 'spr-state-' + dc, title: 'searchproxy ' + dc
                    },
                    null));
            }

            th.push(React.createElement('th',
                {key: 'prx-th-ipr'},
                React.createElement('a', {
                        href: '{% url "cluster_info" %}?ctype=' + ctype + '&service=indexerproxy',
                        target: '_blank'
                    }, 'indexerproxy')));
            spr_dcs.push(React.createElement('td', {key: 'ipr-emp'}, null));

            for (let dc in proxies_states['indexerproxy']['ans']) {
                if (!proxies_states['indexerproxy']['ans'].hasOwnProperty(dc)) {
                    continue;
                }
                th.push(React.createElement('th', {key: 'ipr-dc-' + dc}, dc));
                spr_dcs.push(React.createElement(
                    IntervalCell,
                    {
                        stat: do_hosts_stat(proxies_states['indexerproxy']['ans'][dc]['result.controller_status']),
                        key: 'ipr-state-' + dc, title: 'indexerproxy ' + dc
                    },
                    null));
            }
            return React.createElement('table', null,
                [
                    React.createElement('thead', {key: 'prx-thead'}, React.createElement('tr', null, th)),
                    React.createElement('tbody', {key: 'prx-tbody'}, React.createElement('tr', null, spr_dcs))
                ]);
        }
    }

    function queue_format(size, limits) {
        if (isNaN(size)) { return {txt: size, cls: 'absent'}; }
        size = Number(size);
        let txt = size;
        if (size > 100000000) {
            txt =  (Math.round(size / 10000000) / 100) + 'G';
        } else if (size > 100000) {
            txt = (Math.round(size / 10000) / 100) + 'M';
        } else if(size > 100) {
            txt = (Math.round(size / 10) / 100) + 'k';
        } else {
            txt = Math.round(100* size) / 100;
        }
        let cls = 'absent';
        if (limits.length > 0 && size > limits[0]) {
            cls = 'very_danger';
        } else if (limits.length > 1 && size > limits[1]) {
            cls = 'danger';
        } else if (limits.length > 2 && size > limits[2]) {
            cls = 'warning';
        } else { cls = 'normal'; }
        return {txt: txt, cls: cls};
    }

    function get_daily_index_docs_ratio(service, qdocs) {
        try {
            if (!queues_daily_stat.hasOwnProperty('result') || !queues_daily_stat['result'].hasOwnProperty(service)) {
                return '-';
            }
            let qdaily = queues_daily_stat['result'][service];
            if (qdaily > 0) {
                return qdocs / qdaily;
            } else {
                return qdocs > 500 ? 100500 : 0;
            }
        } catch(err) {
            console.log(err);
            return '-';
        }
    }

    class QueuesTable extends React.Component {
        render() {
            let elements = [];
            for (let service in queues_states['ans']) {
                if (!queues_states['ans'].hasOwnProperty(service)) {
                    continue;
                }
                let cells = [];
                let queue_bytes = queues_states['ans'][service]['space_bytes'];
                let queue_docs = queues_states['ans'][service]['docs'];
                let qbytes_format = queue_format(queue_bytes, queue_bytes_limits);
                let qdocs_format = queue_format(queue_docs, queue_bytes_limits);
                let qratio = get_daily_index_docs_ratio(service, queue_docs);
                let qratio_format = queue_format(qratio, queue_ratio_limits);
                cells.push(React.createElement('td', {key: 'queues-sn-' + service}, service));
                cells.push(React.createElement(
                    'td',
                    {key: 'queues-bytes-' + service, className: qbytes_format.cls},
                    qbytes_format.txt));
                cells.push(React.createElement(
                    'td',
                    {key: 'queues-docs-' + service, className: qdocs_format.cls},
                    qdocs_format.txt));
                cells.push(React.createElement(
                    'td',
                    {key: 'queues-ratio-' + service, className: qratio_format.cls},
                    qratio_format.txt));
                elements.push(React.createElement(
                    'tr',
                    {key: 'queues-' + service, qbytes: queue_bytes},
                    cells));
            }
            elements.sort(function(a, b) {return b.props.qbytes - a.props.qbytes});
            return React.createElement('table', null, [
                React.createElement('thead', {key: 'queues-th'},
                    React.createElement('tr', null, [
                        React.createElement('th', {key: 'queue-th-0'}, ''),
                        React.createElement('th', {key: 'queue-th-1'}, 'bytes'),
                        React.createElement('th', {key: 'queue-th-2'}, 'docs'),
                        React.createElement('th', {key: 'queue-th-3'}, 'docs/daily')
                    ])),
                React.createElement('tbody', {key: 'queues-tb'}, elements)
            ]);
        }
    }

    function update_proxies(res) {
        proxies_states = res;
        let el = React.createElement(ProxiesTable, null, null);
        ReactDOM.render(el, document.getElementById('proxy_states'));
    }

    function update_queues(res) {
        queues_states = res;
        let el = React.createElement(QueuesTable, null, null);
        ReactDOM.render(el, document.getElementById('queues'));
    }

    function update_cluster(res) {
        cluster_states = res;
        let el = React.createElement(ClusterTable, null, null);
        ReactDOM.render(el, document.getElementById('service_repshards'));
    }

    $(document).ready(function() {
        data_load(proxy_updates);
        data_load(cluster_updates);
        data_load(queues_updates);
        data_load(queues_dailystat_updates);
    });
</script>

{% include 'nav_ctype.html' %}
<body>
<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 map<span class="caret"></span></span>
        <ul id="nav-screens-menu" class="dropdown-menu">
            <li><a href="{% url 'sla_info' %}?ctype={{ ctype }}">SLA</a></li>
            <li><a href="{% url 'cluster_map' %}?ctype={{ ctype }}">Cluster map</a></li>
            <li><a href="{% url 'nanny_info' %}?ctype={{ ctype }}">Nanny info</a></li>
        </ul></span>
<span id="ctype_selector">{{ ctype }}</span><span class="caret"></span>

<table>
    <tr><td id="proxy_states"></td>
        <td style="border:white;vertical-align: bottom"><label>
            <input type="checkbox" style="margin-left:10px" id="chb_skip_eps" checked> skip endpointsets</label></td></tr>
</table>
<table><tr>
    <td>
        <div id="service_repshards"></div>
    </td>
    <td style="vertical-align: top">
        <div id="queues"></div>

    </td>
</tr></table>
<div id="answer_proxy"></div>
<div id="answer_cluster"></div>
<div id="answer_queues"></div>
<div id="answer_yasm"></div>
</body>
</html>
