ntopng/http_src/vue/date-time-range-picker.vue
2025-08-06 11:15:12 +02:00

441 lines
21 KiB
Vue

<!-- (C) 2022 - ntop.org -->
<template>
<div class="input-group">
<div class="form-group">
<div class="controls">
<div class="btn-group me-auto btn-group-sm flex-wrap d-flex">
<slot name="begin"></slot>
<div>
<select-search :disabled="disabled_date_picker" v-model:selected_option="selected_time_option"
:id="'time_preset_range_picker'" :options="time_preset_list_filtered"
@select_option="change_select_time(null)">
</select-search>
</div>
<div class="btn-group ms-2">
<input :disabled="disabled_date_picker" class="flatpickr flatpickr-input form-control"
type="text" placeholder="Choose a date.." data-id="datetime" ref="begin-date"
style="width:10rem;">
<!-- <input ref="begin-date" @change="enable_apply=true" @change="change_begin_date" type="date" class="date_time_input begin-timepicker form-control border-right-0 fix-safari-input"> -->
<!-- <input ref="begin-time" @change="enable_apply=true" type="time" class="date_time_input begin-timepicker form-control border-right-0 fix-safari-input"> -->
<span class="input-group-text">
<i class="fas fa-long-arrow-alt-right"></i>
</span>
<input :disabled="disabled_date_picker" class="flatpickr flatpickr-input form-control"
type="text" placeholder="Choose a date.." data-id="datetime" ref="end-date"
style="width:10rem;">
<!-- <input ref="end-date" @change="enable_apply=true" type="date" class="date_time_input end-timepicker form-control border-left-0 fix-safari-input" style="width: 2.5rem;"> -->
<!-- <input ref="end-time" @change="enable_apply=true" type="time" class="date_time_input end-timepicker form-control border-left-0 fix-safari-input"> -->
<span v-show="wrong_date || wrong_min_interval" :title="invalid_date_message"
style="margin-left:0.2rem;color:red;">
<i class="fas fa-exclamation-circle"></i>
</span>
</div>
<div class="d-flex align-items-center ms-2">
<button :disabled="!enable_apply || wrong_date || wrong_min_interval" @click="apply"
type="button" class="btn btn-sm btn-primary">{{
i18n('apply') }}</button>
<div class="btn-group">
<button :disabled="select_time_value == 'custom' || disabled_date_picker"
@click="change_select_time()" type="button" class="btn btn-sm btn-link"
data-bs-toggle="tooltip" data-bs-placement="top"
:title="i18n('date_time_range_picker.btn_refresh')">
<i class="fas fa-sync"></i>
</button>
<slot name="extra_buttons"></slot>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { default as SelectSearch } from "./select-search.vue";
import { ntopng_utility, ntopng_url_manager, ntopng_events_manager } from "../services/context/ntopng_globals_services";
import FormatterUtils from "../utilities/formatter-utils.js";
export default {
components: {
'select-search': SelectSearch,
},
props: {
id: String,
enable_refresh: Boolean,
disabled_date_picker: Boolean,
min_time_interval_id: String,
round_time: Boolean, //if min_time_interval_id != null round time by min_time_interval_id
custom_time_interval_list: Array,
custom_change_select_time: Function,
},
computed: {
// a computed getter
invalid_date_message: function () {
if (this.wrong_date) {
return this.i18n('wrong_date_range');
}
else if (this.wrong_min_interval) {
let msg = this.i18n('wrong_min_interval');
msg.replace('%time_interval', this.i18n(`show_alerts.presets.${this.min_time_interval_id}`));
return msg
}
}
},
watch: {
"enable_refresh": function (val, oldVal) {
if (val == true) {
this.start_refresh();
} else if (this.refresh_interval != null) {
clearInterval(this.refresh_interval);
this.refresh_interval = null;
}
},
"min_time_interval_id": function () {
// todo
},
"round_time": function () {
// todo
},
},
emits: ["epoch_change"],
/** This method is the first method of the component called, it's called before html template creation. */
created() {
},
beforeMount() {
if (this.$props.custom_time_interval_list != null) {
this.time_preset_list = this.$props.custom_time_interval_list;
}
// filter interval
if (this.min_time_interval_id == null) {
this.time_preset_list_filtered = this.time_preset_list;
return;
}
const timeframes_dict = this.get_timeframes_available();
const min_time_interval = timeframes_dict[this.min_time_interval_id];
this.time_preset_list_filtered = this.time_preset_list.filter((elem) => {
if (elem.value == "custom") {
return true;
}
return min_time_interval == null || timeframes_dict[elem.value] >= min_time_interval;
});
},
/** This method is the first method called after html template creation. */
mounted() {
let epoch_begin = ntopng_url_manager.get_url_entry("epoch_begin");
let epoch_end = ntopng_url_manager.get_url_entry("epoch_end");
if (epoch_begin != null && epoch_end != null) {
// update the status
this.emit_epoch_change({ epoch_begin: Number.parseInt(epoch_begin), epoch_end: Number.parseInt(epoch_end) }, this.$props.id, true);
}
let me = this;
let f_set_picker = (picker, var_name) => {
return flatpickr($(this.$refs[picker]), {
enableTime: true,
dateFormat: "d/m/Y H:i",
//altInput: true,
//dateFormat: "YYYY-MM-DD HH:mm",
//altFormat: "d-m-Y H:i",
//locale: "it",
time_24hr: true,
clickOpens: true,
//mode: "range",
//static: true,
onChange: function (selectedDates, dateStr, instance) {
me.enable_apply = true;
me.wrong_date = me.flat_begin_date.selectedDates[0].getTime() > me.flat_end_date.selectedDates[0].getTime();
me.wrong_min_interval = me.min_time_interval_id && me.get_utc_seconds((me.flat_end_date.selectedDates[0].getTime() - me.flat_begin_date.selectedDates[0].getTime()) < ntopng_utility.get_timeframe_from_timeframe_id(me.min_time_interval_id));
//me.a[data] = d;
},
});
};
this.flat_begin_date = f_set_picker("begin-date", "begin_date");
this.flat_end_date = f_set_picker("end-date", "end_date");
ntopng_events_manager.on_event_change(this.$props.id, ntopng_events.EPOCH_CHANGE, (new_status) => this.on_status_updated(new_status), true);
// notifies that component is ready
//console.log(this.$props["id"]);
ntopng_sync.ready(this.$props["id"]);
if (this.enable_refresh) {
this.start_refresh();
}
},
/** Methods of the component. */
methods: {
start_refresh: function () {
this.refresh_interval = setInterval(() => {
let value = this.selected_time_option?.value;
if (this.enable_refresh && value != null && value != "custom") {
this.update_from_interval = true;
this.change_select_time(true);
}
}, this.refresh_interval_seconds * 1000);
// }, 10* 1000);
},
on_status_updated: function (status) {
if (!status.epoch_begin && !status.epoch_end) {
// First iteration, choose from the time_preset_list based on the currently_active parameter
const default_selected = this.time_preset_list.find((elem) => (elem.currently_active === true));
if (default_selected) {
let s_values = this.get_timeframes_available();
let interval_s = s_values[default_selected.value];
status.epoch_end = this.get_utc_seconds(Date.now());
status.epoch_begin = status.epoch_end - interval_s;
}
}
let end_date_time_utc = Date.now();
// default begin date time now - 30 minutes
let begin_date_time_utc = end_date_time_utc - 30 * 60 * 1000;
if (status.epoch_end != null && status.epoch_begin != null
&& Number.parseInt(status.epoch_end) >= Number.parseInt(status.epoch_begin)) {
status.epoch_begin = Number.parseInt(status.epoch_begin);
status.epoch_end = Number.parseInt(status.epoch_end);
end_date_time_utc = status.epoch_end * 1000;
begin_date_time_utc = status.epoch_begin * 1000;
} else {
status.epoch_end = this.get_utc_seconds(end_date_time_utc);
status.epoch_begin = this.get_utc_seconds(begin_date_time_utc);
ntopng_url_manager.add_obj_to_url(status);
this.emit_epoch_change(status, this.$props.id);
}
// this.flat_begin_date.setDate(new Date(status.epoch_begin * 1000));
// this.flat_end_date.setDate(new Date(status.epoch_end * 1000));
this.flat_begin_date.setDate(FormatterUtils.utc_s_to_server_date(status.epoch_begin));
this.flat_end_date.setDate(FormatterUtils.utc_s_to_server_date(status.epoch_end));
// this.set_date_time("begin-date", begin_date_time_utc, false);
// this.set_date_time("begin-time", begin_date_time_utc, true);
// this.set_date_time("end-date", end_date_time_utc, false);
// this.set_date_time("end-time", end_date_time_utc, true);
this.set_select_time_value(begin_date_time_utc, end_date_time_utc);
this.epoch_status = { epoch_begin: status.epoch_begin, epoch_end: status.epoch_end };
if (this.update_from_interval == false) {
this.add_status_in_history(this.epoch_status);
}
this.enable_apply = false;
this.update_from_interval = false;
ntopng_url_manager.add_obj_to_url(this.epoch_status);
},
set_select_time_value: function (begin_utc, end_utc) {
const timeframes_dict = this.get_timeframes_available();
const tolerance = 60;
let now = this.get_utc_seconds(Date.now());
if (this.round_time == true && this.min_time_interval_id != null) {
now = this.round_time_by_min_interval(now)
}
const end_utc_s = this.get_utc_seconds(end_utc);
const begin_utc_s = this.get_utc_seconds(begin_utc);
if (this.is_between(end_utc_s, now, tolerance)) {
this.select_time_value = null;
for (let time_id in timeframes_dict) {
if (this.is_between(begin_utc_s, now - timeframes_dict[time_id], tolerance)) {
this.select_time_value = time_id;
}
}
if (this.select_time_value == null) {
this.select_time_value = "custom";
}
} else {
this.select_time_value = "custom";
}
this.time_preset_list_filtered.forEach(element => {
element.currently_active = false
if (element.value == this.select_time_value) {
this.selected_time_option = element;
element.currently_active = true;
}
});
},
apply: function () {
// let date_begin = this.$refs["begin-date"].valueAsDate;
// let d_time_begin = this.$refs["begin-time"].valueAsDate;
// date_begin.setHours(d_time_begin.getHours());
// date_begin.setMinutes(d_time_begin.getMinutes() + d_time_begin.getTimezoneOffset());
// date_begin.setSeconds(d_time_begin.getSeconds());
// let date_end = this.$refs["end-date"].valueAsDate;
// let d_time_end = this.$refs["end-time"].valueAsDate;
// date_end.setHours(d_time_end.getHours());
// date_end.setMinutes(d_time_end.getMinutes() + d_time_end.getTimezoneOffset());
// date_end.setSeconds(d_time_end.getSeconds());
// let epoch_begin = this.get_utc_seconds(date_begin.valueOf());
// let epoch_end = this.get_utc_seconds(date_end.valueOf());
let now_s = this.get_utc_seconds(Date.now());
let begin_date = FormatterUtils.server_date_to_date(this.flat_begin_date.selectedDates[0]);
let epoch_begin = this.get_utc_seconds(begin_date.getTime());
let end_date = FormatterUtils.server_date_to_date(this.flat_end_date.selectedDates[0]);
let epoch_end = this.get_utc_seconds(end_date.getTime());
if (epoch_end > now_s) {
epoch_end = now_s;
}
let status = { epoch_begin, epoch_end };
this.emit_epoch_change(status);
},
// set_date_time: function(ref_name, utc_ts, is_time) {
// utc_ts = this.get_utc_seconds(utc_ts) * 1000;
// let date_time = new Date(utc_ts);
// date_time.setMinutes(date_time.getMinutes() - date_time.getTimezoneOffset());
// if (is_time) {
// this.$refs[ref_name].value = date_time.toISOString().substring(11,16);
// } else {
// this.$refs[ref_name].value = date_time.toISOString().substring(0,10);
// }
// },
change_select_time: function (refresh_data) {
let epoch_end;
let epoch_begin;
if (this.$props.custom_change_select_time) {
[epoch_begin, epoch_end] = this.$props.custom_change_select_time(this.selected_time_option.value);
} else {
let s_values = this.get_timeframes_available();
let interval_s = s_values[this.selected_time_option.value];
epoch_end = this.get_utc_seconds(Date.now());
epoch_begin = epoch_end - interval_s;
}
let status = { epoch_begin: epoch_begin, epoch_end: epoch_end, timeframe_id: this.selected_time_option.value, refresh_data };
this.emit_epoch_change(status);
},
get_timeframes_available: function () {
const timeframes_dict = ntopng_utility.get_timeframes_dict();
const timeframes_ids = this.time_preset_list.map((ts) => ts.value);
let timeframes_available = {};
timeframes_ids.forEach((tf_id) => {
timeframes_available[tf_id] = timeframes_dict[tf_id];
});
return timeframes_available;
},
get_utc_seconds: function (utc_ms) {
return ntopng_utility.get_utc_seconds(utc_ms);
},
is_between: function (x, y, tolerance) {
return x >= y - tolerance && x <= y;
},
zoom: function (scale) {
if (this.epoch_status == null) { return; }
let interval = (this.epoch_status.epoch_end - this.epoch_status.epoch_begin) / scale;
let center = (this.epoch_status.epoch_end / 2 + this.epoch_status.epoch_begin / 2);
this.epoch_status.epoch_begin = center - interval / 2;
this.epoch_status.epoch_end = center + interval / 2;
let now = this.get_utc_seconds(Date.now());
if (this.epoch_status.epoch_end > now) {
this.epoch_status.epoch_end = now;
}
this.epoch_status.epoch_end = Number.parseInt(this.epoch_status.epoch_end);
this.epoch_status.epoch_begin = Number.parseInt(this.epoch_status.epoch_begin);
if (this.epoch_status.epoch_begin == this.epoch_status.epoch_end) {
this.epoch_status.epoch_begin -= 2;
}
this.emit_epoch_change(this.epoch_status);
},
jump_time_back: function () {
if (this.epoch_status == null) { return; }
const min = 60;
this.epoch_status.epoch_begin -= (30 * min);
this.epoch_status.epoch_end -= (30 * min);
this.emit_epoch_change(this.epoch_status);
},
jump_time_ahead: function () {
if (this.epoch_status == null) { return; }
const min = 60;
let previous_end = this.epoch_status.epoch_end;
let now = this.get_utc_seconds(Date.now());
this.epoch_status.epoch_end += (30 * min);
if (this.epoch_status.epoch_end > now) {
this.epoch_status.epoch_end = now;
}
this.epoch_status.epoch_begin += (this.epoch_status.epoch_end - previous_end);
this.emit_epoch_change(this.epoch_status);
},
emit_epoch_change: function (epoch_status, id, emit_only_global_event) {
if (epoch_status.epoch_end == null || epoch_status.epoch_begin == null) { return; };
this.wrong_date = false;
if (epoch_status.epoch_begin > epoch_status.epoch_end) {
this.wrong_date = true;
return;
}
if (this.min_time_interval_id && this.round_time == true) {
epoch_status.epoch_begin = this.round_time_by_min_interval(epoch_status.epoch_begin);
epoch_status.epoch_end = this.round_time_by_min_interval(epoch_status.epoch_end);
}
if (id != this.id) {
this.on_status_updated(epoch_status);
}
ntopng_events_manager.emit_event(ntopng_events.EPOCH_CHANGE, epoch_status, this.id);
if (emit_only_global_event) {
return;
}
this.$emit("epoch_change", epoch_status);
},
round_time_by_min_interval: function (ts) {
return ntopng_utility.round_time_by_timeframe_id(ts, this.min_time_interval_id);
},
add_status_in_history: function (epoch_status) {
this.history_last_status = this.history[this.history.length - 1];
if (this.history.length > 5) {
this.history.shift();
}
this.history.push(epoch_status);
},
apply_status_by_history: function () {
if (this.history_last_status == null) { return; }
this.history.pop();
this.history.pop();
this.emit_epoch_change(this.history_last_status);
},
},
/**
Private date of vue component.
*/
data() {
return {
i18n: (t) => i18n(t),
//status_id: "date-time-range-picker" + this.$props.id,
epoch_status: null,
refresh_interval: null,
refresh_interval_seconds: 60,
update_from_interval: false,
history: [],
history_last_status: null,
enable_apply: false,
select_time_value: "5_min",
selected_time_option: { value: "5_min", label: i18n('show_alerts.presets.5_min'), currently_active: false },
wrong_date: false,
wrong_min_interval: false,
flat_begin_date: null,
flat_end_date: null,
time_preset_list: [
{ value: "5_min", label: i18n('show_alerts.presets.5_min'), currently_active: false },
{ 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, },
],
time_preset_list_filtered: [],
};
},
}
</script>
<style scoped>
.date_time_input {
width: 10.5rem;
max-width: 10.5rem;
min-width: 10.5rem;
}
</style>