Main Service View¶
This document directs you through the process of creating the landing page for managing repositories in the UX1 panel for customers.
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:
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
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 }});
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 returnedlicenseCounter
structure to update the data model. In a later step here, we will bind the model to proper widgets.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.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.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.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 }] ]] ]]]]];
In the
onContext
method, initiate the actions that must be done after the widgets are loaded and the navigation variabletenant
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, theonContext
method completes the view loading by calling the traditionalaps.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