ntopng/http_src/vue/page-flow-historical.vue
2023-06-07 11:27:14 +02:00

502 lines
18 KiB
Vue

<!-- (C) 2022 - ntop.org -->
<template>
<Navbar
id="navbar"
:main_title="context.navbar.main_title"
:base_url="context.navbar.base_url"
:help_link="context.navbar.help_link"
:items_table="context.navbar.items_table"
@click_item="click_navbar_item">
</Navbar>
<div class='row'>
<div class='col-12'>
<div class="mb-2">
<div class="w-100">
<div clas="range-container d-flex flex-wrap">
<div class="range-picker d-flex m-auto flex-wrap">
<AlertInfo id="alert_info" :global="true" ref="alert_info"></AlertInfo>
<RangePicker ref="range_picker" id="range_picker">
<template v-slot:extra_range_buttons>
<button v-if="context.show_permalink" class="btn btn-link btn-sm" @click="get_permanent_link" :title="_i18n('graphs.get_permanent_link')" ref="permanent_link_button"><i class="fas fa-lg fa-link"></i></button>
<a v-if="context.show_download" class="btn btn-link btn-sm" id="dt-btn-download" :title="_i18n('graphs.download_records')" ><i class="fas fa-lg fa-file"></i></a>
<button v-if="context.show_pcap_download" class="btn btn-link btn-sm" @click="show_modal_traffic_extraction" :title="_i18n('traffic_recording.pcap_download')"><i class="fas fa-lg fa-download"></i></button>
<button v-if="context.is_ntop_enterprise_m" class="btn btn-link btn-sm" @click="show_modal_snapshot" :title="_i18n('datatable.manage_snapshots')"><i class="fas fa-lg fa-camera-retro"></i></button>
</template>
</RangePicker>
</div>
</div>
</div>
</div>
</div>
<div class='col-12'>
<div class="card card-shadow">
<div class="card-body">
<div v-if="context.show_chart" class="row">
<div class="col-12 mb-2" id="chart-vue">
<div class="card h-100 overflow-hidden">
<Chart ref="chart"
id="chart_0"
:chart_type="chart_type"
:base_url_request="chart_data_url"
:register_on_status_change="false">
</Chart>
</div>
</div>
<TableWithConfig ref="table_alerts"
:table_id="table_id"
:csrf="context.csrf"
:f_map_columns="map_table_def_columns"
:get_extra_params_obj="get_extra_params_obj"
@loaded="on_table_loaded"
@custom_event="on_table_custom_event">
<template v-slot:custom_header>
<Dropdown v-for="(t, t_index) in top_table_array" :f_on_open="get_open_top_table_dropdown(t, t_index)" :ref="el => { top_table_dropdown_array[t_index] = el }"> <!-- Dropdown columns -->
<template v-slot:title>
<Spinner :show="t.show_spinner" size="1rem" class="me-1" ></Spinner>
<a class="ntopng-truncate" :title="t.title">{{t.label}}</a>
</template>
<template v-slot:menu>
<a v-for="opt in t.options" style="cursor:pointer;" @click="add_top_table_filter(opt, $event)" class="ntopng-truncate tag-filter " :title="opt.value">{{opt.label}}</a>
</template>
</Dropdown> <!-- Dropdown columns -->
</template> <!-- custom_header -->
</TableWithConfig>
</div>
</div> <!-- card body -->
<div v-if="props.context.show_acknowledge_all || props.context.show_delete_all" class="card-footer">
<button v-if="props.context.show_acknowledge_all" id="dt-btn-acknowledge" :disabled="true" data-bs-target="#dt-acknowledge-modal" data-bs-toggle="modal" class="btn btn-primary me-1">
<i class="fas fa fa-user-check"></i> Acknowledge Alerts
</button>
<button v-if="props.context.show_delete_all" id="dt-btn-delete" :disabled="true" data-bs-target="#dt-delete-modal" data-bs-toggle="modal" class="btn btn-danger">
<i class="fas fa fa-trash"></i> Delete Alerts
</button>
</div> <!-- card footer -->
</div> <!-- card-shadow -->
</div> <!-- div col -->
<NoteList :note_list="note_list"></NoteList>
</div> <!-- div row -->
<ModalTrafficExtraction id="modal_traffic_extraction" ref="modal_traffic_extraction">
</ModalTrafficExtraction>
<ModalSnapshot ref="modal_snapshot" :csrf="context.csrf">
</ModalSnapshot>
<ModalAcknoledgeAlert ref="modal_acknowledge" :context="context" @acknowledge="refresh_page_components"></ModalAcknoledgeAlert>
<ModalDeleteAlert ref="modal_delete" :context="context" @delete_alert="refresh_page_components"></ModalDeleteAlert>
<ModalAlertsFilter
:alert="current_alert"
:page="page"
@exclude="add_exclude"
ref="modal_alerts_filter">
</ModalAlertsFilter>
</template>
<script setup>
import { ref, onMounted, onBeforeMount, nextTick } from "vue";
import { ntopng_status_manager, ntopng_custom_events, ntopng_url_manager, ntopng_utility } from "../services/context/ntopng_globals_services";
import NtopUtils from "../utilities/ntop-utils";
import { ntopChartApex } from "../components/ntopChartApex.js";
import { DataTableRenders } from "../utilities/datatable/sprymedia-datatable-utils.js";
import TableUtils from "../utilities/table-utils";
import { default as SelectSearch } from "./select-search.vue";
import { default as Navbar } from "./page-navbar.vue";
import { default as AlertInfo } from "./alert-info.vue";
import { default as Chart } from "./chart.vue";
import { default as RangePicker } from "./range-picker.vue";
import { default as TableWithConfig } from "./table-with-config.vue";
import { default as Dropdown } from "./dropdown.vue";
import { default as Spinner } from "./spinner.vue";
import { default as NoteList } from "./note-list.vue";
import { default as ModalTrafficExtraction } from "./modal-traffic-extraction.vue";
import { default as ModalSnapshot } from "./modal-snapshot.vue";
import { default as ModalAlertsFilter } from "./modal-alerts-filter.vue";
import { default as ModalAcknoledgeAlert } from "./modal-acknowledge-alert.vue";
import { default as ModalDeleteAlert } from "./modal-delete-alert.vue";
const _i18n = (t) => i18n(t);
const props = defineProps({
context: Object,
});
const alert_info = ref(null);
const chart = ref(null);
const table_alerts = ref(null);
const modal_traffic_extraction = ref(null);
const modal_snapshot = ref(null);
const range_picker = ref(null);
const permanent_link_button = ref(null);
const modal_alerts_filter = ref(null);
const modal_acknowledge = ref(null);
const modal_delete = ref(null);
const current_alert = ref(null);
const default_ifid = props.context.ifid;
let page;
let table_id;
let chart_data_url = `${http_prefix}/lua/pro/rest/v2/get/db/ts.lua`;
const chart_type = ntopChartApex.typeChart.TS_COLUMN;
const top_table_array = ref([]);
const top_table_dropdown_array = ref([]);
const note_list = ref([_i18n('show_alerts.alerts_info')]);
onBeforeMount(async () => {
page = ntopng_url_manager.get_url_entry("page");
if (page == null) { page = "overview"; }
chart_data_url = `${http_prefix}/lua/pro/rest/v2/get/db/ts.lua`;
table_id = `flow_historical`;
init_url_params();
});
onMounted(async () => {
register_components_on_status_update();
load_top_table_array_overview();
});
function init_url_params() {
if (ntopng_url_manager.get_url_entry("ifid") == null) {
ntopng_url_manager.set_key_to_url("ifid", default_ifid);
}
if (ntopng_url_manager.get_url_entry("epoch_begin") == null
|| ntopng_url_manager.get_url_entry("epoch_end") == null) {
let default_epoch_begin = Number.parseInt((Date.now() - 1000 * 30 * 60) / 1000);
let default_epoch_end = Number.parseInt(Date.now() / 1000);
ntopng_url_manager.set_key_to_url("epoch_begin", default_epoch_begin);
ntopng_url_manager.set_key_to_url("epoch_end", default_epoch_end);
}
if (ntopng_url_manager.get_url_entry("page") == "flow"
&& ntopng_url_manager.get_url_entry("status") == "engaged") {
ntopng_url_manager.set_key_to_url("status", "historical");
}
}
async function load_top_table_array_overview(action) {
if (props.context.show_cards != true) { return; }
top_table_array.value = await load_top_table_array("overview");
}
async function load_top_table_details(top, top_index) {
top.show_spinner = true;
await nextTick();
if (top.data_loaded == false) {
let new_top_array = await load_top_table_array(top.id, top);
top.options = new_top_array.find((t) => t.id == top.id).options;
await nextTick();
let dropdown = top_table_dropdown_array.value[top_index];
dropdown.load_menu();
}
top.show_spinner = false;
}
async function load_top_table_array(action, top) {
// top_table.value = [];
const url_params = ntopng_url_manager.get_url_params();
const url = `${http_prefix}/lua/pro/rest/v2/get/flow/top.lua?${url_params}&action=${action}`;
let res = await ntopng_utility.http_request(url);
return res.map((t) => {
return {
id: t.action || t.name,
label: t.label,
title: t.tooltip,
show_spinner: false,
data_loaded: action != 'overview',
options: t.value,
};
});
}
const get_open_top_table_dropdown = (top, top_index) => {
return (d) => {
load_top_table_details(top, top_index);
};
};
async function register_components_on_status_update() {
await ntopng_sync.on_ready("range_picker");
//if (show_chart) {
chart.value.register_status();
//}
//updateDownloadButton();
ntopng_status_manager.on_status_change(page.value, (new_status) => {
let url_params = ntopng_url_manager.get_url_params();
table_alerts.value.refresh_table();
load_top_table_array_overview();
}, false);
}
function on_table_loaded() {
register_table_alerts_events();
}
function register_table_alerts_events() {
let jquery_table_alerts = $(`#${table_id}`);
jquery_table_alerts.on('click', `a.tag-filter`, async function (e) {
add_table_row_filter(e, $(this));
});
}
const map_table_def_columns = (columns) => {
let html_ref = '';
let location = '';
const f_print_asn = (key, asn, row) => {
if (asn !== undefined && asn.value != 0) {
return `<a class='tag-filter' data-tag-key='${key}' data-tag-value='${asn.value}' title='${asn.title}' href='javascript:void(0)'>${asn.label}</a>`;
}
return "";
};
const f_print_latency = (key, latency, row) => {
if (latency == null) { return ""; }
return `<a class='tag-filter' data-tag-key='${key}' data-tag-value='${latency}' href='javascript:void(0)'>${NtopUtils.msecToTime(latency)}</a>`;
};
let map_columns = {
"first_seen": (first_seen, row) => {
if (first_seen !== undefined)
return first_seen.time;
},
"l7proto": (proto, row) => {
let confidence = "";
if (proto.confidence !== undefined) {
const title = proto.confidence;
(title == "DPI") ? confidence = `<span class="badge bg-success" title="${title}">${title}</span>` : confidence = `<span class="badge bg-warning" title="${title}">${title}</span>`
}
return DataTableRenders.filterize('l7proto', proto.value, proto.label) + " " + `${confidence}`;
},
"packets": (packets, row) => {
if (packets !== undefined) {
return NtopUtils.formatPackets(packets);
}
return "";
},
"cli_asn": (cli_asn, row) => f_print_asn("cli_asn", cli_asn, row),
"srv_asn": (srv_asn, row) => f_print_asn("srv_asn", srv_asn, row),
"flow_risk": (flow_risks, row) => {
if (flow_risks == null) { return ""; }
let res = [];
for (let i = 0; i < flow_risks.length; i++) {
const flow_risk = flow_risks[i];
const flow_risk_label = (flow_risk.label || flow_risk.value);
const flow_risk_help = (flow_risk.help);
res.push(`${flow_risk_label} ${flow_risk_help}`);
}
return res.join(', ');
},
"cli_nw_latency": (cli_nw_latency, row) => f_print_latency("cli_nw_latency", cli_nw_latency, row),
"srv_nw_latency": (srv_nw_latency, row) => f_print_latency("srv_nw_latency", srv_nw_latency, row),
"info": (info, row) => {
if (info == null) { return ""; }
return `<a class='tag-filter' data-tag-key='info' data-tag-value='${info.title}' title='${info.title}' href='javascript:void(0)'>${info.label}</a>`;
},
};
columns = columns.filter((c) => props.context?.visible_columns[c.data_field] != false);
columns.forEach((c) => {
c.render_func = map_columns[c.data_field];
if (c.id == "actions") {
const visible_dict = {
info: props.context.actions.show_info,
historical_data: props.context.actions.show_historical,
flow_alerts: props.context.actions.show_alerts,
pcap_download: props.context.actions.show_pcap_download,
};
c.button_def_array.forEach((b) => {
if (!visible_dict[b.id]) {
b.class.push("link-disabled");
}
});
}
});
return columns;
};
const add_table_row_filter = (e, a) => {
e.stopPropagation();
let key = undefined;
let displayValue = undefined;
let realValue = undefined;
let operator = 'eq';
// Read tag key and value from the <a> itself if provided
if (a.data('tagKey') != undefined) key = a.data('tagKey');
if (a.data('tagRealvalue') != undefined) realValue = a.data('tagRealvalue');
else if (a.data('tagValue') != undefined) realValue = a.data('tagValue');
if (a.data('tagOperator') != undefined) operator = a.data('tagOperator');
let filter = {
id: key,
value: realValue,
operator: operator,
};
add_filter(filter);
}
function add_top_table_filter(opt, event) {
event.stopPropagation();
let filter = {
id: opt.key,
value: opt.value,
operator: opt.operator,
};
add_filter(filter);
}
function add_filter(filter) {
if (range_picker.value.is_filter_defined(filter)) {
ntopng_events_manager.emit_custom_event(ntopng_custom_events.SHOW_MODAL_FILTERS, filter);
} else {
ntopng_url_manager.set_key_to_url("query_preset", "");
ntopng_url_manager.set_key_to_url(filter.id, `${filter.value};${filter.operator}`);
ntopng_url_manager.reload_url();
}
}
const get_extra_params_obj = () => {
let extra_params = ntopng_url_manager.get_url_object();
return extra_params;
};
function click_navbar_item(item) {
ntopng_url_manager.set_key_to_url('page', item.page_name);
let is_alert_stats_url = window.location.toString().match(/alert_stats.lua/) != null;
if (is_alert_stats_url) {
remove_filters_from_url();
}
ntopng_url_manager.reload_url();
}
function remove_filters_from_url() {
let status = ntopng_status_manager.get_status();
let filters = status.filters;
if (filters == null) { return; }
ntopng_url_manager.delete_params(filters.map((f) => f.id));
}
function show_modal_alerts_filter(alert) {
current_alert.value = alert;
modal_alerts_filter.value.show();
}
function get_permanent_link() {
const $this = permanent_link_button.value;
const placeholder = document.createElement('input');
placeholder.value = location.href;
document.body.appendChild(placeholder);
placeholder.select();
// copy the url to the clipboard from the placeholder
document.execCommand("copy");
document.body.removeChild(placeholder);
$this.attr("title", "{{ i18n('copied') }}!")
.tooltip("dispose")
.tooltip()
.tooltip("show");
}
function show_modal_traffic_extraction() {
modal_traffic_extraction.value.show();
}
function show_modal_snapshot() {
modal_snapshot.value.show();
}
async function add_exclude(params) {
params.csrf = props.context.csrf;
let url = `${http_prefix}/lua/pro/rest/v2/add/alert/exclusion.lua`;
try {
let headers = {
'Content-Type': 'application/json'
};
await ntopng_utility.http_request(url, { method: 'post', headers, body: JSON.stringify(params) });
let url_params = ntopng_url_manager.get_url_params();
setTimeout(() => {
//todo reloadTable($table, url_params);
ntopng_events_manager.emit_custom_event(ntopng_custom_events.SHOW_GLOBAL_ALERT_INFO, { text_html: _i18n('check_exclusion.disable_warn'), type: "alert-info", timeout: 2 });
}, 1000);
} catch(err) {
console.error(err);
}
}
function refresh_page_components() {
let t = table_alerts.value;
let c = chart.value;
setTimeout(() => {
t.refresh_table();
c.update_chart();
}, 1 * 1000);
}
function on_table_custom_event(event) {
let events_managed = {
"click_button_info": click_button_info,
"click_button_flow_alerts": click_button_flow_alerts,
"click_button_historical_flows": click_button_historical_flows,
"click_button_pcap_download": click_button_pcap_download,
};
if (events_managed[event.event_id] == null) {
return;
}
events_managed[event.event_id](event);
}
function click_button_info(event) {
const flow = event.row;
const href = `${http_prefix}/lua/pro/db_flow_details.lua?row_id=${flow.rowid}&tstamp=${flow.tstamp}&instance_name=${flow.NTOPNG_INSTANCE_NAME}`;
window.open(href, "_blank");
}
function click_button_pcap_download(event) {
const flow = event.row;
modal_traffic_extraction.value.show(flow?.filter?.bpf);
}
function click_button_historical_flows(event) {
const flow = event.row;
let filters_params_object = {};
for (let key in flow) {
let filter_key = key;
if (flow[key].tag_key != null && flow[key].tag_key != "") {
filter_key = flow[key].tag_key;
}
if (flow[key].value == null && flow[key].value != "") { continue; }
let filter = `${flow[key].value};eq`;
filters_params_object[filter_key] = filter;
}
ntopng_url_manager.set_key_to_url("query_preset", "");
ntopng_url_manager.add_obj_to_url(filters_params_object);
ntopng_url_manager.reload_url();
}
function click_button_flow_alerts(event) {
const flow = event.row;
if (flow.alerts_url) {
ntopng_url_manager.go_to_url(flow.alerts_url);
}
}
function get_status_view() {
let status_view = ntopng_url_manager.get_url_entry("status");
if (status_view == null || status_view == "") {
status_view = "historical";
}
return status_view;
}
</script>
<style scoped>
</style>