ntopng/httpdocs/js/pages/datasource_list.js
2020-12-02 16:57:01 +01:00

473 lines
17 KiB
JavaScript

$(document).ready(function () {
class TimeserieSourceBuilder {
constructor() {
this.counter = 0;
this.currentSources = [];
this.MIN_SOURCE_COUNTER = 1;
this.MAX_SOURCE_COUNTER = 4;
}
canCreateSource() {
return (this.currentSources.length <= this.MAX_SOURCE_COUNTER);
}
canRemoveSource() {
return (this.currentSources.length > this.MIN_SOURCE_COUNTER);
}
buildNewSource(accordionName, name = `source-${this.counter++}`) {
const timeserieSourceTemplate = $(`template#ds-source`).html();
const tagTemplate = $(`template#ds-source-tag`).html();
const timeseriesSourceInstance = new TimeserieSource({
name: name,
tagTemplate: tagTemplate,
timeseriesFamilies: timeseriesFamilies,
builder: this,
accordionName: accordionName,
$domElement: $(timeserieSourceTemplate),
$tagElement: $(tagTemplate),
});
this.currentSources.push(timeseriesSourceInstance);
return timeseriesSourceInstance;
}
emptyCurrentSources() {
this.currentSources = [];
}
}
class TimeserieSource {
constructor(args) {
const { $domElement, $tagElement, name, timeseriesFamilies, builder, accordionName } = args;
this.$domElement = $domElement;
this.$cardTitle = $domElement.find(`a[data-toggle='collapse']`);
this.$btnRemoveSource = $domElement.find(`.btn-remove-source`);
this.$familiesSelect = $domElement.find(`select.family`);
this.$schemasSelect = $domElement.find(`select.schema`);
this.$metricsSelect = $domElement.find(`select.metric`);
this.$tagsContainer = $domElement.find('.tags-container');
this.$tagElement = $tagElement;
this.parentName = accordionName;
this.steps = [$domElement.find('.step-1'), $domElement.find('.step-2')];
this.stepsCompleted = [false, false];
this.timeseriesFamilies = timeseriesFamilies;
this.builder = builder;
this.setSourceId(name);
this.setAccordionParent(this.parentName);
this.bindEventListeners();
}
get getDomElement() {
return this.$domElement;
}
get identifierSource() {
return `${this.$familiesSelect.val()}-${this.$schemasSelect.val()}-${this.$metricsSelect.val()}`;
}
setAccordionParent(parentName) {
this.$domElement.find('.collapse').attr('data-parent', parentName);
}
bindEventListeners() {
const self = this;
this.$familiesSelect.change(function (e) {
self.onFamilySelect(e);
});
this.$schemasSelect.change(function (e) {
self.onSchemaSelect(e);
});
this.$metricsSelect.change(function (e) {
self.onMetricSelect(e);
});
this.$btnRemoveSource.click(function (e) {
self.onRemoveSourceClick(e);
});
}
setSourceId(name) {
this.$cardTitle.attr('href', `#source-${name}`);
this.$domElement.find(`div.collapse.show`).attr('id', `source-${name}`);
this.setCardTitle(name);
}
setCardTitle(title) {
this.$cardTitle.html(`<b>${title}</b>`);
}
generateTag(tagName, tagValue) {
const $tagElement = this.$tagElement.clone();
$tagElement.find('span').html(`<b>${tagName}</b>`);
$tagElement.find('input').val(`${tagValue}`)
.attr('pattern', `(\\\$${tagName}|[0-9]+)`)
.attr('minlength', 1)
.attr(`data-name`, tagName);
this.$tagsContainer.append($tagElement);
return $tagElement;
}
generateTags(tags) {
this.$tagsContainer.empty();
for (const tag of tags) {
this.$tagsContainer.append(this.generateTag(tag, `$${tag}`));
}
}
generateFilledTags(tags) {
this.$tagsContainer.empty();
for (const [tagName, tagValue] of Object.entries(tags)) {
this.$tagsContainer.append(this.generateTag(tagName, tagValue));
}
}
generateSelectOptions($select, values, generateEmpty = true, titleDummy = "Select ...") {
const generateOption = (val, label, disabled = false) => {
const $option = $(`<option value ${disabled ? 'disabled hidden selected' : ''}>${label}</option>`);
if (val) {
$option.attr('value', val);
}
$select.append($option);
};
/* clean all the options */
$select.find('option').remove();
if (generateEmpty) generateOption(undefined, '', true);
// generate dummy option
generateOption(null, titleDummy, true);
if (values.length > 0) {
for (const val of values) generateOption(val, val);
return;
}
for (const [key, _] of Object.entries(values).sort((a, b) => a[0].localeCompare(b[0])))
generateOption(key, key);
}
onFamilySelect(event) {
/* render only schema which belongs to the selected family */
const schemas = this.timeseriesFamilies[this.$familiesSelect.val()];
this.generateSelectOptions(this.$schemasSelect, schemas, "Select a schema...");
/* show step one inside the card */
if (!this.stepsCompleted[0]) {
this.steps[0].fadeIn();
this.stepsCompleted[0] = true;
}
if (this.stepsCompleted[1]) {
this.steps[1].fadeOut();
this.$schemasSelect.prop('selectedIndex', -1).removeClass('is-valid');
this.stepsCompleted[0] = false;
}
}
onSchemaSelect(event) {
/* render only schema which belongs to the selected family */
const schema = this.timeseriesFamilies[this.$familiesSelect.val()][this.$schemasSelect.val()];
const { metrics, tags } = schema;
this.generateSelectOptions(this.$metricsSelect, metrics, false, "Select a metric...");
if (!this.preCreated) this.generateTags(tags);
this.steps[1].fadeIn();
this.stepsCompleted[1] = true;
}
onMetricSelect(event) {
// check if exists a source with the same id
const self = this;
const exists = this.builder.currentSources.some((source) => {
if (source == self) return false;
return (source.identifierSource == self.identifierSource);
});
if (exists) {
event.stopPropagation();
event.preventDefault();
this.$metricsSelect
.removeClass('is-valid').addClass('is-invalid');
this.$metricsSelect.parent().append("<span class='invalid-feedback'>${i18n.source_exists}</span>");
this.$metricsSelect.parents('form').find(`button[type='submit']`).attr("disabled", "disabled");
return;
}
else {
this.$metricsSelect.parent().find(`span.invalid-feedback`).remove();
this.$metricsSelect.parents('form').find(`button[type='submit']`).removeAttr("disabled");
}
this.setCardTitle(`${this.$schemasSelect.val()} / ${this.$metricsSelect.val()}`);
}
onRemoveSourceClick(event) {
if (!this.builder.canRemoveSource()) return;
event.preventDefault();
this.$domElement.fadeOut(200, function () { $(this).remove(); });
this.removeSourceFromBuilder();
}
removeSourceFromBuilder() {
const index = this.builder.currentSources.indexOf(this);
if (index > -1) {
this.builder.currentSources.splice(index, 1);
}
}
fillSource(args) {
const {metric, schema, tags} = args;
const family = schema.split(":")[0];
this.$familiesSelect.val(family).trigger('change');
this.$schemasSelect.val(schema).trigger('change');
this.$metricsSelect.val(metric);
this.generateFilledTags(tags);
}
}
const timeseriesSourceBuilder = new TimeserieSourceBuilder();
let dtConfig = DataTableUtils.getStdDatatableConfig( [
{
text: '<i class="fas fa-plus"></i>',
action: function (e, dt, node, config) {
$('#add-datasource-modal').modal('show');
}
},
{
text: '<i class="fas fa-sync"></i>',
action: function (e, dt, node, config) {
$datasourcesTable.ajax.reload();
}
}
]);
dtConfig = DataTableUtils.setAjaxConfig(dtConfig,`${http_prefix}/lua/get_datasources.lua`);
dtConfig = DataTableUtils.extendConfig(dtConfig, {
columns: [
{ data: 'alias' },
{
data: 'hash',
render: (hash) => `<a target=\"_blank\" href=\"/datasources/${hash}\"'>${hash}</a>`
},
{ data: 'scope' },
{ data: 'origin', render: (origin, type) => {
if (type === "display") return `<code>${origin}</code>`;
return origin;
}},
{ data: 'data_retention', widht: '5%', render: (retention, type) => {
if (type === "display") return NtopUtils.fint(retention);
return retention;
} },
{
targets: -1,
className: 'text-center',
data: null,
width: '10%',
render: function (data) {
const isDeleteDisabled = data.in_use;
return DataTableUtils.createActionButtons([
{ class: 'btn-info', icon: 'fa-edit', modal: '#edit-datasource-modal' },
{
class: `btn-danger ${isDeleteDisabled ? 'disabled' : ''}`,
icon: 'fa-trash',
modal: '#remove-datasource-modal'
},
]);
}
},
],
});
const $datasourcesTable = $(`#datasources-list`).DataTable(dtConfig);
const prepareFormData = (form) => {
const $form = $(form);
const serialized = NtopUtils.serializeFormArray($form.serializeArray());
serialized.schemas = {};
$form.find('fieldset').each(function(i, fieldset) {
const schemaKey = $(this).find(`[name='schema[]']`).val();
const metric = $(this).find(`[name='metric[]']`).val();
const tags = {};
$(this).find(`.tag`).each(function(i, tag) {
tags[$(this).attr('data-name')] = $(this).val();
});
serialized.schemas[schemaKey] = { metric: metric, tags: tags };
});
return serialized;
}
const $editDatasourceHandler = $('#edit-datasource-modal form').modalHandler({
method: 'post',
endpoint: `${http_prefix}/lua/edit_datasources.lua`,
csrf: ds_csrf,
resetAfterSubmit: false,
beforeSumbit: function (selectedDatasource) {
const ds = prepareFormData(`#edit-datasource-modal form`);
ds.hash = selectedDatasource.hash;
return { action: 'edit', JSON: JSON.stringify(ds) };
},
onModalInit: function(selectedDatasource) {
$(`.datasource-name`).text(selectedDatasource.alias);
/* fill default datasource values */
$(`#edit-datasource-modal form`).find('[name]').each(function(e) {
$(this).val(selectedDatasource[$(this).attr('name')]);
});
const $sourcesContainer = $(`#edit-datasource-modal .ds-source-container`);
const $btnAddSource = $(`#edit-datasource-modal .btn-add-source`);
$btnAddSource.hide();
$sourcesContainer.hide().empty();
/* if the origin is of type timeseries then prepare sources */
if (selectedDatasource.origin != "timeseries.lua") return;
const schemas = selectedDatasource.schemas;
for (const [key, schema] of Object.entries(schemas)) {
const source = timeseriesSourceBuilder.buildNewSource(
`#edit-ds-source-container`,
`source-${selectedDatasource.hash}-${schema.metric}`
);
source.setCardTitle(`${key} / ${schema.metric}`);
source.fillSource({ schema: key, metric: schema.metric, tags: schema.tags });
$sourcesContainer.append(source.$domElement);
}
$sourcesContainer.show();
$btnAddSource.show();
},
onSubmitSuccess: function(response) {
if (response.success) {
$datasourcesTable.ajax.reload();
timeseriesSourceBuilder.emptyCurrentSources();
$('#edit-datasource-modal').modal('hide');
}
else {
$(`#edit-datasource-modal .invalid-feedback`).show().text(response.message);
}
}
});
/* bind edit datasource event */
$(`#datasources-list`).on('click', `a[href='#edit-datasource-modal']`, function (e) {
const selectedDatasource = $datasourcesTable.row($(this).parent().parent()).data();
$editDatasourceHandler.invokeModalInit(selectedDatasource);
});
/* bind add datasource event */
$(`#add-datasource-modal form`).modalHandler({
method: 'post',
endpoint: `${http_prefix}/lua/edit_datasources.lua`,
csrf: ds_csrf,
resetAfterSubmit: false,
beforeSumbit: function () {
return {
action: 'add',
JSON: JSON.stringify(prepareFormData(`#add-datasource-modal form`))
};
},
onSubmitSuccess: function (response) {
if (response.success) {
$('#add-datasource-modal').modal('hide');
$(`#add-ds-source-container`).fadeOut().empty();
$(`#btn-add-source`).fadeOut();
timeseriesSourceBuilder.emptyCurrentSources();
$datasourcesTable.ajax.reload();
}
else {
$(`#add-datasource-modal .invalid-feedback`).show().text(response.message);
}
}
}).invokeModalInit();
const $removeDatasourceHandler = $(`#remove-datasource-modal form`).modalHandler({
method: 'post',
endpoint: `${http_prefix}/lua/edit_datasources.lua`,
dontDisableSubmit: true,
csrf: ds_csrf,
beforeSumbit: (datasource) => {
return { action: 'remove', JSON: JSON.stringify({ ds_key: datasource.hash}) }
},
onModalInit: (datasource) => {
$(`.datasource-name`).text(datasource.alias);
},
onSubmitSuccess: (response) => {
if (response.success) {
$datasourcesTable.ajax.reload();
$('#remove-datasource-modal').modal('hide');
}
else {
$(`#remove-datasource-modal .invalid-feedback`).show().text(response.message);
}
}
});
/* bind remove datasource event */
$(`#datasources-list`).on('click', `a[href='#remove-datasource-modal']`, function (e) {
const selectedDatasource = $datasourcesTable.row($(this).parent().parent()).data();
$removeDatasourceHandler.invokeModalInit(selectedDatasource);
});
/* **************************************************************************************** */
$(`.btn-add-source`).click(function (e) {
e.preventDefault(); e.stopPropagation();
const $sourcesContainer = $(this).parents('form').find(`.ds-source-container`);
if (timeseriesSourceBuilder.canCreateSource()) {
$sourcesContainer.find('.collapse').collapse('hide');
$sourcesContainer.append(
timeseriesSourceBuilder.buildNewSource(`#add-ds-source-container`).$domElement
);
}
});
$(`#add-datasource-modal select[name='origin'], #edit-datasource-modal select[name='origin']`).change(function (e) {
const isTimeseries = $(this).val() == "timeseries.lua";
const $sourcesContainer = $(this).parents('form').find(`.ds-source-container`);
const $btnAddSource = $(this).parents().find(`.btn-add-source`);
if (!isTimeseries) {
$sourcesContainer.fadeOut().empty();
$btnAddSource.fadeOut();
return;
}
// be sure to create new elements
timeseriesSourceBuilder.emptyCurrentSources();
$sourcesContainer.append(timeseriesSourceBuilder.buildNewSource(`#add-ds-source-container`).$domElement);
$sourcesContainer.fadeIn();
$btnAddSource.fadeIn();
});
});