You are here

Blog

GWT/GXT dashboard primer

This post is based on research we’ve undertaken to develop a pilot mash-up style, charting dashboard for our monitoring solution, Opsview Enterprise. However the concepts we discuss could be used when building a dashboard that displays information from many other enterprise solutions. The assumption we make is that the reader is familiar with Java and the Google Web Toolkit (GWT). For more information about GWT and the other libraries used in this blog please see the Resources section at the end.

Introduction

The Opsview Enterprise platform is very flexible and allows us to extract monitoring information via a REST based API. This API returns monitoring data as JSON objects or XML so it is easy to integrate with many UI frameworks. In this example we will develop a mini portal-like application that displays status for different host groups in one of its portlets. The client will be using the ‘GXT’ framework that is based on GWT for the user interface and some Java libraries for HTTP communication and JSON parsing.

Using the REST based API to talk to Opsview Enterprise

Let’s first have a look at how you can call Opsview Enterprise from a browser to collect monitoring data. In this case we can use the following URL to fetch host groups: http://<hostname>/opsview/api/status/hostgroup If we try using this URL directly from a browser it will not work, a 403 access denied will be returned even if we login with correct credentials. A couple of extra headers are needed for the authentication to work; we will look at them later on. To get around this problem we must first login to Opsview Enterprise the normal way via the Opsview Enterprise UI and then hit the URL: https://<hostname>/opsview/status/hostgroup This is basically the first URL you get to. Now you will have a session in the browser so you can test the API URL again. It will return JSON looking something like this:
{"hostgroup":{
"summary":{"handled":782,"unhandled":60,"total":842,
   "service":{"ok":558,"critical":206,"handled":747,"unknown":29,
"unhandled":58,"warning":12,"total":805},
  		   "host":{"handled":35,"unhandled":2,"down":7,"up":30,"total":37}},
"list":[
        {
            "hostgroup_id":"7",
            "hosts":{ "handled":2, "unhandled":0, "down":{"handled":1},
"up":{"handled":1}, "total":2},
"services":{"ok":{"handled":24},"critical":{"handled":21,"unhandled":3},
"handled":50,"highest":"critical","unknown":{"handled":5}, "unhandled":3,"total":53},
            "name":"ProjectX",
            "downtime":null
        },
        {
            "hostgroup_id":"13",
            "hosts":{"handled":2,"unhandled":1,"down":{"handled":2,"unhandled":1},"total":3},
            "services":{"critical":{"handled":73},"handled":86,"highest":"critical",
			"unknown":{"handled":13},"unhandled":0,"total":86},
            "name":"Internal",
            "downtime":"2"
        },
        {
            "hostgroup_id":"14",
            "hosts":{"handled":31,"unhandled":1,"down":{"handled":2,"unhandled":1},
"up":{"handled":29},"total":32},
            "services":{"ok":{"handled":534},"critical":{"handled":66,"unhandled":43},
"handled":611,"highest":"critical","unknown":{"handled":9,
"unhandled":2},"unhandled":55,"warning":{"handled":2,"unhandled":10},
"total":666},
            "name":"Hosting",
            "downtime":"2"
        }
    ]}}
So we can see by looking at it that it is nested JSON and in this case it returned the current status for 3 host groups: ProjectX, Internal, and Hosting. Because the JSON is nested we will benefit from doing some processing on it before giving it to the UI routines that in GXT which only handles flat JSON.

The Architecture for the Mini-Dashboard Application

The following picture shows an overview of the architecture and the different components that will be used in this solution: The GXT client will not call the Opsview Enterprise server directly but instead go via a proxy server. This has the benefit that the JSON response from the Opsview Enterprise server can be processed and converted into Serializable Java objects. In this case the JSON is turned into a Java object called HostGroup, which in turn contains a HostStatus object. The HostGroup object will contain enough information to be able to draw a pie chart showing the status for the different host groups. The HostStatus object will be used when the user clicks on one of the host group pie slices and wants to drill down to see more detailed status for a particular host group. Because we send the HostStatus object at the same time as the HostGroup object we do not have to make another remote call when the user drills down to see individual host group status. Another benefit of having the proxy server is that it enables us to have the Mini Dashboard client web app and the Opsview Enterprise server web app on different hosts in different domains. If we did not have the proxy server it would not be possible to access content in Opsview Enterprise as for security reasons you cannot open URL connections to a different host to the host from where the client is downloaded. Instead, you would have to install the Dashboard web app on the same host as Opsview Enterprise is running on.

