Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
paschmann
Advisor
Advisor

This is what we will be developing in this blog ....

Over the past few years Node.js has really caught my attention. The simplicity of Javascript with server side processing, Non-blocking-IO, Event Driven, and simple integration always intrigued me as a great combination for enterprise applications. (Somehow it sounds similar to HANA XS Engine :wink: )

A couple months ago I ran into a similar problem to Jon-Paul Boyd (HANA Forum Post) in which I wanted to use XS Engine for websocket/persistant connections to my HANA Instance, but due to the support not being included in SPS6, I decided to look elsewhere, and ended up using Node.js to fulfill this requirement.

In the past, while developing HANA/Node apps, I resorted to creating a XSJS App which really just acted as a middleware layer to push and pull data from my HANA DB, until recently I noticed a great blog post from ingo.sauerzapf which piqued my interest. The blog mentioned that holger.koser had created a HANA DB Client for Node making life extremely easy to connect to HANA directly from Node. I thought it would be good share the small project I developed using Node.js and this new client with the community in the hopes that others will share their experiences with the technology.

This blog is not necessarily an introduction to Node.js development as there are some nice tutorials and examples out there from Tobias Hoffman and Alessandro Spadoni. The blog is intended to cover a small app developed in Node.js and shows the development process behind it, taking it from conception through to reality. I encourage you to download a few of these components, and also the example out. This app, similar to another app I developed called Metric² (which you can read about here), it is a web based widget showing some friendly KPI’s on the performance of your HANA Instance. The app gets streaming data from our HANA backend displaying this in a friendly, simple dashboard. The dashboard is designed to be shown on a large format monitor in a Ops or IT center and can also very easily be modified to show any KPI's relevant to your business or needs.

Requirements:

SAP HANA Instance (e.g. AWS Developer Image)

Node.js installed (this does not need to be on the HANA box but same network with access to the HANA port - normally 30015).

Node Dependencies

We will also use a couple of helpful dependencies from the node community including Socket.io, Express and obviously hdb. Installing these packages is as simple as running "npm install hdb”. Once you have the dependencies installed we can start creating our app.

https://nodei.co/npm/hdb.png?compact=true

App Design

For me, I always start mocking up in my favorite Image IDE (Photoshop), I used this image as inspiration for my app. I liked the simplicity, clean layout with focus on the numbers at the bottom. In our case, we will add a small chart in the center, giving us a basic visual representation of the numbers being displayed:

PhotoshopHTML Mockup

Download the PSD

App Development

Index.html

In this case I decided to use Twitter Bootstrap to help with some of the layout/formatting of the page as well as some mundane tasks like Modal popups. From a coding perspective I started out developing the Single paged “index.html” file, doing my best to stick with my mockup which I previously created. I was sure to "id" all of my elements on this page as we will be updating these values from our node.js backend. This aspect of node development is strictly "traditional" web based development. No need to work with Node or any server for that matter. Once you have your page rendering the way you want, we can move on.


