Applications may provide what we call a "Topology Overlay" – a component that provides new behaviors to the GUI Topology View.
An overlay can:
- augment or override the contents of the Summary Panel
- augment or override the contents of the Details Panel for a selected item
- cause links to be highlighted and/or labeled
- cause devices/hosts to be badged with numeric or iconic information
- provide toolbar buttons to allow the user to invoke new functions
This tutorial walks you through the steps of developing such a component.
A fictitious company, "Meowster, Inc." is used throughout the examples. This tutorial builds on top of the Table View tutorial, although you could choose to only provide a topology overlay in your app, if you wished.
Let's get started...
Adding a Topology Overlay to our App
First of all, let's go back to the top level directory for our sample application:
Now let's add the topology overlay template files by overlaying the uitopo archetype:
When asked for the version, accept the suggested default: 1.0-SNAPSHOT, and press enter.
When asked to confirm the properties configuration, press enter.
Note that the pom.xml file should be ready to go (since we already edited it in the Custom View tutorial).
New Files in the Project Structure
You should see a number of new files added to the project:
- New Java classes:
- New directory ~/resources/app/view/sampleTopov, containing:
- New directory ~/resources/sampleTopov, containing:
Building and Installing the App
From the top level project directory (the one with the pom.xml file) build the project:
Assuming that you have ONOS running on your local machine, you can install the app from the command line:
If you still have the app installed from the custom or table view tutorials, you can "reinstall" instead:
$ onos-app localhost reinstall! target/meowster-sample-1.0-SNAPSHOT.oar
After refreshing the GUI in your web browser, the Topology View toolbar should have an additional overlay button:
Pressing our overlay button will activate the overlay (invoking "activate" callbacks both on the client side and server side) and insert our custom action buttons in the toolbar.
Pressing the slash ( / ) key will display the Quick Help panel, where we can see our action button key bindings are listed:
Pressing the Esc key will dismiss the panel.
Pressing an alternate overlay button will deactivate our overlay (invoking "deactivate" callbacks both on the client side and server side).
The Sample Application
Our sample app installs a topology overlay that provides custom buttons:
Pressing the "Mouse mode" button and then moving the mouse over devices causes egress links on that device to highlight:
Pressing the "Link mode" button causes all the links in the topology to highlight in the secondary color, and each link in turn to highlight in the primary color, together with a label:
Pressing the "Cancel mode" button will put the topology view back into a quiescent state.
Overlays in Brief
- The HTML stub file is simply a "hidden view" placeholder.
- The CSS (if needed) can provide custom styling.
- The server-side Java code provides the back-end support for deciding how to highlight the topology when needed, and how to customize the summary and detail panels.
Description of the Template Files - Server Side
These files are under the directory ~/src/main/java/org/meowster/app/sample.
The exact path depends on the groupId (also used as the Java package) specified when the application was built with onos-create-app.
The following subsections describe the salient features of each file...
This is the base Application class and may be used for non-UI related functionality (not addressed in this tutorial).
This is the base class for UI functionality, tailored to support a topology overlay component. The code is similar to the Custom View tutorial, but has a couple of subtle differences.
(1) Reference to the UIExtensionService:
(2) List a single, hidden application view descriptor, defining the internal identifier:
(3) Declaration of a UiMessageHandlerFactory to generate message handlers on demand. The sample factory generates a single handler each time, AppUiTopovMessageHandler, described below:
(4) Declaration of a UiTopoOverlayFactory to generate topology overlays on demand. The sample factory generates a single overlay each time, AppUiTopovOverlay, described below:
(5) Declaration of a UiExtension, configured with the previously declared UI view descriptor, message handler factory, and topology overlay factory:
Note that we are, once again, declaring a "resource path" (relative to the ~/src/main/resources directory) using the view ID as the subdirectory name. This tells the extension service that the glue files (see later) are located at ~/src/main/resources/sampleTopov/*.html.
(6) Activation and deactivation callbacks that register and unregister the UI extension at the appropriate times:
This class extends UiMessageHandler to implement code that handles events from the client.
(1) override init() to store references to the services we need:
Since we will be querying for devices, hosts and links, grab references to the corresponding service APIs.
(2) implement createRequestHandlers() to provide request handler implementations for specific event types from our topology overlay.
(3) define DisplayStartHandler class to handle "sampleTopovDisplayStart" events from the client:
(3a) implement process() to react to the start events, taking into account which "mode" we are in:
First of all, we clear our internal state:
..and make sure that no links are highlighted on the topology:
Then we deal with one of three modes: "mouse", "link", or "idle":
(3a.i) "mouse" mode reacts to which node in the UI the mouse is hovering over and returns data about the egress links for that node:
Note that cancelTask() simply cancels the background timer, if it is running (it is used for the "link" mode only).
sendMouseData() looks to see if a device is being hovered over, and if so, finds and highlights the egress links from that device:
Note the use of the fromLinks() helper method that takes a set of links and generates a Highlights object from them:
See descriptions of DemoLink and DemoLinkMap below for more details.
(3a.ii) "link" mode uses a background timer to iterate across the set of links, highlighting each one in turn:
initLinkSet() initializes an array of links to all active links in the topology, ready to iterate across them:
sendLinkData() uses a DemoLinkMap (collection of DemoLink instances) to collate information about the links, to use as intermediate data to create highlighting information:
First, all the links are added to the link map without "modification"; then the link from the set at the current index is added (again), but this time the demo link instance is marked as important and labeled with the index.
A highlights object is generated from the collection of demo links.
Note that the background task (invoked once a second, while active) also calls sendLinkData().
(3a.iii) "idle" mode puts our service into a quiescent state:
(4) define DisplayUpdateHandler class to handle "sampleTopovDisplayUpdate" events from the client:
(4a) implement process() to react to update events:
updateForMode() sets up the element for the identifier in the payload (if any), then invokes sendMouseData() or sendLinkData() if necessary:
Please note that this is not great coding (using exception handling to define control flow), but hopefully it gets the point across.
(5) define DisplayStopHandler class to handle "sampleTopovDisplayStop" events from the client:
(5a) implement process() to react to stop events:
This class extends UiTopoOverlay to implement server-side callbacks for modifying the summary and details panels. In the constructor, we provide a unique identifier against which our overlay will be registered. Note that the same identifier must be used in the client-side definition of our overlay also:
What happens in the background is that when it is time to refresh the Summary Panel, the framework constructs a PropertyPanel instance, modeling the information to be shown in the summary panel. If there is an active topology overlay, that overlay is given the opportunity to tweak the model, before it is shipped back to the client.
To make alterations to the panel, we override the modifySummary() method:
First we set a new title and change the glyph to display, then we remove standard line items that we don't care to show, finally adding our own version string.
Note that the removeAllProps() method can be used to delete all the properties in one fell swoop (if you don't want to use any of the default property values).
Alterations can also be made to the details panel. Here we override the modifyDeviceDetails() method:
Here we change the title, and remove a couple of properties. We also remove two of the four default (core) buttons, replacing them with two of our own.
This class extends BiLink to facilitate collation of information about links, prior to determining how the links should be highlighted. The constructor simply delegates to the super class:
To collect the pertinent data while iterating over links, we provide public methods to capture the data:
Note that the methods return a reference to this, to facilitate chained calls.
Finally, we need to implement the highlight() method to generate a LinkHighlight for this particular instance, when requested:
The enumeration parameter is provided in the signature to allow parameterization of the highlight generation based on some user-defined state. We are not making use of that feature here.
This class extends BiLinkMap<DemoLink> to provide a concrete class for collating DemoLink instances. Here we simply need to override the create() method:
Description of the Template Files - Client Side
Note that the directory naming convention must be observed for the files to be placed in the correct location when the archive is built. Since our view is using the unique identifier "sampleTopov", its client source files should be placed under the directory ~/src/main/resources/app/view/sampleTopov.
We need to define a "view" to store the resources under the .../app/view/ directory. However, the view will be marked as "hidden", so that it is not listed in the navigation pane; we only want to contribute a topology view overlay, not add another view.
|client files||client files for UI views||client files for "sampleTopov" view|
There are four files here:
Note the convention to name (or prefix) the files using our unique identifier, in this case "sampleTopov".
This HTML snippet is used simply as a placeholder:
This file implements our "Sample Demo Service" which provides an API for our topology view toolbar custom buttons to invoke. Let's break down the file and look at it in sections:
An anonymous function invocation is used to wrap the contents of the file, to provide private scope (keeping our variables and functions out of the global scope):
Variables are declared to hold injected references (console logger, various services), configuration "constants", and internal state:
Module and Demo Service Declaration
Towards the bottom of the file we declare both our angular module, and our Demo Service (factory):
The first argument to the module() function call is the declared name of our module - again, we are using camel-cased identifier prefixed with "ov"; the second argument is an empty array, indicating that our module has no dependencies (on other modules).
The factory() function is invoked on the returned module instance to declare our "SampleTopovDemoService" :
The first argument is the name of our service; the second argument is an array...
All elements in the array (except the last) are strings – the names of services on which our service depends. The last element of the array is a function that gets invoked to instantiate our service; note that the angular framework injects instances of the required services into the parameters of the function.
Our service simply stores the references in the outer scope (to be accessible to the other functions in the file) and returns its API – three functions for starting, updating and stopping our "display" application:
Main API Functions
The first API function, startDisplay(), expects an argument to declare the mode to be either "mouse" or "link":
The function remembers the current mode and then delegates to a function to send an event to the server, using the web socket service:
The first argument to the sendEvent() function is the name of the event; the second parameter is the event payload.
The second API function, updateDisplay(), takes an optional argument – m. This parameter should either be null (or undefined), or an object with an "id" property whose value is the identity of the node over which the mouse is currently hovering:
As long as we are in one of the modes ("mouse" or "link"), an update message is sent to the server, with the ID packed in the payload (if provided):
The third API function, stopDisplay(), sends an event to the server to clear the "mode", if we are currently in one:
Notice that the return value is true if the mode was cancelled and false otherwise; the function is written this way so that it can be used as an Escape key handler.
The sampleTopovOverlay.js file defines and configures our overlay. Again, we use an anonymous function invocation to cocoon our variables in their own scope:
Variables are declared for injected services, and for the definition of our overlay:
We'll come back to the overlay definition structure in a moment...
Registering the Overlay
At the bottom of the file we get a reference to our module and immediately run an anonymous function (with dependency injection) to register our overlay with the topology framework:
Notice that we inject a reference to our "SampleTopovDemoService" so that our overlay code can access the API to send messages back to the server.
The Overlay Object Structure
Firstly, the overlayId property should be a unique identifying string that is defined both here and in the UiTopologyOverlay subclass (AppUiTopovOverlay in this example):
The glyphId property defines the identity of the glyph to use for the overlay button in the topology toolbar. If the ID starts with an asterisk, as it does in this case, it tells the overlay service that the glyph is defined locally in this overlay object. Alternatively, the name of a glyph (without the asterisk) indicates a glyph from the standard glyph library should be used.
The tooltip property defines the text of the tooltip for when the mouse hovers over our overlay button in the toolbar. For consistency with other overlays, the form should be "<something> Overlay". Examples of other overlay tooltips are:
- Traffic Overlay
- Path Painter Overlay
The glyphs property (optional) defines custom glyphs to be installed into the glyph library so that they can be used in the topology view:
For each glyph property:
- the key is used as the glyph ID (prefixed with the overlay ID, as noted)
- the vb property defines the "view box"
- the d property defines the SVG "data" (with respect to the view box)
The activate and deactivate properties define functions that will be invoked when our overlay is activated (user clicks on our overlay button) and deactivated (user clicks on some other overlay button).
The buttons property (optional) binds button IDs (the object keys) to a glyph ID, tooltip, and callback function. These are our own custom buttons that can be injected into the details panel:
The keys should match the button IDs defined in AppUiTopoOverlay:
Custom buttons for our overlay are added to the third row of the toolbar, when our overlay is activated. The keyBindings property defines the keys to bind to, along with the glyph to use, the button tooltip, and the callback function for when the button is pressed:
The _keyOrder property should be an array which defines the order in which the keys should be added to the button row in the toolbar.
The keyBindings property names are taken from the list of logical key names defined in the Key Service; they should be selected from the following subset (since the other keys are used by the core topology view):
The hooks property defines a number of callback functions that get invoked as various user interaction events occur. Note that every hook function is optional – if your overlay does not require it, simply omit it from the hooks structure. (If you don't use any callbacks, you can omit the hooks structure altogether).
- invoked when the Esc key is pressed
- the function should return true if the event was "consumed", false otherwise
- typically the function in the business logic will maintain state as to whether there is anything to "clear" / "reset", and use that to decide whether Esc should be consumed or not
- Some example code:
- empty: invoked when the topology node selection changes to "nothing selected"
- single: invoked when the topology node selection changes to "a single node selected" (either a device or a host)
- the data parameter is the data model for the selected device
- multi: invoked when the topology node selection changes to "more than one node selected" (may be a combination of devices and hosts)
- the selectOrder parameter is an array of node IDs in the order that they were selected by the user
- mouseover: invoked when the mouse hovers over a node (device or host)
- the m parameter is a small object with the node ID, class and type
- mouseout: invoked when the mouse moves away from a node
- acceptIntent: a filter function that the overlay uses to declare which intent types it can visualize on the topology view
- the type parameter is the simple class name of the selected intent
- the function should return true for intent types that it can handle, false otherwise
- showIntent: a special callback invoked if the user selects an intent from the table on the Intent View, and selects this overlay to visualize it
- the info parameter is a small object containing the identity of the selected intent, (appId, appName, key, intentType)
- the function should pass this parameter to the business logic, which typically sends an event to the server
- the server-side code locates the appropriate intent, and generates an appropriate "showHighlights" response to send back to the topology view
- Refer to the traffic overlay for sample code:
For our sample code we do not define any custom styling; should we need to, this would be the place to do it.
The final piece to the puzzle are the two "glue" files used to patch references to our client-side source into index.html. These files are located in the ~src/main/resources directory.
This is a short snippet that is injected into index.html. It contains exactly one line:
This is a short snippet that is injected into index.html. It contains exactly two lines:
It's pretty obvious that this is a contrived example, but hopefully you can see how to put the pieces in place to have user gestures on the topology view interact with server-side code to retrieve, process, and return data to be displayed on the topology view.