The Mini-Dashboard Proxy Server

Let’s start implementing the proxy server using the Apache Commons HTTP Client and the Jackson JSON parser. For the remote calls between the Mini-Dashboard client and the proxy server (i.e. AJAX based client calls) we will implement a com.google.gwt.user.client.rpc. RemoteService as provided by Google Web Toolkit. It will handle all marshalling of calls for us. Here is a UML Diagram giving you an overview of the involved proxy server classes: First setup the OpsviewServerProxyService interface as follows:
/**
  *
  * The client side stub for the Opsview Proxy Service RPC service.
  * @author Martin Bergljung, Opsera Ltd.
  */
@RemoteServiceRelativePath("opsviewproxy")
public interface OpsviewServerProxyService extends RemoteService {
    public static final String SERVICE_NAME = "OpsviewServerProxyService";

    public List getHostGroups();
}
The RemoteServiceRelativePath annotation associates a RemoteService with a relative path. This annotation will cause the client-side proxy to automatically invoke the ServiceDefTarget.setServiceEntryPoint method with GWT.getModuleBaseURL() + value() as its argument. Subsequent calls to ServiceDefTarget.setServiceEntryPoint will override this default path. We define one method called getHostGroups that will return a list of HostGroup objects. Create a HostGroup object as follows:
/**
 * Host Group (method level javadoc omitted for simplicity).
 *  
 *  {
 *   "hostgroup_id":"14",
 *   "hosts":{"handled":31,"unhandled":1,"down":{"handled":2,"unhandled":1},
 *            "up":{"handled":29},"total":32},
 *   "services":{"ok":{"handled":534},"critical":{"handled":66,"unhandled":43},
 *              "handled":611,"highest":"critical","unknown":{"handled":9,"unhandled":2},
 *              "unhandled":55,"warning":{"handled":2,"unhandled":10},"total":666},
 *   "name":"Internal",
 *   "downtime":"2"
 *   }
 * 
 * @author Martin Bergljung, Opsera Ltd.
 */
public class HostGroup implements Serializable {
    private int m_downtime;
    private String m_hostgroupId;
    private HostStatus m_hostStatus;
    private String m_name;
    private String m_services; // not used

    public HostGroup() {}

    //Getters, Setters, equals, hashCode, and toString have intentionally been left out to save space 

}
Next step is to create an asynchronous version of the interface as AJAX calls are asynchronous by nature. This is the actual interface that the client will come in contact with:
/**
 * The async counterpart of OpsviewServerProxyService.
 */
