Skip to content

IRIS UI Programming With AngularJS

Justin edited this page Feb 10, 2015 · 9 revisions

IRIS UI was programmed in JavaScript without using frameworks. Thus, it was quite difficult to be modified by 3rd party programmers. To solve this issue, IRIS 1.3.2 now adopts AngularJS-driven Periscope (UI), which is more enjoyable to tweak to suit your needs. If you are still using IRIS 1.0.0, just replace the Torpedo/web directory with that of IRIS 1.3.2. That is all you need to do.

Files

The web/index.html file is simply an AngularJS template. Each part of the template is associated with a AngularJS controller. All the controllers are defined within the web/controllers directory as .js files. Contrary to the older version of Periscope, this version does not have model directory.

index.html

header

<div class="header">
	<h1>IRIS: <span class="subtitle">The Recursive SDN Controller</span></h1>
	<div class="nav">
		<ul>
		<li><a class="home">Home</a></li>
		<li><a class="switches">Switches</a></li>
		<li><a class="devices">Devices</a></li>
		<li><a class="topology">Topology</a></li>
		</ul>
	</div> <!-- /nav -->
</div>

The above part of index.html defines tabs located at the upper-right corner of periscope window.

Sections (a.k.a AngularJS template)

<div class="home controller_statistics" ng-controller="CntlControllerStatus">
	<h1>Controller Statistics</h1><a ng-click="getData()"></a>
	<table class="vertical_table">
		<tr><td>Hostname:</td><td>{{stat.host}}:{{stat.ofport}}</td></tr>
		<tr><td>Healthy:</td><td>{{stat.healthy}}</td>
		<tr><td>Uptime:</td><td>{{stat.uptime}}</td>
		<tr><td>JVM memory bloat:</td><td>{{stat.free}} free out of {{stat.total}}</td>
		<tr><td>Modules loaded:</td><td>{{stat.moduleText}}</td>
	</table>
</div>

Above <div> defines a section. The classes assigned to this section (“home controller_statistics”) represents that this <div> belongs to ‘home’ tab (a section can belong to multiple tabs - in that case, you just list the names of the tab like ‘home switches’) and the name of the section: in this case, ‘controller_statistics’.

As you can easily identify, this section is associated with an AngularJS controller ‘CntlControllerStatus’. By attaching a controller to a part of HTML document using ‘ng-controller’ directive, it becomes automatically a template that is able to render the controller’s data. The data is delivered by a ‘scope’ object associated with the controller. (I know that this explanation is overly simplified. But, as you know, this document is but for IRIS, not for AngularJS.)

In the template, you can find AngularJS markups which begin with ‘{{’ and end with ‘}}’. The data within the markups represents data within the scope associated with the controller. Thus, ‘stat.host’ is actually ‘$scope.stat.host’

Controller

irisApp.controller('CntlControllerStatus', 
	['$scope', '$http', '$timeout',
	 function($scope, $http, $timeout) {
		$scope.getData = function() {
			$http.get('/wm/core/health/json')
			.success(function(data) {
				$scope.stat = data;
			})
			.error(function(){
				$scope.stat = {
					'host':'unknown',
					'ofport':'0',
					'healthy':'false',
					'uptime':'0',
					'free':'0',
					'total':'0',
					'moduleText':'Not available'
				};
			});
		};

		$scope.intervalFunction = function() {
			$timeout(function(){
				$scope.getData();
				$scope.intervalFunction();
			}, 3000);
		};

		$scope.getData();
		$scope.intervalFunction();
	}]
);

Above is the definition of ‘CntlControllerStatus’ controller. At the first a few lines, you can see symbols like $scope, $timeout, $http. Those names starts with $ are conventionally AngularJS services. The services are passed to your controller initialization function (3rd line) as arguments. The most important service instance is ‘$scope’ as you may already know.

In the controller initialization function, you should define controller methods, and call some of them if needed. In the above example, we have defined two methods and attached them to $scope, to make the methods possible to be called by client that uses the controller (i.e. index.html). And finally, we have invoked two method at the end of the initialization function. Thus, the controller periodically retrieves some data using REST API call, and saves the data into $scope.state as an object.

When the model (I mean, $scope.state) is updated, the updated model is automatically rendered in the view (I mean, the template associated with the controller ‘CntlControllerStatus’). The beauty of AngularJS is that the model-view synchronization is done automatically, behind the scenes.

