Subsections of Advanced

BIM/IFC Widget

The BIM/IFC widget allows you to configure a 3D model under the BIM/IFC standard and add sensors to the desired elements of the model.

How it Works

BIM/IFC Widget

The widget displays the selected model, covering the widget area. To the left of the widget is a panel with available actions, and to the right are listed the selected and monitored elements.

Actions Panel

BIM/IFC Actions Panel

With the Actions Panel, you can:

  • All elements displays a list of all elements in the model, allowing you to search and select an element quickly
BIM/IFC All Elements
  • Show/Hide list allows you to show/hide the right panel of elements
  • Highlight elements will select all preconfigured elements from the right panel in the model
  • Capture an image downloads a snapshot of only the model in its current state
  • Create clipping plane enables the option to select an element in the model to create a cutting layer that allows you to cut the model and see elements inside
  • Remove clipping plane removes the selected cutting layer
  • Enable/disable clipping planes activates/deactivates the cutting layers without needing to remove and add
  • Clear cache and reload clears the cache and reloads the image to return it to its initial state

Elements Panel

BIM/IFC Elements Panel

With the Elements Panel, you can select an element to highlight it in the model for more precise location.

States are also represented here as established by the configured code.

Configuration

General

  • Boxed: widget will be displayed with background in dahsboard.
  • About: widget description in Markdown format.
  • Title: widget title. It can be configured to remain fixed in the widget or only be displayed when it receives focus.
  • Toolbar: configures the behavior of the widget bar on the dashboard, allowing you to hide it, hide it when not in use, or leave it always visible.
  • Refresh Frequency: allows configuring the data refresh frequency displayed in the list.
  • Extra actions: allows user to add new specific actions to the widget with your own code.

Extra action config

You can add a new one by pressing the New button.

Once you added a custom action it can be modified later by pressing the name in the list.

In order to remove the custom action click the delete icon button on the right.

In extra actions you can write your own code were you can open other dashboards, entities dashboards or execute wizards.

Extra action code

You can find all available functions and methods in Extra parameters

BIM/IFC General Configuration
  • Organization files allows you to select a file to add to our widget

  • Preview here, you can see the loaded model and preselect those elements you want to highlight/monitor

In the preview panel, the following actions are found:

[…The actions are similar to the Actions Panel section…]

Items Tab

From here, you can alter the values of the elements selected in the model preview:

  • Express ID allows you to modify the ID of the selected element if it has changed in the model
  • Alias you can assign an alternative display name
BIM/IFC Items Configuration

Code Tab

Here, you configure the logic needed for the identification of different values and to display them on the elements of the model.

BIM/IFC Code Configuration

IMPORTANT NOTE: Whenever code is modified, it must be evaluated to save the changes

Function

Depending of the configuration receives the following parameters:

  • entityData contains the data of the opened entity NOTE: only available when the user opens an entity dashboard template

Example:

{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "device_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}
  • relatedEntities contains an array of entities related to the entity selected. NOTE: only available when the user opens an entity dashboard template

Example:

[{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "related_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}]
  • timeserieData contains info about the timeserie opened by the user
    • config timeserie configuration
    • data timeserie row selected

NOTE: only available when the user opens an entity dashboard template from timeserie table widget

An example:

{
  "config": {
    "identifier": "69281dc43545e97df66c42a1",
    "name": "Battery charge history",
    "timeBucket": 3600,
    "bucketColumn": "bucketEnd",
    "bucketInitColumn": "bucketInit",
    "identifierColumn": "EntityID",
    "retention": 2592000,
    "origin": "2025-11-26T23:00:00Z",
    "context": [
      {
        "path": "provision.device.administrativeState",
        "name": "Administrative state",
        "sort": "true",
        "filter": "YES",
        "type": "string"
      }
    ],
    "columns": [
      {
        "path": "device.powersupply.battery.charge._current.value",
        "name": "Powersupply battery charge Current Value",
        "filter": "NO",
        "type": "number",
        "sort": false,
        "aggregationFunction": "FIRST"
      }
    ]
  },
  "data": {
    "bucketEnd": "2025-12-11T13:00:00+01:00",
    "bucketInit": "2025-12-11T12:00:00+01:00",
    "EntityID": "entity_1",
    "Powersupply battery charge Current Value": 34
  }
}
  • alarmData contains the data of the alarm opened in template
{
  "identifier": "270dd9f9-1396-4660-bb5f-8d8b471e1dcd",
  "name": "activityForbidden",
  "rule": "activityForbidden",
  "description": "Activity detected for an entity with administrative state disabled",
  "severity": "INFORMATIVE",
  "priority": "LOW",
  "organization": "organization_name",
  "channel": "default_channel",
  "entityIdentifier": "A_WORKER_1",
  "subEntityIdentifier": "A_WORKER_1",
  "resourceType": "ENTITY_ASSET",
  "status": "CLOSED",
  "openingDate": "2019-06-27T08:57:36+02:00",
  "closureDate": "2019-06-27T08:57:51+02:00"
}
  • callback (optional) function used to send data to the widget (only when the api/http petitions are promised, use return instead)
callback();

or

return;

Available utils

$api -> use it to create http petitions to OpenGate Api Rest doc

$user -> Logged user

Example:

{
    "email": "email@amplia.es",
    "workgroup": "workgroup",
    "domain": "domain",
    "profile": "profile",
    "countryCode": "ES",
    "langCode": "en",
    "timezone": "Europe/Madrid"
}

$moment -> use it to format date doc

console -> display messages in navigator console

Promise -> allows easy execution of multiple promises

http -> javascript encapsulation of useFetch (Nuxt 4) library doc

alert -> alert navigator method

setValueToItem -> Sets a value to the selected item

setValueToItem(itemID, alias, value [,style, datetime, deviceId, datastreamId])

setItemStatus -> Sets an special status

setItemStatus(itemID, [rgbColor(string format)|null])

Final code structure build by the application

async function main(entityData, relatedEntitiesData, timeserieData, alarmData,callback) {
  // YOUR CODE HERE WITH RETURN OR CALLBACK
}

Examples


Subsections of BIM/IFC Widget

Change Item Status by Battery Charge

Code

/**
 * Logic:
 * 1. Queries a sample of maximum 50 entities.
 * 2. Calculates the average of the `device.powersupply.battery.charge` values.
 * 3. Update "VentanaPrincipal" value with the average.
 * 4. If the floor of average is even, changes "VentanaPrincipal" status to Red (#FF0000).
 * 5. If the floor of average is odd, changes "VentanaPrincipal" status to Green (#00FF00).
 */

// 1. Create the builder to search for entities, limiting to 50
var builder = $api.entitiesSearchBuilder()
    .limit(50)
    .flattened();

// 2. Execute the query
var response = await builder.build().execute();

var totalCharge = 0;
var count = 0;

// 3. Iterate through the results and sum the battery charge
if (response && response.data && response.data.entities) {
    response.data.entities.forEach(function(entity) {
        // Access the battery charge field
        // Field: device.powersupply.battery.charge
        var batteryField = entity['device.powersupply.battery.charge'];
        
        if (batteryField && batteryField._value && batteryField._value._current && batteryField._value._current.value) {
            var val = parseFloat(batteryField._value._current.value);
            if (!isNaN(val)) {
                totalCharge += val;
                count++;
            }
        }
    });
}

// 4. Calculate Average
var average = count > 0 ? totalCharge / count : 0;

// 5. Determine color based on parity of the floor of the average
// Even -> Red "#FF0000"
// Odd -> Green "#00FF00"
var floorAvg = Math.floor(average);
var isEven = floorAvg % 2 === 0;
var color = isEven ? "#FF0000" : "#00FF00";

// 6. Set the item status and value
// setItemStatus(itemID, [rgbColor(string format)|null])
setItemStatus("VentanaPrincipal", color);

// setValueToItem(itemID, alias, value)
setValueToItem("VentanaPrincipal", "Battery Avg", average.toFixed(2));

// Use return as the function is async
return;

All Entities Average Battery

Code

/**
 * Logic:
 * 1. Iterates through ALL entities in the platform using `executeWithAsyncPaging` (efficient pagination).
 * 2. Calculates the average of the `device.powersupply.battery.charge` values across all entities.
 * 3. Update "VentanaPrincipal" value with the global average.
 * 4. If the floor of average is even, changes "VentanaPrincipal" status to Red (#FF0000).
 * 5. If the floor of average is odd, changes "VentanaPrincipal" status to Green (#00FF00).
 */

var totalCharge = 0;
var count = 0;

// 1. Create the builder
var builder = $api.entitiesSearchBuilder()
    .limit(2000) // Max limit per page
    .flattened();

// 2. Execute with async paging
// executeWithAsyncPaging(resourceName) returns a Promise
return builder.build().executeWithAsyncPaging('entities').then(
    // Success Callback (called when all pages are processed)
    function() {
        // 4. Calculate Average
        var average = count > 0 ? totalCharge / count : 0;

        // 5. Determine color based on parity of the floor of the average
        var floorAvg = Math.floor(average);
        var isEven = floorAvg % 2 === 0;
        var color = isEven ? "#FF0000" : "#00FF00";

        // 6. Set the item status and value
        setItemStatus("VentanaPrincipal", color);
        setValueToItem("VentanaPrincipal", "Battery Avg", average.toFixed(2));
    },
    // Error Callback
    function(err) {
        console.error("Error in paging:", err);
    },
    // Notify Callback (called for each page of results)
    function(pageData) {
        if (pageData && pageData.length > 0) {
            pageData.forEach(function(entity) {
                // Access the battery charge field
                var batteryField = entity['device.powersupply.battery.charge'];
                
                if (batteryField && batteryField._value && batteryField._value._current && batteryField._value._current.value) {
                    var val = parseFloat(batteryField._value._current.value);
                    if (!isNaN(val)) {
                        totalCharge += val;
                        count++;
                    }
                }
            });
        }
    }
);

Custom Action

Custom action widgets allow you to code an action to be performed, which will be triggered by a button or a form.

How it Works

Custom action widget

This widget will display a form or button that will execute the user-coded action.

In the case of using a form, the action will receive the form values and any desired logic can be applied to them.

Additionally, this widget can be configured so that the action executes within a dialog box as an independent form.

Custom action execution result

Once the execution is initiated, a progress box will appear displaying the result of the action.

The execution will be successful as long as no exception is thrown in the source code.

Configuration

General

  • Boxed: widget will be displayed with background in dahsboard.
  • About: widget description in Markdown format.
  • Title: widget title. It can be configured to remain fixed in the widget or only be displayed when it receives focus.
  • Toolbar: configures the behavior of the widget bar on the dashboard, allowing you to hide it, hide it when not in use, or leave it always visible.
  • Refresh Frequency: allows configuring the data refresh frequency displayed in the list.
  • Extra actions: allows user to add new specific actions to the widget with your own code.

Extra action config

You can add a new one by pressing the New button.

Once you added a custom action it can be modified later by pressing the name in the list.

In order to remove the custom action click the delete icon button on the right.

In extra actions you can write your own code were you can open other dashboards, entities dashboards or execute wizards.

Extra action code

You can find all available functions and methods in Extra parameters

  • Description here you will write what action will be executed when the corresponding button is pressed
  • Icon determines the type of desired visualization: a button with an icon, an image, or a custom form. In the case of a form, it can also be specified if you want to display it as a dialog box.
Custom action general configuration

If it’s a form, you must enter a JSON schema that determines what will be drawn.

Custom action schema configuration

Expert

Custom action expert configuration

Here you can configure source code that will run before the widget is loaded, allowing for the dynamic construction of the JSON schema of the form to be displayed, thereby facilitating the construction of dynamic forms based on platform data.

Available utils

$api -> use it to create http petitions to OpenGate Api Rest doc

$user -> Logged user

Example:

{
    "email": "email@amplia.es",
    "workgroup": "workgroup",
    "domain": "domain",
    "profile": "profile",
    "countryCode": "ES",
    "langCode": "en",
    "timezone": "Europe/Madrid"
}

$moment -> use it to format date doc

console -> display messages in navigator console

Promise -> allows easy execution of multiple promises

http -> javascript encapsulation of useFetch (Nuxt 4) library doc

Function

Depending of the configuration receives the following parameters:

  • entityData contains the data of the opened entity NOTE: only available when the user opens an entity dashboard template

Example:

{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "device_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}
  • relatedEntities contains an array of entities related to the entity selected. NOTE: only available when the user opens an entity dashboard template

Example:

[{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "related_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}]
  • timeserieData contains info about the timeserie opened by the user
    • config timeserie configuration
    • data timeserie row selected

NOTE: only available when the user opens an entity dashboard template from timeserie table widget

An example:

{
  "config": {
    "identifier": "69281dc43545e97df66c42a1",
    "name": "Battery charge history",
    "timeBucket": 3600,
    "bucketColumn": "bucketEnd",
    "bucketInitColumn": "bucketInit",
    "identifierColumn": "EntityID",
    "retention": 2592000,
    "origin": "2025-11-26T23:00:00Z",
    "context": [
      {
        "path": "provision.device.administrativeState",
        "name": "Administrative state",
        "sort": "true",
        "filter": "YES",
        "type": "string"
      }
    ],
    "columns": [
      {
        "path": "device.powersupply.battery.charge._current.value",
        "name": "Powersupply battery charge Current Value",
        "filter": "NO",
        "type": "number",
        "sort": false,
        "aggregationFunction": "FIRST"
      }
    ]
  },
  "data": {
    "bucketEnd": "2025-12-11T13:00:00+01:00",
    "bucketInit": "2025-12-11T12:00:00+01:00",
    "EntityID": "entity_1",
    "Powersupply battery charge Current Value": 34
  }
}
  • alarmData contains the data of the alarm opened in template
{
  "identifier": "270dd9f9-1396-4660-bb5f-8d8b471e1dcd",
  "name": "activityForbidden",
  "rule": "activityForbidden",
  "description": "Activity detected for an entity with administrative state disabled",
  "severity": "INFORMATIVE",
  "priority": "LOW",
  "organization": "organization_name",
  "channel": "default_channel",
  "entityIdentifier": "A_WORKER_1",
  "subEntityIdentifier": "A_WORKER_1",
  "resourceType": "ENTITY_ASSET",
  "status": "CLOSED",
  "openingDate": "2019-06-27T08:57:36+02:00",
  "closureDate": "2019-06-27T08:57:51+02:00"
}

Action code specific parameters

  • value is the value, in string format, entered in value field when enabled

  • model contains the custom form values data

Example:

{
    "your.datastream.name": "datastreamvalue",
    "field1": "value1",
    "field2": "value2"
}
Final code structure build by the application
async function main(entityData,alarmData,relatedEntities,timeserieData,value, model) {
  // YOUR CODE HERE WITH RETURN OR CALLBACK
}

Expert code specific parameters

  • config receives the configuration object of the widget in order to modify it
  • callback function used to send the new configuration to the widget

Example:

callback(newConfig);

or

return newConfig;
Final code structure build by the application
async function main(entityData,alarmData,relatedEntities,timeserieData,config,callback) {
  // YOUR CODE HERE WITH RETURN OR CALLBACK
}

Examples


Subsections of Custom Action

Custom Action Example

Code

// Check if a value is provided
if (!value) {
  console.warn("No value provided for search.");
  return;
}

// Define the API endpoint (JSONPlaceholder)
// We'll filter todos by userId based on the input value
var apiUrl = "https://jsonplaceholder.typicode.com/todos?userId=" + value;

console.log("Fetching data from: " + apiUrl);

try {
  // Perform the request using the platform's http utility (encapsulation of Nuxt 4 useFetch)
  // useFetch typically parses JSON automatically.
  // We await the result. 
  // Note: useFetch in Nuxt returns { data, error, ... }
  const { data, error } = await http(apiUrl);

  if (error && error.value) {
    throw new Error("Generic Error: " + error.value);
  }

  // Access the data (Ref value if it's a ref, or direct if the utility un-refs it)
  // Assuming standard Nuxt composition API behavior where top level properties are Refs
  const results = data.value || data;

  console.log("--- Search Results (User ID: " + value + ") ---");
  // Check if we got any results
  if (results && results.length > 0) {
    console.table(results); // Display as a table for better readability
    console.log("Total records found: " + results.length);
  } else {
    console.log("No records found for User ID: " + value);
  }

} catch (err) {
  console.error("Fetch error:", err);
}

Custom Action Entity Search Example

Code

// Check if model is provided
if (!model) {
  console.warn("No form data (model) provided.");
  return;
}

const { name, specificType } = model;
console.log("Searching entities with Name:", name, "and Specific Type:", specificType);

// Create the builder
var builder = $api.entitiesSearchBuilder().flattened();

// Define filters based on model values
var filter = {
  and: [
    {
      eq: {
        'resourceType': 'entity.device'
      }
    }
  ]
};

if (name) {
  filter.and.push({
    like: {
      'provision.administration.identifier': name // Assuming 'name' maps to identifier for this example, or use 'provision.device.name' if appropriate
    }
  });
}

if (specificType) {
  filter.and.push({
    eq: {
      'provision.device.specificType': specificType
    }
  });
}

// Apply filter if any conditions were added
if (filter.and.length > 0) {
  builder.filter(filter);
}

try {
  // Execute the search
  // await is supported in this context
  const response = await builder.build().execute();

  if (response.statusCode === 200) {
    console.log("--- Entity Search Results ---");
    const entities = response.data.entities;
    if (entities && entities.length > 0) {
      console.table(entities.map(e => ({
        id: e['provision.administration.identifier']._value._current.value,
        specificType: e['provision.device.specificType'] ? e['provision.device.specificType']._value._current.value : 'N/A'
      })));
      console.log("Total entities found:", entities.length);
    } else {
      console.log("No entities match the criteria.");
    }
  } else {
    console.error("Search failed with status:", response.statusCode);
  }

} catch (err) {
  console.error("Error executing entity search:", err);
}

Custom Chart

Custom charts allow for the display of data in graphical format based on user-defined logic.

How it Works

Custom chart widget

Once the logic for data retrieval is entered, the data will be displayed on the widget.

What sets the custom chart apart from others is that the data source can be external, internal, both, or even fabricated. Moreover, multiple charts can be combined within the same widget.

This widget is compatible with eCharts library version 5. Multiple examples can be accessed via the following link: eCharts

Configuration

General

  • Boxed: widget will be displayed with background in dahsboard.
  • About: widget description in Markdown format.
  • Title: widget title. It can be configured to remain fixed in the widget or only be displayed when it receives focus.
  • Toolbar: configures the behavior of the widget bar on the dashboard, allowing you to hide it, hide it when not in use, or leave it always visible.
  • Refresh Frequency: allows configuring the data refresh frequency displayed in the list.
  • Extra actions: allows user to add new specific actions to the widget with your own code.

Extra action config

You can add a new one by pressing the New button.

Once you added a custom action it can be modified later by pressing the name in the list.

In order to remove the custom action click the delete icon button on the right.

In extra actions you can write your own code were you can open other dashboards, entities dashboards or execute wizards.

Extra action code

You can find all available functions and methods in Extra parameters

  • Show widget filters instructs the widget to display its filters. These filters will be passed as an additional parameter to the data retrieval function.
Custom chart code configuration

Here, you input the necessary code to obtain the information to be displayed.

Depending on the selected options, the parameters received by the function will be displayed.

The function must always return a JSON object that is compatible with eCharts’ chart configuration.

Every time the code is updated, it must be evaluated where a preview of the result can be seen.

Custom chart code preview

Function

Depending of the configuration receives the following parameters:

  • entityData contains the data of the opened entity NOTE: only available when the user opens an entity dashboard template

Example:

{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "device_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}
  • relatedEntities contains an array of entities related to the entity selected. NOTE: only available when the user opens an entity dashboard template

Example:

[{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "related_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}]
  • timeserieData contains info about the timeserie opened by the user
    • config timeserie configuration
    • data timeserie row selected

NOTE: only available when the user opens an entity dashboard template from timeserie table widget

An example:

{
  "config": {
    "identifier": "69281dc43545e97df66c42a1",
    "name": "Battery charge history",
    "timeBucket": 3600,
    "bucketColumn": "bucketEnd",
    "bucketInitColumn": "bucketInit",
    "identifierColumn": "EntityID",
    "retention": 2592000,
    "origin": "2025-11-26T23:00:00Z",
    "context": [
      {
        "path": "provision.device.administrativeState",
        "name": "Administrative state",
        "sort": "true",
        "filter": "YES",
        "type": "string"
      }
    ],
    "columns": [
      {
        "path": "device.powersupply.battery.charge._current.value",
        "name": "Powersupply battery charge Current Value",
        "filter": "NO",
        "type": "number",
        "sort": false,
        "aggregationFunction": "FIRST"
      }
    ]
  },
  "data": {
    "bucketEnd": "2025-12-11T13:00:00+01:00",
    "bucketInit": "2025-12-11T12:00:00+01:00",
    "EntityID": "entity_1",
    "Powersupply battery charge Current Value": 34
  }
}
  • alarmData contains the data of the alarm opened in template
{
  "identifier": "270dd9f9-1396-4660-bb5f-8d8b471e1dcd",
  "name": "activityForbidden",
  "rule": "activityForbidden",
  "description": "Activity detected for an entity with administrative state disabled",
  "severity": "INFORMATIVE",
  "priority": "LOW",
  "organization": "organization_name",
  "channel": "default_channel",
  "entityIdentifier": "A_WORKER_1",
  "subEntityIdentifier": "A_WORKER_1",
  "resourceType": "ENTITY_ASSET",
  "status": "CLOSED",
  "openingDate": "2019-06-27T08:57:36+02:00",
  "closureDate": "2019-06-27T08:57:51+02:00"
}
  • filters introduced by the user. These filters are:
    • generic widget generic filter
    • period widget date period filter
    • inherit json object with inherited filter if ‘shared filter’ is enabled. This filter is composed by ‘and’ filter that contains standar filter, private/template filter and headers filters from the source widget.

An example:

{
    "filters": {
        "generic": "filter introduced by the user",
        "period": {"from":"2023-03-27T10:59:27+02:00","to":null},
        "inherit": {
            "and": [
              { "eq": {"field.identifier._current.value": "value"}},
              { "eq": {"field2.identifier._current.value": "value2"}}
            ]
        }
    }
}
  • callback (optional) function used to send chart data (only when the api/http petitions are promised, use return instead)
callback(chartConfig);

or

callback({
  title: {
    text: 'Referer of a Website',
    subtext: 'Fake Data',
    left: 'center'
  },
  tooltip: {
    trigger: 'item'
  },
  legend: {
    orient: 'vertical',
    left: 'left'
  },
  series: [
    {
      name: 'Access From',
      type: 'pie',
      radius: '50%',
      data: [
        { value: 1048, name: 'Search Engine' },
        { value: 735, name: 'Direct' },
        { value: 580, name: 'Email' },
        { value: 484, name: 'Union Ads' },
        { value: 300, name: 'Video Ads' }
      ]
    }
  ]
});

or

return chartConfig;

Available utils

$api -> use it to create http petitions to OpenGate Api Rest doc

$user -> Logged user

Example:

{
    "email": "email@amplia.es",
    "workgroup": "workgroup",
    "domain": "domain",
    "profile": "profile",
    "countryCode": "ES",
    "langCode": "en",
    "timezone": "Europe/Madrid"
}

$moment -> use it to format date doc

console -> display messages in navigator console

Promise -> allows easy execution of multiple promises

http -> javascript encapsulation of useFetch (Nuxt 4) library doc

echarts -> echarts core library

ecStat -> echarts stats library

addChartEvent -> (event, handler[(event, chartInstance) => {}] [, query]) - adds an event handler doc

Final code structure build by the application

async function main(entityData,timeserieData,filters,page,pageElements,callback) {
  // YOUR CODE HERE WITH RETURN OR CALLBACK
}

Examples


Subsections of Custom Chart

Custom Chart Examples

Code

return {
  title: {
    text: 'Referer of a Website',
    subtext: 'Fake Data',
    left: 'center'
  },
  tooltip: {
    trigger: 'item'
  },
  legend: {
    orient: 'vertical',
    left: 'left'
  },
  series: [
    {
      name: 'Access From',
      type: 'pie',
      radius: '50%',
      data: [
        { value: 1048, name: 'Search Engine' },
        { value: 735, name: 'Direct' },
        { value: 580, name: 'Email' },
        { value: 484, name: 'Union Ads' },
        { value: 300, name: 'Video Ads' }
      ],
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: 'rgba(0, 0, 0, 0.5)'
        }
      }
    }
  ]
};

Custom HeatMap Chart Example

Code

const getDaysArray = function (start, end) {
  const arr = [];
  var curMonth;
  var monthData = {};
  var days = [];
  for (const dt = new Date(start); dt <= new Date(end); dt.setDate(dt.getDate() + 1)) {
    var curDate = new Date(dt);

    var newMonth = (curDate.getMonth() + 1) + '/' + curDate.getFullYear();

    if (curMonth && curMonth !== newMonth) {
      monthData = {
        value: curMonth,
        children: days
      };
      arr.push(monthData);
      days = [];
    }

    days.push(curDate.getDate() + '/' + (curDate.getMonth() + 1) + '/' + curDate.getFullYear());


    curMonth = newMonth;
  }

  if (days.length) {
    monthData = {
      value: curMonth,
      children: days
    };
    arr.push(monthData);
  }
  return arr;
};

const chartConfig = {
  visualMap: {
    type: 'continuous',
    min: 0,
    max: 1,
    dimension: 2,
    calculable: true,
    orient: 'horizontal',
    top: 5,
    left: 'center',
    color: ['#0f0', '#f00']
  },
  legend: {
    show: true,
    bottom: 10
  },
  matrix: {
    x: {
      data: [],
      levelSize: 40,
    },
    y: {
      label: {
        show: true,
        width: 150
      },
      levelSize: 150,
      data: []
    },
    top: 70,
    bottom: 40,
    left: 2,
    right: 2
  },
  series: {
    type: 'heatmap',
    coordinateSystem: 'matrix',
    data: [],
    label: {
      show: false
    }
  },
  tooltip: {
    show: true
  }
};

// busqueda de todas las entidades
var entitiesBuilder = $api.entitiesSearchBuilder().limit(1000, 1);
var entitiesFilter = {
  and: [{
    eq: {
      'provision.device.specificType': 'METER'
    }
  }]
};
if (filters && filters.generic) {
  entitiesFilter.and.push({
    eq: {
      'provision.administration.identifier': filters.generic
    }
  });
}

entitiesBuilder.filter(entitiesFilter);

const tempData = {};

var initDate = new Date(new Date().getDate() - 30);
var endDate = new Date();

var filter = {
  and: [{
    eq: {
      "datapoints.datastreamId": "volTot"
    }
  }]
};


if (filters && filters.period) {
  if (filters.period.from) {
    initDate = new Date(filters.period.from);
    filter.and.push({
      gte: {
        'datapoints._current.at': filters.period.from
      }
    });
  }

  if (filters.period.to) {
    endDate = new Date(filters.period.to);
    filter.and.push({
      lt: {
        'datapoints._current.at': filters.period.to
      }
    });
  }
}

chartConfig.matrix.x.data = getDaysArray(initDate, endDate);

var response = await entitiesBuilder.flattened().build().execute();

if (response && response.data && response.data.entities && response.data.entities.length > 0) {
  response.data.entities.forEach((entityDataTmp) => {
    const entityIdentifier = entityDataTmp['provision.administration.identifier']._value._current.value;

    if (!tempData[entityIdentifier]) {
      tempData[entityIdentifier] = [];

      chartConfig.matrix.x.data.forEach((monthData) => {
        monthData.children.forEach((dayData) => {
          tempData[entityIdentifier].push([dayData, entityIdentifier, 0]);
        });
      });
    }
  });
}


var builder = $api.datapointsSearchBuilder().limit(2000, 1).addSortBy('datapoints._current.at', 'DESCENDING');


builder.filter(filter).build().executeWithAsyncPaging('datapoints').then(
  function endFunction() {
    //drawChart(datapointsBuffer, true)
    chartConfig.matrix.y.data = Object.keys(tempData);

    let finalData = [];
    chartConfig.matrix.y.data.forEach((deviceId) => {
      finalData = finalData.concat(tempData[deviceId]);
    });
    chartConfig.series.data = finalData;
    callback(chartConfig);
  },
  function cancelado(err) {
    console.error(err);
    //drawChart(datapointsBuffer, true)
    callback(chartConfig);
  },
  function notify(notifyData) {
    if (notifyData && notifyData.length > 0) {
      notifyData.forEach((datapoint) => {
        if (!tempData[datapoint.entityIdentifier]) {
          tempData[datapoint.entityIdentifier] = [];

          chartConfig.matrix.x.data.forEach((monthData) => {
            monthData.children.forEach((dayData) => {
              tempData[datapoint.entityIdentifier].push([dayData, datapoint.entityIdentifier, 0]);
            });
          });
        }

        const currentAt = new Date(datapoint._current.at);
        const currentAtTxt = currentAt.getDate() + '/' + (currentAt.getMonth() + 1) + '/' + currentAt.getFullYear();
        const atIndex = tempData[datapoint.entityIdentifier].findIndex((element) => element[0] === currentAtTxt);
        tempData[datapoint.entityIdentifier][atIndex][2] = 1;
      });
    }
  }
).catch(function (err) {
  console.error(err);
});

Custom Table

Custom tables allow you to display data in a list format based on logic encoded by the user.

How it Works

Custom table widget

Once the logic for data retrieval is entered, the data will be displayed in the table.

What sets the custom table apart is that the data source can be external, internal, both, or even fabricated. Moreover, custom charts can be mixed in, and additional information can be included in the expandable panel.

Custom table expanded row

Configuration

General

  • Boxed: widget will be displayed with background in dahsboard.
  • About: widget description in Markdown format.
  • Title: widget title. It can be configured to remain fixed in the widget or only be displayed when it receives focus.
  • Toolbar: configures the behavior of the widget bar on the dashboard, allowing you to hide it, hide it when not in use, or leave it always visible.
  • Refresh Frequency: allows configuring the data refresh frequency displayed in the list.
  • Extra actions: allows user to add new specific actions to the widget with your own code.

Extra action config

You can add a new one by pressing the New button.

Once you added a custom action it can be modified later by pressing the name in the list.

In order to remove the custom action click the delete icon button on the right.

In extra actions you can write your own code were you can open other dashboards, entities dashboards or execute wizards.

Extra action code

You can find all available functions and methods in Extra parameters

  • Pagination type allows you to specify the kind of pagination you want. There may be no pagination, pagination handled by the table component (local), or server-side pagination.

If server-side pagination is chosen, the script will receive parameters like the number of elements and the page, allowing the user to decide.

  • Page elements specifies the number of elements to show per page.
  • Allow data grouping enables the table to group items by elements in a column.
  • Show widget filters instructs the widget to display its filters. These filters will be passed as an additional parameter to the data retrieval function.
  • Compact the size of the table rows will make the table rows more compact to save vertical space.
  • Expandable rows enables an information button for expanded data on each row. The code must fill this information, or an empty space will be displayed.
Custom table general configuration

Column Configuration

Custom table column configuration

The data that will be displayed in table. In order to finish the configuration for this you must add one column at least and select a primary key. For every column you can define the next data:

  • Name to show in headers
  • JSON field is the field to read in data returned by the function. Primary key use that field
  • Sortable permits sorting for this column
  • Groupable allow group by this field in table
  • Filterable allow filter by this field in table
  • Data type of the filter (only when filterable)
  • Divisor draws a separator between this column and the next
  • Show entity actions enables context menu for the column allowing to perform some actions depending of the item value.

Columns supports drag&drop in order to determine the position in the table.

Code Tab

This is where you input the necessary code to retrieve the data to be displayed.

Custom table code configuration

Depending on the options selected in the general tab, the parameters received by the function will be displayed.

The function must always return an array of JSON objects compatible with the specified configuration for the component to be able to render them.

Each time the code is updated, it must be evaluated where a preview of the result can be seen. Finally you MUST return an array with a json that matches the columns configuration.

Custom table code preview

Function

Depending of the configuration receives the following parameters:

  • entityData contains the data of the opened entity NOTE: only available when the user opens an entity dashboard template

Example:

{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "device_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}
  • relatedEntities contains an array of entities related to the entity selected. NOTE: only available when the user opens an entity dashboard template

Example:

[{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "related_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}]
  • timeserieData contains info about the timeserie opened by the user
    • config timeserie configuration
    • data timeserie row selected

NOTE: only available when the user opens an entity dashboard template from timeserie table widget

An example:

{
  "config": {
    "identifier": "69281dc43545e97df66c42a1",
    "name": "Battery charge history",
    "timeBucket": 3600,
    "bucketColumn": "bucketEnd",
    "bucketInitColumn": "bucketInit",
    "identifierColumn": "EntityID",
    "retention": 2592000,
    "origin": "2025-11-26T23:00:00Z",
    "context": [
      {
        "path": "provision.device.administrativeState",
        "name": "Administrative state",
        "sort": "true",
        "filter": "YES",
        "type": "string"
      }
    ],
    "columns": [
      {
        "path": "device.powersupply.battery.charge._current.value",
        "name": "Powersupply battery charge Current Value",
        "filter": "NO",
        "type": "number",
        "sort": false,
        "aggregationFunction": "FIRST"
      }
    ]
  },
  "data": {
    "bucketEnd": "2025-12-11T13:00:00+01:00",
    "bucketInit": "2025-12-11T12:00:00+01:00",
    "EntityID": "entity_1",
    "Powersupply battery charge Current Value": 34
  }
}
  • alarmData contains the data of the alarm opened in template
{
  "identifier": "270dd9f9-1396-4660-bb5f-8d8b471e1dcd",
  "name": "activityForbidden",
  "rule": "activityForbidden",
  "description": "Activity detected for an entity with administrative state disabled",
  "severity": "INFORMATIVE",
  "priority": "LOW",
  "organization": "organization_name",
  "channel": "default_channel",
  "entityIdentifier": "A_WORKER_1",
  "subEntityIdentifier": "A_WORKER_1",
  "resourceType": "ENTITY_ASSET",
  "status": "CLOSED",
  "openingDate": "2019-06-27T08:57:36+02:00",
  "closureDate": "2019-06-27T08:57:51+02:00"
}
  • filters introduced by the user. These filters are:
    • generic widget generic filter
    • period widget date period filter
    • column json object with each column filter
    • sort an array containing every sorted column with its direction sorted by user preferences
    • inherit json object with inherited filter if ‘shared filter’ is enabled. This filter is composed by ‘and’ filter that contains standar filter, private/template filter and headers filters from the source widget.

An example:

{
    "filters": {
        "generic": "filter introduced by the user",
        "period": {"from":"2023-03-27T10:59:27+02:00","to":null},
        "column": {
            [column value field]: {
                operator: "eq",
                value: "filter introduced by the user in colum"
            },
            [column value field]: {
                operator: "gt",
                value: "filter introduced by the user in colum"
            }
        },
        "sort": [
            {
                column: "column value field",
                direction: "asc" or "desc"
            },
            {
                column: "column value field",
                direction: "asc" or "desc"
            }
        ],
        "inherit": {
            "and": [
              { "eq": {"field.identifier._current.value": "value"}},
              { "eq": {"field2.identifier._current.value": "value2"}}
            ]
        }
    }
}
  • pageElements and page that determines the current page to display. Only enabled when server pagination enabled in table parameters. Disabling server pagination quits this parameters and function must be evaluated again.

  • callback function used to send table data only when the api/http petitions are promised

callback(data);

or

callback([{
  "field1": "value1",
  "field2": "value2"
}]);

Available utils

$api -> use it to create http petitions to OpenGate Api Rest doc

$user -> Logged user

Example:

{
    "email": "email@amplia.es",
    "workgroup": "workgroup",
    "domain": "domain",
    "profile": "profile",
    "countryCode": "ES",
    "langCode": "en",
    "timezone": "Europe/Madrid"
}

$moment -> use it to format date doc

console -> display messages in navigator console

Promise -> allows easy execution of multiple promises

http -> javascript encapsulation of useFetch (Nuxt 4) library doc

openDashboard -> (workspaceId, dashboardId, newPage) -> Opens the selected dashboard in selected workspace

openEntityDashboard -> (entityIdentifier[, organization[user if empty], resourceType[’entity.device’ if empty] , newPage]) -> Opens the entity’s temporary dashboard

Data format

Returned data must have one of the following formats (per item):

  • simple json data
{
    "jsonfield": "value to display. It can be HTML."
}
  • ‘complex’ json data
{
    "jsonfield": {
        "value": "value to display. It can be HTML",
        "_style": "cell custom style",
        "_chart": "displays an echarts chart. Overrides others in this item",
        "_extension": "jsonfield like (value, _style, _chart)  plus _table. Only enabled when expandable rows enabled"
    }
}

NOTE _extension field can combine _chart and _table elements in the same item

  • Element _chart Must have an echarts config json.

  • Element _table Displays a table inside the column and overrides others in this item.

{
    columns: ['each', 'item', 'is', 'a', 'column],
    data: [
        ['data 1', 'in', 'columns', 'order', { jsonfield (without _extension is supported) }],
        ['data 2', 'in', 'columns', 'order', { jsonfield (without _extension is supported) }]
    ]

}

Examples


Subsections of Custom Table

Custom Table Example

Code

function baState(value) {
  if (value) {
    var newValor;
    var style;
    var title;
    switch (value) {
      case "0":
        newValor = "(OK)🟢";
        style = 'color:green;';
        title = "Bateria OK";
        break;
      case "1":
        newValor = "(BAJA)🟡";
        style = 'color:yellow;';
        title = "Bateria BAJA";
        break;
      case "2":
        newValor = "(MUY BAJA)🟠";
        style = 'color:orange;';
        title = "Bateria MUY BAJA";
        break;
      case "3":
        newValor = "(AGOTADA)🔴";
        style = 'color:red;';
        title = "Bateria AGOTADA";
        break;
      default:
        newValor = "Error en el campo";
        break;
    }
    return {
      _style: style,
      value: "<div title='" + title + ">" + newValor + "</div>"
    };
  } else {
    return 'N/A';
  }
}

function bpaState(value) {
  var newValor;
  var title;
  var style;
  if (value) {
    switch (value) {
      case "0":
        newValor = "(OK)🟢";
        title = "Protección no activa";
        style = 'color:green;';
        break;
      case "1":
        newValor = "(ACTIVADA)🔴";
        title = "Protección activada";
        style = 'color:red;';

        break;
      default:
        newValor = "Error en el campo";
        break;
    }
    return {
      value: "<div title='" + title + "'>" + newValor + "</div>",
      _style: style
    };
  }
}

function boolState(value) {
  var newValor;
  var title;
  var style;
  if (value === true || value === false) {
    if (value) {
      newValor = "🔴";
      title = "TRUE";
      style = 'color:red;';
    } else {
      newValor = "🟢";
      title = "FALSE";
      style = 'color:green;';
    }
    return {
      value: "<div title='" + title + "'>" + newValor + "</div>",
      _style: style
    };
  }
}


console.log("--------------------------------------------");
var builder = $api.entitiesSearchBuilder().limit(1000).flattened();

var filter = {
  and: [
    {
      neq: {
        'provision.device.specificType': 'CONCENTRATOR'
      }
    }
  ]
};

if (entityData && entityData['provision.administration.identifier']) {
  var entityKey = entityData['provision.administration.identifier']._value._current.value;
  filter.and.push({
    eq: {
      'provision.Sector': entityKey
    }
  });
}

builder.filter(filter);

const response = await builder.build().execute();

var entities = [];
if (response.statusCode === 200) {
  response.data.entities.forEach(function (entityTmp) {
    var finalData;
    if (entityTmp['ba']) {
      finalData = {
        identifier: {
          value: entityTmp['provision.administration.identifier']._value._current.value,
          _style: 'margin-left: 4px;'
        }
      };
      finalData.tipo_alerta = 'Batería';
      finalData.estado = baState(entityTmp['ba']._value._current.value);
      finalData.fecha = new Date(entityTmp['ba']._value._current.at).toLocaleString();
      entities.push(finalData);
    }

    if (entityTmp['bpA']) {
      finalData = {
        identifier: {
          value: entityTmp['provision.administration.identifier']._value._current.value,
          _style: 'margin-left: 4px;'
        }
      };
      finalData.tipo_alerta = 'Protección Batería';
      finalData.estado = bpaState(entityTmp['bpA']._value._current.value);
      finalData.fecha =  new Date(entityTmp['bpA']._value._current.at).toLocaleString();
      entities.push(finalData);
    }

    if (entityTmp['ta']) {
      finalData = {
        identifier: {
          value: entityTmp['provision.administration.identifier']._value._current.value,
          _style: 'margin-left: 4px;'
        }
      };
      finalData.tipo_alerta = 'Tampering';
      finalData.estado = boolState(entityTmp['ta']._value._current.value);
      finalData.fecha = new Date( entityTmp['ta']._value._current.at).toLocaleString();
      entities.push(finalData);
    }

    if (entityTmp['fa']) {
      finalData = {
        identifier: {
          value: entityTmp['provision.administration.identifier']._value._current.value,
          _style: 'margin-left: 4px;'
        }
      };
      finalData.tipo_alerta = 'Fuga';
      finalData.estado = boolState(entityTmp['fa']._value._current.value);
      finalData.fecha =  new Date(entityTmp['fa']._value._current.at).toLocaleString();
      entities.push(finalData);
    }

  });
}

return entities;

External API (USGS Earthquakes)

Static content

This example demonstrates how to fetch data from the USGS Earthquake Hazards Program API. This service supports server-side date filtering, which aligns perfectly with the widget’s Period Filter.

Function Explanation

  1. Date Filtering: The code checks the filters.period object.
    • If from and to are present, they are formatted to ISO 8601 strings (YYYY-MM-DD) and sent as starttime and endtime parameters.
    • If no period is selected, it defaults to the last 24 hours.
  2. Name/Text Filtering: The filters.generic (search text) is used to filter the results client-side (searching within the place field), as the API does not support a direct “text search” parameter for this endpoint.

Code

// Main function executed by the Custom Table widget
// parameters: entityData, filters, page, pageElements, callback

// Base URL for USGS Earthquake API (GeoJSON format)
let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=200';

// 1. Handle Date Filters (Server-Side)
if (filters && filters.period && filters.period.from && filters.period.to) {
  // Helper to format date as YYYY-MM-DD
  const formatDate = (dateStr) => new Date(dateStr).toISOString().split('T')[0];
  
  const start = formatDate(filters.period.from);
  const end = formatDate(filters.period.to);
  
  url += `&starttime=${start}&endtime=${end}`;
} else {
  // Default to 'now' if no filter (API defaults to last 30 days usually, let's limit to recent)
  // Actually, let's explicitely ask for last 2 days to keep data manageable if no filter
  // But for simplicity, we rely on the API defaults or a 'limit' param already added above.
}

try {
  // 2. Fetch Data
  const response = await http(url);
  
  let features = [];
  if (response && response.features) {
      features = response.features;
  } else if (response && response.json) {
      const json = await response.json();
      features = json.features || [];
  }

  // 3. Handle Name/Text Filter (Client-Side)
  // filtering by 'place' property
  if (filters && filters.generic && filters.generic.length > 0) {
    const search = filters.generic.toLowerCase();
    features = features.filter(f => 
      f.properties.place && f.properties.place.toLowerCase().includes(search)
    );
  }

  // 4. Map to Table Columns
  // Configured Columns hint: 'place', 'magnitude', 'time', 'status'
  const tableData = features.map(f => {
    const props = f.properties;
    const dateObj = new Date(props.time);
    
    // Determine color based on magnitude
    let magColor = 'green';
    if (props.mag >= 5) magColor = 'red';
    else if (props.mag >= 3) magColor = 'orange';

    return {
      place: props.place,
      magnitude: {
          value: props.mag ? props.mag.toFixed(1) : '0.0',
          _style: `font-weight:bold; color: ${magColor};`
      },
      time: dateObj.toLocaleString(),
      status: `<a href="${props.url}" target="_blank">Ver Detalles</a>`
    };
  });

  callback(tableData);

} catch (error) {
  console.error("Error fetching earthquake data:", error);
  callback([]);
}

Filtered Entity Retrieval

Description

This example shows how to retrieve entities filtering by a parameter and sorting by name.

Code

/**
 * Main function to retrieve and display entities
 * @param {Object} entityData - Context entity data
 * @param {Object} filters - Filters passed from the widget
 */

var builder = $api.entitiesSearchBuilder().limit(100).flattened();

// 1. Filter by parameter (assuming it comes in filters.generic or a specific field)
// Here we assume filters.generic contains a string to filter by name
if (filters && filters.generic) {
    builder.filter({
        like: {
            'provision.asset.name': filters.generic // Adjust field as needed (e.g., provision.device.name)
        }
    });
}

// 2. Sort by name
builder.sort([
    {
        column: 'provision.asset.name',
        direction: 'asc'
    }
]);

// 3. Execute query
var response = await builder.build().execute();

// 4. Transform results
var results = [];
if (response && response.data && response.data.entities) {
    response.data.entities.forEach(function(entity) {
            // Extract identifier (using bracket notation for flattened keys)
        var id = entity['provision.administration.identifier'] ? entity['provision.administration.identifier']._value._current.value : "Unknown";
        
        // Extract name (handle if it doesn't exist)
        var name = "N/A";
        if (entity['provision.asset.name']) {
            name = entity['provision.asset.name']._value._current.value;
        }

        results.push({
            identifier: id,
            name: name
        });
    });
}

return results;

Server Pagination Example (Reqres)

Static content

This example demonstrates how to implement server-side pagination using an external API (reqres.in). When “Server Pagination” is enabled in the widget configuration, the script receives page and pageElements parameters.

Function Explanation

  1. Page Parameters: The page and pageElements arguments act as the current page number and the page size (limit), respectively.
  2. API Request: The code constructs a request to reqres.in passing page and per_page query parameters.
  3. Callback: The function processes the response and sends the array of users to the widget via the callback.

Code

// Main function executed by the Custom Table widget
// parameters: entityData, filters, page, pageElements, callback

// 1. Prepare Pagination Parameters
// Ensure we have defaults if arguments are missing (safeguard)
const currentPage = page || 1;
const perPage = pageElements || 5;

// 2. Construct URL with pagination params
// reqres.in uses 'page' (1-based) and 'per_page'
const url = `https://reqres.in/api/users?page=${currentPage}&per_page=${perPage}`;

try {
  // 3. Fetch Data
  const response = await http(url);
  
  // 4. Extract Data
  // reqres.in returns: { page: 1, per_page: 6, total: 12, total_pages: 2, data: [...] }
  let users = [];
  
  // Check various response wrappers as 'http' might auto-parse JSON
  if (response && response.data && Array.isArray(response.data)) {
      users = response.data;
  } else if (response && response.json) {
      const json = await response.json();
      users = json.data || [];
  } else if (response && Array.isArray(response)) {
      users = response;
  }

  // 5. Format for Table
  // Configured Columns hint: 'id', 'avatar', 'first_name', 'last_name'
  const tableData = users.map(user => {
    return {
      id: user.id,
      avatar: `<img src="${user.avatar}" style="width: 30px; border-radius: 50%;">`,
      first_name: user.first_name,
      last_name: user.last_name,
      email: user.email
    };
  });

  // 6. Return Data
  // We return the array of items for the current page.
  callback(tableData);

} catch (error) {
  console.error("Error fetching paged data:", error);
  callback([]);
}

Battery Level Pie Chart

Description

This example shows how to retrieve entities and display their battery level as a Pie Chart within the table row.

Code

var builder = $api.entitiesSearchBuilder().limit(100).flattened();

// Handle generic filter if present
if (filters && filters.generic) {
    builder.filter({
        like: {
            'provision.administration.identifier': filters.generic
        }
    });
}

var response = await builder.build().execute();

var results = [];
if (response && response.data && response.data.entities) {
    response.data.entities.forEach(function(entity) {
        // Get identifier
        var id = entity['provision.administration.identifier'] ? entity['provision.administration.identifier']._value._current.value : "Unknown";
        
        // Get battery charge (default to 0 if not present)
        var charge = 0;
        if (entity['device.powersupply.battery.charge']) {
            charge = entity['device.powersupply.battery.charge']._value._current.value;
        }

        // Create the chart configuration
        var pieOption = {
            color: ['#91c7ae', '#c23531'],
            series: [
                {
                    type: 'pie',
                    radius: ['50%', '70%'],
                    avoidLabelOverlap: false,
                    label: {
                        show: false,
                        position: 'center'
                    },
                    emphasis: {
                        label: {
                            show: true,
                            fontSize: '10',
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: false
                    },
                    data: [
                        { value: charge, name: 'Charge' },
                        { value: 100 - charge, name: 'Empty' }
                    ]
                }
            ]
        };

        results.push({
            identifier: id,
            battery: {
                "_chart": pieOption,
                "_style": "height: 50px; width: 50px;" // Optional styling for the cell
            }
        });
    });
}

return results;

Open HMI/Custom Image

Programmable HMIs allow for the construction and modification of the interface.

How it Works

Open HMI widget

Once the logic for data retrieval and SVG painting/modification has been entered, it will be displayed on the widget.

What distinguishes this widget from the original HMI is that data sources can be external, and the widget’s content is built/modified through coding.

Configuration

General

  • Boxed: widget will be displayed with background in dahsboard.
  • About: widget description in Markdown format.
  • Title: widget title. It can be configured to remain fixed in the widget or only be displayed when it receives focus.
  • Toolbar: configures the behavior of the widget bar on the dashboard, allowing you to hide it, hide it when not in use, or leave it always visible.
  • Refresh Frequency: allows configuring the data refresh frequency displayed in the list.
  • Extra actions: allows user to add new specific actions to the widget with your own code.

Extra action config

You can add a new one by pressing the New button.

Once you added a custom action it can be modified later by pressing the name in the list.

In order to remove the custom action click the delete icon button on the right.

In extra actions you can write your own code were you can open other dashboards, entities dashboards or execute wizards.

Extra action code

You can find all available functions and methods in Extra parameters

  • SVG Content allows you to input the source code of an SVG as a starting point. The SVG content can be manipulated with the svgDom object of the editor. Content can either be entered directly or by selecting a file.
  • CSS Content allows you to apply styles to the SVG. Content can be entered directly or by selecting a file.
  • Preview shows a live preview resulting from the combination of the previously entered values, without running any code.
Open HMI general configuration

Code Tab

This is where you enter the code required for SVG manipulation. It is not necessary to enter code, but you must at least return the svgDom object for it to be able to render.

Open HMI code configuration

For SVG manipulation, standard HTML manipulation libraries will be used. Interactions can also be added to the SVG itself to integrate it with the platform’s data, linking it to device data and providing access to it.

Every time the code is updated, it must be evaluated, where a preview of the result will be shown. For this preview, the entered code is indeed evaluated, so the outcome will vary based on its execution.

Open HMI code preview

Available utils

$api -> use it to create http petitions to OpenGate Api Rest doc

$user -> Logged user

Example:

{
    "email": "email@amplia.es",
    "workgroup": "workgroup",
    "domain": "domain",
    "profile": "profile",
    "countryCode": "ES",
    "langCode": "en",
    "timezone": "Europe/Madrid"
}

$moment -> use it to format date doc

console -> display messages in navigator console

Promise -> allows easy execution of multiple promises

http -> javascript encapsulation of useFetch (Nuxt 4) library doc

alert -> javascript alert function

document -> javascript document object

domParser -> javascript DOMParser object

showPopup -> shows options for the selected entity: showPopup(entityId[,datastreamId])

Function

Depending of the configuration receives the following parameters:

  • entityData contains the data of the opened entity NOTE: only available when the user opens an entity dashboard template

Example:

{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "device_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}
  • relatedEntities contains an array of entities related to the entity selected. NOTE: only available when the user opens an entity dashboard template

Example:

[{
  "provision.administration.identifier": {
    "_value": {
      "_current": {
        "value": "related_1"
      }
    }
  },
  "provision.administration.organization": {
    "_value": {
      "_current": {
        "value": "organization_name"
      }
    }
  },
  "provision.administration.channel": {
    "_value": {
      "_current": {
        "value": "channel_name"
      }
    }
  },
  "provision.administration.serviceGroup": {
    "_value": {
      "_current": {
        "value": "service_group_name"
      }
    }
  }
}]
  • timeserieData contains info about the timeserie opened by the user
    • config timeserie configuration
    • data timeserie row selected

NOTE: only available when the user opens an entity dashboard template from timeserie table widget

An example:

{
  "config": {
    "identifier": "69281dc43545e97df66c42a1",
    "name": "Battery charge history",
    "timeBucket": 3600,
    "bucketColumn": "bucketEnd",
    "bucketInitColumn": "bucketInit",
    "identifierColumn": "EntityID",
    "retention": 2592000,
    "origin": "2025-11-26T23:00:00Z",
    "context": [
      {
        "path": "provision.device.administrativeState",
        "name": "Administrative state",
        "sort": "true",
        "filter": "YES",
        "type": "string"
      }
    ],
    "columns": [
      {
        "path": "device.powersupply.battery.charge._current.value",
        "name": "Powersupply battery charge Current Value",
        "filter": "NO",
        "type": "number",
        "sort": false,
        "aggregationFunction": "FIRST"
      }
    ]
  },
  "data": {
    "bucketEnd": "2025-12-11T13:00:00+01:00",
    "bucketInit": "2025-12-11T12:00:00+01:00",
    "EntityID": "entity_1",
    "Powersupply battery charge Current Value": 34
  }
}
  • alarmData contains the data of the alarm opened in template
{
  "identifier": "270dd9f9-1396-4660-bb5f-8d8b471e1dcd",
  "name": "activityForbidden",
  "rule": "activityForbidden",
  "description": "Activity detected for an entity with administrative state disabled",
  "severity": "INFORMATIVE",
  "priority": "LOW",
  "organization": "organization_name",
  "channel": "default_channel",
  "entityIdentifier": "A_WORKER_1",
  "subEntityIdentifier": "A_WORKER_1",
  "resourceType": "ENTITY_ASSET",
  "status": "CLOSED",
  "openingDate": "2019-06-27T08:57:36+02:00",
  "closureDate": "2019-06-27T08:57:51+02:00"
}
  • svgDom DOMElement object that contains the root element of the SVG (this object always will be a clean svg element)

If SVG content not filled in configuration you may consider that default viewPort for svg will be: 0 0 100 100 (upgradable)

Every svg will be setted with width and height to 100% and auto respectively

  • callback function used to return data when the api/http petitions are promised. Use “return” if not using promises.

Example:

callback(svgDom);

or

return svgDom;
Final code structure build by the application
async function main(entityData,alarmData,relatedEntities,timeserieData,svgDom,callback) {
  // YOUR CODE HERE WITH RETURN OR CALLBACK
}

Examples


Subsections of Open HMI/Custom Image

Time-based SVG

Code

// Determine shape and color based on current milliseconds
const now = new Date();
const milliseconds = now.getMilliseconds();

// If milliseconds is even, draw a green circle. Else, a crimson rectangle.
const isEven = milliseconds % 2 === 0;
const color = isEven ? 'green' : 'crimson';
const shapeType = isEven ? 'circle' : 'rect';

console.log(`Milliseconds: ${milliseconds}. Drawing ${color} ${shapeType}.`);

try {
    // 1. Clear existing SVG content
    while (svgDom.firstChild) {
        svgDom.removeChild(svgDom.firstChild);
    }

    // 2. Create the new geometric shape
    const shape = document.createElementNS("http://www.w3.org/2000/svg", shapeType);

    if (shapeType === 'circle') {
        shape.setAttribute('cx', '50');
        shape.setAttribute('cy', '50');
        shape.setAttribute('r', '40');
    } else {
        shape.setAttribute('x', '10');
        shape.setAttribute('y', '10');
        shape.setAttribute('width', '80');
        shape.setAttribute('height', '80');
    }

    shape.setAttribute('fill', color);
    shape.setAttribute('stroke', 'black');
    shape.setAttribute('stroke-width', '2');

    // 3. Add text label
    const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
    text.setAttribute('x', '50');
    text.setAttribute('y', '50');
    text.setAttribute('dominant-baseline', 'middle');
    text.setAttribute('text-anchor', 'middle');
    text.setAttribute('fill', 'white');
    text.setAttribute('font-family', 'Arial');
    text.setAttribute('font-size', '12');
    text.textContent = `ms: ${milliseconds}`;

    // 4. Append elements to SVG
    svgDom.appendChild(shape);
    svgDom.appendChild(text);

} catch (err) {
    console.error("Error in Open HMI script:", err);
    
    // Fallback display in SVG
    const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
    text.setAttribute('x', '10');
    text.setAttribute('y', '20');
    text.setAttribute('fill', 'red');
    text.textContent = "Error executing script";
    svgDom.appendChild(text);
}

// Return the modified DOM
return svgDom;