public interface OpsviewServerProxyServiceAsync {
    public void getHostGroups(AsyncCallback
> callback);
}       
The extra element here is the callback parameter that the client will have to implement. Now let’s move on to the actual implementation of this interface. Create the following class that implements the getHostGroups method:
/**
 * The server side implementation of the Opsview Proxy Server RPC service.
*/
public class OpsviewServerProxyServiceImpl extends RemoteServiceServlet
implements OpsviewServerProxyService {
   public static final String OPSVIEW_API_BASE_URI =
            "https:///opsview/api";
   public static final String OPSVIEW_API_HOSTGROUP_STATUS_URI =
            OPSVIEW_API_BASE_URI + "/status/hostgroup";

   public List getHostGroups() {
        String json = callOpsview(OPSVIEW_API_HOSTGROUP_STATUS_URI);

        if (json != null && json.length() > 0) {
            return readHostGroupsJSON(json);
        }

        return new ArrayList();
   }
Here you need to update the hostname in the OPSVIEW_API_BASE_URI constant to reflect your installation. Also, if you are not using SSL then change to http. Then add the readHostGroupsJSON method that takes JSON returned from Opsview Enterprise and turns it into a list of HostGroup objects:
    private List readHostGroupsJSON(String json) {
        List hostGroups = new ArrayList();

        try {
            ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
            JsonNode rootNode = mapper.readValue(json, JsonNode.class);
            JsonNode hostGroupNode = rootNode.path("hostgroup");
            JsonNode listNode = hostGroupNode.path("list");

            for (JsonNode hostGroup : listNode) {
                HostGroup hg = new HostGroup();
                hg.setName(hostGroup.path("name").getTextValue());
                hg.setDowntime(hostGroup.path("downtime").getIntValue());
                String hostGroupId = hostGroup.path("hostgroup_id").getTextValue();
                hg.setHostGroupId(hostGroupId);
                hg.setServices("0"); // not used at the moment

                JsonNode hostStatus = hostGroup.path("hosts");
                HostStatus hs = new HostStatus();
                hs.setHostGroupId(hostGroupId);
                hs.setHandled(hostStatus.path("handled").getIntValue());
                hs.setUnhandled(hostStatus.path("unhandled").getIntValue());
                hs.setTotal(hostStatus.path("total").getIntValue()); // only one needed now

                JsonNode down = hostStatus.path("down");
                JsonNode up = hostStatus.path("up");
                hs.setDownHandled(down.path("handled").getIntValue());
                hs.setDownUnhandled(down.path("unhandled").getIntValue());
                hs.setUpHandled(up.path("handled").getIntValue());
                hs.setUpUnhandled(up.path("unhandled").getIntValue());

                hg.setHosts(hs);
                hostGroups.add(hg);
            }
        } catch (IOException e) {
            System.err.println("Fatal JSON Parsing error: " + e.getMessage());
            e.printStackTrace();
        }

        return hostGroups;
    }
What we do here is use Jackson JSON parser (org.codehaus.jackson) to first lookup the hostgroup node in the JSON response and then with this node we go on to lookup the list node. When we have the list node we can step through all the host groups and setup corresponding HostGroup objects. The HostStatus object is also setup by looking up the hosts node for each hostgroup node. Here is a snippet of the JSON structure that we are talking about:
{"hostgroup":{
...
"list":[
        {
            "hostgroup_id":"7",
            "hosts":{
Finally we also need to implement the callOpsview method that will use Apache Commons HTTPClient to call Opsview Enterprise. Here is how that is done:
private String callOpsview(String url) {
        String username = ;
        String password =
;

        HttpClient client = new HttpClient();
	// Apache is adding basic authentication in Opsera’s installation so
// the following 2 lines are necessary
        Credentials defaultcreds = new UsernamePasswordCredentials(username, password);
        client.getState().setCredentials(AuthScope.ANY, defaultcreds);
        GetMethod getMethod = new GetMethod(url);
        getMethod.setRequestHeader("X-Username", username);
        getMethod.setRequestHeader("X-Password", password);
        getMethod.setRequestHeader("Accept", "application/json");

        try {
            int statusCode = client.executeMethod(getMethod);
            if (statusCode == HttpStatus.SC_OK) {
                String contents = getMethod.getResponseBodyAsString();
                System.out.println(contents);
                return contents;
            } else {
                System.err.println("Got Error " + statusCode +
                        " when calling Opsview API: " +url);
            }
        } catch (Exception e) {
            System.err.println("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Release the connection.
            getMethod.releaseConnection();
        }

        return "";
    }
When you implement this the username and password need to be specified according to the user account in your Opsview Enterprise installation. Here we can also see that we are setting the following headers:
  • X-Username – Username for Opsview user account
  • X-Password – Password for Opsview user account
  • Accept – indicates that we will accept and handle JSON responses (can also be set to accept XML)
The Opsview Enterprise proxy server implementation also needs to be registered to a URL. We do this in the GWT module definition file as follows for testing purposes:
  <!— Setup the Opsview Server Proxy Service for hosted mode testing -->
    <servlet path="/opsviewproxy" class= 
			"com..server.OpsviewServerProxyServiceImpl" />
And as follows for the real web application, open up web.xml and add:
      <servlet>
      <servlet-name>opsviewServerProxyServiceServlet</servlet-name>
      <servlet-class>com.<your package>.server.OpsviewServerProxyServiceImpl</servlet-class>
    </servlet>

    <servlet-mapping>
      <servlet-name>opsviewServerProxyServiceServlet</servlet-name>
      <url-pattern>/dashboardApp/opsviewproxy</url-pattern>
    </servlet-mapping>

The Mini-Dashboard Client

The client is implemented as Google Web Toolkit application. So first create the main entry point / class for the application:
public class DashboardApp implements EntryPoint {

    public void onModuleLoad() {
        OpsviewServerProxyServiceAsync service = (OpsviewServerProxyServiceAsync)
                GWT.create(OpsviewServerProxyService.class);
        Registry.register(OpsviewServerProxyService.SERVICE_NAME, service);

        LayoutContainer container = new LayoutContainer();
        final BorderLayout layout = new BorderLayout();
        container.setLayout(layout);
        container.setWidth(1024);
        container.setHeight(768);
        container.add(createMainMenuAndToolbarPanel(), createMainMenuAndToolbarLayoutData());
        container.add(new Dashboard(2), createDashboardLayoutData());
        container.add(createBottomStatusBar(), createBottomStatusBarLayoutData());

        RootPanel.get().add(container);
    }
This creates the stub that handles communication with the Opsview Enterprise proxy server and registers this stub object locally. Then the layout for the GXT application is setup. Now create the individual panels and layout data:
    private ContentPanel createMainMenuAndToolbarPanel() {
        ContentPanel mainMenuAndToolbarPanel = new ContentPanel();
        mainMenuAndToolbarPanel.setTopComponent(new MainMenu());
        mainMenuAndToolbarPanel.setHeading("Opsview Dashboard...");
        return mainMenuAndToolbarPanel;
    }

    private ContentPanel createBottomStatusBar() {
        ContentPanel panel = new ContentPanel();
        ToolBar toolbar = new ToolBar();
        panel.setHeaderVisible(false);
        Label opsviewVersion = new Label();
        opsviewVersion.setStyleName("Arial");
        opsviewVersion.setText("Opsview Dashboard Example");
        Label loggedInInfo = new Label();
        loggedInInfo.setText("Logged in as Opsview Admin");
        toolbar.add(opsviewVersion);
        toolbar.add(new FillToolItem());
        toolbar.add(loggedInInfo);
        toolbar.setHeight(25);
        panel.add(toolbar);
        return panel;
    }

    private BorderLayoutData createMainMenuAndToolbarLayoutData() {
        BorderLayoutData breadcrumbsLayoutData =
new BorderLayoutData(Style.LayoutRegion.NORTH, 50);
        breadcrumbsLayoutData.setHideCollapseTool(true);
        breadcrumbsLayoutData.setMargins(new Margins(5, 5, 0, 5));
        return breadcrumbsLayoutData;
    }

    private BorderLayoutData createDashboardLayoutData() {
        BorderLayoutData data = new BorderLayoutData(Style.LayoutRegion.CENTER);
        data.setMargins(new Margins(0, 5, 0, 5));
        return data;
    }

    private BorderLayoutData createBottomStatusBarLayoutData() {
        BorderLayoutData breadcrumbsLayoutData =
new BorderLayoutData(Style.LayoutRegion.SOUTH, 25);
        breadcrumbsLayoutData.setHideCollapseTool(true);
        breadcrumbsLayoutData.setMargins(new Margins(0, 5, 0, 5));
        return breadcrumbsLayoutData;
    }
We are also going to need a menu and you can implement it if you like as it is not going to be used in this example. Here is a starting point:
public class MainMenu extends ToolBar {
   public MainMenu() {....
   }
}
Last thing we need for the client is the actual Dashboard class that will call the Opsview Enterprise proxy and draw the pie chart with host group status:
public class Dashboard extends Portal {
   public static final String OFC_FLASH_LOCATION = "dashboardApp/chart/open-flash-chart.swf";

   private Chart m_hostGroupsChart;
   private List m_currentHostGroups;

   public Dashboard(int columns) {
        super(columns);
        setBorders(true);
        setStyleAttribute("backgroundColor", "white");
        setColumnWidth(0, .50);
        setColumnWidth(1, .50);

        add(createHostGroupsChartPortlet(), 1);
        loadHostGroupsChartPortlet();
   }
Here we start off by implementing the Dashboard as a GXT Portal. We add one Portlet that will show the Host Group status pie chart. Then we call a method that will load the pie chart. Next implement the method that creates the Host Group chart portlet as follows:
   private Portlet createHostGroupsChartPortlet() {
        Portlet portlet = new Portlet();
        portlet.setHeading("Status - Host Groups Hierarchy");
        portlet.setLayout(new FitLayout());
        portlet.setHeight(400);

        String url = !isExplorer() ? "../../" : "";
        url += OFC_FLASH_LOCATION;
        m_hostGroupsChart = new Chart(url);
        m_hostGroupsChart.setBorders(true);

        //portlet.setIcon(ICONS.hostgroup());
        portlet.setTopComponent(createChartPortletToolBar());
        portlet.add(m_hostGroupsChart);

        configurePortlet(portlet);

        return portlet;
    }
First we create the portlet and set its layout and heading etc. We then create a chart component and add it to the portlet. The chart component uses Flash to draw the chart so we need to supply the chart component with the URL for the Flash component. We also create a little toolbar for the portlet to show that you could implement configuration of what you want to see this way:
    private ToolBar createChartPortletToolBar() {
        ToolBar toolbar = new ToolBar();

        Button searchButton = new Button("Config");
        //searchButton.setIcon(ICONS.properties());
        searchButton.setBorders(true);
        searchButton.setToolTip("Click here to configure the host group status chart");

        toolbar.setBorders(true);
        toolbar.setHeight(25);
        toolbar.add(new FillToolItem());
        toolbar.add(searchButton);

        return toolbar;
    }
The loading of the Chart Portlet is done as follows by calling the getHostGroups method of the Opsview Enterprise proxy server stub and implementing the onSuccess and onFailure methods:
    private void loadHostGroupsChartPortlet() {
        // Get the Opsview Server Proxy Service from the Registry
        final OpsviewServerProxyServiceAsync opsviewServerProxyService =
		(OpsviewServerProxyServiceAsync)
                Registry.get(OpsviewServerProxyService.SERVICE_NAME);

        // Make sure we got the service
        if (opsviewServerProxyService == null) {
            showInfoMsg("Opsview Server Proxy service could not be detected!");
        } else {
            opsviewServerProxyService.getHostGroups(new AsyncCallback
>() {
                public void onSuccess(List hostGroups) {
                    m_currentHostGroups = hostGroups;

                    ChartModel cm = new ChartModel("Hosts by Host Group",
                            "font-size: 14px; font-family: Verdana; text-align: center;");
                    cm.setBackgroundColour("#fffff5");

                    PieChart pie = new PieChart();
                    pie.addChartListener(listener);
                    pie.setAlpha(0.5f);
                    pie.setTooltip("#label#
#percent#");
                    pie.setColours("#ff0000", "#00aa00", "#0000ff", "#ff9900", "#ff00ff");

                    for (HostGroup hostGroup : hostGroups) {
                        int totalHosts = hostGroup.getHosts().getTotal();
                        String name = hostGroup.getName();
                        pie.addSlices(new PieChart.Slice(totalHosts, name + " (" +
totalHosts + ")", name));
                    }

                    cm.addChartConfig(pie);

                    m_hostGroupsChart.setChartModel(cm);
                }

                public void onFailure(Throwable throwable) {
			throw new RuntimeException(throwable);
                    showInfoMsg("Opsview Server Proxy
getHostGroups call failed: " + throwable.getMessage());
                }
            });
        }
    }
When the Opsview Enterprise server proxy responds we store the current host groups in a global variable so we can access it later on when the user clicks on one of the slices. We then do not have to call the server proxy as we have the HostStatus for the clicked on host group slice. We then create the PieChart and add a slice for each host group that was returned. A listener is also set for the pie chart and it will load the host status pie chart. The listener implementation looks like this:
    private ChartListener listener = new ChartListener() {
        public void chartClick(ChartEvent ce) {
            PieChart.Slice hostGroup = (PieChart.Slice) ce.getDataType();
            loadHostStatusChartPortlet(hostGroup.getText());
            Info.display("Chart Clicked", "You selected {0}.", "" + ce.getValue() +
", " + hostGroup.getLabel());
        }
    };
The Host Status Chart is loaded like this:
    private void loadHostStatusChartPortlet(String hostGroupName) {
        ChartModel cm = new ChartModel("Host Status for Host Group " + hostGroupName,
                "font-size: 14px; font-family: Verdana; text-align: center;");
        cm.setBackgroundColour("#fffff5");

        PieChart pie = new PieChart();
        pie.addChartListener(listener);
        pie.setAlpha(0.5f);
        pie.setTooltip("#label#
#percent#");
        pie.setColours("#ff0000", "#00aa00", "#0000ff", "#ff9900", "#ff00ff");

        for (HostGroup hostGroup : m_currentHostGroups) {
            if (hostGroupName.equals(hostGroup.getName())) {
                HostStatus hostStatus = hostGroup.getHosts();
                pie.addSlices(new PieChart.Slice(hostStatus.getDownUnhandled(),
"Down - unhandled (" + hostStatus.getDownUnhandled() + ")", "Down - unhandled"));
                pie.addSlices(new PieChart.Slice(hostStatus.getUpHandled(),
"Up - handled (" + hostStatus.getUpHandled() + ")", "Up - handled"));
                pie.addSlices(new PieChart.Slice(hostStatus.getDownHandled(),
"Down - handled (" + hostStatus.getDownHandled() + ")", "Down - handled"));
                pie.addSlices(new PieChart.Slice(hostStatus.getUpUnhandled(),
"Up - unhandled (" + hostStatus.getUpUnhandled() + ")", "Up - unhandled"));
            }
        }

        cm.addChartConfig(pie);

        m_hostGroupsChart.setChartModel(cm);
    }
When the Host Status chart is loaded it is not possible to go back to the Host Groups chart. There is no menu item for that or any implemented code to do that. If you like go ahead and implement this solution by yourself. The little toolbar in the Chart Portlet is implemented like this:
    private void configurePortlet(final ContentPanel panel) {
        panel.setCollapsible(true);
        panel.setAnimCollapse(false);
        panel.getHeader().addTool(new ToolButton("x-tool-gear"));
        panel.getHeader().addTool(
                new ToolButton("x-tool-close", new SelectionListener() {
                    @Override
                    public void componentSelected(IconButtonEvent ce) {
                        panel.removeFromParent();
                    }
                }));
    }
The last couple of methods are helpers:
    public static boolean isExplorer() {
        String test = Window.Location.getPath();
        if (test.indexOf("pages") != -1) {
            return false;
        }
        return true;
    }

    public static void showInfoMsg(String msg) {
        MessageBox box = new MessageBox();
        box.setButtons(MessageBox.OK);
        box.setIcon(MessageBox.INFO);
        box.setTitle("Information");
        box.setMessage(msg);
        box.show();
    }
}

Running the Mini-Dashboard Client

To run this application after installing it under for example Tomcat we use a URL like this: http://localhost:8080/opsviewui/dashboardApp.html When we run this we should see a Host Groups Chart looking something like this: Clicking on one of the Host Groups will show Host status as follows:

Resources

The Java libraries that were used in this example can be found here:

Get unified insight into your IT operations with Opsview Monitor

webteam's picture
by Opsview Team,
Administrator
Opsview is passionately focused on monitoring that enables DevOps teams to deliver smarter business services, faster.

More like this

Nov 05, 2015
Blog
By Opsview Team, Administrator

Have you ever wondered how Opsview generates all those graphs for your dashboards, reports and the new Graph Center?

Feb 05, 2016
Whitepapers
By Opsview Team, Administrator

In today’s Enterprise IT environments, 24x7 uptime is becoming an increasingly common requirement. Supporting...

Dec 15, 2014
Blog
By Opsview Team, Administrator

Hello all!
This is a brief blog post to explain how I quickly integrated my existing Opsview server with my existing ELK deployment.