From the web-browser's point of view, the client side code is comprised of:
|The main application page|
|Third party libraries|
|Supporting framework utilities|
(installed with out-of-the-box ONOS)
... along with their associated
For the following notes, the "wiring" can be seen by examining the
web.xml configuration file.
index.html is generated dynamically via the
MainIndexResource class. The
UiExtensions registered with the
onos.js is generated dynamically via the
MainModuleResource class. The
onos.js file is used as a template, with view IDs injected where indicated. The list of view IDs is generated by iterating across the
UiViews defined by each registered
nav.html is an HTML fragment generated dynamically via the
MainNavResource class. The
nav.html file is used as a template, with the navigation category headers and links injected where indicated. The headers and links are generated by iterating across the category values, and within each category iterating across the
UiViews (for that category) defined by each registered
Additional views can be injected into the GUI at runtime. Each view has a unique identifier (e.g. "myviewid"). The following convention should be used to place client-side resources in the .oar file:
| +-- <UiExtId>
| +-- css.html
| +-- js.html
| +-- *.css
| +-- *.html
| +-- *.js
where <UiExtId> is the unique ID of the UiExtension instance (passed in as the "path" parameter at construction), and <viewid1> and <viewid2> are the unique IDs of the views provided by the extension.
The ONOS GUI has a corresponding server-side presence (on each ONOS instance in the cluster) which is exposed as the UiExtensionService. The following figure illustrates the relationships between the various components:
The UiExtensionService provides an API allowing the run-time addition (or removal) of UiExtension instances. The UiExtensionManager implements this service, and internally registers the "core" UiExtension instance (shown at the top of the figure) on activation of the service. ONOS applications may provide their own UiExtension instance, which they will register on activation, and unregister on deactivation. The bottom of the figure shows a typical extension implementing a single, additional view.
When a UiExtension registers, a mapping is cached to bind the identifiers of the extension's views to the extension instance, so that a look up of which extension implements a specific view can be performed at runtime.
See also the section "When a browser connects.."
Each UiExtension instance provides:
- a unique internal identifier (e.g. "core")
- a UiMessageHandlerFactory which generates UiMessageHandlers on demand, each of which generate RequestHandlers.
- one or more UiViews; these are server side "model" objects each representing a "view" in the GUI.
- two .html snippets providing linkage for injected .css and .js resources.
Note that there is generally a 1:1 correspondence between UiViews and UiMessageHandlers; the message handler providing a request handler for each request type that the view sends from the client to the server.
UiView instances are immutable objects that encapsulate information about a view:
- A Category; (currently, PLATFORM, NETWORK, OTHER, or HIDDEN)
- An internal identifier (e.g. "topo")
- A label (display string, e.g. "Topology")
- An icon identifier (e.g. "nav_topo")
CSS and JS Linkage
js.html should be provided under the resources directory. These snippets are injected into
css.html file might look like this:
js.html file might look like this:
The UiMessageHandlerFactory is responsible for generating a set of UiMessageHandlers on demand. These are requested when a browser connection is established with the server, to serve that GUI instance. The factory generates message handlers to handle all message events from the client (browser) that the views in this UI extension are expecting.
Generally, there is a 1:1 correspondence between a UiView and a UiMessageHandler; i.e. the message handler handles all request messages that a specific (client-side) view sends to the server. For example, the DeviceViewMessageHandler is responsible for fielding the messages received from the "Device" view.
Each instance of RequestHandler handles one specific request type. When the corresponding event comes in from a client, the appropriate handler's process(...) method is invoked to handle the request.
The TableRequestHandler is an abstract subclass of RequestHandler. The table pattern is so common that it makes sense to provide a partial implementation of the handler, as well as additional constructs to model the construction of the table data, and facilitate custom sorts and cell renderers.
When a Browser Connects...
As noted above, the
index.html file is generated on-the-fly to provide the initial load of the GUI. During the "bootstrap" sequence (in particular, the creation of the main (Angular) controller "OnosCtrl") a web-socket connection is established with the server by a call to the web socket service function createWebSocket().
On the server-side, the UiWebSocketServlet handles the incoming request and creates a UiWebSocket instance to establish a dedicated TCP connection between the server and the GUI. It is during the web-socket's onOpen() method call that the UiExtensionService is referenced to create handler instances and bind them to this web-socket instance.
(See also: Federated ONOS Web UI)
Message Events between Client and Server
With the establishment of the web-socket, both the client (browser) and the server are free to send messages to the other. This means that the server may send unsolicited messages to the client, as events occur in the environment.
Event messages are structured JSON objects, with the following general format:
The event field is a string uniquely identifying the event type.
The sid field is an optional sequence identifier (monotonically increasing integers) which, if provided by the client, should be duplicated in the server's response to that event. Note that, in general, server-initiated events do not include a sid field.
The payload field is an arbitrary JSON object, the structure of which is implied by the event type. Many payloads include an id field at the top level, which holds the unique ID of the item being described by the event.
See also the Core UI Extension Architecture page, which provides details on the architecture of the out-of-the-box views.