<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, maximum-scale=1.0" />
        <title>Metric²</title>
           <!-- jQuery -->
        <script src="https://code.jquery.com/jquery.js"></script>
        <!-- Socket.IO -->
        <script src='/socket.io/socket.io.js'></script>
        <!-- Peity - a lightweight sparkline package for jQuery -->
        <script src='js/jquery.peity.min.js'></script>
        <!-- Client side code needed -->
        <script src='js/client.js'></script>
       
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://blogs.sap.com//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
        <!-- Latest compiled and minified JavaScript -->
        <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
        <!-- CSS -->
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="top" class="background">
          <div class="centercontainer">
            <div class="container shadow">
                <div class="containerheader">
                    <table class="heading">
                        <tr>
                            <td rowspan="2" style="text-align: center; width: 10%;" >
                                <img id="statusicon" src="img/OKIcon.png"/>
                            </td>
                            <td style="vertical-align: top;">
                                <h1><span id="info-name">SAP HANA Instance</span>
                                <button class="btn btn-link" data-toggle="modal" data-target="#myModal">
                                  <span class="glyphicon glyphicon-cog settingsicon"></span>
                                </button>
                                </h1>
                            </td>
                        </tr>
                        <tr>
                            <td class="h3" style="padding-top: 20px;">
                                <span class="glyphicon glyphicon-flag icon" style="margin-left: 0px;" /></span><span id="info-alerts">0</span> Alerts
                                <span class="glyphicon glyphicon-repeat icon"></span><span id="info-version">1.0</span>
                                <span class="glyphicon glyphicon-map-marker icon"></span><span id="info-detail">Server Location</span>                       
                            </td>
                        </tr>
                    </table>
           
                </div>
                <div class="containerbody">
                    <span class="history">0</span>
                </div>
                <div class="containerfooter">
                    <table class="info">
                        <tr>
                            <td id="infoUSERS" class="td-info" onClick="setChart('USERS');">
                                <!-- The ID of each of our <SPAN> tags is important for updating the data being returned from the server -->
                                <span id="info-users" class="h1">0</span><br />
                                <span class="h3">Users</span>
                            </td>
                            <td class="td-info"> </td>
                            <td id="infoDISK" class="td-info" onClick="setChart('DISK');">
                                <span id="info-disk" class="h1">0</span> <sup>GB</sup><br />
                                <span class="h3">Free Disk</span>
                            </td>
                            <td id="infoMEM" class="td-info" onClick="setChart('MEM');">
                                <span id="info-mem" class="h1">0</span> <sup>GB</sup><br />
                                <span class="h3">Free Memory</span>
                            </td>
                            <td id="infoCPU" class="td-info selected" onClick="setChart('CPU');">
                                <span id="info-cpu" class="h1">0</span> <sup>%</sup><br />
                                <span class="h3">CPU</span>
                            </td>
                        </tr>
                    </table>
                </div> <!-- /.containerfooter -->
            </div> <!-- /.container -->
          </div> <!-- /.centercontainer -->
        </div> <!-- /.top -->
        <!-- Modal -->
        <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
          <div class="modal-dialog">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h4 class="modal-title" id="myModalLabel">Settings</h4>
              </div>
              <div class="modal-body">
              <form id="modalbox" role="form">
                  <div class="form-group">
                    <label for="servername">Name</label>
                    <input type="text" class="form-control" id="servername" placeholder="Enter a reference server name">
                  </div>
                  <div class="form-group">
                    <label for="serverdetail">Location</label>
                    <input type="text" class="form-control" id="serverdetail" placeholder="Description, Location or Other Information">
                  </div>
                  <div class="form-group">
                  <label for="bg">Background</label><br />
                      <label class="radio-inline">
                      <input type="radio" name="bg" value="../img/bg1.jpg" checked> Background 1
                    </label>
                    <label class="radio-inline">
                      <input type="radio" name="bg" value="../img/bg2.jpg"> Background 2
                    </label>
                    <label class="radio-inline">
                      <input type="radio" name="bg" value="../img/bg3.jpg"> Background 3
                    </label>
                    <label class="radio-inline">
                      <input type="radio" name="bg" value="none;"> None
                    </label>
                  </div>
                   <div class="form-group">
                    <label for="colorscheme">Color Scheme</label><br />
                       <label class="radio-inline">
                      <input type="radio" name="colorscheme" value="Dark" checked> Dark
                    </label>
                    <label class="radio-inline">
                      <input type="radio" name="colorscheme" value="Light"> Light
                    </label>
                    <label class="radio-inline">
                      <input type="radio" name="colorscheme" value="Fiori"> Fiori
                    </label>
              </div>
              <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary" id="modalSave" onClick="saveSettings();">Save changes</button>
              </div>
            </div><!-- /.modal-content -->
          </div><!-- /.modal-dialog -->
        </div><!-- /.modal -->
    </body>
</html>







App.js

