ntopng/http_src/vue/modal-add-vs-host.vue
2025-09-02 09:32:35 +02:00

566 lines
19 KiB
Vue

<!-- (C) 2023 - ntop.org -->
<template>
<modal ref="modal_id">
<template v-slot:title>{{ title }}</template>
<template v-slot:body>
<!-- Target information, here an IP is put -->
<div class="form-group ms-2 me-2 mt-3 row">
<label class="col-form-label col-sm-2">
<b>{{ _i18n("hosts_stats.page_scan_hosts.host_or_network") }}</b>
</label>
<div class="col-sm-4 pe-0">
<input v-model="host" @input="check_host_regex" :disabled="is_edit_page" class="form-control" type="text"
:placeholder="host_placeholder" required />
</div>
<div class="col-1 ps-5 pe-0 mt-1">
<span>/</span>
</div>
<div class="col-2 ps-0">
<input v-model="selected_cidr" @input="check_cidr" :disabled="is_edit_page" class="form-control" type="text"
:placeholder="cidr_placeholder" required />
</div>
</div>
<div class="form-group ms-2 me-2 mt-3 row">
<label class="col-form-label col-sm-2">
<b>{{ _i18n("hosts_stats.page_scan_hosts.ports") }}</b>
</label>
<div class="col-sm-10">
<input v-model="ports" @focusout="check_ports" class="form-control" :class="hide_ports_placeholder === true ? 'ntopng-hide-placeholder' : ''
" type="text" :placeholder="ports_placeholder" required />
</div>
</div>
<div class="form-group ms-2 me-2 mt-3 row">
<label class="col-form-label col-sm-2">
<b>{{ _i18n("hosts_stats.page_scan_hosts.scan_type") }}</b>
</label>
<div class="col-10">
<SelectSearch v-model:selected_option="selected_scan_type" :options="scan_type_list" :disabled="is_edit_page" @select_option="set_is_ipv4_netscan()">
</SelectSearch>
</div>
</div>
<template v-if="selected_scan_type.id == 'ipv4_netscan'">
<div class="form-group ms-2 me-2 mt-3 row">
<label class="col-form-label col-sm-2">
<b>{{ _i18n("hosts_stats.page_scan_hosts.host_discovered_scan_type") }}</b>
</label>
<div class="col-10" >
<SelectSearch v-model:selected_options="selected_discovered_scan_types" @change_selected_options="update_selected_discovered_scan_types" @unselect_option="remove_selected_discovered_scan_types" :options="discovered_scan_type_list" :multiple="true">
</SelectSearch>
</div>
</div>
</template>
<template v-if="is_enterprise_l == true">
<div class="form-group ms-2 me-2 mt-3 row">
<label class="col-form-label col-sm-2">
<b>{{ _i18n("hosts_stats.page_scan_hosts.periodicity") }}</b>
</label>
<div class="col-10">
<SelectSearch v-model:selected_option="selected_scan_frequency" :options="scan_frequencies_list">
</SelectSearch>
</div>
</div>
</template>
<div class="mt-4">
<template v-if="is_enterprise_l == false">
<NoteList :note_list="note_list"> </NoteList>
</template>
<template v-else>
<NoteList :note_list="enterprise_note_list"> </NoteList>
</template>
</div>
</template>
<template v-slot:footer>
<div v-if="is_data_not_ok" class="me-auto text-danger d-inline">
{{ no_host_feedback }}
</div>
<div>
<Spinner :show="activate_add_spinner" size="1rem" class="me-2"></Spinner>
<button v-if="is_edit_page == false" type="button" @click="add_" class="btn btn-primary"
:disabled="!(isCIDRValid && isHostValid && isPortCorrect && isNetscanValid)">
{{ _i18n("add") }}
</button>
<button v-else type="button" @click="edit_" class="btn btn-primary"
:disabled="!(isCIDRValid && isHostValid && isPortCorrect && isNetscanValid)">
{{ _i18n("apply") }}
</button>
</div>
</template>
</modal>
</template>
<script setup>
/* Imports */
import { ref, onMounted } from "vue";
import { default as modal } from "./modal.vue";
import { default as SelectSearch } from "./select-search.vue";
import { default as NoteList } from "./note-list.vue";
import { default as Spinner } from "./spinner.vue";
import { default as dataUtils } from "../utilities/data-utils";
import regexValidation from "../utilities/regex-validation.js";
import NtopUtils from "../utilities/ntop-utils";
/* ****************************************************** */
const _i18n = (t) => i18n(t);
const emit = defineEmits(["add", "edit"]);
const props = defineProps({
context: Object,
});
/* Consts */
const title = ref(i18n("hosts_stats.page_scan_hosts.add_host"));
const no_host_feedback = ref(i18n("hosts_stats.page_scan_hosts.host_not_resolved"));
const host_placeholder = i18n("hosts_stats.page_scan_hosts.host_placeholder");
const cidr_placeholder = i18n("hosts_stats.page_scan_hosts.cidr_placeholder");
const ports_placeholder = i18n("hosts_stats.page_scan_hosts.ports_placeholder");
const note_list = [
_i18n("hosts_stats.page_scan_hosts.notes.note_1"),
_i18n("hosts_stats.page_scan_hosts.notes.note_2"),
_i18n("hosts_stats.page_scan_hosts.notes.note_3"),
_i18n("hosts_stats.page_scan_hosts.notes.note_3_1"),
];
const enterprise_note_list = [
_i18n("hosts_stats.page_scan_hosts.notes.note_1"),
_i18n("hosts_stats.page_scan_hosts.notes.note_2"),
_i18n("hosts_stats.page_scan_hosts.notes.note_3"),
_i18n("hosts_stats.page_scan_hosts.notes.note_3_1"),
_i18n("hosts_stats.page_scan_hosts.notes.note_4"),
];
const modal_id = ref(null);
const selected_scan_type = ref({});
const selected_discovered_scan_types = ref([]); // array for hosts discovered scan types
const hide_ports_placeholder = ref("");
const row_to_edit_id = ref("");
const activate_add_spinner = ref(false);
const is_edit_page = ref(false);
const scan_type_list = ref([]);
const discovered_scan_type_list = ref([]);
const ifid = ref(null);
const host = ref(null);
const ports = ref(null);
const show_port_feedback = ref(false);
const is_enterprise_l = ref(null);
const isPortCorrect = ref(true);
const isCIDRValid = ref(false);
const isNetscanValid = ref(true); // bool to be sure that on netscan at least one discovered hosts scan type is selected
const isHostValid = ref(false);
const is_ipv4_netscan = ref(false);
const scan_frequencies_list = ref([
{ id: "disabled", label: i18n("hosts_stats.page_scan_hosts.disabled") },
{ id: "1day", label: i18n("hosts_stats.page_scan_hosts.every_night") },
{ id: "1week", label: i18n("hosts_stats.page_scan_hosts.every_week") },
]);
const CIDR_24 = 24;
const CIDR_30 = 30;
const CIDR_32 = 32;
const CIDR_128 = 128;
const selected_cidr = ref(null);
const selected_scan_frequency = ref(scan_frequencies_list.value[0]);
const is_data_not_ok = ref(false);
const refresh_select_search = ref(false);
/* ****************************************************** */
/*
* Reset fields in modal form
*/
const reset_modal_form = function () {
host.value = "";
ports.value = "";
selected_cidr.value = "";
isPortCorrect.value = true;
isCIDRValid.value = false;
isHostValid.value = false;
activate_add_spinner.value = false;
show_port_feedback.value = false;
selected_scan_type.value = scan_type_list.value[0];
selected_discovered_scan_types.value = [];
row_to_edit_id.value = null;
is_edit_page.value = false;
is_data_not_ok.value = false;
is_ipv4_netscan.value = false;
isNetscanValid.value = !is_ipv4_netscan.value;
};
/* ****************************************************** */
/*
* Set row to edit
*/
const set_row_to_edit = (row) => {
is_edit_page.value = true;
/* Set host values */
host.value = row.host;
ports.value = row.ports;
isHostValid.value = true;
isPortCorrect.value = true;
row_to_edit_id.value = row.id;
/* Set the correct values if available */
/* Scan Type */
selected_scan_type.value = scan_type_list.value.find(
(item) => item.id == row.scan_type
);
/* Sub Scans Types */
if (row.discovered_host_scan_type != null) {
const discovered_scan_type_ids_array = row.discovered_host_scan_type.split(",");
let tmp_selected_scan_types = [];
let tmp_found_scan_type;
discovered_scan_type_ids_array.forEach((setted_scan_type) => {
tmp_found_scan_type = discovered_scan_type_list.value.find((item) => item.id == setted_scan_type);
tmp_selected_scan_types.push(tmp_found_scan_type);
})
selected_discovered_scan_types.value = tmp_selected_scan_types;
}
/* CIDR */
if (check_is_network_address(host.value) || row.cidr == CIDR_128) {
selected_cidr.value = row.cidr;
} else {
selected_cidr.value = CIDR_32;
}
isCIDRValid.value = true;
/* Scan Frequency */
if (is_enterprise_l) {
selected_scan_frequency.value = scan_frequencies_list.value.find(
(item) => item.id == row.scan_frequency
);
}
};
/* ****************************************************** */
/* This method is called whenever the modal is opened */
const show = (row, _host) => {
/* First of all reset all the data */
reset_modal_form();
title.value = i18n("hosts_stats.page_scan_hosts.add_host");
if (!dataUtils.isEmptyOrNull(row)) {
/* In case row is not null then an edit is requested */
title.value = i18n("hosts_stats.page_scan_hosts.edit_host_title");
set_row_to_edit(row);
}
if (!dataUtils.isEmptyOrNull(_host)) {
host.value = _host;
isHostValid.value = true;
}
modal_id.value.show();
};
/* ****************************************************** */
/* Function called when a new selected discovered scan type
is added
*/
const update_selected_discovered_scan_types = (items) => {
selected_discovered_scan_types.value = items;
isNetscanValid.value = selected_discovered_scan_types.value.length > 0;
}
/* Function called when is removed a selected discovered scan type
*/
const remove_selected_discovered_scan_types = (item_to_delete) => {
selected_discovered_scan_types.value = selected_discovered_scan_types.value.filter((item) => item.id != item_to_delete.id);
isNetscanValid.value = selected_discovered_scan_types.value.length > 0;
}
/* ****************************************************** */
/* Function to set is_ipv4_netscan in order to disable cidr selectio
only /24 (for now)
*/
const set_is_ipv4_netscan = () => {
if (selected_scan_type.value.id == 'ipv4_netscan') {
// /24
selected_cidr.value = CIDR_24;
// is_ipv4_netscan -> enable the discovered_hosts_scan_types multiselection
is_ipv4_netscan.value = true;
// isNetscanValid -> disabled apply or add button because is necessary at least one discovered_hosts_scan_type
isNetscanValid.value = false;
} else {
// is_ipv4_netscan -> disable the discovered_hosts_scan_types multiselection
is_ipv4_netscan.value = false;
// isNetscanValid -> enable the add or apply button because is not ipv4_netscan case
isNetscanValid.value = true;
}
}
/* ****************************************************** */
const check_is_network_address = (address) => {
const addr_parts = address.split(".");
if (addr_parts.length > 3) {
return addr_parts[3] == 0;
}
}
/* ****************************************************** */
/* Regex to check if the host is correct or not */
const check_host_regex = () => {
const is_ipv4 = regexValidation.validateIPv4(host.value);
const is_ipv6 = regexValidation.validateIPv6(host.value);
const is_host_name = regexValidation.validateHostName(host.value);
if (selected_scan_type.value.id == 'ipv4_netscan') {
if (is_ipv4) {
// the IP must be an IPv4
/* IPv4 */
isHostValid.value = true;
// the selected_discovered_scan_types must be an array with lenght more than 0
isNetscanValid.value = selected_discovered_scan_types.value && selected_discovered_scan_types.value.length > 0;
}
isNetscanValid.value = true; // not ipv4_netscan case so isNetscanValid is true
} else {
/* When it isn't the ipv4_netscan case the cidr selection is enabled */
if (is_ipv4) {
/* IPv4 */
if (!check_is_network_address(host.value)) {
/* In case the CIDR is wrong */
selected_cidr.value = CIDR_32;
} else {
selected_cidr.value = CIDR_24;
}
isHostValid.value = true;
isCIDRValid.value = true;
} else if (is_ipv6) {
/* IPv6 */
isHostValid.value = true;
isCIDRValid.value = true;
/* In case the CIDR is wrong */
selected_cidr.value = CIDR_128
/* IPv6 */
} else if (is_host_name) {
/* Host Name */
isHostValid.value = true;
isCIDRValid.value = true;
/* In case the CIDR is wrong */
selected_cidr.value = CIDR_32;
} else {
isHostValid.value = false;
}
}
};
const check_cidr = () => {
if (( selected_cidr.value >= CIDR_24 && selected_cidr.value <= CIDR_30) ||
selected_cidr.value == CIDR_32 || selected_cidr.value == CIDR_128) {
isCIDRValid.value = true;
return true;
}
isCIDRValid.value = false;
return false;
}
/* ****************************************************** */
/* Regex to check if ports list is correct or not */
const check_ports = () => {
if (
!regexValidation.validatePortRange(ports.value) &&
!regexValidation.validateCommaSeparatedPortList(ports.value) &&
!dataUtils.isEmptyOrNull(ports.value)
) {
isPortCorrect.value = false;
} else {
/* Empty port is alright! */
isPortCorrect.value = true;
}
};
/* ****************************************************** */
/* Resolve hostname */
async function resolve_host_name(host) {
const resolve_host_name_url = `${http_prefix}/lua/rest/v2/get/host/resolve_host_name.lua`;
const url = NtopUtils.buildURL(resolve_host_name_url, {
host: host,
});
return await ntopng_utility.http_request(url);
}
/* ****************************************************** */
/* Function called when the edit button is clicked */
const edit_ = () => {
add_(true);
};
/* ****************************************************** */
/* Function called when the modal is closed */
const close = () => {
refresh_select_search.value = false;
modal_id.value.close();
};
/* ****************************************************** */
/* Function to add host to scan */
const add_ = async (is_edit) => {
const host_ports = ports.value;
const host_scan_type = selected_scan_type.value.id;
const emit_event = (is_edit === true) ? "edit" : "add";
const row_id = (is_edit === true) ? row_to_edit_id.value : null;
let new_host = host.value;
let new_host_name_resolved = true;
/* Activate the spinner to give the user a feedback */
activate_add_spinner.value = true;
/* Check if it's an IP or not, if not it means it's an hostname */
if (!regexValidation.validateIP(host.value)) {
/* During the validation disable the add button */
isHostValid.value = false;
new_host = await resolve_host_name(host.value);
if (new_host === "no_success") {
/* The resolution failed! */
new_host_name_resolved = false;
no_host_feedback.value = host.value + " " + i18n("hosts_stats.page_scan_hosts.host_not_resolved");
is_data_not_ok.value = true;
/* Hide the message after 3 seconds */
setTimeout(() => {
is_data_not_ok.value = false
}, 4000)
}
/* Validation ended, re-enable the button */
isHostValid.value = true;
}
let tmp_second_scan_types = [];
selected_discovered_scan_types.value.forEach((item) => {
tmp_second_scan_types.push(item.id);
})
/* The discovered scan types are sent to the rest in comma separated string list */
const tmp_second_scan_types_formatted = tmp_second_scan_types.join(",");
/* If the resolution was ok or no resolution at all was done emit the event */
activate_add_spinner.value = new_host_name_resolved;
if (new_host_name_resolved) {
/* Emit the event, only if the resolution
was ok or no resolution at all was needed */
emit(emit_event, {
host: new_host,
scan_type: host_scan_type,
scan_ports: host_ports,
vs_cidr: selected_cidr.value,
scan_frequency: is_enterprise_l ? selected_scan_frequency.value.id : null,
scan_id: row_id,
discovered_host_scan_type : tmp_second_scan_types_formatted
});
}
};
/* ****************************************************** */
/* Load the available metrics */
const metricsLoaded = async (_scan_type_list, _ifid, _is_enterprise_l) => {
const scan_types = _scan_type_list.sort((a, b) =>
a.label.localeCompare(b.label)
);
ifid.value = _ifid;
scan_type_list.value = scan_types;
discovered_scan_type_list.value = scan_types.filter((item) => (item.id != 'ipv4_netscan'));
is_enterprise_l.value = _is_enterprise_l;
selected_scan_type.value = scan_type_list.value[0];
};
/* ****************************************************** */
/* ****************************************************** */
/* Function called whenever the CIDR changes,
* in case of a network the port is not needed
*/
/*
<div class="form-group ms-2 me-2 mt-3 row">
<div class="col-2"></div>
<div class="col-10 d-flex align-items-center">
<!--
HIDDEN BUTTON FOR NOW
<button
type="button"
@click="load_ports"
:disabled="!isHostValid || disable_load_ports"
class="btn btn-primary"
>
{{ _i18n("hosts_stats.page_scan_hosts.load_ports") }}
</button>
-->
<dd v-if="show_port_feedback" class="ms-2 mb-0 text-danger">
{{ port_feedback }}
</dd>
<a class="ntopng-truncate"></a>
</div>
</div>
-- Hidden function for now, it's not needed --
const port_feedback = i18n("hosts_stats.page_scan_hosts.no_ports_detected");
const server_ports = `${http_prefix}/lua/rest/v2/get/host/open_ports.lua`;
const disable_load_ports = ref(false);
function disable_ports() {
if (selected_cidr.value.id != cidr_24) {
disable_load_ports.value = false;
} else {
disable_load_ports.value = true;
}
}
async function load_ports() {
activate_spinner.value = true;
/* In case the host is not empty, hide the placeholder */
/* if (dataUtils.isEmptyOrNull(host.value)) {
hide_ports_placeholder.value = true;
} else {
hide_ports_placeholder.value = false;
}
/* Request for the available ports */
/* const url = NtopUtils.buildURL(server_ports, {
host: host.value,
ifid: ifid.value,
clisrv: "server",
});
const result = await ntopng_utility.http_request(url);
/* Show the results or empty if no data was found */
/* if (!dataUtils.isEmptyOrNull(result)) {
ports.value = result.map((x) => x.key).join(",");
show_port_feedback.value = false;
} else {
show_port_feedback.value = true;
ports.value = "";
/* Remove the message after 5 seconds! */
/* setTimeout(() => {
show_port_feedback.value = false;
}, 5000);
}
activate_spinner.value = false;
}
*/
defineExpose({ show, close, metricsLoaded });
</script>