From: Alex Bennee <a...@cbnl.com> This patch adds support for slippy maps to display a live geo-location aware status view of your network. The new display (called node-map) is scoped by surveillance categories. By selecting a category the map will snap to the size to fit all elements in. If nodes are too close to each other they are globed together until the map zooms to a suitable level when they can be expanded into individual icons.
Configuration ============= You will require a working OpenLayers and Tile Cache setup. In our case we run it on our NMS machine but there is no reason to. For Debian/Ubuntu systems the package dependencies look like: python-mapnik (>=0.7.1-2), openlayers, tilecache, osm2pgsql, osm-mapnik-utils, osm-base-data The osm-base-data provides a world map, you will need additional data if you want more detail. OpenNMS uses the following parameters in /etc/opennms/opennms.properties: For a locally setup map server just use non http:// prefixed relative links and the node map pages will automatically munge them to be served from port 80. This prevents confusion with multi-homed setups. If you are serving the map data from something other than port 80 you will need to use long explicit form URLs for the map data. Geo-location data ================= Currently the only way to import geo-location data for nodes is through the model-importer (which is what CBNL uses as OpenNMS cannot discover network topology on its own). The node element now takes an optional geolocation tag, e.g.: <geolocation lat="52.227352" lon="0.154387"></geolocation> Updates ======= * Re-factored the code into Map/Table and Data controllers * Cleaned up DOM manipulation using jQuery methods * Changed mapping for map markers based on alarm and availability * re-organises the layout in into more OpenNMS like 3 pane view * Add OpenNMS style info boxes to the display * Add the ability to display child nodes * Made the pop-up box use OpenNMS table styling * Removed a bunch of dead code, generalised a bunch more * add mock console object so non-Chromium/Firebug enabled browsers don't get confused. * fix loading of Javascript so cache state is not critical to working * create the ALL and Un-categorised entries first so the drop down is correctly set on Firefox * handle local relative links * only use time string instead of full date Written-by: Gareth Bailey <g...@kynesim.co.uk> Updated-by: Alex Bennée <a...@cbnl.com> Signed-off-by: Alex Bennée <a...@cbnl.com> --- .../src/main/filtered/etc/opennms.properties | 9 + .../src/main/webapp/WEB-INF/dispatcher-servlet.xml | 6 + opennms-webapp/src/main/webapp/node_map/index.jsp | 128 ++++ opennms-webapp/src/main/webapp/node_map/js/main.js | 636 ++++++++++++++++++++ .../src/main/webapp/node_map/js/node_map_marker.js | 251 ++++++++ .../src/main/webapp/node_map/js/options.jsp | 55 ++ .../src/main/webapp/node_map/js/utils.js | 24 + 7 files changed, 1109 insertions(+), 0 deletions(-) create mode 100644 opennms-webapp/src/main/webapp/node_map/index.jsp create mode 100644 opennms-webapp/src/main/webapp/node_map/js/main.js create mode 100644 opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js create mode 100644 opennms-webapp/src/main/webapp/node_map/js/options.jsp create mode 100644 opennms-webapp/src/main/webapp/node_map/js/utils.js diff --git a/opennms-base-assembly/src/main/filtered/etc/opennms.properties b/opennms-base-assembly/src/main/filtered/etc/opennms.properties index 710b3d6..aaefa00 100644 --- a/opennms-base-assembly/src/main/filtered/etc/opennms.properties +++ b/opennms-base-assembly/src/main/filtered/etc/opennms.properties @@ -380,6 +380,15 @@ gwt.geocoder.email= # Open MapQuest tile server gwt.openlayers.url=http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png +###### SLIPPY MAPS ###### +# In addition to the remote poller maps, an implementation of OpenLayers slippy maps has +# been implemented which allows you to view nodes with geolocation data. +# +# To use it, set these properties: + +# slippy.openlayers.url=http://SERVER/OpenLayers +# slippy.tilecache.url=http://SERVER/tilecache-cgi + ###### UI DISPLAY OPTIONS ###### # This value allows you to show or hide the Acknowledge event button. This is only diff --git a/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml index fda9629..7dd6fd0 100644 --- a/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml +++ b/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml @@ -455,6 +455,12 @@ <property name="url" value="RemotePollerMap/index.jsp"/> <property name="locationMatch" value="RemotePollerMap"/> </bean> + + <bean class="org.opennms.web.navigate.LocationBasedNavBarEntry"> + <property name="name" value="Node Map"/> + <property name="url" value="node_map/index.jsp"/> + <property name="locationMatch" value="node_map"/> + </bean> <bean class="org.opennms.web.navigate.FileBasedNavBarEntry"> <property name="name" value="Vulnerabilities"/> diff --git a/opennms-webapp/src/main/webapp/node_map/index.jsp b/opennms-webapp/src/main/webapp/node_map/index.jsp new file mode 100644 index 0000000..2124eda --- /dev/null +++ b/opennms-webapp/src/main/webapp/node_map/index.jsp @@ -0,0 +1,128 @@ +<!-- + +// +// This file is part of the OpenNMS(R) Application. +// +// OpenNMS(R) is Copyright (C) 2002-2007 The OpenNMS Group, Inc. All rights reserved. +// OpenNMS(R) is a derivative work, containing both original code, included code and modified +// code that was published under the GNU General Public License. Copyrights for modified +// and included code are below. +// +// OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. +// +// Copyright (C) 2003 Networked Knowledge Systems, Inc. All rights reserved. +// +// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// For more information contact: +// OpenNMS Licensing <lice...@opennms.org> +// http://www.opennms.org/ +// http://www.opennms.com/ +// + +--> +<%@ page contentType="text/html;charset=UTF-8" language="java"%> + +<% + String breadcrumb1 = "Node Map"; + + // Do special handling for slippy.openlayers.url doesn't contain a leading http:// + String openlayers_url = System.getProperty("slippy.openlayers.url"); + if (!openlayers_url.startsWith("http://")) { + StringBuffer requestURL = request.getRequestURL(); + String baseURL = requestURL.substring(0, requestURL.indexOf(":8980")); + openlayers_url = baseURL + openlayers_url; + } + + String openlayers_location = "<script defer src=\"" + openlayers_url + "/OpenLayers.js\" type=\"text/javascript\"></script>"; + + //avoid cache + response.setHeader("Cache-Control","no-store"); + response.setHeader("Pragma","no-cache"); + response.setHeader("Expires","0"); + +%> + + +<jsp:include page="/includes/header.jsp" flush="false"> + <jsp:param name="title" value="Node Map" /> + <jsp:param name="breadcrumb" value="<%=breadcrumb1%>" /> + + <jsp:param name="script" value="<%=openlayers_location%>" /> + <jsp:param name="script" value='<script defer src="js/jquery/jquery.js" type="text/javascript"> </script>' /> + <jsp:param name="script" value='<script defer src="node_map/js/options.jsp" type="text/javascript"> </script>' /> + <jsp:param name="script" value='<script defer src="node_map/js/utils.js" type="text/javascript"> </script>' /> + <jsp:param name="script" value='<script defer src="node_map/js/node_map_marker.js" type="text/javascript"> </script>' /> + <jsp:param name="script" value='<script defer src="node_map/js/main.js" type="text/javascript"> </script>' /> +</jsp:include> + + <!-- Left Column --> + <div id="index-contentleft"> + + <h3 class="o-box">Map Information</h3> + <table class="o-box"> + <tr> + <th> + Select Category: + </th> + <td> + <form> + <select name="menu" id="categories"/> + </form> + </td> + </tr> + <tr> + <th>Include Child Nodes</th> + <td> + <input type="radio" name="include_children" value="yes" onClick="osm_reload()"/> <label for="on">Yes</label> + <input type="radio" name="include_children" value="no" onClick="osm_reload()" CHECKED/> <label for="off">No</label> + </td> + </tr> + <tr> + <th>Update Time</th> + <td> + <div id="update_time" style="height: 1.5em; margin-top: 10px;"></div> + </td> + </tr> + <tr> + <th>Location</th> + <td> + <div id="location" style="clear:both; height: 1em;" alt="position of last right click on map"></div> + </td> + </tr> + </table> + + + </div> + + <!-- Middle Column --> + <div id="index-contentmiddle"> + <div id="map" style="float: center; width: 95%; height: 500px; "> </div> + </div> + + <!-- Right Column --> + <div id="index-contentright"> + <div style="overflow: auto; padding: 0px; margin: 0px; width: 90%; "> + + <h3 class="o-box" style=" margin: 0px;">Node List</h3> + <table id="link_table" class="standard o-box"></table> + </div> + + </div> + +<jsp:include page="/includes/footer.jsp" flush="false" > + <jsp:param name="location" value="map" /> +</jsp:include> + diff --git a/opennms-webapp/src/main/webapp/node_map/js/main.js b/opennms-webapp/src/main/webapp/node_map/js/main.js new file mode 100644 index 0000000..e337b4d --- /dev/null +++ b/opennms-webapp/src/main/webapp/node_map/js/main.js @@ -0,0 +1,636 @@ +/* + Slippy Maps, main javascript. + + Code starts at osm_init() on load. + + data_controller - object for getting data from OpenNMS + map_controller - object for for updating the OpenLayers map + table_controller - object for updating the element table on the right +*/ + +var data_controller = null; +var map_controller = null; +var table_controller = null; + +/* + * Create a mock console object if it doesn't exist, this stops any spare + * console logging operations from breaking browsers without it. + */ + +if(!window.console) { + window.console = new function() { + this.log = function(str) {}; + this.dir = function(str) {}; + }; +} + +var onms; +if (!onms) onms = {}; +if (!onms.vs) onms.vs = {}; + +/* + * DataContoller + * + * This object is responsible for fetching data from OpenNMS and making it + * available to the rest of the page. This is principally done with XmlHttp + * requests to the NodeMapServlet in OpenNMS. + * + */ +if (!onms.vs.DataController) { + + onms.vs.DataController = function DataController(node_request_url, + category_url) { + /* + * node_list, flattened_node_list + * + * The first is the json data as fetched from OpenNMS + * The second is the data with all children included as nodes themselves + */ + this.node_list = []; + this.flattened_node_list = []; + + /* + * category_data + */ + this.category_data = null; + + /* + * Simple wrapper to get the XmlHttp request object or error out + */ + function getXmlHttp() + { + var xmlHttp; + try{ + // Firefox, Opera 8.0+, Safari + xmlHttp=new XMLHttpRequest(); + } + catch (e){ + alert("Browser doesn't support xmlHttpRequest(), please use a better browser"); + } + return xmlHttp; + } + + /* + * Do a XML request and pass the data back to the callback function. + * This requires OpenNMS to have authorised the request (which it should + * have at this point given we are here). + */ + function do_request(me, url, processing_callback, notify_callback) { + var xmlHttp = getXmlHttp(); + console.log("do_request: "+url); + + xmlHttp.onreadystatechange=function() + { + if(xmlHttp.readyState==4 && xmlHttp.status == 200){ + try { + var data = eval("(" + xmlHttp.responseText + ")"); + processing_callback(me, data); + } catch (e) { + console.log("do_request: error "+e); + alert("Error "+e+" while processing: " + url); + } + + if (notify_callback) { + notify_callback() + } + } + }; + xmlHttp.open("GET", url, true); + xmlHttp.send(null); + + return; + } + + /* + * Handle Category data + */ + function process_category_data(me, cat_data) { + if (me) { + me.category_data = cat_data; + } else { + console.log("process_category_data() no data controller yet"); + } + } + + this.get_category_data = function(callback) { + do_request(this, category_url, process_category_data, callback); + return; + }; + + /* + * Handle fetching the node data + */ + function add_children(array, children) { + if (typeof children.length == "undefined") { + console.log("odd state"); + } + + var i; + for (i=0; i<children.length; i++) { + var child = children[i]; + var id = child.nodeId; + if (!array[id]) { + array[id] = child; + } + if (child.children) { + add_children(array, child.children); + } + } + } + function process_node_data(me, node_data) { + if (me) { + me.node_list = node_data; + me.flattened_node_list = []; + + /* Process the nodes in a hash Array so + * we can de-duplicate any child nodes we see + */ + + var hash_array = new Array(); + add_children(hash_array, node_data); + for (var key in hash_array) { + var node = hash_array[key]; + me.flattened_node_list.push(node); + } + + } else { + console.log("process_node_data() can't see data_controller yet"); + } + } + + this.get_node_data = function(node_callback, category) { + // Do we include children? + var url = node_request_url; + + if ($("input[name=include_children]:checked").val() == "yes") { + url = url + "?depth=2"; + } else { + url = url + "?depth=0"; + } + + // Category selected? + if (category) { + url = url + "&cat=" + category; + } + + do_request(this, url, process_node_data, node_callback); + return; + }; + }; +} + +/* + * TableController + * + * This object is responsible for interfacing with the node table on the + * right side of the screen. + */ +if (!onms.vs.TableController) { + + onms.vs.TableController = function TableController() { + + /* Start by loading all nodes/cpes/aps */ + this.category = "ALL"; + + /* + Update the table based on category data + */ + onms.vs.TableController.prototype.category_data_updated_cb = function() { + console.log("category_data_updated_cb()"); + var cat_data = data_controller.category_data; + + if (cat_data) { + var cats = $("#categories"); + var selected = 0; + var add_option = function(id, name) { + if (name == this.category) { + selected = id; + } + return $("<option></option>").val(id).html(name); + }; + + cats.append(add_option("ALL", "All")); + cats.append(add_option("UCAT", "Uncategorised")); + for (var i = 0; i < cat_data.length; i++) { + cats.append(add_option(cat_data[i].id, cat_data[i].name)); + } + + } else { + console.log("category_data_updated_cb(), no data :-("); + } + }; + + /* Mappings for severity of alarms. See: + * + *./opennms-qosdaemon/src/main/java/org/openoss/opennms/... + * .../spring/dao/OnmsAlarmOssjMapper.java + * + * public static final int INDETERMINATE_SEVERITY = 1; + * public static final int CLEARED_SEVERITY = 2; + * public static final int NORMAL_SEVERITY = 3; + * public static final int WARNING_SEVERITY = 4; + * public static final int MINOR_SEVERITY = 5; + * public static final int MAJOR_SEVERITY = 6; + * public static final int CRITICAL_SEVERITY = 7; + */ + severity_mapping = []; + severity_mapping[0] = "Normal"; + severity_mapping[1] = "Indeterminate"; + severity_mapping[2] = "Cleared"; + severity_mapping[3] = "Normal"; + severity_mapping[4] = "Warning"; + severity_mapping[5] = "Minor"; + severity_mapping[6] = "Major"; + severity_mapping[7] = "Critical"; + + /* Create table element dom nodes. */ + function create_table_element(node) { + var elm = document.createElement('tr'); + var node_elm = document.createElement('td'); + var alarm_elm = document.createElement('td'); + var alarm_link = document.createElement('a'); + var node_link = document.createElement('a'); + var node_text = document.createTextNode(node.nodeLabel); + var alarm_text = document.createTextNode("view alarms"); + var text = document.createTextNode(""); + + node_link.href = "/opennms/element/node.jsp?node=" + node.nodeId; + node_link.name = node.nodeId; + alarm_link.href = "/opennms/alarm/list.htm?filter=node%3d" + node.nodeId; + + node_elm.id = "tb_node_"+node.nodeId; + + if (node.severity == 0) { + if (node.avail < 95.0) { + elm.className = "Warning"; + } else { + elm.className = "Normal"; + } + } else { + elm.className = severity_mapping[node.severity]; + } + + node_elm.className = "divider"; + alarm_elm.className = "divider"; + + alarm_link.appendChild(alarm_text); + node_link.appendChild(node_text); + node_elm.appendChild(node_link); + alarm_elm.appendChild(alarm_link); + + elm.appendChild(node_elm); + elm.appendChild(alarm_elm); + + return elm; + } + + onms.vs.TableController.prototype.update_table = function() { + var nodes = data_controller.flattened_node_list; + var table = $("#link_table"); + var i, j; + + // clear the table + table.find("tr").remove(); + + for (i = 0; i < nodes.length; i++) { + var elm = create_table_element(nodes[i]); + table.append(elm); + } + }; + + function category_change() { + onms.vs.NodeMapController.prototype.update_map(); + } + + /* + * initialisation functions + */ + console.log("TableController started"); + $("#categories").change(category_change); + data_controller.get_category_data(this.category_data_updated_cb); + }; +} + + + +if (!onms.vs.NodeMapController) { + + onms.vs.NodeMapController = function NodeMapController(map_id, + tilecache_url, + update_id, + cluster_threshold) { + /* Start by loading all nodes/cpes/aps */ + this.category = "ALL"; + this.refresh_time = 30000; + + /* Some id's for html tags */ + this.update_id = update_id; + + /* The distance in pixels. Nodes spaced within this radius + * are clustered together under one marker. + */ + this.cluster_threshold = cluster_threshold; + + /* Make 'this' accessable to our closures */ + var this_map_cont = this; + function mapEvent(e) { + if (e.type == "zoomend") { + this_map_cont.current_extent = this_map_cont.map.getExtent(); + this_map_cont.clear_popups(); + this_map_cont.refresh(); + } + } + + /* + * define the map + */ + var map_options = { + restrictedExtent: new OpenLayers.Bounds(-20037508.34, + -20037508.34, + 20037508.34, + 20037508.34), + sphericalMercator: true, + maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34, + 20037508.34,20037508.34), + units: 'm', + maxResolution: 78271.516950000005, + projection: "EPSG:900913", + + controls : [ + new OpenLayers.Control.MouseDefaults(), + new OpenLayers.Control.ArgParser(), + new OpenLayers.Control.PanZoom() + ], + eventListeners: { + "zoomend": mapEvent + } + }; + + /* create some openlayers objects. */ + this.map = new OpenLayers.Map(map_id, map_options); + this.osm_layer = new OpenLayers.Layer.WMS("VffMap0", + tilecache_url, + ({layers: 'osm', + format: 'image/png'})); + + this.map.addLayer(this.osm_layer); + this.map.setCenter(new OpenLayers.LonLat(0 ,0), 0); + + if (!this.map.getCenter()) this.map.zoomToMaxExtent(); + this.current_extent = this.map.getExtent(); + + this.markers_layer = new OpenLayers.Layer.Markers("Markers"); + this.map.addLayer(this.markers_layer); + + /* Disable oncontext menu for right clicks */ + this.map.div.oncontextmenu = function(){return false;}; + + /* Handle mouse up events */ + this.map.div.onmouseup = function(e){ + + /* right click, show location */ + if (OpenLayers.Event.isRightClick(e)){ + e.xy = this_map_cont.map.events.getMousePosition(e); + e.geoxy = this_map_cont.map.getLonLatFromPixel(e.xy); + + var n = {}; + n.longitude = e.geoxy.lon; + n.latitude = e.geoxy.lat; + + e.lonlat = onms.vs.utils.projection_lonlat_from_4326(this_map_cont.map, n); + var pos_string = "lon="+OpenLayers.Util.toFloat(e.lonlat.lon, 7)+",lat="+OpenLayers.Util.toFloat(e.lonlat.lat, 7); + $("#location").html(pos_string); + } + }; + + + /* Now we keep track of map drags so that we can clear popups on + * a click to the map but not when the map is dragged. + */ + function mouse_down_handler(e) { + if (OpenLayers.Event.isLeftClick(e)){ + /* We've not been dragged yet but the left buton was clicked */ + this_map_cont.phase_down = true; + this_map_cont.is_dragged = false; + } + } + this.map.events.register('mousedown', this, mouse_down_handler); + + + this.map.div.onmousemove = function(e){ + if (this_map_cont.phase_down) { + /* the mouse move while the button was held down. */ + this_map_cont.is_dragged = true; + } + }; + + this.map.div.onclick = function(e){ + if (OpenLayers.Event.isLeftClick(e)){ + if (!this_map_cont.is_dragged) { + /* if the click cam after a drag don't clear popup */ + this_map_cont.clear_popups(); + } + + /* remember, mouse button is up */ + this_map_cont.phase_down = false; + this_map_cont.is_dragged = false; + } + + }; + + }; + + onms.vs.NodeMapController.prototype.clear_popups = function(){ + for(var i=0; i < this.map.popups.length; i++) { + this.map.popups[i].related_node.clear_highlight(); + this.map.removePopup(this.map.popups[i]); + this.markers_layer.selectedFeature = null; + } + }; + +/* onms.vs.NodeMapController.prototype. */ + + + onms.vs.NodeMapController.prototype.set_refresh_timer = function(ms) { + /* Just in case... */ + this.clear_refresh_timer(); + + var this_map_cont = this; + function node_data_cb(node_data) { + console.log("node_data_cb"); + + this_map_cont.node_data = node_data; + this_map_cont.set_last_update(); + this_map_cont.clear_popups(); + this_map_cont.refresh(); + } + + function on_refresh() { + cat = $("#categories").val(); + data_controller.get_node_data(node_data_cb, cat); + } + + this.refresh_timer = setTimeout(on_refresh, ms); + }; + + onms.vs.NodeMapController.prototype.clear_refresh_timer = function(ms) { + console.log("clear_refresh_timer"); + clearTimeout(this.refresh_timer); + }; + + + onms.vs.NodeMapController.prototype.refresh = function() { + console.log("refresh"); + + /* Remove the markers... */ + if (this.nms) { + for (var j = 0; j < this.nms.length; j++) { + this.nms[j].remove_from_map(this.markers_layer, true); + } + } + + table_controller.update_table(); + this.draw_map(); + }; + + + + + onms.vs.NodeMapController.prototype.draw_map = function() { + console.log("draw_map"); + var i, j, k; + + /* clear previous nodes */ + this.nms = []; + var nms = this.nms; + var node_data = data_controller.flattened_node_list; + + + for (i = 0; i < node_data.length; i++) { + var node = node_data[i]; + var added = false; + var pos = onms.vs.utils.projection_lonlat(this.map, node); + + /* + * Is the node within range of any of the markers we have added so far? + */ + for (j = 0; j < nms.length; j++) { + if (nms[j].check_proximity(node, this.cluster_threshold )) { + nms[j].add(node); + added = true; + break; + } + } + + if (!added) { + nms.push(new onms.vs.NodeMapMarker(this.map, node)); + } + + } + + for (j = 0; j < nms.length; j++) { + nms[j].add_to_map(this.markers_layer); + } + + /* Refresh in */ + this.set_refresh_timer(this.refresh_time); + }; + + onms.vs.NodeMapController.prototype.set_last_update = function() { + var current_time = new Date(); + $("#update_time").fadeOut("fast", function() { + $("#update_time").text(current_time.toTimeString()); + $("#update_time").fadeIn(); + }); + }; + + onms.vs.NodeMapController.prototype.zoom_to_bounds = function(nodes) { + console.log("zoom to bounds"); + var i; + var node_bounds = new OpenLayers.Bounds(); + var last_zoom = this.map.getZoom() ; + + for (i = 0; i < nodes.length; i++) { + /* need to project between coordinate systems */ + node_bounds.extend(onms.vs.utils.projection_lonlat(this.map, + nodes[i])); + } + + if (nodes.length > 0) { + this.map.zoomToExtent(node_bounds); + if (last_zoom == this.map.getZoom()) { + /* If we didn't actually zoom, redraw here, otherwise + * redraw in the zoom event that occurs soon. + */ + this.clear_popups(); + this.refresh(); + } + } else { + /* No nodes to show, show the whole world map */ + this.map.zoomToMaxExtent(); + } + }; + + /* + * Handle updates + */ + onms.vs.NodeMapController.prototype.node_data_updated_cb = function () { + console.log("node_data_updated_cb"); + var node_data = data_controller.flattened_node_list; + map_controller.zoom_to_bounds(node_data); + map_controller.set_last_update(node_data); + }; + + + + /* + * Update map, triggered when we select a new category or want + * to include children in the display. + */ + onms.vs.NodeMapController.prototype.update_map = function() { + console.log("update_map()"); + /* clear refresh time so it doesn't go in the middle of this + * request. + */ + this.clear_refresh_timer(); + cat = $("#categories").val(); + data_controller.get_node_data(this.node_data_updated_cb, cat); + console.log("update_map() done"); + }; + + + /* Kick things off at the start of time. */ + onms.vs.NodeMapController.prototype.run_map = function() { + console.log("run_map()"); + data_controller.get_node_data(this.node_data_updated_cb); + console.log("run_map() done"); + }; +} + + + +/* Initialise the page and it's objects */ +function osm_init(){ + console.log("osm_init()"); + + data_controller = new onms.vs.DataController(onms.vs.NodeMapOptions.NODE_REQUEST_URL, + onms.vs.NodeMapOptions.CAT_REQUEST_URL); + + table_controller = new onms.vs.TableController(); + + map_controller = new onms.vs.NodeMapController(onms.vs.NodeMapOptions.MAP_ID, + onms.vs.NodeMapOptions.TILECACHE_URL, + onms.vs.NodeMapOptions.UPDATE_ID, + onms.vs.NodeMapOptions.CLUSTER_THRESH); + + + console.log("osm_init: running map"); + map_controller.run_map(); +} + +/* Trigger a reload of the map data */ +function osm_reload() { + onms.vs.NodeMapController.prototype.update_map(); +} + +$("document").ready(osm_init()); diff --git a/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js b/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js new file mode 100644 index 0000000..bc1395d --- /dev/null +++ b/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js @@ -0,0 +1,251 @@ +/* + Slippy Maps, Node Markers + + This file is just concerned with the display and creation of node markers on the map + and the associated popup info box. Each marker represents one or more nodes in the system. +*/ + +var onms; +if (!onms) onms = {}; +if (!onms.vs) onms.vs = {}; + +if (!onms.vs.NodeMapMarker) { + /* A node map marked represents the nodes that will show as + * one marker on the map. + */ + + + onms.vs.NodeMapMarker = function(map, node) { + this.nodes = []; + this.map = map; + + /* Add the first node */ + this.add(node); + }; + + /* The type of popup to use. */ + onms.vs.NodeMapMarker.prototype.NodeInfoBubble = OpenLayers.Class(OpenLayers.Popup.FramedCloud, { + 'autoSize': false + }); + + /* If a node is clustered we add it to this array */ + onms.vs.NodeMapMarker.prototype.add = function(node) { + this.nodes.push(node); + }; + + /* coords need projecting between different coordinate systems */ + onms.vs.NodeMapMarker.prototype.projection_lonlat = function(node) { + return onms.vs.utils.projection_lonlat(this.map, node); + }; + + /* check to see if a new node if close to the base node */ + onms.vs.NodeMapMarker.prototype.check_proximity = function(node, radius) { + if (!this.nodes[0].projected_lonlat) { + this.nodes[0].projected_lonlat = this.projection_lonlat(this.nodes[0]); + } + + var pll = node.projected_lonlat = this.projection_lonlat(node); + var pix1 = this.map.getPixelFromLonLat( this.nodes[0].projected_lonlat); + var pix2 = this.map.getPixelFromLonLat(pll); + + var d = Math.sqrt(((pix1.x - pix2.x) * (pix1.x - pix2.x)) + ((pix1.y - pix2.y) * (pix1.y - pix2.y))); + + if (d < radius) { + return true; + } else { + return false; + } + }; + + onms.vs.NodeMapMarker.prototype.NODE_DOWN_ICON = onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker.png"; + onms.vs.NodeMapMarker.prototype.NODE_UP_ICON = onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker-green.png"; + onms.vs.NodeMapMarker.prototype.NODE_WARNING_ICON = onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker-gold.png"; + + /* + Calculate the image marker based on: + + * is the node having an outage? + * does the node have current alarms? + * does the node have children with alarms? + * has the node had recent outages? + */ + onms.vs.NodeMapMarker.prototype.get_icon_img_path = function() { + var icon = this.NODE_UP_ICON; + + var alarms = this.count_alarms(); + if (alarms.count > 0) { + if (alarms.severity > 4) { + return this.NODE_DOWN_ICON; + } else if (alarms.severity > 3) { + return this.NODE_WARNING_ICON; + } + } + + // Now check availability... + if (alarms.avail < 95.0) { + icon = this.NODE_WARNING_ICON; + } + return icon; + }; + + /* + * Summarise the alarm information across the nodes in this marker + * + * This boils down to the maximum alarm severity and the minimum availability. + */ + onms.vs.NodeMapMarker.prototype.count_alarms = function() { + var i; + var count = 0; + var max_severity = 0; + var min_avail = 100.0; + + for (i = 0; i < this.nodes.length; i++) { + if (this.nodes[i].severity > 0) { + max_severity = Math.max(max_severity, this.nodes[i].severity); + count++; + } + min_avail = Math.min(min_avail, this.nodes[i].avail); + } + + return { count: count, severity: max_severity, avail: min_avail }; + }; + + /* Mappings for severity of alarms. See: + * + *./opennms-qosdaemon/src/main/java/org/openoss/opennms/... + * .../spring/dao/OnmsAlarmOssjMapper.java + * + * public static final int INDETERMINATE_SEVERITY = 1; + * public static final int CLEARED_SEVERITY = 2; + * public static final int NORMAL_SEVERITY = 3; + * public static final int WARNING_SEVERITY = 4; + * public static final int MINOR_SEVERITY = 5; + * public static final int MAJOR_SEVERITY = 6; + * public static final int CRITICAL_SEVERITY = 7; + */ + severity_mapping = []; + severity_mapping[0] = "Normal"; + severity_mapping[1] = "Indeterminate"; + severity_mapping[2] = "Cleared"; + severity_mapping[3] = "Normal"; + severity_mapping[4] = "Warning"; + severity_mapping[5] = "Minor"; + severity_mapping[6] = "Major"; + severity_mapping[7] = "Critical"; + + /* Display one nodes info in a line in the popup */ + function summarise_one_node(node) { + var span_class = "Normal"; + if (node.severity>0) { + span_class = severity_mapping[node.severity]; + } else if (node.avail < 95.0 ) { + span_class = "Warning"; + } + + var node_html = "<tr class='"+span_class+"'>"; + node_html += "<td><a href='/opennms/element/node.jsp?node="+node.nodeId+"'>"; + node_html += node.nodeLabel; + node_html += "</a></td>"; + node_html += "<td>"+node.avail+"%</td><td>"; + if (node.severity>0) { + node_html += "<a href='/opennms/alarm/list.htm?filter=node%3d"+node.nodeId+"'>Alarms</a>"; + } else { + node_html += " "; + } + node_html += "</td></tr>"; + return node_html; + } + + onms.vs.NodeMapMarker.prototype.get_summary = function(report_cpe) { + /* Return HTML to go in popup box. */ + var out_html = "<div><table class='standard o-box'><th>Node</th><th>Avail</th><th> </th><tbody>"; + + for (i = 0; i < this.nodes.length; i++) { + out_html += summarise_one_node(this.nodes[i]); + } + out_html += "</tbody></table></div>"; + return out_html; + }; + + onms.vs.NodeMapMarker.prototype.clear_highlight = function() { + this.set_table_class("divider"); + }; + + onms.vs.NodeMapMarker.prototype.highlight = function() { + this.set_table_class("divider bright"); + this.view(); + }; + + /* make sure at least one node is on view in the table */ + onms.vs.NodeMapMarker.prototype.view = function() { + var elm = document.getElementById("tb_node_"+this.nodes[0].nodeId); + elm.scrollIntoView(true); + }; + + /* Highlight nodes in the summary table when clicked */ + onms.vs.NodeMapMarker.prototype.set_table_class = function(classes) { + for (var i = 0; i < this.nodes.length; i++) { + var elm = document.getElementById("tb_node_" + this.nodes[i].nodeId); + elm.className = classes; + } + }; + + onms.vs.NodeMapMarker.prototype.remove_from_map = function(markers_layer, report_cpe) { + markers_layer.removeMarker(this.marker); + }; + + /* create the openlayers markers and event handling for popups */ + onms.vs.NodeMapMarker.prototype.add_to_map = function(markers_layer) { + var node = this.nodes[0]; + var popup_html = this.get_summary(); + var location = (this.nodes[0].projected_lonlat + || this.projection_lonlat(node)); + + var icon_img = this.get_icon_img_path(node); + + var feature = new OpenLayers.Feature(markers_layer.markers, location); + feature.closeBox = false; + feature.popupClass = this.NodeInfoBubble; + feature.data.popupContentHTML = popup_html; + feature.data.overflow = "auto"; + feature.size = new OpenLayers.Size(21,25); + feature.data.icon = new OpenLayers.Icon(icon_img); + feature.data.icon.size = new OpenLayers.Size(21,25); + feature.data.icon.offset = new OpenLayers.Pixel(-(feature.data.icon.size.w/2), + -feature.data.icon.size.h); + + var marker = feature.createMarker(); + + var this_node = this; + var markerClick = function (evt) { + { + var sameMarkerClicked = (this == markers_layer.selectedFeature); + markers_layer.selectedFeature = (!sameMarkerClicked) ? this : null; + + /* remove the popups already showing */ + for(var i=0; i < this_node.map.popups.length; i++) { + this_node.map.popups[i].related_node.clear_highlight(); + this_node.map.removePopup(this_node.map.popups[i]); + } + + if (!sameMarkerClicked) { + /* then we need a new one... to replace the one we closed + */ + var pop = this.createPopup(this.closeBox); + + pop.related_node = this_node; + this_node.highlight(); + this_node.map.addPopup(pop); + } + + } + + OpenLayers.Event.stop(evt); + return false; + }; + marker.events.register("click", feature, markerClick); + this.marker = marker; + + markers_layer.addMarker(marker); + }; +}; diff --git a/opennms-webapp/src/main/webapp/node_map/js/options.jsp b/opennms-webapp/src/main/webapp/node_map/js/options.jsp new file mode 100644 index 0000000..a6840e4 --- /dev/null +++ b/opennms-webapp/src/main/webapp/node_map/js/options.jsp @@ -0,0 +1,55 @@ +<%@ page contentType="application/javascript" language="java"%> + +<%! + // Do special handling for external URLs without a leading http:// + // + // This basically allows handling of the OpenLayers and Tilecache URLs if + // the services are running on the same machine as OpenNMS. To avoid hassle + // with multiple - potentially invisible to each other - network interfaces + // we just munge the relative URL to the normal apache port. + // + // Of course if your serving from somewhere else or OpenNMS isn't on 8980 this + // will break fairly hard. + // + public static String rationalise_external_url(HttpServletRequest request, String url) { + if (!url.startsWith("http://")) { + StringBuffer requestURL = request.getRequestURL(); + String baseURL = requestURL.substring(0, requestURL.indexOf(":8980")); + url = baseURL + url; + } + return url; + } +%> + +<% + String openlayers_url = rationalise_external_url(request, System.getProperty("slippy.openlayers.url")); + String tilecache_url = rationalise_external_url(request, System.getProperty("slippy.tilecache.url")); +%> +/* + Node Map Options + + This JSP JavaScript is responsible for setting the various options needed + to instantiate the OpenLayers map. Some if fixed, some needs to come from + OpenNMS itself. +*/ + + +var onms; +if (!onms) onms = {}; +if (!onms.vs) onms.vs = {}; + +if (!onms.vs.NodeMapOptions) { + onms.vs.NodeMapOptions = {}; + + // OpenNMS servlets + onms.vs.NodeMapOptions.NODE_REQUEST_URL = "/opennms/nodemap/getMapNodes"; + onms.vs.NodeMapOptions.CAT_REQUEST_URL = "/opennms/nodemap/getMapViewCategories"; + onms.vs.NodeMapOptions.MAP_ID = "map"; + onms.vs.NodeMapOptions.CAT_ID = "categories"; + onms.vs.NodeMapOptions.UPDATE_ID = "update_time"; + onms.vs.NodeMapOptions.CLUSTER_THRESH = 20; + + // Location of OpenLayers and Tilecache + <% out.println("onms.vs.NodeMapOptions.TILECACHE_URL=\"" + tilecache_url + "/tilecache.cgi?\";"); %> + <% out.println("onms.vs.NodeMapOptions.OPENLAYERS_BASE=\"" + openlayers_url + "\";"); %> +} diff --git a/opennms-webapp/src/main/webapp/node_map/js/utils.js b/opennms-webapp/src/main/webapp/node_map/js/utils.js new file mode 100644 index 0000000..92bf6a7 --- /dev/null +++ b/opennms-webapp/src/main/webapp/node_map/js/utils.js @@ -0,0 +1,24 @@ +/* + Node Map utility functions +*/ +var onms; +if (!onms) onms = {}; +if (!onms.vs) onms.vs = {}; +if (!onms.vs.utils) onms.vs.utils = {}; + +if (!onms.vs.utils.projection_lonlat) { + onms.vs.utils.projection_lonlat = function(map, node) { + var location = new OpenLayers.LonLat(node.longitude, node.latitude); + var latlon_proj = new OpenLayers.Projection("EPSG:4326"); + return location.transform(latlon_proj, map.getProjectionObject()); + }; +} + +if (!onms.vs.utils.projection_lonlat_from_4326) { + onms.vs.utils.projection_lonlat_from_4326 = function(map, node) { + var location = new OpenLayers.LonLat(node.longitude, node.latitude); + var latlon_proj = new OpenLayers.Projection("EPSG:900913"); + var latlon_proj2 = new OpenLayers.Projection("EPSG:4326"); + return location.transform(latlon_proj, latlon_proj2); + }; +} -- 1.7.5.2 ------------------------------------------------------------------------------ Simplify data backup and recovery for your virtual environment with vRanger. Installation's a snap, and flexible recovery options mean your data is safe, secure and there when you need it. Data protection magic? Nope - It's vRanger. Get your free trial download today. http://p.sf.net/sfu/quest-sfdev2dev _______________________________________________ Please read the OpenNMS Mailing List FAQ: http://www.opennms.org/index.php/Mailing_List_FAQ opennms-devel mailing list To *unsubscribe* or change your subscription options, see the bottom of this page: https://lists.sourceforge.net/lists/listinfo/opennms-devel