Next we develop the app.js file which is the brains of our operation. This file is firstly going to act as our web server for our web site, and secondly provide the data from our HANA server to the web page, pushing the data via web sockets.

Below is the app.js code, here you can see how we process each request based on the type and subsequently respond with the requested data. You can also see how simple it is to call the HANA DB and respond with the results.


var express = require('express'),
  http = require('http'),
  hdb = require('hdb');
try {
    var app = express();
    var server = http.createServer(app);
    server.listen(3000);
    var io = require('socket.io').listen(server);
    app.use(express.static(__dirname + '/'));
    // development only
    if ('development' == app.get('env')) {
      app.use(express.errorHandler());
    }
    var client = hdb.createClient({
      host     : 'Your HANA IP Address or DNS Name',
      port     : 30015,
      user     : 'username',
      password : 'password'
    });
    client.connect(function (err) {
        if (err) {
            console.error('Connect Error:', err);
        } else {
            console.log('Connected to server');
        }
    });
    process.on('uncaughtException', function (err) {
      console.log('Caught exception: ' + err);
    });
    strContent = '';
    io.sockets.on('connection', function (socket) {
        socket.on('request', function (data) {
        // Handle Service Requests
        switch (data.service) {
            case 'CPU':
                client.exec("SELECT ABS(SUM(PROCESS_CPU)) as CPU from SYS.M_SERVICE_STATISTICS", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: 'CPU', response: rows[0].CPU});
                    }
                });
                break;
            case 'MEM':
                client.exec("select TO_VARCHAR(ROUND((FREE_PHYSICAL_MEMORY) /1024/1024/1024, 2)) AS FREEMEM
from PUBLIC.M_HOST_RESOURCE_UTILIZATION", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: data.service, response: rows[0].FREEMEM});
                    }
                });
                break;
            case 'INFO':
                client.exec("SELECT VALUE FROM SYS.M_SYSTEM_OVERVIEW WHERE NAME = 'Version'", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: data.service, response: rows[0].VALUE});
                    }
                });
                break;
            case 'DISK':
                client.exec("select TO_VARCHAR((ROUND(d.total_size/1024/1024/1024, 2) - ROUND(d.used_size/1024/1024/1024,2))) as FREESPACE
from ( ( m_volumes as v1 join M_VOLUME_SIZES as v2 on v1.volume_id = v2.volume_id ) right outer join m_disks as d on d.disk_id = v2.disk_id )
where d.usage_type = 'DATA' group by v1.host, d.usage_type, d.total_size,    d.device_id, d.path, d.used_size", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: data.service, response: rows[0].FREESPACE});
                    }
                });
                break;
            case 'USERS':
                client.exec("SELECT COUNT(CONNECTION_ID) as STATUS FROM SYS.M_CONNECTIONS
WHERE CONNECTION_STATUS = 'RUNNING'", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: data.service, response: rows[0].STATUS});
                    }
                });
                break;
            case 'ALERTS':
                client.exec("SELECT COUNT(ALERT_DETAILS) as ALERTCOUNT FROM _SYS_STATISTICS.STATISTICS_CURRENT_ALERTS", function(err, rows) {
                    if (err) {
                        console.error('Error:', err);
                    } else {
                        socket.emit('response', {service: data.service, response: rows[0].ALERTCOUNT});
                    }
                });
                break;
        }
      });
    });
} catch(err) {
    console.log(err);
}







The App does have a couple of different themes

which will hopefully make it fit with your office decor :wink:

Below you can see a couple of images of the app running and showing the output. You can obviously very easily modify the code to show anything relevant to your business case as well. The Node-hdb package for node.js really makes developing HANA connected Node apps a breeze! Thanks Holger!

Fiori Styled
Light Theme
Dark Theme
 

As usual - please feel free to comment on your experience with Node.js and if you feel like this type of technology is a good fit in the enterprise? Do you have any suggestions on what I could have done differently? :wink:

You can download the app here: Metric² for Node

Credits: The Bokeh backgrounds are from devientArt.

13 Comments
Labels in this area