Popups

New Periscope use popups instead of hidden tables. As many developers complained that the hidden tables are very hard to read and hard to extend for new features, I have humbly given up all the hidden tables, and introduced popup windows as an alternative. The popup is implemented by jQuery UI 1.10.2 APIs.

How to popup windows

In the some of the templates in index.html, you can easily find the following lines:

<td><a ng-click="showPortsPopup(switch.id)">{{switch.id}}</a></td>

Why we use ‘ng-click’, instead of onclick? If it was possible, we would have written as follows:

<td><a onClick="showPortsPopup('{{switch.id}}')">{{switch.id}}</a></td>

But this is simply not possible. For various reasons, AngularJS does not allow the JavaScript handler to access the AngularJS $scope. Thus, the popup-opening code should be in the controller, as a method. Let’s look at the definition of ‘CntlSwitches’ controller in controllers/switches.js file.

irisApp.controller('CntlSwitches', 
	['$scope', '$http', '$timeout', '$iris', '$compile',
	 function($scope, $http, $timeout, $iris, $compile) {
		// ... omitted ...

		$scope.showPortsPopup = function(id) {
			var e = angular.element('#hiddens>div.ports').clone();
			e.append('<ng-include src="\'tpl/ports.html\'"></ng-include>');
			var newScope = $scope.$new();
			$compile(e)( newScope );
			newScope.id = id;
			e.dialog({
				title: 'Ports of the switch ' + id,
				width: 800,
				close: function(event, ui) {
					newScope.goon = false;
				}
			});
		};

		// ... omitted ...

As ‘popup’ requires multiple on-demand templates to be created, we should create templates on the fly. The $compile service enables an HTML snippet to be turned into an AngularJS template. The variable ‘e’ is the HTML snippet. By compiling ‘e’ using a newly created scope, we get a new controller instance up and running. One sad thing about the controller is that we cannot reliably call the methods of the controller via ‘newScope’ object. One reason is that it took time for the controller to be up and running. Therefore, if you call the method (for example, newScope.something()) instantaneously, the browser will complain that the method ‘something’ is not defined yet. Thus, it’s best to let the controller do something on its own. And if you want to control its behavior, it’s best to use member variables (in the above example, ‘newScope.goon’).

Let’s see the ‘ports.html’, which is a dynamically-loaded template in the above example (using ‘ng-include’ directive).

<div ng-controller="CntlPorts">
<table class="horizontal_table mark_2th_row" width="100%">
	<caption><span>Ports:</span><span type="links"></span></caption>
	<thead>
	<tr>
		<th>#</th><th>Link Status</th><th>TX Bytes</th>
		<th>RX Bytes</th><th>TX Pkts</th><th>RX Pkts</th>
		<th>Dropped</th><th>Errors</th>
	</tr>
	</thead>
	<tbody>
		<tr ng-if="ports.length==0"><td colspan="8">No ports</td></tr>
		<tr ng-if="ports.length>0" ng-repeat="port in ports">
			<td>{{port.portNumber}}</td><td>{{port.status}}</td>
			<td>{{port.transmitBytes}}</td><td>{{port.receiveBytes}}</td>
			<td>{{port.transmitPackets}}</td><td>{{port.receivePackets}}</td>
			<td>{{port.receiveDropped + port.transmitDropped}}</td>
			<td>{{port.receiveErrors + port.transmitErrors}}</td>
		</tr>
	</tbody>
</table>
</div>

Now it’s time to take a look at the definition of ‘CntlPorts’ controller.

irisApp.controller('CntlPorts', 
	['$scope', '$http', '$timeout', 
	 function($scope, $http, $timeout) {
		// initialize some data internal to this controller.
		$scope.ports = [];

		// define getData method.
		$scope.getData = function() {
			// scope.id is set externally.
			$http.get('/wm/core/switch/'+ $scope.id +'/port/json')
			.success(function(data) {
				$scope.ports = [];
				_.each(data[$scope.id], function(port) {
					port['status'] = 'UP';						
					$scope.ports.push( port );
				});					
			})
			.error(function(){
				$scope.ports = [];
			});
			// go-on flag will be externally set.
			if ( $scope.goon != false )
				$timeout(function(){
					$scope.getData();
				}, 1000);
		};
		// fire once.
		$scope.getData();
	}]
);

Pretty simple, huh?

Clone this wiki locally