-
Notifications
You must be signed in to change notification settings - Fork 15
CLIRA
CLIRA is an application framework layered over [JUISE] (https://github.com/Juniper/juise/wiki/Intro) which helps create rich web applications by utilizing technologies like Javascript/Jquery, CSS, Handlebars, HTML5, etc. Apps are invoked by typing commands into the CLIRA command text box similar to how commands are executed on a JUNOS device CLI. The scope for the CLIRA app is limitless and not bound to just JUNOS device interactions.
CLIRA discussion forum: https://groups.google.com/forum/#!forum/clira-users
###Installing CLIRA
Download the CLIRA setup from the link below and double click to install.
Windows 64-bit setup : clira-0.7.0-2-Win-x64.zip
Windows 32-bit setup : clira-0.7.0-2-Win-x86.zip
Mac setup : CLIRA-0.7.0-2.dmg
Debian setup: juise_0.7.0-2.amd64.deb
RPM setup: juise-0.7.0-2.x86_64.rpm
Once this process is completed, you can verify if CLIRA is up and running by pointing your browser to :
In the main CLIRA page that loads, type show commands
in the command box
to see the list of available commands.
###Adding devices to CLIRA
Once CLIRA is up and running, its time to add remote devices to work with. Click on the Settings Icon at the top right corner. Under the 'Preferences' section click on 'Setup up Devices'. In the form that loads click on the '+' at the bottom left corner to add a device. Fill in the information as requested and click 'Submit' once you are done. If all went well, you will get a notification that the device has been added and can be used in CLIRA.
This process is included in the show-interfaces-demo app screencast here :
###Walkthrough a demo CLIRA app : show-interfaces-demo
Since this walkthrough goes over a specific app, some options and API information might not be discussed. For detailed API information please refer: https://github.com/Juniper/juise/blob/develop/web/README.md
In this walkthrough we will create a simple CLIRA app that would fetch a list of physical interfaces from a JUNOS device and render them in the app's view. We can then click on an interface item and it would expand to show some information about it.
Installed applications can be found at:
<CLIRA-INSTALL-DIR>/share/juise/web/apps
(Referred to as <apps>
from here on)
Each app and its related files are housed in one directory. These get auto-loaded when CLIRA is started.
The goal of this walkthrough is to demonstrate how to define the commands, create the template and handle DOM events like click, onChange, etc.
The demo app can be found at : <apps>/show-interfaces-demo
####1. Command File :
We first create our application directory under <apps>
and name it
show-interfaces-demo
. CLIRA simply searches for a js file with the same
name as its parent directory and loads it. So under this directory we create a
command file for our app and name it show-interfaces-demo.js
. The command
file contains app definition, templates and pre-requisite information. Its a
javascript file that invokes the commandFile
function from the $.clira
object to create the CLIRA command. We pass a command
object to this
function to define our app.
Our app would be invoked with the command : show interfaces <device-name>
where device-name
is the argument to the command. Before running the app we
would need to add this device to the CLIRA device list (As shown in
section 'Adding devices to CLIRA' above).
$(function($) {
$.clira.commandFile({
name : "show-interfaces-demo",
templateFile : "/apps/show-interfaces-demo/show-interfaces-demo.hbs",
commands : [
{
command : "show interfaces",
help : "Show interfaces on device",
templateName : "show-interfaces",
arguments : [
{
name : "device",
help : "Remote JUNOS device",
type : "string",
nokeyword : true
}
],
execute : function (view, cmd, parse, poss) {
/* Extract the argument */
var device = poss.data.device;
fetchInterfaceList(view, device);
}
}
]
});
});
Let's examine the command object properties in detail :
-
name : Command file name, can be any string of your choice.
-
templateFile : Location of the file where the template is defined. An app can have multiple templates housed inside one templateFile. The file needs to have a .hbs extension (handlebars file). We will see more about templates a little later.
-
commands : This is an array of commands. In our case we will have only one command in this array but you can obviously define more. Each command can have the following properties:
* **_command_** : the actual command string that will be entered in the CLIRA command box. * **_templateName_** : name of the template in the templatesFile to render the output of this command. * **_arguments_** : An array of argument objects. These are arguments to the command. Some of the properties are : * _name_ : argument name string * _type_ : type of argument (string, number, etc) * _help_ : help string for this argument. Is shown to the user as the command is being typed. * _nokeyword_ : Allows skipping of argument name/keyword and enter the argument value directly. For ex. : instead of `show interfaces device foo` we can do `show interfaces foo` * _execute_ : this function gets executed when we run our command. The arguments with which the function is executed are : * _view_ : the app output is controlled using this object. More on how to do that is explained in the next section. In order to render some output for our app we need to pass some data to the app's template. These templates are backed by a controller. You dont have to know what that means, just know that changing the properties of this controller causes Ember to re-render the app view with the new data passed to the controller. So to change the app output we simply get the app's controller and update its property. And we can get the app's controller using its view object as follows : `view.get('controller').set('ifList', ifList);` We just set the controller's `ifList` property to the list array we computed in our app. Ember will automatically update the app's view to reflect this new value. How to use the `ifList` property in a template is explained in the templates section a little later. You can also shorten that syntax to just : `view.set('controller.ifList', ifList);` * _cmd_ : the command string we executed. * _parse_ : internal use, ignore for now. * _poss_ : This is the possibilites object which contains the various completions to our command. We use this to extract the various arguments like this : `poss.data.<argument-name>` (in our case `poss.data.device`)
####2. Fetching the interface list
In the execute
function we call fetchInterfaceList
function to get us a
list of interfaces from the remote device and update the app's view with it.
function fetchInterfaceList (view, device) {
var cmd = "show interfaces";
/*
* Set the app loading view
*/
view.set('controller.loading', true);
$.clira.runCommand(view, device, cmd, "json", function (view, status,
result) {
if (status) {
var res = $.parseJSON(result);
var ifList = [];
/*
* Get the physical interfaces container (IFD's)
*/
var ifds = res["interface-information"][0]["physical-interface"];
/*
* Populate our ifList array with some IFD fields
*/
ifds.forEach(function (ifd) {
ifList.push({
name : field(ifd, 'name'),
adminStatus : field(ifd, 'admin-status'),
mtu : field(ifd, 'mtu'),
speed : field(ifd, 'speed')
});
});
/*
* Update our app's view with the list of interfaces and remove
* the loading message
*/
view.set('controller.loading', false);
view.set('controller.ifList', ifList);
} else {
/*
* RPC failed to execute
*/
view.set('controller.error', "RPC failed.");
}
});
}
function field(obj, prop) {
if (obj.hasOwnProperty(prop)
&& $.isArray(obj[prop])
&& obj[prop][0].hasOwnProperty('data')) {
return obj[prop][0].data;
}
return "N/A";
}
The above function can be broken down into 4 main parts :
-
Sending over the JUNOS command to the remote device
We use the
$.clira.runCommand
method to execute a command on a JUNOS device and get back its reply. The first 3 arguments are trivial :view
,device
andcommand
string to be executed. The 4thformat
argument specifies what format we want our response to be in. In this example we use JSON. The last argument is the callback function to be executed when the response is available. We use an anonymous function as the callback, which is executed in the global scope with 3 args:* _view_ : the app's view * _status_ : boolean to indicate RPC success or failure * _result_ : The actual result string in the format specified
-
Parsing the response string
To extract different parts of the response we first need to parse it into a
javascript object. For this we use the Jquery function $.parseJSON
. Once the
response has been parsed, we simply extract what we need and use it.
You obviously need to know the structure of the response in order to correctly extract stuff from it. For ex. :
var ifds = res["interface-information"][0]["physical-interface"];
will return the array of interface objects contained inside the
physical-interface
container.
We define a simple field()
function to extract the data from the leaf nodes.
Leaf nodes from the json output look like this :
"physical-interface" : [
{
"name" : [
{
"data" : "fxp0"
}
]
}
]
- Create an interface list array to hold interface information
For the purpose of this demo we simply extract a few fields from each interface
and populate our ifList
array. We use Javascript's forEach
function to
iterate over each interface entry in the array.
- Update the application view
In order to update our app output we use the view.set()
method and set the
controller properties as explained earlier. This triggers an automatic update
of the app's view and renders new HTML based on what data was passed.
For ex. :
Give user some feedback while fetching the interface information
view.set('controller.loading', true)
Pass on the ifList array to get rendered by the template
view.set('controller.ifList', ifList)
This will make more sense in the next section when we go through template definition.
####3. Defining the application view
The output from an app can we designed and controlled using a template. These templates follow the Handlebars framework. Ember compiles a template into a Javascript function which takes some data as input and generates HTML as output. Handlebars provide powerful constructs to control the output of your app. More info on this is available here :
http://guides.emberjs.com/v1.10.0/templates/handlebars-basics/
We first create a template file show-interfaces-demo.hbs
under
<apps>/show-interfaces-demo
In this file we can define many templates but we need just one for now. A
template needs to be wrapped inside the <script>
tag like this :
<script type="text/x-handlebars" data-template-name="show-interfaces">
{{#if loading}}
<label>Fetching interface information, please wait...</label>
{{/if}}
{{#if ifList}}
{{#each ifd in ifList}}
{{#view Clira.showInterfacesApp.interfaceView contentBinding='this'}}
<div class='ifdContainer'>
<div class='ifdName'><label>{{ifd.name}}</label></div>
<div class='ifdInfo'>
<label>Admin status : {{ifd.adminStatus}}</label>
<label>MTU : {{ifd.mtu}}</label>
<label>Speed : {{ifd.speed}}</label>
</div>
</div>
{{/view}}
{{/each}}
{{/if}}
{{#if error}}
<label>RPC failed</label>
{{/if}}
</script>
The above template is what our app will use. It contains a mix of HTML and
handlebars helpers. Handlebars stuff is enclosed in {{}}
. This template
contains 3 sections controlled using the {{#if}}
helper. In the previous
section we set the controller's properties like loading, ifList, etc. We use
that data to render our template.
We use 3 different helpers in the above template :
The {{#if}}
helper checks to see if ifList
exists and if it does we use the
{{#each}}
helper to iterate over the ifList
array. This {{#each}}
is
similar to the javascript forEach
method. We iterate through each ifd entry
in this array and create the HTML elements to display the interface
information. The third helper called the {{#view}}
helper is Ember specific
and allows more control over the view logic. It can be thought of as a section
of the template that can be processed further using the view logic. This logic
can be defined in your app and be passed on to the view helper.
For ex. :
{{#view Clira.showInterfacesApp.interfaceView contentBinding='this'}}
View context
{{view}}
The first argument to the view helper is the view path or the object
containing the view logic. There is a global object called Clira
available to
all apps, we simply add a showInterfacesApp
object to it to contain all our
app views. The second argument allows specifying the view
content to be passed
to the view logic. In our case this
corresponds to the current view context
and can be used in the view logic using the this
keyword as we will see next.
We define our view like this :
Clira.showInterfacesApp = {
interfaceView : Ember.View.extend({
didInsertElement : function () {
/*
* 'this' corresponds to the view context. The Jquery
* selector search space is restricted to the elements in the
* current view's block. We save the value of 'this' to 'that'
* since it'll have a different meaning inside the callback.
*/
var that = this;
this.$('.ifdInfo').hide();
this.$(".ifdContainer").click(function() {
that.$('.ifdInfo').toggle(200);
});
}
})
};
This might look overwhelming at first so lets break it down...
We first create an interfaceView
property inside our global Clira
object.
This property will point to an Ember view object created using
Ember.View.extend
method. We pass a hash containing various hooks to this
method. One of these hooks is the didInsertElement
which is called when
elements corresponding to this view are inserted into the DOM. So in our case
these elements are the HTML elements contained within the
{{#view}}...{{/view}}
block. This allows us to use Jquery selectors to
select HTML elements from within this view and define event handlers for them.
this.$()
=>
selects all elements in the view and 'this' corresponds to the view context.
In our example :
this.$('.ifdInfo')
=>
select elements in the view with class ifdInfo. An important thing to note
here is that the search is performed only on the the current view elements. So
even if you have elements in your HTML elsewhere with class name ifdInfo
,
they will not be selected. This gives us more control on our view.
So to express our logic in simple terms, we select all elements with class
ifdInfo
and hide them by default. We also define a click function for the
element with class ifdContainer
. On clicking this element, the ifdInfo
section visibilty will be toggled (hide or show depending on state).
Ok, so where are the class definitions we used in our template ? You need to add these CSS definitions in the application template using style tags :
<script type="text/x-handlebars" data-template-name="show-interfaces">
<!--APP CSS definitions-->
<style type="text/css">
.ifdContainer {
margin : 5px;
width : 150px;
border : solid 1px black;
border-radius : 5px;
}
.ifdName {
background: rgb(255,197,120);
border-radius : 5px;
padding : 5px;
}
.ifdName label {
font-family : 'Helvetica';
font-weight : bold;
}
.ifdInfo {
padding : 5px;
}
.ifdInfo label {
display : block;
margin : 1px;
}
</style>
<!--App template content follows-->
</script>
That's it! The app is ready and you can test it by running this command in the CLIRA command box:
show interfaces <device-name>
Check out the screencast : https://vimeo.com/130499891
-
CLIRA API : https://github.com/Juniper/juise/blob/develop/web/README.md
-
Jquery concepts: http://learn.jquery.com/
-
View helpers : http://guides.emberjs.com/v1.11.0/views/
-
Handlebars helpers : http://handlebarsjs.com/builtin_helpers.html