ntopng/http_src/vue/page-snmp-qos.vue
2024-07-02 16:28:24 +02:00

450 lines
No EOL
15 KiB
Vue

<!--
(C) 2013-23 - ntop.org
-->
<template>
<div class="col-12 mb-2 mt-2">
<div v-if="is_qos_polled">
<div class="button-group mb-2 d-flex align-items-center"> <!-- TableHeader -->
<div class="form-group d-flex align-items-end" style="flex-wrap: wrap;">
<div class="dropdown me-3 d-inline-block" v-for="item in filter_table_array">
<span class="no-wrap d-flex align-items-center filters-label fs-6"><b>{{ item["basic_label"]
}}</b></span>
<SelectSearch v-model:selected_option="item['current_option']" theme="bootstrap-5" dropdown_size="medium"
:disabled="loading" :options="item['options']" @select_option="add_filter">
</SelectSearch>
</div>
<div class="d-flex justify-content-center align-items-center">
<div class="btn btn-sm btn-primary mb-1 me-3" type="button" @click="search_timeseries">
{{ _i18n('search') }}
</div>
<Spinner :show="loading" size="1rem" class="me-1"></Spinner>
</div>
</div>
</div>
<div class="card h-100 overflow-hidden">
<Loading v-if="loading_chart"></Loading>
<DateTimeRangePicker style="margin-top:0.5rem;" class="ms-1" :id="id_date_time_picker" :enable_refresh="true"
ref="date_time_picker" @epoch_change="epoch_change" :min_time_interval_id="min_time_interval_id"
:custom_time_interval_list="time_preset_list">
</DateTimeRangePicker>
<div class="mt-3" :class="[(loading_chart) ? 'ntopng-gray-out' : '']">
<TimeseriesChart ref="all_qos_chart" :id="all_qos_id" :chart_type="chart_type" :base_url_request="base_url"
:get_custom_chart_options="get_chart_options" :register_on_status_change="false"
:disable_pointer_events="false" :key="all_qos_id">
</TimeseriesChart>
</div>
<div class="m-3 card card-shadow" :class="[(loading_chart) ? 'ntopng-gray-out' : '']">
<div class="card-body">
<BootstrapTable id="page_stats_bootstrap_table" :columns="stats_columns" :rows="stats_rows"
:print_html_column="(col) => print_stats_column(col)"
:print_html_row="(col, row) => print_stats_row(col, row)">
</BootstrapTable>
</div>
</div>
</div>
<div class="card-footer">
<NoteList :note_list="note_list"> </NoteList>
</div>
</div>
<div v-else class="col-12 alert alert-info alert-dismissable">
<span> {{ qos_not_polled_yet }} </span>
<span class="ms-2 spinner-border spinner-border-sm" role="status"></span>
</div>
</div>
</template>
<script setup>
/* Imports */
import { ref, onMounted, onBeforeMount, computed } from "vue";
import { default as NoteList } from "./note-list.vue";
import { default as TimeseriesChart } from "./timeseries-chart.vue";
import { default as SelectSearch } from "./select-search.vue";
import { default as DateTimeRangePicker } from "./date-time-range-picker.vue";
import { ntopng_url_manager } from "../services/context/ntopng_globals_services.js";
import { default as Loading } from "./loading.vue";
import { default as Spinner } from "./spinner.vue";
import { default as BootstrapTable } from "./bootstrap-table.vue";
import formatterUtils from "../utilities/formatter-utils";
import metricsManager from "../utilities/metrics-manager.js";
import timeseriesUtils from "../utilities/timeseries-utils.js";
/* ******************************************************************** */
const _i18n = (t) => i18n(t);
const props = defineProps({
context: Object,
});
/* Consts */
const id_date_time_picker = "date_time_picker";
const chart_type = ntopChartApex.typeChart.TS_LINE;
const min_time_interval_id = "10_min";
const all_qos_id = ref("chart_qos_all");
const basic_source_type = "snmp_qos";
const group_option_mode = timeseriesUtils.getGroupOptionMode('1_chart_x_metric');
const filter_table_array = ref([]);
const loading = ref(false);
const loading_chart = ref(false);
const filters = ref([]);
const is_qos_polled = ref(false);
const qos_not_polled_yet = _i18n('snmp.snmp_qos_info_not_polled')
const note_list = [
_i18n("snmp.snmp_note_periodic_interfaces_polling"),
_i18n("snmp.snmp_note_thpt_calc"),
_i18n("snmp.snmp_lldp_cdp_descr")
];
const time_preset_list = [
{ value: "10_min", label: i18n('show_alerts.presets.10_min'), currently_active: false },
{ value: "30_min", label: i18n('show_alerts.presets.30_min'), currently_active: true },
{ value: "hour", label: i18n('show_alerts.presets.hour'), currently_active: false },
{ value: "2_hours", label: i18n('show_alerts.presets.2_hours'), currently_active: false },
{ value: "6_hours", label: i18n('show_alerts.presets.6_hours'), currently_active: false },
{ value: "12_hours", label: i18n('show_alerts.presets.12_hours'), currently_active: false },
{ value: "day", label: i18n('show_alerts.presets.day'), currently_active: false },
{ value: "week", label: i18n('show_alerts.presets.week'), currently_active: false },
{ value: "month", label: i18n('show_alerts.presets.month'), currently_active: false },
{ value: "year", label: i18n('show_alerts.presets.year'), currently_active: false },
{ value: "custom", label: i18n('show_alerts.presets.custom'), currently_active: false, disabled: true, },
];
const stats_columns = [
{ id: "metric", label: i18n("page_stats.metric") },
{ id: "avg", label: i18n("page_stats.average"), class: "text-end" },
{ id: "perc_95", label: i18n("page_stats.95_perc"), class: "text-end" },
{ id: "max", label: i18n("page_stats.max"), class: "text-end" },
{ id: "min", label: i18n("page_stats.min"), class: "text-end" },
];
/* Height and width of the charts */
const height_per_row = 62.5
const height = ref(null);
/* Consts */
/* *************************************************** */
/* Return the base url of the REST API */
const base_url = computed(() => {
return `${http_prefix}/lua/pro/rest/v2/get/timeseries/ts_multi.lua`;
});
/* Refs */
const all_qos_chart = ref(null);
const timeseries_groups = ref([]);
const ts_request = ref([{
ts_query: "ifid:-1,device:%host,if_index:%interface_id,qos_class_id:%qos_class",
ts_schema: "snmp_if:cbqos",
tskey: "%interface_id",
source_def: [
"-1", /* System Interface */
"%host",
"%interface_id",
"%qos_class"
]
}]);;
const stats_rows = ref([]);
function set_stats_rows(result) {
const f_get_total_formatter_type = (type) => {
let map_type = {
"bps": "bytes",
"fps": "flows",
"alertps": "alerts",
"hitss": "hits",
"pps": "packets",
};
if (map_type[type] != null) {
return map_type[type];
}
return type;
};
stats_rows.value = [];
result.forEach((options, i) => {
options.series?.forEach((s, j) => {
const ts_stats = s.statistics;
const name = timeseries_groups.value[0].metric.timeseries[s.id].label;
const formatter = formatterUtils.getFormatter(timeseries_groups.value[0].metric.measure_unit);
const row = {
metric: name,
perc_95: formatter(ts_stats["95th_percentile"]),
avg: formatter(ts_stats.average),
max: formatter(ts_stats.max_val),
min: formatter(ts_stats.min_val),
};
stats_rows.value.push(row);
});
});
}
/* *************************************************** */
function print_stats_column(col) {
return col.label;
}
/* *************************************************** */
function print_stats_row(col, row) {
let label = row[col.id];
return label;
}
/* *************************************************** */
function search_timeseries() {
/* This is a trick to reload the entire timeseries component
* if not reloaded there could be some issues with parameters
*/
all_qos_id.value = ntopng_url_manager.get_url_entry("qos_class_id");
}
/* *************************************************** */
async function add_filter(opt) {
ntopng_url_manager.set_key_to_url(opt.key, `${opt.value}`);
await load_table_filters_array(opt);
}
/* ************************************** */
const get_extra_params_obj = () => {
let extra_params = ntopng_url_manager.get_url_object();
return extra_params;
};
/* ************************************** */
function set_filter_array_label() {
filter_table_array.value.forEach((el, index) => {
/* Setting the basic label */
if (el.basic_label == null) {
el.basic_label = el.label;
}
/* Getting the currently selected filter */
const url_entry = ntopng_url_manager.get_url_entry(el.id)
el.options.forEach((option) => {
if (option.value.toString() === url_entry) {
el.current_option = option;
}
})
})
}
/* ************************************** */
function set_filters_list(res, opt) {
if (!res) {
filter_table_array.value = filters.value.filter((t) => {
if (t.show_with_key) {
const key = ntopng_url_manager.get_url_entry(t.show_with_key)
if (key !== t.show_with_value) {
return false
}
const first_option = t.options[0];
if (opt && opt.key !== first_option.key) {
/* Changing the dropdown, changing the option too */
ntopng_url_manager.set_key_to_url(first_option.key, first_option.value);
}
}
return true
})
} else {
filters.value = res.filter(t => t.value.length > 0).map((t) => {
/* Do not add filters if no values are found */
if (t.value.length == 0)
return;
const key_in_url = ntopng_url_manager.get_url_entry(t.name);
if ((key_in_url === null || key_in_url === '') && t.value[0]) {
ntopng_url_manager.set_key_to_url(t.name, t.value[0].value);
}
return {
id: t.name,
label: t.label,
title: t.tooltip,
options: t.value,
show_with_key: t.show_with_key,
show_with_value: t.show_with_value,
};
});
if (filters.value.length > 0) {
is_qos_polled.value = true;
set_filters_list(null, opt);
} else {
is_qos_polled.value = false;
setTimeout(load_table_filters_array, 10000)
}
return;
}
set_filter_array_label();
}
/* ************************************** */
function substitute_params(params) {
let res = {}
const host = ntopng_url_manager.get_url_entry("host");
const interface_id = ntopng_url_manager.get_url_entry("snmp_port_idx") || "0"; /* Default value */
const qos_class = ntopng_url_manager.get_url_entry("qos_class_id") || "0"; /* Default value */
if (!(host && interface_id)) {
/* Safe check */
return params
}
for (const value of params) {
res.ts_query = value.ts_query.replace('%host', host).replace('%interface_id', interface_id).replace('%qos_class', qos_class);
res.tskey = value.tskey.replace('%interface_id', interface_id);
res.ts_schema = value.ts_schema
res.source_def = [];
value.source_def.forEach((source, index) => {
let tmp = source.replace('%host', host);
tmp = tmp.replace('%interface_id', interface_id);
tmp = tmp.replace('%qos_class', qos_class);
res.source_def[index] = tmp;
});
}
return res
}
/* ************************************** */
async function load_table_filters_array(opt) {
loading.value = true;
let extra_params = get_extra_params_obj();
let url_params = ntopng_url_manager.obj_to_url_params(extra_params);
const url = `${http_prefix}/lua/pro/rest/v2/get/snmp/device/qos_filters.lua?${url_params}`;
const res = await ntopng_utility.http_request(url);
set_filters_list(res, opt)
loading.value = false;
}
/* *************************************************** */
/* The source_type can be found on the json and the source_array is automatically generated
* by using the source_type
*/
async function get_timeseries_groups_from_metric(metric_schema, source_def) {
const status = {
epoch_begin: ntopng_url_manager.get_url_entry("epoch_begin"),
epoch_end: ntopng_url_manager.get_url_entry("epoch_end"),
};
const source_type = metricsManager.get_source_type_from_id(basic_source_type);
const source_array = await metricsManager.get_source_array_from_value_array(http_prefix, source_type, source_def);
const metric = await metricsManager.get_metric_from_schema(http_prefix, source_type, source_array, metric_schema, null, status);
const ts_group = metricsManager.get_ts_group(source_type, source_array, metric, { past: false });
return ts_group;
}
/* *************************************************** */
async function retrieve_basic_info() {
const tmp = substitute_params(ts_request.value);
const metric_schema = tmp?.ts_schema;
const source_def = tmp.source_def;
if (source_def && metric_schema) {
delete tmp.source_def /* Remove the property otherwise it's going to be added to the REST */
/* Return the timeseries group, info found in the json */
if (timeseries_groups.value.length == 0) {
const group = await get_timeseries_groups_from_metric(metric_schema, source_def);
timeseries_groups.value.push(group);
}
return [tmp];
}
return null;
}
/* *************************************************** */
/* Remove the property otherwise it's going to be added to the REST */
function remove_extra_params(tmp) {
for (const value of tmp) {
if (value.source_def) {
delete value.source_def
}
}
}
/* *************************************************** */
async function get_component_data(url, url_params, post_params) {
let info = null;
const data_url = `${url}?${url_params}`;
info = ntopng_utility.http_post_request(data_url, post_params)
return info;
}
/* *************************************************** */
async function check_params() {
const qos_class = ntopng_url_manager.get_url_entry("qos_class_id");
if (!qos_class) {
await load_table_filters_array();
}
}
/* *************************************************** */
/* This function run the REST API with the data */
async function get_chart_options() {
loading_chart.value = true;
await check_params();
const tmp = await retrieve_basic_info();
let result = [];
if (tmp) {
remove_extra_params(tmp);
const url = base_url.value;
const post_params = {
csrf: props.context.csrf,
ifid: props.context.ifid,
epoch_begin: ntopng_url_manager.get_url_entry("epoch_begin"),
epoch_end: ntopng_url_manager.get_url_entry("epoch_end"),
ts_requests: tmp
}
/* Have to be used this get_component_data, in order to create report too */
result = await get_component_data(url, '', post_params);
}
set_stats_rows(result)
/* Format the result in the format needed by Dygraph */
result = timeseriesUtils.tsArrayToOptionsArray(result, timeseries_groups.value, group_option_mode, '');
if (result[0]) {
result[0].height = height.value;
}
loading_chart.value = false;
return result?.[0];
}
/* *************************************************** */
/* Run the init here */
onBeforeMount(async () => {
load_table_filters_array();
height.value = 4 * height_per_row;
});
/* *************************************************** */
onMounted(async () => { });
/* *************************************************** */
function epoch_change() {
refresh_chart();
}
/* *************************************************** */
/* Refresh function */
async function refresh_chart() {
if (all_qos_chart.value) {
const result = await get_chart_options();
all_qos_chart.value.update_chart_series(result.data);
}
}
</script>