<?php /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ /** * @var CView $this */ ?> <script type="text/x-jquery-tmpl" id="lldoverride-row-templated"> <?= (new CRow([ '', (new CSpan('1:'))->setAttribute('data-row-num', ''), (new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});')))), '#{stop_verbose}', (new CCol( (new CButtonLink(_('Remove'))) ->addClass('element-table-remove') ->setEnabled(false) ))->addClass(ZBX_STYLE_NOWRAP) ]))->toString() ?> </script> <script type="text/x-jquery-tmpl" id="lldoverride-row"> <?= (new CRow([ (new CCol((new CDiv())->addClass(ZBX_STYLE_DRAG_ICON)))->addClass(ZBX_STYLE_TD_DRAG_ICON), (new CCol((new CSpan('1:'))->setAttribute('data-row-num', ''))) ->setWidth('15'), (new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});')))) ->setWidth('350'), (new CCol('#{stop_verbose}')) ->setWidth('100'), (new CCol( (new CButtonLink(_('Remove')))->addClass('element-table-remove') )) ->addClass(ZBX_STYLE_NOWRAP) ->setWidth('50') ])) ->toString() ?> </script> <script type="text/x-jquery-tmpl" id="override-filters-row"> <?= (new CRow([[ new CSpan('#{formulaId}'), new CVar('overrides_filters[#{rowNum}][formulaid]', '#{formulaId}') ], (new CTextBox('overrides_filters[#{rowNum}][macro]', '', false, DB::getFieldLength('lld_override_condition', 'macro'))) ->setWidth(ZBX_TEXTAREA_MACRO_WIDTH) ->addClass(ZBX_STYLE_UPPERCASE) ->addClass('macro') ->setAttribute('placeholder', '{#MACRO}') ->setAttribute('data-formulaid', '#{formulaId}'), (new CSelect('overrides_filters[#{rowNum}][operator]')) ->setValue(CONDITION_OPERATOR_REGEXP) ->addClass('js-operator') ->addOptions(CSelect::createOptionsFromArray([ CONDITION_OPERATOR_REGEXP => _('matches'), CONDITION_OPERATOR_NOT_REGEXP => _('does not match'), CONDITION_OPERATOR_EXISTS => _('exists'), CONDITION_OPERATOR_NOT_EXISTS => _('does not exist') ])), (new CDiv( (new CTextBox('overrides_filters[#{rowNum}][value]', '', false, DB::getFieldLength('lld_override_condition', 'value'))) ->addClass('js-value') ->setWidth(ZBX_TEXTAREA_MACRO_VALUE_WIDTH) ->setAttribute('placeholder', _('regular expression')) ))->setWidth(ZBX_TEXTAREA_MACRO_VALUE_WIDTH), (new CCol( (new CButton('overrides_filters#{rowNum}_remove', _('Remove'))) ->addClass(ZBX_STYLE_BTN_LINK) ->addClass('element-table-remove') ))->addClass(ZBX_STYLE_NOWRAP) ])) ->addClass('form_row') ->toString() ?> </script> <script type="text/x-jquery-tmpl" id="lldoverride-operation-row-templated"> <?= (new CRow([ ['#{condition_object} #{condition_operator} ', italic('#{value}')], (new CCol( (new CButtonLink(_('View'))) ->addClass('element-table-open') ->onClick('lldoverrides.operations.open(#{no});') ))->addClass(ZBX_STYLE_NOWRAP) ]))->toString() ?> </script> <script type="text/x-jquery-tmpl" id="lldoverride-operation-row"> <?= (new CRow([ ['#{condition_object} #{condition_operator} ', italic('#{value}')], (new CHorList([ (new CButtonLink(_('Edit'))) ->addClass('element-table-open') ->onClick('lldoverrides.operations.open(#{no});'), (new CButtonLink(_('Remove')))->addClass('element-table-remove') ]))->addClass(ZBX_STYLE_NOWRAP) ]))->toString() ?> </script> <script type="text/x-jquery-tmpl" id="lldoverride-custom-intervals-row"> <?= (new CRow([ (new CRadioButtonList('opperiod[delay_flex][#{rowNum}][type]', 0)) ->addValue(_('Flexible'), ITEM_DELAY_FLEXIBLE) ->addValue(_('Scheduling'), ITEM_DELAY_SCHEDULING) ->setModern(true), [ (new CTextBox('opperiod[delay_flex][#{rowNum}][delay]')) ->setAttribute('placeholder', ZBX_ITEM_FLEXIBLE_DELAY_DEFAULT), (new CTextBox('opperiod[delay_flex][#{rowNum}][schedule]')) ->setAttribute('placeholder', ZBX_ITEM_SCHEDULING_DEFAULT) ->setAttribute('style', 'display: none;') ], (new CTextBox('opperiod[delay_flex][#{rowNum}][period]')) ->setAttribute('placeholder', ZBX_DEFAULT_INTERVAL), (new CButton('opperiod[delay_flex][#{rowNum}][remove]', _('Remove'))) ->addClass(ZBX_STYLE_BTN_LINK) ->addClass('element-table-remove') ])) ->addClass('form_row') ->toString() ?> </script> <script type="text/x-jquery-tmpl" id="lldoverride-tag-row"> <?= renderTagTableRow('#{rowNum}', ['tag' => '', 'value' => ''], ['field_name' => 'optag', 'add_post_js' => false]) ?> </script> <script type="text/javascript"> jQuery(function($) { window.lldoverrides = { templated: <?= $data['limited'] ? 1 : 0 ?>, msg: { yes: <?= json_encode(_('Yes')) ?>, no: <?= json_encode(_('No')) ?>, item_prototype: <?= json_encode(_('Item prototype')) ?>, trigger_prototype: <?= json_encode(_('Trigger prototype')) ?>, graph_prototype: <?= json_encode(_('Graph prototype')) ?>, host_prototype: <?= json_encode(_('Host prototype')) ?>, equals: <?= json_encode(_('equals')) ?>, does_not_equal: <?= json_encode(_('does not equal')) ?>, contains: <?= json_encode(_('contains')) ?>, does_not_contain: <?= json_encode(_('does not contain')) ?>, matches: <?= json_encode(_('matches')) ?>, does_not_match: <?= json_encode(_('does not match')) ?> } }; window.lldoverrides.override_row_template = new Template(jQuery(lldoverrides.templated ? '#lldoverride-row-templated' : '#lldoverride-row' ).html()); window.lldoverrides.operations_row_template = new Template(jQuery(lldoverrides.templated ? '#lldoverride-operation-row-templated' : '#lldoverride-operation-row' ).html()); window.lldoverrides.overrides = new Overrides($('#overridesTab'), <?= json_encode(array_values($data['overrides'])) ?> ); window.lldoverrides.actions = ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends', 'opseverity', 'optag', 'optemplate', 'opinventory' ]; window.lldoverrides.$form = $('form[name="itemForm"]').on('submit', function(e) { var hidden_form = this.querySelector('#hidden-form'); hidden_form && hidden_form.remove(); hidden_form = document.createElement('div'); hidden_form.id = 'hidden-form'; hidden_form.appendChild(lldoverrides.overrides.toFragment()); this.appendChild(hidden_form); }); }); /** * Writes data index as new nodes attribute. Bind remove event. */ function dynamicRowsBindNewRow($el) { $el.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { e.new_node.setAttribute('data-index', e.data_index); e.new_node.querySelector('.element-table-remove') .addEventListener('click', dynamic_rows.removeRow.bind(dynamic_rows, e.data_index)); // Because IE does not have NodeList.prototype.forEach method. Array.prototype.forEach.call(e.new_node.querySelectorAll('input'), function(input_node) { input_node.addEventListener('keyup', function(e) { $el.trigger('dynamic_rows.updated', dynamic_rows); }); }); }); } /** * A helper method for creating hidden input nodes. * * @param {string} name * @param {string} value * @param {string} prefix * * @return {object} Return input element node. */ function hiddenInput(name, value, prefix) { var input = window.document.createElement('input'); input.type = 'hidden'; input.value = value; input.name = prefix ? prefix + '[' + name + ']' : name; return input; } /** * @param {object} $element * @param {object} options * @param {array} data */ function DynamicRows($element, options, data) { if (!(options.add_before instanceof Node)) { throw 'Error: options.add_before must be instanceof Node.'; } if (!(options.template instanceof Template)) { throw 'Error: options.template must be instanceof Template.'; } this.data = {}; this.$element = $element; this.options = jQuery.extend({}, { // Please note, this option does not work if data_index option is in use. ensure_min_rows: 0, // If this option is used, it represents key of data object whose value will be used as data_index. data_index: null }, options); this.data_index = 0; this.length = 0; data && this.setData(data); } /** * All events are dispatched on $element. Second argument is instance, first argument is event object. * * @param {string} evt_key * @param {object} evt_data Optional object to be merged into event data. * * @return {object} Returns a jQuery.Event object. */ DynamicRows.prototype.dispatch = function(evt_key, evt_data) { var evt = jQuery.Event('dynamic_rows.' + evt_key, evt_data); this.$element.trigger(evt, [this]); return evt; } /** * Adds a row before the given row. * * @param {object} data Data to be passed into template. * @param {number} data_index Optional index, if data with given index exists, then in place update will happen. * In case of update all events dispatched as if add new was performed. */ DynamicRows.prototype.addRow = function(row_data, data_index) { if (this.options.disabled) { return; } if (!data_index) { data_index = this.options.data_index ? row_data[this.options.data_index] : ++this.data_index; } var new_row = { node: this.createRowNode(row_data), data: row_data || {} }; var evt_before_add = this.dispatch('beforeadd', { new_data: new_row.data, new_node: new_row.node, add_before: this.options.add_before, data_index: data_index }); if (evt_before_add.isDefaultPrevented()) { return; } if (this.data[data_index]) { evt_before_add.add_before.parentNode.replaceChild(evt_before_add.new_node, this.data[data_index].node); } else { evt_before_add.add_before.parentNode.insertBefore(evt_before_add.new_node, evt_before_add.add_before); this.length++; } this.data[data_index] = new_row; this.dispatch('updated'); }; /** * Replaces current data with new one. * Be aware that min rows are ensured and events are triggered only for add. * Removing happens outside this API, to not to call. * * @param {array} data Array of data for row templates. * * @return {object} Returns the DynamicRows object. */ DynamicRows.prototype.setData = function(data) { if (!(data instanceof Array)) { throw 'Expected Array.'; } for (var i in this.data) { this.unlinkIndex(i); } data.forEach(function(obj) { this.addRow(obj); }.bind(this)); this.ensureMinRows(); return this; }; /** * Adds empty rows if needed. */ DynamicRows.prototype.ensureMinRows = function() { var rows_to_add = this.options.ensure_min_rows - this.length; while (rows_to_add > 0) { rows_to_add--; this.addRow(); } }; /** * Renders Node from template. * * @param {object} data Data to be passed into template. * * @return {object} */ DynamicRows.prototype.createRowNode = function(data) { var evt = this.dispatch('beforerender', {view_data: data}); var html_str = this.options.template.evaluate(evt.view_data); return jQuery(html_str).get(0); }; /** * Removes data at given index. Method is to be used by plugin internally, does not dispatch events. * * @param {number} data_index * * @return {object} Object that just got removed. */ DynamicRows.prototype.unlinkIndex = function(data_index) { this.data[data_index].node.remove(); var ref = this.data[data_index]; delete this.data[data_index]; this.length--; return ref; } /** * Removes the given row. * * @param {number} data_index */ DynamicRows.prototype.removeRow = function(data_index) { if (this.options.disabled) { return; } var removed_row = this.unlinkIndex(data_index); this.dispatch('afterremove', {removed_row: removed_row, data_index: data_index}); this.dispatch('updated'); this.ensureMinRows(); }; /** * Represents overrides tab in discovery rules layout. * * @param {object} $tab * @param {array} overrides Initial overrides objects data array. */ function Overrides($tab, overrides) { this.data = {}; this.new_id = 0; this.sort_index = []; overrides.forEach(function(override, no) { this.data[no + 1] = new Override(override); this.sort_index.push(no + 1); }.bind(this)); this.$container = jQuery('.lld-overrides-table', $tab); this.$container.find('.element-table-add').on('click', this.openNew.bind(this)); this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) { e.view_data.stop_verbose = (e.view_data.stop === '1') ? lldoverrides.msg.yes : lldoverrides.msg.no; }); this.overrides_dynamic_rows = new DynamicRows(this.$container, { add_before: this.$container.find('.element-table-add').closest('tr')[0], template: lldoverrides.override_row_template }); if (!lldoverrides.templated) { this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) { delete this.data[e.data_index]; this.onSortOrderChange(); }.bind(this)); dynamicRowsBindNewRow(this.$container); } else { this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { e.new_node.setAttribute('data-index', e.data_index); }); } new CSortable(this.$container[0].querySelector('tbody'), { selector_handle: 'div.<?= ZBX_STYLE_DRAG_ICON ?>', freeze_end: 1, enable_sorting: <?= json_encode(!$data['limited']) ?> }) .on(CSortable.EVENT_SORT, () => this.onSortOrderChange()); this.renderData(); } /** * The parts of form that are easier to maintain in functional objects are transformed into hidden input fields. * * @return {object} Returns DocumentFragment object. */ Overrides.prototype.toFragment = function() { var frag = document.createDocumentFragment(), iter_step = 0; this.sort_index.forEach(function(id) { var override = this.data[id], prefix_override = 'overrides[' + (iter_step++) + ']', prefix_filter = prefix_override + '[filter]', iter_filters = 0, iter_operations = 0; frag.appendChild(hiddenInput('step', iter_step, prefix_override)); frag.appendChild(hiddenInput('name', override.data.name, prefix_override)); frag.appendChild(hiddenInput('stop', override.data.stop, prefix_override)); frag.appendChild(hiddenInput('evaltype', override.data.overrides_evaltype, prefix_filter)); frag.appendChild(hiddenInput('formula', override.data.overrides_formula, prefix_filter)); override.data.overrides_filters.forEach(function(override_filter) { var prefix = prefix_filter + '[conditions][' + (iter_filters++) + ']'; frag.appendChild(hiddenInput('formulaid', override_filter.formulaid, prefix)); frag.appendChild(hiddenInput('macro', override_filter.macro, prefix)); frag.appendChild(hiddenInput('value', override_filter.value, prefix)); frag.appendChild(hiddenInput('operator', override_filter.operator, prefix)); }); override.data.operations.forEach(function(operation) { var prefix = prefix_override + '[operations][' + (iter_operations++) + ']'; frag.appendChild(hiddenInput('operationobject', operation.operationobject, prefix)); frag.appendChild(hiddenInput('operator', operation.operator, prefix)); frag.appendChild(hiddenInput('value', operation.value, prefix)); if ('opstatus' in operation) { frag.appendChild(hiddenInput('status', operation.opstatus.status, prefix + '[opstatus]')); } if ('opdiscover' in operation) { frag.appendChild(hiddenInput('discover', operation.opdiscover.discover, prefix + '[opdiscover]')); } if ('opperiod' in operation) { frag.appendChild(hiddenInput('delay', operation.opperiod.delay, prefix + '[opperiod]')); } if ('ophistory' in operation) { frag.appendChild(hiddenInput('history', operation.ophistory.history, prefix + '[ophistory]')); } if ('optrends' in operation) { frag.appendChild(hiddenInput('trends', operation.optrends.trends, prefix + '[optrends]')); } if ('opseverity' in operation) { frag.appendChild(hiddenInput('severity', operation.opseverity.severity, prefix + '[opseverity]')); } if ('optag' in operation) { var iter_tags = 0; operation.optag.forEach(function(tag) { var prefix_tag = prefix + '[optag][' + (iter_tags++) + ']'; frag.appendChild(hiddenInput('tag', tag.tag, prefix_tag)); if (('value' in tag) && 'value' !== '') { frag.appendChild(hiddenInput('value', tag.value, prefix_tag)); } }); } if ('optemplate' in operation) { var iter_templates = 0; operation.optemplate.forEach(function(template) { var prefix_template = prefix + '[optemplate][' + (iter_templates++) + ']'; frag.appendChild(hiddenInput('templateid', template.templateid, prefix_template)); }); } if ('opinventory' in operation) { frag.appendChild(hiddenInput('inventory_mode', operation.opinventory.inventory_mode, prefix + '[opinventory]' )); } }); }.bind(this)); return frag; }; /** * This method maintains property for iterating overrides in the order that rows have in DOM at the moment, * also updates visual counter in DOM for override rows. */ Overrides.prototype.onSortOrderChange = function() { var order = []; this.$container.find('[data-index]').each(function(index) { this.querySelector('[data-row-num]').innerText = (index + 1) + ':'; order.push(this.attributes.getNamedItem('data-index').value); }); this.sort_index = order; }; /** * Used to validate override names with server, on PopUp form validate event. * * @return {array} Array of strings. */ Overrides.prototype.getOverrideNames = function() { var names = []; for (var no in this.data) { names.push(this.data[no].data.name); } return names; }; /** * This method hydrates the parsed html PopUp form with data from specific override. * * @param {number} no Override index. */ Overrides.prototype.onStepOverlayReadyCb = function(no) { var override_ref = this.data[no] ? this.data[no] : this.new_override; this.edit_form = new OverrideEditForm(jQuery('#lldoverride_form'), override_ref); }; /** * Creates new override id and opens form for it. */ Overrides.prototype.openNew = function() { this.new_id -= 1; this.new_override = new Override({no: this.new_id}); this.new_override.open(this.new_id, this.$container.find('.element-table-add')); }; /** * Renders overrides in DOM. */ Overrides.prototype.renderData = function() { this.sort_index.forEach(function(data_index) { this.overrides_dynamic_rows.addRow(this.data[data_index].data, data_index); }.bind(this)); this.onSortOrderChange(); } /** * Opens popup for an override. * * @param {number} no */ Overrides.prototype.open = function(no) { this.data[no].open(no, this.$container.find('[data-index="' + no + '"] a')); }; /** * This object represents an override of web scenario. * * @param {object} data Optional override initial data. */ function Override(data) { var defaults = { name: '', stop: '0', filter: { 'evaltype': '0', 'formula': '', 'conditions': [] }, operations: [] }; this.data = jQuery.extend(true, defaults, data); this.data.no = this.data.step; this.data.overrides_evaltype = this.data.filter.evaltype; this.data.overrides_formula = this.data.filter.formula; this.data.overrides_filters = this.data.filter.conditions; delete this.data.filter; /* * Used to add proper letter, when creating new dynamic row for filter. If no filters are configured, * one empty row is created by View. */ this.filter_counter = (this.data.overrides_filters.length > 0) ? this.data.overrides_filters.length : 1; } /** * Merges old data with new data. */ Override.prototype.update = function(data) { jQuery.extend(this.data, data); }; /** * Opens override popup - edit or create form. * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed. * * @param {number} step Override index. * @param {Node} trigger_element A node to set focus to, when popup is closed. */ Override.prototype.open = function(no, trigger_element) { return PopUp('popup.lldoverride', { no: no, templated: lldoverrides.templated, name: this.data.name, old_name: this.data.name, stop: this.data.stop, overrides_evaltype: this.data.overrides_evaltype, overrides_formula: this.data.overrides_formula, overrides_filters: this.data.overrides_filters, operations: this.data.operations, overrides_names: lldoverrides.overrides.getOverrideNames() }, {dialogue_class: 'modal-popup-generic', trigger_element}); }; /** * Represents popup form. * * @param {object} $form * @param {object} override_ref Reference to override instance from Overrides object. */ function OverrideEditForm($form, override_ref) { this.$form = $form; this.override = override_ref; // Initiate Filters dynamic rows and evaltype. this.filterDynamicRows(); this.operations = new Operations(this.$form, this.override.data.operations); // This will be used for link on edit button. window.lldoverrides.operations = this.operations; } OverrideEditForm.prototype.updateExpression = function() { var filters = []; jQuery('#overrides_filters .macro').each(function(index, macroInput) { macroInput = jQuery(macroInput); macroInput.val(macroInput.val().toUpperCase()); filters.push({ id: macroInput.data('formulaid'), type: macroInput.val() }); }); jQuery('#overrides_expression').html(getConditionFormula(filters, +jQuery('#overrides-evaltype').val())); }; OverrideEditForm.prototype.filterDynamicRows = function() { var that = this; jQuery('#overrides_filters') .dynamicRows({ template: '#override-filters-row', counter: this.override.filter_counter, allow_empty: true, dataCallback: function(data) { data.formulaId = num2letter(data.rowNum); that.override.filter_counter++; return data; } }) .bind('tableupdate.dynamicRows', function(event, options) { jQuery('#overrideRow').toggle(jQuery(options.row, jQuery(this)).length > 1); if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) { that.updateExpression(); } }) .on('change', '.macro', function() { if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) { that.updateExpression(); } }) .on('afteradd.dynamicRows', (event) => { [...event.currentTarget.querySelectorAll('.js-operator')] .pop() .addEventListener('change', view.toggleConditionValue); }) .ready(function() { jQuery('#overrideRow').toggle(jQuery('.form_row', jQuery('#overrides_filters')).length > 1); overlays_stack.end().centerDialog(); }); jQuery('#overrides-evaltype').change(function() { var show_formula = (jQuery(this).val() == <?= CONDITION_EVAL_TYPE_EXPRESSION ?>); jQuery('#overrides_expression').toggle(!show_formula); jQuery('#overrides_formula').toggle(show_formula); if (!show_formula) { that.updateExpression(); } overlays_stack.end().centerDialog(); }); jQuery('#overrides-evaltype').trigger('change'); [...document.getElementById('overrides_filters').querySelectorAll('.js-operator')].map((elem) => { elem.addEventListener('change', view.toggleConditionValue); }); }; /** * This method is bound via popup button attribute. It posts serialized version of current form to be validated. * Note that we do not bother posting dynamic fields, since they are not validated at this point. * * @param {object} overlay */ OverrideEditForm.prototype.validate = function(overlay) { var url = new Curl(this.$form.attr('action')); url.setArgument('validate', 1); this.$form.trimValues(['input[type="text"]']); this.$form.parent().find('.msg-bad, .msg-good').remove(); overlay.setLoading(); overlay.xhr = jQuery.ajax({ url: url.getUrl(), data: this.$form.serializeJSON(), dataType: 'json', type: 'post' }) .always(function() { overlay.unsetLoading(); }) .done(function(ret) { if ('error' in ret) { const message_box = makeMessageBox('bad', ret.error.messages, ret.error.title); return message_box.insertBefore(this.$form); } if (!lldoverrides.overrides.data[ret.params.no]) { lldoverrides.overrides.sort_index.push(ret.params.no); lldoverrides.overrides.data[ret.params.no] = this.override; } this.operations.sort_index.forEach(function(data_index) { ret.params.operations.push(this.operations.data[data_index].data); }.bind(this)); lldoverrides.overrides.data[ret.params.no].update(ret.params); lldoverrides.overrides.renderData(); overlayDialogueDestroy(overlay.dialogueid); }.bind(this)); }; function Operations($form, operations) { var that = this; this.data = {}; this.new_id = 0; this.sort_index = []; operations.sort((a, b) => { const a_operator = this.operatorName(a.operator); const b_operator = this.operatorName(b.operator); if (a.operationobject < b.operationobject || (a.operationobject === b.operationobject && a_operator < b_operator) || (a.operationobject === b.operationobject && a_operator === b_operator && a.value < b.value)) { return -1; } if (a.operationobject > b.operationobject || (a.operationobject === b.operationobject && a_operator > b_operator) || (a.operationobject === b.operationobject && a_operator === b_operator && a.value > b.value)) { return 1; } return 0; }); operations.forEach(function(operation, no) { this.data[no + 1] = new Operation(operation, no + 1); this.sort_index.push(no + 1); }.bind(this)); this.$container = jQuery('.lld-overrides-operations-table', $form); this.$container.find('.element-table-add').on('click', this.openNew.bind(this)); this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) { e.view_data.condition_object = that.operationobjectName(e.view_data.operationobject); e.view_data.condition_operator = that.operatorName(e.view_data.operator); }); this.operations_dynamic_rows = new DynamicRows(this.$container, { add_before: this.$container.find('.element-table-add').closest('tr')[0], template: lldoverrides.operations_row_template }); if (!lldoverrides.templated) { this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) { delete this.data[e.data_index]; var index = this.sort_index.indexOf(e.data_index); if (index > -1) { this.sort_index.splice(index, 1); } }.bind(this)); dynamicRowsBindNewRow(this.$container); } else { this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { e.new_node.setAttribute('data-index', e.data_index); }); } this.renderData(); }; Operations.prototype.operationobjectName = function(operationobject) { var operationobject_name = ''; if (operationobject === '<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>') { operationobject_name = window.lldoverrides.msg.item_prototype; } else if (operationobject === '<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>') { operationobject_name = window.lldoverrides.msg.trigger_prototype; } else if (operationobject === '<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>') { operationobject_name = window.lldoverrides.msg.graph_prototype; } else if (operationobject === '<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>') { operationobject_name = window.lldoverrides.msg.host_prototype; } return operationobject_name; }; Operations.prototype.operatorName = function(operator) { var operator_name = ''; if (operator === '<?= CONDITION_OPERATOR_EQUAL ?>') { operator_name = window.lldoverrides.msg.equals; } else if (operator === '<?= CONDITION_OPERATOR_NOT_EQUAL ?>') { operator_name = window.lldoverrides.msg.does_not_equal; } else if (operator === '<?= CONDITION_OPERATOR_LIKE ?>') { operator_name = window.lldoverrides.msg.contains; } else if (operator === '<?= CONDITION_OPERATOR_NOT_LIKE ?>') { operator_name = window.lldoverrides.msg.does_not_contain; } else if (operator === '<?= CONDITION_OPERATOR_REGEXP ?>') { operator_name = window.lldoverrides.msg.matches; } else if (operator === '<?= CONDITION_OPERATOR_NOT_REGEXP ?>') { operator_name = window.lldoverrides.msg.does_not_match; } return operator_name; }; /** * This method hydrates the parsed html PopUp form with data from specific override. * * @param {number} no Override index. */ Operations.prototype.onOperationOverlayReadyCb = function(no) { var operation_ref = this.data[no] ? this.data[no] : this.new_operation; this.edit_form = new OperationEditForm(jQuery('#lldoperation_form'), operation_ref); }; /** * Creates new override id and opens form for it. */ Operations.prototype.openNew = function() { this.new_id -= 1; this.new_operation = new Operation({no: this.new_id}); this.new_operation.open(this.new_id, this.$container.find('.element-table-add')); }; /** * Renders overrides in DOM. */ Operations.prototype.renderData = function() { this.sort_index.forEach(function(data_index) { this.operations_dynamic_rows.addRow(this.data[data_index].data, data_index); }.bind(this)); } /** * Opens popup for a override. * * @param {number} no */ Operations.prototype.open = function(no) { this.data[no].open(no, this.$container.find('[data-index="' + no + '"] .element-table-open')); }; function Operation(data, no) { this.data = data; this.data.no = no; } /** * Replaces data with new one. */ Operation.prototype.update = function(data) { this.data = data; }; /** * Opens override popup - edit or create form. * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed. * * @param {number} step Override index. * @param {Node} trigger_element A node to set focus to, when popup is closed. */ Operation.prototype.open = function(no, trigger_element) { var parameters = { no: no, templated: lldoverrides.templated, operationobject: this.data.operationobject, operator: this.data.operator, value: this.data.value }; window.lldoverrides.actions.forEach(function(action) { if (action in this.data) { parameters[action] = this.data[action]; } }.bind(this)); return PopUp('popup.lldoperation', parameters, {dialogue_class: 'modal-popup-generic', trigger_element}); }; /** * Represents popup form. * * @param {object} $form * @param {object} operation_ref Reference to override instance from Overrides object. */ function OperationEditForm($form, operation_ref) { this.$form = $form; this.operation = operation_ref; var that = this, $custom_intervals = jQuery('#lld_overrides_custom_intervals', this.$form); $custom_intervals.on('click', 'input[type="radio"]', function() { var rowNum = jQuery(this).attr('id').split('_')[3]; if (jQuery(this).val() == <?= ITEM_DELAY_FLEXIBLE; ?>) { jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).hide(); jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).show(); jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).show(); } else { jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).hide(); jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).hide(); jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).show(); } }); $custom_intervals.dynamicRows({template: '#lldoverride-custom-intervals-row', allow_empty: true}); jQuery('#ophistory_history_mode', this.$form) .change(function() { if (jQuery('[name="ophistory[history_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) { jQuery('#ophistory_history', that.$form).prop('disabled', true).hide(); } else { jQuery('#ophistory_history', that.$form).prop('disabled', false).show(); } }) .trigger('change'); jQuery('#optrends_trends_mode', this.$form) .change(function() { if (jQuery('[name="optrends[trends_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) { jQuery('#optrends_trends', that.$form).prop('disabled', true).hide(); } else { jQuery('#optrends_trends', that.$form).prop('disabled', false).show(); } }) .trigger('change'); jQuery('.tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible(); jQuery('.tags-table', this.$form) .dynamicRows({template: '#lldoverride-tag-row', allow_empty: true}) .on('click', 'button.element-table-add', function() { jQuery('.tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible(); }); // Override actions available per override object. var available_actions = { '<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends', 'optag'], '<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opseverity', 'optag'], '<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>': ['opdiscover'], '<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'optemplate', 'optag', 'opinventory'] }; jQuery('#operationobject', this.$form) .change(function() { window.lldoverrides.actions.forEach(function(action) { if (available_actions[this.value].indexOf(action) !== -1) { that.showActionRow(action + '_row'); } else { that.hideActionRow(action + '_row'); } }.bind(this)); }); }; OperationEditForm.prototype.initHideActionRows = function() { jQuery('#operationobject', this.$form).trigger('change'); }; OperationEditForm.prototype.showActionRow = function(row_id) { var obj = document.getElementById(row_id); if (is_null(obj)) { throw 'Cannot find action row with id [' + row_id + ']'; } // Show it only if it was previously hidden. if (obj.originalObject) { obj.parentNode.replaceChild(obj.originalObject, obj); } }; OperationEditForm.prototype.hideActionRow = function(row_id) { var obj = document.getElementById(row_id); if (is_null(obj)) { throw 'Cannot find action row with id [' + row_id +']'; } // Hide it only if it was previously visible. if (!('originalObject' in obj)) { try { var new_obj = document.createElement('li'); new_obj.setAttribute('id', obj.id); } catch(e) { throw 'Cannot create new element'; } new_obj.originalObject = obj; obj.parentNode.replaceChild(new_obj, obj); } }; /** * This method is bound via popup button attribute. It posts serialized version of current form to be validated. * Note that we do not bother posting dynamic fields, since they are not validated at this point. * * @param {object} overlay */ OperationEditForm.prototype.validate = function(overlay) { var url = new Curl(this.$form.attr('action')); url.setArgument('validate', 1); this.$form.trimValues(['input[type="text"]', 'textarea']); this.$form.parent().find('.msg-bad, .msg-good').remove(); overlay.setLoading(); overlay.xhr = jQuery.ajax({ url: url.getUrl(), data: this.$form.serialize(), dataType: 'json', type: 'post' }) .always(function() { overlay.unsetLoading(); }) .done(function(ret) { if ('error' in ret) { const message_box = makeMessageBox('bad', ret.error.messages, ret.error.title); return message_box.insertBefore(this.$form); } if (!lldoverrides.operations.data[ret.params.no]) { lldoverrides.operations.sort_index.push(ret.params.no); lldoverrides.operations.data[ret.params.no] = this.operation; } lldoverrides.operations.data[ret.params.no].update(ret.params); lldoverrides.operations.renderData(); overlayDialogueDestroy(overlay.dialogueid); }.bind(this)); }; </script>