Main Service View

../../../../_images/step-project1.png ../../../../_images/step-meta1.png ../../../../_images/step-provision1.png ../../../../_images/step-presentation-b.png ../../../../_images/step-deploy1.png ../../../../_images/step-provisioning1.png

This document directs you through the process of creating the landing page for managing repositories in the UX1 panel for customers.

../../../../_images/step-presentation-licenses.png ../../../../_images/step-presentation-license-new.png ../../../../_images/step-presentation-license-edit.png ../../../../_images/step-presentation-services-b.png

In this document:

Overview

When managing a list of resources created from an APS type, you normally need a landing page that presents this list and allows users to launch particular management operations with the objects individually or together. For this purpose, use the aps/Grid container consisting of toolbars and a table with specified columns. To display general tenant parameters, use other widgets. Usually, these are combinations of aps/Panel and aps/FieldSet.

The view must allow a user to operate the following:

  • Monitor some brief data about the license and associated scopes bound to the tenant.

  • Monitor a list of repositories created by the tenant.

  • Add and delete repositories for the tenant.

Development

Extend the ui/services.js file you have created from the project template with the following JavaScript code:

  1. Ensure the define list contains all JavaScript modules that the main callback function will need. The view source structure must look as follows:

    define([
          "dojo/_base/declare",
          "dojox/mvc/getStateful",
          "dojox/mvc/at",
          "dojo/when",
          "aps/View",
          "aps/ResourceStore",
          "aps/xhr",
          "aps/Memory",
          "aps/Output",
          "./displayError"
        ],
        function(declare, getStateful, at, when, View, Store, xhr, Memory, Output, displayError) {
            var self;
            return declare(View, {
                init: function() {
                    self = this;
    
                },   // End of Init
    
                onContext: function(context) {
    
                }  // End of onContext
             });   // End of Declare
    });            // End of Define
    
  2. In the init method, initialize a data source store and a data model:

    /* Define the data source store */
    self.repoCache = new Memory({
       data: [],
       idProperty: "id"
    });
    /* Initialize a data model to sync with widgets */
    self.licenseModel = getStateful({ "data": {
       operations: "",
       licLimit: 0,
       usage: 0
    }});
    
  3. In the init method, define a handler for the license refresh button to update the license limit and usage in the data model.

    var refreshLicense = function() {
       xhr.get(
              "/aps/2/resources/" + self.tenant.aps.id + "/refreshdata")
       .then(function() {
          xhr.get("/aps/2/resources/" + self.tenant.aps.id)
          .then(function (tenant) {
              self.licenseModel.data.set("licLimit", tenant.licenseCounter.licLimit);
              self.licenseModel.data.set("usage", tenant.licenseCounter.usage);
          });
       });
    };
    

    The above code calls the custom operation refreshdata (GET /refreshdata) defined earlier in the provisioning logic. Then it uses the returned licenseCounter structure to update the data model. In a later step here, we will bind the model to proper widgets.

  4. In the init method, define a handler for the repository refresh button to update the list of repositories created by the tenant:

    var refreshRepos = function() {
       when(xhr(
          // Call the custom getRepos operation
          "/aps/2/resources/" + self.tenant.aps.id + "/repos",
          { method: "GET", handleAs: "json" }
          ),
          // When finished, refresh the Grid
          function (repos) {
              var data = [];
              repos.forEach(function(repo) {
                  data.push(repo);
              });
              self.repoCache.setData(data);
              self.repoGrid.refresh();
       });
    };
    

    The above code calls the custom operation getRepos (GET /repos) defined earlier in the provisioning logic. Then it uses the returned set of repositories to update the data store and refreshes the grid. In a later step here, we will bind the data store to that grid.

  5. In the init method, define a handler for the “add repository” button that must request the APS controller to create a required repository:

    var addRepo = function() {
       var name_suffix = Math.random().toString(36).substr(2, 10);
       var payload = { "name_suffix" : name_suffix};
       when(xhr(
          // Call the custom addRepos operation
          "/aps/2/resources/" + self.tenant.aps.id + "/repos",
          {
              method: "POST",
              handleAs: "json",
              headers: { 'Content-Type': 'application/json' },
              data: JSON.stringify(payload)
          }),
          // When finished, refresh the list of repos
          function () {
              refreshRepos();
       });
    };
    

    According to the design model, a repository name consists of a prefix reflecting the owner name and an arbitrary suffix. For simplicity, the above code generates a suffix as a random name. Then it calls the custom method createRepo (POST /repos) and refreshes the list of repositories.

  6. In the init method, define a handler for the “remove repository” button that must request the APS controller to remove all selected repositories one by one:

    var removeRepos = function() {
      var btn = this;
      /* Get confirmation from the user for the delete operation */
      if (!confirm("Are you sure you want to delete the selected Repositories?")) {
          btn.cancel();
          return;
      }
    
      var sel = self.repoGrid.get("selectionArray");
      var counter = sel.length;
    
      /* Clear the current messages on the screen */
      self.page.get("messageList").removeAll();
    
      sel.forEach(function(repoId){
          console.log("I'm trying to delete Repo with id = [" + repoId + "]");
          var repo = self.repoCache.get(repoId);
          /* Remove the Repo from the APS controller DB */
          when(xhr(
              // Call the custom removeRepo operation
              "/aps/2/resources/" + self.tenant.aps.id + "/repos/" + repo.name,
              {
                  method: "DELETE"
              }),
              function() {
                  self.repoCache.remove(repoId);
                  console.log("Repo with id = [" + repoId + "] removed");
    
                  /* Remove the processed VPS from the selection */
                  sel.splice(sel.indexOf(repoId),1);
                  self.repoGrid.refresh();
                  if (--counter === 0) { btn.cancel(); }
              },
              /* If failure, call the error handler */
              function(e){
                  displayError(e);
                  if (--counter === 0) { btn.cancel(); }
              }
          );
      });
    
    };
    

    The above code is similar to the respective function in the License Management view. To remove a repository on the application side, the handler calls the custom operation removeRepo (DELETE /repos) defined earlier in the provisioning logic.

  7. Define and return a hierarchy of widgets that must consist of two panels, one with general license data and the other with a grid showing a list of repositories created by the tenant.

    return [
      ["aps/Panel", { title: "Service License" }, [
          ["aps/FieldSet", [
              ["aps/Output", {
                  label: "Licensed Operations",
                  gridSize: "md-4",
                  value: at(self.licenseModel.data, "operations")
              }],
              ["aps/Output", {
                  label: "Limit",
                  gridSize: "md-2",
                  value: at(self.licenseModel.data, "licLimit")
              }],
              ["aps/Output", {
                  label: "Used",
                  gridSize: "md-2",
                  value: at(self.licenseModel.data, "usage")
              }],
              ["aps/Button", {
                  title: "Refresh",
                  gridSize: "md-4",
                  autoBusy: false,
                  onClick: refreshLicense
              }]
          ]]]],
      ["aps/Panel", { title: "Repositories" }, [
          ["aps/Grid", {
              id: this.genId("repo_grid"),
              store: self.repoCache,
              selectionMode: "multiple",
              noDataText: "No resources provisioned",
              noEntriesFoundText: "No services meet your search criteria",
              columns:[{
                      field: "name",
                      name: "Name"
                  },{
                      field: "url",
                      name: "URL",
                      renderCell: function(row, URL) {
                          var link = "<a href=" + URL + " target='_blank'>" + URL + "</a>";
                          return new Output({
                              content: link
                          });
                      }
              }]
              },[
              ["aps/Toolbar", [
                  ["aps/ToolbarButton", {
                      iconClass:"fa-refresh",
                      label: "Refresh",
                      type: "primary",
                      autoBusy: false,
                      onClick: refreshRepos
                  }],
                  ["aps/ToolbarButton", {
                      iconClass:"fa-plus",
                      label: "Add",
                      type: "success",
                      autoBusy: false,
                      onClick: addRepo
                  }],
                  ["aps/ToolbarButton", {
                      iconClass:"fa-trash",
                      label: "Remove",
                      type: "warning",
                      requireItems: true,
                      autoBusy: false,
                      onClick: removeRepos
                  }]
              ]]
    ]]]]];
    
  8. In the onContext method, initiate the actions that must be done after the widgets are loaded and the navigation variable tenant declared in metadata is ready for use:

    self.page = this.byId("apsPageContainer");
    self.tenant = context.vars.tenant;
    self.licenseApsId = self.tenant.license.aps.id;
    xhr.get("/aps/2/resources/" + self.licenseApsId)
    .then(function(license) {
       self.licenseModel.data.set("operations", license.scopes.toString());
       self.licenseModel.data.set("licLimit", self.tenant.licenseCounter.licLimit);
       self.licenseModel.data.set("licUsage", self.tenant.licenseCounter.licUsage);
    
       self.repoGrid = self.byId("repo_grid");
       self.repoGrid.refresh();
       aps.apsc.hideLoading();
    });
    

    First, the above code initiates global variables inside the self structure. Then, it retrieves the license bound to the tenant, updates the grid data store and the grid itself. Finally, the onContext method completes the view loading by calling the traditional aps.apsc.hideLoading method.

