Building a Realtime AngularJS Dashboard using Spring Rest and MongoDB — Part 1

Building a Realtime AngularJS Dashboard using Spring Rest and MongoDB — Part 1

In this post will show you how to build a RESTful web service using Spring MVC 4.0.6, Spring Data for MongoDB 1.6.1 so that we can integrate our Realtime Dashboard Web Application with a highly efficient datastore (MongoDB 3.0). This tutorial will be making extensive use of the SIGAR libraries to gather system statistics from the host operating system where the services reside. Additionally, this tutorial we will walk you through building the web service and NoSQL database backend and illustrate you how to implement some of the CRUD operations.

I will be using AngularJS for the front end, and will be using the Polling Technique or Periodic Refresh Pattern to get statistics from the Web Service via REST calls. This will be achieved using Angular’s $interval service. More on that later on…

To further enhance the user experience, we will use AngularJS, jQuery and several different Javascript Charting and Graphing Libraries (Sparkline, NVD3, ChartJS, JustGage, and CanvGauge) to create a visually appealing user interface that is highly efficient and easy to navigate and use.

As with other posts, we are continuing to build upon using an older post (Spring RESTful Web Service Example with JSON and Jackson using Spring Tool Suite) as a foundation.

We will be providing another tutorial with the focus on the GUI side and how we structured it using the various components including AngularJS, Javascript, CSS, JQuery, and JSPs. However, for this tutorial we will focus on Spring MVC, Spring Data for persistence to MongoDB and RESTful API.

Our Realtime Dashboard AngularJS Application

DashboardCpu

Getting Started using Spring Data

The primary goal of Spring Data is to make it easy to access both legacy relational databases in addition to new data technologies like NoSQL databases, map reduce frameworks and cloud based solutions. Spring Data for MongoDB is an umbrella project which aim to keep the consistent and familiar manner of the Spring based programming paradigm for new datastores.

In order to run this tutorial yourself, you will need the following:

Project Structure — Server Side

I have added the project overview to give you a full view of the structure and show you all files contained in this sample project.

dashboard_realtime_project

WEB-INF\Lib Folder Structure

I have included the folder structure because I would recommend including all of the SIGAR files as part of your WAR file. This library includes of the native libraries for the specific operating systems. If you do not wish to include the SIGAR libraries inside your package you will need to ensure that the Java Library Path contains the correct path to the location of the SIGAR libraries using:

-Djava.library.path=../path/lib/sigar/lib.

dashboard_lib_folder

RESTful Web Service End Points

#URIMethodDescription
1/statusGETReturns a simple HTML output of server status
2/statisticsGETReturns statistics used in the four upper panels of the Dashboard UI from MongoDB repository
3/cpuGETRetrieves details on CPU, processes, memory and swap space as well as historical running data
4/osdetailsGETRetrieves details on Operating System and Java Virtual Machine (JVM)
5/getallnetworkinterfacesGETFinds all network interfaces that are available
6/getnetworkstats?interface={interface}GETRetrieves statistics on network interface like speed, rxBytes, txBytes, packets, collisions, errors, etc
7/networkdetails?interface={interface}GETRetrieves details of the network interface like name, type, flags, description, MAC address, etc
8/getalldisksGETRetrieves a list of all available disks or devices that are mapped or available
9/diskdetails?drive={drive}GETProvides details on drive specified including available bytes, disk reads, free bytes, total bytes, and percentage used
10/heartbeatGETReturns the current count of heartbeat from the server
11/resetheartbeatGETResets the count of the heartbeat back to one (1)

DashboardRepository Data Access Object (DAO) for MongoDB (DashboardRepository.java)

In this class you will notice two annotations being used. The first one, @Repository indicates that the class DashboardRepository fulfills the role of a Data Access Object of a repository. This class will handle all of the persistence of Statistics object and database access for us.

Please Note

Although the Dashboard Repository has all the CRUD operations included in the code, the dashboard application is only using the getStatisticsByID method which will retrieve the one record containing all the data stats. I have also decided to put all the stats into one document to avoid unnecessary I/O and enhance performance. You may decide on using individual documents or records based on your own needs.

The second annotation, @Autowired indicates that MongoTemplate is autowired from the Spring configuration, in this case our dispatcher-servlet.xml file.

package com.avaldes.dao;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;

import com.avaldes.model.Statistics;
 
@Repository
public class DashboardRepository {
  private static final Logger logger = 
             LoggerFactory.getLogger(DashboardRepository.class);
						 
  public static final String COLLECTION_NAME = "statistics";
 
  @Autowired
  private MongoTemplate mongoTemplate;
 
  public void addStatistics(Statistics stats) {
    if (!mongoTemplate.collectionExists(Statistics.class)) {
      mongoTemplate.createCollection(Statistics.class);
    }
    
    logger.info("Inside addStatistics()...");
    mongoTemplate.insert(stats, COLLECTION_NAME);
  }
 
