Skip to content

Waltz Workshop@ESRF_development

Ingvord edited this page Jan 31, 2019 · 5 revisions

This is a continuation for Waltz workshop@ESRF

Prerequisites

  1. Install Tango Controls
  2. Install Tango REST server
  3. Setup your IDE

IV. Developing custom dashboard for Waltz

In this section we will, first, develop a new widget (Dashboard) and integrate it into Waltz. Next, we will create a small custom application with this dashboard.

Adding a new widget

  1. Create new .js file in resources/webix_widgets and name it my_dashboard.js

  2. Copy'n'Paste the following code into the newly created file:

/**
 * @module MyDashboard
 */
(function(){
    //this function is private to this module
    var newPlotWidget=function(){
        return {
            gravity: 3,
            template: "template"
        }
    };

    /**
     * @type {webix.protoUI}
     */
    var my_dashboard = webix.protoUI(
        {
            name: 'my_dashboard',
            /**
             * @return {webix.ui}
             * @private
             */
            _ui:function(){
                return {
                    rows:[
                        {},
                        {
                            gravity: 3,
                            cols:[
                                {},
                                //call of the functuon. It is a good idea to move parts of the UI to a dedicated functions
                                newPlotWidget(),
                                {}
                            ]
                        },
                        {}
                    ]
                }
            },
            /**
             *
             * @param config
             * @constructor
             */
            $init:function(config){
                //extend client config with this widget's ui
                webix.extend(config, this._ui());
                //add some after construction logic
                this.$ready.push(function(){
                    webix.message("My dashboard has been initialized!")
                }.bind(this));//very important to bind function to a proper this object
            }
        }
    // webix.IdSpace is required to isolate ids within this component
    , webix.IdSpace, webix.ui.layout);//this component extends webix layout -- an empty view

    //this function will be available globally i.e. exports our dashboard
    newMyDashboard = function(config){
        return webix.extend({
            view: 'my_dashboard'
        }, config);
    }
})();

Here we have created a webix.protoUI my_dashboard -- a stub for a smart component of our new dashboard that extends webix layout. We also extends its functionality injecting webix.IdSpace mixin.

READ MORE:

[1] webix.protoUI

[2] webix.ui.layout

[3] webix mixins -- building blocks for existing and new components

[4] webix.IdSpace

  1. Next, we need to register our new dashboard among other platform widgets. For this open setup.js in resources/webix_widgets and add 'my_dashboard' to the array of webix files:
//file: resources/webix_widgets/setup.js

TangoWebappPlatform.ui = {
    _webix_files: [
        //...
        "attrs_monitor_view","device_monitor_view","scripting_console",
        "my_dashboard" //<!-- add this
    ]
};

This array is a list of widgets that are used in the application.

setup.js is required due to limitation of Nashorn (see upcoming presentation)

  1. Finally, lets add our dashboard to Waltz. Open main_controller.js in controllers/tango_webapp. And edit its buildUI function:
//file: controllers/tango_webapp/main_controller.js

    buildUI: function (platform_api) {
            //...

            ui_builder.set_right_item(TangoWebapp.ui.newDeviceControlPanel(platform_api.context));
            //add this to buildUI function -->
            ui_builder.add_mainview_item(
                {
                    header: "<span class='webix_icon fa-dashboard'></span> My Dashboard",
                    borderless: true,
                    body: newMyDashboard({id: 'my_dashboard'})
                });
            //<--
  1. Run Tomcat in IntelliJ IDEA (if not yet started and be sure REST is started) and check the result:

IMPORTANT: run Tomcat from IntelliJ IDEA, NOT from Terminal. Otherwise Tomcat won't update sources and may not deploy proper application at all.

adding plot to the new widget

  1. Replace newPlotWidget function from the previous part with the following:
    //file: resources/webix_widgets/my_dashboard.js

    //this function is private to this module
    var newPlotWidget=function(){
        return TangoWebapp.ui.newImageView({
            id:'mydashboard.plot',
            gravity: 3
        });
    };

Here we use existing ImageView from Waltz platform. In the next step we will add data to it.

  1. Let's update our PlotWidget every second.

NOTE Change localhost to what you have in Waltz if your Tango Host name differs.

NOTE Be sure your sys/tg_test/1 device is exported. If not, execute /usr/lib/tango/TangoTest test in Terminal where your Tango Controls locates.

Add this code to $ready.push(function(){...}) in my_dashboard.js.

    //file: resources/webix_widgets/my_dashboard.js#$init#this.$ready

    //store our plot widget reference for later use
    var plot = $$(this).$$('mydashboard.plot');
    //store attr promise for later use
    var attr = PlatformContext.rest.fetchHost('localhost:10000')
        .then(function(host){
            return host.fetchDevice('sys/tg_test/1');
        }).then(function (device) {
            return device.fetchAttr('double_image_ro')
        });
    //builtin JS function. It will execute attr.read every 1000 ms
    setInterval(function(){
        attr.then(function(attr){
                return attr.read();
            })
            .then(function(value){
                plot.update(value);
            })
            .fail(function(err){
                TangoWebappHelpers.error("Could not read attribute", err);
            });
    }.bind(this),1000);

Read more

[1] webix.promise

  1. Check the result. Switch to Firefox and refresh the page (F5):

Further improvements

  1. Use Runnable mixin to perform the routine. Mixin - a block of code that lets us group declarations we may reuse.

Inject TangoWebappPlatform.mixin.Runnable into my_dashboard view:

    //file: resources/webix_widgets/my_dashboard.js#my_dashboard

    //code line # ~76
    , TangoWebappPlatform.mixin.Runnable, webix.IdSpace, webix.ui.layout);//<-- add TangoWebappPlatform.mixin.Runnable
  1. Insert run method before $init:function(config){...}.
    //NEW CODE HERE!!!
    run:function(){
        var $$plot = $$(this).$$('mydashboard.plot');
        //note this.
        this.attr.then(function(attr){
                return attr.read();
            })
            .then(function(value){
                $$plot.update(value)
            })
            .fail(function(err){
                TangoWebappHelpers.error("Could not read attribute", err);
            })
    },
    /**
     *
     * @param config
     * @constructor
     */
    $init:function(config){

And we change the body of the this.$ready.push function.

Replace the this.$ready.push with following:

        this.$ready.push(function(){
            //NOW STORE attr AS PROPERTY
            this.attr = PlatformContext.rest.fetchHost('localhost:10000')
                .then(function(host){
                    return host.fetchDevice('sys/tg_test/1');
                }).then(function (device) {
                    return device.fetchAttr('double_image_ro')
                });
            //start the routine
            this.start();//this function is defined in Runnable mixin
        }.bind(this));//very important to bind function to a proper this object

NOTE here we delete setInterval in $ready function and add run method:

Usage of TangoWebappPlatform.mixin.Runnable will handle a number of situations for you. Like suspending the routine when the widget is not visible. To check this lets use Firefox dev tools:

  1. Switch to Firefox and refresh the page (F5). Now open dev tools (F12) and switch to the Network tab:

You will notice a bunch of requests every second. If you switch to Settings tab requests will cease to appear. This is because TangoWebappPlatform.mixin.Runnable implements this functionality.

And of course it is a good practice to extract common functionality and re-use it. You can define your own mixins!!!

  1. Using dev tools debugger. Very important feature and skill - to debug the code. Lets have a look what we get from the server when reading our attribute. Switch to Debugger tab look for my_dashboard.js, put a break point and switch back to My Dashboard tab in Waltz:

Add functional test.

  1. First generate a new test stub in your project root:
$>  ./jmvcc jmvc/generate/test functional test_my_dashboard
Generating ... test/functional/test_my_dashboard_test.js

              Done!


              Make sure to add to your application files!

$>

This command will add a new file in test/functional folder. Alter its content as follows:

new Test.Functional('test_my_dashboard',{
   test_truth: function() {
       var dashboard = newMyDashboard({id:'my_dashboard_test'});
       webix.ui({
           view: 'window',
           close: true,
           width: 800,
           height:600,
           body: dashboard
       }).show();

       this.assert(true);
   }
});

In this test we will simply open a new webix window with our dashboard.

Now we need to enable this test and run the application in test mode:

Read more

[1] webix.window

  1. Add 'test_my_dashboard' line into apps/tango_webapp/test.js
//file: apps/tango_webapp/test.js

include.functional_tests(
    //...
    'tango_webapp/attrs_monitor_view',
    'test_my_dashboard' //<!--
);
  1. Enable test mode in apps/tango_webapp/index.html:
<!-- file: apps/tango_webapp/index.html-->

<!--<script type="text/javascript" src="../../jmvc/include.js?tango_webapp,development"></script>-->
<script type="text/javascript" src="../../jmvc/include.js?tango_webapp,test"></script> <!-- replace development with test -->
  1. Switch to Firefox and refresh the page (F5). A test console will popup. Wait until all tests are loaded, switch to Functional tab, scroll to my_dashboard test and run it:

The final version of the code for this exercise is available here

Live demo >>

V. Developing custom application using Waltz (ex. TangoWebapp) platform

  1. Create new jmvc app in the project root:
$> ./jmvcc jmvc/generate/app my_app
               apps/my_app
Generating ... apps/my_app/compress.js
               apps/my_app/index.html
               apps/my_app/run_unit.js
               apps/my_app/test.js
               apps/my_app.js
               controllers/my_app/main_controller.js

Make sure to add new files to your application and test file!
$>
  1. Set up dependencies. Add include('platform') and webix widgets to apps/my_app.js :
//file: apps/my_app.js

include('platform')

//...
include(function(){
    //...
    //webix widgets
    include.resources(
        "webix_widgets/setup"
    );
});
  1. Enable plotly library as it is required for plots. Uncomment plotly in index.html:
<!--file: apps/my_app/index.html -->

<!--<script type="text/javascript" src="https://cdn.plot.ly/plotly-latest.js"></script>-->
<!-- uncomment plotly -->
<script type="text/javascript" src="https://cdn.plot.ly/plotly-latest.js"></script>
  1. Replace main_controller.js#load function with buildUI in controllers/my_app/main_controller.js:
//file: controllers/my_app/main_controller.js

//replace load function
buildUI: function(platform){
    var ui_builder = platform.ui_builder;

    ui_builder.add_mainview_item({
        header: "<span class='webix_icon fa-dashboard'></span> My Dashboard",
        borderless: true,
        body: newMyDashboard({id: 'my_dashboard'})
    });
}
  1. Check the result. Switch to Firefox and navigate to http://localhost:8080/WaltzDev/apps/my_app/index.html

NOTE my_app in the link

Packaging application

Packaging. Create assemble script in the project root folder:

var buildDir = "build/work";
$EXEC("ant -f jmvc/ant/build.xml build -Dapp=platform -DbuildDir=${buildDir}")
echo($OUT)
echo($ERR)
if($EXIT !== 0) exit($EXIT)

$EXEC("ant -f jmvc/ant/build.xml build -Dapp=my_app -DbuildDir=${buildDir}")
echo($OUT)
echo($ERR)
if($EXIT !== 0) exit($EXIT)

$EXEC("ant -f jmvc/ant/build.xml compress-and-move -Dapp=my_app -DbuildDir=${buildDir}")
echo($OUT)
echo($ERR)
if($EXIT !== 0) exit($EXIT)

$EXEC("ant -f jmvc/ant/build.xml copy-webapp -DbuildDir=${buildDir}")
echo($OUT)
echo($ERR)
if($EXIT !== 0) exit($EXIT)

$EXEC("ant -f jmvc/ant/build.xml war -DbuildDir=${buildDir}")
echo($OUT)
echo($ERR)
if($EXIT !== 0) exit($EXIT)

And run it: $> ./jmvcc assemble

The following output indicates successful execution:

...
war:
      [zip] Building zip: /storage/Projects/hzg.wpn/mTangoSDK/tango-webapp/build/distributions/TangoWebapp.war

BUILD SUCCESSFUL
Total time: 0 seconds
$>

build/distributions/TangoWebapp.war file can now be deployed to tomcat. But it is a good idea to automatize this process. See next section.

Continuous integration

  1. Move assemble script to jmvc/ folder. Now Travis will automatically build my_app every time new code is pushed to GitHub.

3. Applying custom styles

  1. Go to the webix skin builder link:

  1. Play with different style themes. We will choose material and adjust background color/view color and download the resulting theme:

  1. Unpack downloaded archive into stylesheets folder and enable the new skin in index.html (apps/my_app folder) adding:
<!-- file: apps/my_app/index.html -->

<!--<link rel="stylesheet" href="https://cdn.webix.com/5.2/skins/aircompact.css" type="text/css">-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="../../stylesheets/webix.css" type="text/css">

<!-- ... -->
<script type="text/javascript" src="https://cdn.webix.com/5.2/webix_debug.js"></script>
<script type="text/javascript" src="../../stylesheets/skin.js"></script><!-- add this -->
  1. Check the result:

Read more

[1] webix skins

Final code for this exercise can be found here

Live demo >>

VI. Advanced exercise

Data binding

Automatically switch data source for our widget when user clicks on an image attribute in the Device tree view

Responsive design

Test application for design responsiveness using dev tools

Debug Tango REST server

  1. Run Apache Tomcat from IntelliJ IDEA in debug mode

  1. Put a breakpoint on a particular Tango JAX-RS resource method e.g. JaxRsDevice#get:

  1. Now once client accesses Tango JAX-RS Device info rest-server will suspend at the breakpoint:

Run Tango REST server with HTTP/2.0 support

NOTE: to run Tango REST server with HTTP/2.0 a native tomcat HTTPS connector library must be installed. On Ubuntu/Debian it is libtcnative-1 e.g. Debian 9-bpo:

$> sudo apt-get install -t stretch-backports libtcnative-1
Reading package lists... Done
Building dependency tree       
Reading state information... Done
libtcnative-1 is already the newest version (1.2.18-1~bpo9+1).
0 upgraded, 0 newly installed, 0 to remove and 167 not upgraded.
$>

Add SSL certificate properties to TangoRestServer startup script:

#!/bin/bash

echo "Using TANGO_HOST=$TANGO_HOST"

#USERNAME=`whoami`
USERNAME=ingvord
echo "Using USERNAME=$USERNAME"

TOMCAT_SSL_CERTIFICATE_FILE=/etc/ssl/certs/ssl-cert-snakeoil.pem
TOMCAT_SSL_CERTIFICATE_KEY_FILE=/etc/ssl/private/ssl-cert-snakeoil.key

#enable debug
JAVA_OPTS="-Xmx4G -Xshare:off -XX:+UseG1GC -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5009"
echo "Using JAVA_OPTS=$JAVA_OPTS"

/usr/bin/java -jar $JAVA_OPTS -DTOMCAT_SSL_CERTIFICATE_FILE=$TOMCAT_SSL_CERTIFICATE_FILE -DTOMCAT_SSL_CERTIFICATE_KEY_FILE=$TOMCAT_SSL_CERTIFICATE_KEY_FILE -DTANGO_HOST=$TANGO_HOST /home/$USERNAME/bin/rest-server-1.6.jar -nodb -dlist test/rest/0

Here we have used pre-generated ssl cert/key from Ubuntu/Debian ssl-cert package. This is OK for development, but for production you will need a valid certificate signed by an authority.

Next run TangoRestServer under root user:

$> sudo ./TangoRestServer
...
INFO: Starting ProtocolHandler ["http-nio-10001"]
Jan 24, 2019 3:58:29 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["https-openssl-apr-10041"]
DEBUG 2019-01-24 15:58:29,750 [main - test/rest/0 - o.t.w.s.t.TomcatBootstrap] o.t.w.server.tomcat.TomcatBootstrap.bootstrap:82 - Done.

Now if we access Tango REST server on port 10041 it will upgrade communication protocol to HTTP/2.0 aka h2:

If you do not see protocol column in the developer/network window right click on the table header.

Resources

[1] Exploring JS

[2] Waltz platform API references

[3] webix documentation