Creating Custom Interfaces
Learn how to create custom user interfaces in Oracle Communications Unified Assurance.
You should only consider the following as reasons for creating a custom interface:
-
Custom view that is not an existing interface
-
Custom data view and using the Database Grid Panel does not meet requirements
-
Custom Unified Assurance Tool that requires the look and feel of a Unified Assurance interface and using the Database Grid Panel does not meet requirements
If an existing interface or DDO can be tweaked to achieve the functionality, you can override the existing interface instead. See Overriding a User Interface for more details.
Once implemented, a custom interface can be seamlessly integrated into the Unified Assurance platform using a variety of methods:
-
Using links. See Links in Unified Assurance User's Guide for more details.
-
Using dashboards. See Dashboards in Unified Assurance User's Guide for more details.
-
Using tools with the internal path types in diagrams, events, graphs, and Vision. See the following topics in Unified Assurance User's Guide:
-
Using the Knowledgebase wiki link
About SDK Package Architecture
Unified Assurance UIs follow the model-view-controller architecture guidelines. The code is organized into the following types:
-
Model: Definitions of data fields.
-
View: Any viewable types of components such as grids, trees, layouts, etc.
-
Controller: Known as the brains of the interface, this brings all the interface classes together, initiating rendering, instantiating models, and other application logic functions such as handlers for listeners.
Generally, each custom package should have the following:
-
A directory with the package name under $A1BaseDir/www/packages/custom which contains the model, view, and controller Javascript files in three sub-directories
-
A PHP API file in the $A1BaseDir/www/packages/custom/api for loading data into the custom UI via the model, if needed
Sometimes, there will be common core functionalities used in the controller that can be used across multiple custom packages. These can be placed in a Javascript file in the $A1BaseDir/www/packages/custom/mixin directory.
The folder and file structure of a custom UI package should be similar to the following:
-
api
- <API>.php
-
component A
-
controller
- <CONTROLLER>.js
-
model
- <MODEL>.js
-
view
-
Grid.js or Tree.js
-
Form.js
-
View.js
-
-
-
component B
-
controller
- <CONTROLLER>.js
-
model
- <MODEL>.js
-
view
-
Grid.js or Tree.js
-
Form.js
-
View.js
-
-
-
mixin
- Mixin.js
where component A and component B are views or pages in your custom interface.
Example:
-
api
- CustomTables.php
-
customTable
-
controller
- CustomTableGridController.js
-
model
- CustomTableModel.js
-
view
-
CustomTableGrid.js
-
CustomTableView.js
-
-
-
mixin
- CustomMixin.js
Naming Conventions
Note:
Avoid using numbers in all names as it may cause errors.
-
Project folders and containing package folders should be in lower camel case. For example, customUIs/requests/crud/view.
-
Controller, model, and view directory names should be object-oriented and reflect what the interface is, operating on, or using. For example, if the interface is used to manage custom tables, you can set the names of the directories as CustomTables.
-
ExtJS Model
-
Filenames should be in upper camel case and singular. For example, CustomTableModel.js.
-
Classnames should be in upper camel case and singular. They should follow the format <PATH>.model.<MODEL>, where <PATH> is the Unified Assurance internal path until the model folder and <MODEL> is the filename without extension of the ExtJS model. For example, if the path is custom/customTable/crud and the filename is CustomTable.js, the classname should be Custom.customTable.crud.model.CustomTableModel.
-
-
API
-
Filenames should be in upper camel case and plural. For example, CustomTables.php.
-
Classnames should be in upper camel case and plural. PHP Class must match the filename without extension. For example, for custom/src/api/CustomTables.php, the classname would be CustomTables.
-
-
Controller
-
Filenames should be in upper camel case and plural. For example, CustomTables.js.
-
Classnames should be in upper camel case and plural. They should follow the format: <PATH>.controller.<CONTROLLER>, where <PATH> is the Unified Assurance internal path until the controller folder and <CONTROLLER> is the filename without extension of the controller. For example, for custom/src/customTable/crud/controller/CustomTableGridController.js, the classname should be Custom.customTable.crud.controller.CustomTableGridController.
-
-
View
-
Directory name should be in lower camel and singular. For example, request.
-
Filenames should be in upper camel case and should be Grid.js for table grids, Tree.js for hierarchical grids, and Form.js for forms. For multiple forms or grids, use prefixes.
-
Classnames should be in upper camel case and must match the filename without extension. They should follow the format: <PATH>.view.<TYPE>, where <PATH> is the Unified Assurance internal path until the view folder and <TYPE> is the type of view. For example, for custom/src/customTable/crud/view/CustomTablesGrid.js, the classname should be Custom.customTable.crud.view.CustomTableGrid.
-
About the Model
The model consists of an ExtJS store model, which should reside in the model directory.
The ExtJS model defines where to request data and provides a list of fields that you expect to receive from the API as part of its response. If the exact field is not listed, incoming data from the API for unlisted fields are ignored by the form and grids.
The ExtJS model file contains two important definitions:
-
fields: An array mapping to the keys in the JSON the API returns.
-
proxy: The definition for the API the model will request data from.
ExtJS Model
Ext.define('<CLASSNAME>', {
extend: 'Ext.data.Model',
idProperty: '<IDFIELD>',
fields: [
<FIELD>,
...
<FIELD>
],
proxy: {
type: 'rest',
url: '/api/<PACKAGE>/<API>',
reader: {
type: 'json',
root: 'data'
}
}
});
where:
-
<CLASSNAME> is the classname according to convention for ExtJS models. See Naming Conventions for more details.
-
<IDFIELD> is the field from the list of fields properties that should be considered unique and used as the id.
-
<FIELD> is the field you expect to receive from the API. For example, UserName.
-
<PACKAGE> is the folder name in "www/ui/".
-
<API> is the API classname in lower camel case. For example, users.
For example:
Ext.define('Custom.customTable.crud.model.CustomTableModel', {
extend: 'CoreWebLib.baseCrud.crud.model.Model',
idProperty: null,
fields: [
'DeviceName',
{
name: 'GeoLocation',
convert: function(data) {
if (data === undefined || data === null) {
return '{"type": "Point", "coordinates": [0,0]}'
} else if (typeof(data) === 'string') {
return data
}
else {
return Ext.encode(data)
}
}
}
],
proxy: {
type: 'rest',
url: '/api/custom/CustomTables',
reader: {
type: 'json',
rootProperty: 'data'
}
}
});
API
The API folder contains a PHP file that defines the API. This is the API that the model calls for data.
You can use custom APIs to subclass an existing Unified Assurance API, similar to Overriding a User Interface, or create a REST API class from scratch. If you are creating from scratch, your custom API should comply with REST guidelines like Unified Assurance APIs do, and it should extend the base API class Model to ensure access to validation libraries, request and response objects, and so on.
Note:
- Custom APIs do not have Unified Assurance permissions, so you must define a local variable $skipSessionValidation in order to suppress the Unified Assurance permission validation for the custom API:
public $skipSessionValidation = true;
- Because custom UIs skip the session validation, manual session validation is recommended using $this->session->validate().
APIs follow the following structure:
<?php
special parent classes
<PARENTREQUIRES>
...
class <CLASSNAME> extends <PARENTCLASS> {
//disable permission checking on this class
public $skipSessionValidation = true;
/**
* ID specified for individual CRUD operations
*
* @var int
*/
public $id;
<REQUESTPARAMS>
...
public $validations = [
<VALIDATIONS>
];
protected $filterConversions = [
<FILTERS>
];
<APIFUNCTIONS>
...
<VALIDATIONFUNCTIONS>
...
}
?>
where:
-
<PARENTREQUIRES> is the path to parent class if the parent class is not Model and in the same package. For example, require_once(dirname(FILE) . '/../../metric/api/Metrics.php');
-
<CLASSNAME> is the classname according to convention for APIs. See Naming Conventions for more details.
-
<PARENTCLASS> is either Model or the classname of a Unified Assurance API. If the class name is not Model and it is in another package, a <PARENTREQUIRES> value is mandatory.
-
<REQUESTPARAMS> are the definitions of any class variables that we expect to be passed to us from the requester or that will be used across function calls. All class variables are accessible via the global $this object from any function. For example, public $MyParam -> $this->MyParam.
-
<VALIDATIONS> is the associative array of client-side and server-side validation definitions to be applied for API parameters. See Validation for more information on available client and server side validations.
-
<FILTERS> is the associative array of parameter to special SQL conversions. They are used mostly when filtering on aggregate or concatted fields. These are converted into WHERE filters in the SQL. For example, "Status" => "ELT('Disabled', 'Enabled')".
-
<APIFUNCTIONS> are the API interface functions such as read(), create(), update(), delete(), and execute().
-
<VALIDATIONFUNCTIONS> are the optional server-side validation functions. See Validation for more information on available server side validation.
For example:
<?php
class CustomTable extends Model
{
public $skipSessionValidation = true;
protected $filterConversions = [
'TimestampAdded' => 'DATE(FROM_UNIXTIME(TimestampAdded))',
];
public function read()
{
$this->response->total = 0;
// Retrieve single device
if (isset($this->id) && $this->isIdNumeric($this->id)) {
$this->filter[] = (object)[
"property" => "DeviceID",
"value" => $this->id,
"operator" => "eq"
];
}
$exampleSQL = "
SELECT DeviceName,
ST_AsGeoJson(GeoLocation) AS GeoLocation,
FROM_UNIXTIME(D.TimestampAdded) AS TimestampAdded
FROM Devices
";
$exampleSQL = $this->addQueryFilter($exampleSQL);
$exampleSQL = $this->addQuerySort($exampleSQL);
$exampleSQL = $this->addQueryLimit($exampleSQL);
$exampleStatement = $this->DBH->prepare($exampleSQL);
if ($exampleStatement->execute()) {
$rows = $exampleStatement->fetchAll(PDO::FETCH_ASSOC);
$rowCount = count($rows);
$this->response->success = true;
$this->response->message = tr('Loaded %d entries', $rowCount);
$this->response->data = $rows;
$this->response->total = $this->foundRows();
} else {
$this->response->success = false;
$this->response->message = tr('Failed to load data');
}
$exampleStatement = null;
}
}
About the View
The view portion of the interface usually consists of a container index which holds a grid and a form piece. Depending on the use of the interface, the grid and form are optional. But generally, if intended as a CRUD (Create, Read, Update, Delete) interface, it should implement the index, grid, and form parts.
Grid.js
The grid is the main part of an interface. It can display data in a tabular format. See ExtJS documentation on "grid" xtypes for more information on the options used in these examples.
Some important sections of the grid ExtJS file are:
-
store: Tells the grid to use the model and adds some configurations.
-
toolbarItems: Defines the buttons in the toolbar and what functions to execute when the buttons are clicked.
-
columns: Defines the columns in the grid. The dataIndex must match with fields in the model.
The tabular grid displays summary data columns row by row and supports filtering and sorting. Tabular grids follow the following structure:
Ext.define('<CLASSNAME>', {
extend: 'CoreWebLib.baseCrud.crud.view.Grid',
alias 'widget.<ALIAS>',
title '<TITLE>',
iconCls: 'fa fa-table',
stateful: true,
stateId: '<STATEID>',
requires: [
<REQUIRES>
],
store: {
model: '<MODEL>',
pageSize: 100,
remoteFilter: true,
remoteSort: true,
sorters: [
<SORTERS>
...
]
},
columns = [
<COLUMNDEF>
...
],
toolbarItems: [
<GRIDBUTTONS>
]
});
where:
-
<CLASSNAME> is the classname according to convention for ExtJS Grids. See Naming Conventions for more details.
-
<ALIAS> is the unique shortname used as an xtype for this class when instantiating. It should be in lowercase and follow the format <CONTROLLERBASENAME>grid (For example, devicegrid). See About the Controller for more details about <CONTROLLERBASENAME>.
-
<TITLE> is the text displayed in the grid's titlebar.
-
<REQUIRES> are the classnames of additional custom or Unified Assurance xtype class libraries used by the grid. Basic tools provided by ExtJS like toolbar and filterbar are available by default and do not need a requires entry.
-
<MODEL> is the classname of the model definition. See About the Model for more details.
-
<SORTERS> is the initial sort for items in the grid sent to the API.
-
<COLUMNDEF> are the column definitions based on summary data returned by the API (read() no id). The dataIndex must match a field definition in the associated <MODEL>. See ExtJS grid column documentation for the available configurations.
-
<STATEID> is the globally unique name used to store the sorted state and modified width of the columns within the interface. It follows the format <PACKAGE>-<CONTROLLERBASENAME>-grid. For example, customUI-userrequest-grid.
-
<GRIDBUTTONS> are the buttons for the grid toolbar. These are commonly used for the standard CRUD actions of create, clone, and delete.
For example:
Ext.define('Custom.customTable.crud.view.CustomTableGrid', {
extend: 'CoreWebLib.baseCrud.crud.view.Grid',
alias: 'widget.customTable.grid',
controller: 'Custom.customTable.crud.controller.CustomTableGridController',
requires: [
'Custom.customTable.crud.model.CustomTableModel',
'Custom.customTable.crud.controller.CustomTableGridController'
],
title: tr('Example Table'),
iconCls: 'fa fa-table',
stateful: true,
stateId: 'custom-customTable-grid',
packageName: 'custom',
viewName: 'Example',
store: {
model: 'Custom.customTable.crud.model.CustomTableModel',
pageSize: 100,
remoteFilter: true,
remoteSort: true,
sorters: [
{
property : 'DeviceName',
direction: 'ASC'
}
]
},
toolbarItems: [
{
iconCls: 'fa fa-line-chart',
text: tr('Show Message'),
handler: 'onShowClicked'
},
{
xtype: 'relativetime',
name: 'TimeRange',
fieldLabel: tr('Time Range'),
queryMode: 'local',
forceSelection: true,
triggerAction: 'all',
value: 'now-24h..now',
width: 150
}
],
columns: [
{
header: tr('Name'),
dataIndex: 'DeviceName',
width: 500,
filter: 'string'
},
{
header: tr('GeoLocation'),
dataIndex: 'GeoLocation',
filter: 'string',
width: 800,
}
]
});
View.js
The view is the main page of a custom UI and is a container for the grid and forms components, if present. Unified Assurance views use the border layout for arrangement. The view can be entirely customized in what it displays but should contain at least one item. See ExtJS documentation for more information on the Ext.container.Container class.
Ext.define('<CLASSNAME>', {
extend: 'Ext.container.Container',
alias: 'widget.<ALIAS>',
title: '<TABTITLE>',
requires: [
'<REQUIRES>'
],
gridClass: '<GRIDCLASSS>',
formClass: '<FORMCLASSS>',
breadcrumbs: [{
text: '<TABTITLE>',
href: '#'
}]
});
where:
-
<CLASSNAME> is the classname according to convention for views. See Naming Conventions for more details.
-
<ALIAS> is the unique shortname for use as an xtype for this class when instantiating. It should be in lowercase and follow the format <CONTROLLERBASENAME>index (For example, deviceindex). See About the Controller for more details about <BASENAME>.
-
<TABTITLE> is the text for the title of the Unified Assurance tab.
-
<REQUIRES> are the classnames of the additional custom or Unified Assurance xtype class libraries used by the view.
-
<GRIDCLASS> is the classname of the specific library from <REQUIRES> that should be used as the grid.
-
<FORMCLASS> is the classname of the specific library from <REQUIRES> that should be used as the form. If there is only one item in <REQUIRES>, this can be set to false.
For example:
Ext.define('Custom.customTable.crud.view.CustomTableView', {
extend: 'CoreWebLib.baseCrud.crud.view.CustomView',
alias: 'widget.customTableview',
requires: [
'Custom.customTable.crud.view.CustomTableGrid'
],
gridClass: 'Custom.customTable.crud.view.CustomTableGrid',
formClass: false,
title: tr('Custom Table'),
breadcrumbs: [{
text: tr('Custom Table'),
href: '#'
}]
});
About the Controller
The controller is the centerpiece of an interface. It houses the listener callbacks, sends the API requests, and initializes the view pieces that make up the interface and its functionality. Unified Assurance provides a few default controllers with common functionality built in for specific types of controller. See Unified Assurance Controllers for available classes and functionality from which to extend your controller.
The controller follows the following structure:
Ext.define('<CLASSNAME>', {
extend: '<CONTROLLERLIB>',
});
where:
-
<CLASSNAME> is the classname according to convention for controllers. See Naming Conventions for more details.
-
<CONTROLLERLIB> is the optional Unified Assurance Controller base class to inherit any default functionality.
For example:
Ext.define('Custom.customTable.crud.controller.CustomTableGridController', {
extend: 'CoreWebLib.baseCrud.crud.controller.GridController',
alias: 'controller.Custom.customTable.crud.controller.CustomTableGridController',
mixins: [
'Custom.mixin.CustomMixin'
],
listen: {
component: {
'relativetime': {
change: 'onTimeRangeChange'
}
}
},
onShowClicked: function (button, event) {
var selection = button.up('panel[itemId=grid]').getSelection(),
timeRange = button.up('panel[itemId=grid]').down('relativetime');
console.log(timeRange.getValue());
if (selection[0]) {
var selectedDeviceNames = '';
selection.forEach(function(element, index, array){
if (index === 0) {
selectedDeviceNames += element.get('DeviceName');
} else if (index < selection.length) {
selectedDeviceNames += ', ' + element.get('DeviceName');
} else {
selectedDeviceNames += element.get('DeviceName');
}
})
this.showToast('Selected device(s): ' + selectedDeviceNames)
} else {
this.showToast('No device(s) selected');
}
},
onTimeRangeChange: function (timerange) {
console.log("Time Range Changed");
console.log(timerange.getValue());
console.trace();
}
});
Navigation
With the new custom interface built, you may need to access it through the Links menu. You can add the link using the UI and insert the data using a SQL request.
To add the custom interface to the Links menu and insert the data:
-
Place your files on the Unified Assurance presentation server under $A1BASEDIR/www/packages.
-
Run an SQL request to add relevant data. The structure of the SQL request should be:
INSERT INTO UIExtraPaths (Namespace, Path) values ('<CLASS PREFIX>', 'ui/<PACKAGE>/<SUBFOLDER>'); INSERT INTO UIExtraRequires (Class) values ('<FULL CLASS>');
where:
-
<PACKAGE> is the new UI package without type suffix.
-
<SUBFOLDER> is the optional subfolder if separating projects within the package.
-
<CLASS PREFIX> is the ExtJS class prefix that is the package name in upper camel case and any subfolders before the MVC folders.
-
<FULL CLASS> is the full ExtJS class name based on the directory path. An entry must be made for each ExtJS class created for the project.
For example:
INSERT INTO UIExtraPaths (Namespace, Path) values ('Custom', 'packages/custom'); INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.view.CustomTableView'); INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.view.CustomTableGrid'); INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.controller.CustomTableGridController'); INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.model.CustomTableModel'); INSERT INTO UIExtraRequires (Class) values ('Custom.mixin.CustomMixin');
-
-
From the Configuration menu, go to Links, then click Links to go to the Links interface, and click Add.
-
Enter the following information:
-
Name: <NAME>, where <NAME> is the name of the project. For example, CustomTable.
-
Type: Internal
-
Path: <PATH>, where <PATH> is the Unified Assurance internal path to the interface's controller, following the format <PACKAGE>/
. For example, custom/src/customTable/crud/controller/CustomTableGridController. -
Icon: <ICON>, where <ICON> is your choice of icon.
-
User Owner: Set to Public to All Users In Group or restrict to a specific user.
-
Group Owner: Set to Public to All Groups or restrict to a specific group.
-
Viewers: Optionally, select a group to grant read-only access to.
-
-
Click Submit.
-
Verify that the link was created.
-
From the Configuration menu, go to Links, then click on Link Groups to go to the Link Groups interface.
-
Select the Root group in the grid.
-
Switch the multiselector from Selected to Available.
-
Double-click the name of the link created earlier.
-
Click Submit.
-
Go back to the Links UI (as in step 3).
-
Click the name of the link created earlier in the grid. The custom defined table should load.
Custom Buttons and Listeners
There are several cases where your custom UI may require extra functionality not covered by Unified Assurance default controllers, such as a new button in a grid that performs a non-CRUD action. Regardless of what type of functionality is required for the custom UI, all listeners and handlers should be defined in the Controller class and any viewable items should be in the appropriate View class. ExtJS provides a unique searching system to find the correct dom elements within strings. It will check dom elements against certain criteria before returning it. Criteria are checked against element attributes, types and names, but attributes can be specifically defined within square brackets using the format:
<NAME/TYPE/CLASS/ALIAS>[<ATTRIB>=
For example, to search for the Submit button in the Requests form, define button[action=submit], or to find the grid, define <BASENAME>grid.
Starting with the Requests example as its base, the following example adds a button to the Grid's renderToolbar() function to open an email with the details of the selected request:
Grid
...
renderToolbar: function() {
return {
xtype: 'toolbar',
items: [
{
iconCls: 'icon-add',
text: 'Add',
action: 'add'
},
{
iconCls: 'icon-clone',
text: 'Clone',
action: 'clone',
disabled: true
},
{
iconCls: 'icon-delete',
text: 'Delete',
action: 'delete',
disabled: true
},
{
iconCls: 'icon-mail',
text: 'Email',
action: 'email',
disabled: true
}
]
};
}
...
Controller
...
init: function(viewID, action) {
//Add listener for new email button
this.listeners[this.baseName + 'grid button[action=email]'] = {
click: this.onEmailRequest
};
this.callParent(arguments);
},
...
/*
* override parent CoreWebLib.baseCrud.crud.controller.GridController onSelectRow() so the new 'email' button is enabled
* when a request is selected. ExtJS dom navigation possible through up() and down() to the
* elements of our view. Anything the parent doesn't know about will need to be handled special.
*/
onSelectRow: function(gridView, record) {
var grid = gridView.up(this.baseName + 'grid'),
selections = grid.getSelectionModel().getSelection(),
emailButton = grid.down('button[action=email]');
//disable button unless there is only one item selected
if (emailButton) {
emailButton.setDisabled(selections.length != 1);
}
//let parent do the rest
this.callParent(arguments);
},
...
//handler function
onEmailRequest: function(button) {
var grid = button.up(this.baseName + 'grid');
//get the selection for values
var request = grid.getSelectionModel().getSelection()[0];
//use a hidden iframe to setup the 'mailto:', the browser will take care of the rest
var exportFrame = Ext.get('export-frame').dom;
//build the email with the selection
var url = 'mailto:?' + 'subject=' + request.RequestName +
'&' + 'body=' + request.RequestDetails;
exportFrame.src = url;
},
...
Similarly, listeners can be removed by deleting the listeners entry in the init function of the controller. This is commonly used when an interface does not implement a default CRUD component such as a button.
delete this.listeners[<SEARCH>];
Example:
...
init: function(viewID, action) {
//remove listener for the add grid button
delete this.listeners[this.baseName + 'grid button[action=add]'];
this.callParent(arguments);
},
...
Validation
Unified Assurance supports both client-side and server-side validation for creates and updates. Supported client-side validations are defined in the API model's $validations array and applied directly to the form. Server-side validation is implemented with custom functions. This allows the custom UI to have one set of validations that will apply regardless of whether it is called from the form or from another API.
The types of client-side validation supported by Unified Assurance API are:
-
simple data types (number, numberBoolean, etc.)
-
complex data types (FQDN, IPv4, etc.)
Validation entries are keyed on the API parameter they validate, that is, the key should match the defined class variable names. For example, for public $DeviceName;, the key would be DeviceName.
public $validations = [
'<PARAM>' => [
'required' => '<REQUIRED>',
'vtype' => '<VTYPE>',
<VTYPESPECIFIC>
...
'dbValidation' => '<DBVALIDATION>'
]
...
];
where:
-
<PARAM> is the API parameter to validate. It should match a documented class variable.
-
<REQUIRED> are the defined parameters that are assumed to be required. The required validation allows for specifying when it should not be required. It takes a function name, and the output of that function is whether it is required or not. (See required for the function template). It can also be notRequired for when it is never required. A parameter that is not required will still run any vtype or vtype-specific validations if passed into the API.
-
<VTYPE> is the built-in data type validation. See Data Type, Integrity and Format for the list of available vtypes.
-
<VTYPESPECIFIC> are the vtype-specific validations. See Data Type, Integrity and Format for vtype-specific validations.
-
<DBVALIDATION> is the custom validation function name to perform custom validation on a parameter. See dbValidation for function template.
Example:
public $validations = [
'RequestName' => [
'vtype' => 'alphanum',
'allowBlank' => false,
'dbValidation' => 'validateRequestName'
],
'RequestType' => [
'vtype' => 'number',
'allowBlank' => false,
'minValue' => 1,
'maxValue' => 3
],
'RequestEmail' => [
'vtype' => 'email',
'allowBlank' => false
],
RequestDetails => [
'required' => 'notRequired'
]
];
Note:
-
All validation types are individually optional for a parameter but, if empty, it will still assume it is at least required.
-
All parameters that require validation should be defined in the $validations array.
-
Even if no parameter validation will be specified, the $validations array variable must be defined.
Permission and Multitenant Access Checks
While a Custom UI cannot support its own set of custom permissions at this time, it can certainly use existing permissions to restrict access. Permissions for a user are stored in the session and available to the UI and API accordingly using "Assure1.Session.Permissions" variable; permissions are stored by package -> controller -> action flags. Access can be restricted on buttons, features, or even columns and fields based on requirements. Note that not all package controllers support all permissions. Review the specific role permissions in the Roles interface.
General permission types and access:
-
read: Access to the interface.
-
create: Access to create items, "Add" and "Clone" buttons.
-
update: Access to update items.
-
delete: Access to delete items, "Delete" button.
-
execute: Access to special execute functionality such as running tools, running services, etc. in the API, special buttons. Highly dependent on UI.
ExtJS
Example snippet to restrict submit action when rendering buttons:
...
renderButtons: function() {
var buttons = [
{
xtype: 'tberror',
flex: 1
}
];
var permission = Assure1.Session.Permissions.device;
if (permission && permission.Devices && (permission.Devices.create || permission.Devices.update)) {
buttons.push(
{
text: 'Reset',
iconCls: 'icon-reset',
action: 'reset'
},
{
text: 'Submit',
iconCls: 'icon-submit',
action: 'submit',
formBind: true,
disabled: true
}
);
}
return buttons;
}
...
Example snippet to restrict access to the add, clone, delete buttons:
...
renderToolbar: function() {
var permission = Assure1.Session.Permissions.device;
if (permission && (permission.Devices && (permission.Devices.create || permission.Devices.delete))) {
return {
xtype: 'toolbar',
items: [
{
iconCls: 'icon-add',
text: 'Add',
action: 'add',
hidden: permission.Devices.create ? false : true
},
{
iconCls: 'icon-clone',
text: 'Clone',
action: 'clone',
disabled: true,
hidden: permission.Devices.create ? false : true
},
{
iconCls: 'icon-delete',
text: 'Delete',
action: 'delete',
disabled: true,
hidden: permission.Devices.delete ? false : true
}
]
};
}
else {
return null;
}
}
...
API
Permissions are NOT checked by default in custom UIs and access checks should be similarly validated using the appropriate Permission and Multitenant Access Checks built-in functions.
Packaging for Testing & Release
Custom UIs should be packaged as the next step in testing and release. Packaging your custom UI not only allows for easy installation on any Unified Assurance servers, but it also provides versioning and maintainability. Custom UIs should consist of at least two packages for maintainability; one of SQL schema changes ('-schemaDB') and one of MVC files ('-ui'). Other package types should be created as necessary based on content. See Creating Custom Packages for all the package types and how to create.
Schema DB should contain:
-
Inserts into namespace and ExtJS class requires
-
Navigation links or method
-
Other SQL changes
UI package should contain:
-
Controller file(s)
-
Model file(s)
-
View file(s)
-
Other view file(s) if any
Best Practices
-
Use of developer tools in your browser is highly recommended to view the JS console and troubleshoot any API errors.
-
Enable logging of "XMLHttpRequests" to see API request and responses in the developer's console of your browser.
-
Custom UIs should be packaged as a "-ui" type package.
-
Navigation link creation can be created with a "-schemaDB" type package.
Troubleshooting
Type | Symptom/Error | Description |
---|---|---|
API response | PHP Warning
|
Opens Form for edit loading the selected record, enabling buttons based on selection, etc. |
Custom UI during navigation to interface | Does not navigate. Browser developer's console has an error "Uncaught TypeError: c is not a function" | The UI's controller couldn't be found. Check that the custom controller path was added to the namespace in "UIExtraPaths" and classes added to "UIExtraRequires" tables during Navigation setup. |
Custom UI during navigation to interface | Partial load of interface, grid or form stay in "loading" state for a while, etc. Browser developer's console has a syntax error in one of the component files. | Syntax error in one or more ExtJS files, review code. |
Custom UI during navigation to interface | Does not navigate, partial load of interface, grid or form stay in "loading" state for a while, etc. Browser developer's console has an XHR error "GET XXXXX 404 (Not Found)". | Could not find one or more ExtJS components. Check classnames, path, package path was added to the namespace in "UIExtraPaths" and classes added to "UIExtraRequires" tables during Navigation setup. |
Appendix
Unified Assurance Validations
Data Type, Integrity and Format
VTypes
Case sensitive vtypes
'vtype' => '<VTYPEFUNCTION>'
<VTYPEFUNCTION> | Description | Example |
---|---|---|
alpha |
This field should only contain letters and _ | apple |
alphanum |
This field should only contain letters, numbers and _ | apple123 |
email |
This field should be an e-mail address in the format "user@example.com" | apple@example.com |
emailList |
comma separated list of emails | apple@example.com, orange@example.com |
url |
This field should be a URL in the format "http://www.example.com" | http://www.example.com |
cron |
This field should only contain a number (or *) and optionally a range, list, or interval | /10 3 * * |
number |
This field should only contain positive or negative integers | 10 |
numberList |
comma separated list of positive and negative integers | 10, 5, -1 |
numberFloat |
This field should only contain numbers and an optional decimal point | 10.5 |
numberBoolean |
This field should only contain the numbers 0 or 1 | 1 |
filename |
This field cannot contain any common file system special characters such as colon (:), pipe (\ | ), etc. myfile.txt |
daterange |
Accepts a date in epoch format. Requires a parameter "StartDate" be present and validates this param's value is less than StartDate's | 1443060000 |
ipv4address |
This field must be a numeric IPv4 address | 192.0.2.1 |
ipv4addressList |
comma separated list of IPv4 addresses | 192.0.2.1, 192.0.2.1, 192.0.2.3 |
ipv4addressRange |
This field must be an IPv4 address range in glob format | 192.0-10.2.* |
ipv4CIDRAddress |
This field must be a valid IPv4 address range in CIDR format | 192.0.2.0/8 |
ipv6address |
This field must be a numeric IPv6 address | 2001:DB8::64 |
ipv6addressList |
comma separated list of IPv6 addresses | 2001:DB8::64, 2001:DB8::C8 |
ipv6addressRange |
This field must be an IPv6 address range in glob format | 2001:DB8::100-103 |
ipv6CIDRAddress |
This field must be a valid IPv6 address range in CIDR format | 2001:DB8::/32 |
ipAddress |
This field must satisfy either ipv4address or ipv6address | 192.0.2.1 |
ipAddressRange |
This field must satisfy either ipv4addressRange or ipv6addressRange | 192.0-10.2.* |
ipAddressList |
This field must satisfy either ipv4addressList or ipv6addressList | 192.0.2.1, 192.0.2.1, 192.0.2.3 |
ipCIDRAddress |
This field must satisfy either ipv4CIDRAddress or ipv6CIDRAddress | 192.0.2.0/8 |
metricPeriod |
This field must be a valid string metric period: Hourly, Daily, Weekly, Monthly, Yearly | Daily |
fqdn |
This field must be a valid FQDN | hostname.example.com |
fqdnList |
comma separated list of FQDNs | hostname.example.com, hostname2.example.com |
macAddress |
This field must be a valid MAC Address | 11:22:33:AA:BB:CC |
VType Specific
Case-sensitive validations based on specific vtypes
'<SPECIFIC>' => <VALUE>
<SPECIFIC> | <VTYPE> | <VALUE> Data | Description | Example |
---|---|---|---|---|
allowBlank |
Any | Boolean (true/false) | Indicates whether parameter can be blank or NULL. | 'allowBlank' => true |
maxLength |
Alpha, alphanum, string etc. | Integer | Maximum character length of the string. Commonly used to limit value when inserting into database fields. | 'maxLength' => 255 |
minLength |
Alpha, alphanum, string etc. | Integer | Minimum character length of the string. | 'minLength' => 3 |
maxValue |
Number, numberFloat, etc. | Float | Maximum value of a numeric field. | 'maxValue' => 100 |
minValue |
Number, numberFloat, etc. | Float | Minimum value of a numeric field. | 'minValue' => 0 |
required
Custom server-side validation on whether the parameter is required. Commonly used when validating interdependent params are present such as requiring certain fields if other fields are set to a value. Should be defined within the same API class that uses it in Validation to maintain access to class parameters. Generally follows the given template:
...
/**
* <DOCS>
*/
public function <VALIDATIONNAME>() {
<VALIDATIONCHECKS>
}
...
-
<DOCS>: PHP documentation on validation function such as valid data, invalid data, etc.
-
<VALIDATIONNAME>: Function name. Should follow convention: validate<PARAMETER>Requirement where <PARAMETER> is the parameter it is determining if it should be required.
-
<VALIDATIONCHECKS>: Custom checks to perform. Should set return value to true if required, false if not.
It returns:
- bool $required: true if required, false if not
The synopsis of required is:
...
/**
* Require email if of type Complaint
*
* @ignore
* @uses RequestType
* @return bool
*/
public function validateRequestEmailRequirement() {
if ($this->RequestType == 2) {
return true;
}
else {
return false;
}
}
...
dbValidation
Custom server-side validation function. Commonly used to verify uniqueness, data existence, or other checks. Should be defined within the same API class that uses it in Validation to maintain access to class parameters. Generally follows the given template:
...
/**
* <DOCS>
*/
public function <VALIDATIONNAME>() {
//set defaults
$valid = false;
$errorText = '<FAILUREMESSAGE>';
<VALIDATIONCHECKS>
return [$valid, $errorText];
}
...
-
<DOCS>: PHP documentation on validation function such as valid data, invalid data, etc.
-
<VALIDATIONNAME>: Function name. Should follow convention: validate<PARAMETER> where <PARAMETER> is the parameter it is validating.
-
<FAILUREMESSAGE>: Message returned to user if validation returns false.
-
<VALIDATIONCHECKS>: Custom checks to perform. Should set the return value to true if passes, false if not.
It must return array tuple:
- bool $valid : whether this validation passed. Boolean "false" triggers a validation error
- string $errorText : validation message to return if validation did not pass.
Example:
return [$valid, $errorText];
The synopsis of dbValidation is:
...
/**
* Validate Request Name
*
* @ignore
* @uses $id
* @uses $RequestName
* @return array Validation and error text
*/
public function validateRequestName() {
$valid = false;
$errorText = 'This RequestName already exists';
$queryParams = [$this->RequestName];
$RequestSQL = '
SELECT COUNT(*)
FROM UserRequests
WHERE RequestName = ?
';
updates
if (isset($this->id)) {
$RequestSQL .= '
AND RequestID != ?
';
$queryParams[] = $this->id;
}
$RequestStatement = $this->DBH->prepare($RequestSQL);
if ($RequestStatement->execute($queryParams)) {
$count = $RequestStatement->fetchColumn();
if ($count == 0) {
$valid = true;
}
}
$RequestStatement = null;
return [$valid, $errorText];
}
...