diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:24:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:24:14 +0000 |
commit | a58a146ad5cc0079fd68431cd679943f933e35a4 (patch) | |
tree | 91608c01e82676102457ffff2219f321ec474657 /public/js/module.js | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-map-upstream.tar.xz icingaweb2-module-map-upstream.zip |
Adding upstream version 1.1.0.upstream/1.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'public/js/module.js')
-rw-r--r-- | public/js/module.js | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/public/js/module.js b/public/js/module.js new file mode 100644 index 0000000..8201104 --- /dev/null +++ b/public/js/module.js @@ -0,0 +1,628 @@ +(function (Icinga) { + + function colorMarker(worstState, icon) { + var markerColor = 'awesome-marker'; + + // TODO: Different marker icon for not-OK states + // if(worstState > 0) { + // markerColor = markerColor + ' awesome-marker-square'; + // } + + var marker = L.AwesomeMarkers.icon({ + icon: icon, + markerColor: state2color(worstState), + className: markerColor + }); + + return marker + } + + function state2color(state) { + switch (parseInt(state)) { + case 0: + return "green"; + case 1: + return "orange"; + case 2: + return "red"; + case 3: + return "purple"; + default: + return "blue"; + } + } + + function isFilterParameter(parameter) { + return (parameter.charAt(0) === '(' || parameter.match('^[_]{0,1}(host|service)') || parameter.match('^(object|state)Type') || parameter.match('^problems')); + } + + function getParameters(id) { + var params = decodeURIComponent($('#map-' + id).closest('.module-map').data('icingaUrl')).split('&'); + + // remove module path from url parameters + if (params.length > 0) { + params[0] = params[0].replace(/^.*\?/, '') + } + + return params + } + + function unique(list) { + var result = []; + $.each(list, function (i, e) { + if ($.inArray(e, result) == -1) result.push(e); + }); + return result; + } + + function filterParams(id, extra) { + var sURLVariables = getParameters(id); + var params = [], + i; + + if (extra !== undefined) { + sURLVariables = $.merge(extra.split('&'), sURLVariables); + sURLVariables = unique(sURLVariables); + } + + for (i = 0; i < sURLVariables.length; i++) { + // Protect Icinga filter syntax + if (isFilterParameter(sURLVariables[i])) { + params.push(sURLVariables[i]); + continue; + } + } + + return params.join("&") + } + + function showDefaultView() { + if (map_default_lat !== null && map_default_long !== null) { + if (map_default_zoom !== null) { + cache[id].map.setView([map_default_lat, map_default_long], map_default_zoom); + } else { + cache[id].map.setView([map_default_lat, map_default_long]); + } + } else { + cache[id].map.fitWorld() + } + } + + function toggleFullscreen() { + icinga.ui.toggleFullscreen(); + cache[id].map.invalidateSize(); + cache[id].fullscreen = !cache[id].fullscreen; + if (cache[id].fullscreen) { + $('.controls').hide(); + } else { + $('.controls').show(); + } + } + + // TODO: Allow update of multiple parameters + function updateUrl(pkey, pvalue) { + // Don't update URL if in dashlet mode + if (dashlet) { + return; + } + + var $target = $('.module-map'); + var $currentUrl = $target.data('icingaUrl'); + var basePath = $currentUrl.replace(/\?.*$/, ''); + var searchPath = $currentUrl.replace(/^.*\?/, ''); + + var sURLVariables = (searchPath === basePath ? [] : searchPath.split('&')); + + var updated = false; + for (var i = 0; i < sURLVariables.length; i++) { + // Don't replace Icinga filters + if (isFilterParameter(sURLVariables[i])) { + continue; + } + + var tmp = sURLVariables[i].split('='); + if (tmp[0] == pkey) { + sURLVariables[i] = tmp[0] + '=' + pvalue; + updated = true; + break; + } + } + + // Parameter is to be added + if (!updated) { + sURLVariables.push(pkey + "=" + pvalue); + } + + $target.data('icingaUrl', basePath + '?' + sURLVariables.join('&')); + icinga.history.pushCurrentState(); + } + + function getWorstState(states) { + var worstState = 0; + var allPending = -1; + var allUnknown = -1; + var last = -1; + + if (states.length == 1) { + return states[0]; + } + + for (var i = 0, len = states.length; i < len; i++) { + var state = states[i]; + if (state < 3) { + if (allPending == 1) { + allPending = 0; + } else if (allUnknown == 1) { + allUnknown = 0; + } + } + + if (state > 2) { + // PENDING + if (state == 99) { + if (allPending < 0 && last < 0) { + allPending = 1; + } + + // OK -> PENDING -> UNKNOWN -> WARNING -> CRITICAL + state = 0.25; + } + + // UNKNOWN + if (state == 3) { + if (allUnknown < 0 && last < 0) { + allUnknown = 1; + } + + // OK -> PENDING -> UNKNOWN -> WARNING -> CRITICAL + state = 0.5; + } + } + + if (state > worstState) { + worstState = state; + } + + last = state; + } + + if (allPending == 1) { + worstState = 99; + } + + if (allUnknown == 1) { + worstState = 3; + } + + // Restore PENDING and UNKNOWN + if (worstState == 0.25) { + worstState = 99; + } else if (worstState == 0.5) { + worstState = 3; + } + + return worstState; + } + + function mapCenter(hostname) { + if (cache[id].hostMarkers[hostname]) { + var el = cache[id].hostMarkers[hostname]; + cache[id].map.panTo(cache[id].hostMarkers[hostname].getLatLng()) + } + } + + var cache = {}; + + var Map = function (module) { + this.module = module; + this.initialize(); + this.timer; + this.module.icinga.logger.debug('Map module loaded'); + }; + + Map.prototype = { + + initialize: function () { + this.timer = {}; + this.module.on('rendered', this.onRenderedContainer); + this.registerTimer() + }, + + registerTimer: function (id) { + this.timer = this.module.icinga.timer.register( + this.updateAllMapData, + this, + 10000 + ); + return this; + }, + + removeTimer: function (id) { + this.module.icinga.timer.unregister(this.timer); + return this + }, + + onPopupOpen: function (evt) { + $('.detail-link').on("click", function (ievt) { + mapCenter(evt.popup._source.options.id); + cache[id].map.invalidateSize(); + }); + }, + + updateAllMapData: function () { + var _this = this; + + if (cache.length == 0) { + this.removeTimer(id) + return this + } + + $.each(cache, function (id) { + if (!$('#map-' + id).length) { + delete cache[id] + } else { + _this.updateMapData({id: id}) + } + }); + }, + + updateMapData: function (parameters) { + var id = parameters.id; + var show_host = parameters.show_host; + var $that = this; + + function showHost(hostname) { + if (cache[id].hostMarkers[hostname]) { + var el = cache[id].hostMarkers[hostname]; + cache[id].markers.zoomToShowLayer(el, function () { + el.openPopup(); + }) + } + } + + function removeOldMarkers(id, data) { + // remove old markers + $.each(cache[id].hostMarkers, function (identifier, d) { + if ((data['hosts'] && !data['hosts'][identifier]) && (data['services'] && !data['services'][identifier])) { + cache[id].markers.removeLayer(d); + delete cache[id].hostMarkers[identifier]; + } + }); + } + + function errorMessage(msg) { + cache[id].map.spin(false); + $map = cache[id].map; + $map.openModal({ + content: "<p>Could not fetch data from API:</p><pre>" + msg + "</pre>", + onShow: function (evt) { + $that.removeTimer(id) + }, + onHide: function (evt) { + $that.registerTimer(id); + } + }); + } + + function processData(json) { + if (json['message']) { + errorMessage(json['message']); + return; + } + removeOldMarkers(id, json); + + $.each(json, function (type, element) { + $.each(element, function (identifier, data) { + if (data.length < 1 || data['coordinates'] == "") { + console.log('found empty coordinates: ' + data) + return true + } + + var states = []; + var icon; + var services; + var worstState; + var display_name = (data['host_display_name'] ? data['host_display_name'] : hostname); + + if (type === 'hosts') { + states.push((data['host_state'] == 1 ? 2 : data['host_state'])) + } + + services = '<div class="map-popup-services">'; + services += '<h1><span class="icon-services"></span> Services</h1>'; + services += '<div class="scroll-view">'; + services += '<table class="icinga-module module-monitoring">'; + services += '<tbody>'; + + $.each(data['services'], function (service_display_name, service) { + states.push(service['service_state']); + + service_handled = ""; + if ((data['host_state'] == 1 && data['host_acknowledged']) || (service['service_acknowledged'] == 1 || service['service_in_downtime'] == 1)) { + service_handled = " handled"; + } + + services += '<tr>'; + + services += '<td class="'; + services += "state-col"; + services += " state-" + service_status[service['service_state']][1].toLowerCase(); + services += "" + service_handled; + services += '">'; + services += '<div class="state-label">'; + services += service_status[service['service_state']][0]; + services += '</div>'; + services += '</td>'; + + services += '<td>'; + services += '<div class="state-header">'; + services += '<a data-hostname="' + data['host_name'] + '" data-base-target="_next" href="' + + icinga.config.baseUrl + + '/monitoring/service/show?host=' + + data['host_name'] + + '&service=' + + service['service_name'] + + '">'; + services += service_display_name; + services += '</a>' + services += '</div>' + services += '</td>'; + + services += '</tr>'; + + if (type === 'services') { + display_name = service_display_name + " (" + display_name + ")"; + } + }); + + services += '</tbody>'; + services += '</table>'; + services += '</div>'; + services += '</div>'; + + worstState = getWorstState(states); + + var marker_icon = (type === 'hosts' ? 'host' : 'service'); + if (data['icon']) { + marker_icon = data['icon']; + } + + icon = colorMarker(worstState, marker_icon); + + var host_icon = ""; + if (data['host_icon_image'] != "") { + host_icon = '<img src="' + icinga.config.baseUrl + '/img/icons/' + + data['host_icon_image'] + + '"' + + ((data['host_icon_image_alt'] != "") ? ' alt="' + data['host_icon_image_alt'] + '"' : '') + + ' class="host-icon-image icon">'; + } + + var host_status = type === 'hosts' && data['host_state'] == 1 ? "<div id=\"hoststatus\">" + translation['host-down'] + "</div>" : ""; + + var info = '<div class="map-popup">'; + info += '<h1>'; + info += '<a class="detail-link" data-hostname="' + data['host_name'] + '" data-base-target="_next" href="' + + icinga.config.baseUrl + + '/monitoring/host/show?host=' + + data['host_name'] + + '">'; + info += ' <span class="icon-eye"></span> '; + info += '</a>'; + info += data['host_display_name'] + '</h1>'; + info += host_status; + + info += services; + info += '</div>'; + + var marker; + + if (cache[id].hostMarkers[identifier]) { + marker = cache[id].hostMarkers[identifier]; + marker.options.state = worstState; + marker.setIcon(icon); + } else { + marker = L.marker(data['coordinates'], + { + icon: icon, + title: display_name, + riseOnHover: true, + id: identifier, + state: worstState, + }).addTo(cache[id].markers); + + cache[id].hostMarkers[identifier] = marker; + cache[id].hostData[identifier] = data + } + + marker.bindPopup(info); + }) + }); + + cache[id].markers.refreshClusters(); + cache[id].map.spin(false); + + // TODO: Should be updated instant and not only on data refresh + cache[id].map.invalidateSize(); + + if (show_host != "") { + showHost(show_host); + show_host = "" + } + } + + var url = icinga.config.baseUrl + '/map/data/points?' + filterParams(id, cache[id].parameters); + $.getJSON(url, processData) + .fail(function (jqxhr, textStatus, error) { + errorMessage(error); + }); + }, + + onRenderedContainer: function (event) { + // in module configuration we don't have a map, so return peacefully + if (typeof id === 'undefined') { + return; + } + + cache[id] = {}; + cache[id].map = L.map('map-' + id, { + zoomControl: false, + worldCopyJump: true + } + ); + + var osm = L.tileLayer(tile_url, { + attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>', + subdomains: ['a', 'b', 'c'], + maxNativeZoom: map_max_native_zoom, + maxZoom: map_max_zoom, + minZoom: map_min_zoom + }); + osm.addTo(cache[id].map); + + cache[id].markers = new L.MarkerClusterGroup({ + iconCreateFunction: function (cluster) { + var childCount = cluster_problem_count ? 0 : cluster.getChildCount(); + + var states = []; + $.each(cluster.getAllChildMarkers(), function (id, el) { + states.push(el.options.state); + + if (cluster_problem_count && el.options.state > 0) { + childCount++; + } + }); + + var worstState = getWorstState(states); + var c = ' marker-cluster-' + worstState; + + return new L.DivIcon({ + html: '<div><span>' + childCount + '</span></div>', + className: 'marker-cluster' + c, + iconSize: new L.Point(40, 40) + }); + }, + maxClusterRadius: function (zoom) { + return (zoom <= disable_cluster_at_zoom) ? 80 : 1; // radius in pixels + }, + }); + + cache[id].hostMarkers = {}; + cache[id].hostData = {}; + + cache[id].fullscreen = false; + cache[id].parameters = url_parameters; + + // TODO: fixme + // var basePath = $currentUrl.replace(/\?.*$/, ''); + // var initialUrl = icinga. + // $('#map-' + id).closest('.module-map').data('icingaUrl', url_parameters); + + showDefaultView(); + + cache[id].map.on('popupopen', this.onPopupOpen); + + L.control.zoom({ + zoomInTitle: translation['btn-zoom-in'], + zoomOutTitle: translation['btn-zoom-in'] + } + ).addTo(cache[id].map); + + if (!dashlet) { + L.easyButton({ + states: [{ + icon: 'icon-dashboard', title: translation['btn-dashboard'], onClick: function (btn, map) { + var dashletUri = "map" + window.location.search; + var uri = icinga.config.baseUrl + "/" + "dashboard/new-dashlet?url=" + encodeURIComponent(dashletUri); + + window.open(uri, "_self") + } + }] + }).addTo(cache[id].map); + + L.easyButton({ + states: [{ + icon: 'icon-resize-full-alt', + title: translation['btn-fullscreen'], + onClick: function (btn, map) { + toggleFullscreen(); + } + }] + }).addTo(cache[id].map); + + L.easyButton({ + states: [{ + icon: 'icon-globe', title: translation['btn-default'], onClick: function (btn, map) { + showDefaultView(); + } + }] + }).addTo(cache[id].map); + + + L.control.locate({ + icon: 'icon-pin', + strings: {title: translation['btn-locate']} + }).addTo(cache[id].map); + + cache[id].map.on('map-container-resize', function () { + map.invalidateSize(); + console.log("Resize") + }); + + cache[id].map.on('moveend', function (e) { + var center = cache[id].map.getCenter(); + + var lat = center.lat; + var lng = center.lng; + + updateUrl('default_lat', lat); + updateUrl('default_long', lng) + }); + + cache[id].map.on('zoomend', function (e) { + var zoomLevel = cache[id].map.getZoom(); + updateUrl('default_zoom', zoomLevel) + }); + + cache[id].map.on('click', function (e) { + // only for debugging needed + // var id = e.target._container.id.replace('map-', ''); + + if (e.originalEvent.ctrlKey) { + var coord = 'vars.geolocation = "' + + e.latlng.lat.toFixed(6) + + ',' + + e.latlng.lng.toFixed(6) + + '"'; + + var popup = "<h1>Location selected</h1>" + + "<p>To use this location with your host(s) or service(s), just add the following config to your object definition:</p>" + + "<pre>" + coord + "</pre>"; + + var marker; + marker = L.marker(e.latlng, {icon: colorMarker(99, 'globe')}); + marker.bindPopup(popup); + marker.addTo(cache[id].markers); + + marker.on('popupclose', function (evt) { + cache[id].markers.removeLayer(marker); + }); + + cache[id].markers.zoomToShowLayer(marker, function () { + marker.openPopup(); + }) + } + }); + } + + cache[id].markers.addTo(cache[id].map); + + cache[id].map.spin(true); + this.updateMapData({id: id, show_host: map_show_host}) + + } + }; + + Icinga.availableModules.map = Map; + +}(Icinga)); |