  public Statistics getStatisticsByID(String id) {
    Statistics stats =  mongoTemplate.findOne(
    Query.query(Criteria.where("_id").is(id)), Statistics.class, 
		   COLLECTION_NAME);
    
    logger.info("Inside getStatisticsByID (" + id +"), stats=" + stats);
    return stats;
  }
  
  public Statistics deleteStatistics(String id) {
  Statistics stats = mongoTemplate.findOne(
      Query.query(Criteria.where("_id").is(id)), Statistics.class, 
			   COLLECTION_NAME);
  
    mongoTemplate.remove(stats, COLLECTION_NAME);
 
    return stats;
  }
 
  public Statistics updateStatistics(String id, Statistics stats) {
  Statistics myStats = mongoTemplate.findOne(
    Query.query(Criteria.where("_id").is(id)), Statistics.class, 
		   COLLECTION_NAME);
    
    if (myStats != null) {
      logger.info("Inside updateStatistics(), updating record...");
      myStats.setId(id);
      myStats.setTodayHeading(stats.getTodayHeading());
      myStats.setTodayCount(stats.getTodayCount());
      myStats.setTodayAverage(stats.getTodayAverage());
      myStats.setTodayAverageSubheading(stats.getTodayAverageSubheading());
      myStats.setOnboardedHeading(stats.getOnboardedHeading());
      myStats.setOnboardedCount(stats.getOnboardedCount());
      myStats.setOnboardedSubheading(stats.getOnboardedSubheading());
      myStats.setSignupsHeading(stats.getSignupsHeading());
      myStats.setSignupsCount(stats.getSignupsCount());
      myStats.setSignupsSubheading(stats.getSignupsSubheading());

        mongoTemplate.save(myStats, "Statistics");    
        return stats;
        
    } else {
      logger.info("Inside updateStatistics(), unable to update record...");
        return null;
    } 
  }
}

The Model Classes (Statistics.java)

The simple model is used as a basis for storing and retrieving the fields to and from MongoDB as a document in the collection. This class contains several annotations. The first one, the @Document annotation identifies objects or entities that are going to be persisted to MongoDB. The next one, @Id is used to identify the field that will be used as an ID in MongoDB. This ID is labeled _id in MongoDB. The other two annotations are used mostly for Jackson conversion to XML and JSON respectively.

package com.avaldes.model;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonProperty;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
@XmlRootElement(name = "statistics")
public class Statistics  implements Serializable {
  private static final long serialVersionUID = -1982982909309202L;

  @Id private String id;
    private String todayHeading;
  private int todayCount;
  private double todayAverage;
  private String todayAverageSubheading;
  private String onboardedHeading;
  private int onboardedCount;
  private String onboardedSubheading;
  private String signupsHeading;
  private int signupsCount;
  private String signupsSubheading;

  @XmlElement(name="id", type=String.class) 
  @JsonProperty("id")
  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }
  
  @XmlElement(name="today_heading", type=String.class) 
  @JsonProperty("today_heading")
  public String getTodayHeading() {
    return todayHeading;
  }

  public void setTodayHeading(String todayHeading) {
    this.todayHeading = todayHeading;
  }

  @XmlElement(name="today_count") 
  @JsonProperty("today_count")
  public int getTodayCount() {
    return todayCount;
  }

  public void setTodayCount(int todayCount) {
    this.todayCount = todayCount;
  }

  @XmlElement(name="today_avg") 
  @JsonProperty("today_avg")
  public double getTodayAverage() {
    return todayAverage;
  }

  public void setTodayAverage(double todayAverage) {
    this.todayAverage = todayAverage;
  }

  @XmlElement(name="today_avg_subheading", type=String.class) 
  @JsonProperty("today_avg_subheading")
  public String getTodayAverageSubheading() {
    return todayAverageSubheading;
  }

  public void setTodayAverageSubheading(String todayAverageSubheading) {
    this.todayAverageSubheading = todayAverageSubheading;
  }

  @XmlElement(name="onboarded_heading", type=String.class) 
  @JsonProperty("onboarded_heading")
  public String getOnboardedHeading() {
    return onboardedHeading;
  }

  public void setOnboardedHeading(String onboardedHeading) {
    this.onboardedHeading = onboardedHeading;
  }

  @XmlElement(name="onboarded_count") 
  @JsonProperty("onboarded_count")
  public int getOnboardedCount() {
    return onboardedCount;
  }

  public void setOnboardedCount(int onboardedCount) {
    this.onboardedCount = onboardedCount;
  }

  @XmlElement(name="onboarded_subheading", type=String.class) 
  @JsonProperty("onboarded_subheading")
  public String getOnboardedSubheading() {
    return onboardedSubheading;
  }

  public void setOnboardedSubheading(String onboardedSubheading) {
    this.onboardedSubheading = onboardedSubheading;
  }

  @XmlElement(name="signups_heading", type=String.class) 
  @JsonProperty("signups_heading")
  public String getSignupsHeading() {
    return signupsHeading;
  }

  public void setSignupsHeading(String signupsHeading) {
    this.signupsHeading = signupsHeading;
  }

  @XmlElement(name="signups_count") 
  @JsonProperty("signups_count")
  public int getSignupsCount() {
    return signupsCount;
  }

  public void setSignupsCount(int signupsCount) {
    this.signupsCount = signupsCount;
  }

  @XmlElement(name="signups_subheading", type=String.class) 
  @JsonProperty("signups_subheading")
  public String getSignupsSubheading() {
    return signupsSubheading;
  }

  public void setSignupsSubheading(String signupsSubheading) {
    this.signupsSubheading = signupsSubheading;
  }

  @Override
  public String toString() {
    return "Statistics [id=" + id + ", todayHeading=" + todayHeading 
		    + ", todayCount=" + todayCount + ", todayAverage=" + todayAverage 
				+ ", todayAverageSubheading=" + todayAverageSubheading
        + ", onboardingHeading=" + onboardedHeading + ", onboardedCount=" 
				+ onboardedCount + ", onboardedSubheading=" + onboardedSubheading 
				+ ", signupsHeading=" + signupsHeading + ", signupsCount=" 
				+ signupsCount + ", signupsSubheading=" + signupsSubheading + "]";
  }
}

