ntopng/http_src/vue/page-assets.vue
2025-12-31 12:20:46 +01:00

461 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- (C) 2024 - ntop.org -->
<template>
<div class="m-2 mb-3">
<div class="">
<TableWithConfig ref="table_assets" :table_id="table_id" :csrf="context.csrf" :showLoading="true"
:f_map_columns="map_table_def_columns" :get_extra_params_obj="get_extra_params_obj"
@custom_event="on_table_custom_event">
<template v-slot:custom_header>
<div class="dropdown me-3 d-inline-block" v-for="item in filter_table_array">
<span class="no-wrap d-flex align-items-center my-auto me-2 filters-label"><b>{{
item["basic_label"]
}}</b></span>
<!-- :key="host_filters_key" -->
<SelectSearch v-model:selected_option="item['current_option']" theme="bootstrap-5"
dropdown_size="small" :options="item['options']" @select_option="add_table_filter">
</SelectSearch>
</div>
<div class="d-flex justify-content-center align-items-center">
<div class="btn btn-sm btn-primary mt-2 me-3" type="button" @click="reset_filters">
{{ _i18n('reset') }}
</div>
</div>
</template> <!-- Dropdown filters -->
</TableWithConfig>
<div class="card-footer mt-3">
<button class="btn btn-primary me-1" type="button" @click="import_assets">
<i class="fa-solid fa-file-arrow-down" data-bs-toggle="tooltip" data-bs-placement="top"
:title="_i18n('asset_details.import')"></i> {{
_i18n('asset_details.import') }}
</button>
<a class="btn btn-primary" download="assets.csv" :href="export_assets_url">
<i class="fa-solid fa-file-arrow-up" data-bs-toggle="tooltip" data-bs-placement="top"
:title="_i18n('asset_details.export')"></i> {{
_i18n('asset_details.export') }}
</a>
<button v-if="props.context.is_admin" type="button" @click="delete_assets()"
class="btn btn-danger ms-1">
<i class="fas fa fa-trash"></i>
{{ _i18n("delete_all_entries") }}
</button>
<button v-if="props.context.is_admin" type="button" @click="delete_assets_epoch"
class="btn btn-danger ms-1">
<i class="fas fa fa-trash"></i>
{{ _i18n("asset_details.delete_asset_older_title") }}
</button>
</div>
</div>
</div>
<ModalImportAssets ref="modal_import_assets" :context="context" @add="import_assets_rest">
</ModalImportAssets>
<ModalDeleteAssets ref="modal_delete_assets" :context="context" @delete="refresh_table">
</ModalDeleteAssets>
<ModalDeleteAssetsEpoch ref="modal_delete_assets_epoch" :context="context" @delete="refresh_table">
</ModalDeleteAssetsEpoch>
</template>
<script setup>
import { ref, nextTick, onMounted } from "vue";
import { default as TableWithConfig } from "./table-with-config.vue";
import { default as SelectSearch } from "./select-search.vue";
import { default as dataUtils } from "../utilities/data-utils.js";
import { default as osUtils } from "../utilities/map/os-utils.js";
import { default as ModalDeleteAssets } from "./modal-delete-assets.vue";
import { default as ModalDeleteAssetsEpoch } from "./modal-delete-assets-epoch.vue";
import { default as ModalImportAssets } from "./modal-import-assets.vue"
import { ntopng_url_manager } from "../services/context/ntopng_globals_services.js";
import FormatterUtils from "../utilities/formatter-utils.js";
/* ************************************** */
const props = defineProps({
context: Object,
});
const _i18n = (t) => i18n(t);
/* ************************************** */
const import_assets_url = `${http_prefix}/lua/pro/rest/v2/add/assets/assets.lua`;
// For the export, its necessary to know if the page is the SNMP page
const export_assets_url = `${http_prefix}/lua/pro/rest/v2/export/assets/assets.lua?is_snmp=${props.context.is_system_interface}`
const modal_import_assets = ref();
const host_filters_key = ref(0);
const table_id = ref(props.context.is_system_interface ? 'assets_snmp' : 'assets');
const filter_table_array = ref([]);
const table_assets = ref();
const modal_delete_assets = ref();
const modal_delete_assets_epoch = ref();
const child_safe_icon = "<font color='#5cb85c'><i class='fas fa-lg fa-child' aria-hidden='true' title='" + i18n("host_pools.children_safe") + "'></i></font>"
const system_host_icon = "<i class='fas fa-flag' title='" + i18n("system_host") + "'></i>"
const hidden_from_top_icon = "<i class='fas fa-eye-slash' title='" + i18n("hidden_from_top_talkers") + "'></i>"
const dhcp_host_icon = '<i class="fa-solid fa-bolt" title="DHCP Host"></i>'
const blacklisted_icon = "<i class='fas fa-ban fa-sm' title='" + i18n("hosts_stats.blacklisted") + "'></i>"
const crawler_bot_scanner_host_icon = "<i class='fas fa-spider fa-sm' title='" + i18n("hosts_stats.crawler_bot_scanner") + "'></i>"
const multicast_icon = "<abbr title='" + i18n("multicast") + "'><span class='badge bg-primary'>" + i18n("short_multicast") + "</span></abbr>"
const localhost_icon = "<abbr data-bs-toggle='tooltip' data-bs-placement='top' data-bs-original-title='" + i18n("details.label_local_host") + "'><span class='badge bg-success'>" + i18n("details.label_short_local_host") + "</span></abbr>"
const remotehost_icon = "<abbr title='" + i18n("details.label_remote") + "'><span class='badge bg-secondary'>" + i18n("details.label_short_remote") + "</span></abbr>"
const blackhole_icon = "<abbr title='" + i18n("details.label_blackhole") + "'><span class='badge bg-info'>" + i18n("details.label_short_blackhole") + "</span></abbr>"
const blocking_quota_icon = "<i class='fas fa-hourglass' title='" + i18n("hosts_stats.blocking_traffic_policy_popup_msg") + "'></i>"
/* ************************************** */
const map_table_def_columns = (columns) => {
let map_columns = {
"ip_address": (value, row) => {
const host = row.host
let ip_address = host.ip
let icons = ''
if (dataUtils.isEmptyOrNull(ip_address)) {
return ''
}
let is_asset_online = row.online;
if (!dataUtils.isEmptyOrNull(host.vlan.name)) {
ip_address = `${ip_address}@${host.vlan.name}`
}
if (!dataUtils.isEmptyOrNull(host.system_host)) {
icons = `${icons} ${system_host_icon}`
}
if (!dataUtils.isEmptyOrNull(host.os)) {
const os_icon = osUtils.getOS(host.os);
icons = `${icons} ${os_icon.icon}`
}
if (!dataUtils.isEmptyOrNull(host.device_type)) {
icons = `${icons} ${osUtils.getAssetIcon(host.device_type) || ''}`
}
if (!dataUtils.isEmptyOrNull(host.hidden_from_top)) {
icons = `${icons} ${hidden_from_top_icon}`
}
if (!dataUtils.isEmptyOrNull(host.child_safe)) {
icons = `${icons} ${child_safe_icon}`
}
if (!dataUtils.isEmptyOrNull(host.dhcp_host)) {
icons = `${icons} ${dhcp_host_icon}`
}
if (!dataUtils.isEmptyOrNull(host.blocking_traffic_policy)) {
icons = `${icons} ${blocking_quota_icon}`
}
if (!dataUtils.isEmptyOrNull(host.country)) {
icons = `${icons} <a href='${http_prefix}/lua/hosts_stats.lua?country=${host.country}'><img src='${http_prefix}/dist/images/blank.gif' class='flag flag-${host.country.toLowerCase()}'></a>`
}
if (!dataUtils.isEmptyOrNull(host.is_blacklisted)) {
icons = `${icons} ${blacklisted_icon}`
}
if (!dataUtils.isEmptyOrNull(host.crawler_bot_scanner_host)) {
icons = `${icons} ${crawler_bot_scanner_host_icon}`
}
if (!dataUtils.isEmptyOrNull(host.is_multicast)) {
icons = `${icons} ${multicast_icon}`
}
if (!dataUtils.isEmptyOrNull(host.remotehost)) {
icons = `${icons} ${remotehost_icon}`
}
if (!dataUtils.isEmptyOrNull(host.is_blackhole)) {
icons = `${icons} ${blackhole_icon}`
}
if (!dataUtils.isEmptyOrNull(host.is_dhcp_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_dhcp_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_dns_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_dns_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_smtp_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_smtp_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_ntp_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_ntp_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_imap_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_imap_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_pop_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_pop_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_ssh_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_ssh_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_http_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_http_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_rdp_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_rdp_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_modbus_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_modbus_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_s7comm_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_s7comm_server")}</span>`
}
if (!dataUtils.isEmptyOrNull(host.is_profinet_server)) {
icons = `${icons} <span class="badge bg-success">${i18n("details.label_profinet_server")}</span>`
}
const host_url = create_button_host_details(row);
// If host is online show host details icon fas-laptop to jump to host details page
let host_icon = `<a href='/lua/host_details.lua?host=${row.host.ip}&vlan=${row.host.vlan.id}' data-bs-toggle='tooltip' data-bs-placement='top' title='Host Details'><i class='fas fa-laptop'></i></a>`;
if (is_asset_online) {
return `<a href="${host_url}">${ip_address}</a> ${host_icon} ${icons}`
}
return `<a href="${host_url}">${ip_address}</a> ${icons}`
},
"host_name": (value, row) => {
let name = value.name
if (!dataUtils.isEmptyOrNull(value.alt_name)) {
name = value.alt_name
if (value.alt_name != value.name && !dataUtils.isEmptyOrNull(value.name)) {
name = `${name} [${value.name}]`
}
}
return name
},
"switch_ip": (value, row) => {
const url = http_prefix + "/lua/pro/enterprise/snmp_device_details.lua?host=" + value.value
if (!dataUtils.isEmptyOrNull(value.name) && value.name != value.value) {
return `<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title='${value.value}'><a href='${url}'>${value.name}</a></span>`
}
return `<a href='${url}'>${value.value}</a>`
},
"switch_port": (value, row) => {
const url = http_prefix + "/lua/pro/enterprise/snmp_interface_details.lua?host=" + row.switch_ip.value + "&snmp_port_idx=" + value.value
if (!dataUtils.isEmptyOrNull(value.name) && value.name != value.value) {
return `<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title='${value.value}'><a href='${url}'>${value.name}</a></span>`
}
return `<a href='${url}'>${value.value}</a>`
},
"mac": (value, row) => {
let result = value.value
if (!dataUtils.isEmptyOrNull(value.name)) {
result = `<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title='${value.value}'>${value.name}</span>`
}
if (value.is_in_memory) {
result = `<a href='${http_prefix}/lua/mac_details.lua?host=${value.value}'>${result}</a>`
}
return result;
},
"manufacturer": (value, row) => {
return value;
},
"model": (value, row) => {
return value;
},
"status": (value, row) => {
let badge = `<span class="badge bg-secondary">${i18n('asset_details.offline')}</span>`
if (row.online) {
badge = `<span class="badge bg-success">${i18n('asset_details.online')}</span>`
}
return badge
},
"first_seen": (value, row) => {
return FormatterUtils.formatDateTime(value.timestamp)
},
"last_seen": (value, row) => {
if (row.online) {
return ""
}
return FormatterUtils.formatDateTime(value.timestamp)
}
};
columns.forEach((c) => {
c.render_func = map_columns[c.data_field];
if (c.id == "actions") {
const visible_dict = {
historical_flows: props.context.historical_available,
details: true,
delete: props.context.is_admin
};
c.button_def_array.forEach((b) => {
if (!visible_dict[b.id]) {
b.class.push("disabled");
}
});
}
});
return columns;
};
/* ************************************** */
function delete_assets_epoch() {
modal_delete_assets_epoch.value.show();
}
/* ************************************** */
function delete_assets(row) {
modal_delete_assets.value.show(row);
}
/* ************************************** */
function reset_filters() {
filter_table_array.value.forEach((el, index) => {
/* Getting the currently selected filter */
ntopng_url_manager.set_key_to_url(el.id, ``);
})
load_table_filters_array();
refresh_table();
}
/* ************************************** */
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;
}
})
})
}
/* ************************************** */
async function add_table_filter(opt) {
ntopng_url_manager.set_key_to_url(opt.key, `${opt.value}`);
set_filter_array_label();
table_assets.value.refresh_table();
}
/* ************************************** */
async function load_table_filters_array() {
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/host/assets_filters.lua?${url_params}`;
let res = await ntopng_utility.http_request(url);
host_filters_key.value = host_filters_key.value + 1
filter_table_array.value = res.map((t) => {
const key_in_url = ntopng_url_manager.get_url_entry(t.name);
if (dataUtils.isEmptyOrNull(key_in_url)) {
ntopng_url_manager.set_key_to_url(t.name, ``);
}
return {
id: t.name,
label: t.label,
title: t.tooltip,
options: t.value,
hidden: (t.value.length == 1)
};
});
set_filter_array_label();
}
/* ************************************** */
const get_extra_params_obj = () => {
let extra_params = ntopng_url_manager.get_url_object();
extra_params = {
...{ ifid: props.context.ifid },
...extra_params,
}
return extra_params;
};
/* ************************************** */
function create_historical_flows_url_link(row) {
return `${http_prefix}/lua/pro/db_search.lua?ip=${row.host.ip};eq&vlan_id=${row.host.vlan.id};eq&epoch_begin=${row.first_seen.timestamp}&epoch_end=${row.last_seen.timestamp}`
}
/* ************************************** */
function create_button_host_details(row) {
let url = `/lua/pro/asset_details.lua?ifid=${props.context.ifid}&serial_key=${row.key}`
return `${http_prefix}${url}`
}
/* ************************************** */
function click_button_delete(event) {
const row = event.row;
delete_assets(row);
}
/* ************************************** */
function click_button_historical_flows(event) {
const row = event.row;
window.open(create_historical_flows_url_link(row));
}
/* ************************************** */
function click_button_host_details(event) {
const row = event.row;
window.open(create_button_host_details(row));
}
/* ************************************** */
function on_table_custom_event(event) {
let events_managed = {
"click_button_historical_flows": click_button_historical_flows,
"click_button_host_details": click_button_host_details,
"click_button_delete": click_button_delete,
};
if (events_managed[event.event_id] == null) {
return;
}
events_managed[event.event_id](event);
}
/* ************************************** */
function refresh_table() {
table_assets.value.refresh_table();
}
/* ************************************** */
function import_assets() {
modal_import_assets.value.show();
}
/* ************************************** */
const import_assets_rest = async function (params) {
const url = import_assets_url;
const result = await ntopng_utility.http_post_request(url, { ...{ csrf: props.context.csrf }, ...params }, false, true);
if (result == null) {
modal_import_assets.value.show_bad_feedback(_i18n("asset_details.import_error"));
} else if (result.rc < 0) {
modal_import_assets.value.show_bad_feedback(result.rsp.feedback);
refresh_table();
} else {
setTimeout(() => { modal_import_assets.value.close(); refresh_table(); }, 1000);
}
}
/* ************************************** */
onMounted(async () => {
load_table_filters_array();
});
/* ************************************** */
</script>