This is the 8th article of a Dashboard development series. You can check all the articles by clicking here
In this article, we are going to build the next component:
This component shows the total consumed energy as a bar chart and the kW/ton ratio as a line chart. Let's start by creating the alarmList.js
and alarmList.html
files in the components/alarmList
directory:
alarmList.js
/**
* @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
* @author Luis Güette
*/
define(['angular', 'require'], (angular, require) => {
'use strict';
class AlarmListController {
static get $$ngIsClass() {
return true;
}
static get $inject() {
return [];
}
constructor() {
}
$onInit() {
}
}
return {
bindings: {
},
controller: AlarmListController,
templateUrl: require.toUrl('./alarmList.html')
};
});
alarmList.html
<h1>Alarm List</h1>
Then, we need to import it in hvac.js
module:
hvac.js
/**
* @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
* @author Luis Güette
*/
define([
'angular',
'require',
'./pages/overview/overview.js',
'./components/map/map.js',
'./components/selectedUnitCard/selectedUnitCard.js',
'./components/unitsTable/unitsTable.js',
'./components/kpiIndicators/kpiIndicators.js',
'./components/energyChart/energyChart.js',
'./components/alarmList/alarmList.js',
'./services/unit.js'
], (
angular,
require,
overview,
map,
selectedUnitCard,
unitsTable,
kpiIndicators,
energyChart,
alarmList,
unitService
) => {
'use strict';
const hvacModule = angular
.module('hvacModule', ['maUiApp'])
.component('hvacOverview', overview)
.component('hvacMap', map)
.component('hvacSelectedUnitCard', selectedUnitCard)
.component('hvacUnitsTable', unitsTable)
.component('hvacKpiIndicators', kpiIndicators)
.component('hvacAlarmList', alarmList)
.component('hvacEnergyChart', energyChart)
.factory('hvacUnit', unitService);
hvacModule.config([
'maUiMenuProvider',
(maUiMenuProvider) => {
maUiMenuProvider.registerMenuItems([
{
name: 'ui.overview',
url: '/overview',
menuIcon: 'map',
template: '<hvac-overview></hvac-overview>',
menuText: 'Overview',
weight: 100,
params: {
dateBar: {
rollupControls: true
}
}
},
]);
}
]);
return hvacModule;
}); // define
Let's call the hvac-energy-chart
component in the overview.html
file:
overview.html
<div class="left-container" layout="row" layout-wrap layout-align="space-between start">
<div flex="100" flex-gt-sm="50" flex-gt-md="60">
<hvac-kpi-indicators units-count="$ctrl.units.length"></hvac-kpi-indicators>
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Active Alarms</p>
</md-card-header>
<md-card-content>
<hvac-map units="$ctrl.units" on-select-unit="$ctrl.onSelectUnit(unit)"></hvac-map>
</md-card-content>
</md-card>
<hvac-selected-unit-card ng-if="$ctrl.selectedUnit" unit="$ctrl.selectedUnit"></hvac-selected-unit-card>
</div>
<div class="right-container" flex="100" flex-gt-sm="50" flex-gt-md="40">
<div layout="row" layout-wrap layout-align="space-between stretch">
<div class="energy-chart-container" flex="100" flex-gt-md="60">
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Energy vs kW/ton ratio</p>
</md-card-header>
<md-card-content>
<hvac-energy-chart></hvac-energy-chart>
</md-card-content>
</md-card>
</div>
<div class="alarms-list-container" flex="100" flex-gt-md="40">
<md-card flex="100">
<md-card-header>
<p md-colors="::{color: 'accent'}">Active Alarms</p>
</md-card-header>
<md-card-content>
<hvac-alarm-list></hvac-alarm-list>
</md-card-content>
</md-card>
</div>
</div>
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Units</p>
</md-card-header>
<md-card-content>
<hvac-units-table units="$ctrl.units"></hvac-units-table>
</md-card-content>
</md-card>
</div>
</div>
Reload the page, and you should see something like this:
First, we need to get the events count, we will do this in alarmList.js
:
alarmList.js
/**
* @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
* @author Luis Güette
*/
define(['angular', 'require'], (angular, require) => {
'use strict';
const EVENT_KEYS = {
'NONE': 'none',
'INFORMATION': 'information',
'IMPORTANT': 'important',
'WARNING': 'warning',
'URGENT': 'urgent',
'CRITICAL': 'critical',
'SAFETY': 'safety',
}
class AlarmListController {
static get $$ngIsClass() {
return true;
}
static get $inject() {
return ['maEvents', '$scope'];
}
constructor(Events, $scope) {
this.Events = Events;
this.$scope = $scope;
}
$onInit() {
this.getEvents();
}
getEvents() {
this.Events
.buildQuery()
.or()
.eq('active', true)
.limit(1000)
.query()
.then(events => {
this.eventsCount = this.mapEvents(events);
this.subscribeToEvents();
});
}
mapEvents(events) {
return events.reduce((result, event) => {
const shortName = EVENT_KEYS[event.alarmLevel];
if (Object.keys(EVENT_KEYS).includes(event.alarmLevel)) {
result[shortName] += 1;
}
return result;
}, {
none: 0,
information: 0,
important: 0,
warning: 0,
urgent: 0,
critical: 0,
safety: 0,
});
}
subscribeToEvents () {
this.Events.notificationManager.subscribe((event, mangoEvent) => {
if (mangoEvent.id < 0) {
return;
}
const shortName = EVENT_KEYS[mangoEvent.alarmLevel];
if (event.name === 'RAISED' && mangoEvent.active) {
this.eventsCount[shortName] += 1;
}
if (event.name === 'RETURN_TO_NORMAL' && !mangoEvent.active) {
this.eventsCount[shortName] -= 1;
}
}, this.$scope, ['RAISED', 'RETURN_TO_NORMAL']);
}
}
return {
bindings: {
},
controller: AlarmListController,
templateUrl: require.toUrl('./alarmList.html')
};
});
- With
getEvents()
we get all the active events and calculate the counts by the alarm level. - Then we call
subscribeToEvents()
, which will update the counts in real time if we have new alarms or if any alarm has returned to normal state
Now, we update the alarmList.html
and the hvac.css
to show this data:
alarmList.html
<div>
<div class="header" layout="row" layout-align="space-between center">
<span>Alarm Level</span>
<span>Count</span>
</div>
<div class="body">
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-life-safety"></md-icon>
Safety
</span>
<span ng-bind="$ctrl.eventsCount.safety"></span>
</div>
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-critical"></md-icon>
Critical
</span>
<span ng-bind="$ctrl.eventsCount.critical"></span>
</div>
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-urgent"></md-icon>
Urgent
</span>
<span ng-bind="$ctrl.eventsCount.urgent"></span>
</div>
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-warning"></md-icon>
Warning
</span>
<span ng-bind="$ctrl.eventsCount.warning"></span>
</div>
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-important"></md-icon>
Important
</span>
<span ng-bind="$ctrl.eventsCount.important"></span>
</div>
<div layout="row" layout-align="space-between center">
<span>
<md-icon class="fa fa-flag fa-lg ma-alarm-flag ma-alarm-level-information"></md-icon>
Information
</span>
<span ng-bind="$ctrl.eventsCount.information"></span>
</div>
</div>
</div>
- We use the same flag colors that Mango use by default
hvac.css
...
hvac-alarm-list div .header {
padding: 0.5rem 0;
border-bottom: var(--ma-primary-700) solid 1px;
color: var(--ma-accent);
font-size: 1.25rem;
text-transform: uppercase;
font-weight: 700;
}
hvac-alarm-list div .body {
margin-top: 0.5rem;
color: var(--ma-primary-700);
font-size: 1.5rem;
text-transform: uppercase;
}
hvac-alarm-list div .body div span:last-child {
font-weight: 700;
}
...
Finally, let's update the overview.html
to add a link to the events page:
overview.html
<div class="left-container" layout="row" layout-wrap layout-align="space-between start">
<div flex="100" flex-gt-sm="50" flex-gt-md="60">
<hvac-kpi-indicators units-count="$ctrl.units.length"></hvac-kpi-indicators>
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Active Alarms</p>
</md-card-header>
<md-card-content>
<hvac-map units="$ctrl.units" on-select-unit="$ctrl.onSelectUnit(unit)"></hvac-map>
</md-card-content>
</md-card>
<hvac-selected-unit-card ng-if="$ctrl.selectedUnit" unit="$ctrl.selectedUnit"></hvac-selected-unit-card>
</div>
<div class="right-container" flex="100" flex-gt-sm="50" flex-gt-md="40">
<div layout="row" layout-wrap layout-align="space-between stretch">
<div class="energy-chart-container" flex="100" flex-gt-md="60">
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Energy vs kW/ton ratio</p>
</md-card-header>
<md-card-content>
<hvac-energy-chart></hvac-energy-chart>
</md-card-content>
</md-card>
</div>
<div class="alarms-list-container" flex="100" flex-gt-md="40">
<md-card flex="100">
<md-card-header layout="row" layout-align="space-between center">
<p md-colors="::{color: 'accent'}">Active Alarms</p>
<a ui-sref="ui.events">
<md-icon>visibility</md-icon>
</a>
</md-card-header>
<md-card-content>
<hvac-alarm-list></hvac-alarm-list>
</md-card-content>
</md-card>
</div>
</div>
<md-card>
<md-card-header>
<p md-colors="::{color: 'accent'}">Units</p>
</md-card-header>
<md-card-content>
<hvac-units-table units="$ctrl.units"></hvac-units-table>
</md-card-content>
</md-card>
</div>
</div>
and let's add some styles to hvac.css
:
hvac.css
...
hvac-overview .right-container .alarms-list-container a {
border-bottom-color: transparent;
}
hvac-overview .right-container .alarms-list-container a md-icon {
color: var(--ma-accent);
}
...
And we ar done! We have built an overview dashboard. You can download de project files from here