%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /backups/router/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/
Upload File :
Create Path :
Current File : //backups/router/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/health.volt

<!--
/*
*    Copyright (C) 2023 Deciso B.V.
*    Copyright (C) 2015 Jos Schellevis <jos@opnsense.org>
*    All rights reserved.
*
*    Redistribution and use in source and binary forms, with or without
*    modification, are permitted provided that the following conditions are met:
*
*    1. Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*    2. Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*
*    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
*    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
*    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*    AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
*    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
*    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
*    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*    POSSIBILITY OF SUCH DAMAGE.
*/
-->

<style type="text/css">
.panel-heading-sm{
    height: 28px;
    padding: 4px 10px;
}
</style>

<!-- nvd3 -->
<link rel="stylesheet" type="text/css" href="{{ cache_safe(theme_file_or_default('/css/nv.d3.css', ui_theme|default('opnsense'))) }}" />

<!-- d3 -->
<script src="{{ cache_safe('/ui/js/d3.min.js') }}"></script>

<!-- nvd3 -->
<script src="{{ cache_safe('/ui/js/nv.d3.min.js') }}"></script>

<!-- System Health -->
<style>

    #chart svg {
        height: 500px;
    }

</style>


<script>
    let chart;
    let data = [];
    let disabled = [];
    let resizeTimer;
    let current_detail = 0;
    let csvData = [];
    let zoom_buttons;
    let rrd="";


    // create our chart
    nv.addGraph(function () {
        chart = nv.models.lineWithFocusChart()
                .margin( {left:70})
                .x(function (d) {
                    return d[0]
                })
                .y(function (d) {
                    return d[1]
                });
        chart.xAxis
                .tickFormat(function (d) {
                    return d3.time.format('%b %e %H:%M')(new Date(d))
                });

        chart.x2Axis
                .tickFormat(function (d) {
                    return d3.time.format('%Y-%m-%d')(new Date(d))
                });

        chart.yAxis
                .tickFormat(d3.format(',.2s'));

        chart.y2Axis
                .tickFormat(d3.format(',.1s'));

        chart.focusHeight(80);
        chart.interpolate('step-before');

        // dispatch when one of the streams is enabled/disabled
        chart.dispatch.on('stateChange', function (e) {
            disabled = e['disabled'];
        });

        // dispatch on window resize - delay action with 500ms timer
        nv.utils.windowResize(function () {
            if (resizeTimer) {
                clearTimeout(resizeTimer);
            }
            resizeTimer = setTimeout(function () {
                chart.update();
                resizeTimer = null;
            }, 500);
        });

        return chart;
    });

    function getRRDlist() {
        ajaxGet("/api/diagnostics/systemhealth/getRRDlist/", {}, function (data, status) {
            if (status == "success") {
                let category;
                let tabs = "";
                let subitem = "";
                let active_category = Object.keys(data["data"])[0];
                let active_subitem = data["data"][active_category][0];
                let rrd_name = "";
                for ( category in data["data"]) {
                    if (category == active_category) {
                        tabs += '<li role="presentation" class="dropdown active">';
                    } else {
                        tabs += '<li role="presentation" class="dropdown">';
                    }

                    subitem = data["data"][category][0]; // first sub item
                    rrd_name = subitem + '-' + category;

                    // create dropdown menu
                    tabs+='<a data-toggle="dropdown" href="#" class="dropdown-toggle pull-right visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" role="button">';
                    tabs+='<b><span class="caret"></span></b>';
                    tabs+='</a>';
                    tabs+='<a data-toggle="tab" onclick="$(\'#'+rrd_name+'\').click();" class="visible-lg-inline-block visible-md-inline-block visible-xs-inline-block visible-sm-inline-block" style="border-right:0px;"><b>'+category[0].toUpperCase() + category.slice(1)+'</b></a>';
                    tabs+='<ul class="dropdown-menu" role="menu">';

                    // add subtabs
                    for (let count=0;  count<data["data"][category].length;++count ) {
                        subitem=data["data"][category][count];
                        rrd_name = subitem + '-' + category;

                        if (subitem==active_subitem && category==active_category) {
                            tabs += '<li class="active"><a data-toggle="tab" class="rrd_item" id="'+rrd_name+'">' + subitem[0].toUpperCase() + subitem.slice(1) + '</a></li>';
                        } else {
                            tabs += '<li><a data-toggle="tab" class="rrd_item"  id="'+rrd_name+'">' + subitem[0].toUpperCase() + subitem.slice(1) + '</a></li>';
                        }
                    }
                    tabs+='</ul>';
                    tabs+='</li>';
                }
                $('#maintabs').html(tabs);
                $('#tab_1').toggleClass('active');

                // map interface descriptions
                $(".rrd_item").each(function(){
                    let rrd_item = $(this);
                    let rrd_item_name = $(this).attr('id').split('-')[0].toLowerCase();
                    $.map(data['interfaces'], function(value, key){
                        if (key.toLowerCase() == rrd_item_name) {
                            rrd_item.html(value['descr']);
                        }
                    });
                });
                $(".rrd_item").click(function(){
                    // switch between rrd graphs
                    $('#zoom').empty();
                    disabled = [];  // clear disabled stream data
                    chart.brushExtent([0, 0]); // clear focus area
                    getdata($(this).attr('id'));
                });
                $(".update_options").change(function(){
                    window.onresize = null; // clear any pending resize events
                    let rrd = $(".dropdown-menu > li.active > a").attr('id');
                    if ($(this).attr('id') == 'zoom') {
                        chart.brushExtent([0, 0]); // clear focus area
                    }
                    getdata(rrd);
                });
                $("#"+active_subitem+"-"+active_category).click();
            }
        });
    }

    function getdata(rrd_name) {

        let from = 0;
        let to = 0;
        let maxitems = 120;

        let detail = $('input:radio[name=detail]:checked').val() ?? '0';
        let inverse = $('input:radio[name=inverse]:checked').val() == 1 ? '1' : '0';

        // array used for cvs export option
        csvData = [];

        // array used for min/max/average table when shown
        let min_max_average = {};

        // info bar - hide averages info bar while refreshing data
        $('#averages').hide();
        $('#chart_title').hide();
        // info bar - show loading info bar while refreshing data
        $('#loading').show();
        // API call to request data
        ajaxGet("/api/diagnostics/systemhealth/getSystemHealth/" + rrd_name + "/" + inverse + "/" + detail, {}, function (data, status) {
            if (status == "success") {
                let stepsize = data["set"]["step_size"];
                let scale = "{{ lang._('seconds') }}";
                let dtformat = '%m-%d %H:%M';

                // set defaults based on stepsize
                if (stepsize >= 86400) {
                    stepsize = stepsize / 86400;
                    scale = "{{ lang._('days') }}";
                    dtformat = '\'%y w%U%';
                } else if (stepsize >= 3600) {
                    stepsize = stepsize / 3600;
                    scale = "{{ lang._('hours') }}";
                    dtformat = '\'%y d%j%';
                } else if (stepsize >= 60) {
                    stepsize = stepsize / 60;
                    scale = "{{ lang._('minutes') }}";
                    dtformat = '%H:%M';
                }

                // Add zoomlevel buttons/options
                if ($('input:radio[name=detail]:checked').val() == undefined) {
                    $('#zoom').html("<b>No data available</b>");
                    for (let setcount = 0; setcount < data["sets"].length; ++setcount) {
                        const set_stepsize = data["sets"][setcount]["step_size"];
                        let detail_text = '';
                        // Find out what text matches best
                        if (set_stepsize >= 31536000) {
                            detail_text = Math.floor(set_stepsize / 31536000).toString() + " {{ lang._('Year(s)') }}";
                        } else if (set_stepsize >= 259200) {
                            detail_text = Math.floor(set_stepsize / 86400).toString() + " {{ lang._('Days') }}";
                        } else if (set_stepsize > 3600) {
                            detail_text = Math.floor(set_stepsize / 3600).toString() + " {{ lang._('Hours') }}";
                        } else {
                            detail_text = Math.floor(set_stepsize / 60).toString() + " {{ lang._('Minute(s)') }}";
                        }
                        if (setcount == 0) {
                            $('#zoom').empty();
                            $('#zoom').append('<label class="btn btn-default active"> <input type="radio" id="d' + setcount.toString() + '" name="detail" checked="checked" value="' + setcount.toString() + '" /> ' + detail_text + ' </label>');
                        } else {
                            $('#zoom').append('<label class="btn btn-default"> <input type="radio" id="d' + setcount.toString() + '" name="detail" value="' + setcount.toString() + '" /> ' + detail_text + ' </label>');
                        }

                    }
                }
                $('#stepsize').text(stepsize + " " + scale);

                // Check for enabled or disabled stream, to make sure that same set stays selected after update
                for (let index = 0; index < disabled.length; ++index) {
                    window.resize = null;
                    data["set"]["data"][index]["disabled"] = disabled[index]; // disable stream if it was disabled before updating dataset
                }

                // Create tables (general and detail)
                if ($('input:radio[name=show_table]:checked').val() == 1) { // check if toggle table is on
                    // Setup variables for table data
                    let table_head; // used for table headings in html format
                    let table_row_data = {}; // holds row data for table
                    let table_view_rows = ""; // holds row data in html format

                    let keyname = ""; // used for name of key
                    let rowcounter = 0;// general row counter
                    let min; // holds calculated minimum value
                    let max; // holds calculated maximum value
                    let average; // holds calculated average

                    let t; // general date/time variable
                    let item; // used for name of key

                    let counter = 1; // used for row count

                    table_head = "<th>#</th>";
                    if ($('input:radio[name=toggle_time]:checked').val() == 1) {
                        table_head += "<th>{{ lang._('full date & time') }}</th>";
                    } else {
                        table_head += "<th>{{ lang._('timestamp') }}</th>";
                    }


                    for (let index = 0; index < data["set"]["data"].length; ++index) {
                        rowcounter = 0;
                        min = 0;
                        max = 0;
                        average = 0;
                        if (data["set"]["data"][index]["disabled"] != true) {
                            table_head += '<th>' + data["set"]["data"][index]["key"] + '</th>';
                            keyname = data["set"]["data"][index]["key"].toString();
                            for (let value_index = 0; value_index < data["set"]["data"][index]["values"].length; ++value_index) {

                                if (data["set"]["data"][index]["values"][value_index][0] >= (from * 1000) && data["set"]["data"][index]["values"][value_index][0] <= (to * 1000) || ( from == 0 && to == 0 )) {

                                    if (table_row_data[data["set"]["data"][index]["values"][value_index][0]] === undefined) {
                                        table_row_data[data["set"]["data"][index]["values"][value_index][0]] = {};
                                    }
                                    if (table_row_data[data["set"]["data"][index]["values"][value_index][0]][data["set"]["data"][index]["key"]] === undefined) {
                                        table_row_data[data["set"]["data"][index]["values"][value_index][0]][data["set"]["data"][index]["key"]] = data["set"]["data"][index]["values"][value_index][1];
                                    }
                                    if (csvData[rowcounter] === undefined) {
                                        csvData[rowcounter] = {};
                                    }
                                    if (csvData[rowcounter]["timestamp"] === undefined) {
                                        t = new Date(parseInt(data["set"]["data"][index]["values"][value_index][0]));
                                        csvData[rowcounter]["timestamp"] = data["set"]["data"][index]["values"][value_index][0] / 1000;
                                        csvData[rowcounter]["date_time"] = t.toString();
                                    }
                                    csvData[rowcounter][keyname] = data["set"]["data"][index]["values"][value_index][1];
                                    if (data["set"]["data"][index]["values"][value_index][1] < min) {
                                        min = data["set"]["data"][index]["values"][value_index][1];
                                    }
                                    if (data["set"]["data"][index]["values"][value_index][1] > max) {
                                        max = data["set"]["data"][index]["values"][value_index][1];
                                    }
                                    average += data["set"]["data"][index]["values"][value_index][1];
                                    ++rowcounter;
                                }
                            }
                            if (min_max_average[keyname] === undefined) {
                                min_max_average[keyname] = {};
                                min_max_average[keyname]["min"] = min;
                                min_max_average[keyname]["max"] = max;
                                min_max_average[keyname]["average"] = average / rowcounter;
                            }

                        }
                    }

                    for ( item in min_max_average) {
                        table_view_rows += "<tr>";
                        table_view_rows += "<td>" + item + "</td>";
                        table_view_rows += "<td>" + min_max_average[item]["min"].toString() + "</td>";
                        table_view_rows += "<td>" + min_max_average[item]["max"].toString() + "</td>";
                        table_view_rows += "<td>" + min_max_average[item]["average"].toString() + "</td>";
                        table_view_rows += "</tr>";
                    }
                    $('#table_view_general_heading').html('<th>item</th><th>min</th><th>max</th><th>average</th>');
                    $('#table_view_general_rows').html(table_view_rows);
                    table_view_rows = "";

                    for ( item in table_row_data) {
                        if ($('input:radio[name=toggle_time]:checked').val() == 1) {
                            t = new Date(parseInt(item));
                            table_view_rows += "<tr><td>" + counter.toString() + "</td><td>" + t.toString() + "</td>";
                        } else {
                            table_view_rows += "<tr><td>" + counter.toString() + "</td><td>" + parseInt(item / 1000).toString() + "</td>";
                        }
                        for (let value in table_row_data[item]) {
                            table_view_rows += "<td>" + table_row_data[item][value] + "</td>";
                        }
                        ++counter;
                        table_view_rows += "</tr>";
                    }

                    $('#table_view_heading').html(table_head);
                    $('#table_view_rows').html(table_view_rows);
                    $('#chart_details_table').show();
                    $('#chart_general_table').show();
                } else {
                    $('#chart_details_table').hide();
                    $('#chart_general_table').hide();
                }
                chart.xAxis
                        .tickFormat(function (d) {
                            return d3.time.format(dtformat)(new Date(d))
                        });
                chart.yAxis.axisLabel(data["y-axis_label"]);
                chart.forceY([0]);
                chart.useInteractiveGuideline(true);
                chart.interactive(true);

                d3.select('#chart svg')
                        .datum(data["set"]["data"])
                        .transition().duration(0)
                        .call(chart);


                chart.update();
                window.onresize = null; // clear any pending resize events


                $('#loading').hide(); // Data has been found and chart will be drawn
                $('#averages').show();
                $('#chart_title').show();
                if (data["title"]!="") {
                    $('#chart_title').show();
                    $('#chart_title').text(data["title"]);
                } else
                {
                    $('#chart_title').hide();
                }

            } else {
                $('#loading').hide();
                $('#chart_title').show();
                $('#chart_title').text("{{ lang._('Unable to load data') }}");
            }
        });
    }

    // convert a data Array to CSV format
    function convertToCSV(args) {
        let result, ctr, keys, columnDelimiter, lineDelimiter, data;

        data = args.data || null;
        if (data == null || !data.length) {
            return null;
        }

        columnDelimiter = args.columnDelimiter || ';';
        lineDelimiter = args.lineDelimiter || '\n';

        keys = Object.keys(data[0]);

        result = '';
        result += keys.join(columnDelimiter);
        result += lineDelimiter;

        data.forEach(function (item) {
            ctr = 0;
            keys.forEach(function (key) {
                if (ctr > 0) result += columnDelimiter;

                result += item[key];
                ctr++;
            });
            result += lineDelimiter;
        });

        return result;
    }

    // download CVS file
    function downloadCSV(args) {
        let data, filename, link;
        let csv = convertToCSV({
            data: csvData
        });
        if (csv == null) return;
        filename = args.filename || 'export.csv';

        if (!csv.match(/^data:text\/csv/i)) {
            csv = 'data:text/csv;charset=utf-8,' + csv;
        }
        data = encodeURI(csv);

        link = document.createElement('a');
        link.href = data;
        link.target = '_blank';
        link.download = filename;
        document.body.appendChild(link);
        link.click();
    }

    $(document).ready(function() {
        $("#options").collapse('show');
        // hide title row
        $(".page-content-head").addClass("hidden");
        // Load data when document is ready
        getRRDlist();
    });

