| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| /** |
| * |
| * This program implements the QMF Console User Interface logic for qmf.html |
| * |
| * It has dependencies on the following: |
| * qmf.css |
| * itablet.css |
| * iscroll.js |
| * jquery.js (jquery-1.7.1.min.js) |
| * itablet.js |
| * excanvas.js (for IE < 9 only) |
| * qpid.js |
| * |
| * author Fraser Adams |
| */ |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| // Create a new namespace for the qmfui "package". |
| var qmfui = {}; |
| qmfui.TOUCH_ENABLED = 'ontouchstart' in window && !((/hp-tablet/gi).test(navigator.appVersion)); |
| qmfui.END_EV = (qmfui.TOUCH_ENABLED) ? "touchend" : "mouseup"; |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * This class holds the history of various key statistics that may be held for some QMF |
| * Management Objects so we may see how the state has changes over a particular time range. |
| */ |
| qmfui.Statistics = function(description) { |
| this.description = description; // Array describing the contents of each stored statistic |
| this.short = new util.RingBuffer(60); // Statistics for a 10 minute period (10*60/REFRESH_PERIOD) |
| this.medium = new util.RingBuffer(60); // Statistics for a 1 hour period (Entries are updated every minute) |
| this.long = new util.RingBuffer(144); // Statistics for a 1 day period (Entries are updated every 10 minutes) |
| |
| /** |
| * Add an item to the end of each statistic buffer. |
| * @param item an array containing the current statistics for each property that we want to hold for a |
| * Management Object, the last item in the array is the Management Object's update timestamp. |
| * As an example for the connection Management Object we would do: |
| * stats.put([connection.msgsFromClient, connection.msgsToClient, connection._update_ts]); |
| * This approach is a little ugly and not terribly OO, but it's pretty memory efficient, which is |
| * important as there could be lots of Management Objects on a heavily utilised broker. |
| */ |
| this.put = function(item) { |
| var TIMESTAMP = item.length - 1; // The timestamp is stored as the last item of each sample. |
| var timestamp = item[TIMESTAMP]; |
| |
| var lastItem = this.short.getLast(); |
| if (lastItem == null) { |
| this.short.put(item); // Update the 10 minute period statistics. |
| } else { |
| var lastTimestamp = lastItem[TIMESTAMP]; |
| // 9000000000 is 9 seconds in nanoseconds. If the time delta is less than 9 seconds we hold off adding |
| // the sample otherwise the ring buffer will end up holding less than the full 10 minutes worth. |
| if ((timestamp - lastTimestamp) >= 9000000000) { |
| this.short.put(item); // Update the 10 minute period statistics. |
| } |
| } |
| |
| var lastItem = this.medium.getLast(); |
| if (lastItem == null) { |
| this.medium.put(item); // Update the 1 hour period statistics. |
| } else { |
| var lastTimestamp = lastItem[TIMESTAMP]; |
| // 59000000000 is 59 seconds in nanoseconds. We use 59 seconds rather than 60 seconds because the |
| // update period has a modest +/i variance around 10 seconds. |
| if ((timestamp - lastTimestamp) >= 59000000000) { |
| this.medium.put(item); // Update the 1 hour period statistics. |
| } |
| } |
| |
| lastItem = this.long.getLast(); |
| if (lastItem == null) { |
| this.long.put(item); // Update the 1 day period statistics. |
| } else { |
| var lastTimestamp = lastItem[TIMESTAMP]; |
| // 599000000000 is 599 seconds in nanoseconds (just short of 10 minutes). We use 599 seconds rather |
| // than 600 seconds because the update period has a modest +/ variance around 10 seconds. |
| if ((timestamp - lastTimestamp) >= 599000000000) { |
| this.long.put(item); // Update the 1 day period statistics. |
| } |
| } |
| }; |
| |
| /** |
| * This method computes the most recent instantaneous rate for the property specified by the index. |
| * For example for the Connection Management Object an index of 1 would represent msgsToClient as |
| * described in the comments for put(). Note that the rate that is returned is the most recent |
| * instantaneous rate, which means that it uses the samples held in the ring buffer used to hold |
| * the ten minute window. |
| * @param the index of the property that we wish to obtain the rate for. |
| * @return the most recent intantaneous rate in items/s |
| */ |
| this.getRate = function(index) { |
| var size = this.short.size(); |
| if (size < 2) { |
| return 0; |
| } |
| |
| var s1 = this.short.get(size - 2); |
| var t1 = s1[s1.length - 1]; |
| |
| var s2 = this.short.get(size - 1); |
| var t2 = s2[s2.length - 1]; |
| |
| var delta = (t2 == t1) ? 0.0000001 : t2 - t1; // Shouldn't happen, but this tries to avoid divide by zero. |
| var rate = ((s2[index] - s1[index]) * 1000000000)/delta |
| return rate; |
| }; |
| }; |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Helper Methods to provide a consistent way to render the UI lists. |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * This helper method renders the specified properties of the specifed JavaScript object to the specified html list. |
| * @param list jQuery object representing the html list (ul) we wish to populate. |
| * @param object the object whose properties we wish to render. |
| * @param props an array of properties that we wish to render. |
| * @param href optional string specifying URL fragment to e.g. "#graphs?connectionId=" + connectionId. |
| */ |
| qmfui.renderObject = function(list, object, props, href, useIndex) { |
| iTablet.renderList(list, function(i) { |
| var key = props[i]; |
| var value = object[key]; |
| if (value == null) { // Only show properties that are actually available. |
| return false; |
| } else { |
| if (href) { |
| var anchor = href + "&property=" + i; |
| return "<li class='arrow'><a href='" + anchor + "'>" + key + "<p>" + value + "</p></a></li>"; |
| } else { |
| return "<li><a href='#'>" + key + "<p>" + value + "</p></a></li>"; |
| } |
| } |
| }, props.length); |
| }; |
| |
| /** |
| * This helper method renders the specified list of html list items (li) to the specified html list. |
| * @param list jQuery object representing the html list (ul) we wish to populate. |
| * @param array the array of html list items (li) that we wish to render. |
| */ |
| qmfui.renderArray = function(list, array) { |
| iTablet.renderList(list, function(i) { |
| return array[i]; |
| }, array.length); |
| }; |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Main Console Class |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the main Console class. |
| * This class contains the QMF Console and and caches the core QMF management objects. Caching the getObjects() |
| * results is obviously sensible and helps avoid the temptation to call getObjects() elsewhere which is rather |
| * inefficient as it is invoked using AMQP request/response but morever in the case of this UI is is called via |
| * a REST proxy. Caching getObjects() calls also helps to abstract the asynchronous nature of JavaScript as the |
| * cache methods can be called synchronously which "feels" a more natural way to get the data. |
| * |
| * This class is also responsible for initialising the rest of the pages when jQuery.ready() fires and updating |
| * them when QMF object updates occur, in other words it might be considered "Main". |
| * |
| * This class handles the QMF Console Connection lifecycle management. It's worth pointing out that it's fairly |
| * subtle and complex particularly due to the asynchronous nature of JavaScript. It's made even more complex |
| * by the fact that there are two distinct ways used to decide when to get the QMF Management Objects. The default |
| * way is where QMF Event delivery is enabled, in this case the onEvent() method is triggered periodically by |
| * the underlying QMF Console's Event dispatcher and in this case the Event dispatcher takes care of reconnection |
| * attempts. However if disableEvents is selected then the QMF Management Objects are retrieved via a timed |
| * poll. In this case this method must correctly start the pollForData() but must also let it expire if the user |
| * selects a new Connection that has QMF Event deliver enabled. |
| */ |
| qmfui.Console = new function() { |
| /** |
| * This is an array of QMF Console Connections that the user is interested in. It gets initiated with the |
| * default connection (that is to say the connection that the REST API has been configured to use if no |
| * explicit URL has been supplied). Using a Connection URL of "" makes the REST API use its default. |
| * This property has been exposed as a "public" property of qmfui.Console because then it becomes possible |
| * to "configure" the initial set of consoleConnections via a trivial config.js file containing: |
| * qmfui.Console.consoleConnections = [{name: "default", url: ""},{name: "wildcard", url: "0.0.0.0:5672", |
| * connectionOptions: {sasl_mechs:"ANONYMOUS"}, disableEvents: true}]; |
| * i.e. a JSON array of Console Connection settings. |
| */ |
| this.consoleConnections = [{name: "default", url: ""}]; |
| |
| var _objects = {}; // A map used to cache the QMF Management Object lists returned by getObjects(). |
| var _disableEvents = false; // Set for Connections that have Events disabled and thus refresh via timed polling. |
| var _polling = false; // Set when the timed polling is active so we can avoid starting it multiple times. |
| var _receivedData = false; // This flag is used to tell the difference between a failure to connect and a disconnect. |
| var _console = null; // The QMF Console used to retrieve information from the broker. |
| var _connection = null; |
| var _activeConsoleConnection = 0; // The index of the currently active QMF Console Connection. |
| |
| /** |
| * Resets the messages that get rendered if the REST API Server or the Qpid broker fail. |
| */ |
| var resetErrorMessages = function() { |
| $("#restapi-disconnected").hide(); |
| $("#broker-disconnected").hide(); |
| $("#failed-to-connect").hide(); |
| }; |
| |
| /** |
| * Show Rest API Disconnected Message, hide the others. |
| */ |
| var showRestAPIDisconnected = function() { |
| resetErrorMessages(); |
| $("#restapi-disconnected").show(); |
| }; |
| |
| /** |
| * Show Broker Disconnected Message, hide the others. |
| */ |
| var showBrokerDisconnected = function() { |
| resetErrorMessages(); |
| $("#broker-disconnected").show(); |
| }; |
| |
| /** |
| * Show Failed to Connect Message, hide the others. |
| */ |
| var showFailedToConnect = function() { |
| resetErrorMessages(); |
| $("#failed-to-connect").show(); |
| }; |
| |
| /** |
| * QMF2 EventHandler that we register with the QMF2 Console. |
| * @param workItem a QMF2 API WorkItem. |
| */ |
| var onEvent = function(workItem) { |
| if (workItem._type == "AGENT_DELETED") { |
| var agent = workItem._params.agent; |
| |
| if (agent._product == "qpidd" && _connection != null) { |
| if (_receivedData) { |
| showBrokerDisconnected(); |
| } else { |
| showFailedToConnect(); |
| } |
| } else if (agent._product == "qpid.restapi") { |
| showRestAPIDisconnected(); |
| } |
| } else { |
| _receivedData = true; |
| resetErrorMessages(); |
| if (workItem._type == "EVENT_RECEIVED") { |
| qmfui.Events.update(workItem); |
| } |
| } |
| |
| // onEvent() will be called periodically by the broker heartbeat events, so we use that fact to trigger |
| // a call to getAllObjects() which will itself trigger a call to updateState() when its results return. |
| getAllObjects(); |
| }; |
| |
| /** |
| * This method is called if startConsole() has been called with disableEvents. In this state |
| * the onEvent() method won't be triggered by the QMF2 callback so we need to explicitly poll via a timer. |
| */ |
| var pollForData = function() { |
| if (_connection != null && _disableEvents) { |
| _polling = true; |
| getAllObjects(); |
| setTimeout(function() { |
| pollForData(); |
| }, qmf.REFRESH_PERIOD); |
| } else { |
| _polling = false; |
| } |
| }; |
| |
| /** |
| * This method is called by the failure handler of getAllObjects(). If it is triggered it means there has |
| * been some form of Server side disconnection. If this occurs set an error banner then attempt to re-open |
| * the Qpid Connection. If the Connection reopens successfully updateState() will start getting called again. |
| * This method returns immediately if _disableEvents is false because the underlying QMF Console has its own |
| * reconnection logic in its Event dispatcher, which we only need to replicate if that is disabled. |
| * @param xhr the jQuery XHR object. |
| */ |
| var timeout = function(xhr) { |
| if (_disableEvents) { |
| if (xhr.status == 0) { |
| showRestAPIDisconnected(); |
| } else { |
| if (_receivedData) { |
| showBrokerDisconnected(); |
| } else { |
| showFailedToConnect(); |
| } |
| if (xhr.status == 404 && _connection != null && _connection.open) { |
| _connection.open(); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * This method is called when getAllObjects() completes, that is to say when all of the asynchronous responses |
| * for the Deferred XHR objects returned by getObjects() have all returned successfully. This method triggers |
| * the update() method on each of the user interface pages. |
| */ |
| var updateState = function() { |
| _receivedData = true; |
| resetErrorMessages(); |
| |
| qmfui.Broker.update(); |
| qmfui.Connections.update(); |
| qmfui.Exchanges.update(); |
| qmfui.Queues.update(); |
| //qmfui.Links.update(); // TODO |
| //qmfui.RouteTopology.update(); // TODO |
| |
| // Update sub-pages after main pages as these may require state (such as statistics) set in the main pages. |
| qmfui.SelectedConnection.update(); |
| qmfui.SelectedQueue.update(); |
| qmfui.QueueSubscriptions.update(); |
| qmfui.ConnectionSubscriptions.update(); |
| qmfui.SelectedExchange.update(); |
| qmfui.Bindings.update(); |
| qmfui.Graphs.update(); |
| }; |
| |
| /** |
| * This method retrieves the QmfConsoleData Objects from the real QMF Console via AJAX calls to the REST API. |
| * Because the AJAX calls are all asynchronous we use jQuery.when(), which provides a way to execute callback |
| * functions based on one or more objects, usually Deferred objects that represent asynchronous events. |
| * See http://api.jquery.com/jQuery.when/ and http://api.jquery.com/deferred.then/ |
| */ |
| var getAllObjects = function() { |
| $.when( |
| _console.getObjects("broker", function(data) {_objects.broker = data;}), |
| _console.getObjects("queue", function(data) {_objects.queue = data;}), |
| _console.getObjects("exchange", function(data) {_objects.exchange = data;}), |
| _console.getObjects("binding", function(data) {_objects.binding = data;}), |
| _console.getObjects("subscription", function(data) {_objects.subscription = data;}), |
| _console.getObjects("connection", function(data) {_objects.connection = data;}), |
| // _console.getObjects("link", function(data) {_objects.link = data;}), |
| // _console.getObjects("bridge", function(data) {_objects.bridge = data;}), |
| _console.getObjects("session", function(data) {_objects.session = data;}) |
| ).then(updateState, timeout); |
| }; |
| |
| /** |
| * Handle the load event, triggered when the document has completely loaded. |
| */ |
| var loadHandler = function() { |
| qmfui.Console.startConsole(0); // Start the default QMF Console Connection. |
| }; |
| |
| /** |
| * Handle the unload event, triggered when the document unloads or we navigate off the page. It's not 100% |
| * reliable, which is a shame as it's the best way to clear up Server state. If it fails the Server will |
| * eventually clear up unused Connections after a timeout period. TODO Opera seems to be especially bad at |
| * firing the unloadHandler, not sure why this is, something to look into. |
| * @param event the event that triggered the unloadHandler. |
| */ |
| var unloadHandler = function(event) { |
| // For mobile Safari (at least) we get pagehide events when navigating off the page, but also when closing via |
| // home or locking the device. Fortunately this case has the persisted flag set as we don't want to close then. |
| var persisted = (event.type == "pagehide") ? event.originalEvent.persisted : false; |
| if (!persisted) { |
| qmfui.Console.stopConsole(); |
| } |
| }; |
| |
| /** |
| * Callback handler triggered when _console.addConnection() fails. This should only occur if an actual |
| * exception occurs on the REST API Server due to invalid connectionOptions. |
| */ |
| var handleConnectionFailure = function() { |
| qmfui.Console.stopConsole(); |
| showFailedToConnect(); |
| } |
| |
| // ******************************************* Accessor Methods ******************************************* |
| |
| /** |
| * Retrieve the broker Management Object, optionally as a QmfConsoleData Object (with invokeMethod attached). |
| * @param makeConsoleData if true attach the invokeMethod method to the returned Management Object. |
| * @return the QMF broker Management Object optionally as a QmfConsoleData. |
| */ |
| this.getBroker = function(makeConsoleData) { |
| var brokers = _objects.broker; |
| |
| if (brokers == null || brokers.length == 0) { |
| // Return a fake QmfConsoleData Object with an invokeMethod that calls the callback handler with a |
| // response object containing error_text. It is actually pretty uncommon for the brokers array to |
| // be empty so this approach allows us to call invokeMethod without lots of checking for broker == null. |
| return {invokeMethod: function(name, inArgs, handler) { |
| handler({"error_text" : "Could not retrieve broker Management Object"}); |
| }}; |
| } else { |
| var broker = brokers[0]; |
| if (makeConsoleData) { |
| _console.makeConsoleData(broker); |
| } |
| return broker; |
| } |
| }; |
| |
| /** |
| * These methods are basic accessors returning the cached lists of QmfData objects returned by getObjects() |
| */ |
| this.getQueues = function() { |
| return _objects.queue; |
| }; |
| |
| this.getExchanges = function() { |
| return _objects.exchange; |
| }; |
| |
| this.getBindings = function() { |
| return _objects.binding; |
| }; |
| |
| this.getSubscriptions = function() { |
| return _objects.subscription; |
| }; |
| |
| this.getConnections = function() { |
| return _objects.connection; |
| }; |
| |
| this.getSessions = function() { |
| return _objects.session; |
| }; |
| |
| /* TODO |
| this.getLinks = function() { |
| return _objects.link; |
| }; |
| |
| this.getBridges = function() { |
| return _objects.bridge; |
| }; |
| */ |
| |
| /** |
| * Calls the underlying QMF Console's makeConsoleData(). This call turns a QmfData object into a QmfConsoleData |
| * object, which adds methods such as invokeMethod() to the object. |
| * @param the object that we want to turn into a QmfConsoleData. |
| */ |
| this.makeConsoleData = function(object) { |
| _console.makeConsoleData(object); |
| }; |
| |
| /** |
| * @return the list of QMF Console Connections that the user is interested in. The returned list is a list of |
| * objects containing url, name and connectionOptions properties. |
| */ |
| this.getConsoleConnectionList = function() { |
| return this.consoleConnections; |
| }; |
| |
| /** |
| * Note that in the following it's important to note that there is separation between adding/removing |
| * Console Connections and actually starting/stopping Console Connections, this is because a user may wish |
| * to add several QMF Console Connections to point to a number of different brokers before actually |
| * chosing to connect to a particular broker. Similarly a user may wish to delete a QMF Console Connection |
| * from the list (s)he is interested in independently from selecting a new connection. |
| */ |
| |
| /** |
| * Append a new Console Connection with the specified url, name and connectionOptions to the end of the |
| * list of QMF Console Connections the the user many be interested in. Note that this method *does not* |
| * actually start a connection to the new console for that we must call the startConsole() method. |
| * The name supplied in this method is simply a user friendly name and is not related to the name that |
| * may be applied to the Connection when it is stored on the REST API, that name is really best considered |
| * as an opaque "handle" and is intended to be unique for each connection. |
| * |
| * @param name a user friendly name for the Console Connection. |
| * @param url the broker Connection URL in one of the formats supported by the Java ConnectionHelper class |
| * namely an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Java Connection URL. |
| * @param connectionOptions a JSON string containing the Connection Options in the same form as used |
| * in the qpid::messaging API. |
| */ |
| this.addConsoleConnection = function(name, url, connectionOptions, disableEvents) { |
| if (disableEvents) { |
| this.consoleConnections.push({name: name, url: url, connectionOptions: connectionOptions, |
| disableEvents: true}); |
| } else { |
| this.consoleConnections.push({name: name, url: url, connectionOptions: connectionOptions}); |
| } |
| }; |
| |
| /** |
| * Remove the Console Connection specified by the index. Note that this method *does not* actually stop |
| * a connection to the console for that we must call the stopConsole() method. |
| * @param index the index of the Console Connection that we want to remove. |
| */ |
| this.removeConsoleConnection = function(index) { |
| if (_activeConsoleConnection > index) { |
| _activeConsoleConnection--; |
| } |
| this.consoleConnections.splice(index, 1); // remove a single array item at the specified index. |
| }; |
| |
| /** |
| * Actually start the Qpid Connection and QMF Console for the Console Connection stored at the specified index. |
| * When the QMF Console successfully starts it will start sending QMF Events and updating the Management |
| * Objects automatically, this will in turn cause the User Interface pages to refresh. |
| * Alternatively if QMF Event delivery is disabled this method initiates the pollForData(). |
| * @param index the index of the Console Connection that we wish to start. Index zero is the default Console. |
| */ |
| this.startConsole = function(index) { |
| var connection = this.consoleConnections[index]; |
| |
| // Using a Connection URL of "" makes the REST API use its default configured broker connection. |
| var factory = new qpid.ConnectionFactory(connection.url, connection.connectionOptions); |
| _connection = factory.createConnection(); |
| |
| // Initialise QMF Console |
| _console = new qmf.Console(onEvent); |
| if (connection.disableEvents != null) { |
| _disableEvents = true; |
| _console.disableEvents(); |
| } else { |
| _disableEvents = false; |
| } |
| |
| _receivedData = false; |
| _console.addConnection(_connection, handleConnectionFailure); |
| _activeConsoleConnection = index; |
| |
| // If disableEvents is set we have to use a timed poll to get the Management Objects. We check if the polling |
| // loop is already running, because if we try and start it multiple times we may get spurious refreshes. |
| if (_disableEvents && !_polling) { |
| pollForData(); |
| } |
| }; |
| |
| /** |
| * Stops the currently running Console Connection and closes the Qpid Connection. This will result in the |
| * underlying Connection object on the REST API Server getting properly cleaned up. It's not essential |
| * to call stopConsole() before a call to startConsole() as the server Connection objects will eventually |
| * time out, but it's good practice to do it if at all possible. |
| */ |
| this.stopConsole = function() { |
| if (_console) { |
| _console.destroy(); |
| } |
| |
| if (_connection) { |
| _connection.close(); |
| _connection = null; |
| } |
| }; |
| |
| /** |
| * @return the index of the currently active (connected) QMF Console Connection. |
| */ |
| this.getActiveConsoleConnection = function() { |
| return _activeConsoleConnection; |
| }; |
| |
| // *********************** Initialise class when the DOM loads using jQuery.ready() *********************** |
| $(function() { |
| // Create a fake logging console for browsers that don't have a real console.log - only for debugging. |
| if (!window.console) { |
| /* // A slightly hacky console.log() to help with debugging on old versions of IE. |
| console = window.open("", "console", "toolbar, menubar, status, width=500, height=500, scrollbars=yes"); |
| console.document.open("text/plain"); |
| console.log = function(text) {console.document.writeln(text);};*/ |
| |
| console = {log: function(text) {}}; // Dummy to avoid bad references in case logging accidentally added. |
| } |
| |
| // Add a default show handler. Pages that bind update() to show should remove this by doing unbind("show"). |
| $(".main").bind("show", function() {$("#resource-deleted").hide();}); |
| |
| // pagehide and unload each work better than the other in certain circumstances so we trigger on both. |
| $(window).bind("unload pagehide", unloadHandler); |
| |
| // Iterate through each page calling its initialise method if one is present. |
| for (var i in qmfui) { |
| if (qmfui[i].initialise) { |
| qmfui[i].initialise(); |
| } |
| } |
| |
| // Send a synthesised click event to the settings-tab element to select the settings page on startup. |
| // We check if the left property of the main class is zero, if it is then the sidebar has been expanded |
| // to become the main menu (e.g. for mobile devices) otherwise we show the settings page. |
| if (parseInt($(".main").css('left'), 10) != 0) { |
| $("#settings-tab").click(); |
| } |
| |
| // Hide the splash page. |
| $("#splash").hide(); |
| }); |
| |
| $(window).load(loadHandler); |
| }; // End of qmfui.Console definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Configure Settings |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Settings class managing the id="settings" page. |
| */ |
| qmfui.Settings = new function() { |
| /** |
| * Show the Settings page, rendering any dynamic content if necessary. |
| */ |
| var show = function() { |
| // Retrieve the currently configured QMF Console Connections. |
| var qmfConsoleConnections = qmfui.Console.getConsoleConnectionList(); |
| |
| iTablet.renderList($("#qmf-console-selector"), function(i) { |
| var qmfConsoleConnection = qmfConsoleConnections[i]; |
| var name = qmfConsoleConnection.name; |
| var url = qmfConsoleConnection.url; |
| url = (url == null || url == "") ? "" : " (" + url + ")"; |
| var label = (name == null || name == "") ? url : name + url; |
| var checked = (i == qmfui.Console.getActiveConsoleConnection()) ? "checked" : ""; |
| |
| return "<li class='arrow'><label for='qmf-console" + i + "'>" + label + "</label><input type='radio' id='qmf-console" + i + "' name='qmf-console-selector' value='" + i + "' " + checked + "/><a href='#selected-qmf-console-connection?index=" + i + "'></a></li>"; |
| }, qmfConsoleConnections.length); |
| |
| $("#qmf-console-selector input").change(changeConsole); |
| }; |
| |
| /** |
| * If the settings-hide-qmf-objects checkbox gets changed refresh the Queues and Exchanges pages to reflect this. |
| */ |
| var changeHideQmf = function() { |
| qmfui.Queues.update(); |
| qmfui.Exchanges.update(); |
| }; |
| |
| /** |
| * Handles changes to the Console selection Radio buttons. If a change occurs the Console Connection is stopped |
| * and the newly selected Console Connection is started (we chose based on the index into the list) |
| */ |
| var changeConsole = function() { |
| qmfui.Console.stopConsole(); |
| qmfui.Console.startConsole($(this).val()); |
| }; |
| |
| this.initialise = function() { |
| $("#settings").bind("show", show); |
| $("#settings-hide-qmf-objects").change(changeHideQmf); |
| }; |
| }; // End of qmfui.Settings definition |
| |
| |
| /** |
| * Create a Singleton instance of the AddConsoleConnection class managing the id="add-console-connection" page. |
| */ |
| qmfui.AddConsoleConnection = new function() { |
| var submit = function() { |
| var consoleURL = $("#console-url"); |
| |
| // Check that a URL value has been supplied. TODO Probably worth doing some validation that the supplied |
| // Connection URL is at least syntactically valid too. |
| var url = consoleURL.val(); |
| if (url == "") { |
| consoleURL.addClass("error"); |
| return; |
| } else { |
| consoleURL.removeClass("error"); |
| } |
| |
| var name = $("#console-name").val(); |
| try { |
| var connectionOptions = $.parseJSON($("#add-connection-options textarea").val()); |
| // TODO worth checking that the connection options are valid and in a usable format. Connection Options |
| // is still a bit of a work in progressed. though it seems to work fine if sensible options are used. |
| qmfui.Console.addConsoleConnection(name, url, connectionOptions, $("#console-disable-events")[0].checked); |
| iTablet.location.back(); |
| } catch(e) { |
| setTimeout(function() { |
| alert("Connection Options must be entered as a well-formed JSON string."); |
| return; |
| }, 0); |
| } |
| }; |
| |
| this.initialise = function() { |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#add-console-connection .right.button").bind(qmfui.END_EV, submit); |
| }; |
| }; // End of qmfui.AddConsoleConnection definition |
| |
| |
| /** |
| * Create a Singleton instance of the SelectedQMFConsoleConnection class managing the |
| * id="selected-qmf-console-connection" page. |
| */ |
| qmfui.SelectedQMFConsoleConnection = new function() { |
| var _index = null; |
| var _name = ""; |
| var _url = ""; |
| |
| /** |
| * This method deletes the selected QMFConsoleConnection. |
| */ |
| var deleteHandler = function() { |
| // Flag to check if the Console we're trying to delete is the currently selected/connected one. |
| var currentlySelected = ($("#qmf-console-selector input[checked]").val() == _index); |
| |
| // Text for the confirm dialogue with additional wording if it's currently connected. |
| var confirmText = 'Delete QMF Connection "' + _name + '" to ' + _url + '?'; |
| confirmText += currentlySelected ? "\nNote that this is the current active Connection, so deleting it will cause a reconnection to the default Connection." : "" |
| |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| setTimeout(function() { |
| if (confirm(confirmText) == false) { |
| return; |
| } else { |
| qmfui.Console.removeConsoleConnection(_index); |
| // If the QMF Console Connection being deleted is the currently connected one we stop the Console |
| // and establish a connection to the default QMF Console Connection. |
| if (currentlySelected) { |
| qmfui.Console.stopConsole(); |
| qmfui.Console.startConsole(0); // Start the default QMF Console Connection. |
| } |
| |
| iTablet.location.back(); // Navigate to the previous page. |
| } |
| }, 0); |
| }; |
| |
| var show = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#selected-qmf-console-connection") { |
| return; |
| } |
| |
| // Get the selected QMFConsoleConnection |
| _index = parseInt(data.index); // The parseInt is important! Without it the index lookup gives odd results.. |
| var qmfConsoleConnections = qmfui.Console.getConsoleConnectionList(); |
| var qmfConsoleConnection = qmfConsoleConnections[_index]; |
| _name = qmfConsoleConnection.name; |
| _url = qmfConsoleConnection.url; |
| var connectionOptions = qmfConsoleConnection.connectionOptions; |
| |
| var eventsDisabled = false; |
| if (qmfConsoleConnection.disableEvents != null) { |
| eventsDisabled = qmfConsoleConnection.disableEvents; |
| } |
| |
| // If the selected Console is the default one hide the delete button, otherwise show it. |
| if (_index == 0) { |
| $("#selected-qmf-console-connection .header a.delete").hide(); |
| } else { |
| $("#selected-qmf-console-connection .header a.delete").show(); |
| } |
| |
| // Populate the page header with the Console Connection name/url. |
| var urlText = (_url == null || _url == "") ? "" : " (" + _url + ")"; |
| var label = (_name == null || _name == "") ? urlText : _name + urlText; |
| $("#selected-qmf-console-connection .header h1").text(label); |
| |
| $("#selected-qmf-console-connection-url p").text(((_url == "") ? 'default' : _url)); |
| $("#selected-qmf-console-connection-name p").text(((_name == "") ? '""' : _name)); |
| $("#selected-qmf-console-connection-events-disabled p").text(eventsDisabled); |
| |
| if (_url == "") { |
| $("#selected-qmf-console-connection-default-info").show(); |
| } else { |
| $("#selected-qmf-console-connection-default-info").hide(); |
| } |
| |
| if (connectionOptions == "") { |
| $("#selected-qmf-console-connection-connection-options").hide(); |
| } else { |
| $("#selected-qmf-console-connection-connection-options textarea").val(util.stringify(connectionOptions)); |
| $("#selected-qmf-console-connection-connection-options").show(); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#selected-qmf-console-connection").unbind("show").bind("show", show); |
| |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#selected-qmf-console-connection .header a.delete").bind(qmfui.END_EV, deleteHandler); |
| }; |
| }; // End of qmfui.SelectedQMFConsoleConnection definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Broker Information |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Broker class managing the id="broker" page. |
| */ |
| qmfui.Broker = new function() { |
| /** |
| * Convert nanoseconds into hours, minutes and seconds. |
| */ |
| var convertTime = function(ns) { |
| var milliSecs = ns/1000000; |
| var msSecs = (1000); |
| var msMins = (msSecs * 60); |
| var msHours = (msMins * 60); |
| var numHours = Math.floor(milliSecs/msHours); |
| var numMins = Math.floor((milliSecs - (numHours * msHours)) / msMins); |
| var numSecs = Math.floor((milliSecs - (numHours * msHours) - (numMins * msMins))/ msSecs); |
| numSecs = numSecs < 10 ? numSecs = "0" + numSecs : numSecs; |
| numMins = numMins < 10 ? numMins = "0" + numMins : numMins; |
| |
| return (numHours + ":" + numMins + ":" + numSecs); |
| }; |
| |
| this.update = function() { |
| var broker = qmfui.Console.getBroker(); |
| broker.uptime = convertTime(broker.uptime); // Convert uptime to a more human readable value. |
| |
| qmfui.renderObject($("#broker-list"), broker, ["name", "version", "uptime", "port", "maxConns", "connBacklog", |
| "dataDir", "mgmtPublish", "mgmtPubInterval", "workerThreads", |
| /* 0.20 publishes many more stats so include them if available */ |
| "queueCount", "acquires", "releases", "abandoned", "abandonedViaAlt"]); |
| |
| // Render a number of 0.20 statistics in their own subsections to improve readability. |
| |
| // Render the Message Input Output Statistics. |
| if (broker.msgDepth == null) { |
| $("#broker-msgio-container").hide(); |
| } else { |
| $("#broker-msgio-container").show(); |
| qmfui.renderObject($("#broker-msgio"), broker, ["msgDepth", "msgTotalEnqueues", "msgTotalDequeues"]); |
| } |
| |
| // Render the Byte Input Output Statistics. |
| if (broker.byteDepth == null) { |
| $("#broker-byteio-container").hide(); |
| } else { |
| $("#broker-byteio-container").show(); |
| qmfui.renderObject($("#broker-byteio"), broker, ["byteDepth", "byteTotalEnqueues", "byteTotalDequeues"]); |
| } |
| |
| var hideDetails = $("#settings-hide-details")[0].checked; |
| |
| // Render the Flow-to-disk Statistics. |
| if (broker.msgFtdDepth == null || hideDetails) { |
| $("#broker-flow-to-disk-container").hide(); |
| } else { |
| $("#broker-flow-to-disk-container").show(); |
| qmfui.renderObject($("#broker-flow-to-disk"), broker, ["msgFtdDepth", "msgFtdEnqueues", "msgFtdDequeues", |
| "byteFtdDepth", "byteFtdEnqueues", "byteFtdDequeues"]); |
| } |
| |
| // Render the Dequeue Details. |
| if (broker.discardsTtl == null || hideDetails) { |
| $("#broker-dequeue-container").hide(); |
| } else { |
| $("#broker-dequeue-container").show(); |
| qmfui.renderObject($("#broker-dequeue"), broker, ["discardsTtl", "discardsRing", "discardsLvq", |
| "discardsOverflow", "discardsSubscriber", "discardsPurge", "reroutes"]); |
| } |
| }; |
| |
| /** |
| * This click handler is triggered by a click on the broker-log-level radio button. It invokes the QMF |
| * setLogLevel method to set the log level of the connected broker to debug or normal. |
| */ |
| var setLogLevel = function(e) { |
| var level = $(e.target).val(); |
| |
| // Set to the QMF2 levels for broker debug and normal. |
| level = (level == "debug") ? "debug+:Broker" : "notice+"; |
| |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("setLogLevel", {"level": level}, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } |
| }); |
| }; |
| |
| this.initialise = function() { |
| // Explicitly using a click handler rather than change as it's possible that another instance has changed |
| // the log level so we may want to be able to reset it by clicking the currently selected level. |
| $("#broker-log-level li input").click(setLogLevel); |
| }; |
| }; // End of qmfui.Broker definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Connection Information |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Connections class managing the id="connections" page. |
| */ |
| qmfui.Connections = new function() { |
| var _connectionMap = {}; // Connections indexed by ObjectId. |
| var _sessionMap = {}; // Sessions indexed by ObjectId. |
| var _subscriptionMap = {}; // Subscriptions indexed by ObjectId. |
| var _queueToSubscriptionAssociations = {}; // 0..* association between Queue and Subscription keyed by Queue ID. |
| |
| /** |
| * Return the Connection object indexed by QMF ObjectId. |
| */ |
| this.getConnection = function(oid) { |
| return _connectionMap[oid]; |
| }; |
| |
| /** |
| * Return the Session object indexed by QMF ObjectId. |
| */ |
| this.getSession = function(oid) { |
| return _sessionMap[oid]; |
| }; |
| |
| /** |
| * Return the Subscription object indexed by QMF ObjectId. |
| */ |
| this.getSubscription = function(oid) { |
| return _subscriptionMap[oid]; |
| }; |
| |
| /** |
| * Return the Subscription association List indexed by a queue's QMF ObjectId. |
| */ |
| this.getQueueSubscriptions = function(oid) { |
| var subs = _queueToSubscriptionAssociations[oid]; |
| return (subs == null) ? [] : subs; // If it's null set it to an empty array. |
| }; |
| |
| /** |
| * The Connections update method includes a number of subtle complexities due to the way QMF Management |
| * Objects are associated with each other. For example a Subscription is associated with a single |
| * Session however a Session may have zero or more Subscriptions, similarly a Session is associated with |
| * a single Connection but a Connection may have zero or more Sessions. |
| * |
| * The QMF Management Objects maintain the single unidirectional associations, which generally makes sense |
| * but an unfortunate side-effect of this is that if one wishes to obtain information about Sessions and |
| * Subscriptions related to a given Connection it's somewhat of a pain as one has to do a multi-pass dereference. |
| * |
| * This method does the dereferencing and creates a 0..* association to Session in each Connection object and |
| * a 0..* association to Subscription in each Session object so other pages can avoid their own dereferencing. |
| * N.B. these added associations are references to actual Session or Subscription objects and NOT via ObjectIds |
| * This is because these associations are not *really* QmfData object properties and only exist within the local |
| * memory space of this application, so using actual memory references avoids an additional dereference. |
| */ |
| this.update = function() { |
| _subscriptionMap = {}; // Clear _subscriptionMap. |
| _sessionMap = {}; // Clear _sessionMap. |
| _queueToSubscriptionAssociations = {}; // Clear _queueToSubscriptionAssociations. |
| |
| var subscriptions = qmfui.Console.getSubscriptions(); |
| for (var i in subscriptions) { |
| var subscription = subscriptions[i]; |
| var subscriptionId = subscription._object_id; |
| var sessionRef = subscription.sessionRef; |
| var queueRef = subscription.queueRef; |
| |
| // Create the Session->Subscriptions association array and store it keyed by the sessionRef, when we go |
| // to store the actual Session we can retrieve the association and store it as a property of the Session. |
| if (_sessionMap[sessionRef] == null) { |
| _sessionMap[sessionRef] = [subscription]; |
| } else { |
| _sessionMap[sessionRef].push(subscription); |
| } |
| |
| // Create the Queue->Subscriptions association array and store it keyed by the queueRef, when we go |
| // to store the actual Session we can retrieve the association and store it as a property of the Session. |
| if (_queueToSubscriptionAssociations[queueRef] == null) { |
| _queueToSubscriptionAssociations[queueRef] = [subscription]; |
| } else { |
| _queueToSubscriptionAssociations[queueRef].push(subscription); |
| } |
| |
| _subscriptionMap[subscriptionId] = subscription; // Index subscriptions by ObjectId. |
| } |
| |
| var connectionToSessionAssociations = {}; |
| var sessions = qmfui.Console.getSessions(); |
| for (var i in sessions) { |
| var session = sessions[i]; |
| var sessionId = session._object_id; |
| var connectionRef = session.connectionRef; |
| |
| var subs = _sessionMap[sessionId]; // Retrieve the association array and store it as a property. |
| subs = (subs == null) ? [] : subs; // If it's null set it to an empty array. |
| session._subscriptions = subs; |
| |
| // Create the Connection->Sessions association array and store it keyed by the connectionRef, when we go to |
| // store the actual Connection we can retrieve the association and store it as a property of the Connection. |
| if (connectionToSessionAssociations[connectionRef] == null) { |
| connectionToSessionAssociations[connectionRef] = [session]; |
| } else { |
| connectionToSessionAssociations[connectionRef].push(session); |
| } |
| _sessionMap[sessionId] = session; // Index sessions by ObjectId. |
| } |
| |
| // Temporary connections map, we move active connections to this so deleted connections wither and die. |
| var temp = {}; |
| var connections = qmfui.Console.getConnections(); |
| iTablet.renderList($("#connections-list"), function(i) { |
| var connection = connections[i]; |
| var connectionId = connection._object_id; |
| |
| // Look up the previous value for the indexed connection using its objectId. |
| var prev = _connectionMap[connectionId]; |
| var stats = (prev == null) ? new qmfui.Statistics(["msgsFromClient", "msgsToClient"]) : prev._statistics; |
| stats.put([connection.msgsFromClient, connection.msgsToClient, connection._update_ts]); |
| connection._statistics = stats; |
| |
| // Retrieve the association array and store it as a property. |
| var sessions = connectionToSessionAssociations[connectionId]; |
| sessions = (sessions == null) ? [] : sessions; // If it's null set it to an empty array. |
| connection._sessions = sessions; |
| temp[connectionId] = connection; |
| |
| // Calculate the total number of subscriptions for this connection, this is useful because a count of |
| // zero indicates that a connection is probably a producer only connection. |
| var connectionSubscriptions = 0; |
| for (var i in connection._sessions) { |
| var session = connection._sessions[i]; |
| connectionSubscriptions += session._subscriptions.length; |
| } |
| |
| var address = connection.address + " (" + connection.remoteProcessName + ")"; |
| if (connectionSubscriptions == 0) { |
| return "<li class='arrow'><a href='#selected-connection?id=" + connectionId + "'>" + address + |
| "<p>No Subscriptions</p></a></li>"; |
| } else { |
| return "<li class='arrow'><a href='#selected-connection?id=" + connectionId + "'>" + address + |
| "</a></li>"; |
| } |
| }, connections.length); |
| |
| // Replace the saved statistics with the newly populated temp instance, which only has active objects |
| // moved to it, this means that any deleted objects are no longer present. |
| _connectionMap = temp; |
| }; |
| |
| }; // End of qmfui.Connections definition |
| |
| |
| /** |
| * Create a Singleton instance of the SelectedConnection class managing the id="selected-connection" page. |
| */ |
| qmfui.SelectedConnection = new function() { |
| var _sessions = []; // Populate this with the ID of matching sessions enabling navigation to sessions. |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#selected-connection") { |
| return; |
| } |
| |
| // Get the latest statistics update of the selected connection object. |
| var connectionId = data.id; |
| var connection = qmfui.Connections.getConnection(connectionId); |
| if (connection == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| var name = connection.address + " (" + connection.remoteProcessName + ")"; |
| $("#selected-connection .header h1").text(name); |
| |
| // Populate the back button with "Subscription" or "Connections" depending on context |
| var backText = data.fromSubscription ? "Subscrip..." : "Connect..."; |
| |
| // Using $("#selected-connection .header a").text(backText) wipes all child elements so use the following. |
| $("#selected-connection .header a")[0].firstChild.nodeValue = backText; |
| |
| // Render the connection message statistics to #selected-connection-msgio |
| qmfui.renderObject($("#selected-connection-msgio"), connection, ["msgsFromClient", "msgsToClient"], |
| "#graphs?connectionId=" + connectionId); |
| |
| // Render the connection byte statistics to #selected-connection-byteio |
| qmfui.renderObject($("#selected-connection-byteio"), connection, ["bytesFromClient", "bytesToClient"]); |
| |
| // Render the connection frame statistics to #selected-connection-frameio |
| qmfui.renderObject($("#selected-connection-frameio"), connection, ["framesFromClient", "framesToClient"]); |
| |
| // Render selected general connection properties to #selected-connection-general. |
| qmfui.renderObject($("#selected-connection-general"), connection, ["federationLink", "SystemConnection", |
| "incoming", "authIdentity", "userProxyAuth", "saslMechanism", "saslSsf", "remotePid", |
| "shadow", "closing", "protocol"]); |
| |
| // Render links to the sessions associated with this connection. |
| _sessions = connection._sessions; |
| var subscribedSessions = $("#selected-connection-subscribed-sessions"); |
| var unsubscribedSessions = $("#selected-connection-unsubscribed-sessions"); |
| if (_sessions.length == 0) { // Show a message if there are no sessions at all |
| subscribedSessions.hide(); |
| subscribedSessions.prev().hide(); |
| unsubscribedSessions.show(); |
| unsubscribedSessions.prev().show(); |
| iTablet.renderList(unsubscribedSessions, function(i) { |
| return "<li class='grey'><a href='#'>There are currently no sessions attached to " + |
| name + "</a></li>"; |
| }); |
| } else { |
| var subscribed = []; |
| var unsubscribed = []; |
| for (var i in _sessions) { |
| var session = _sessions[i]; |
| var id = session._object_id; |
| var subscriptionCount = session._subscriptions.length; |
| if (subscriptionCount == 0) { |
| unsubscribed.push("<li><a href='#'>" + session.name + "</a></li>"); |
| } else { |
| var plural = subscriptionCount > 1 ? " Subscriptions" : " Subscription"; |
| subscribed.push("<li class='multiline arrow'><a href='#connection-subscriptions?id=" + id + "'><div>" + |
| session.name + "<p class='sub'>" + subscriptionCount + plural + "</p></div></a></li>"); |
| } |
| } |
| |
| if (subscribed.length > 0) { |
| subscribedSessions.show(); |
| subscribedSessions.prev().show(); |
| qmfui.renderArray(subscribedSessions, subscribed); |
| } else { |
| subscribedSessions.hide(); |
| subscribedSessions.prev().hide(); |
| } |
| |
| if (unsubscribed.length > 0) { |
| unsubscribedSessions.show(); |
| unsubscribedSessions.prev().show(); |
| qmfui.renderArray(unsubscribedSessions, unsubscribed); |
| } else { |
| unsubscribedSessions.hide(); |
| unsubscribedSessions.prev().hide(); |
| } |
| } |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#selected-connection").unbind("show").bind("show", qmfui.SelectedConnection.update); |
| }; |
| }; // End of qmfui.SelectedConnection definition |
| |
| |
| /** |
| * Create a Singleton instance of the ConnectionSubscriptions class managing the id="connection-subscriptions" page. |
| * This page is slightly different than most of the others as there can be multiple subscriptions and thus multiple |
| * arbitrary lists. Most pages have tried to reuse HTML list items for efficiency but in this page we clear and |
| * regenerate the contents of the page div each update as it's simpler than attempting to reuse elements. |
| */ |
| qmfui.ConnectionSubscriptions = new function() { |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#connection-subscriptions") { |
| return; |
| } |
| |
| var sessionId = data.id; |
| var session = qmfui.Connections.getSession(sessionId); |
| if (session == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked; |
| |
| // Populate the page header with the session name. |
| $("#connection-subscriptions .header h1").text(session.name); |
| |
| var subscriptions = session._subscriptions; |
| var length = subscriptions.length; |
| |
| var page = $("#connection-subscriptions .page"); |
| page.children().remove(); // Clear the contents of the page div. |
| |
| for (var i = 0; i < length; i++) { |
| var subscription = subscriptions[i]; |
| var id = subscription.queueRef; |
| var queue = qmfui.Queues.getQueue(id); |
| |
| if (i == 0) { |
| page.append("<h1 class='first'>Subscription 1</h1>"); |
| } else { |
| page.append("<h1>Subscription " + (i + 1) + "</h1>"); |
| } |
| |
| var name = $("<ul id='connection-subscription-name" + i + "' class='list'></ul>"); |
| page.append(name); |
| |
| // Render the associated queue name to #connection-subscription-name. |
| var isQmfQueue = queue._isQmfQueue; |
| iTablet.renderList(name, function(i) { |
| // If the associated Queue is a QMF Queue and hideQmfObjects has been selected render the |
| // Queue name grey and make it non-navigable otherwise render normally. |
| if (isQmfQueue && hideQmfObjects) { |
| return "<li class='grey'><a href='#selected-queue?id=" + id + "&fromSubscriptions=true'>" + |
| queue.name + "</a></li>"; |
| } else { |
| return "<li class='arrow'><a href='#selected-queue?id=" + id + "&fromSubscriptions=true'>" + |
| queue.name + "</a></li>"; |
| } |
| }); |
| |
| page.append("<p/>"); |
| |
| var list = $("<ul id='connection-subscription" + i + "' class='list'></ul>"); |
| page.append(list); |
| |
| // Render the useful subscription properties to #connection-subscriptions-list. |
| qmfui.renderObject(list, subscription, |
| ["delivered", "browsing", "acknowledged", "exclusive", "creditMode"]); |
| } |
| |
| $("#connection-subscriptions").trigger("refresh"); // Make sure touch scroller is up-to-date. |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#connection-subscriptions").unbind("show").bind("show", qmfui.ConnectionSubscriptions.update); |
| }; |
| }; // End of qmfui.ConnectionSubscriptions definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Exchange Information |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Exchanges class managing the id="exchanges" page. |
| */ |
| qmfui.Exchanges = new function() { |
| var QMF_EXCHANGES = {"qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true}; |
| var _exchangeNameMap = {}; // Exchanges indexed by name. |
| var _exchangeMap = {}; // Exchanges indexed by ObjectId. |
| |
| /** |
| * Return the Exchange object for the given QMF ObjectId. The Exchange object contains the latest update |
| * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period. |
| */ |
| this.getExchange = function(oid) { |
| return _exchangeMap[oid]; |
| }; |
| |
| /** |
| * Return the Exchange object with the given name. The Exchange object contains the latest update |
| * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period. |
| */ |
| this.getExchangeByName = function(name) { |
| return _exchangeNameMap[name]; |
| }; |
| |
| this.update = function() { |
| var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked; |
| |
| // We move active exchanges to temp so deleted exchanges wither and die. We can't just do _exchangeMap = {} |
| // as we need to retrieve and update statistics from any active (non-deleted) exchange. |
| var temp = {}; |
| _exchangeNameMap = {}; // We can simply clear the map of exchanges indexed by name though. |
| var exchanges = qmfui.Console.getExchanges(); |
| iTablet.renderList($("#exchanges-list"), function(i) { |
| var exchange = exchanges[i]; |
| |
| // Look up the previous value for the indexed exchange using its objectId. |
| var prev = _exchangeMap[exchange._object_id]; |
| var stats = (prev == null) ? new qmfui.Statistics(["msgReceives", "msgRoutes", "msgDrops"]) : |
| prev._statistics; |
| |
| stats.put([exchange.msgReceives, exchange.msgRoutes, exchange.msgDrops, exchange._update_ts]); |
| exchange._statistics = stats; |
| |
| var id = exchange._object_id; |
| temp[id] = exchange; |
| |
| var name = exchange.name; |
| name = (name == "") ? "'' (default direct)" : name; |
| |
| _exchangeNameMap[name] = exchange; |
| |
| if (QMF_EXCHANGES[name] && hideQmfObjects) { |
| return false; // Filter out any QMF related exchanges if the settings filter is checked. |
| } else { |
| return "<li class='arrow'><a href='#selected-exchange?id=" + id + "'>" + name + |
| "<p>" + exchange.type + "</p></a></li>"; |
| } |
| }, exchanges.length); |
| |
| // Replace the saved statistics with the newly populated temp instance, which only has active objects |
| // moved to it, this means that any deleted objects are no longer present. |
| _exchangeMap = temp; |
| }; |
| |
| }; // End of qmfui.Exchanges definition |
| |
| |
| /** |
| * Create a Singleton instance of the AddExchange class managing the id="add-exchange" page. |
| */ |
| qmfui.AddExchange = new function() { |
| var submit = function() { |
| var properties = {}; |
| |
| if ($("#exchange-durable")[0].checked) { |
| properties["durable"] = true; |
| } else { |
| properties["durable"] = false; |
| } |
| |
| if ($("#sequence")[0].checked) { |
| properties["qpid.msg_sequence"] = 1; |
| } |
| |
| if ($("#ive")[0].checked) { |
| properties["qpid.ive"] = 1; |
| } |
| |
| properties["exchange-type"] = $("#exchange-type input[checked]").val(); |
| |
| var alternateExchangeName = $("#add-exchange-additional-alternate-exchange-name p").text(); |
| if (alternateExchangeName != "None (default)") { |
| alternateExchangeName = alternateExchangeName.split(" (")[0]; // Remove the exchange type from the text. |
| properties["alternate-exchange"] = alternateExchangeName; |
| } |
| |
| var exchangeName = $("#exchange-name"); |
| var name = exchangeName.val(); |
| if (name == "") { |
| exchangeName.addClass("error"); |
| } else { |
| exchangeName.removeClass("error"); |
| |
| var arguments = {"type": "exchange", "name": name, "properties": properties}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("create", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }; |
| |
| var changeType = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| $("#add-exchange-exchange-type p").text(jthis.siblings("label").text()); |
| } |
| }; |
| |
| this.initialise = function() { |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#add-exchange .right.button").bind(qmfui.END_EV, submit); |
| $("#exchange-type input").change(changeType); |
| |
| // Always initialise to default value irrespective of browser caching. |
| $("#direct").click(); |
| }; |
| }; // End of qmfui.AddExchange definition |
| |
| |
| /** |
| * Create a Singleton instance of the ExchangeSelector class managing the id="exchange-selector" page. |
| */ |
| qmfui.ExchangeSelector = new function() { |
| // Protected Exchanges are exchanges that we don't permit binding to - default direct and the QMF exchanges. |
| var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true, |
| "qmf.default.topic": true, "qpid.management": true}; |
| var _id; |
| |
| /** |
| * This method renders dynamic content in the ExchangeSelector page. This is necessary because the set |
| * of exchanges to be rendered may change as exchanges are added or deleted. |
| */ |
| var show = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#exchange-selector") { |
| return; |
| } |
| |
| // We pass in the ID of the list ltem that contains the anchor with an href to exchange-selector. |
| _id = data.id; |
| |
| if (_id == "#add-binding-exchange-name") { |
| $("#exchange-selector .header a").text("Add Bin..."); |
| $("#exchange-selector .header h1").text("Select Exchange"); |
| } else if (_id == "#reroute-messages-exchange-name") { |
| $("#exchange-selector .header a").text("Reroute..."); |
| $("#exchange-selector .header h1").text("Select Exchange"); |
| } else { |
| $("#exchange-selector .header a").text("Additio..."); |
| $("#exchange-selector .header h1").text("Alternate Exchange"); |
| } |
| |
| var exchanges = qmfui.Console.getExchanges(); |
| var filteredExchanges = []; |
| var currentlySelected = $(_id + " p").text(); |
| |
| // Check the status of any Exchange that the user may have previously selected prior to hitting "Done". |
| if (currentlySelected != "None (default)") { |
| // Remove the exchange type from the text before testing. |
| currentlySelected = currentlySelected.split(" (")[0]; |
| if (qmfui.Exchanges.getExchangeByName(currentlySelected) == null) { // Check if it has been deleted. |
| alert('The currently selected Exchange "' + currentlySelected + '" appears to have been deleted.'); |
| currentlySelected = "None (default)"; |
| } |
| } |
| |
| var checked = (currentlySelected == "None (default)") ? "checked" : ""; |
| filteredExchanges.push("<li><label for='exchange-selector-exchangeNone'>None (default)</label><input type='radio' id='exchange-selector-exchangeNone' name='exchange-selector' value='None (default)' " + checked + "/></li>"); |
| |
| var length = exchanges.length; |
| for (var i = 0; i < length; i++) { |
| var name = exchanges[i].name; |
| var type = exchanges[i].type; |
| checked = (currentlySelected == name) ? "checked" : ""; |
| |
| // Filter out default direct and QMF exchanges as we don't want to allow binding to those. |
| if (!PROTECTED_EXCHANGES[name]) { |
| filteredExchanges.push("<li><label for='exchange-selector-exchange" + i + "'>" + name + " (" + type + ")</label><input type='radio' id='exchange-selector-exchange" + i + "' name='exchange-selector' value='" + name + "' " + checked + "/></li>"); |
| } |
| } |
| |
| qmfui.renderArray($("#exchange-selector-list"), filteredExchanges); |
| $("#exchange-selector-list input").change(changeExchange); |
| }; |
| |
| /** |
| * Event handler for the change event on "#exchange-selector-list input". Note that this is bound "dynamically" |
| * in the show handler because the exchange list is created dynamically each time the ExchangeSelector is shown. |
| */ |
| var changeExchange = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| $(_id + " p").text(jthis.siblings("label").text()); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#exchange-selector").unbind("show").bind("show", show); |
| }; |
| }; // End of qmfui.ExchangeSelector definition |
| |
| |
| /** |
| * Create a Singleton instance of the SelectedExchange class managing the id="selected-exchange" page. |
| */ |
| qmfui.SelectedExchange = new function() { |
| // System Exchanges are exchanges that should not be deleted so we hide the delete button for those Exchanges. |
| var SYSTEM_EXCHANGES = {"''": true, "amq.direct": true, "amq.fanout": true, "amq.match": true, "amq.topic": true, |
| "qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true}; |
| |
| // Protected Exchanges are exchanges that we don't binding to - default direct and the QMF exchanges. |
| var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true, |
| "qmf.default.topic": true, "qpid.management": true}; |
| var _name = ""; |
| |
| /** |
| * This method deletes the selected exchange by invoking the QMF delete method. |
| */ |
| var deleteHandler = function() { |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| setTimeout(function() { |
| if (confirm('Delete Exchange "' + _name + '"?') == false) { |
| return; |
| } else { |
| var arguments = {"type": "exchange", "name": _name}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("delete", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }, 0); |
| }; |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#selected-exchange") { |
| return; |
| } |
| |
| // Get the latest update of the selected exchange object. |
| var exchangeId = data.id; |
| var exchange = qmfui.Exchanges.getExchange(exchangeId); |
| if (exchange == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| // Populate the page header with the exchange name and type |
| _name = exchange.name; |
| _name = (_name == "") ? "''" : _name; |
| $("#selected-exchange .header h1").text(_name + " (" + exchange.type + ")"); |
| |
| // If the selected Exchange is a system Exchange hide the delete button, otherwise show it. |
| if (SYSTEM_EXCHANGES[_name]) { |
| $("#selected-exchange .header a.delete").hide(); |
| } else { |
| $("#selected-exchange .header a.delete").show(); |
| } |
| |
| // Populate the back button with "Exchanges" or "Bindings" depending on context |
| var backText = "Exchan..."; |
| if (data.bindingKey) { |
| backText = "Bindings"; |
| // Ensure that the binding that linked to this page gets correctly highlighted if we navigate back. |
| // If default direct add an extra "&" otherwise all ObjectIds will match in a simple string search. |
| qmfui.Bindings.setHighlightedBinding(exchangeId + (_name == "''" ? "&" : ""), |
| data.bindingKey); |
| } |
| |
| // Using $("#selected-exchange .header a").text(backText) wipes all child elements so use the following. |
| $("#selected-exchange .header a")[0].firstChild.nodeValue = backText; |
| |
| // Render the bindingCount to #selected-exchange-bindings |
| if (exchange.bindingCount == 0) { |
| // We don't allow bindings to be added to default direct or QMF Exchanges. |
| if (PROTECTED_EXCHANGES[_name]) { |
| iTablet.renderList($("#selected-exchange-bindings"), function(i) { |
| return "<li class='grey'><a href='#'>There are currently no bindings to " + _name + "</a></li>"; |
| }); |
| } else { |
| iTablet.renderList($("#selected-exchange-bindings"), function(i) { |
| return "<li class='pop'><a href='#add-binding?exchangeId=" + exchangeId + "'>Add Binding</a></li>"; |
| }); |
| } |
| } else { |
| iTablet.renderList($("#selected-exchange-bindings"), function(i) { |
| return "<li class='arrow'><a href='#bindings?exchangeId=" + exchangeId + "'>bindingCount<p>" + |
| exchange.bindingCount + "</p></a></li>"; |
| }); |
| } |
| |
| // Render the exchange statistics to #selected-exchange-msgio |
| qmfui.renderObject($("#selected-exchange-msgio"), exchange, ["msgReceives", "msgRoutes", "msgDrops"], |
| "#graphs?exchangeId=" + exchangeId); |
| |
| // Render the exchange statistics to #selected-exchange-byteio |
| qmfui.renderObject($("#selected-exchange-byteio"), exchange, ["byteReceives", "byteRoutes", "byteDrops"]); |
| |
| // Render selected general exchange properties and exchange.declare arguments to #selected-exchange-general. |
| keys = ["durable", "autoDelete", "producerCount"]; |
| var general = []; |
| |
| // Render any alternate exchange that may be attached to this exchange. Note that exchange.altExchange |
| // is a reference property so we need to dereference it before extracting the exchange name. |
| var altExchange = qmfui.Exchanges.getExchange(exchange.altExchange); |
| if (altExchange) { |
| general.push("<li><a href='#'>altExchange<p>" + altExchange.name + "</p></a></li>"); |
| } |
| |
| for (var i in keys) { // Populate with selected properties. |
| var key = keys[i]; |
| general.push("<li><a href='#'>" + key + "<p>" + exchange[key] + "</p></a></li>"); |
| } |
| for (var i in exchange.arguments) { // Populate with arguments. |
| general.push("<li><a href='#'>" + i + "<p>" + exchange.arguments[i] + "</p></a></li>"); |
| } |
| qmfui.renderArray($("#selected-exchange-general"), general); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#selected-exchange").unbind("show").bind("show", qmfui.SelectedExchange.update); |
| |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#selected-exchange .header a.delete").bind(qmfui.END_EV, deleteHandler); |
| }; |
| }; // End of qmfui.SelectedExchange definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Queue Information |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Queues class managing the id="queues" page. |
| */ |
| qmfui.Queues = new function() { |
| var QMF_EXCHANGES = { "qmf.default.direct": true, "qmf.default.topic": true, "qpid.management": true}; |
| var _queueNameMap = {}; // Queues indexed by name. |
| var _queueMap = {}; // Queues indexed by ObjectId. |
| |
| /** |
| * Return the Queue object for the given QMF ObjectId. The Queue object contains the latest update |
| * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period. |
| */ |
| this.getQueue = function(oid) { |
| return _queueMap[oid]; |
| }; |
| |
| /** |
| * Return the Queue object with the given name. The Queue object contains the latest update |
| * of the given object and a RingBuffer containing previous values of key statistics over a 24 hour period. |
| */ |
| this.getQueueByName = function(name) { |
| return _queueNameMap[name]; |
| }; |
| |
| this.update = function() { |
| var hideQmfObjects = $("#settings-hide-qmf-objects")[0].checked; |
| |
| // We move active queues to temp so deleted queues wither and die. We can't just do _queueMap = {} as we |
| // need to retrieve and update statistics from any active (non-deleted) queue. |
| var temp = {}; |
| _queueNameMap = {}; // We can simply clear the map of queues indexed by name though. |
| var queues = qmfui.Console.getQueues(); |
| iTablet.renderList($("#queues-list"), function(i) { |
| var queue = queues[i]; |
| var objectId = queue._object_id; |
| |
| // Look up the previous value for the indexed queue using its objectId. |
| var prev = _queueMap[objectId]; |
| var stats = (prev == null) ? new qmfui.Statistics(["msgDepth", "msgTotalEnqueues", "msgTotalDequeues"]) : |
| prev._statistics; |
| |
| stats.put([queue.msgDepth, queue.msgTotalEnqueues, queue.msgTotalDequeues, queue._update_ts]); |
| |
| // Add statistics as an additional property of the queue object. |
| queue._statistics = stats; |
| |
| /* |
| * Check if the queue is associated with a QMF exchange. Because we need to iterate through the |
| * bindings in an inner loop we only want to do this once for each queue, however we can't only |
| * do it when the queue is missing from the _queueMap, because there's a race condition with QMF |
| * properties getting asynchronously returned, so it's possible for a queue to exist without a |
| * binding object referencing it existing for a short period, so we don't add the _isQmfQueue |
| * property until a binding referencing the queue actually exists. |
| */ |
| if (prev == null || prev._isQmfQueue == null) { |
| var bindings = qmfui.Console.getBindings(); // Get the cached list of binding objects. |
| for (var i in bindings) { |
| var b = bindings[i]; |
| if (b.queueRef == objectId) { |
| var exchange = qmfui.Exchanges.getExchange(b.exchangeRef); // Dereference the exchangeRef. |
| if (exchange != null) { |
| // If a binding referencing the queue and an exchange exists add isQmfQueue state as an |
| // additional property of the queue object. |
| if (QMF_EXCHANGES[exchange.name]) { |
| queue._isQmfQueue = true; |
| break; |
| } else { |
| queue._isQmfQueue = false; |
| } |
| } |
| } |
| } |
| } else { |
| // Add previous status of _isQmfQueue as an additional property of the queue object. |
| queue._isQmfQueue = prev._isQmfQueue; |
| } |
| |
| temp[objectId] = queue; |
| _queueNameMap[queue.name] = queue; |
| |
| if (queue._isQmfQueue && hideQmfObjects) { |
| return false; // Filter out any QMF related queues if the settings filter is checked. |
| } else { |
| return "<li class='arrow'><a href='#selected-queue?id=" + objectId + "'>" + queue.name + "</a></li>"; |
| } |
| }, queues.length); |
| |
| // Replace the saved statistics with the newly populated temp instance, which only has active objects |
| // moved to it, this means that any deleted objects are no longer present. |
| _queueMap = temp; |
| }; |
| |
| }; // End of qmfui.Queues definition |
| |
| |
| /** |
| * Create a Singleton instance of the AddQueue class managing the id="add-queue" page. |
| */ |
| qmfui.AddQueue = new function() { |
| var _properties = {}; |
| |
| var parseIntegerProperty = function(selector, name) { |
| var value = $(selector).removeClass("error").val(); |
| if (value == "") { |
| return true; // "" doesn't populate the property, but it's still valid so return true; |
| } else { |
| if (value.search(/[kKmMgG]/) == (value.length - 1)) { // Does it end in K/M/G |
| _properties[name] = value; |
| return true; |
| } else { |
| var integer = parseInt(value); |
| if (isNaN(integer)) { |
| $(selector).addClass("error"); |
| return false; |
| } else { |
| _properties[name] = integer; |
| return true; |
| } |
| } |
| } |
| }; |
| |
| var submit = function() { |
| _properties = {}; |
| |
| if (!parseIntegerProperty("#max-queue-size", "qpid.max_size")) { |
| return; |
| } else if (!parseIntegerProperty("#max-queue-count", "qpid.max_count")) { |
| return; |
| } else if (!parseIntegerProperty("#flow-stop-size", "qpid.flow_stop_size")) { |
| return; |
| } else if (!parseIntegerProperty("#flow-stop-count", "qpid.flow_stop_count")) { |
| return; |
| } else if (!parseIntegerProperty("#flow-resume-size", "qpid.flow_resume_size")) { |
| return; |
| } else if (!parseIntegerProperty("#flow-resume-count", "qpid.flow_resume_count")) { |
| return; |
| } |
| |
| if ($("#queue-durable")[0].checked) { |
| _properties["durable"] = true; |
| if (!parseIntegerProperty("#file-size", "qpid.file_size")) { |
| return; |
| } else if (!parseIntegerProperty("#file-count", "qpid.file_count")) { |
| return; |
| } |
| |
| if ($("#queue-cluster-durable")[0].checked) { |
| _properties["qpid.persist_last_node"] = 1; |
| } |
| } else { |
| _properties["durable"] = false; |
| } |
| |
| var limitPolicy = $("#limit-policy input[checked]").val(); |
| if (limitPolicy != "none") { |
| _properties["qpid.policy_type"] = limitPolicy; |
| } |
| |
| var orderingPolicy = $("#ordering-policy input[checked]").val(); |
| if (orderingPolicy == "lvq") { |
| _properties["qpid.last_value_queue"] = 1; |
| } else if (orderingPolicy == "lvq-no-browse") { |
| _properties["qpid.last_value_queue_no_browse"] = 1; |
| } |
| |
| if (!parseIntegerProperty("#generate-queue-events input[checked]", "qpid.queue_event_generation")) { |
| return; |
| } |
| |
| var alternateExchangeName = $("#add-queue-additional-alternate-exchange-name p").text(); |
| if (alternateExchangeName != "None (default)") { |
| alternateExchangeName = alternateExchangeName.split(" (")[0]; // Remove the exchange type from the text. |
| _properties["alternate-exchange"] = alternateExchangeName; |
| } |
| |
| var queueName = $("#queue-name"); |
| var name = queueName.val(); |
| if (name == "") { |
| queueName.addClass("error"); |
| } else { |
| queueName.removeClass("error"); |
| |
| var arguments = {"type": "queue", "name": name, "properties": _properties}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("create", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }; |
| |
| var changeLimitPolicy = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| $("#add-queue-limit-policy p").text(jthis.siblings("label").text()); |
| } |
| }; |
| |
| var changeOrderingPolicy = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| var value = jthis.attr("value"); |
| if (value == "fifo") { |
| $("#add-queue-ordering-policy p").text("Fifo (default)"); |
| } else if (value == "lvq") { |
| $("#add-queue-ordering-policy p").text("LVQ"); |
| } else if (value == "lvq-no-browse") { |
| $("#add-queue-ordering-policy p").text("LVQ No Browse"); |
| } |
| } |
| }; |
| |
| var changeQueueEventGeneration = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| $("#add-queue-generate-queue-events p").text(jthis.siblings("label").text()); |
| } |
| }; |
| |
| var changeDurable = function(e) { |
| var durable = $("#queue-durable")[0].checked; |
| var durableList = $("#add-queue-additional-durable-list"); |
| var hiddenList = $("#add-queue-additional-hidden-list").hide(); |
| |
| if (durable) { |
| setTimeout(function() { |
| $("#queue-cluster-durable").parent().appendTo(durableList); |
| iTablet.renderList(durableList); |
| setTimeout(function() { |
| $("#file-size").parent().appendTo(durableList); |
| iTablet.renderList(durableList); |
| setTimeout(function() { |
| $("#file-count").parent().appendTo(durableList); |
| iTablet.renderList(durableList); |
| $("#add-queue-additional").trigger("refresh"); // Refresh touch scroller. |
| }, 30); |
| }, 30); |
| }, 30); |
| |
| $("#add-queue-additional-journal-note").show(); |
| } else { |
| if ($("#queue-cluster-durable").is(":checked")) { |
| // Have to use a timeout to call click() within an event handler. |
| setTimeout(function() {$("#queue-cluster-durable").click();}, 0); |
| } |
| |
| setTimeout(function() { |
| $("#file-count").parent().appendTo(hiddenList); |
| setTimeout(function() { |
| $("#file-size").parent().appendTo(hiddenList); |
| setTimeout(function() { |
| $("#queue-cluster-durable").parent().appendTo(hiddenList); |
| iTablet.renderList(durableList); |
| $("#add-queue-additional").trigger("refresh"); // Refresh touch scroller. |
| }, 30); |
| }, 30); |
| }, 30); |
| |
| $("#add-queue-additional-journal-note").hide(); |
| } |
| }; |
| |
| this.initialise = function() { |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#add-queue .right.button").bind(qmfui.END_EV, submit); |
| $("#limit-policy input").change(changeLimitPolicy); |
| $("#ordering-policy input").change(changeOrderingPolicy); |
| $("#generate-queue-events input").change(changeQueueEventGeneration); |
| |
| // Always initialise to default value irrespective of browser caching. |
| $("#none").click(); |
| $("#fifo").click(); |
| $("#generate-no-events").click(); |
| |
| changeDurable(); |
| $("#queue-durable").change(changeDurable); |
| }; |
| }; // End of qmfui.AddQueue definition |
| |
| |
| /** |
| * Create a Singleton instance of the SelectedQueue class managing the id="selected-queue" page. |
| */ |
| qmfui.SelectedQueue = new function() { |
| var _name = ""; |
| |
| // The Queue depth and number of consumers are reported during the Queue delete confirmation process. |
| var _depth = 0; |
| var _consumers = 0; |
| |
| /** |
| * This method deletes the selected queue by invoking the QMF delete method. |
| */ |
| var deleteHandler = function() { |
| var plural = (_consumers == 1) ? " consumer?" : " consumers?" |
| |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| setTimeout(function() { |
| if (confirm('Delete Queue "' + _name + |
| '"\nwhich contains ' + _depth + " messages and has " + _consumers + plural) == false) { |
| return; |
| } else { |
| var arguments = {"type": "queue", "name": _name}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("delete", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }, 0); |
| }; |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#selected-queue") { |
| return; |
| } |
| |
| // Get the latest update of the selected queue object. |
| var queueId = data.id; |
| var queue = qmfui.Queues.getQueue(queueId); |
| if (queue == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| _name = queue.name; |
| _depth = queue.msgDepth; // Reported to user if Queue delete is selected. |
| _consumers = queue.consumerCount; // Reported to user if Queue delete is selected. |
| |
| // Populate the page header with the queue name. |
| $("#selected-queue .header h1").text(_name); |
| |
| // If the selected Queue is a QMF Queue hide the delete button, otherwise show it. |
| // Deleting QMF Queues is a bad thing as it will stop any QMF Consoles behaving as the should. |
| if (queue._isQmfQueue) { |
| $("#selected-queue .header a.delete").hide(); |
| $("#selected-queue-admin-wrapper").hide(); |
| } else { |
| $("#selected-queue .header a.delete").show(); |
| $("#selected-queue-admin-wrapper").show(); |
| } |
| |
| // Populate the back button with "Queues", "Bindings" or "Subscriptions" depending on context |
| var backText = "Queues"; |
| if (data.bindingKey) { |
| backText = "Bindings"; |
| // Ensure that the binding that linked to this page gets correctly highlighted if we navigate back. |
| qmfui.Bindings.setHighlightedBinding(queueId, data.bindingKey); |
| } else if (data.fromSubscriptions) { |
| backText = "Subscrip..."; |
| } |
| |
| // Using $("#selected-queue .header a").text(backText) wipes all child elements so use the following. |
| $("#selected-queue .header a")[0].firstChild.nodeValue = backText; |
| |
| // Render the bindingCount to #selected-queue-bindings |
| // There should always be at least one binding to a queue as the default direct exchange is always bound. |
| iTablet.renderList($("#selected-queue-bindings"), function(i) { |
| return "<li class='arrow'><a href='#bindings?queueId=" + queueId + "'>bindingCount<p>" + |
| queue.bindingCount + "</p></a></li>"; |
| }); |
| |
| // Render the queue statistics to #selected-queue-msgio |
| qmfui.renderObject($("#selected-queue-msgio"), queue, ["msgDepth", "msgTotalEnqueues", "msgTotalDequeues"], |
| "#graphs?queueId=" + queueId); |
| |
| // Render the queue statistics to #selected-queue-byteio |
| qmfui.renderObject($("#selected-queue-byteio"), queue, ["byteDepth", "byteTotalEnqueues", |
| "byteTotalDequeues"]); |
| |
| // Render selected general queue properties and queue.declare arguments to #selected-queue-general. |
| keys = ["durable", "autoDelete", "exclusive", "unackedMessages", "acquires", "releases", |
| "messageLatency", "messageLatencyAvg", "consumerCount", "flowStopped", "flowStoppedCount"]; |
| var general = []; |
| |
| // Render any alternate exchange that may be attached to this queue. Note that queue.altExchange |
| // is a reference property so we need to dereference it before extracting the exchange name. |
| var altExchange = qmfui.Exchanges.getExchange(queue.altExchange); |
| if (altExchange) { |
| general.push("<li><a href='#'>altExchange<p>" + altExchange.name + "</p></a></li>"); |
| } |
| |
| for (var i in keys) { // Populate with selected properties. |
| var key = keys[i]; |
| var value = queue[key]; |
| if (value != null) { |
| general.push("<li><a href='#'>" + key + "<p>" + value + "</p></a></li>"); |
| } |
| } |
| for (var i in queue.arguments) { // Populate with arguments. |
| general.push("<li><a href='#'>" + i + "<p>" + queue.arguments[i] + "</p></a></li>"); |
| } |
| qmfui.renderArray($("#selected-queue-general"), general); |
| |
| var hideDetails = $("#settings-hide-details")[0].checked; |
| |
| // Render the Flow-to-disk Statistics. |
| if (queue.msgFtdDepth == null || hideDetails) { |
| $("#selected-queue-flow-to-disk-container").hide(); |
| } else { |
| $("#selected-queue-flow-to-disk-container").show(); |
| qmfui.renderObject($("#selected-queue-flow-to-disk"), queue, ["msgFtdDepth", "msgFtdEnqueues", |
| "msgFtdDequeues", "byteFtdDepth", "byteFtdEnqueues", "byteFtdDequeues"]); |
| } |
| |
| // Render the Dequeue Details. |
| if (queue.discardsTtl == null || hideDetails) { |
| $("#selected-queue-dequeue-container").hide(); |
| } else { |
| $("#selected-queue-dequeue-container").show(); |
| qmfui.renderObject($("#selected-queue-dequeue"), queue, ["discardsTtl", "discardsRing", "discardsLvq", |
| "discardsOverflow", "discardsSubscriber", "discardsPurge", "reroutes"]); |
| } |
| |
| // Render links to the subscriptions associated with this queue to #selected-queue-subscriptions. |
| // Unfortunately the subscription name isn't especially useful so find the associated connection |
| // and display the connection address instead. |
| var subscriptions = qmfui.Connections.getQueueSubscriptions(queueId); |
| if (subscriptions.length == 0) { |
| iTablet.renderList($("#selected-queue-subscriptions"), function(i) { |
| return "<li class='grey'><a href='#'>There are currently no subscriptions to " + _name + "</a></li>"; |
| }); |
| } else { |
| iTablet.renderList($("#selected-queue-subscriptions"), function(i) { |
| var subscription = subscriptions[i]; |
| var id = subscription._object_id; |
| |
| // The subscription.sessionRef really should be present, but the Java Broker does not yet correctly |
| // populate the association between Subscription and Session so we need this defensive block. |
| if (subscription.sessionRef != null) { |
| var session = qmfui.Connections.getSession(subscription.sessionRef); |
| var connection = qmfui.Connections.getConnection(session.connectionRef); |
| var address = connection.address + " (" + connection.remoteProcessName + ")"; |
| return "<li class='arrow'><a href='#queue-subscriptions?id=" + id + "'>" + address + "</a></li>"; |
| } else { |
| return "<li class='arrow'><a href='#queue-subscriptions?id=" + id + "'>" + subscription.name + "</a></li>"; |
| } |
| }, subscriptions.length); |
| } |
| |
| // We have to dynamically render the admin list so that we can attach the queueId to the URL. |
| var admin = []; |
| admin.push("<li class='arrow pop'><a href='#purge-queue?queueId=" + queueId + "'>Purge</a></li>"); |
| admin.push("<li class='arrow pop'><a href='#reroute-messages?queueId=" + queueId + "'>Reroute Messages</a></li>"); |
| admin.push("<li class='arrow pop'><a href='#move-messages?queueId=" + queueId + "'>Move Messages</a></li>"); |
| qmfui.renderArray($("#selected-queue-admin"), admin); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#selected-queue").unbind("show").bind("show", qmfui.SelectedQueue.update); |
| |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#selected-queue .header a.delete").bind(qmfui.END_EV, deleteHandler); |
| }; |
| }; // End of qmfui.SelectedQueue definition |
| |
| |
| /** |
| * Create a Singleton instance of the QueueSubscriptions class managing the id="queue-subscriptions" page. |
| */ |
| qmfui.QueueSubscriptions = new function() { |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#queue-subscriptions") { |
| return; |
| } |
| |
| var subscriptionId = data.id; |
| var subscription = qmfui.Connections.getSubscription(subscriptionId); |
| if (subscription == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| var session = qmfui.Connections.getSession(subscription.sessionRef); |
| session = session ? session : {}; |
| var connection = qmfui.Connections.getConnection(session.connectionRef); |
| connection = connection ? connection : {}; |
| |
| // The connection.address should be present but for the 0.20 Java Broker it is not yet populated |
| // so we need to do some defensive code to check if it's set and if not render subscription.name. |
| var name = connection.address ? connection.address + " (" + connection.remoteProcessName + ")" : |
| subscription.name; |
| |
| var connectionId = connection._object_id; |
| |
| // Populate the page header with the address of the connection associated with the subscription. |
| $("#queue-subscriptions .header h1").text(name); |
| |
| iTablet.renderList($("#queue-subscriptions-connection"), function(i) { |
| if (connectionId) { |
| return "<li class='arrow'><a href='#selected-connection?id=" + connectionId + |
| "&fromSubscription=true'>" + name + "</a></li>"; |
| } else { |
| return "<li><a href='#'>Connection is Unknown</a></li>"; |
| } |
| }); |
| |
| // subscription.sessionRef should be present but for the 0.20 Java Broker it is not yet populated |
| // so we need to do some defensive code to check if it's set and if not render "Session is Unknown". |
| if (subscription.sessionRef) { |
| // Render the useful session properties to #queue-subscriptions-session |
| qmfui.renderObject($("#queue-subscriptions-session"), session, ["name", "framesOutstanding", |
| "unackedMessages", "channelId", "maxClientRate", "clientCredit"]); |
| } else { |
| iTablet.renderList($("#queue-subscriptions-session"), function(i) { |
| return "<li><a href='#'>Session is Unknown</a></li>"; |
| }); |
| } |
| |
| // Render the useful subscription properties to #queue-subscriptions-subscription |
| qmfui.renderObject($("#queue-subscriptions-subscription"), subscription, ["name", "delivered", "browsing", |
| "acknowledged", "exclusive", "creditMode"]); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#queue-subscriptions").unbind("show").bind("show", qmfui.QueueSubscriptions.update); |
| }; |
| }; // End of qmfui.QueueSubscriptions definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Queue Admin |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the PurgeQueue class managing the id="purge-queue" page. |
| */ |
| qmfui.PurgeQueue = new function() { |
| /** |
| * Actually purge the messages using the QMF purge method on the Queue Management Object. |
| */ |
| var submit = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#purge-queue") { |
| return; |
| } |
| |
| var selector = $("#purge-queue-request-number"); |
| var value = selector.val(); |
| var messageCount = 0; |
| if (value != "") { |
| messageCount = parseInt(value); |
| if (isNaN(messageCount)) { |
| selector.addClass("error"); |
| return false; |
| } |
| } |
| |
| selector.removeClass("error"); |
| |
| var queueId = data.queueId; |
| var queue = qmfui.Queues.getQueue(queueId); |
| qmfui.Console.makeConsoleData(queue); // Make queue a QmfConsoleData object with an invokeMethod method. |
| |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| var countText = (messageCount == 0) ? "all" : messageCount; |
| setTimeout(function() { |
| if (confirm('Purge ' + countText + ' messages from "' + queue.name + '"?') == false) { |
| return; |
| } else { |
| var arguments = {"request": messageCount}; |
| |
| queue.invokeMethod("purge", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }, 0); |
| }; |
| |
| this.initialise = function() { |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#purge-queue .right.button").bind(qmfui.END_EV, submit); |
| }; |
| }; // End of qmfui.PurgeQueue definition |
| |
| |
| /** |
| * Create a Singleton instance of the RerouteMessages class managing the id="reroute-messages" page. |
| */ |
| qmfui.RerouteMessages = new function() { |
| /** |
| * Actually reroute the messages using the QMF reroute method on the Queue Management Object. |
| */ |
| var submit = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#reroute-messages") { |
| return; |
| } |
| |
| var selector = $("#reroute-messages-request-number"); |
| var value = selector.val(); |
| var messageCount = 0; |
| if (value != "") { |
| messageCount = parseInt(value); |
| if (isNaN(messageCount)) { |
| selector.addClass("error"); |
| return false; |
| } |
| } |
| |
| selector.removeClass("error"); |
| |
| var queueId = data.queueId; |
| var queue = qmfui.Queues.getQueue(queueId); |
| qmfui.Console.makeConsoleData(queue); // Make queue a QmfConsoleData object with an invokeMethod method. |
| |
| var useAltExchange = $("#reroute-messages-use-alternate-exchange")[0].checked; |
| var countText = (messageCount == 0) ? "all" : messageCount; |
| |
| var exchangeName = $("#reroute-messages-exchange-name p").text(); |
| if (exchangeName != "None (default)") { |
| exchangeName = exchangeName.split(" (")[0]; // Remove the exchange type from the text. |
| } |
| |
| var exchangeText = useAltExchange ? "Alternate Exchange?" : exchangeName + "?"; |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| setTimeout(function() { |
| if (confirm('Reroute ' + countText + ' messages from "' + queue.name + '" to ' + exchangeText) == false) { |
| return; |
| } else { |
| var arguments = {"request": messageCount, "useAltExchange": useAltExchange}; |
| if (!useAltExchange) { |
| arguments["exchange"] = exchangeName; |
| } |
| |
| queue.invokeMethod("reroute", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }, 0); |
| }; |
| |
| /** |
| * This method is the change handler for the use alternate exchange switch, it is used to show or hide the |
| * exchange selector widget. This handler is also bound to "show" because the state of the alternate exchange |
| * switch is cached in some browsers so triggering on change alone wouldn't handle that. |
| */ |
| var changeUseAlternateExchange = function() { |
| if ($("#reroute-messages-use-alternate-exchange")[0].checked) { |
| $("#reroute-messages-use-selected-exchange").hide(); |
| } else { |
| $("#reroute-messages-use-selected-exchange").show(); |
| } |
| }; |
| |
| this.initialise = function() { |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#reroute-messages .right.button").bind(qmfui.END_EV, submit); |
| $("#reroute-messages-use-alternate-exchange").change(changeUseAlternateExchange); |
| $("#reroute-messages").unbind("show").bind("show", changeUseAlternateExchange); |
| }; |
| }; // End of qmfui.RerouteMessages definition |
| |
| |
| /** |
| * Create a Singleton instance of the RerouteMessages class managing the id="move-messages" page. |
| */ |
| qmfui.MoveMessages = new function() { |
| var _sourceQueueName = ""; |
| var _destinationQueueName = ""; |
| var _sourceQueue = {}; |
| |
| /** |
| * Actually reroute the messages using the QMF reroute method on the Queue Management Object. |
| */ |
| var submit = function() { |
| // The queueMoveMessages method returns an InvalidParameter Exception if called when srcQueue has msgDepth == 0 |
| // This is pretty confusing as the parameters *are* actually OK. https://issues.apache.org/jira/browse/QPID-4543 |
| // has been raised on this but the following is some defensive logic to provide a more helpful warning. |
| if (_sourceQueue.msgDepth == 0) { |
| setTimeout(function() { |
| alert("Can't call Move Messages on a queue with a msgDepth of zero"); |
| return false; |
| }, 0); |
| } else { |
| var selector = $("#move-messages-request-number"); |
| var value = selector.val(); |
| var messageCount = 0; |
| if (value != "") { |
| messageCount = parseInt(value); |
| if (isNaN(messageCount)) { |
| selector.addClass("error"); |
| return false; |
| } |
| } |
| |
| selector.removeClass("error"); |
| |
| // Wrap in a timeout call because confirm doesn't play nicely with touchend and causes it to trigger twice. |
| // Calling confirm within a timeout ensures things are placed correctly onto the event queue. |
| var countText = (messageCount == 0) ? "all" : messageCount; |
| setTimeout(function() { |
| if (confirm('Move ' + countText + ' messages from "' + _sourceQueueName + '" to "' + _destinationQueueName + '"?') == false) { |
| return; |
| } else { |
| var arguments = {"srcQueue": _sourceQueueName, "destQueue": _destinationQueueName, "qty": messageCount}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("queueMoveMessages", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| }, 0); |
| } |
| }; |
| |
| /** |
| * This method renders the main move-messages page when it is made visible by the show event being triggered. |
| */ |
| var show = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#move-messages") { |
| return; |
| } |
| |
| var queueId = data.queueId; |
| _sourceQueue = qmfui.Queues.getQueue(queueId); |
| _sourceQueueName = (_sourceQueue == null) ? "" : _sourceQueue.name; |
| _destinationQueueName = $("#move-messages-queue-name p").text(); |
| |
| if (_sourceQueueName == _destinationQueueName) { |
| $("#move-messages-queue-name p").text("None (default)"); |
| } else { |
| $("#move-messages-queue-name p").text(_destinationQueueName); |
| } |
| }; |
| |
| this.getSourceQueueName = function() { |
| return _sourceQueueName; |
| }; |
| |
| this.initialise = function() { |
| $("#move-messages").unbind("show").bind("show", show); |
| |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#move-messages .right.button").bind(qmfui.END_EV, submit); |
| }; |
| }; // End of qmfui.MoveMessages definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Generic Bindings Rendering Page |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Bindings class managing the id="bindings" page. |
| */ |
| qmfui.Bindings = new function() { |
| // Protected Exchanges are exchanges that we don't permit unbinding from - default direct and the QMF exchanges. |
| var PROTECTED_EXCHANGES = {"": true, "''": true, "qmf.default.direct": true, |
| "qmf.default.topic": true, "qpid.management": true}; |
| var _highlightedObject = null; |
| var _highlightedObjectKey = null; |
| |
| /** |
| * This method is used to render the binding information for headers and XML exchanges which need a little |
| * bit more effort than just rendering the binding key. We use <p class="title"> and <p class="sub"> |
| * to give fairly neat formatting. |
| * |
| * @param exchange the exchange that the binding is bound to. |
| * @param binding the binding that we wish to render. |
| */ |
| var render = function(exchange, binding) { |
| if (exchange.type == "headers") { |
| var arguments = binding.arguments; |
| var headers = "<p class='title'>x-match: " + arguments["x-match"] + "</p>"; |
| for (var key in arguments) { |
| if (key != "x-match") { |
| headers = headers + "<p class='sub'>" + key + ": " + arguments[key] + "</p>"; |
| } |
| } |
| return headers; |
| } else if (exchange.type == "xml") { |
| var arguments = binding.arguments; |
| var xquery = "<p class='title'>xquery:</p>"; |
| xquery = xquery + "<p class='sub'>" + arguments["xquery"] + "</p>"; |
| return xquery; |
| } else { |
| return " "; |
| } |
| }; |
| |
| /** |
| * This method confirms the unbind request and invokes the QMF delete binding method on the broker. |
| * |
| * @param exchangeName the exchange that we wish to unbind from. |
| * @param queueName the queue that we wish to unbind from. |
| * @param bindingKey the binding key that we wish to unbind from. |
| */ |
| var unbind = function(exchangeName, queueName, bindingKey) { |
| var bindingIdentifier = exchangeName + "/" + queueName; |
| if (bindingKey != "") { |
| bindingIdentifier = bindingIdentifier + "/" + bindingKey; |
| } |
| |
| if (confirm('Delete Binding "' + bindingIdentifier + '"?') == false) { |
| return; |
| } else { |
| var arguments = {"type": "binding", "name": bindingIdentifier}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("delete", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * This handler is triggered by clicking a <li class="clickable-icon">. Unfortunately there's bit more work |
| * to do because we only want to respond if the actual icon has been clicked so we need to work out the |
| * mouse or tap position within the li and check it's less than the icon width. |
| * Once we're happy that we've clicked the icon we need to work out which binding the <li> relates to. The |
| * approach to this is a little messy and involves scraping some of the html associated with the <li> |
| */ |
| var clickHandler = function(e) { |
| var target = e.target; |
| var jthis = $(target).closest("ul li.clickable-icon"); |
| |
| if (jthis.length != 0) { |
| var ICON_WIDTH = 45; // The width of the icon image plus some padding. |
| var offset = Math.ceil(jthis.offset().left); |
| var x = (e.pageX != null) ? e.pageX - offset : // Mouse position. |
| (e.originalEvent != null) ? e.originalEvent.targetTouches[0].pageX - offset : 0; // Touch pos. |
| |
| if (x < ICON_WIDTH) { |
| var bindingKey = jthis.text().split("]")[0]; |
| bindingKey = bindingKey.split("[")[1]; |
| var href = jthis.children("a:first").attr("href"); |
| href = href.replace(window.location, ""); |
| |
| if (href.indexOf("#selected-exchange") == 0) { |
| var queue = $("#bindings .header h1").text().split(" bindings")[0]; |
| var exchange = jthis.find("p:last").text(); |
| unbind(exchange, queue, bindingKey); |
| |
| } else { |
| var queue = jthis.find("p:last").text(); |
| var exchange = $("#bindings .header h1").text().split(" bindings")[0]; |
| unbind(exchange, queue, bindingKey); |
| } |
| } |
| } |
| }; |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#bindings") { |
| return; |
| } |
| |
| // Get the latest update of the selected object. |
| var queueId = data.queueId; |
| var exchangeId = data.exchangeId; |
| var object = queueId ? qmfui.Queues.getQueue(queueId) : qmfui.Exchanges.getExchange(exchangeId); |
| |
| if (object == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| var name = object.name; |
| name = (name == "") ? "''" : name; |
| |
| // Populate the page header with the object name. |
| $("#bindings .header h1").text(name + " bindings"); |
| |
| // Populate the back button with "Queues" or "Bindings" depending on context |
| var backText = "Exchange"; |
| |
| if (queueId) { |
| backText = "Queue"; |
| $("#bindings").addClass("queue"); |
| } else { |
| $("#bindings").removeClass("queue"); |
| } |
| |
| // Using $("#bindings .header a").text(backText) wipes all child elements so use the following. |
| $("#bindings .header a")[0].firstChild.nodeValue = backText; |
| |
| // Ensure the correct item on the previous page is set active so it gets highlighted when we slide back. |
| if (queueId) { |
| $("#selected-queue-bindings").children("li").addClass("active"); |
| $("#selected-exchange-bindings").children("li").removeClass("active"); |
| |
| // Add queueId to add-binding URL so add-binding page can automatically populate the queue name. |
| iTablet.renderList($("#bindings-add-binding"), function(i) { |
| return "<li class='pop'><a href='#add-binding?queueId=" + queueId + "'>Add Binding</a></li>"; |
| }); |
| } else { |
| $("#selected-queue-bindings").children("li").removeClass("active"); |
| $("#selected-exchange-bindings").children("li").addClass("active"); |
| |
| // Add exchangeId to add-binding URL so add-binding page can automatically populate the exchange name. |
| iTablet.renderList($("#bindings-add-binding"), function(i) { |
| return "<li class='pop'><a href='#add-binding?exchangeId=" + exchangeId + "'>Add Binding</a></li>"; |
| }); |
| } |
| |
| // Note that we don't allow bindings to be added to the default direct exchange, QMF exchanges or |
| // queues that are bound to QMF exchanges as doing so may have undesired consequenses. |
| if (PROTECTED_EXCHANGES[name] || object._isQmfQueue) { |
| $("#bindings-add-binding").hide(); |
| $("#bindings .page h1").addClass("first"); |
| } else { |
| $("#bindings-add-binding").show(); |
| $("#bindings .page h1").removeClass("first"); |
| } |
| |
| // Render selected queue's bindings to #queue-details-bindings. |
| var bindings = qmfui.Console.getBindings(); |
| var binding = []; |
| for (var i in bindings) { |
| var b = bindings[i]; |
| if (b.queueRef == queueId) { |
| var exchange = qmfui.Exchanges.getExchange(b.exchangeRef); // Dereference exchangeRef |
| if (exchange != null) { |
| var ename = exchange.name; |
| if (ename == "") { |
| // Note that we don't allow bindings to be deleted from default direct exchange. |
| binding.push("<li class='arrow'><a href='#selected-exchange?id=" + |
| b.exchangeRef + "&bindingKey=" + b.bindingKey + "'>" + |
| "bind [" + b.bindingKey + "] => <p>''</p></a></li>"); |
| } else { |
| var text = render(exchange, b); |
| |
| // Note that we don't allow bindings to be deleted from QMF exchanges. |
| if (PROTECTED_EXCHANGES[ename]) { |
| binding.push("<li class='multiline arrow'><a href='#selected-exchange?id=" + |
| b.exchangeRef + "&bindingKey=" + b.bindingKey + "'>" + |
| "<div>bind [" + b.bindingKey + "] =></div>" + |
| "<div>" + text + "<p>" + ename + "</p></div></a></li>"); |
| } else { |
| binding.push("<li class='multiline arrow clickable-icon'><a class='delete' href='#selected-exchange?id=" + |
| b.exchangeRef + "&bindingKey=" + b.bindingKey + "'>" + |
| "<div>bind [" + b.bindingKey + "] =></div>" + |
| "<div>" + text + "<p>" + ename + "</p></div></a></li>"); |
| } |
| } |
| } |
| } else if (b.exchangeRef == exchangeId) { |
| var queue = qmfui.Queues.getQueue(b.queueRef); // Dereference queueRef |
| if (queue != null) { |
| var qname = queue.name; |
| var ename = object.name; // object is actually exchange in this case. |
| var text = render(object, b); |
| |
| // Note that we don't allow bindings to be deleted from default direct or QMF exchanges. |
| if (PROTECTED_EXCHANGES[ename]) { |
| binding.push("<li class='multiline arrow'><a href='#selected-queue?id=" + |
| b.queueRef + "&bindingKey=" + b.bindingKey + "'>" + |
| "<div>bind [" + b.bindingKey + "] =></div>" + |
| "<div>" + text + "<p class='fullwidth'>" + qname + "</p></div></a></li>"); |
| |
| } else { |
| binding.push("<li class='multiline arrow clickable-icon'><a class='delete' href='#selected-queue?id=" + |
| b.queueRef + "&bindingKey=" + b.bindingKey + "'>" + |
| "<div>bind [" + b.bindingKey + "] =></div>" + |
| "<div>" + text + "<p class='fullwidth'>" + qname + "</p></div></a></li>"); |
| } |
| } |
| } |
| } |
| |
| if (binding.length == 0) { |
| iTablet.renderList($("#bindings-list"), function(i) { |
| return "<li class='grey'><a href='#'>There are currently no bindings to " + name + "</a></li>"; |
| }); |
| } else { |
| qmfui.renderArray($("#bindings-list"), binding); |
| |
| // If _highlightedObject has been set by qmfui.SelectedQueue or qmfui.SelectedExchange search for |
| // the list item that would have caused navigation to that page and set it active, which causes |
| // it to be given a highlight that fades as we navigate back to qmfui.Bindings. |
| if (_highlightedObject) { |
| $("#bindings-list li").each(function() { |
| var li = $(this); |
| // Find the HTML that contains the highlightedObject string and highlightedObjectKey string. |
| var html = li.html(); |
| if (html.search(_highlightedObject) != -1 && html.search(_highlightedObjectKey) != -1) { |
| li.addClass("active"); |
| } |
| }); |
| |
| _highlightedObject = null; |
| _highlightedObjectKey = null; |
| } |
| } |
| } |
| }; |
| |
| /** |
| * This method is called by qmfui.SelectedQueue or qmfui.SelectedExchange to allow qmfui.Bindings to set the |
| * correct list item highlighting to allow it to fade out as we navigate back to qmfui.Bindings. This is necessary |
| * because we maintain a singleton qmfui.Bindings page and as such its state will get modified as we navigate |
| * through multiple queue->binding->exchange->binding etc. Setting the name of the most recent page navigated |
| * to in this method allows the fading to be handled correctly. |
| */ |
| this.setHighlightedBinding = function(name, bindingKey) { |
| _highlightedObject = name; |
| _highlightedObjectKey = "bindingKey=" + bindingKey; // Binding keys are always rendered within square braces. |
| }; |
| |
| this.initialise = function() { |
| $("#bindings").unbind("show").bind("show", qmfui.Bindings.update); |
| |
| // Using a click handler here doesn't seem to have the 300ms delay that occurs when using click handlers |
| // attached to anchors (hence why those use END_EV). It's *probably* because this is a delegating handler. |
| // JavaScript alert() can interfere with touchend so it's generally better to use click() if it's possible |
| // to do so without it causing that irritating delay!! |
| $("#bindings-list").click(clickHandler); |
| }; |
| }; // End of qmfui.Bindings definition |
| |
| |
| /** |
| * Create a Singleton instance of the AddBinding class managing the id="add-binding" page. |
| */ |
| qmfui.AddBinding = new function() { |
| var _queueName = ""; |
| var _exchangeName = ""; |
| var _exchangeType = ""; |
| var _properties = {}; |
| |
| /** |
| * Actually add the new binding using the QMF create method. |
| */ |
| var submit = function() { |
| if (_exchangeType == "headers") { |
| _properties["x-match"] = $("#x-match input[checked]").val(); |
| } else if (_exchangeType == "xml") { |
| _properties = {}; |
| var textarea = $("#add-xml-binding textarea"); |
| var xquery = textarea.val(); |
| if (xquery == "") { |
| textarea.addClass("error"); |
| return; |
| } else { |
| textarea.removeClass("error"); |
| _properties["xquery"] = xquery; |
| } |
| } else { |
| _properties = {}; |
| } |
| |
| var bindingKey = $("#add-binding-key-name").val(); |
| var bindingIdentifier = _exchangeName + "/" + _queueName; |
| if (bindingKey != "") { |
| bindingIdentifier = bindingIdentifier + "/" + bindingKey; |
| } |
| |
| // Before we actually add the new binding check to see if a binding with specified bindingIdentifier currently |
| // exists. It's a slight faff, but the Qpid broker doesn't currently detect this condition. |
| var duplicateKey = false; |
| var bindings = qmfui.Console.getBindings(); |
| for (var i in bindings) { |
| var b = bindings[i]; |
| // If a binding with the chosen key is found check if the queue and exchange names match. |
| if (b.bindingKey == bindingKey) { |
| var exchange = qmfui.Exchanges.getExchange(b.exchangeRef); // Dereference the exchangeRef. |
| if (exchange != null && exchange.name == _exchangeName) { |
| var queue = qmfui.Queues.getQueue(b.queueRef); // Dereference the queueRef. |
| if (queue != null && queue.name == _queueName) { |
| duplicateKey = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (duplicateKey) { |
| alert('A binding with the identifier "' + bindingIdentifier + '" aleady exists.'); |
| } else { |
| var arguments = {"type": "binding", "name": bindingIdentifier, "properties": _properties}; |
| var broker = qmfui.Console.getBroker(true); // Retrieve broker QmfConsoleData object. |
| broker.invokeMethod("create", arguments, function(data) { |
| if (data.error_text) { |
| alert(data.error_text); |
| } else { |
| iTablet.location.back(); |
| } |
| }); |
| } |
| |
| // Delete the x-match and xquery properties. Note that the other properties are retained, this is |
| // deliberate because it's quite often the case that one may wish to add several headers bindings |
| // that may only differ slightly, it's pretty quick to delete match values that aren't needed. |
| delete _properties["x-match"]; |
| delete _properties["xquery"]; |
| }; |
| |
| /** |
| * This method renders the main add-binding page when it is made visible by the show event being triggered. |
| * It tries to be fairly clever by populating either the queue name or exchange name depending on where |
| * add-binding was navigated from. If a queueId or exchangeId isn't available navigation is added to the |
| * queue-selector or exchange-selector page by adding the arrow class to add-binding-queue-name or |
| * add-binding-exchange-name. This method then checks the exchange type and provides additional rendering |
| * necessary for XML or Headers exchanges. |
| */ |
| var show = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#add-binding") { |
| return; |
| } |
| |
| var queueId = data.queueId; |
| var exchangeId = data.exchangeId; |
| |
| if (queueId) { |
| var queue = qmfui.Queues.getQueue(queueId); |
| _queueName = (queue == null) ? "" : queue.name; |
| _exchangeName = $("#add-binding-exchange-name p").text(); |
| if (_exchangeName != "None (default)") { |
| _exchangeName = _exchangeName.split(" (")[0]; // Remove the exchange type from the text. |
| } |
| |
| var exchange = qmfui.Exchanges.getExchangeByName(_exchangeName); |
| _exchangeType = (exchange == null) ? "" : exchange.type; |
| |
| $("#add-binding-queue-name").removeClass("arrow"); |
| $("#add-binding-exchange-name").addClass("arrow"); |
| } else { |
| _queueName = $("#add-binding-queue-name p").text(); |
| var exchange = qmfui.Exchanges.getExchange(exchangeId); |
| _exchangeName = (exchange == null) ? "" : exchange.name; |
| _exchangeType = (exchange == null) ? "" : exchange.type; |
| |
| $("#add-binding-queue-name").addClass("arrow"); |
| $("#add-binding-exchange-name").removeClass("arrow"); |
| } |
| |
| var typeText = (_exchangeType == "") ? "" : " (" + _exchangeType + ")"; |
| |
| $("#add-binding-queue-name p").text(_queueName); |
| $("#add-binding-exchange-name p").text(_exchangeName + typeText); |
| |
| if (_exchangeType == "headers") { |
| // Render the properties and navigation needed to populate Headers bindings. These are dynamically |
| // populated and updated as Headers key/value properties get added or deleted. |
| $("#add-binding div.page h1").show().text("Headers"); |
| $("#add-headers-binding").show(); |
| $("#add-xml-binding").hide(); |
| |
| var list = []; |
| list.push("<li class='arrow'><a href='#x-match'>Match<p>" + $("#x-match input[checked]").val() + "</p></a></li>"); |
| |
| for (var i in _properties) { |
| var key = i; |
| var value = _properties[i]; |
| // Note we add key/value to the query part of the URL too to let showHeaderMatch() populate the values. |
| list.push("<li class='arrow clickable-icon'><a class='delete' href='#add-header-match?key=" + key + "&value=" + value + "'>" + key + "<p>" + value + "</p></a></li>"); |
| } |
| |
| list.push("<li class='arrow'><a href='#add-header-match'>Add...</a></li>"); |
| qmfui.renderArray($("#add-headers-binding"), list); |
| } else if (_exchangeType == "xml") { |
| $("#add-binding div.page h1").show().text("XML"); |
| $("#add-xml-binding").show(); |
| $("#add-headers-binding").hide(); |
| } else { |
| $("#add-binding div.page h1").hide(); |
| $("#add-headers-binding").hide(); |
| $("#add-xml-binding").hide(); |
| } |
| }; |
| |
| /** |
| * This method renders the add-header-match page when its show event is triggered. This page |
| * needs a show handler because it needs to be dynamically updated with any previously selected key/value pairs. |
| */ |
| var showHeaderMatch = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| var key = ""; |
| var value = ""; |
| |
| if (data != null && location.hash == "#add-header-match") { |
| key = data.key; |
| value = data.value; |
| } |
| |
| $("#header-match-key").val(key); |
| $("#header-match-value").val(value); |
| }; |
| |
| /** |
| * Adds a a header match value from the binding being populated. |
| * This is triggered by the "Done" button on the add-header-match page. There's some validation logic |
| * in place to ensure that a key and value are both supplied. |
| */ |
| var addHeaderMatch = function() { |
| var key = $("#header-match-key"); |
| var keyVal = key.val(); |
| var value = $("#header-match-value"); |
| var valueVal = value.val(); |
| |
| if (keyVal == "") { |
| key.addClass("error"); |
| } else { |
| key.removeClass("error"); |
| } |
| |
| if (valueVal == "") { |
| value.addClass("error"); |
| } else { |
| value.removeClass("error"); |
| } |
| |
| if (keyVal != "" && valueVal != "") { |
| _properties[keyVal] = valueVal; |
| iTablet.location.back(); |
| } |
| }; |
| |
| /** |
| * Removes a header match value from the binding being populated. |
| * This handler is triggered by clicking a <li class="clickable-icon">. Unfortunately there's bit more work |
| * to do because we only want to respond if the actual icon has been clicked so we need to work out the |
| * mouse or tap position within the li and check it's less than the icon width. |
| * Once we're happy that we've clicked the icon we need to work out which binding the <li> relates to. The |
| * approach to this is a little messy and involves scraping some of the html associated with the <li> |
| */ |
| var removeHeaderMatch = function(e) { |
| var target = e.target; |
| var jthis = $(target).closest("ul li.clickable-icon"); |
| |
| if (jthis.length != 0) { |
| var ICON_WIDTH = 45; // The width of the icon image plus some padding. |
| var offset = Math.ceil(jthis.offset().left); |
| var x = (e.pageX != null) ? e.pageX - offset : // Mouse position. |
| (e.originalEvent != null) ? e.originalEvent.targetTouches[0].pageX - offset : 0; // Touch pos. |
| |
| if (x < ICON_WIDTH) { |
| var key = jthis.children("a:first")[0].firstChild.nodeValue; |
| delete _properties[key]; |
| $("#add-binding").trigger("show"); |
| } |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#add-binding").unbind("show").bind("show", show); |
| $("#add-header-match").unbind("show").bind("show", showHeaderMatch); |
| |
| // Using END_EV avoids the 300ms delay responding to anchor click events that occurs on mobile browsers. |
| $("#add-binding .right.button").bind(qmfui.END_EV, submit); |
| $("#add-header-match .right.button").bind(qmfui.END_EV, addHeaderMatch); |
| |
| // Always initialise to default value irrespective of browser caching. |
| $("#x-match-all").click(); |
| |
| // Using a click handler here doesn't seem to have the 300ms delay that occurs when using click handlers |
| // attached to anchors (hence why those use END_EV). It's *probably* because this is a delegating handler. |
| // JavaScript alert() can interfere with touchend so it's generally better to use click() if it's possible |
| // to do so without it causing that irritating delay!! |
| $("#add-headers-binding").click(removeHeaderMatch); |
| }; |
| }; // End of qmfui.AddBinding definition |
| |
| |
| /** |
| * Create a Singleton instance of the QueueSelector class managing the id="queue-selector" page. |
| */ |
| qmfui.QueueSelector = new function() { |
| var _id; |
| |
| /** |
| * This method renders dynamic content in the ExchangeSelector page. This is necessary because the set |
| * of exchanges to be rendered may change as exchanges are added or deleted. |
| */ |
| var show = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#queue-selector") { |
| return; |
| } |
| |
| // We pass in the ID of the list ltem that contains the anchor with an href to exchange-selector. |
| _id = data.id; |
| |
| var sourceQueueName = ""; |
| if (_id == "#add-binding-queue-name") { |
| $("#queue-selector .header a").text("Add Bin..."); |
| } else if (_id == "#move-messages-queue-name") { |
| $("#queue-selector .header a").text("Move Me..."); |
| sourceQueueName = qmfui.MoveMessages.getSourceQueueName(); // Use this to filter out that queue name. |
| } |
| |
| var queues = qmfui.Console.getQueues(); |
| var filteredQueues = []; |
| var currentlySelected = $(_id + " p").text(); |
| |
| // Check the status of any Queue that the user may have previously selected prior to hitting "Done". |
| if (currentlySelected != "None (default)") { |
| if (qmfui.Queues.getQueueByName(currentlySelected) == null) { // Check if it has been deleted. |
| alert('The currently selected Queue "' + currentlySelected + '" appears to have been deleted.'); |
| currentlySelected = "None (default)"; |
| } |
| } |
| |
| var checked = (currentlySelected == "None (default)") ? "checked" : ""; |
| filteredQueues.push("<li><label for='queue-selector-queueNone'>None (default)</label><input type='radio' id='queue-selector-queueNone' name='queue-selector' value='None (default)' " + checked + "/></li>"); |
| |
| var length = queues.length; |
| for (var i = 0; i < length; i++) { |
| var name = queues[i].name; |
| // We do getQueueByName(name) because the _isQmfQueue property is a "fake" property added by the |
| // qmfui.Queues class, it's only stored in QMF objects held in the getQueueByName() and getQueue() caches. |
| var isQmfQueue = qmfui.Queues.getQueueByName(name)._isQmfQueue; |
| checked = (currentlySelected == name) ? "checked" : ""; |
| |
| // Filter out queues bound to QMF exchanges as we don't want to allow additional binding to those. |
| if (!isQmfQueue && (name != sourceQueueName)) { |
| filteredQueues.push("<li><label for='queue-selector-queue" + i + "'>" + name + "</label><input type='radio' id='queue-selector-queue" + i + "' name='queue-selector' value='" + name + "' " + checked + "/></li>"); |
| } |
| } |
| |
| qmfui.renderArray($("#queue-selector-list"), filteredQueues); |
| $("#queue-selector-list input").change(changeQueue); |
| }; |
| |
| /** |
| * Event handler for the change event on "#queue-selector-list input". Note that this is bound "dynamically" |
| * in the show handler because the queue list is created dynamically each time the QueueSelector is shown. |
| */ |
| var changeQueue = function(e) { |
| var jthis = $(e.target); |
| if (jthis.attr("checked")) { |
| $(_id + " p").text(jthis.siblings("label").text()); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#queue-selector").unbind("show").bind("show", show); |
| }; |
| }; // End of qmfui.QueueSelector definition. |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Generic Graph Rendering Page |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Graphs class managing the id="graphs" page. |
| */ |
| qmfui.Graphs = new function() { |
| var IS_IE = (navigator.appName == "Microsoft Internet Explorer"); |
| var IE_VERSION = IS_IE ? /MSIE (\d+)/.exec(navigator.userAgent)[1] : -1; |
| |
| var SECONDS_AS_NANOS = 1000000000; // One second represented in nanoseconds. |
| var MILLIS_AS_NANOS = 1000000; // One millisecond represented in nanoseconds. |
| var HEIGHT = 300; // Canvas height (including radiused borders). |
| var BORDER = 10; |
| |
| var _ctx = null; |
| |
| var _radius = new Image(); |
| _radius.src = "/itablet/images/ie/radius-10px-sprite.png"; |
| |
| /** |
| * qmfui is pretty much browser neutral as browser quirks have been taken care of by jQuery and the |
| * iTablet framework but canvas support is an edge case. Wrapping a canvas in a <li> doesn't seem to |
| * work (<li> is where most of the fake border radius stuff is done), so for canvas we simply use the |
| * canvas rendering itself and use drawImage() to render radius-10px-sprite.png. We use IE_VERSION |
| * from iTablet to detect the IE version as we only draw borders for IE 7 & 8 as IE9 has fake border-radius |
| * support and for IE6 radiused borders haven't been done at all because life is too short...... |
| */ |
| var drawBorderRadius = function(context) { |
| if (IE_VERSION == 8 || IE_VERSION == 7) { |
| var width = context.canvas.width; |
| var height = context.canvas.height; |
| |
| // Draw the border lines. |
| context.beginPath(); |
| // Drawing mid point of a pixel e.g. starting at 0.5 is important for getting one pixel lines. |
| context.rect(0.5, 0.5, width - 2, height - 2); |
| context.strokeStyle = "black"; |
| context.stroke(); // Draw new path |
| |
| // Render the radiused borders from the radius-10px-sprite.png sprite using canvas drawImage(). |
| context.drawImage(_radius, 0, 0, 10, 10, 0, 0, 10, 10); |
| context.drawImage(_radius, 0, 10, 10, 10, 0, height - 10, 10, 10); |
| context.drawImage(_radius, 10, 0, 10, 10, width - 10, 0, 10, 10); |
| context.drawImage(_radius, 10, 10, 10, 10, width - 10, height - 10, 10, 10); |
| } |
| }; |
| |
| |
| /** |
| * Aaaargh. Another IE edge case!! |
| * IE7 has very quirky behaviour - using $("#graphs-time-selector").innerWidth() is unreliable, it |
| * fails when page is initially shown and calling it also seems to cause the page to take a long |
| * time to re-render on resize. Using the width of body doesn't have that issue but getting the |
| * css left value of graphs is unreliable too!!! so I've just coded the values of LEFT & PAGE_WIDTH. |
| */ |
| var getWidth = function() { |
| if (IE_VERSION == 7) { |
| var LEFT = 251; // .main css left, |
| var PAGE_WIDTH = 0.9; // .page 100% - padding left + right |
| var width = $("body").outerWidth(); |
| width = Math.floor((width - LEFT) * PAGE_WIDTH - 0.5); |
| return width; |
| } else { |
| return ($("#graphs-time-selector").innerWidth() - 2); // The -2 compensates for the border width. |
| } |
| }; |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#graphs") { |
| return; |
| } |
| |
| // Get the latest update of the selected object and populate the text of the back button. |
| var object = null; |
| var backText = "Unknown"; |
| |
| if (data.queueId) { |
| object = qmfui.Queues.getQueue(data.queueId); |
| backText = "Queue"; |
| $("#graphs").removeClass("exchange connection"); |
| } else if (data.exchangeId) { |
| object = qmfui.Exchanges.getExchange(data.exchangeId); |
| backText = "Exchange"; |
| $("#graphs").addClass("exchange").removeClass("connection"); |
| } else if (data.connectionId) { |
| object = qmfui.Connections.getConnection(data.connectionId); |
| backText = "Connect..."; |
| $("#graphs").addClass("connection").removeClass("exchange"); |
| } |
| |
| if (object == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| var property = data.property; // An index to the particular property to be displayed. |
| var statistics = object._statistics; |
| |
| if (statistics == null) { |
| return; |
| } |
| |
| var description = statistics.description[property]; // Lookup the description of the property. |
| var isRate = (description == "msgDepth") ? false : true; // Is the statistic a rate. |
| |
| // Populate the page header with the property being graphed. |
| var header = (object.name == null) ? object.address + " " + description : object.name + " " + description; |
| $("#graphs .header h1").text(header); |
| |
| // Using $("#graphs .header a").text(backText) wipes all child elements so use the following. |
| $("#graphs .header a")[0].firstChild.nodeValue = backText; |
| |
| // Populate the graph title with the property being graphed. |
| if (isRate) { |
| description += " " + statistics.getRate(property).toFixed(2) + "/sec" |
| } else if (!statistics.short.isEmpty()) { |
| description += " " + statistics.short.getLast()[property]; |
| } |
| $("#graphs .page h1").text(description); |
| |
| var width = getWidth(); |
| if (_ctx) { |
| if (width != _ctx.canvas.width) { |
| _ctx.canvas.width = width; |
| } |
| |
| _ctx.clearRect(0, 0, width, HEIGHT); // Clear previous image. |
| drawBorderRadius(_ctx); // Draw fake border radius on old versions of IE. |
| |
| _ctx.beginPath(); // Start drawing path for grid. |
| for (var i = BORDER + 0.5; i < width; i += ((width - (BORDER*2))/10)) { // Draw vertical lines |
| _ctx.moveTo(i, BORDER); |
| _ctx.lineTo(i, HEIGHT - BORDER); |
| } |
| |
| for (var i = BORDER + 0.5; i < HEIGHT; i += ((HEIGHT - (BORDER*2))/10)) { // Draw horizontal lines |
| _ctx.moveTo(BORDER, i); |
| _ctx.lineTo(width - BORDER, i); |
| } |
| _ctx.strokeStyle = "#dddddd"; |
| _ctx.stroke(); // Draw grid. |
| } |
| |
| var stats = statistics.short; // 10 mins |
| var period = 600*SECONDS_AS_NANOS; // 600 seconds (ten minutes) in nanoseconds. |
| var interval = $("#graphs-time-selector input:radio[checked]").val(); |
| if (interval == "oneHour") { |
| stats = statistics.medium; // 1 hr |
| period = 3600*SECONDS_AS_NANOS; // 3600 seconds (one hour) in nanoseconds. |
| |
| } else if (interval == "oneDay") { |
| stats = statistics.long; // 1 day |
| period = 24*3600*SECONDS_AS_NANOS; // 24*3600 seconds (one day) in nanoseconds. |
| } |
| |
| var size = stats.size(); |
| var isMinimumSize = isRate ? size > 1 : size > 0; |
| |
| if (isMinimumSize && _ctx) { |
| /* |
| * For reasons of memory efficiency statistics are stored in ring buffers and attached to certain |
| * management objects (queue, exchange & connection) we only record statistics for certain properties |
| * of these management objects. For efficiency each item stored in the ring buffer is an array |
| * containing the statistics for each stored property with the last array item being the update |
| * timestamp of the management object. Thus the timestamp for each statistic sample is the last |
| * item (length - 1) of the array. Getting the length of any item held in the stats ring buffer |
| * gives us the index to use to lookup the timestamp, we use getLast() for convenience get(0) would |
| * be fine too. The isMinimumSize test ensures we have at least one item in the stats ring buffer. |
| */ |
| var TIMESTAMP = stats.getLast().length - 1; |
| |
| var curtime = (+new Date() * MILLIS_AS_NANOS); // Current time represented in nanoseconds. |
| var minimumTimestamp = curtime - period; // Items with timestamps less than this aren't in this period. |
| var limit = isRate ? size - 1 : size; // Maximum number of sample points. |
| var graph = [limit]; // Create an array to hold the data we'll put into the graph plot. |
| var count = 0; // This will be the actual number of sample points that fall within this period. |
| var i = 0; // Index into statistics circular buffer. |
| |
| // Skip over statistics older than the graph period. Mostly this won't happen, but for things like |
| // iOS where the browser may sleep/hibernate there may be a considerable gap between samples. |
| while ((i < limit) && (stats.get(i)[TIMESTAMP] < minimumTimestamp)) i++; |
| |
| var maxValue = 0; |
| for (;i < limit; i++, count++) { // i starts with the minimum index, count starts at zero. |
| var sample = stats.get(i); // Get each statistic sample from the circular buffer. |
| var value = sample[property]; // Get the sample value. |
| var timestamp = sample[TIMESTAMP]; // Get the sample timestamp. |
| |
| if (isRate) { |
| var sample1 = stats.get(i + 1); // Get the next sample so we can calculate the rate. |
| var value1 = sample1[property]; // Get the next sample's value. |
| var timestamp1 = sample1[TIMESTAMP];// Get the next sample's timestamp. |
| value = ((value1 - value)*SECONDS_AS_NANOS)/(timestamp1 - timestamp); // Rate in items/s |
| } |
| |
| var x = width + (timestamp - curtime)*(width/period); |
| graph[count] = {x: x, y: value}; |
| |
| if (value > maxValue) { |
| maxValue = value; |
| } |
| } |
| |
| var heightNormaliser = (HEIGHT - (2*BORDER))/maxValue; |
| |
| _ctx.beginPath(); // Start drawing path for main graph. |
| var sample = graph[0]; |
| _ctx.moveTo(sample.x, (maxValue - sample.y)*heightNormaliser + BORDER); |
| |
| for (i = 1; i < count; i++) { |
| sample = graph[i]; |
| _ctx.lineTo(sample.x, (maxValue - sample.y)*heightNormaliser + BORDER); |
| } |
| _ctx.strokeStyle = "black"; |
| _ctx.stroke(); // Draw main graph. |
| |
| // Draw grid text |
| _ctx.font = "15px Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif"; |
| _ctx.fillStyle = "red"; |
| _ctx.textBaseline = "middle"; |
| _ctx.textAlign = "right"; |
| _ctx.fillText(maxValue.toFixed(2), width - BORDER, BORDER); |
| _ctx.fillText((maxValue/2).toFixed(2), width - BORDER, HEIGHT/2); |
| _ctx.fillText("0.00", width - BORDER, HEIGHT - BORDER); |
| } |
| } |
| }; |
| |
| this.initialise = function() { |
| $(document).bind("orientationchange", qmfui.Graphs.update); |
| $(window).resize(qmfui.Graphs.update); |
| $("#graphs").unbind("show").bind("show", qmfui.Graphs.update); |
| $("#graphs-time-selector input").change(qmfui.Graphs.update); |
| |
| var canvas = $("#graphs-canvas")[0]; |
| if (canvas != null) { |
| // The following "shouldn't" be necessary as the graphs-canvas is part of the static markup and it |
| // works in the author's IE8 XP and Win7 test VMs without it, however getContext("2d") doesn't seem |
| // to be working on some IE8 deployments so it's added experimentally to see if it fixes the problem. |
| if (IS_IE && IE_VERSION == 8 && typeof(G_vmlCanvasManager) != "undefined") { |
| G_vmlCanvasManager.initElement(canvas); |
| } |
| |
| canvas.height = HEIGHT; |
| if (canvas.getContext) { // Is canvas supported? |
| _ctx = canvas.getContext("2d"); |
| } |
| } |
| }; |
| }; // End of qmfui.Graphs definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| |
| /** |
| * Create a Singleton instance of the Links class managing the id="links" page. |
| * TODO Add link/bridge features. |
| */ |
| qmfui.Links = new function() { |
| |
| this.update = function() { |
| |
| }; |
| |
| this.initialise = function() { |
| }; |
| }; // End of qmfui.Links definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the RouteTopology class managing the id="route-topology" page. |
| * TODO Add link/bridge features. The idea of the route topology page is to "discover" federated brokers linked |
| * to a "seed" broker. Not sure if this is actually possible, but it'd be pretty cool. |
| */ |
| qmfui.RouteTopology = new function() { |
| |
| this.initialise = function() { |
| }; |
| }; // End of qmfui.RouteTopology definition |
| |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Create a Singleton instance of the Events class managing the id="events" page. |
| */ |
| qmfui.Events = new function() { |
| var _events = new util.RingBuffer(20); // Store the Event history in a circular buffer. |
| |
| this.getEvent = function(index) { |
| return _events.get(index); |
| }; |
| |
| this.update = function(workItem) { |
| if (workItem._type == "EVENT_RECEIVED") { |
| var params = workItem._params; |
| var agent = params.agent; |
| |
| if (agent._product == "qpidd") { // Only log events from the broker ManagementAgent |
| _events.put(params.event); |
| iTablet.renderList($("#events-list"), function(i) { |
| var event = _events.get(i); |
| var name = event._schema_id._class_name; |
| var timestamp = new Date(event._timestamp/1000000).toLocaleString(); |
| var text = name; |
| |
| return "<li class='multiline arrow'><a href='#selected-event?index=" + i + "'>" + i + " " + text + |
| "<p class='sub'>" + timestamp + "</p></a></li>"; |
| }, _events.size()); |
| } |
| } |
| }; |
| }; // End of qmfui.Events definition |
| |
| /** |
| * Create a Singleton instance of the SelectedEvent class managing the id="selected-event" page. |
| */ |
| qmfui.SelectedEvent = new function() { |
| var _severities = ["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"]; |
| |
| this.update = function() { |
| var location = iTablet.location; |
| var data = location.data; |
| if (data == null || location.hash != "#selected-event") { |
| return; |
| } |
| |
| // Get the latest update of the selected event object. |
| var index = parseInt(data.index); // The parseInt is important! Without it the index lookup gives odd results.. |
| var event = qmfui.Events.getEvent(index); |
| |
| if (event == null) { |
| $("#resource-deleted").show(); |
| } else { |
| $("#resource-deleted").hide(); |
| |
| // Populate the page header with the event name. |
| var name = event._schema_id._package_name + ":" + event._schema_id._class_name; |
| $("#selected-event .header h1").text(name); |
| |
| var general = []; |
| var timestamp = new Date(event._timestamp/1000000).toLocaleString(); |
| general.push("<li><a href='#'>timestamp<p>" + timestamp + "</p></a></li>"); |
| general.push("<li><a href='#'>severity<p>" + _severities[event._severity] + "</p></a></li>"); |
| qmfui.renderArray($("#selected-event-list"), general); |
| |
| var values = []; |
| for (var i in event._values) { // Populate with event _values properties. |
| var value = event._values[i]; |
| if (i == "args" || i == "properties") { // If there are any args try and display them. |
| value = util.stringify(value); |
| } |
| values.push("<li><a href='#'>" + i + "<p>" + value + "</p></a></li>"); |
| } |
| qmfui.renderArray($("#selected-event-values"), values); |
| } |
| }; |
| |
| this.initialise = function() { |
| $("#selected-event").unbind("show").bind("show", qmfui.SelectedEvent.update); |
| }; |
| }; // End of qmfui.SelectedEvent definition |
| |