/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ ZABBIX.namespace('classes.Observer'); ZABBIX.classes.Observer = (function() { var Observer = function() { this.listeners = {}; }; Observer.prototype = { constructor: ZABBIX.classes.Observer, bind: function(event, callback) { var i; if (typeof callback === 'function') { event = ('' + event).toLowerCase().split(/\s+/); for (i = 0; i < event.length; i++) { if (this.listeners[event[i]] === void(0)) { this.listeners[event[i]] = []; } this.listeners[event[i]].push(callback); } } return this; }, trigger: function(event, target) { event = event.toLowerCase(); var handlers = this.listeners[event] || [], i; if (handlers.length) { event = jQuery.Event(event); for (i = 0; i < handlers.length; i++) { try { if (handlers[i](event, target) === false || event.isDefaultPrevented()) { break; } } catch(ex) { window.console && window.console.log && window.console.log(ex); } } } return this; } }; Observer.makeObserver = function(object) { var i; for (i in Observer.prototype) { if (Observer.prototype.hasOwnProperty(i) && typeof Observer.prototype[i] === 'function') { object[i] = Observer.prototype[i]; } } object.listeners = {}; }; return Observer; }()); ZABBIX.namespace('apps.map'); ZABBIX.apps.map = (function($) { // dependencies var Observer = ZABBIX.classes.Observer; const ZBX_STYLE_DEFAULT_OPTION = 'default-option'; function createMap(containerId, mapData) { var CMap = function(containerId, mapData) { var selementid, shapeid, linkid; this.reupdateImage = false; // if image should be updated again after last update is finished this.imageUpdating = false; // if ajax request for image updating is processing this.selements = {}; // element objects this.shapes = {}; // shape objects this.links = {}; // map links array this.selection = { count: { // number of selected items selements: 0, shapes: 0 }, selements: {}, // selected elements shapes: {} // selected shapes }; this.currentLinkId = '0'; // linkid of currently edited link this.allLinkTriggerIds = {}; this.sysmapid = mapData.sysmap.sysmapid; this.data = mapData.sysmap; this.background = null; this.iconList = mapData.iconList; this.defaultAutoIconId = mapData.defaultAutoIconId; this.defaultIconId = mapData.defaultIconId; this.defaultIconName = mapData.defaultIconName; this.csrf_token = mapData.csrf_token; this.container = $('#' + containerId); if (this.container.length === 0) { this.container = $(document.body); } this.images = {}; Object.keys(this.iconList).forEach(function (id) { var item = this.iconList[id]; this.images[item.imageid] = item; }, this); this.map = new SVGMap({ theme: mapData.theme, canvas: { width: this.data.width, height: this.data.height, mask: true }, container: this.container[0] }); this.container.css({ width: this.data.width + 'px', height: this.data.height + 'px', overflow: 'hidden' }); this.container.css('position', 'relative'); this.base64image = true; $('#sysmap_img').remove(); for (selementid in this.data.selements) { if (this.data.selements.hasOwnProperty(selementid)) { this.selements[selementid] = new Selement(this, this.data.selements[selementid]); } } var shapes = []; for (shapeid in this.data.shapes) { if (this.data.shapes.hasOwnProperty(shapeid)) { shapes.push(this.data.shapes[shapeid]); } } shapes = shapes.sort(function (a,b) { return a.zindex - b.zindex; }); shapes.forEach(function (shape) { this.shapes[shape.sysmap_shapeid] = new Shape(this, shape); }, this); for (linkid in this.data.links) { if (this.data.selements.hasOwnProperty(selementid)) { this.links[linkid] = new Link(this, this.data.links[linkid]); } } // create container for forms this.formContainer = $('
', { id: 'map-window', class: 'overlay-dialogue', style: 'display: none; top: 0; left: 0; padding-top: 13px;' }) .appendTo('.wrapper') .draggable({ containment: [0, 0, 3200, 3200] }); this.updateImage(); this.form = new SelementForm(this.formContainer, this); this.massForm = new MassForm(this.formContainer, this); this.linkForm = new LinkForm(this.formContainer, this); this.shapeForm = new ShapeForm(this.formContainer, this); this.massShapeForm = new MassShapeForm(this.formContainer, this); this.bindActions(); // initialize selectable this.container.selectable({ start: $.proxy(function(event) { if (!event.ctrlKey && !event.metaKey) { this.clearSelection(); } }, this), stop: $.proxy(function(event) { var selected = this.container.children('.ui-selected'), ids = [], i, ln; for (i = 0, ln = selected.length; i < ln; i++) { ids.push({ id: $(selected[i]).data('id'), type: $(selected[i]).data('type') }); // remove ui-selected class, to not confuse next selection selected.removeClass('ui-selected'); } this.selectElements(ids, event.ctrlKey || event.metaKey); }, this) }); }; CMap.LABEL_TYPE_LABEL = 0; // MAP_LABEL_TYPE_LABEL CMap.LABEL_TYPE_IP = 1; // MAP_LABEL_TYPE_IP CMap.LABEL_TYPE_NAME = 2; // MAP_LABEL_TYPE_NAME CMap.LABEL_TYPE_STATUS = 3; // MAP_LABEL_TYPE_STATUS CMap.LABEL_TYPE_NOTHING = 4; // MAP_LABEL_TYPE_NOTHING CMap.LABEL_TYPE_CUSTOM = 5; // MAP_LABEL_TYPE_CUSTOM CMap.prototype = { copypaste_buffer: [], buffered_expand: false, expand_sources: [], save: function() { var url = new Curl(); $.ajax({ url: url.getPath() + '?output=ajax', type: 'post', data: { favobj: 'sysmap', action: 'update', _csrf_token: this.csrf_token, sysmapid: this.sysmapid, sysmap: JSON.stringify(this.data) }, error: function() { throw new Error('Cannot update map.'); } }); }, setExpandedLabels: function (elements, labels) { for (var i = 0, ln = elements.length; i < ln; i++) { if (labels !== null) { elements[i].expanded = labels[i]; } else { elements[i].expanded = null; } } this.updateImage(); if (labels === null) { alert(t('S_MACRO_EXPAND_ERROR')); } }, expandMacros: function(source) { var url = new Curl(); if (source !== null) { if (/\{.+\}/.test(source.getLabel(false))) { this.expand_sources.push(source); } else { source.expanded = null; } } if (this.buffered_expand === false && this.expand_sources.length > 0) { var sources = this.expand_sources, post = [], map = this; this.expand_sources = []; for (var i = 0, ln = sources.length; i < ln; i++) { post.push(sources[i].data); } $.ajax({ url: url.getPath() + '?output=ajax', type: 'post', dataType: 'html', data: { favobj: 'sysmap', action: 'expand', _csrf_token: this.csrf_token, sysmapid: this.sysmapid, name: this.data.name, source: JSON.stringify(post) }, success: function(data) { try { data = JSON.parse(data); } catch (e) { data = null; } map.setExpandedLabels(sources, data); }, error: function() { map.setExpandedLabels(sources, null); } }); } else if (this.buffered_expand === false) { this.updateImage(); } }, updateImage: function() { var shapes = [], links = [], elements = [], grid_size = (this.data.grid_show === '1') ? parseInt(this.data.grid_size, 10) : 0; if (grid_size !== this.data.last_grid_size) { this.map.setGrid(grid_size); this.data.last_grid_size = grid_size; } Object.keys(this.selements).forEach(function(key) { var element = {}, data = this.selements[key].data; ['selementid', 'x', 'y', 'label_location'].forEach(function (name) { element[name] = data[name]; }, this); element['label'] = this.selements[key].getLabel(); // host group elements if (data.elementtype === '3' && data.elementsubtype === '1') { element.width = (data.areatype === '0') ? this.data.width : data.width; element.height = (data.areatype === '0') ? this.data.height : data.height; } if ((data.use_iconmap === '1' && this.data.iconmapid !== '0') && (data.elementtype === '0' || (data.elementtype === '3' && data.elementsubtype === '1'))) { element.icon = this.defaultAutoIconId; } else { element.icon = data.iconid_off; } elements.push(element); }, this); Object.keys(this.links).forEach(function(key) { var link = {}; ['linkid', 'selementid1', 'selementid2', 'drawtype', 'color'].forEach(function (name) { link[name] = this.links[key].data[name]; }, this); link['label'] = this.links[key].getLabel(); links.push(link); }, this); Object.keys(this.shapes).forEach(function(key) { var shape = {}; Object.keys(this.shapes[key].data).forEach(function (name) { shape[name] = this.shapes[key].data[name]; }, this); shape['text'] = this.shapes[key].getLabel(); shapes.push(shape); }, this); this.map.update({ 'background': this.data.backgroundid, 'elements': elements, 'links': links, 'shapes': shapes, 'label_location': this.data.label_location }); }, // elements deleteSelectedElements: function() { var selementid; if (this.selection.count.selements && confirm(t('S_DELETE_SELECTED_ELEMENTS_Q'))) { for (selementid in this.selection.selements) { this.selements[selementid].remove(); this.removeLinksBySelementId(selementid); } this.toggleForm(); this.updateImage(); } }, // shapes deleteSelectedShapes: function() { var shapeid; if (this.selection.count.shapes && confirm(t('S_DELETE_SELECTED_SHAPES_Q'))) { for (shapeid in this.selection.shapes) { this.shapes[shapeid].remove(); } this.toggleForm(); this.updateImage(); } }, removeLinksBySelementId: function(selementid) { var selementIds = {}, linkids, i, ln; selementIds[selementid] = selementid; linkids = this.getLinksBySelementIds(selementIds); for (i = 0, ln = linkids.length; i < ln; i++) { this.links[linkids[i]].remove(); } }, /** * Returns the links between the given elements. * * @param selementIds * * @return {Array} an array of link ids */ getLinksBySelementIds: function(selementIds) { var linkIds = [], link, linkid; for (linkid in this.data.links) { link = this.data.links[linkid]; if (!!selementIds[link.selementid1] && !!selementIds[link.selementid2] || (objectSize(selementIds) === 1 && (!!selementIds[link.selementid1] || !!selementIds[link.selementid2]))) { linkIds.push(linkid); } } return linkIds; }, bindActions: function() { var that = this; /* * Map panel events */ // toggle expand macros $('#expand_macros').click(function() { that.data.expand_macros = (that.data.expand_macros === '1') ? '0' : '1'; $(this).html((that.data.expand_macros === '1') ? t('S_ON') : t('S_OFF')); that.updateImage(); }); // change grid size $('#gridsize').change(function() { var value = $(this).val(); if (that.data.grid_size !== value) { that.data.grid_size = value; that.updateImage(); } }); // toggle autoalign $('#gridautoalign').click(function() { that.data.grid_align = (that.data.grid_align === '1') ? '0' : '1'; $(this).html((that.data.grid_align === '1') ? t('S_ON') : t('S_OFF')); }); // toggle grid visibility $('#gridshow').click(function() { that.data.grid_show = (that.data.grid_show === '1') ? '0' : '1'; $(this).html((that.data.grid_show === '1') ? t('S_SHOWN') : t('S_HIDDEN')); that.updateImage(); }); // perform align all $('#gridalignall').click(function() { var selementid; for (selementid in that.selements) { that.selements[selementid].align(true); } that.updateImage(); }); // save map $('#sysmap_update').click(function() { that.save(); }); // add element $('#selementAdd').click(function() { if (typeof(that.iconList[0]) === 'undefined') { alert(t('S_NO_IMAGES')); return; } var selement = new Selement(that); that.selements[selement.id] = selement; that.updateImage(); }); // remove element $('#selementRemove').click($.proxy(this.deleteSelectedElements, this)); // add shape $('#shapeAdd').click(function() { var shape = new Shape(that); that.shapes[shape.id] = shape; that.updateImage(); }); // remove shapes $('#shapeRemove, #shapesRemove, #shapeMassRemove').click($.proxy(this.deleteSelectedShapes, this)); // add link $('#linkAdd').click(function() { var link; if (that.selection.count.selements !== 2) { alert(t('S_TWO_MAP_ELEMENTS_SHOULD_BE_SELECTED')); return false; } link = new Link(that); that.links[link.id] = link; that.updateImage(); that.linkForm.updateList(that.selection.selements); }); // removes all of the links between the selected elements $('#linkRemove').click(function() { var linkids; if (that.selection.count.selements !== 2) { alert(t('S_PLEASE_SELECT_TWO_ELEMENTS')); return false; } linkids = that.getLinksBySelementIds(that.selection.selements); if (linkids.length && confirm(t('S_DELETE_LINKS_BETWEEN_SELECTED_ELEMENTS_Q'))) { for (var i = 0, ln = linkids.length; i < ln; i++) { that.links[linkids[i]].remove(); } that.linkForm.hide(); that.linkForm.updateList({}); that.updateImage(); } }); /* * Selements events */ // Delegate selements icons clicks. $(this.container).on('click', '.sysmap_element, .sysmap_shape', function(event) { that.selectElements([{ id: $(this).attr('data-id'), type: $(this).attr('data-type') }], event.ctrlKey || event.metaKey); }); $(this.container).on('contextmenu', function(event) { var target = $(event.target), item_data = { id: target.attr('data-id'), type: target.attr('data-type') }, can_copy = false, can_paste = (that.copypaste_buffer.items && that.copypaste_buffer.items.length > 0), can_remove = false, can_reorder = false; if (typeof item_data.id === 'undefined') { that.clearSelection(); } else if (item_data.type && typeof that.selection[item_data.type][item_data.id] === 'undefined') { that.selectElements([item_data], false, true); } can_copy = (that.selection.count.shapes > 0 || that.selection.count.selements > 0); can_remove = can_copy; can_reorder = (that.selection.count.shapes > 0); event.preventDefault(); event.stopPropagation(); const overlay = overlays_stack.end(); if (typeof overlay !== 'undefined' && 'element' in overlay && overlay.element !== event.target) { $('.menu-popup-top').menuPopup('close', null, false); } if (!(can_copy || can_paste || can_remove || can_reorder)) { return false; } var items = [ { 'items': [ { label: t('S_BRING_TO_FRONT'), disabled: !can_reorder, clickCallback: function() { that.reorderShapes(that.selection.shapes, 'last'); } }, { label: t('S_BRING_FORWARD'), disabled: !can_reorder, clickCallback: function() { that.reorderShapes(that.selection.shapes, 'next'); } }, { label: t('S_SEND_BACKWARD'), disabled: !can_reorder, clickCallback: function() { that.reorderShapes(that.selection.shapes, 'previous'); } }, { label: t('S_SEND_TO_BACK'), disabled: !can_reorder, clickCallback: function() { that.reorderShapes(that.selection.shapes, 'first'); } } ] }, { 'items': [ { label: t('S_COPY'), disabled: !can_copy, clickCallback: function() { that.copypaste_buffer = that.getSelectionBuffer(that); } }, { label: t('S_PASTE'), disabled: !can_paste, clickCallback: function() { var offset = $(that.container).offset(), delta_x = event.pageX - offset.left - that.copypaste_buffer.left, delta_y = event.pageY - offset.top - that.copypaste_buffer.top, selectedids; delta_x = Math.min(delta_x, parseInt(that.data.width, 10) - that.copypaste_buffer.right ); delta_y = Math.min(delta_y, parseInt(that.data.height, 10) - that.copypaste_buffer.bottom ); selectedids = that.pasteSelectionBuffer(delta_x, delta_y, that, true); that.selectElements(selectedids, false); that.updateImage(); that.linkForm.updateList(that.selection.selements); } }, { label: t('S_PASTE_SIMPLE'), disabled: !can_paste, clickCallback: function() { var offset = $(that.container).offset(), delta_x = event.pageX - offset.left - that.copypaste_buffer.left, delta_y = event.pageY - offset.top - that.copypaste_buffer.top, selectedids; delta_x = Math.min(delta_x, parseInt(that.data.width, 10) - that.copypaste_buffer.right ); delta_y = Math.min(delta_y, parseInt(that.data.height, 10) - that.copypaste_buffer.bottom ); selectedids = that.pasteSelectionBuffer(delta_x, delta_y, that, false); that.selectElements(selectedids, false); that.updateImage(); that.linkForm.updateList(that.selection.selements); } }, { label: t('S_REMOVE'), disabled: !can_remove, clickCallback: function() { if (that.selection.count.selements || that.selection.count.shapes) { for (selementid in that.selection.selements) { that.selements[selementid].remove(); that.removeLinksBySelementId(selementid); } for (shapeid in that.selection.shapes) { that.shapes[shapeid].remove(); } } that.toggleForm(); that.updateImage(); } } ] } ]; $(event.target).menuPopup(items, event, { position: { of: event, my: 'left top', at: 'left bottom', using: (pos, data) => { let max_left = (data.horizontal === 'left') ? document.getElementById(containerId).clientWidth : document.getElementById(containerId).clientWidth - data.element.width; pos.top = Math.max(0, pos.top); pos.left = Math.max(0, Math.min(max_left, pos.left)); data.element.element[0].style.top = `${pos.top}px`; data.element.element[0].style.left = `${pos.left}px`; } }, background_layer: false }); }); /* * Form events */ $('#elementType').change(function() { var obj = $(this); switch (obj.val()) { // host case '0': $('#elementNameHost').multiSelect('clean'); $('#triggerContainer tbody').html(''); break; // map case '1': $('#elementNameMap').multiSelect('clean'); $('#triggerContainer tbody').html(''); break; // triggers case '2': $('#elementNameTriggers').multiSelect('clean'); $('#triggerContainer tbody').html(''); break; // host group case '3': $('#elementNameHostGroup').multiSelect('clean'); $('#triggerContainer tbody').html(''); break; // others types default: $('input[name=elementName]').val(''); $('#triggerContainer tbody').html(''); } }); $('#elementClose').click(function() { that.clearSelection(); that.toggleForm(); }); $('#elementRemove').click($.proxy(this.deleteSelectedElements, this)); $('#elementApply').click($.proxy(function() { if (this.selection.count.selements !== 1) { throw 'Try to single update element, when more than one selected.'; } var values = this.form.getValues(); if (values) { for (var selementid in this.selection.selements) { this.selements[selementid].update(values, true); } } }, this)); $('#shapeApply').click($.proxy(function() { if (this.selection.count.shapes !== 1) { throw 'Trying to single update shape, when more than one selected.'; } var values = this.shapeForm.getValues(); if (values) { for (var id in this.selection.shapes) { this.shapes[id].update(values); } } }, this)); $('#shapeClose, #shapeMassClose').click(function() { that.clearSelection(); that.toggleForm(); }); $('#shapeMassApply').click($.proxy(function() { var values = this.massShapeForm.getValues(); if (values) { this.buffered_expand = true; for (var shapeid in this.selection.shapes) { this.shapes[shapeid].update(values); } this.buffered_expand = false; this.expandMacros(null); } }, this)); $('#newSelementUrl').click($.proxy(function() { this.form.addUrls(); }, this)); $('#newSelementTriggers').click($.proxy(function() { this.form.addTriggers(); }, this)); $('#x, #y', this.form.domNode).change(function() { var value = parseInt(this.value, 10); this.value = isNaN(value) || (value < 0) ? 0 : value; }); $('#areaSizeWidth, #areaSizeHeight', this.form.domNode).change(function() { var value = parseInt(this.value, 10); this.value = isNaN(value) || (value < 10) ? 10 : value; }); // Init tag fields. $('#selement-tags') .dynamicRows({template: '#tag-row-tmpl', counter: 0, allow_empty: true}) .on('beforeadd.dynamicRows', function() { var options = $('#selement-tags').data('dynamicRows'); options.counter = ++options.counter; }) .on('afteradd.dynamicRows', function() { var rows = this.querySelectorAll('.form_row'); new CTagFilterItem(rows[rows.length - 1]); }); // mass update form $('#massClose').click(function() { that.clearSelection(); that.toggleForm(); }); $('#massRemove').click($.proxy(this.deleteSelectedElements, this)); $('#massApply').click($.proxy(function() { var values = this.massForm.getValues(); if (values) { this.buffered_expand = true; for (var selementid in this.selection.selements) { this.selements[selementid].update(values); } this.buffered_expand = false; this.expandMacros(null); } }, this)); // open link form $('.element-links').on('click', '.openlink', function() { that.currentLinkId = $(this).attr('data-linkid'); var linkData = that.links[that.currentLinkId].getData(); that.linkForm.setValues(linkData); that.linkForm.show(); }); // link form $('#formLinkRemove').click(function() { that.links[that.currentLinkId].remove(); that.linkForm.updateList(that.selection.selements); that.linkForm.hide(); that.updateImage(); }); $('#formLinkApply').click(function() { try { var linkData = that.linkForm.getValues(); that.links[that.currentLinkId].update(linkData); that.linkForm.updateList(that.selection.selements); } catch (err) { alert(err); } }); $('#formLinkClose').click(function() { that.linkForm.hide(); }); this.linkForm.domNode.on('click', '.triggerRemove', function() { var triggerid, tid = $(this).attr('data-linktriggerid').toString(); $('#linktrigger_' + tid).remove(); for (triggerid in that.linkForm.triggerids) { if (that.linkForm.triggerids[triggerid] === tid) { delete that.linkForm.triggerids[triggerid]; } } }); $('#border_type').on('change', function() { $(this).parent().find('input').prop("disabled", this.value === '0'); }); $('#mass_border_type, #chkboxBorderType').on('change', function() { var disable = ($('#mass_border_type').val() === '0' && $('#chkboxBorderType').is(":checked")); $('#chkboxBorderWidth, #chkboxBorderColor').prop("disabled", disable); $('#mass_border_width').prop("disabled", disable || !$('#chkboxBorderWidth').is(":checked")); $('#mass_border_color').prop("disabled", disable || !$('#chkboxBorderColor').is(":checked")); }); $('#shapeForm input[type=radio][name=type]').on('change', function() { var value = parseInt(this.value, 10), last_value = parseInt($('#shapeForm #last_shape_type').val(), 10); $('#shape-text-row, #shape-background-row').toggle(value !== SVGMapShape.TYPE_LINE); $('.switchable-content').each(function (i, element) { element.textContent = element.hasAttribute('data-value-' + value) ? element.getAttribute('data-value-' + value) : element.getAttribute('data-value'); }); if ((last_value === SVGMapShape.TYPE_LINE) !== (value === SVGMapShape.TYPE_LINE)) { var x = parseInt($('#shapeX').val(), 10), y = parseInt($('#shapeY').val(), 10), width = parseInt($('#shapeAreaSizeWidth').val(), 10), height = parseInt($('#shapeAreaSizeHeight').val(), 10); if (value === SVGMapShape.TYPE_LINE) { // Switching from figures to line. $('#shapeAreaSizeWidth').val(x + width); $('#shapeAreaSizeHeight').val(y + height); } else { // Switching from line to figures. var mx = Math.min(x, width), my = Math.min(y, height); $('#shapeX').val(mx); $('#shapeY').val(my); $('#shapeAreaSizeWidth').val(Math.max(x, width) - mx); $('#shapeAreaSizeHeight').val(Math.max(y, height) - my); } } $('#last_shape_type').val(value); }); $(this.container).parents('.sysmap-scroll-container').eq(0) .on('scroll', (e) => { if (!e.target.dataset.last_scroll_at || Date.now() - e.target.dataset.last_scroll_at > 1000) { $('.menu-popup-top').menuPopup('close', null, false); e.target.dataset.last_scroll_at = Date.now(); } }); $('input[type=radio][name=type]:checked').change(); }, /** * Buffer for draggable elements and elements group bounds. */ draggable_buffer: null, /** * Returns virtual DOM element used by draggable. * * @return {object} */ dragGroupPlaceholder: function() { return $('
').css({ width: $(this.domNode).width(), height: $(this.domNode).height() }); }, /** * Recalculate x and y position of moved elements. * * @param {int} delta_x Shift between old and new x position. * @param {int} delta_y Shift between old and new y position. * * @return {object} Object of elements with recalculated positions. */ dragGroupRecalculate: function(cmap, delta_x, delta_y) { var dragged = cmap.draggable_buffer; dragged.items.forEach(function(item) { var node = cmap[item.type][item.id]; if ('updatePosition' in node) { var dimensions = node.getDimensions(); node.updatePosition({ x: dimensions.x + delta_x, y: dimensions.y + delta_y }, false); } }); }, /** * Initializes multiple elements dragging. * * @param {object} draggable Draggable DOM element where drag event was started. */ dragGroupInit: function(draggable) { var buffer, draggable_node = $(draggable.domNode), dimensions = draggable.getDimensions(); if (draggable.selected) { buffer = draggable.sysmap.getSelectionBuffer(draggable.sysmap); } else { draggable_node = $(draggable.domNode); // Create getSelectionBuffer structure if drag event was started on unselected element. buffer = { items: [{ type: draggable_node.attr('data-type'), id: draggable.id }], left: dimensions.x, right: dimensions.x + draggable_node.width(), top: dimensions.y, bottom: dimensions.y + draggable_node.height() }; } buffer.xaxis = { min: dimensions.x - buffer.left, max: (draggable.sysmap.container).width() - (buffer.right - dimensions.x) }; buffer.yaxis = { min: dimensions.y - buffer.top, max: (draggable.sysmap.container).height() - (buffer.bottom - dimensions.y) }; draggable.sysmap.draggable_buffer = buffer; }, /** * Handler for drag event. * * @param {object} data jQuery UI draggable data. * @param {object} draggable Element where drag event occurred. */ dragGroupDrag: function(data, draggable) { var cmap = draggable.sysmap, dimensions = draggable.getDimensions(), delta_x = data.position.left - dimensions.x, delta_y = data.position.top - dimensions.y; if (data.position.left < cmap.draggable_buffer.xaxis.min) { delta_x = dimensions.x < cmap.draggable_buffer.xaxis.min ? 0 : cmap.draggable_buffer.xaxis.min - dimensions.x; } else if (data.position.left > cmap.draggable_buffer.xaxis.max) { delta_x = dimensions.x > cmap.draggable_buffer.xaxis.max ? 0 : cmap.draggable_buffer.xaxis.max - dimensions.x; } if (data.position.top < cmap.draggable_buffer.yaxis.min) { delta_y = dimensions.y < cmap.draggable_buffer.yaxis.min ? 0 : cmap.draggable_buffer.yaxis.min - dimensions.y; } else if (data.position.top > cmap.draggable_buffer.yaxis.max) { delta_y = dimensions.y > cmap.draggable_buffer.yaxis.max ? 0 : cmap.draggable_buffer.yaxis.max - dimensions.y; } if (delta_x != 0 || delta_y != 0) { cmap.dragGroupRecalculate(cmap, Math.round(delta_x), Math.round(delta_y)); cmap.updateImage(); } }, /** * Final tasks for dragged element on drag stop event. * * @param {object} draggable Element where drag stop event occurred. */ dragGroupStop: function(draggable) { var cmap = draggable.sysmap, should_align = (cmap.data.grid_align === '1'); if (should_align) { cmap.draggable_buffer.items.forEach(function(item) { var node = cmap[item.type][item.id]; if ('updatePosition' in node) { var dimensions = node.getDimensions(); node.updatePosition({ x: dimensions.x, y: dimensions.y }); } }); } }, /** * Paste that.copypaste_buffer content at new location. * * @param {number} delta_x Shift between desired and actual x position. * @param {number} delta_y Shift between desired and actual y position. * @param {object} that CMap object * @param {bool} keep_external_links Should be links to non selected elements copied or not. * * @return {array} */ pasteSelectionBuffer: function(delta_x, delta_y, that, keep_external_links) { var selectedids = [], source_cloneids = {}; that.copypaste_buffer.items.forEach(function(element_data) { var data = $.extend({}, element_data.data, false), type = element_data.type, element; switch (type) { case 'selements': element = new Selement(that); delete data.selementid; break; case 'shapes': element = new Shape(that); delete data.sysmap_shapeid; break; default: throw 'Unsupported element type found in copy buffer!'; } if (element) { data.x = parseInt(data.x, 10) + delta_x; data.y = parseInt(data.y, 10) + delta_y; element.expanded = element_data.expanded; if (type === 'shapes' && data.type == SVGMapShape.TYPE_LINE) { // Additional shift for line shape. data.width = parseInt(data.width, 10) + delta_x; data.height = parseInt(data.height, 10) + delta_y; } element.update(data); that[type][element.id] = element; selectedids.push({ id: element.id, type: type }); source_cloneids[element_data.id] = element.id; if (that.data.grid_align === '1') { element.align(true); } } }); var link, fromid, toid, data; that.copypaste_buffer.links.forEach(function(link_data) { data = $.extend({}, link_data.data, false); if (!keep_external_links && (data.selementid1 in source_cloneids === false || data.selementid2 in source_cloneids === false)) { return; } link = new Link(that); delete data.linkid; fromid = (data.selementid1 in source_cloneids) ? source_cloneids[data.selementid1] : data.selementid1; toid = (data.selementid2 in source_cloneids) ? source_cloneids[data.selementid2] : data.selementid2; data.selementid1 = fromid; data.selementid2 = toid; link.update(data); that.links[link.id] = link; }); return selectedids; }, /** * Return object with selected elements data and links. * * @param {object} that CMap object * * @return {object} */ getSelectionBuffer: function(that) { var items = [], left = null, top = null, right = null, bottom = null; for (var type in that.selection) { if (type in that === false || typeof that[type] !== 'object') { continue; } var data, dimensions, dom_node, x, y; for (var id in that.selection[type]) { if ('getData' in that[type][id] === false) { continue; } // Get current data without observers. data = $.extend({}, that[type][id].getData(), false); dimensions = that[type][id].getDimensions(); dom_node = that[type][id].domNode; x = dimensions.x; y = dimensions.y; left = Math.min(x, (left === null) ? x : left); top = Math.min(y, (top === null) ? y : top); right = Math.max(x + dom_node.outerWidth(true), (right === null) ? 0 : right); bottom = Math.max(y + dom_node.outerHeight(true), (bottom === null) ? 0 : bottom); items.push({ id: id, type: type, data: data }); } } // Sort items array according to item.data.zindex value. items = items.sort(function(a, b) { var aindex = parseInt(a.data.zindex, 10) || 0, bindex = parseInt(b.data.zindex, 10) || 0; return aindex - bindex; }); var links = []; for (var id in that.links) { // Get current data without observers. var data = $.extend({}, that.links[id].getData(), false); if (data.selementid1 in that.selection.selements || data.selementid2 in that.selection.selements) { links.push({ id: id, data: data }) } } return { items: items, links: links, top: top, left: left, right: right, bottom: bottom }; }, clearSelection: function() { var id; ['selements', 'shapes'].forEach(function (type) { for (id in this.selection[type]) { this.selection.count[type]--; this[type][id].toggleSelect(false); delete this.selection[type][id]; } }, this); // Clean trigger selement. if ($('#elementType').val() == 2) { $('#elementNameTriggers').multiSelect('clean'); $('#triggerContainer tbody').html(''); } this.form.cleanTagsField(); }, reorderShapes: function(ids, position) { var shapes = [], target, temp, ignore = [], selection = []; Object.keys(this.shapes).forEach(function(key) { shapes.push(this.shapes[key]); }, this); shapes = shapes.sort(function (a,b) { return a.data.zindex - b.data.zindex; }); shapes.forEach(function(value, index) { if (typeof ids[value.id] !== 'undefined') { selection.push(index); } }); // All shapes are selected, no need to update order. if (shapes.length === selection.length) { return; } switch (position.toLowerCase()) { case 'first': target = []; for (var i = selection.length - 1; i >= 0; i--) { target.unshift(shapes.splice(selection[i], 1)[0]); } for (var i = 0; i < target.length; i++) { $(target[i].domNode).insertBefore(shapes[0].domNode); } shapes = target.concat(shapes); shapes.forEach(function(shape, index) { shape.data.zindex = index; }); break; case 'last': target = []; for (var i = selection.length - 1; i >= 0; i--) { target.unshift(shapes.splice(selection[i], 1)[0]); } for (var i = target.length - 1; i >= 0 ; i--) { $(target[i].domNode).insertAfter(shapes[shapes.length-1].domNode); } shapes = shapes.concat(target); shapes.forEach(function(shape, index) { shape.data.zindex = index; }); break; case 'next': ignore.push(shapes.length - 1); for (var i = selection.length - 1; i >= 0; i--) { target = selection[i]; // No need to update. if (ignore.indexOf(target) !== -1) { ignore.push(target - 1); continue; } $(shapes[target].domNode).insertAfter(shapes[target + 1].domNode); shapes[target + 1].data.zindex--; shapes[target].data.zindex++; temp = shapes[target + 1]; shapes[target + 1] = shapes[target]; shapes[target] = temp; } break; case 'previous': ignore.push(0); for (var i = 0; i < selection.length; i++) { target = selection[i]; // No need to update. if (ignore.indexOf(target) !== -1) { ignore.push(target + 1); continue; } $(shapes[target].domNode).insertBefore(shapes[target - 1].domNode); shapes[target - 1].data.zindex++; shapes[target].data.zindex--; temp = shapes[target - 1]; shapes[target - 1] = shapes[target]; shapes[target] = temp; } break; } this.map.invalidate('shapes'); this.updateImage(); }, selectElements: function(ids, addSelection, prevent_form_open) { var i, ln; $('.menu-popup-top').menuPopup('close', null, false); if (!addSelection) { this.clearSelection(); } for (i = 0, ln = ids.length; i < ln; i++) { var id = ids[i].id, type = ids[i].type; if (typeof id === 'undefined' || typeof type === 'undefined') { continue; } if (this[type][id].toggleSelect()) { this.selection.count[type]++; this.selection[type][id] = id; } else { this.selection.count[type]--; delete this.selection[type][id]; } } if (typeof prevent_form_open === 'undefined' || !prevent_form_open) { this.toggleForm(); } }, toggleForm: function() { var id; this.shapeForm.hide(); this.linkForm.hide(); if (this.selection.count.selements + this.selection.count.shapes === 0 || (this.selection.count.selements > 0 && this.selection.count.shapes > 0)) { $('#map-window').hide(); } else { this.linkForm.updateList(this.selection.selements); // only one element selected if (this.selection.count.selements === 1) { for (id in this.selection.selements) { this.form.setValues(this.selements[id].getData()); } this.massForm.hide(); this.massShapeForm.hide(); $('#link-connect-to').show(); this.form.show(); } // only one shape is selected else if (this.selection.count.shapes === 1) { this.form.hide(); this.massForm.hide(); this.massShapeForm.hide(); for (id in this.selection.shapes) { this.shapeForm.setValues(this.shapes[id].getData()); } this.shapeForm.show(); } // multiple elements selected else if (this.selection.count.selements > 1) { this.form.hide(); this.massShapeForm.hide(); $('#link-connect-to').hide(); this.massForm.show(); } // multiple shapes selected else { var figures = null; for (id in this.selection.shapes) { if (figures === null) { figures = (this.shapes[id].data.type != SVGMapShape.TYPE_LINE); } else if (figures !== (this.shapes[id].data.type != SVGMapShape.TYPE_LINE)) { // Different shape types are selected (lines and figures). $('#map-window').hide(); this.massShapeForm.hide(); return; } } this.form.hide(); this.massForm.hide(); $('#link-connect-to').hide(); this.massShapeForm.show(figures); } } } }; /** * Creates a new Link. * * @class represents connector between two Elements * * @property {object} sysmap Reference to Map object. * @property {object} data Link db values. * @property {string} id Link ID (linkid). * * @param {object} sysmap Map object. * @param {object} linkData Link data from DB. */ function Link(sysmap, linkData) { var selementid; this.sysmap = sysmap; if (!linkData) { linkData = { label: '', selementid1: null, selementid2: null, linktriggers: {}, drawtype: 0, color: '00CC00' }; for (selementid in this.sysmap.selection.selements) { if (linkData.selementid1 === null) { linkData.selementid1 = selementid; } else { linkData.selementid2 = selementid; } } // generate unique linkid linkData.linkid = getUniqueId(); } else { if ($.isArray(linkData.linktriggers)) { linkData.linktriggers = {}; } } this.data = linkData; this.id = this.data.linkid; this.expanded = this.data.expanded; delete this.data.expanded; for (var linktrigger in this.data.linktriggers) { this.sysmap.allLinkTriggerIds[linktrigger.triggerid] = true; } // assign by reference this.sysmap.data.links[this.id] = this.data; } Link.prototype = { /** * Return label based on map constructor configuration. * * @param {boolean} return label with expanded macros. * * @returns {string} */ getLabel: function (expand) { var label = this.data.label; if (typeof(expand) === 'undefined') { expand = true; } if (expand && typeof(this.expanded) === 'string' && this.sysmap.data.expand_macros === '1') { label = this.expanded; } return label; }, /** * Updades values in property data. * * @param {object} data */ update: function(data) { var key, invalidate = (this.data.label !== data.label); for (key in data) { this.data[key] = data[key]; } if (invalidate) { sysmap.expandMacros(this); } else { sysmap.updateImage(); } }, /** * Removes Link object, delete all reference to it. */ remove: function() { delete this.sysmap.data.links[this.id]; delete this.sysmap.links[this.id]; if (sysmap.form.active) { sysmap.linkForm.updateList(sysmap.selection.selements); } sysmap.linkForm.hide(); }, /** * Gets Link data. * * @returns {Object} */ getData: function() { return this.data; } }; Observer.makeObserver(Link.prototype); /** * Creates a new Shape. * * @class represents shape (static) element * * @property {object} sysmap Reference to Map object * @property {object} data Shape values from DB. * @property {string} id Shape ID (shapeid). * * @param {object} sysmap Map object * @param {object} [shape_data] shape data from db */ function Shape(sysmap, shape_data) { var default_data = { type: SVGMapShape.TYPE_RECTANGLE, x: 10, y: 10, width: 50, height: 50, border_color: '000000', background_color: '', border_width: 2, font: 9, // Helvetica font_size: 11, font_color: '000000', text_valign: 0, text_halign: 0, text: '', border_type: 1 }; this.sysmap = sysmap; if (!shape_data) { shape_data = default_data; // generate unique sysmap_shapeid shape_data.sysmap_shapeid = getUniqueId(); shape_data.zindex = Object.keys(sysmap.shapes).length; } else { for (var field in default_data) { if (typeof shape_data[field] === 'undefined') { shape_data[field] = default_data[field]; } } } this.data = shape_data; this.id = this.data.sysmap_shapeid; this.expanded = this.data.expanded; delete this.data.expanded; // assign by reference this.sysmap.data.shapes[this.id] = this.data; // create dom this.domNode = $('
', { style: 'position: absolute; z-index: 1; background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat', }) .appendTo(this.sysmap.container) .addClass('cursor-pointer sysmap_shape') .attr('data-id', this.id) .attr('data-type', 'shapes'); this.makeDraggable(true); this.makeResizable(this.data.type != SVGMapShape.TYPE_LINE); var dimensions = this.getDimensions(); this.domNode.css({ top: dimensions.y + 'px', left: dimensions.x + 'px', width: dimensions.width + 'px', height: dimensions.height + 'px' }); } Shape.prototype = { /** * Updades values in property data. * * @param {object} data */ update: function(data) { var key, dimensions, invalidate = (data.type != SVGMapShape.TYPE_LINE && typeof(data.text) !== 'undefined' && this.data.text !== data.text); if (typeof data['type'] !== 'undefined' && /^[0-9]+$/.test(this.data.sysmap_shapeid) === true && (data['type'] == SVGMapShape.TYPE_LINE) != (this.data.type == SVGMapShape.TYPE_LINE)) { delete data['sysmap_shapeid']; this.data.sysmap_shapeid = getUniqueId(); } for (key in data) { this.data[key] = data[key]; } ['x', 'y', 'width', 'height'].forEach(function(name) { this[name] = parseInt(this[name], 10); }, this.data); dimensions = this.getDimensions(); this.domNode .css({ width: dimensions.width + 'px', height: dimensions.height + 'px' }); this.makeDraggable(true); this.makeResizable(this.data.type != SVGMapShape.TYPE_LINE); this.align(false); this.trigger('afterMove', this); for (key in data) { this.data[key] = data[key]; } if (invalidate) { sysmap.expandMacros(this); } else { sysmap.updateImage(); } }, /** * Return label based on map constructor configuration. * * @param {boolean} return label with expanded macros. * * @returns {string} */ getLabel: function (expand) { var label = this.data.text; if (typeof(expand) === 'undefined') { expand = true; } if (expand && typeof(this.expanded) === 'string' && this.sysmap.data.expand_macros === '1') { label = this.expanded; } return label; }, /** * Gets shape dimensions. */ getDimensions: function () { var dimensions = { x: parseInt(this.data.x, 10), y: parseInt(this.data.y, 10), width: parseInt(this.data.width, 10), height: parseInt(this.data.height, 10) }; if (this instanceof Shape && this.data.type == SVGMapShape.TYPE_LINE) { var width = parseInt(this.sysmap.data.width), height = parseInt(this.sysmap.data.height), x = Math.min(Math.max(0, Math.min(dimensions.x, dimensions.width)), width), y = Math.min(Math.max(0, Math.min(dimensions.y, dimensions.height)), height), dx = Math.max(dimensions.x, dimensions.width) - x, dy = Math.max(dimensions.y, dimensions.height) - y; dimensions = { x: x, y: y, width: Math.min(Math.max(0, dx), width - x), height: Math.min(Math.max(0, dy), height - y) }; } return dimensions; }, updateHandles: function() { if (typeof this.handles === 'undefined') { this.handles = [ $('
', {'class': 'ui-resize-dot cursor-move'}), $('
', {'class': 'ui-resize-dot cursor-move'}) ]; this.domNode.parent().append(this.handles); for (var i = 0; i < 2; i++) { this.handles[i].data('id', i); this.handles[i].draggable({ containment: 'parent', drag: $.proxy(function(event, data) { var dimensions; if (data.helper.data('id') === 0) { this.data.x = parseInt(data.position.left, 10) + 4; this.data.y = parseInt(data.position.top, 10) + 4; } else { this.data.width = parseInt(data.position.left, 10) + 4; this.data.height = parseInt(data.position.top, 10) + 4; } dimensions = this.getDimensions(); this.domNode.css({ top: dimensions.y + 'px', left: dimensions.x + 'px', width: dimensions.width + 'px', height: dimensions.height + 'px' }); this.trigger('afterMove', this); }, this) }); } } this.handles[0].css({ left: (this.data.x - 3) + 'px', top: (this.data.y - 3) + 'px' }); this.handles[1].css({ left: (this.data.width - 3) + 'px', top: (this.data.height - 3) + 'px' }); }, /** * Allow dragging of shape. */ makeDraggable: function(enable) { var node = this.domNode; if (enable) { if (this instanceof Shape && this.data.type == SVGMapShape.TYPE_LINE) { this.updateHandles(); } else { if (typeof this.handles !== 'undefined') { this.handles.forEach(function (handle) { handle.remove(); }); delete this.handles; } } if (!node.hasClass('ui-draggable')) { node.draggable({ containment: 'parent', helper: $.proxy(function() { return this.sysmap.dragGroupPlaceholder(); }, this), start: $.proxy(function() { this.domNode .addClass('cursor-dragging') .removeClass('cursor-pointer'); this.sysmap.dragGroupInit(this); }, this), drag: $.proxy(function(event, data) { this.sysmap.dragGroupDrag(data, this); }, this), stop: $.proxy(function() { this.domNode .addClass('cursor-pointer') .removeClass('cursor-dragging'); this.sysmap.dragGroupStop(this); }, this) }); } } else { if (typeof this.handles !== 'undefined') { this.handles.forEach(function (handle) { handle.remove(); }); delete this.handles; } if (node.hasClass('ui-draggable')) { node.draggable("destroy"); } } }, /** * Allow resizing of shape. */ makeResizable: function(enable) { var node = this.domNode, enabled = node.hasClass('ui-resizable'); if (enable === enabled) { return; } if (enable) { var handles = {}; $.each(['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw'], function(index, key) { var handle = $('
').addClass('ui-resizable-handle').addClass('ui-resizable-' + key); if ($.inArray(key, ['n', 'e', 's', 'w']) >= 0) { handle .append($('
', {'class': 'ui-resize-dot'})) .append($('
', {'class': 'ui-resizable-border-' + key})); } node.append(handle); handles[key] = handle; }); node.addClass('ui-inner-handles'); node.resizable({ handles: handles, autoHide: true, stop: $.proxy(function(event, data) { this.updatePosition({ x: parseInt(data.position.left, 10), y: parseInt(data.position.top, 10) }); }, this) }); } else { node.removeClass('ui-inner-handles'); node.resizable("destroy"); } }, /** * Toggle shape selection. * * @param {bool} state */ toggleSelect: function(state) { state = state || !this.selected; this.selected = state; if (this.selected) { this.domNode.addClass('map-element-selected'); } else { this.domNode.removeClass('map-element-selected'); } return this.selected; }, /** * Align shape to map or map grid. * * @param {bool} doAutoAlign if we should align element to grid */ align: function(doAutoAlign) { var dims = { height: this.domNode.height(), width: this.domNode.width() }, dimensions = this.getDimensions(), x = dimensions.x, y = dimensions.y, shiftX = Math.round(dims.width / 2), shiftY = Math.round(dims.height / 2), newX = x, newY = y, newWidth = dims.width, newHeight = dims.height, gridSize = parseInt(this.sysmap.data.grid_size, 10); // Lines should not be aligned if (this instanceof Shape && this.data.type == SVGMapShape.TYPE_LINE) { this.domNode.css({ top: dimensions.y + 'px', left: dimensions.x + 'px', width: dimensions.width + 'px', height: dimensions.height + 'px' }); return; } // if 'fit to map' area coords are 0 always if (this.data.elementsubtype === '1' && this.data.areatype === '0' && this.data.elementtype === '3') { newX = 0; newY = 0; } // if autoalign is off else if (doAutoAlign === false || (typeof doAutoAlign === 'undefined' && this.sysmap.data.grid_align == '0')) { if ((x + dims.width) > this.sysmap.data.width) { newX = this.sysmap.data.width - dims.width; } if ((y + dims.height) > this.sysmap.data.height) { newY = this.sysmap.data.height - dims.height; } if (newX < 0) { newX = 0; newWidth = this.sysmap.data.width; } if (newY < 0) { newY = 0; newHeight = this.sysmap.data.height; } } else { newX = x + shiftX; newY = y + shiftY; newX = Math.floor(newX / gridSize) * gridSize; newY = Math.floor(newY / gridSize) * gridSize; newX += Math.round(gridSize / 2) - shiftX; newY += Math.round(gridSize / 2) - shiftY; while ((newX + dims.width) > this.sysmap.data.width) { newX -= gridSize; } while ((newY + dims.height) > this.sysmap.data.height) { newY -= gridSize; } while (newX < 0) { newX += gridSize; } while (newY < 0) { newY += gridSize; } } this.data.y = newY; this.data.x = newX; if (this instanceof Shape || this.data.elementsubtype === '1') { this.data.width = newWidth; this.data.height = newHeight; } this.domNode.css({ top: this.data.y + 'px', left: this.data.x + 'px', width: newWidth, height: newHeight }); }, /** * Updates element position. * * @param {object} coords */ updatePosition: function(coords, invalidate) { if (this instanceof Shape && this.data.type == SVGMapShape.TYPE_LINE) { var dx = coords.x - Math.min(parseInt(this.data.x, 10), parseInt(this.data.width, 10)), dy = coords.y - Math.min(parseInt(this.data.y, 10), parseInt(this.data.height, 10)); this.data.x = parseInt(this.data.x, 10) + dx; this.data.y = parseInt(this.data.y, 10) + dy; this.data.width = parseInt(this.data.width, 10) + dx; this.data.height = parseInt(this.data.height, 10) + dy; this.updateHandles(); } else { this.data.x = coords.x; this.data.y = coords.y; } if (invalidate !== false) { this.align(); this.trigger('afterMove', this); } else { var dimensions = this.getDimensions(); this.domNode.css({ top: dimensions.y + 'px', left: dimensions.x + 'px' }); } }, /** * Removes Shape object, delete all reference to it. */ remove: function() { this.makeDraggable(false); this.domNode.remove(); delete this.sysmap.data.shapes[this.id]; delete this.sysmap.shapes[this.id]; if (typeof this.sysmap.selection.shapes[this.id] !== 'undefined') { this.sysmap.selection.count.shapes--; } delete this.sysmap.selection.shapes[this.id]; }, /** * Gets Shape data. * * @returns {Object} */ getData: function() { return this.data; } }; Observer.makeObserver(Shape.prototype); /** * @class Creates a new Selement. * * @property {object} sysmap reference to Map object * @property {object} data selement db values * @property {bool} selected if element is now selected by user * @property {string} id elementid * @property {object} domNode reference to related DOM element * * @param {object} sysmap reference to Map object * @param {object} selementData element db values */ function Selement(sysmap, selementData) { this.sysmap = sysmap; this.selected = false; if (!selementData) { selementData = { selementid: getUniqueId(), elementtype: '4', // image elements: {}, iconid_off: this.sysmap.defaultIconId, // first imageid label: t('S_NEW_ELEMENT'), label_location: -1, // set default map label location x: 0, y: 0, urls: {}, elementName: this.sysmap.defaultIconName, // first image name use_iconmap: '1', evaltype: 0, // TAG_EVAL_TYPE_AND_OR tags: [], inherited_label: null }; } else { if ($.isArray(selementData.urls)) { selementData.urls = {}; } } this.data = selementData; this.updateLabel(); this.id = this.data.selementid; this.expanded = this.data.expanded; delete this.data.expanded; // assign by reference this.sysmap.data.selements[this.id] = this.data; // create dom this.domNode = $('
', {style: 'position: absolute; z-index: 100'}) .appendTo(this.sysmap.container) .addClass('cursor-pointer sysmap_element') .attr('data-id', this.id) .attr('data-type', 'selements'); this.makeDraggable(true); this.makeResizable(this.data.elementtype == 3 && this.data.elementsubtype == 1 && this.data.areatype == 1); this.updateIcon(); this.domNode.css({ top: this.data.y + 'px', left: this.data.x + 'px' }); } Selement.TYPE_HOST = 0; // SYSMAP_ELEMENT_TYPE_HOST Selement.TYPE_MAP = 1; // SYSMAP_ELEMENT_TYPE_MAP Selement.TYPE_TRIGGER = 2; // SYSMAP_ELEMENT_TYPE_TRIGGER Selement.TYPE_HOST_GROUP = 3; // SYSMAP_ELEMENT_TYPE_HOST_GROUP Selement.TYPE_IMAGE = 4; // SYSMAP_ELEMENT_TYPE_IMAGE Selement.prototype = { /** * Returns element data. */ getData: Shape.prototype.getData, /** * Allows dragging of element */ makeDraggable: Shape.prototype.makeDraggable, /** * Allows resizing of element */ makeResizable: Shape.prototype.makeResizable, /** * Update label data inherited from map configuration. */ updateLabel: function () { if (this.sysmap.data.label_format != 0) { switch (parseInt(this.data.elementtype, 10)) { case Selement.TYPE_HOST_GROUP: this.data.label_type = this.sysmap.data.label_type_hostgroup; if (this.data.label_type == CMap.LABEL_TYPE_CUSTOM) { this.data.inherited_label = this.sysmap.data.label_string_hostgroup; } break; case Selement.TYPE_HOST: this.data.label_type = this.sysmap.data.label_type_host; if (this.data.label_type == CMap.LABEL_TYPE_CUSTOM) { this.data.inherited_label = this.sysmap.data.label_string_host; } break; case Selement.TYPE_TRIGGER: this.data.label_type = this.sysmap.data.label_type_trigger; if (this.data.label_type == CMap.LABEL_TYPE_CUSTOM) { this.data.inherited_label = this.sysmap.data.label_string_trigger; } break; case Selement.TYPE_MAP: this.data.label_type = this.sysmap.data.label_type_map; if (this.data.label_type == CMap.LABEL_TYPE_CUSTOM) { this.data.inherited_label = this.sysmap.data.label_string_map; } break; case Selement.TYPE_IMAGE: this.data.label_type = this.sysmap.data.label_type_image; if (this.data.label_type == CMap.LABEL_TYPE_CUSTOM) { this.data.inherited_label = this.sysmap.data.label_string_image; } break; } } else { this.data.label_type = this.sysmap.data.label_type; this.data.inherited_label = null; } if (this.data.label_type == CMap.LABEL_TYPE_LABEL) { this.data.inherited_label = this.data.label; } else if (this.data.label_type == CMap.LABEL_TYPE_NAME) { if (this.data.elementtype != Selement.TYPE_IMAGE) { this.data.inherited_label = this.data.elements[0].elementName; } else { this.data.inherited_label = t('S_IMAGE'); } } if (this.data.label_type != CMap.LABEL_TYPE_CUSTOM && this.data.label_type != CMap.LABEL_TYPE_LABEL && this.data.label_type != CMap.LABEL_TYPE_IP) { this.data.expanded = null; } else if (this.data.label_type == CMap.LABEL_TYPE_IP && this.data.elementtype == Selement.TYPE_HOST) { this.data.inherited_label = '{HOST.IP}'; } }, /** * Return label based on map constructor configuration. * * @param {boolean} return label with expanded macros. * * @returns {string} or null */ getLabel: function (expand) { var label = this.data.label; if (typeof(expand) === 'undefined') { expand = true; } if (this.data.label_type != CMap.LABEL_TYPE_NOTHING && this.data.label_type != CMap.LABEL_TYPE_STATUS) { if (expand && typeof(this.expanded) === 'string' && (this.sysmap.data.expand_macros === '1' || (this.data.label_type == CMap.LABEL_TYPE_IP && this.data.elementtype == Selement.TYPE_HOST))) { label = this.expanded; } else if (typeof this.data.inherited_label === 'string') { label = this.data.inherited_label; } } else { label = null; } return label; }, /** * Updates element fields. * * @param {object} data * @param {bool} unsetUndefined If true, all fields that are not in data parameter will be removed * from element. */ update: function(data, unsetUndefined) { var fieldName, dataFelds = ['elementtype', 'elements', 'iconid_off', 'iconid_on', 'iconid_maintenance', 'iconid_disabled', 'label', 'label_location', 'x', 'y', 'elementsubtype', 'areatype', 'width', 'height', 'viewtype', 'urls', 'elementName', 'use_iconmap', 'evaltype', 'tags' ], fieldsUnsettable = ['iconid_off', 'iconid_on', 'iconid_maintenance', 'iconid_disabled'], i, ln, invalidate = ((typeof(data.label) !== 'undefined' && this.data.label !== data.label) || (typeof(data.elementtype) !== 'undefined' && this.data.elementtype !== data.elementtype) || (typeof(data.elements) !== 'undefined' && Object.keys(this.data.elements).length !== Object.keys(data.elements).length)); unsetUndefined = unsetUndefined || false; if (!invalidate && typeof(data.elements) !== 'undefined') { var k, id, key, kln, keys, ids = Object.keys(this.data.elements); for (i = 0, ln = ids.length; i < ln && !invalidate; i++) { id = ids[i]; keys = Object.keys(this.data.elements[id]); for (k = 0, kln = keys.length; k < kln; k++) { key = keys[k]; if (this.data.elements[id][key] !== data.elements[id][key]) { invalidate = true; break; } } } } // update elements fields, if not massupdate, remove fields that are not in new values for (i = 0, ln = dataFelds.length; i < ln; i++) { fieldName = dataFelds[i]; if (typeof data[fieldName] !== 'undefined') { this.data[fieldName] = data[fieldName]; } else if (unsetUndefined && (fieldsUnsettable.indexOf(fieldName) === -1)) { delete this.data[fieldName]; } } // if elementsubtype is not set, it should be 0 if (unsetUndefined && typeof this.data.elementsubtype === 'undefined') { this.data.elementsubtype = '0'; } if (unsetUndefined && typeof this.data.use_iconmap === 'undefined') { this.data.use_iconmap = '0'; } this.makeResizable( this.data.elementtype == 3 && this.data.elementsubtype == 1 && this.data.areatype == 1 ); if (this.data.elementtype === '2') { // For element type trigger not exist single element name. delete this.data['elementName']; } else if (this.data.elementtype === '4') { // If element is image, unset advanced icons. this.data.iconid_on = '0'; this.data.iconid_maintenance = '0'; this.data.iconid_disabled = '0'; // If image element, set elementName to image name. for (i in this.sysmap.iconList) { if (this.sysmap.iconList[i].imageid === this.data.iconid_off) { this.data.elementName = this.sysmap.iconList[i].name; } } } else { this.data.elementName = this.data.elements[0].elementName; } this.updateLabel(); this.updateIcon(); this.align(false); this.trigger('afterMove', this); if (invalidate) { this.sysmap.expandMacros(this); } }, /** * Updates element position. * * @param {object} coords */ updatePosition: Shape.prototype.updatePosition, /** * Remove element. */ remove: function() { this.domNode.remove(); delete this.sysmap.data.selements[this.id]; delete this.sysmap.selements[this.id]; if (typeof this.sysmap.selection.selements[this.id] !== 'undefined') { this.sysmap.selection.count.selements--; } delete this.sysmap.selection.selements[this.id]; }, /** * Toggle element selection. * * @param {bool} state */ toggleSelect: Shape.prototype.toggleSelect, /** * Align element to map or map grid. * * @param {bool} doAutoAlign if we should align element to grid */ align: Shape.prototype.align, /** * Get element dimensions. */ getDimensions: Shape.prototype.getDimensions, /** * Updates element icon and height/width in case element is area type. */ updateIcon: function() { var oldIconClass = this.domNode.get(0).className.match(/sysmap_iconid_\d+/); if (oldIconClass !== null) { this.domNode.removeClass(oldIconClass[0]); } if ((this.data.use_iconmap === '1' && this.sysmap.data.iconmapid !== '0') && (this.data.elementtype === '0' || (this.data.elementtype === '3' && this.data.elementsubtype === '1'))) { this.domNode.addClass('sysmap_iconid_' + this.sysmap.defaultAutoIconId); } else { this.domNode.addClass('sysmap_iconid_' + this.data.iconid_off); } if (this.data.elementtype === '3' && this.data.elementsubtype === '1') { if (this.data.areatype === '1') { this.domNode .css({ width: this.data.width + 'px', height: this.data.height + 'px' }) .addClass('map-element-area-bg'); } else { this.domNode .css({ width: this.sysmap.data.width + 'px', height: this.sysmap.data.height + 'px' }) .addClass('map-element-area-bg'); } } else { this.domNode .css({ width: '', height: '' }) .removeClass('map-element-area-bg'); } }, getName: function () { var name; if (typeof this.data.elementName === 'undefined') { name = this.data.elements[0].elementName; if (Object.keys(this.data.elements).length > 1) { name += '...'; } } else { name = this.data.elementName; } return name; } }; Observer.makeObserver(Selement.prototype); /** * Form for elements. * * @param {object} formContainer jQuery object * @param {object} sysmap */ function SelementForm(formContainer, sysmap) { var formTplData = { sysmapid: sysmap.sysmapid }, tpl = new Template($('#mapElementFormTpl').html()), i, icon, formActions = [ { action: 'show', value: '#subtypeRow, #hostGroupSelectRow', cond: [{ elementType: '3' }] }, { action: 'show', value: '#hostSelectRow', cond: [{ elementType: '0' }] }, { action: 'show', value: '#triggerSelectRow, #triggerListRow', cond: [{ elementType: '2' }] }, { action: 'show', value: '#mapSelectRow', cond: [{ elementType: '1' }] }, { action: 'show', value: '#areaTypeRow, #areaPlacingRow', cond: [{ elementType: '3', subtypeHostGroupElements: 'checked' }] }, { action: 'show', value: '#areaSizeRow', cond: [{ elementType: '3', subtypeHostGroupElements: 'checked', areaTypeCustom: 'checked' }] }, { action: 'hide', value: '#iconProblemRow, #iconMainetnanceRow, #iconDisabledRow', cond: [{ elementType: '4' }] }, { action: 'disable', value: '#iconid_off, #iconid_on, #iconid_maintenance, #iconid_disabled', cond: [ { use_iconmap: 'checked', elementType: '0' }, { use_iconmap: 'checked', elementType: '3', subtypeHostGroupElements: 'checked' } ] }, { action: 'show', value: '#useIconMapRow', cond: [ { elementType: '0' }, { elementType: '3', subtypeHostGroupElements: 'checked' } ] }, { action: 'show', value: '#tags-select-row', cond: [ { elementType: '0' }, { elementType: '3' } ] } ]; this.active = false; this.sysmap = sysmap; this.formContainer = formContainer; // create form this.domNode = $(tpl.evaluate(formTplData)).appendTo(formContainer); // populate icons selects const select_icon_off = document.getElementById('iconid_off'); const select_icon_on = document.getElementById('iconid_on'); const select_icon_maintenance = document.getElementById('iconid_maintenance'); const select_icon_disabled = document.getElementById('iconid_disabled'); select_icon_on.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); select_icon_maintenance.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); select_icon_disabled.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); for (i in this.sysmap.iconList) { icon = this.sysmap.iconList[i]; select_icon_off.addOption({label: icon.name, value: icon.imageid}); select_icon_on.addOption({label: icon.name, value: icon.imageid}); select_icon_maintenance.addOption({label: icon.name, value: icon.imageid}); select_icon_disabled.addOption({label: icon.name, value: icon.imageid}); } // hosts $('#elementNameHost').multiSelectHelper({ id: 'elementNameHost', object_name: 'hosts', name: 'elementValue', selectedLimit: 1, popup: { parameters: { srctbl: 'hosts', srcfld1: 'hostid', dstfrm: 'selementForm', dstfld1: 'elementNameHost' } } }); // map $('#elementNameMap').multiSelectHelper({ id: 'elementNameMap', object_name: 'sysmaps', name: 'elementValue', selectedLimit: 1, popup: { parameters: { srctbl: 'sysmaps', srcfld1: 'sysmapid', dstfrm: 'selementForm', dstfld1: 'elementNameMap' } } }); // triggers $('#elementNameTriggers').multiSelectHelper({ id: 'elementNameTriggers', object_name: 'triggers', name: 'elementValue', objectOptions: { real_hosts: true }, popup: { parameters: { srctbl: 'triggers', srcfld1: 'triggerid', dstfrm: 'selementForm', dstfld1: 'elementNameTriggers', with_triggers: '1', real_hosts: '1', multiselect: '1' } } }); // host group $('#elementNameHostGroup').multiSelectHelper({ id: 'elementNameHostGroup', object_name: 'hostGroup', name: 'elementValue', selectedLimit: 1, popup: { parameters: { srctbl: 'host_groups', srcfld1: 'groupid', dstfrm: 'selementForm', dstfld1: 'elementNameHostGroup' } } }); this.actionProcessor = new ActionProcessor(formActions); this.actionProcessor.process(); } SelementForm.prototype = { /** * Shows element form. */ show: function() { this.formContainer.draggable('option', 'handle', '#formDragHandler'); this.formContainer.show(); this.domNode.show(); // Element must first be visible so that outerWidth() and outerHeight() are correct. this.formContainer.positionOverlayDialogue(); this.active = true; }, /** * Hides element form. */ hide: function() { this.domNode.toggle(false); this.active = false; }, /** * Adds element urls to form. * * @param {object} urls */ addUrls: function(urls) { var tpl = new Template($('#selementFormUrls').html()), i, url; if (typeof urls === 'undefined' || $.isEmptyObject(urls)) { urls = {empty: {}}; } for (i in urls) { url = urls[i]; // generate unique urlid url.selementurlid = $('#urlContainer tbody tr[id^=urlrow]').length; while ($('#urlrow_' + url.selementurlid).length) { url.selementurlid++; } $(tpl.evaluate(url)).appendTo('#urlContainer tbody'); } }, /** * Append form tag field options. * * @param {array} tags */ addTags: function(tags) { var tpl = new Template($('#tag-row-tmpl').html()), $add_btn_row = $('#selement-tags .element-table-add').closest('tr'), counter = $('#selement-tags').data('dynamicRows').counter; for (const i in tags) { var tag = jQuery.extend({tag: '', operator: 0, value: '', rowNum: ++counter}, tags[i]), row = $(tpl.evaluate(tag)); row.insertBefore($add_btn_row); row .find('[name="tags[' + tag.rowNum + '][tag]"]') .val(tag.tag); row .find('[name="tags[' + tag.rowNum + '][operator]"]') .val(tag.operator); row .find('[name="tags[' + tag.rowNum + '][value]"]') .val(tag.value); new CTagFilterItem(row[0]); } $('#selement-tags').data('dynamicRows').counter = counter; }, /** * Set evaltype field value. * * @param {integer} value */ setEvaltype: function(value) { $('#operator [name="evaltype"][value='+value+']').prop('checked', true); }, /** * Add triggers to the list. */ addTriggers: function(triggers) { var tpl = new Template($('#selementFormTriggers').html()), selected_triggers = $('#elementNameTriggers').multiSelect('getData'), triggerids = [], triggers_to_insert = []; if (typeof triggers === 'undefined' || $.isEmptyObject(triggers)) { triggers = []; } triggers = triggers.concat(selected_triggers); if (triggers) { triggers.forEach(function(trigger) { if ($('input[name^="element_id[' + trigger.id + ']"]').length == 0) { triggerids.push(trigger.id); triggers_to_insert[trigger.id] = { id: trigger.id, name: typeof trigger.prefix == 'undefined' ? trigger.name : trigger.prefix + trigger.name }; } }); if (triggerids.length != 0) { // get priority var ajaxUrl = new Curl('jsrpc.php'); ajaxUrl.setArgument('type', 11); $.ajax({ url: ajaxUrl.getUrl(), type: 'post', dataType: 'html', data: { method: 'trigger.get', triggerids: triggerids }, success: function(data) { data = JSON.parse(data); triggers.forEach(function(sorted_trigger) { data.result.forEach(function(trigger) { if (sorted_trigger.id == trigger.triggerid) { if ($('input[name^="element_id[' + trigger.triggerid + ']"]').length == 0) { trigger.name = triggers_to_insert[trigger.triggerid].name; $(tpl.evaluate(trigger)).appendTo('#triggerContainer tbody'); return false; } } }); }); SelementForm.prototype.recalculateSortOrder(); SelementForm.prototype.initSortable(); } }); } $('#elementNameTriggers').multiSelect('clean'); } }, /** * Set form controls with element fields values. * * @param {object} selement */ setValues: function(selement) { for (var elementName in selement) { $('[name=' + elementName + ']', this.domNode).val([selement[elementName]]); } // set default icon state if (empty(selement.iconid_on)) { $('[name=iconid_on]', this.domNode).val(0); } if (empty(selement.iconid_disabled)) { $('[name=iconid_disabled]', this.domNode).val(0); } if (empty(selement.iconid_maintenance)) { $('[name=iconid_maintenance]', this.domNode).val(0); } // clear urls $('#urlContainer tbody tr').remove(); this.addUrls(selement.urls); // Set tag properties. var tags = selement.tags; if (!tags || Object.getOwnPropertyNames(tags).length == 0) { tags = {0: {}}; } this.cleanTagsField(); this.addTags(tags); this.setEvaltype(selement.evaltype); // Iconmap. if (this.sysmap.data.iconmapid === '0') { $('#use_iconmap').prop({ checked: false, disabled: true }); } this.actionProcessor.process(); switch (selement.elementtype) { // host case '0': $('#elementNameHost').multiSelect('addData', [{ 'id': selement.elements[0].hostid, 'name': selement.elements[0].elementName }]); break; // map case '1': $('#elementNameMap').multiSelect('addData', [{ 'id': selement.elements[0].sysmapid, 'name': selement.elements[0].elementName }]); break; // trigger case '2': var triggers = []; for (i in selement.elements) { triggers[i] = {'id': selement.elements[i].triggerid, 'name': selement.elements[i].elementName}; } this.addTriggers(triggers); break; // host group case '3': $('#elementNameHostGroup').multiSelect('addData', [{ 'id': selement.elements[0].groupid, 'name': selement.elements[0].elementName }]); break; } }, /** * Remove tag filter rows from DOM. */ cleanTagsField: function() { $('#selement-tags .form_row').remove(); }, /** * Gets form values for element fields. * * @returns {Object|Boolean} */ getValues: function() { var values = $(':input', '#selementForm') .not(this.actionProcessor.hidden) .not('[name^="tags"]') .serializeArray(), data = { urls: {} }, i, urlPattern = /^url_(\d+)_(name|url)$/, url, urlNames = {}, elementsData = {}; for (i = 0; i < values.length; i++) { url = urlPattern.exec(values[i].name); if (url !== null) { if (typeof data.urls[url[1]] === 'undefined') { data.urls[url[1]] = {}; } data.urls[url[1]][url[2]] = values[i].value.toString(); } else { data[values[i].name] = values[i].value.toString(); } } if (data.elementtype == '0' || data.elementtype == '3') { data.tags = {}; $('input, z-select', '#selementForm').filter(function() { return this.name.match(/tags\[\d+\]\[tag\]/); }).each(function() { if (this.value !== '') { var nr = parseInt(this.name.match(/^tags\[(\d+)\]\[tag\]$/)[1]); data.tags[Object.getOwnPropertyNames(data.tags).length] = { tag: this.value, operator: $('[name="tags['+nr+'][operator]"]').val(), value: $('[name="tags['+nr+'][value]"]').val() }; } }); } data.elements = {}; // set element id and name switch (data.elementtype) { // host case '0': elementsData = $('#elementNameHost').multiSelect('getData'); if (elementsData.length != 0) { data.elements[0] = { hostid: elementsData[0].id, elementName: elementsData[0].name }; } break; // map case '1': elementsData = $('#elementNameMap').multiSelect('getData'); if (elementsData.length != 0) { data.elements[0] = { sysmapid: elementsData[0].id, elementName: elementsData[0].name }; } break; // triggers case '2': i = 0; $('input[name^="element_id"]').each(function() { data.elements[i] = { triggerid: $(this).val(), elementName: $('input[name^="element_name[' + $(this).val() + ']"]').val(), priority: $('input[name^="element_priority[' + $(this).val() + ']"]').val() }; i++; }); break; // host group case '3': elementsData = $('#elementNameHostGroup').multiSelect('getData'); if (elementsData.length != 0) { data.elements[0] = { groupid: elementsData[0].id, elementName: elementsData[0].name }; } break; } // validate urls for (i in data.urls) { if (data.urls[i].name === '' && data.urls[i].url === '') { delete data.urls[i]; continue; } if (data.urls[i].name === '' || data.urls[i].url === '') { alert(t('S_INCORRECT_ELEMENT_MAP_LINK')); return false; } if (typeof urlNames[data.urls[i].name] !== 'undefined') { alert(t('S_EACH_URL_SHOULD_HAVE_UNIQUE') + " '" + data.urls[i].name + "'."); return false; } urlNames[data.urls[i].name] = 1; } // validate element id if ($.isEmptyObject(data.elements) && data.elementtype !== '4') { switch (data.elementtype) { case '0': alert('Host is not selected.'); return false; case '1': alert('Map is not selected.'); return false; case '2': alert('Trigger is not selected.'); return false; case '3': alert('Host group is not selected.'); return false; } } return data; }, /** * Drag and drop trigger sorting. */ initSortable: function() { var triggerContainer = $('#triggerContainer'); triggerContainer.sortable({ disabled: (triggerContainer.find('tr.sortable').length < 2), items: 'tbody tr.sortable', axis: 'y', containment: 'parent', cursor: 'grabbing', handle: 'div.drag-icon', tolerance: 'pointer', opacity: 0.6, update: this.recalculateSortOrder, start: function(e, ui) { $(ui.placeholder).height($(ui.helper).height()); } }); }, /** * Sorting triggers by severity. */ recalculateSortOrder: function() { if ($('input[name^="element_id"]').length != 0) { var triggers = [], priority; $('input[name^="element_id"]').each(function() { priority = $('input[name^="element_priority[' + $(this).val() + ']"]').val() if (!triggers[priority]) { triggers[priority] = { 'priority': priority, 'html': $('#triggerrow_' + $(this).val())[0].outerHTML } } else { triggers[priority].html += $('#triggerrow_' + $(this).val())[0].outerHTML; } }); triggers.sort(function (a, b) { return b.priority - a.priority; }); $('#triggerContainer tbody').html(''); triggers.forEach(function(trigger) { $('#triggerContainer tbody').append(trigger.html); }); } } }; /** * Elements mass update form. * * @param {object} formContainer jQuery object * @param {object} sysmap */ function MassForm(formContainer, sysmap) { var i, icon, formActions = [ { action: 'enable', value: '#massLabel', cond: [{ chkboxLabel: 'checked' }] }, { action: 'enable', value: '#massLabelLocation', cond: [{ chkboxLabelLocation: 'checked' }] }, { action: 'enable', value: '#massUseIconmap', cond: [{ chkboxMassUseIconmap: 'checked' }] }, { action: 'enable', value: '#massIconidOff', cond: [{ chkboxMassIconidOff: 'checked' }] }, { action: 'enable', value: '#massIconidOn', cond: [{ chkboxMassIconidOn: 'checked' }] }, { action: 'enable', value: '#massIconidMaintenance', cond: [{ chkboxMassIconidMaintenance: 'checked' }] }, { action: 'enable', value: '#massIconidDisabled', cond: [{ chkboxMassIconidDisabled: 'checked' }] } ]; this.sysmap = sysmap; this.formContainer = formContainer; // create form var tpl = new Template($('#mapMassFormTpl').html()); this.domNode = $(tpl.evaluate()).appendTo(formContainer); // populate icons selects const select_icon_off = document.getElementById('massIconidOff'); const select_icon_on = document.getElementById('massIconidOn'); const select_icon_maintenance = document.getElementById('massIconidMaintenance'); const select_icon_disabled = document.getElementById('massIconidDisabled'); select_icon_on.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); select_icon_maintenance.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); select_icon_disabled.addOption({label: t('S_DEFAULT'), value: '0', class_name: ZBX_STYLE_DEFAULT_OPTION}); for (i in this.sysmap.iconList) { icon = this.sysmap.iconList[i]; select_icon_off.addOption({label: icon.name, value: icon.imageid}); select_icon_on.addOption({label: icon.name, value: icon.imageid}); select_icon_maintenance.addOption({label: icon.name, value: icon.imageid}); select_icon_disabled.addOption({label: icon.name, value: icon.imageid}); } document.getElementById('massLabelLocation').selectedIndex = 0; select_icon_off.selectedIndex = 0 select_icon_on.selectedIndex = 0 select_icon_maintenance.selectedIndex = 0 select_icon_disabled.selectedIndex = 0 this.actionProcessor = new ActionProcessor(formActions); this.actionProcessor.process(); } MassForm.prototype = { /** * Show mass update form. */ show: function() { this.formContainer.draggable('option', 'handle', '#massDragHandler'); this.formContainer.show(); this.domNode.show(); // Element must first be visible so that outerWidth() and outerHeight() are correct. this.formContainer.positionOverlayDialogue(); this.updateList(); }, /** * Hide mass update form. */ hide: function() { this.domNode.toggle(false); $(':checkbox', this.domNode).prop('checked', false); $('z-select', this.domNode).each(function() { this.selectedIndex = 0; }); $('textarea', this.domNode).val(''); this.actionProcessor.process(); }, /** * Get values from mass update form that should be updated in all selected elements. * * @return array */ getValues: function() { var values = $('#massForm').serializeArray(), data = {}, i, ln; for (i = 0, ln = values.length; i < ln; i++) { // special case for use iconmap checkbox, because unchecked checkbox is not submitted with form if (values[i].name === 'chkbox_use_iconmap') { data['use_iconmap'] = '0'; } if (values[i].name.match(/^chkbox_/) !== null) { continue; } data[values[i].name] = values[i].value.toString(); } return data; }, /** * Updates list of selected elements in mass update form. */ updateList: function() { var tpl = new Template($('#mapMassFormListRow').html()), id, list = [], element, elementTypeText, i, ln; $('#massList tbody').empty(); for (id in this.sysmap.selection.selements) { element = this.sysmap.selements[id]; switch (element.data.elementtype) { case '0': elementTypeText = t('S_HOST'); break; case '1': elementTypeText = t('S_MAP'); break; case '2': elementTypeText = t('S_TRIGGER'); break; case '3': elementTypeText = t('S_HOST_GROUP'); break; case '4': elementTypeText = t('S_IMAGE'); break; } list.push({ elementType: elementTypeText, elementName: element.getName().replace(/&/g,'&').replace(//g,'>') .replace(/\"/g,'"').replace(/\'/g,''') }); } // sort by element type and then by element name list.sort(function(a, b) { var elementTypeA = a.elementType.toLowerCase(), elementTypeB = b.elementType.toLowerCase(), elementNameA, elementNameB; if (elementTypeA < elementTypeB) { return -1; } if (elementTypeA > elementTypeB) { return 1; } elementNameA = a.elementName.toLowerCase(); elementNameB = b.elementName.toLowerCase(); if (elementNameA < elementNameB) { return -1; } if (elementNameA > elementNameB) { return 1; } return 0; }); for (i = 0, ln = list.length; i < ln; i++) { $(tpl.evaluate(list[i])).appendTo('#massList tbody'); } } }; /** * Form for shape editing. * * @param {object} formContainer jQuery object * @param {object} sysmap */ function ShapeForm(formContainer, sysmap) { this.sysmap = sysmap; this.formContainer = formContainer; this.triggerids = {}; this.domNode = $(new Template($('#mapShapeFormTpl').html()).evaluate()).appendTo(formContainer); this.domNode.find('.color-picker input').colorpicker(); } ShapeForm.prototype = { /** * Show form. */ show: function() { this.formContainer.draggable('option', 'handle', '#shapeDragHandler'); this.formContainer.show(); this.domNode.show(); // Element must first be visible so that outerWidth() and outerHeight() are correct. this.formContainer.positionOverlayDialogue(); this.active = true; }, /** * Hides element form. */ hide: function() { this.domNode.toggle(false); this.active = false; }, /** * Set form controls with shape fields values. * * @param {object} shape */ setValues: function(shape) { for (var field in shape) { $('[name=' + field + ']', this.domNode).val([shape[field]]); } $('.color-picker input', this.domNode).change(); $('#border_type').change(); $('#last_shape_type').val(shape.type); $('input[type=radio][name=type]:checked').change(); }, /** * Get values from shape update form that should be updated * * @return array */ getValues: function() { var values = $('#shapeForm').serializeArray(), data = {}, i, ln, min_size, width = parseInt(this.sysmap.data.width), height = parseInt(this.sysmap.data.height); for (i = 0, ln = values.length; i < ln; i++) { data[values[i].name] = values[i].value.toString(); } data.x = parseInt(data.x, 10); data.y = parseInt(data.y, 10); data.width = parseInt(data.width, 10); data.height = parseInt(data.height, 10); data.x = isNaN(data.x) || (data.x < 0) ? 0 : data.x; data.y = isNaN(data.y) || (data.y < 0) ? 0 : data.y; min_size = (data.type != SVGMapShape.TYPE_LINE) ? 1 : 0; data.width = isNaN(data.width) || (data.width < min_size) ? min_size : data.width; data.height = isNaN(data.height) || (data.height < min_size) ? min_size : data.height; data.x = (data.x >= width) ? width : data.x; data.y = (data.y >= height) ? height : data.y; data.width = (data.width >= width) ? width : data.width; data.height = (data.height >= height) ? height : data.height; return data; } }; /** * Form for shape editing. * * @param {object} formContainer jQuery object * @param {object} sysmap */ function MassShapeForm(formContainer, sysmap) { var formActions = []; var mapping = { chkboxType: '[name="mass_type"]', chkboxText: '#mass_text', chkboxFont: '#mass_font', chkboxFontSize: '#mass_font_size', chkboxFontColor: '#mass_font_color', chkboxTextHalign: '#mass_text_halign', chkboxTextValign: '#mass_text_valign', chkboxBackground: '#mass_background_color', chkboxBorderType: '#mass_border_type', chkboxBorderWidth: '#mass_border_width', chkboxBorderColor: '#mass_border_color' }; Object.keys(mapping).forEach(function (key) { var condition = {}; condition[key] = 'checked'; formActions.push({ action: 'enable', value: mapping[key], cond: [condition] }); }); this.sysmap = sysmap; this.formContainer = formContainer; this.triggerids = {}; this.domNode = $(new Template($('#mapMassShapeFormTpl').html()).evaluate()).appendTo(formContainer); this.domNode.find('.color-picker input').colorpicker(); this.actionProcessor = new ActionProcessor(formActions); this.actionProcessor.process(); } MassShapeForm.prototype = { /** * Show form. */ show: function(figures) { var value = figures ? 0 : 2; $('.shape_figure_row', this.domNode).toggle(figures); $('.switchable-content', this.domNode).each(function (i, element) { element.textContent = element.hasAttribute('data-value-' + value) ? element.getAttribute('data-value-' + value) : element.getAttribute('data-value'); }); this.formContainer.draggable('option', 'handle', '#massShapeDragHandler'); this.formContainer.show(); this.domNode.show(); // Element must first be visible so that outerWidth() and outerHeight() are correct. this.formContainer.positionOverlayDialogue(); this.active = true; }, /** * Hides element form. */ hide: function() { this.domNode.toggle(false); this.active = false; $(':checkbox', this.domNode).prop('checked', false).prop("disabled", false); $('textarea, input[type=text]', this.domNode).val(''); $('.color-picker input', this.domNode).change(); this.actionProcessor.process(); }, /** * Get values from mass update form that should be updated in all selected shapes. * * @return array */ getValues: function() { var values = $('#massShapeForm').serializeArray(), data = {}, i, ln; for (i = 0, ln = values.length; i < ln; i++) { if (values[i].name.match(/^mass_/) !== null) { data[values[i].name.substring("mass_".length)] = values[i].value.toString(); } } return data; } }; /** * Form for editin links. * * @param {object} formContainer jQuesry object * @param {object} sysmap */ function LinkForm(formContainer, sysmap) { this.sysmap = sysmap; this.formContainer = formContainer; this.triggerids = {}; this.domNode = $(new Template($('#linkFormTpl').html()).evaluate()).appendTo(formContainer); this.domNode.find('.color-picker input').colorpicker(); } LinkForm.prototype = { /** * Show form. */ show: function() { this.domNode.show(); $('.element-edit-control').prop('disabled', true); }, /** * Hide form. */ hide: function() { $('#linkForm').hide(); $('.element-edit-control').prop('disabled', false); }, /** * Get form values for link fields. */ getValues: function() { var values = $('#linkForm').serializeArray(), data = { linktriggers: {} }, i, ln, linkTriggerPattern = /^linktrigger_(\w+)_(triggerid|linktriggerid|drawtype|color|desc_exp)$/, colorPattern = /^[0-9a-f]{6}$/i, linkTrigger; for (i = 0, ln = values.length; i < ln; i++) { linkTrigger = linkTriggerPattern.exec(values[i].name); if (linkTrigger !== null) { if (linkTrigger[2] == 'color' && !values[i].value.toString().match(colorPattern)) { throw sprintf(t('S_COLOR_IS_NOT_CORRECT'), values[i].value); } if (typeof data.linktriggers[linkTrigger[1]] === 'undefined') { data.linktriggers[linkTrigger[1]] = {}; } data.linktriggers[linkTrigger[1]][linkTrigger[2]] = values[i].value.toString(); } else { if (values[i].name == 'color' && !values[i].value.toString().match(colorPattern)) { throw sprintf(t('S_COLOR_IS_NOT_CORRECT'), values[i].value); } data[values[i].name] = values[i].value.toString(); } } return data; }, /** * Update form controls with values from link. * * @param {object} link */ setValues: function(link) { var selement1, tmp, selementid, selement, elementName, optgroups = {}, optgroupType, optgroupLabel, optgroup, i, ln; /* * If only one element is selected, make sure that element1 is equal to the selected element and * element2 - to the connected. */ if (this.sysmap.selection.count.selements === 1 && this.sysmap.selection.count.shapes === 0) { // get currently selected element for (selementid in this.sysmap.selection.selements) { selement1 = this.sysmap.selements[selementid]; } if (selement1.id !== link.selementid1) { tmp = link.selementid1; link.selementid1 = selement1.id; link.selementid2 = tmp; } } // populate list of elements to connect with const connect_to_select = document.createElement('z-select'); connect_to_select._button.id = 'label-selementid2'; connect_to_select.id = 'selementid2'; connect_to_select.name = 'selementid2'; // sort by type for (selementid in this.sysmap.selements) { selement = this.sysmap.selements[selementid]; if (selement.id == link.selementid1) { continue; } if (optgroups[selement.data.elementtype] === void(0)) { optgroups[selement.data.elementtype] = []; } optgroups[selement.data.elementtype].push(selement); } for (optgroupType in optgroups) { switch (optgroupType) { case '0': optgroupLabel = t('S_HOST'); break; case '1': optgroupLabel = t('S_MAP'); break; case '2': optgroupLabel = t('S_TRIGGER'); break; case '3': optgroupLabel = t('S_HOST_GROUP'); break; case '4': optgroupLabel = t('S_IMAGE'); break; } optgroup = {label: optgroupLabel, options: []}; for (i = 0, ln = optgroups[optgroupType].length; i < ln; i++) { optgroup.options.push({ value: optgroups[optgroupType][i].id, label: optgroups[optgroupType][i].getName() }); } connect_to_select.addOptionGroup(optgroup); } $('#selementid2').replaceWith(connect_to_select); // set values for form elements for (elementName in link) { $('[name=' + elementName + ']', this.domNode).val(link[elementName]); } // clear triggers this.triggerids = {}; $('#linkTriggerscontainer tbody tr').remove(); this.addLinkTriggers(link.linktriggers); }, /** * Add link triggers to link form. * * @param {object} triggers */ addLinkTriggers: function(triggers) { var tpl = new Template($('#linkTriggerRow').html()), linkTrigger, table = $('#linkTriggerscontainer tbody'); for (linkTrigger in triggers) { this.triggerids[triggers[linkTrigger].triggerid] = linkTrigger; $(tpl.evaluate(triggers[linkTrigger])).appendTo(table); $('#linktrigger_' + triggers[linkTrigger].linktriggerid + '_drawtype') .val(triggers[linkTrigger].drawtype); } table.find('.color-picker input').colorpicker(); $('.color-picker input', this.domNode).change(); }, /** * Add new triggers which were selected in popup to trigger list. * * @param {object} triggers */ addNewTriggers: function(triggers) { var tpl = new Template($('#linkTriggerRow').html()), linkTrigger = { color: 'DD0000' }, linktriggerid, i, ln, table = $('#linkTriggerscontainer tbody'); for (i = 0, ln = triggers.length; i < ln; i++) { if (typeof this.triggerids[triggers[i].triggerid] !== 'undefined') { continue; } linktriggerid = getUniqueId(); // store linktriggerid to generate every time unique one this.sysmap.allLinkTriggerIds[linktriggerid] = true; // store triggerid to forbid selecting same trigger twice this.triggerids[triggers[i].triggerid] = linktriggerid; linkTrigger.linktriggerid = linktriggerid; linkTrigger.desc_exp = triggers[i].description; linkTrigger.triggerid = triggers[i].triggerid; $(tpl.evaluate(linkTrigger)).appendTo(table); } table.find('.color-picker input').colorpicker(); $('.color-picker input', this.domNode).change(); }, /** * Updates links list for element. * * @param {string} selementIds */ updateList: function(selementIds) { var links = this.sysmap.getLinksBySelementIds(selementIds), linkTable, rowTpl, list, i, j, selement, tmp, ln, link, linktriggers, fromElementName, toElementName; $('.element-links').hide(); $('.element-links tbody').empty(); if (links.length) { $('#mapLinksContainer').show(); if (objectSize(selementIds) > 1) { rowTpl = '#massElementLinkTableRowTpl'; linkTable = $('#mass-element-links'); } else { rowTpl = '#elementLinkTableRowTpl'; linkTable = $('#element-links'); } rowTpl = new Template($(rowTpl).html()); list = []; for (i = 0, ln = links.length; i < ln; i++) { link = this.sysmap.links[links[i]].data; /* * If one element selected and it's not link.selementid1, we need to swap link.selementid1 * and link.selementid2 in order that sorting works correctly. */ if (objectSize(selementIds) == 1 && !selementIds[link.selementid1]) { // Get currently selected element. for (var selementId in this.sysmap.selection.selements) { selement = this.sysmap.selements[selementId]; } if (selement.id !== link.selementid1) { tmp = link.selementid1; link.selementid1 = selement.id; link.selementid2 = tmp; } } linktriggers = []; for (var linktrigger in link.linktriggers) { linktriggers.push(link.linktriggers[linktrigger].desc_exp); } fromElementName = this.sysmap.selements[link.selementid1].getName(); toElementName = this.sysmap.selements[link.selementid2].getName(); list.push({ fromElementName: fromElementName, toElementName: toElementName, linkid: link.linkid, linktriggers: linktriggers }); } // Sort by "from" element and then by "to" element. list.sort(function(a, b) { var fromElementA = a.fromElementName.toLowerCase(), fromElementB = b.fromElementName.toLowerCase(), toElementA = a.toElementName.toLowerCase(), toElementB = b.toElementName.toLowerCase(), linkIdA = a.linkid, linkIdB = b.linkid; if (fromElementA < fromElementB) { return -1; } else if (fromElementA > fromElementB) { return 1; } if (toElementA < toElementB) { return -1; } else if (toElementA > toElementB) { return 1; } if (linkIdA < linkIdB) { return -1; } else if (linkIdA > linkIdB) { return 1; } return 0; }); for (i = 0, ln = list.length; i < ln; i++) { var row = $(rowTpl.evaluate(list[i])), row_urls = $('.element-urls', row); for (j = 0; j < list[i].linktriggers.length; j++) { if (j != 0) { row_urls.append($('
')); } row_urls.append($('').text(list[i].linktriggers[j])); } row.appendTo(linkTable.find('tbody')); } linkTable.closest('.element-links').show(); } else { $('#mapLinksContainer').hide(); } } }; var sysmap = new CMap(containerId, mapData); Shape.prototype.bind('afterMove', function(event, element) { if (sysmap.selection.count.shapes === 1 && sysmap.selection.count.selements === 0 && sysmap.selection.shapes[element.id] !== void(0)) { $('#shapeX').val(element.data.x); $('#shapeY').val(element.data.y); if (typeof element.data.width !== 'undefined') { $('#shapeForm input[name=width]').val(element.data.width); } if (typeof element.data.height !== 'undefined') { $('#shapeForm input[name=height]').val(element.data.height); } } sysmap.updateImage(); }); Selement.prototype.bind('afterMove', function(event, element) { if (sysmap.selection.count.selements === 1 && sysmap.selection.count.shapes === 0 && sysmap.selection.selements[element.id] !== void(0)) { $('#x').val(element.data.x); $('#y').val(element.data.y); if (typeof element.data.width !== 'undefined') { $('#areaSizeWidth').val(element.data.width); } if (typeof element.data.height !== 'undefined') { $('#areaSizeHeight').val(element.data.height); } } if (sysmap.buffered_expand === false) { sysmap.updateImage(); } }); return sysmap; } return { object: null, run: function(containerId, mapData) { if (this.object !== null) { throw new Error('Map has already been run.'); } this.object = createMap(containerId, mapData); } }; }(jQuery)); jQuery(function ($) { /* * Reposition the overlay dialogue window. The previous position is remembered using offset(). Each time overlay * dialogue is opened, it could have different content (shape form, element form etc) and different size, so the * new top and left position must be calculated. If the overlay dialogue is opened for the first time, position is * set depending on map size and canvas top position. This makes map more visible at first. In case popup window is * dragged outside visible view port or window is resized, popup will again be repositioned so it doesn't go outside * the viewport. In case the popup is too large, position it with a small margin depending on whether is too long * or too wide. */ $.fn.positionOverlayDialogue = function () { var $map = $('#map-area'), map_offset = $map.offset(), map_margin = 10, $dialogue = $(this), $dialogue_host = $dialogue.offsetParent(), dialogue_host_offset = $dialogue_host.offset(), // Usable area relative to host. dialogue_host_x_min = $dialogue_host.scrollLeft(), dialogue_host_x_max = Math.min($dialogue_host[0].scrollWidth, $(window).width() + $(window).scrollLeft() - dialogue_host_offset.left + $dialogue_host.scrollLeft() ) - 1, dialogue_host_y_min = $dialogue_host.scrollTop(), dialogue_host_y_max = Math.min($dialogue_host[0].scrollHeight, $(window).height() + $(window).scrollTop() - dialogue_host_offset.top + $dialogue_host.scrollTop() ) - 1, // Coordinates of map's top right corner relative to dialogue host. pos_x = map_offset.left + $map[0].scrollWidth - dialogue_host_offset.left + $dialogue_host.scrollLeft(), pos_y = map_offset.top - map_margin - dialogue_host_offset.top + $dialogue_host.scrollTop(); return this.css({ left: Math.max(dialogue_host_x_min, Math.min(dialogue_host_x_max - $dialogue.outerWidth(), pos_x)), top: Math.max(dialogue_host_y_min, Math.min(dialogue_host_y_max - $dialogue.outerHeight(), pos_y)) }); }; });