summaryrefslogtreecommitdiffstats
path: root/public/js/module.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:24:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:24:14 +0000
commita58a146ad5cc0079fd68431cd679943f933e35a4 (patch)
tree91608c01e82676102457ffff2219f321ec474657 /public/js/module.js
parentInitial commit. (diff)
downloadicingaweb2-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.js628
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: '&copy; <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));