</script>

<div class="tab-content">
    <div id="info_tab" class="tab-pane fade in">
      <div class="panel panel-primary">
          <div class="panel-heading">
              <h3 class="panel-title">
                  <b>{{ lang._('Information') }}</b>
              </h3>
          </div>
          <div class="panel-body">
            {{ lang._('Local data collection is not enabled at the moment') }}
            <a href="/reporting_settings.php">{{ lang._('Go to reporting settings') }} </a>
          </div>
      </div>
    </div>
    <div id="tab_1" class="tab-pane fade in">
        <div class="panel panel-primary">
            <div class="panel-heading panel-heading-sm">
              <i class="fa fa-chevron-down" style="cursor: pointer;" data-toggle="collapse" data-target="#options"></i>
              <b>{{ lang._('Options') }}</b>
            </div>
            <div class="panel-body collapse" id="options">
                <div class="container-fluid">
                <ul class="nav nav-tabs" role="tablist" id="maintabs">
                {# Tab Content #}
                </ul>
                    <div class="row">
                        <div class="col-md-12"></div>
                        <div class="col-md-4">
                            <b>{{ lang._('Granularity') }}:</b>
                            <div class="btn-group btn-group-xs update_options" data-toggle="buttons" id="zoom">
                                <!-- The zoom buttons are generated based upon the current dataset -->
                            </div>
                        </div>
                        <div class="col-md-2">
                            <b>{{ lang._('Inverse') }}:</b>
                            <div class="btn-group btn-group-xs update_options" data-toggle="buttons">
                                <label class="btn btn-default active">
                                    <input type="radio" id="in0" name="inverse" checked="checked" value="0"/>
                                    {{lang._('Off') }}
                                </label>
                                <label class="btn btn-default">
                                    <input type="radio" id="in1" name="inverse" value="1"/> {{ lang._('On') }}
                                </label>
                            </div>
                        </div>
                        <div class="col-md-4">
                        </div>
                        <div class="col-md-2">
                            <b>{{ lang._('Show Tables') }}:</b>
                            <div class="btn-group btn-group-xs update_options" data-toggle="buttons">
                                <label class="btn btn-default active">
                                    <input type="radio" id="tab0" name="show_table" checked="checked" value="0"/> {{
                                    lang._('Off') }}
                                </label>
                                <label class="btn btn-default">
                                    <input type="radio" id="tab1" name="show_table" value="1"/> {{ lang._('On') }}
                                </label>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- place holder for the chart itself -->
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">
                 <span id="loading">
                     <i id="loading" class="fa fa-spinner fa-spin"></i>
                     <b>{{ lang._('Please wait while loading data...') }}</b>
                 </span>
                <span id="chart_title"> </span>
                <span id="averages">
                    <small>
                    (<b>{{ lang._('Current detail is showing') }} <span id="stepsize"></span> {{ lang._('averages') }}.</b>)
                    </small>
                </span>
                </h3>
            </div>
            <div class="panel-body">
                <div id="chart">
                    <svg></svg>
                </div>
            </div>
        </div>

        <!-- place holder for the general table with min/max/averages, is hidden by default -->
        <div id="chart_general_table" class="col-md-12" style="display: none;">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"> {{ lang._('Current View - Overview') }}</h3>
                </div>
                <div class="panel-body">
                    <div class="table-responsive">
                        <table class="table table-condensed table-hover table-striped">
                            <thead>
                                <tr id="table_view_general_heading" class="active">
                                    <!-- Dynamic data -->
                                </tr>
                            </thead>
                            <tbody id="table_view_general_rows">
                                <!-- Dynamic data -->
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div id="chart_details_table" class="col-md-12" style="display: none;">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">
                  {{ lang._('Current View - Details') }}
                </h3>
            </div>
            <div class="panel-body">
                <div class="btn-toolbar" role="toolbar">
                    <i>{{ lang._('Toggle Timeview') }}:</i>

                    <div class="btn-group update_options" data-toggle="buttons">
                        <label class="btn btn-xs btn-default active">
                            <input type="radio" id="time0" name="toggle_time" checked="checked" value="0"/> {{
                            lang._('Timestamp') }}
                        </label>
                        <label class="btn btn-xs btn-default">
                            <input type="radio" id="time1" name="toggle_time" value="1"/> {{ lang._('Full Date & Time') }}
                        </label>
                    </div>
                    <div class="btn btn-xs btn-primary inline" onclick='downloadCSV({ filename: rrd+".csv" });'>
                        <i class="fa fa-download"></i> {{ lang._('Download as CSV') }}
                    </div>
                </div>

                <div class="table-responsive">
                    <table class="table table-condensed table-hover table-striped">
                        <thead>
                        <tr id="table_view_heading" class="active">
                            <!-- Dynamic data -->
                        </tr>
                        </thead>
                        <tbody id="table_view_rows">
                        <!-- Dynamic data -->
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>

Zerion Mini Shell 1.0