1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * @var CView $this 24 */ 25insert_javascript_for_visibilitybox(); 26?> 27<script type="text/x-jquery-tmpl" id="lldoverride-row-templated"> 28 <?= (new CRow([ 29 '', 30 (new CSpan('1:'))->setAttribute('data-row-num', ''), 31 (new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});')))), 32 '#{stop_verbose}', 33 (new CCol((new CButton(null, _('Remove'))) 34 ->addClass(ZBX_STYLE_BTN_LINK) 35 ->addClass('element-table-remove') 36 ->setEnabled(false) 37 ))->addClass(ZBX_STYLE_NOWRAP) 38 ]))->toString() 39 ?> 40</script> 41<script type="text/x-jquery-tmpl" id="lldoverride-row"> 42 <?= (new CRow([ 43 (new CCol((new CDiv())->addClass(ZBX_STYLE_DRAG_ICON))) 44 ->addClass(ZBX_STYLE_TD_DRAG_ICON) 45 ->setWidth('15'), 46 (new CCol((new CSpan('1:'))->setAttribute('data-row-num', ''))) 47 ->setWidth('15'), 48 (new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});')))) 49 ->setWidth('350'), 50 (new CCol('#{stop_verbose}')) 51 ->setWidth('100'), 52 (new CCol((new CButton(null, _('Remove'))) 53 ->addClass(ZBX_STYLE_BTN_LINK) 54 ->addClass('element-table-remove') 55 )) 56 ->addClass(ZBX_STYLE_NOWRAP) 57 ->setWidth('50') 58 ])) 59 ->addClass('sortable') 60 ->toString() 61 ?> 62</script> 63<script type="text/x-jquery-tmpl" id="override-filters-row"> 64 <?= 65 (new CRow([[ 66 new CSpan('#{formulaId}'), 67 new CVar('overrides_filters[#{rowNum}][formulaid]', '#{formulaId}') 68 ], 69 (new CTextBox('overrides_filters[#{rowNum}][macro]', '', false, 70 DB::getFieldLength('lld_override_condition', 'macro'))) 71 ->setWidth(ZBX_TEXTAREA_MACRO_WIDTH) 72 ->addClass(ZBX_STYLE_UPPERCASE) 73 ->addClass('macro') 74 ->setAttribute('placeholder', '{#MACRO}') 75 ->setAttribute('data-formulaid', '#{formulaId}'), 76 (new CSelect('overrides_filters[#{rowNum}][operator]')) 77 ->setValue(CONDITION_OPERATOR_REGEXP) 78 ->addOption(new CSelectOption(CONDITION_OPERATOR_REGEXP, _('matches'))) 79 ->addOption(new CSelectOption(CONDITION_OPERATOR_NOT_REGEXP, _('does not match'))), 80 (new CTextBox('overrides_filters[#{rowNum}][value]', '', false, 81 DB::getFieldLength('lld_override_condition', 'value'))) 82 ->setWidth(ZBX_TEXTAREA_MACRO_VALUE_WIDTH) 83 ->setAttribute('placeholder', _('regular expression')), 84 (new CCol( 85 (new CButton('overrides_filters#{rowNum}_remove', _('Remove'))) 86 ->addClass(ZBX_STYLE_BTN_LINK) 87 ->addClass('element-table-remove') 88 ))->addClass(ZBX_STYLE_NOWRAP) 89 ])) 90 ->addClass('form_row') 91 ->toString() 92 ?> 93</script> 94<script type="text/x-jquery-tmpl" id="lldoverride-operation-row-templated"> 95 <?= (new CRow([ 96 ['#{condition_object} #{condition_operator} ', italic('#{value}')], 97 (new CCol( 98 (new CButton(null, _('View'))) 99 ->addClass(ZBX_STYLE_BTN_LINK) 100 ->addClass('element-table-open') 101 ->onClick('lldoverrides.operations.open(#{no});') 102 ))->addClass(ZBX_STYLE_NOWRAP) 103 ]))->toString() 104 ?> 105</script> 106<script type="text/x-jquery-tmpl" id="lldoverride-operation-row"> 107 <?= (new CRow([ 108 ['#{condition_object} #{condition_operator} ', italic('#{value}')], 109 (new CHorList([ 110 (new CButton(null, _('Edit'))) 111 ->addClass(ZBX_STYLE_BTN_LINK) 112 ->addClass('element-table-open') 113 ->onClick('lldoverrides.operations.open(#{no});'), 114 (new CButton(null, _('Remove'))) 115 ->addClass(ZBX_STYLE_BTN_LINK) 116 ->addClass('element-table-remove') 117 ]))->addClass(ZBX_STYLE_NOWRAP) 118 ]))->toString() 119 ?> 120</script> 121<script type="text/x-jquery-tmpl" id="lldoverride-custom-intervals-row"> 122 <?= (new CRow([ 123 (new CRadioButtonList('opperiod[delay_flex][#{rowNum}][type]', 0)) 124 ->addValue(_('Flexible'), ITEM_DELAY_FLEXIBLE) 125 ->addValue(_('Scheduling'), ITEM_DELAY_SCHEDULING) 126 ->setModern(true), 127 [ 128 (new CTextBox('opperiod[delay_flex][#{rowNum}][delay]')) 129 ->setAttribute('placeholder', ZBX_ITEM_FLEXIBLE_DELAY_DEFAULT), 130 (new CTextBox('opperiod[delay_flex][#{rowNum}][schedule]')) 131 ->setAttribute('placeholder', ZBX_ITEM_SCHEDULING_DEFAULT) 132 ->setAttribute('style', 'display: none;') 133 ], 134 (new CTextBox('opperiod[delay_flex][#{rowNum}][period]')) 135 ->setAttribute('placeholder', ZBX_DEFAULT_INTERVAL), 136 (new CButton('opperiod[delay_flex][#{rowNum}][remove]', _('Remove'))) 137 ->addClass(ZBX_STYLE_BTN_LINK) 138 ->addClass('element-table-remove') 139 ])) 140 ->addClass('form_row') 141 ->toString() 142 ?> 143</script> 144<script type="text/x-jquery-tmpl" id="lldoverride-tag-row"> 145 <?= renderTagTableRow('#{rowNum}', '', '', ['field_name' => 'optag', 'add_post_js' => false]) ?> 146</script> 147<script type="text/javascript"> 148 jQuery(function($) { 149 window.lldoverrides = { 150 templated: <?= $data['limited'] ? 1 : 0 ?>, 151 ZBX_STYLE_DRAG_ICON: <?= zbx_jsvalue(ZBX_STYLE_DRAG_ICON) ?>, 152 ZBX_STYLE_TD_DRAG_ICON: <?= zbx_jsvalue(ZBX_STYLE_TD_DRAG_ICON) ?>, 153 ZBX_STYLE_DISABLED: <?= zbx_jsvalue(ZBX_STYLE_DISABLED) ?>, 154 msg: { 155 yes: <?= json_encode(_('Yes')) ?>, 156 no: <?= json_encode(_('No')) ?>, 157 item_prototype: <?= json_encode(_('Item prototype')) ?>, 158 trigger_prototype: <?= json_encode(_('Trigger prototype')) ?>, 159 graph_prototype: <?= json_encode(_('Graph prototype')) ?>, 160 host_prototype: <?= json_encode(_('Host prototype')) ?>, 161 equals: <?= json_encode(_('equals')) ?>, 162 does_not_equal: <?= json_encode(_('does not equal')) ?>, 163 contains: <?= json_encode(_('contains')) ?>, 164 does_not_contain: <?= json_encode(_('does not contain')) ?>, 165 matches: <?= json_encode(_('matches')) ?>, 166 does_not_match: <?= json_encode(_('does not match')) ?> 167 } 168 }; 169 170 window.lldoverrides.override_row_template = new Template(jQuery(lldoverrides.templated 171 ? '#lldoverride-row-templated' 172 : '#lldoverride-row' 173 ).html()); 174 175 window.lldoverrides.operations_row_template = new Template(jQuery(lldoverrides.templated 176 ? '#lldoverride-operation-row-templated' 177 : '#lldoverride-operation-row' 178 ).html()); 179 180 window.lldoverrides.overrides = new Overrides($('#overridesTab'), 181 <?= json_encode(array_values($data['overrides'])) ?> 182 ); 183 window.lldoverrides.actions = ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends', 'opseverity', 184 'optag', 'optemplate', 'opinventory' 185 ]; 186 187 window.lldoverrides.$form = $('form[name="itemForm"]').on('submit', function(e) { 188 var hidden_form = this.querySelector('#hidden-form'); 189 190 hidden_form && hidden_form.remove(); 191 hidden_form = document.createElement('div'); 192 hidden_form.id = 'hidden-form'; 193 194 hidden_form.appendChild(lldoverrides.overrides.toFragment()); 195 196 this.appendChild(hidden_form); 197 }); 198 }); 199 200 /** 201 * Returns common $.sortable options. 202 * 203 * @return {object} 204 */ 205 function sortableOpts() { 206 return { 207 items: 'tbody tr.sortable', 208 axis: 'y', 209 containment: 'parent', 210 cursor: 'grabbing', 211 handle: 'div.' + lldoverrides.ZBX_STYLE_DRAG_ICON, 212 tolerance: 'pointer', 213 opacity: 0.6, 214 start: function(e, ui) { 215 ui.placeholder.height(ui.item.height()); 216 } 217 }; 218 } 219 220 /** 221 * Writes data index as new nodes attribute. Bind remove event. 222 */ 223 function dynamicRowsBindNewRow($el) { 224 $el.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { 225 e.new_node.setAttribute('data-index', e.data_index); 226 e.new_node.querySelector('.element-table-remove') 227 .addEventListener('click', dynamic_rows.removeRow.bind(dynamic_rows, e.data_index)); 228 229 // Because IE does not have NodeList.prototype.forEach method. 230 Array.prototype.forEach.call(e.new_node.querySelectorAll('input'), function(input_node) { 231 input_node.addEventListener('keyup', function(e) { 232 $el.trigger('dynamic_rows.updated', dynamic_rows); 233 }); 234 }); 235 }); 236 } 237 238 /** 239 * Implements disabling sortable if less than two data rows. 240 */ 241 function dynamicRowsBindSortableDisable($el) { 242 $el.on('dynamic_rows.updated', function(e, dynamic_rows) { 243 if (dynamic_rows.length < 2) { 244 dynamic_rows.$element.sortable('option', 'disabled', true); 245 dynamic_rows.$element.find('.' + lldoverrides.ZBX_STYLE_DRAG_ICON) 246 .addClass(lldoverrides.ZBX_STYLE_DISABLED); 247 } 248 else { 249 dynamic_rows.$element.sortable('option', 'disabled', false); 250 dynamic_rows.$element.find('.' + lldoverrides.ZBX_STYLE_DRAG_ICON) 251 .removeClass(lldoverrides.ZBX_STYLE_DISABLED); 252 } 253 }); 254 } 255 256 /** 257 * A helper method for creating hidden input nodes. 258 * 259 * @param {string} name 260 * @param {string} value 261 * @param {string} prefix 262 * 263 * @return {object} Return input element node. 264 */ 265 function hiddenInput(name, value, prefix) { 266 var input = window.document.createElement('input'); 267 268 input.type = 'hidden'; 269 input.value = value; 270 input.name = prefix ? prefix + '[' + name + ']' : name; 271 272 return input; 273 } 274 275 /** 276 * @param {object} $element 277 * @param {object} options 278 * @param {array} data 279 */ 280 function DynamicRows($element, options, data) { 281 if (!(options.add_before instanceof Node)) { 282 throw 'Error: options.add_before must be instanceof Node.'; 283 } 284 285 if (!(options.template instanceof Template)) { 286 throw 'Error: options.template must be instanceof Template.'; 287 } 288 289 this.data = {}; 290 this.$element = $element; 291 this.options = jQuery.extend({}, { 292 // Please note, this option does not work if data_index option is in use. 293 ensure_min_rows: 0, 294 // If this option is used, it represents key of data object whose value will be used as data_index. 295 data_index: null 296 }, options); 297 298 this.data_index = 0; 299 this.length = 0; 300 data && this.setData(data); 301 } 302 303 /** 304 * All events are dispatched on $element. Second argument is instance, first argument is event object. 305 * 306 * @param {string} evt_key 307 * @param {object} evt_data Optional object to be merged into event data. 308 * 309 * @return {object} Returns a jQuery.Event object. 310 */ 311 DynamicRows.prototype.dispatch = function(evt_key, evt_data) { 312 var evt = jQuery.Event('dynamic_rows.' + evt_key, evt_data); 313 314 this.$element.trigger(evt, [this]); 315 316 return evt; 317 } 318 319 /** 320 * Adds a row before the given row. 321 * 322 * @param {object} data Data to be passed into template. 323 * @param {number} data_index Optional index, if data with given index exists, then in place update will happen. 324 * In case of update all events dispatched as if add new was performed. 325 */ 326 DynamicRows.prototype.addRow = function(row_data, data_index) { 327 if (this.options.disabled) { 328 return; 329 } 330 331 if (!data_index) { 332 data_index = this.options.data_index 333 ? row_data[this.options.data_index] 334 : ++this.data_index; 335 } 336 337 var new_row = { 338 node: this.createRowNode(row_data), 339 data: row_data || {} 340 }; 341 342 var evt_before_add = this.dispatch('beforeadd', { 343 new_data: new_row.data, 344 new_node: new_row.node, 345 add_before: this.options.add_before, 346 data_index: data_index 347 }); 348 349 if (evt_before_add.isDefaultPrevented()) { 350 return; 351 } 352 353 if (this.data[data_index]) { 354 evt_before_add.add_before.parentNode.replaceChild(evt_before_add.new_node, this.data[data_index].node); 355 } 356 else { 357 evt_before_add.add_before.parentNode.insertBefore(evt_before_add.new_node, evt_before_add.add_before); 358 this.length++; 359 } 360 361 this.data[data_index] = new_row; 362 363 this.dispatch('updated'); 364 }; 365 366 /** 367 * Replaces current data with new one. 368 * Be aware that min rows are ensured and events are triggered only for add. 369 * Removing happens outside this API, to not to call. 370 * 371 * @param {array} data Array of data for row templates. 372 * 373 * @return {object} Returns the DynamicRows object. 374 */ 375 DynamicRows.prototype.setData = function(data) { 376 if (!(data instanceof Array)) { 377 throw 'Expected Array.'; 378 } 379 380 for (var i in this.data) { 381 this.unlinkIndex(i); 382 } 383 384 data.forEach(function(obj) { 385 this.addRow(obj); 386 }.bind(this)); 387 388 this.ensureMinRows(); 389 390 return this; 391 }; 392 393 /** 394 * Adds empty rows if needed. 395 */ 396 DynamicRows.prototype.ensureMinRows = function() { 397 var rows_to_add = this.options.ensure_min_rows - this.length; 398 while (rows_to_add > 0) { 399 rows_to_add--; 400 this.addRow(); 401 } 402 }; 403 404 /** 405 * Renders Node from template. 406 * 407 * @param {object} data Data to be passed into template. 408 * 409 * @return {object} 410 */ 411 DynamicRows.prototype.createRowNode = function(data) { 412 var evt = this.dispatch('beforerender', {view_data: data}); 413 var html_str = this.options.template.evaluate(evt.view_data); 414 415 return jQuery(html_str).get(0); 416 }; 417 418 419 /** 420 * Removes data at given index. Method is to be used by plugin internally, does not dispatch events. 421 * 422 * @param {number} data_index 423 * 424 * @return {object} Object that just got removed. 425 */ 426 DynamicRows.prototype.unlinkIndex = function(data_index) { 427 this.data[data_index].node.remove(); 428 var ref = this.data[data_index]; 429 430 delete this.data[data_index]; 431 this.length--; 432 433 return ref; 434 } 435 436 /** 437 * Removes the given row. 438 * 439 * @param {number} data_index 440 */ 441 DynamicRows.prototype.removeRow = function(data_index) { 442 if (this.options.disabled) { 443 return; 444 } 445 446 var removed_row = this.unlinkIndex(data_index); 447 448 this.dispatch('afterremove', {removed_row: removed_row, data_index: data_index}); 449 this.dispatch('updated'); 450 451 this.ensureMinRows(); 452 }; 453 454 /** 455 * Represents overrides tab in discovery rules layout. 456 * 457 * @param {object} $tab 458 * @param {array} overrides Initial overrides objects data array. 459 */ 460 function Overrides($tab, overrides) { 461 this.data = {}; 462 this.new_id = 0; 463 this.sort_index = []; 464 465 overrides.forEach(function(override, no) { 466 this.data[no + 1] = new Override(override); 467 this.sort_index.push(no + 1); 468 }.bind(this)); 469 470 this.$container = jQuery('.lld-overrides-table', $tab); 471 this.$container.find('.element-table-add').on('click', this.openNew.bind(this)); 472 473 this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) { 474 e.view_data.stop_verbose = (e.view_data.stop === '1') ? lldoverrides.msg.yes : lldoverrides.msg.no; 475 }); 476 477 this.overrides_dynamic_rows = new DynamicRows(this.$container, { 478 add_before: this.$container.find('.element-table-add').closest('tr')[0], 479 template: lldoverrides.override_row_template 480 }); 481 482 if (!lldoverrides.templated) { 483 this.$container.sortable(sortableOpts()); 484 this.$container.sortable('option', 'update', this.onSortOrderChange.bind(this)); 485 this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) { 486 delete this.data[e.data_index]; 487 this.onSortOrderChange(); 488 }.bind(this)); 489 490 dynamicRowsBindSortableDisable(this.$container); 491 dynamicRowsBindNewRow(this.$container); 492 } 493 else { 494 this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { 495 e.new_node.setAttribute('data-index', e.data_index); 496 }); 497 } 498 499 this.renderData(); 500 } 501 502 /** 503 * The parts of form that are easier to maintain in functional objects are transformed into hidden input fields. 504 * 505 * @return {object} Returns DocumentFragment object. 506 */ 507 Overrides.prototype.toFragment = function() { 508 var frag = document.createDocumentFragment(), 509 iter_step = 0; 510 511 this.sort_index.forEach(function(id) { 512 var override = this.data[id], 513 prefix_override = 'overrides[' + (iter_step++) + ']', 514 prefix_filter = prefix_override + '[filter]', 515 iter_filters = 0, 516 iter_operations = 0; 517 518 frag.appendChild(hiddenInput('step', iter_step, prefix_override)); 519 frag.appendChild(hiddenInput('name', override.data.name, prefix_override)); 520 frag.appendChild(hiddenInput('stop', override.data.stop, prefix_override)); 521 522 if (override.data.overrides_filters.length > 0) { 523 frag.appendChild(hiddenInput('evaltype', override.data.overrides_evaltype, prefix_filter)); 524 525 if (override.data.overrides_evaltype == <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) { 526 frag.appendChild(hiddenInput('formula', override.data.overrides_formula, prefix_filter)); 527 } 528 529 override.data.overrides_filters.forEach(function(override_filter) { 530 var prefix = prefix_filter + '[conditions][' + (iter_filters++) + ']'; 531 frag.appendChild(hiddenInput('formulaid', override_filter.formulaid, prefix)); 532 frag.appendChild(hiddenInput('macro', override_filter.macro, prefix)); 533 frag.appendChild(hiddenInput('value', override_filter.value, prefix)); 534 frag.appendChild(hiddenInput('operator', override_filter.operator, prefix)); 535 }); 536 } 537 538 override.data.operations.forEach(function(operation) { 539 var prefix = prefix_override + '[operations][' + (iter_operations++) + ']'; 540 frag.appendChild(hiddenInput('operationobject', operation.operationobject, prefix)); 541 frag.appendChild(hiddenInput('operator', operation.operator, prefix)); 542 frag.appendChild(hiddenInput('value', operation.value, prefix)); 543 544 if ('opstatus' in operation) { 545 frag.appendChild(hiddenInput('status', operation.opstatus.status, prefix + '[opstatus]')); 546 } 547 if ('opdiscover' in operation) { 548 frag.appendChild(hiddenInput('discover', operation.opdiscover.discover, prefix + '[opdiscover]')); 549 } 550 if ('opperiod' in operation) { 551 frag.appendChild(hiddenInput('delay', operation.opperiod.delay, prefix + '[opperiod]')); 552 } 553 if ('ophistory' in operation) { 554 frag.appendChild(hiddenInput('history', operation.ophistory.history, prefix + '[ophistory]')); 555 } 556 if ('optrends' in operation) { 557 frag.appendChild(hiddenInput('trends', operation.optrends.trends, prefix + '[optrends]')); 558 } 559 if ('opseverity' in operation) { 560 frag.appendChild(hiddenInput('severity', operation.opseverity.severity, prefix + '[opseverity]')); 561 } 562 if ('optag' in operation) { 563 var iter_tags = 0; 564 565 operation.optag.forEach(function(tag) { 566 var prefix_tag = prefix + '[optag][' + (iter_tags++) + ']'; 567 frag.appendChild(hiddenInput('tag', tag.tag, prefix_tag)); 568 569 if (('value' in tag) && 'value' !== '') { 570 frag.appendChild(hiddenInput('value', tag.value, prefix_tag)); 571 } 572 }); 573 } 574 if ('optemplate' in operation) { 575 var iter_templates = 0; 576 577 operation.optemplate.forEach(function(template) { 578 var prefix_template = prefix + '[optemplate][' + (iter_templates++) + ']'; 579 frag.appendChild(hiddenInput('templateid', template.templateid, prefix_template)); 580 }); 581 } 582 if ('opinventory' in operation) { 583 frag.appendChild(hiddenInput('inventory_mode', operation.opinventory.inventory_mode, 584 prefix + '[opinventory]' 585 )); 586 } 587 }); 588 }.bind(this)); 589 590 return frag; 591 }; 592 593 /** 594 * This method maintains property for iterating overrides in the order that rows have in DOM at the moment, 595 * also updates visual counter in DOM for override rows. 596 */ 597 Overrides.prototype.onSortOrderChange = function() { 598 var order = []; 599 this.$container.find('[data-index]').each(function(index) { 600 this.querySelector('[data-row-num]').innerText = (index + 1) + ':'; 601 order.push(this.attributes.getNamedItem('data-index').value); 602 }); 603 this.sort_index = order; 604 }; 605 606 /** 607 * Used to validate override names with server, on PopUp form validate event. 608 * 609 * @return {array} Array of strings. 610 */ 611 Overrides.prototype.getOverrideNames = function() { 612 var names = []; 613 614 for (var no in this.data) { 615 names.push(this.data[no].data.name); 616 } 617 618 return names; 619 }; 620 621 /** 622 * This method hydrates the parsed html PopUp form with data from specific override. 623 * 624 * @param {number} no Override index. 625 */ 626 Overrides.prototype.onStepOverlayReadyCb = function(no) { 627 var override_ref = this.data[no] ? this.data[no] : this.new_override; 628 this.edit_form = new OverrideEditForm(jQuery('#lldoverride_form'), override_ref); 629 }; 630 631 /** 632 * Creates new override id and opens form for it. 633 */ 634 Overrides.prototype.openNew = function() { 635 this.new_id -= 1; 636 637 this.new_override = new Override({no: this.new_id}); 638 this.new_override.open(this.new_id, this.$container.find('.element-table-add')); 639 }; 640 641 /** 642 * Renders overrides in DOM. 643 */ 644 Overrides.prototype.renderData = function() { 645 this.sort_index.forEach(function(data_index) { 646 this.overrides_dynamic_rows.addRow(this.data[data_index].data, data_index); 647 }.bind(this)); 648 649 this.onSortOrderChange(); 650 } 651 652 /** 653 * Opens popup for a override. 654 * 655 * @param {number} no 656 */ 657 Overrides.prototype.open = function(no) { 658 this.data[no].open(no, this.$container.find('[data-index="' + no + '"] a')); 659 }; 660 661 /** 662 * This object represents a override of web scenario. 663 * 664 * @param {object} data Optional override initial data. 665 */ 666 function Override(data) { 667 var defaults = { 668 name: '', 669 stop: '0', 670 filter: { 671 'evaltype': '0', 672 'formula': '', 673 'conditions': [] 674 }, 675 operations: [] 676 }; 677 this.data = jQuery.extend(true, defaults, data); 678 679 this.data.no = this.data.step; 680 this.data.overrides_evaltype = this.data.filter.evaltype; 681 this.data.overrides_formula = this.data.filter.formula; 682 this.data.overrides_filters = this.data.filter.conditions; 683 delete this.data.filter; 684 685 /* 686 * Used to add proper letter, when creating new dynamic row for filter. If no filters are configured, 687 * one empty row is created by View. 688 */ 689 this.filter_counter = (this.data.overrides_filters.length > 0) ? this.data.overrides_filters.length : 1; 690 } 691 692 /** 693 * Merges old data with new data. 694 */ 695 Override.prototype.update = function(data) { 696 jQuery.extend(this.data, data); 697 }; 698 699 /** 700 * Opens override popup - edit or create form. 701 * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed. 702 * 703 * @param {number} step Override index. 704 * @param {object} refocus A node to set focus to, when popup is closed. 705 */ 706 Override.prototype.open = function(no, refocus) { 707 return PopUp('popup.lldoverride', { 708 no: no, 709 templated: lldoverrides.templated, 710 name: this.data.name, 711 old_name: this.data.name, 712 stop: this.data.stop, 713 overrides_evaltype: this.data.overrides_evaltype, 714 overrides_formula: this.data.overrides_formula, 715 overrides_filters: this.data.overrides_filters, 716 operations: this.data.operations, 717 overrides_names: lldoverrides.overrides.getOverrideNames() 718 }, null, refocus); 719 }; 720 721 /** 722 * Represents popup form. 723 * 724 * @param {object} $form 725 * @param {object} override_ref Reference to override instance from Overrides object. 726 */ 727 function OverrideEditForm($form, override_ref) { 728 this.$form = $form; 729 this.override = override_ref; 730 731 // Initiate Filters dynamic rows and evaltype. 732 this.filterDynamicRows(); 733 this.operations = new Operations(this.$form, this.override.data.operations); 734 // This will be used for link on edit button. 735 window.lldoverrides.operations = this.operations; 736 } 737 738 OverrideEditForm.prototype.updateExpression = function() { 739 var filters = []; 740 741 jQuery('#overrides_filters .macro').each(function(index, macroInput) { 742 macroInput = jQuery(macroInput); 743 macroInput.val(macroInput.val().toUpperCase()); 744 745 filters.push({ 746 id: macroInput.data('formulaid'), 747 type: macroInput.val() 748 }); 749 }); 750 751 jQuery('#overrides_expression').html(getConditionFormula(filters, +jQuery('#overrides-evaltype').val())); 752 }; 753 754 OverrideEditForm.prototype.filterDynamicRows = function() { 755 var that = this; 756 757 jQuery('#overrides_filters') 758 .dynamicRows({ 759 template: '#override-filters-row', 760 counter: this.override.filter_counter, 761 dataCallback: function(data) { 762 data.formulaId = num2letter(data.rowNum); 763 that.override.filter_counter++; 764 765 return data; 766 } 767 }) 768 .bind('tableupdate.dynamicRows', function(event, options) { 769 jQuery('#overrideRow').toggle(jQuery(options.row, jQuery(this)).length > 1); 770 771 if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) { 772 that.updateExpression(); 773 } 774 }) 775 .on('change', '.macro', function() { 776 if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) { 777 that.updateExpression(); 778 } 779 }) 780 .ready(function() { 781 jQuery('#overrideRow').toggle(jQuery('.form_row', jQuery('#overrides_filters')).length > 1); 782 overlays_stack.end().centerDialog(); 783 }); 784 785 jQuery('#overrides-evaltype').change(function() { 786 var show_formula = (jQuery(this).val() == <?= CONDITION_EVAL_TYPE_EXPRESSION ?>); 787 788 jQuery('#overrides_expression').toggle(!show_formula); 789 jQuery('#overrides_formula').toggle(show_formula); 790 if (!show_formula) { 791 that.updateExpression(); 792 } 793 794 overlays_stack.end().centerDialog(); 795 }); 796 797 jQuery('#overrides-evaltype').trigger('change'); 798 }; 799 800 /** 801 * This method is bound via popup button attribute. It posts serialized version of current form to be validated. 802 * Note that we do not bother posting dynamic fields, since they are not validated at this point. 803 * 804 * @param {object} overlay 805 */ 806 OverrideEditForm.prototype.validate = function(overlay) { 807 var url = new Curl(this.$form.attr('action')); 808 url.setArgument('validate', 1); 809 810 this.$form.trimValues(['input[type="text"]']); 811 this.$form.parent().find('.msg-bad, .msg-good').remove(); 812 813 var form_data = this.$form.serializeJSON(); 814 if (Object.keys(form_data.overrides_filters).length <= 1) { 815 delete form_data.overrides_formula; 816 delete form_data.overrides_evaltype; 817 } 818 819 overlay.setLoading(); 820 overlay.xhr = jQuery.ajax({ 821 url: url.getUrl(), 822 data: form_data, 823 dataType: 'json', 824 type: 'post' 825 }) 826 .always(function() { 827 overlay.unsetLoading(); 828 }) 829 .done(function(ret) { 830 if (typeof ret.errors !== 'undefined') { 831 return jQuery(ret.errors).insertBefore(this.$form); 832 } 833 834 if (!lldoverrides.overrides.data[ret.params.no]) { 835 lldoverrides.overrides.sort_index.push(ret.params.no); 836 lldoverrides.overrides.data[ret.params.no] = this.override; 837 } 838 839 this.operations.sort_index.forEach(function(data_index) { 840 ret.params.operations.push(this.operations.data[data_index].data); 841 }.bind(this)); 842 843 lldoverrides.overrides.data[ret.params.no].update(ret.params); 844 lldoverrides.overrides.renderData(); 845 846 overlayDialogueDestroy(overlay.dialogueid); 847 }.bind(this)); 848 }; 849 850 function Operations($form, operations) { 851 var that = this; 852 this.data = {}; 853 this.new_id = 0; 854 this.sort_index = []; 855 856 operations.forEach(function(operation, no) { 857 this.data[no + 1] = new Operation(operation, no + 1); 858 this.sort_index.push(no + 1); 859 }.bind(this)); 860 861 this.$container = jQuery('.lld-overrides-operations-table', $form); 862 this.$container.find('.element-table-add').on('click', this.openNew.bind(this)); 863 864 this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) { 865 e.view_data.condition_object = that.operationobjectName(e.view_data.operationobject); 866 e.view_data.condition_operator = that.operatorName(e.view_data.operator); 867 }); 868 869 this.operations_dynamic_rows = new DynamicRows(this.$container, { 870 add_before: this.$container.find('.element-table-add').closest('tr')[0], 871 template: lldoverrides.operations_row_template 872 }); 873 874 if (!lldoverrides.templated) { 875 this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) { 876 delete this.data[e.data_index]; 877 878 var index = this.sort_index.indexOf(e.data_index); 879 if (index > -1) { 880 this.sort_index.splice(index, 1); 881 } 882 }.bind(this)); 883 884 dynamicRowsBindNewRow(this.$container); 885 } 886 else { 887 this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) { 888 e.new_node.setAttribute('data-index', e.data_index); 889 }); 890 } 891 892 this.renderData(); 893 }; 894 895 Operations.prototype.operationobjectName = function(operationobject) { 896 var operationobject_name = ''; 897 if (operationobject === '<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>') { 898 operationobject_name = window.lldoverrides.msg.item_prototype; 899 } 900 else if (operationobject === '<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>') { 901 operationobject_name = window.lldoverrides.msg.trigger_prototype; 902 } 903 else if (operationobject === '<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>') { 904 operationobject_name = window.lldoverrides.msg.graph_prototype; 905 } 906 else if (operationobject === '<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>') { 907 operationobject_name = window.lldoverrides.msg.host_prototype; 908 } 909 910 return operationobject_name; 911 }; 912 913 Operations.prototype.operatorName = function(operator) { 914 var operator_name = ''; 915 if (operator === '<?= CONDITION_OPERATOR_EQUAL ?>') { 916 operator_name = window.lldoverrides.msg.equals; 917 } 918 else if (operator === '<?= CONDITION_OPERATOR_NOT_EQUAL ?>') { 919 operator_name = window.lldoverrides.msg.does_not_equal; 920 } 921 else if (operator === '<?= CONDITION_OPERATOR_LIKE ?>') { 922 operator_name = window.lldoverrides.msg.contains; 923 } 924 else if (operator === '<?= CONDITION_OPERATOR_NOT_LIKE ?>') { 925 operator_name = window.lldoverrides.msg.does_not_contain; 926 } 927 else if (operator === '<?= CONDITION_OPERATOR_REGEXP ?>') { 928 operator_name = window.lldoverrides.msg.matches; 929 } 930 else if (operator === '<?= CONDITION_OPERATOR_NOT_REGEXP ?>') { 931 operator_name = window.lldoverrides.msg.does_not_match; 932 } 933 934 return operator_name; 935 }; 936 937 /** 938 * This method hydrates the parsed html PopUp form with data from specific override. 939 * 940 * @param {number} no Override index. 941 */ 942 Operations.prototype.onOperationOverlayReadyCb = function(no) { 943 var operation_ref = this.data[no] ? this.data[no] : this.new_operation; 944 this.edit_form = new OperationEditForm(jQuery('#lldoperation_form'), operation_ref); 945 }; 946 947 /** 948 * Creates new override id and opens form for it. 949 */ 950 Operations.prototype.openNew = function() { 951 this.new_id -= 1; 952 953 this.new_operation = new Operation({no: this.new_id}); 954 this.new_operation.open(this.new_id, this.$container.find('.element-table-add')); 955 }; 956 957 /** 958 * Renders overrides in DOM. 959 */ 960 Operations.prototype.renderData = function() { 961 this.sort_index.forEach(function(data_index) { 962 this.operations_dynamic_rows.addRow(this.data[data_index].data, data_index); 963 }.bind(this)); 964 } 965 966 /** 967 * Opens popup for a override. 968 * 969 * @param {number} no 970 */ 971 Operations.prototype.open = function(no) { 972 this.data[no].open(no, this.$container.find('[data-index="' + no + '"] .element-table-open')); 973 }; 974 975 function Operation(data, no) { 976 this.data = data; 977 this.data.no = no; 978 } 979 980 /** 981 * Replaces data with new one. 982 */ 983 Operation.prototype.update = function(data) { 984 this.data = data; 985 }; 986 987 /** 988 * Opens override popup - edit or create form. 989 * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed. 990 * 991 * @param {number} step Override index. 992 * @param {object} refocus A node to set focus to, when popup is closed. 993 */ 994 Operation.prototype.open = function(no, refocus) { 995 var params = { 996 no: no, 997 templated: lldoverrides.templated, 998 operationobject: this.data.operationobject, 999 operator: this.data.operator, 1000 value: this.data.value 1001 }; 1002 1003 window.lldoverrides.actions.forEach(function(action) { 1004 if (action in this.data) { 1005 params[action] = this.data[action]; 1006 } 1007 }.bind(this)); 1008 1009 return PopUp('popup.lldoperation', params, null, refocus); 1010 }; 1011 1012 /** 1013 * Represents popup form. 1014 * 1015 * @param {object} $form 1016 * @param {object} operation_ref Reference to override instance from Overrides object. 1017 */ 1018 function OperationEditForm($form, operation_ref) { 1019 this.$form = $form; 1020 this.operation = operation_ref; 1021 1022 var that = this, 1023 $custom_intervals = jQuery('#lld_overrides_custom_intervals', this.$form); 1024 1025 $custom_intervals.on('click', 'input[type="radio"]', function() { 1026 var rowNum = jQuery(this).attr('id').split('_')[3]; 1027 1028 if (jQuery(this).val() == <?= ITEM_DELAY_FLEXIBLE; ?>) { 1029 jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).hide(); 1030 jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).show(); 1031 jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).show(); 1032 } 1033 else { 1034 jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).hide(); 1035 jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).hide(); 1036 jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).show(); 1037 } 1038 }); 1039 1040 $custom_intervals.dynamicRows({ 1041 template: '#lldoverride-custom-intervals-row' 1042 }); 1043 1044 jQuery('#ophistory_history_mode', this.$form) 1045 .change(function() { 1046 if (jQuery('[name="ophistory[history_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) { 1047 jQuery('#ophistory_history', that.$form).prop('disabled', true).hide(); 1048 } 1049 else { 1050 jQuery('#ophistory_history', that.$form).prop('disabled', false).show(); 1051 } 1052 }) 1053 .trigger('change'); 1054 1055 jQuery('#optrends_trends_mode', this.$form) 1056 .change(function() { 1057 if (jQuery('[name="optrends[trends_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) { 1058 jQuery('#optrends_trends', that.$form).prop('disabled', true).hide(); 1059 } 1060 else { 1061 jQuery('#optrends_trends', that.$form).prop('disabled', false).show(); 1062 } 1063 }) 1064 .trigger('change'); 1065 1066 jQuery('#tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible(); 1067 jQuery('#tags-table', this.$form) 1068 .dynamicRows({template: '#lldoverride-tag-row'}) 1069 .on('click', 'button.element-table-add', function() { 1070 jQuery('#tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible(); 1071 }); 1072 1073 // Override actions available per override object. 1074 var available_actions = { 1075 '<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends'], 1076 '<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opseverity', 'optag'], 1077 '<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>': ['opdiscover'], 1078 '<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'optemplate', 'opinventory'] 1079 }; 1080 1081 jQuery('#operationobject', this.$form) 1082 .change(function() { 1083 window.lldoverrides.actions.forEach(function(action) { 1084 if (available_actions[this.value].indexOf(action) !== -1) { 1085 that.showActionRow(action + '_row'); 1086 } 1087 else { 1088 that.hideActionRow(action + '_row'); 1089 } 1090 }.bind(this)); 1091 }); 1092 }; 1093 1094 OperationEditForm.prototype.initHideActionRows = function() { 1095 jQuery('#operationobject', this.$form).trigger('change'); 1096 }; 1097 1098 OperationEditForm.prototype.showActionRow = function(row_id) { 1099 var obj = document.getElementById(row_id); 1100 if (is_null(obj)) { 1101 throw 'Cannot find action row with id [' + row_id + ']'; 1102 } 1103 1104 // Show it only if it was previously hidden. 1105 if (obj.originalObject) { 1106 obj.parentNode.replaceChild(obj.originalObject, obj); 1107 } 1108 }; 1109 1110 OperationEditForm.prototype.hideActionRow = function(row_id) { 1111 var obj = document.getElementById(row_id); 1112 if (is_null(obj)) { 1113 throw 'Cannot find action row with id [' + row_id +']'; 1114 } 1115 1116 // Hide it only if it was previously visible. 1117 if (!('originalObject' in obj)) { 1118 try { 1119 var new_obj = document.createElement('li'); 1120 new_obj.setAttribute('id', obj.id); 1121 } 1122 catch(e) { 1123 throw 'Cannot create new element'; 1124 } 1125 1126 new_obj.originalObject = obj; 1127 obj.parentNode.replaceChild(new_obj, obj); 1128 } 1129 }; 1130 1131 /** 1132 * This method is bound via popup button attribute. It posts serialized version of current form to be validated. 1133 * Note that we do not bother posting dynamic fields, since they are not validated at this point. 1134 * 1135 * @param {object} overlay 1136 */ 1137 OperationEditForm.prototype.validate = function(overlay) { 1138 var url = new Curl(this.$form.attr('action')); 1139 url.setArgument('validate', 1); 1140 1141 this.$form.trimValues(['input[type="text"]', 'textarea']); 1142 this.$form.parent().find('.msg-bad, .msg-good').remove(); 1143 1144 overlay.setLoading(); 1145 overlay.xhr = jQuery.ajax({ 1146 url: url.getUrl(), 1147 data: this.$form.serialize(), 1148 dataType: 'json', 1149 type: 'post' 1150 }) 1151 .always(function() { 1152 overlay.unsetLoading(); 1153 }) 1154 .done(function(ret) { 1155 if (typeof ret.errors !== 'undefined') { 1156 return jQuery(ret.errors).insertBefore(this.$form); 1157 } 1158 1159 if (!lldoverrides.operations.data[ret.params.no]) { 1160 lldoverrides.operations.sort_index.push(ret.params.no); 1161 lldoverrides.operations.data[ret.params.no] = this.operation; 1162 } 1163 1164 lldoverrides.operations.data[ret.params.no].update(ret.params); 1165 lldoverrides.operations.renderData(); 1166 1167 overlayDialogueDestroy(overlay.dialogueid); 1168 }.bind(this)); 1169 }; 1170</script> 1171