Conclusion

This completes the development of the view presenting the landing page for managing customer resources (repositories, in our case). The developed view shows the customer the current resource consumption state and enables the staff to add and remove repositories when required.

The project file you have created must be similar to the sample licenses.html file:

define([
      "dojo/_base/declare",
      "dojox/mvc/getStateful",
      "dojox/mvc/at",
      "dojo/when",
      "aps/View",
      "aps/ResourceStore",
      "aps/xhr",
      "aps/Memory",
      "aps/Output",
      "./displayError"
	],
	function (declare, getStateful, at, when, View, Store, xhr, Memory, Output, displayError) {
      var self;
      return declare(View, {
         init: function() {
            self = this;
            /* Declare the data sources */
            /* Define the data source store */
            self.repoCache = new Memory({
               data: [],
               idProperty: "id"
            });
            /* Initialize a data model to sync with widgets */
            self.licenseModel = getStateful({ "data": {
               operations: "",
               licLimit: 0,
               licUsage: 0
            }});

            var refreshLicense = function() {
               xhr.get(
                  "/aps/2/resources/" + self.tenant.aps.id + "/refreshdata")
                  .then(function() {
                     xhr.get("/aps/2/resources/" + self.tenant.aps.id)
                        .then(function (tenant) {
                           self.licenseModel.data.set("licLimit", tenant.licenseCounter.licLimit);
                           self.licenseModel.data.set("licUsage", tenant.licenseCounter.licUsage);
                        });
                  });
            };

            var refreshRepos = function() {
               when(xhr(
                  // Call the custom getRepos operation
                  "/aps/2/resources/" + self.tenant.aps.id + "/repos",
                  { method: "GET", handleAs: "json" }
                  ),
                  // When finished, refresh the Grid
                  function (repos) {
                     var data = [];
                     repos.forEach(function(repo) {
                        data.push(repo);
                     });
                     self.repoCache.setData(data);
                     self.repoGrid.refresh();
                  });
            };

            var addRepo = function() {
               var name_suffix = Math.random().toString(36).substr(2, 10);
               var payload = { "name_suffix" : name_suffix};
               when(xhr(
                  // Call the custom addRepos operation
                  "/aps/2/resources/" + self.tenant.aps.id + "/repos",
                  {
                     method: "POST",
                     handleAs: "json",
                     headers: { 'Content-Type': 'application/json' },
                     data: JSON.stringify(payload)
                  }),
                  // When finished, refresh the list of repos
                  function () {
                     refreshRepos();
                  });
            };

            var removeRepos = function() {
               var btn = this;
               /* Get confirmation from the user for the delete operation */
               if (!confirm("Are you sure you want to delete the selected Repositories?")) {
                  btn.cancel();
                  return;
               }

               var sel = self.repoGrid.get("selectionArray");
               var counter = sel.length;

               /* Clear the current messages on the screen */
               self.page.get("messageList").removeAll();

               sel.forEach(function(repoId){
                  console.log("I'm trying to delete Repo with id = [" + repoId + "]");
                  var repo = self.repoCache.get(repoId);
                  /* Remove the Repo from the APS controller DB */
                  when(xhr(
                     // Call the custom removeRepo operation
                     "/aps/2/resources/" + self.tenant.aps.id + "/repos/" + repo.name,
                     {
                        method: "DELETE"
                     }),
                     function() {
                        self.repoCache.remove(repoId);
                        console.log("Repo with id = [" + repoId + "] removed");

                        /* Remove the processed VPS from the selection */
                        sel.splice(sel.indexOf(repoId),1);
                        self.repoGrid.refresh();
                        if (--counter === 0) { btn.cancel(); }
                     },
                     /* If failure, call the error handler */
                     function(e){
                        displayError(e);
                        if (--counter === 0) { btn.cancel(); }
                     }
                  );
               });
            };



            /* Define and return widgets */
            return [
               ["aps/Panel", { title: "Service License" }, [
                  ["aps/FieldSet", [
                     ["aps/Output", {
                        label: "Licensed Operations",
                        gridSize: "md-4",
                        value: at(self.licenseModel.data, "operations")
                     }],
                     ["aps/Output", {
                        label: "Limit",
                        gridSize: "md-2",
                        value: at(self.licenseModel.data, "licLimit")
                     }],
                     ["aps/Output", {
                        label: "Used",
                        gridSize: "md-2",
                        value: at(self.licenseModel.data, "licUsage")
                     }],
                     ["aps/Button", {
                        title: "Refresh",
                        gridSize: "md-4",
                        autoBusy: false,
                        onClick: refreshLicense
                     }]
                  ]]]],
               ["aps/Panel", { title: "Repositories" }, [
                  ["aps/Grid", {
                     id: this.genId("repo_grid"),
                     store: self.repoCache,
                     selectionMode: "multiple",
                     noDataText: "No resources provisioned",
                     noEntriesFoundText: "No services meet your search criteria",
                     columns:[{
                        field: "name",
                        name: "Name"
                     },{
                        field: "url",
                        name: "URL",
                        renderCell: function(row, URL) {
                           var link = "<a href=" + URL + " target='_blank'>" + URL + "</a>";
                           return new Output({
                              content: link
                           });
                        }
                     }]
                  },[
                     ["aps/Toolbar", [
                        ["aps/ToolbarButton", {
                           iconClass:"fa-refresh",
                           label: "Refresh",
                           type: "primary",
                           autoBusy: false,
                           onClick: refreshRepos
                        }],
                        ["aps/ToolbarButton", {
                           iconClass:"fa-plus",
                           label: "Add",
                           type: "success",
                           autoBusy: false,
                           onClick: addRepo
                        }],
                        ["aps/ToolbarButton", {
                           iconClass:"fa-trash",
                           label: "Remove",
                           type: "warning",
                           requireItems: true,
                           autoBusy: false,
                           onClick: removeRepos
                        }]
                     ]]
            ]]]]];
         }, // End of Init

         onContext: function(context) {
            self.page = this.byId("apsPageContainer");
            self.tenant = context.vars.tenant;
            self.licenseApsId = self.tenant.license.aps.id;
            xhr.get("/aps/2/resources/" + self.licenseApsId)
            .then(function(license) {
               self.licenseModel.data.set("operations", license.scopes.toString());
               self.licenseModel.data.set("licLimit", self.tenant.licenseCounter.licLimit);
               self.licenseModel.data.set("licUsage", self.tenant.licenseCounter.licUsage);

               self.repoGrid = self.byId("repo_grid");
               self.repoGrid.refresh();
               aps.apsc.hideLoading();
            });
         }  // End of onContext
		
      });   // End of Declare
});         // End of Define