Application Packaging Standard

Last updated 18-Mar-2019

Presentation Logic

Think of presenting the resource usage for customers.

../../../../_images/counters-step-intro.png ../../../../_images/counters-step-provision.png ../../../../_images/counters-step-presentation-b.png ../../../../_images/counters-step-deploy.png ../../../../_images/counters-step-provisioning.png

Requirements

This development step updates the presentation logic with some UI widgets that show to customers resource consumption provided by the APS counters you have developed in the previous step. When opening the view with the list of resources, a customer will see the newest total resource consumption. Besides, the customer must be able to demand the update of those data.

../../../../_images/pa-demo-counter-resources.png

Continue Your Demo Project

Continue your demo project from the previous step.

Compared with the respective input file, the new ui/servers.js file must present the resource counters by means of the following updates:

  • Add tiles below the server list to show the resource consumption.
  • Make the Refresh button handler update the resource usage counters.

The following steps create the new script.

  1. Use the following script structure:

    define([
       "dojo/_base/declare",
       "dojo/when",
        "dojox/mvc/getStateful",
        "dojox/mvc/at",
       "dojo/_base/array",
    
       "aps/View",
       "aps/ResourceStore",
       "aps/xhr",
    
       "./displayError"
    ], function (
       declare,
       when,
       getStateful,
       at,
       arr,
    
       View,
       Store,
       xhr,
    
       displayError
    ) {
       var page, grid, self, contextId;
       return declare(View, {
          init: function() {
             self = this;   // Refer to the declared View object
    
             /* Initialize data only the first time this view is called
                after the base page is loaded or re-loaded.
                Define widgets and sync them with the data models.
                This method is missed in all subsequent cases when the view is called. */
          },
    
          onContext: function(context) {
             /* Perform some actions with data before visualizing widgets. */
          },
    
          onHide: function() {
             /* Performs some actions on leaving the view for another view. */
          }
       });
    });
    
  2. In the init method, define the data sources:

    self = this;
    /* Create the data store for VPSes */
    var vpsStore = new Store({
       apsType: "http://aps-standard.org/samples/counters/vps/1.0",
       target: "/aps/2/resources/"
    });
    
    /* Initialize a model to collect resource usage */
       self.usageModel = getStateful({
       "cpu": { "limit": 0, "usage": 0 },
       "ram": { "limit": 0, "usage": 0 },
       "disk": { "limit": 0, "usage": 0 }
       });
    
  3. Leave the handler for the New and Delete buttons the same as they are in the input package:

    /* Handler for the *New* button */
    var add = function() {
       /* Start the process of creating a VPS by going to the first screen */
       aps.apsc.gotoView("server-new-1");
    };
    
    /* Handler for the *Delete* button */
    var remove = function() {
       var btn = this;
       /* Get confirmation from the user for the delete operation */
       if (!confirm("Are you sure you want delete VPSes?")) {
          btn.cancel();
          return;
       }
    
       var sel = grid.get("selectionArray");
       var counter = sel.length;
    
       /* Clear the current messages on the screen */
       page.get("messageList").removeAll();
    
       sel.forEach(function(vpsId){
          console.log("I'm trying to delete VPS with id = [" + vpsId + "]");
    
          /* Remove the VPS from the APS controller DB */
          when(vpsStore.remove(vpsId),
              /* If success, process the next VPS until the list is empty */
             function(){
                console.log("VPS with id = [" + vpsId + "] removed");
                sel.splice(sel.indexOf(vpsId),1);
                grid.refresh();
                if (--counter === 0) { btn.cancel(); }
             },
             /* If failure, call the error handler */
             function(e){
                displayError(e);
                if (--counter === 0) { btn.cancel(); }
             }
          );
       });
    };
    
  4. Define the handler for the Refresh button:

    var refresh = function() {
       xhr(
          "/aps/2/resources/" + contextId + "/resourceCounting",
          { method: "GET",  handleAs: "json" }
       ).then(function() {
          return xhr(
             "/aps/2/resources/" + contextId,
             { method: "GET",  handleAs: "text" }
          );
       }).then(function(mgmtContext) {
          self.usageModel.set("cpu", mgmtContext.cpuUsageTotal);
          self.usageModel.set("ram", mgmtContext.memoryUsageTotal);
          self.usageModel.set("disk", mgmtContext.diskUsageTotal);
          grid.refresh();
       });
    };
    

    The handler performs the following actions one by one:

    • Refreshes the resource counters by calling the custom resourceCounting method.
    • Gets the updated management context resource along with the refreshed resource counters.
    • Updates the usage model and consequently updates the widgets synced with the model.
    • Refreshes the list of servers in the grid.
  5. Define the methods that change the state of VPSes - handlers for the Start and Stop buttons

    function changeState(state, btn) {
       var sel = grid.get("selectionArray"),
       counter = sel.length;
    
       /* Clear the current messages on the screen */
       page.get("messageList").removeAll();
    
       arr.forEach(sel, function(vpsId) {
          console.log("I'm trying to change state of VPS with id = [" + vpsId + "]");
    
          /* Save the VPS state property */
          when(
             xhr(
                "/aps/2/resources/" + vpsId + "/" + state,
                { method: "GET",  handleAs: "text" }
             ),
              /* If success, process the next VPS until the list is empty */
             function() {
                console.log("State of VPS with id = [" + vpsId + "] changed");
                sel.splice(sel.indexOf(vpsId),1);
                grid.refresh();
                if (--counter === 0) { btn.cancel(); } /* Remove busy state for button */
             },
             /* If failure, call the error handler */
             function(e) {
                displayError(e);
                if (--counter === 0) { btn.cancel(); }
             }
          ); // End of when();
       });
    }
    
    /* Handler for the *Stop* button */
    var stop = function() {
       changeState("stop", this);
    };
    
    /* Handler for the *Start* button */
    var start = function() {
       changeState("start", this);
    };
    
  6. Define and return the hierarchy of widgets. Along with the grid used in the input package, define the list of tiles showing the resource counters.

    return [
       ["aps/Grid", {
          id: this.genId("srv_grid"),
          store: vpsStore,
          selectionMode: "multiple",
          apsResourceViewId: "server-edit",
          noDataText: "No servers provisioned",
          noEntriesFoundText: "No servers meet your search criteria",
          columns: [{
                field: "name",
                name: "Name",
                type: "resourceName",
                filter: true
             }, {
                field: "hardware.memory",
                name: "RAM",
                title: "RAM",
                type: "integer",
                filter: true
             }, {
                field: "hardware.diskspace",
                name: "Disk Space",
                title: "Disk",
                type: "integer",
                filter: true
             }, {
                field: "hardware.CPU.number",
                name: "CPU",
                type: "integer",
                filter: true
             }, {
                field: "platform.OS.name",
                name: "OS",
                filter: true
             }, {
                field: "state",
                name: "State",
                filter: true
          }]}, [
             ["aps/Toolbar", [
                ["aps/ToolbarButton", {
                   id: "srv_new",
                   iconClass:"fa-plus",
                   type: "primary",
                   label: "New",
                   onClick: add
                }],
                ["aps/ToolbarButton", {
                   id: "srv_start",
                   iconClass:"fa-play",
                   type: "success",
                   label: "Start",
                   requireItems: true,
                   onClick: start
                }],
                ["aps/ToolbarButton", {
                   id: "srv_stop",
                   iconClass:"fa-pause",
                   type: "warning",
                   label: "Stop",
                   requireItems: true,
                   onClick: stop
                }],
                ["aps/ToolbarButton", {
                   id: "srv_delete",
                   iconClass:"fa-trash",
                   type: "danger",
                   label: "Delete",
                   requireItems: true,
                   onClick: remove
                }],
                ["aps/ToolbarButton", {
                   id: "srv_refresh",
                   iconClass:"fa-refresh",
                   label: "Refresh",
                   autoBusy: false,
                   onClick: refresh
                }]
             ]]
          ]],
          ["aps/Tiles", {
                title: "Resource consumption"
             },[
                ["aps/Tile", {
                      title: "CPU power",
                      gridSize: "md-4"
                   }, [
                      ["aps/UsageInfo", {
                         showPie: false,
                         value: at(self.usageModel.cpu, "usage"),
                         maximum: at(self.usageModel.cpu, "limit"),
                         textFirstNumber: '${value}',
                         description: 'core-hours',
                         hint: 'Limit: ${maximum}'
                }]]],
                ["aps/Tile", {
                      title: "Memory usage",
                      gridSize: "md-4"
                   }, [
                      ["aps/UsageInfo", {
                         showPie: false,
                         value: at(self.usageModel.ram, "usage"),
                         maximum: at(self.usageModel.ram, "limit"),
                         textFirstNumber: '${value}',
                         description: 'MB-hours',
                         hint: 'Limit: ${maximum}'
                }]]],
                ["aps/Tile", {
                      title: "Disk usage",
                      gridSize: "md-4"
                   }, [
                      ["aps/UsageInfo", {
                         textSuffix: "GB",
                         value: at(self.usageModel.disk, "usage"),
                         maximum: at(self.usageModel.disk, "limit"),
                         textFirstNumber: '${value}',
                         textSecondNumber: '${maximum}'
                }]]]
           ]]
       ];
    
  7. In the onContext method, assign the proper values to the global variables and the data model, and then refresh the grid.

    onContext: function(context) {
       grid = this.byId("srv_grid");
       page = this.byId("apsPageContainer");
       var mgmtContext = context.vars.context;
       contextId = mgmtContext.aps.id;
       self.usageModel.set("cpu", mgmtContext.cpuUsageTotal);
       self.usageModel.set("ram", mgmtContext.memoryUsageTotal);
       self.usageModel.set("disk", mgmtContext.diskUsageTotal);
       grid.refresh();
       aps.apsc.hideLoading();
    },
    
  8. Define the onHide method. The only button that calls another view is New. Thus, clear its state by calling the cancel function. Otherwise, it would show the loading state when a user comes back to this view.

    onHide: function() {
       this.byId("srv_new").cancel();
    }
    

Conclusion

This completes the development of the ui/servers.js script. You can compare it with the similar file in the sample package.