/* ** 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. **/ class CWidgetGeoMap extends CWidget { static SEVERITY_NO_PROBLEMS = -1; static SEVERITY_NOT_CLASSIFIED = 0; static SEVERITY_INFORMATION = 1; static SEVERITY_WARNING = 2; static SEVERITY_AVERAGE = 3; static SEVERITY_HIGH = 4; static SEVERITY_DISASTER = 5; onInitialize() { this._map = null; this._icons = {}; this._initial_load = true; this._home_coords = {}; this._severity_levels = new Map(); } getUpdateRequestData() { return { ...super.getUpdateRequestData(), initial_load: this._initial_load ? 1 : 0, unique_id: this._unique_id }; } setContents(response) { if (this._initial_load) { super.setContents(response); } if (response.geomap !== undefined) { if (response.geomap.config !== undefined) { this._initMap(response.geomap.config); } this._addMarkers(response.geomap.hosts); } this._initial_load = false; } updateProperties({name, view_mode, fields}) { this._initial_load = true; super.updateProperties({name, view_mode, fields}); } _addMarkers(hosts) { this._markers.clearLayers(); this._clusters.clearLayers(); this._markers.addData(hosts); this._clusters.addLayer(this._markers); } _initMap(config) { const latLng = new L.latLng([config.center.latitude, config.center.longitude]); this._home_coords = config.home_coords; // Initialize map and load tile layer. this._map = L.map(this._unique_id).setView(latLng, config.center.zoom); L.tileLayer(config.tile_url, { tap: false, minZoom: 0, maxZoom: parseInt(config.max_zoom, 10), minNativeZoom: 1, maxNativeZoom: parseInt(config.max_zoom, 10), attribution: config.attribution }).addTo(this._map); this.initSeverities(config.colors); // Create cluster layer. this._clusters = this._createClusterLayer(); this._map.addLayer(this._clusters); // Create markers layer. this._markers = L.geoJSON([], { pointToLayer: function (point, ll) { return L.marker(ll, { icon: this._icons[point.properties.severity] }); }.bind(this) }); this._map.setDefaultView(latLng, config.center.zoom); // Severity filter. this._map.severityFilterControl = L.control.severityFilter({ position: 'topright', checked: config.filter.severity, severity_levels: this._severity_levels, disabled: !this._widgetid }).addTo(this._map); // Navigate home btn. this._map.navigateHomeControl = L.control.navigateHomeBtn({position: 'topleft'}).addTo(this._map); if (Object.keys(this._home_coords).length > 0) { const home_btn_title = ('default' in this._home_coords) ? t('Navigate to default view') : t('Navigate to initial view'); this._map.navigateHomeControl.setTitle(home_btn_title); this._map.navigateHomeControl.show(); } // Workaround to prevent dashboard jumping to make map completely visible. this._map.getContainer().focus = () => {}; // Add event listeners. this._map.getContainer().addEventListener('click', (e) => { if (e.target.classList.contains('leaflet-container')) { this._map.severityFilterControl.close(); } }, false); this._map.getContainer().addEventListener('filter', (e) => { this.removeHintBoxes(); this.updateFilter(e.detail.join(',')); }, false); this._map.getContainer().addEventListener('cluster.click', (e) => { const cluster = e.detail; const node = cluster.originalEvent.srcElement.classList.contains('marker-cluster') ? cluster.originalEvent.srcElement : cluster.originalEvent.srcElement.closest('.marker-cluster'); if ('hintBoxItem' in node) { return; } const container = this._map._container; const style = 'left: 0px; top: 0px;'; const content = document.createElement('div'); content.style.overflow = 'auto'; content.style.maxHeight = (cluster.originalEvent.clientY-60)+'px'; content.style.display = 'block'; content.appendChild(this.makePopupContent(cluster.layer.getAllChildMarkers().map(o => o.feature))); node.hintBoxItem = hintBox.createBox(e, node, content, '', true, style, container.parentNode); const cluster_bounds = cluster.originalEvent.target.getBoundingClientRect(); const hintbox_bounds = this._target.getBoundingClientRect(); let x = cluster_bounds.left + cluster_bounds.width / 2 - hintbox_bounds.left; let y = cluster_bounds.top - hintbox_bounds.top - 10; node.hintBoxItem.position({ of: node.hintBoxItem, my: 'center bottom', at: `left+${x}px top+${y}px`, collision: 'fit' }); Overlay.prototype.recoverFocus.call({'$dialogue': node.hintBoxItem}); Overlay.prototype.containFocus.call({'$dialogue': node.hintBoxItem}); }); this._markers.on('click keypress', (e) => { const node = e.originalEvent.srcElement; if ('hintBoxItem' in node) { return; } if (e.type === 'keypress') { if (e.originalEvent.key !== ' ' && e.originalEvent.key !== 'Enter') { return; } e.originalEvent.preventDefault(); } const container = this._map._container; const content = this.makePopupContent([e.layer.feature]); const style = 'left: 0px; top: 0px;'; node.hintBoxItem = hintBox.createBox(e, node, content, '', true, style, container.parentNode); const marker_bounds = e.originalEvent.target.getBoundingClientRect(); const hintbox_bounds = this._target.getBoundingClientRect(); let x = marker_bounds.left + marker_bounds.width / 2 - hintbox_bounds.left; let y = marker_bounds.top - hintbox_bounds.top - 10; node.hintBoxItem.position({ of: node.hintBoxItem, my: 'center bottom', at: `left+${x}px top+${y}px`, collision: 'fit' }); Overlay.prototype.recoverFocus.call({'$dialogue': node.hintBoxItem}); Overlay.prototype.containFocus.call({'$dialogue': node.hintBoxItem}); }); this._map.getContainer().addEventListener('cluster.dblclick', (e) => { e.detail.layer.zoomToBounds({padding: [20, 20]}); }); this._map.getContainer().addEventListener('contextmenu', (e) => { if (e.target.classList.contains('leaflet-container')) { const $obj = $(e.target); const menu = [{ label: t('Actions'), items: [{ label: t('Set this view as default'), clickCallback: this.updateDefaultView.bind(this), disabled: !this._widgetid }, { label: t('Reset to initial view'), clickCallback: this.unsetDefaultView.bind(this), disabled: !('default' in this._home_coords) }] }]; $obj.menuPopup(menu, e, { position: { of: $obj, my: 'left top', at: 'left+'+e.layerX+' top+'+e.layerY, collision: 'fit' } }); } e.preventDefault(); }); // Close opened hintboxes when moving/zooming/resizing widget. this._map.on('zoomstart movestart resize', () => { this.removeHintBoxes(); }); } /** * Function to create cluster layer. * * @returns {CWidgetGeoMap._createClusterLayer.clusters|L.MarkerClusterGroup} */ _createClusterLayer() { const clusters = L.markerClusterGroup({ showCoverageOnHover: false, zoomToBoundsOnClick: false, removeOutsideVisibleBounds: true, spiderfyOnMaxZoom: false, iconCreateFunction: (cluster) => { const max_severity = Math.max(...cluster.getAllChildMarkers().map(p => p.feature.properties.severity)); const color = this._severity_levels.get(max_severity).color; return new L.DivIcon({ html: `
${t('Host')} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_DISASTER).abbr} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_HIGH).abbr} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_AVERAGE).abbr} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_WARNING).abbr} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_INFORMATION).abbr} | ${this._severity_levels.get(CWidgetGeoMap.SEVERITY_NOT_CLASSIFIED).abbr} |
---|