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 (MVC) architecture guidelines. This means that the code is organized into the following types:
-
Model: Definitions of data fields.
-
View: Any viewable types of components such as grids, trees, and layouts.
-
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.
For 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.
-
For an 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.
-
-
For an 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.
-
-
For a 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.
-
-
For a view:
-
Directory name should be in lower camel case 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 is ignored by the form and grids.
The ExtJS model file contains two important definitions:
-
fields: An array mapping to the keys in the JSON that the API returns.
-
proxy: The definition for the API that 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, Oracle recommends performing manual session validation 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 the 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 you expect to be passed to you 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. These are used mostly when filtering on aggregate or concatenated fields. They are converted into WHERE filters in the SQL. For example, "Status" => "ELT('Disabled', 'Enabled')".
-
<APIFUNCTIONS> are the required 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 may not be required. But generally, if the interface is intended to function 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 the ExtJS documentation for more information about 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 used 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 consists of 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, select Links, and then Links.
See Links in Unified Assurance User's Guide for information about this UI.
-
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>/<CONTROLLER>. 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, select Links, and then Link Groups
See Link Groups in Unified Assurance User's Guide for information about this UI.
-
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.
-
From the Configuration menu, select Links, and then Links.
-
Click the name of the link created earlier in the grid. The custom defined table should load.
Custom Buttons and Listeners
For functionality not covered by Unified Assurance default controllers, such as a new button in a grid that performs a non-CRUD action, use custom buttons and listeners. Define all listeners and handlers in the Controller class and define viewable items in their appropriate View class.
Use the ExtJS search to check strings for dom elements. The search checks criteria against element attributes, types and names, and you can define attributes within square brackets using the following format:
<NAME/TYPE/CLASS/ALIAS>[<ATTRIB>=<VALUE>]
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>];
For 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 Create and Update operations. 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, such as number and numberBoolean.
-
Complex data types such as FQDN and IPv4.
Validation entries are keyed on the API parameter they validate, which means that the key should match the defined class variable names. For example, for the class variable 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 the function template.
For 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'
]
];
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
You cannot use custom permissions for custom UIs. Instead, use existing permissions to restrict access to buttons, features, columns, and fields. Permissions for a user are stored in the session and available to the UI and API using the Assure1.Session.Permissions variable. Permissions are controlled by action flags for each package controller, though not all package controllers support all permissions. You can review the permissions for roles in the Roles interface. From the Configuration menu, select AAA, and then Roles. See Roles in Unified Assurance User's Guide for information about this UI.
The following are the general permission types and access:
-
read: Access to the interface.
-
create: Create items using Add and Clone buttons.
-
update: Update items.
-
delete: Delete items using the Delete button.
-
execute: Run special functions, such as tools and services, using special buttons in the API. This permission type is highly dependent on the individual UI.
ExtJS
The following example restricts 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;
}
...
The following example restricts access to the Add, Clone, and 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 and Release
When you are ready to test and release your custom UIs, create custom packages for them. Packages allow easy installation on any Unified Assurance server and support versioning and maintainability. Create at least two packages for your UIs:
-
A -schemaDB package for SQL schema changes, containing:
-
Inserts into namespace and ExtJS class requires
-
Navigation links or method
-
Other SQL changes
See Schema DB for details.
-
-
A -ui package for MVC files, containing:
-
Controller files
-
Model files
-
View files
-
Other view files, if any
See User Interface for details.
-
You can create other package types if needed based on their content. See Creating Custom Packages for information about all the available package types and how to create them.
Best Practices
-
Oracle recommends using developer tools in your browser to view the JS console and troubleshoot any API errors.
-
Enable logging of XMLHttpRequests to see API requests and responses in the developer's console of your browser.
-
Package custom UIs in a -ui type package.
-
Create navigation links with a -schemaDB type package.
Troubleshooting
Error Type | Symptom or Error Message | Description |
---|---|---|
API response | A PHP warning with text similar to the following:
|
For example, fails to open a form for editing, load a selected record, or enable buttons based on a selection. |
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 could not 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 | The interface only partially loads or a grid or form is stuck in the loading state. The browser's developer 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 | The interface does not navigate. The browser's developer console includes an error similar to the following: Uncaught TypeError: c is not a function |
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. |
Validation Reference
You can use the validations described in this section.
vtypes
Case sensitive vtypes
'vtype' => '<VTYPEFUNCTION>'
<VTYPEFUNCTION> | Description | Example |
---|---|---|
alpha |
This field should only contain letters and underscores (*_). | 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 |
This field should be a 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 |
This field should contain a 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 (|), and so on. | myfile.txt |
daterange |
This field ccepts a date in epoch format. Requires a parameter "StartDate" be present and validates this param's value is less than StartDate. | 1443060000 |
ipv4address |
This field must be a numeric IPv4 address. | 192.0.2.1 |
ipv4addressList |
This field should be a 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 |
This field should be a 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 one of the following valid string metric periods: Hourly, Daily, Weekly, Monthly, or Yearly. | Daily |
fqdn |
This field must be a valid FQDN. | hostname.example.com |
fqdnList |
This field should be a 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 |
Specific vtypes
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, and so on. | Integer | Maximum character length of the string. Commonly used to limit value when inserting into database fields. | 'maxLength' => 255 |
minLength |
Alpha, alphanum, string, and so on. | Integer | Minimum character length of the string. | 'minLength' => 3 |
maxValue |
Number, numberFloat, and so on. | Float | Maximum value of a numeric field. | 'maxValue' => 100 |
minValue |
Number, numberFloat, and so on. | Float | Minimum value of a numeric field. | 'minValue' => 0 |
required
This is the custom server-side validation on whether the parameter is required. It is commonly used when validating interdependent parameters are present, such as requiring certain fields if other fields are set to a value. It should be defined within the same API class that uses it in Validation to maintain access to class parameters. It generally follows the given template:
...
/**
* <DOCS>
*/
public function <VALIDATIONNAME>() {
<VALIDATIONCHECKS>
}
...
where:
-
<DOCS> is the PHP documentation on validation function such as valid data and invalid data.
-
<VALIDATIONNAME> is the function name. It should follow the format validate<PARAMETER>Requirement, where <PARAMETER> is the parameter whose requirement is to be determinined.
-
<VALIDATIONCHECKS> are the custom checks to perform. They should set return value to true if required, and false if not.
It returns bool $required: true if the parameter is required or bool $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
This is the custom server-side validation function. It is commonly used to verify uniqueness and data existence, and perform other checks. It should be defined within the same API class that uses it in Validation to maintain access to class parameters. It generally follows the given template:
...
/**
* <DOCS>
*/
public function <VALIDATIONNAME>() {
//set defaults
$valid = false;
$errorText = '<FAILUREMESSAGE>';
<VALIDATIONCHECKS>
return [$valid, $errorText];
}
...
where:
-
<DOCS> is the PHP documentation on validation function such as valid data, invalid data, and so on.
-
<VALIDATIONNAME> is the function name. It should follow the format validate<PARAMETER>, where <PARAMETER> is the parameter it is validating.
-
<FAILUREMESSAGE> is the message returned if validation returns false.
-
<VALIDATIONCHECKS> are the custom checks to perform. They should set the return value to true if validation is passed, and 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.
For 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];
}
...