The Model Class for DiskData (DiskData.java)

The remaining Model classes are used to store information being retrieved from the SIGAR libraries. The REST calls will then make use of this stored information and relay the details back to the consumer of the service in either JSON format or XML.

package com.avaldes.model;

import javax.xml.bind.annotation.XmlElement;
import org.codehaus.jackson.annotate.JsonProperty;

public class DiskData {
  private String dirName;
  private String devName;
  private String typeName;
  private String sysTypeName;
  private String options;
  private int type;
  private long flags;
  private boolean isOnline;
  private double usedPercentage;
  
  @XmlElement(name="dir_name") 
  @JsonProperty("dir_name")
  public String getDirName() {
    return dirName;
  }

  public void setDirName(String dirName) {
    this.dirName = dirName;
  }

  @XmlElement(name="dev_name") 
  @JsonProperty("dev_name")
  public String getDevName() {
    return devName;
  }

  public void setDevName(String devName) {
    this.devName = devName;
  }

  @XmlElement(name="type_name") 
  @JsonProperty("type_name")
  public String getTypeName() {
    return typeName;
  }

  public void setTypeName(String typeName) {
    this.typeName = typeName;
  }

  @XmlElement(name="sys_type_name") 
  @JsonProperty("sys_type_name")
  public String getSysTypeName() {
    return sysTypeName;
  }

  public void setSysTypeName(String sysTypeName) {
    this.sysTypeName = sysTypeName;
  }

  @XmlElement(name="options") 
  @JsonProperty("options")
  public String getOptions() {
    return options;
  }

  public void setOptions(String options) {
    this.options = options;
  }

  @XmlElement(name="type") 
  @JsonProperty("type")
  public int getType() {
    return type;
  }

  public void setType(int type) {
    this.type = type;
  }

  @XmlElement(name="is_online") 
  @JsonProperty("is_online")
  public boolean isOnline() {
    return isOnline;
  }

  public void setOnline(boolean isOnline) {
    this.isOnline = isOnline;
  }

  @XmlElement(name="flags") 
  @JsonProperty("flags")  
  public long getFlags() {
    return flags;
  }

  public void setFlags(long flags) {
    this.flags = flags;
  }

  @XmlElement(name="used_percentage") 
  @JsonProperty("used_percentage")
  public double getUsedPercentage() {
    return usedPercentage;
  }
  
  public void setUsedPercentage(double usedPercentage) {
    this.usedPercentage = usedPercentage;
  }

  @Override
  public String toString() {
    return "DiskData [dirName=" + dirName + ", devName=" + devName
        + ", typeName=" + typeName + ", sysTypeName=" + sysTypeName
        + ", options=" + options + ", type=" + type + ", flags="
        + flags + ", isOnline=" + isOnline + ", usedPercentage="
        + usedPercentage + "]";
  }
}

The Model Class for Disks (Disks.java)

package com.avaldes.model;

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonProperty;

@XmlRootElement
public class Disks {
  private ArrayList<DiskData> diskData = new ArrayList<DiskData>();

  @XmlElement(name="disk") 
  @JsonProperty("disk")
  public ArrayList<DiskData> getDiskData() {
    return diskData;
  }
  
  public void addDrive(String dirName, String devName, String typeName,
      String sysTypeName, String options, int type, long flags, 
			    boolean isOnline, double usedPercent) {
    DiskData data = new DiskData();
    data.setDirName(dirName);
    data.setDevName(devName);
    data.setTypeName(typeName);
    data.setSysTypeName(sysTypeName);
    data.setOptions(options);
    data.setType(type);
    data.setFlags(flags);
    data.setOnline(isOnline);
    data.setUsedPercentage(usedPercent);
    diskData.add(data);
  }

  @Override
  public String toString() {
    return "AllDisks [diskData=" + diskData + "]";
  }
}

Let’s View the Realtime AngularJS Dashboard GUI Screens

As you can see from the following screenshots, the UI is quite appealing in its design.

Associated Posts

spring_mvc_angular_dashboard

Please Share Us on Social Media

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *