%PDF- %PDF-
Direktori : /backups/router/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/ |
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>