Build a server monitoring app using Node & Angular

Build a server monitoring app using Node & Angular


In this tutorial, we will build a Real-time server monitoring app using Node and Angular that will show the free memory available on our server.

Take a look at the app we are going to build:

server monitor

 we will be using following tech stacks

  • Vagrant
  • Node.js
  • Angular
  • chart.js

 To create our development server we will be using vagrant, so check here for instructions to install vagrant on your operating system.

 we will start by creating our server app, run the following commands:

mkdir duneserver
cd duneserver
vagrant init ubuntu/trusty64

We want our server to run Ubuntu 16.04, have a static IP address 192.168.50.4 and have NodeJS installed, so replace the content of Vagrantfile with the following:

Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.provision :shell, path: "bootstrap.sh"
config.vm.network "private_network", ip: "192.168.50.4"
end

Now, create a provisioning script named bootstrap.sh with the following content:

#!/usr/bin/env bash

apt-get update
curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh
bash nodesource_setup.sh
sudo apt-get install nodejs -y
sudo apt-get install build-essential -y
npm install forever -g

In the script above, we first update the apt repository and then we install NodeJS from a private PPA, install build-essential which is required by some NodeJS modules and install forever which helps us keep a node script running forever.

Now, we start the vagrant server with the command:

 vagrant up

Let’s create a package.json file with the content below in our duneserver directory:

{
      "name": "duneserver",
      "version": "1.0.0",
      "description": "a server monitoring app using Node & Angular",
      "main": "index.js",
      "scripts": {
        "start": "forever start index.js",
        "dev": "nodemon index.js",
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "epoch-charting": "^0.8.4",
        "nodemon": "~1.11.0",
        "os-monitor": "~1.0.5",
        "socket.io": "~2.0.3"
      }
    }

Our package.json contains 3 dependencies:

  • os-monitor–a very simple monitor for the built-in os module in Node.js,
  • nodemon–monitor for any changes in your node.js application and automatically restart the server – perfect for development,
  • socket.io–enables real-time bidirectional event-based communication.

Let us create our index.js file with the following content:

var io = require('socket.io')(8000);
var osm = require("os-monitor");

io.on('connect', function (socket) {
socket.emit('connected', {
status: 'connected',
type: osm.os.type(),
cpus: osm.os.cpus(),
});
});

io.on('disconnect', function (socket) {
socket.emit('disconnected');
});

osm.start({
delay: 3000 // interval in ms between monitor cycles
, stream: false // set true to enable the monitor as a Readable Stream
, immediate: false // set true to execute a monitor cycle at start()
}).pipe(process.stdout);

// define handler that will always fire every cycle
osm.on('monitor', function (monitorEvent) {
io.emit('os-update', monitorEvent);
});

In the code above we imported socket.io and created a server running on port 8000, next we imported os-monitor.

The socket.io server will emit a connect event when a client app connects with it, so on connect we send the os type (osm.os.type) and number of cpus(osm.os.cpus()) to the client.

In the last two sections of the code above, we start monitoring the server using osm.start(), at an interval of 300ms. os-monitor emits a monitor event on each monitor cycle, so on the monitor, we use socket.io to send the monitorEvent to the client.

Let’s ssh into the vagrant server, install the node modules and start the webserver:

vagrant ssh
cd /vagrant # vagrant synced directory
npm install
npm run dev

Building the Frontend

We will be using Angular IDE by CodeMix, so if you do not have it installed go ahead and download it from here.

Let’s get started by firing up our Angular IDE and create a new Angular project named DuneServerMonitor.

server monitor

Once the project is created we can now add Socket.io and Chart.js to our project. 

 npm install socket.io chart.js --save

Let us include bootstrap CSS in our project, by adding the following lines to the head section of our src/index.html file.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>DuneServerMonitor</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Then we replace the content of src/app/app.component.html with the following:

<div class="container">
        <h1>Dune Server Monitor</h1>
        <div style="background:#eee" class="">
                <div class="col-md-4">
                        <h4>Memory</h4>
                        <canvas id="mChart" width="400" height="400"></canvas>
                </div>

                <div class="col-md-8">
                        <h4>Cpu load average</h4>
                        <canvas id="cChart" width="400" height="200"></canvas>
                </div>
        </div>
</div>

Now we will update the Content of src/app/app.component.ts

// Imports
import { Component, OnInit } from '@angular/core';
import io from 'socket.io-client';
import { Chart, pattern } from 'chart.js';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app';
socket = io.connect('192.168.50.4:8000');
memChart = undefined;
cpuChart = undefined;
cpuType = '';
noOfCpu = '';
ngOnInit() {
const ctx = document.getElementById('mChart');
const doughnutGraphData = {
datasets: [{
data: [1, 0],
backgroundColor: ['#36a2eb', '#ff6384'],
}],
labels: [
'Free',
'Used',
]
};
this.memChart = new Chart(ctx, {
type: 'doughnut',
data: doughnutGraphData,
options: {}
});

const ctx2 = document.getElementById('cChart');
const cpuLoadGraphData = {
datasets: [{
label: '15 min average',
data: [],
backgroundColor: 'rgba(75, 192, 192, 0.4)',
}],
labels: ['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'],

};
this.cpuChart = new Chart(ctx2, {
type: 'line',
data: cpuLoadGraphData,
options: {}
});

this.socket.on('connected', (connectData) =&gt; this.connected(connectData));
this.socket.on('os-update', (event) =&gt; this.updateCharts(event));
}

// -----------------------------------------------------------------------Section 2
updateCharts(event) {

this.memChart.data.labels.pop();
this.memChart.data.labels.pop();
this.memChart.data.labels.push(`Free:${this.formatBytes(event.freemem, 2)}`);
this.memChart.data.labels.push(`Used:${this.formatBytes(event.totalmem - event.freemem, 2)}`);

this.memChart.data.datasets.forEach((dataset) =&gt; {
dataset.data.pop();
dataset.data.pop();
dataset.data.push(event.freemem);
dataset.data.push(event.totalmem - event.freemem);
});
this.memChart.update(0);

this.cpuChart.data.datasets.forEach((dataset) =&gt; {
if ( dataset.data.length &gt; 9) {
dataset.data.shift();
}
dataset.data.push(event.loadavg[2]);
});
this.cpuChart.update(0);
}

formatBytes(bytes, decimals) {
if (bytes === 0) {
return '0 Bytes';
}
const k = 1000,
dm = decimals || 2,
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

connected(connectData) {
this.cpuType = connectData.types;
this.noOfCpu = connectData.cpus;
}
}

Let’s understand the code snippet above:

First We used the Component decorator to mark our class as an Angular component and provide additional metadata that determines how the component should behave. The app.component.ts uses the content of app.component.html as it’s template and applies the style rules in app.component.css.

Then Our app class implements the OnInit interface which we imported from @angular/core, so we can use the ngOnInit life cycle hook called by Angular to indicate that Angular is done creating the component.

Finally, we created some class variables to be used in the succeeding sections:

  • socket which is an instance of the socket.io client
  • memChart currently undefined, but will be assigned as an instance of Chart from Chart.js
  •  cpuChart currently undefined, but will be assigned as an instance of Chart from Chart.js
  • cpuType and noOfCpu will be used to store the variables.

Next, we created a Doughnut chart to be used to display free memory and, we created a line chart to be used to display the 15-minute CPU load average.


In section 2 first, we empty the array that holds the labels for the free memory chart whose initial value is ['Free','Used'], then we push two new labels of the format Free: XXMB and Used: XXMB, then we empty the dataset for the doughnut chart and push new values, then we update the chart.

Conclusion

I hope you learned a few things about Angular and Node. Every article can be made better, so please leave your suggestions and contributions in the comments below. If you have questions about any of the steps, please do ask also in the comments section below.

You can check out the Server monitor repo.


Share on social media

//