TB-63: Add new 'Alarms' tab in entity details.

This commit is contained in:
Igor Kulikov 2017-06-13 12:33:34 +03:00
parent 351684a6cd
commit 3e0ef11678
41 changed files with 937 additions and 48 deletions

View File

@ -57,6 +57,19 @@ public class AlarmController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET)
@ResponseBody
public AlarmInfo getAlarmInfoById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
checkParameter("alarmId", strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmInfoId(alarmId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm", method = RequestMethod.POST)
@ResponseBody

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink;
@ -351,6 +352,17 @@ public abstract class BaseController {
}
}
AlarmInfo checkAlarmInfoId(AlarmId alarmId) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);
AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(alarmId).get();
checkAlarm(alarmInfo);
return alarmInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected void checkAlarm(Alarm alarm) throws ThingsboardException {
checkNotNull(alarm);
checkTenantId(alarm.getTenantId());

View File

@ -55,6 +55,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName {
public Alarm(Alarm alarm) {
super(alarm.getId());
this.createdTime = alarm.getCreatedTime();
this.tenantId = alarm.getTenantId();
this.type = alarm.getType();
this.originator = alarm.getOriginator();

View File

@ -35,6 +35,8 @@ public interface AlarmService {
ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId);
ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query);
}

View File

@ -198,6 +198,23 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.findAlarmByIdAsync(alarmId.getId());
}
@Override
public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId) {
log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return Futures.transform(alarmDao.findAlarmByIdAsync(alarmId.getId()),
(AsyncFunction<Alarm, AlarmInfo>) alarm1 -> {
AlarmInfo alarmInfo = new AlarmInfo(alarm1);
return Futures.transform(
entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function<String, AlarmInfo>)
originatorName -> {
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}
);
});
}
@Override
public ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query) {
ListenableFuture<List<AlarmInfo>> alarms = alarmDao.findAlarms(query);

View File

@ -0,0 +1,134 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/theme/github';
import beautify from 'js-beautify';
import './alarm-details-dialog.scss';
const js_beautify = beautify.js;
/*@ngInject*/
export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) {
var vm = this;
vm.alarmId = alarmId;
vm.types = types;
vm.alarm = null;
vm.alarmUpdated = false;
showingCallback.onShowing = function(scope, element) {
updateEditorSize(element);
}
vm.alarmDetailsOptions = {
useWrapMode: false,
mode: 'json',
showGutter: false,
showPrintMargin: false,
theme: 'github',
advanced: {
enableSnippets: false,
enableBasicAutocompletion: false,
enableLiveAutocompletion: false
},
onLoad: function (_ace) {
vm.editor = _ace;
}
};
vm.close = close;
vm.acknowledge = acknowledge;
vm.clear = clear;
loadAlarm();
function updateEditorSize(element) {
var newWidth = 600;
var newHeight = 200;
angular.element('#tb-alarm-details', element).height(newHeight.toString() + "px")
.width(newWidth.toString() + "px");
vm.editor.resize();
}
function loadAlarm() {
alarmService.getAlarmInfo(vm.alarmId).then(
function success(alarm) {
vm.alarm = alarm;
loadAlarmFields();
},
function fail() {
vm.alarm = null;
}
);
}
function loadAlarmFields() {
vm.createdTime = $filter('date')(vm.alarm.createdTime, 'yyyy-MM-dd HH:mm:ss');
vm.startTime = null;
if (vm.alarm.startTs) {
vm.startTime = $filter('date')(vm.alarm.startTs, 'yyyy-MM-dd HH:mm:ss');
}
vm.endTime = null;
if (vm.alarm.endTs) {
vm.endTime = $filter('date')(vm.alarm.endTs, 'yyyy-MM-dd HH:mm:ss');
}
vm.ackTime = null;
if (vm.alarm.ackTs) {
vm.ackTime = $filter('date')(vm.alarm.ackTs, 'yyyy-MM-dd HH:mm:ss')
}
vm.clearTime = null;
if (vm.alarm.clearTs) {
vm.clearTime = $filter('date')(vm.alarm.clearTs, 'yyyy-MM-dd HH:mm:ss');
}
vm.alarmSeverity = $translate.instant(types.alarmSeverity[vm.alarm.severity].name);
vm.alarmStatus = $translate.instant('alarm.display-status.' + vm.alarm.status);
vm.alarmDetails = null;
if (vm.alarm.details) {
vm.alarmDetails = angular.toJson(vm.alarm.details);
vm.alarmDetails = js_beautify(vm.alarmDetails, {indent_size: 4});
}
}
function acknowledge () {
alarmService.ackAlarm(vm.alarmId).then(
function success() {
vm.alarmUpdated = true;
loadAlarm();
}
);
}
function clear () {
alarmService.clearAlarm(vm.alarmId).then(
function success() {
vm.alarmUpdated = true;
loadAlarm();
}
);
}
function close () {
$mdDialog.hide(vm.alarmUpdated ? vm.alarm : null);
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-alarm-details-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
#tb-alarm-details {
min-width: 600px;
min-height: 200px;
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,107 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'alarm.alarm-details' | translate }}">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>alarm.alarm-details</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.close()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content" layout="column">
<div layout="row">
<md-input-container class="md-block">
<label translate>alarm.created-time</label>
<input ng-model="vm.createdTime" readonly>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alarm.originator</label>
<input ng-model="vm.alarm.originatorName" readonly>
</md-input-container>
</div>
<div layout="row" ng-if="vm.startTime || vm.endTime">
<md-input-container ng-if="vm.startTime" flex class="md-block">
<label translate>alarm.start-time</label>
<input ng-model="vm.startTime" readonly>
</md-input-container>
<md-input-container ng-if="vm.endTime" flex class="md-block">
<label translate>alarm.end-time</label>
<input ng-model="vm.endTime" readonly>
</md-input-container>
<span flex ng-if="!vm.startTime || !vm.endTime"></span>
</div>
<div layout="row" ng-if="vm.ackTime || vm.clearTime">
<md-input-container ng-if="vm.ackTime" flex class="md-block">
<label translate>alarm.ack-time</label>
<input ng-model="vm.ackTime" readonly>
</md-input-container>
<md-input-container ng-if="vm.clearTime" flex class="md-block">
<label translate>alarm.clear-time</label>
<input ng-model="vm.clearTime" readonly>
</md-input-container>
<span flex ng-if="!vm.ackTime || !vm.clearTime"></span>
</div>
<div layout="row">
<md-input-container flex class="md-block">
<label translate>alarm.type</label>
<input ng-model="vm.alarm.type" readonly>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alarm.severity</label>
<input class="tb-severity" ng-class="vm.types.alarmSeverity[vm.alarm.severity].class"
ng-model="vm.alarmSeverity" readonly>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alarm.status</label>
<input ng-model="vm.alarmStatus" readonly>
</md-input-container>
</div>
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>alarm.details</div>
<div flex class="tb-alarm-details-panel" layout="column">
<div flex id="tb-alarm-details" readonly
ui-ace="vm.alarmDetailsOptions"
ng-model="vm.alarmDetails">
</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||
vm.alarm.status==vm.types.alarmStatus.clearedUnack"
class="md-raised md-primary"
ng-disabled="loading"
ng-click="vm.acknowledge()"
style="margin-right:20px;">{{ 'alarm.acknowledge' |
translate }}
</md-button>
<md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||
vm.alarm.status==vm.types.alarmStatus.activeUnack"
class="md-raised md-primary"
ng-disabled="loading"
ng-click="vm.clear()">{{ 'alarm.clear' |
translate }}
</md-button>
<span flex></span>
<md-button ng-disabled="loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |
translate }}
</md-button>
</md-dialog-actions>
</md-dialog>

View File

@ -0,0 +1,39 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import alarmHeaderTemplate from './alarm-header.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function AlarmHeaderDirective($compile, $templateCache) {
var linker = function (scope, element) {
var template = $templateCache.get(alarmHeaderTemplate);
element.html(template);
$compile(element.contents())(scope);
}
return {
restrict: "A",
replace: false,
link: linker,
scope: false
};
}

View File

@ -15,6 +15,9 @@
limitations under the License.
-->
<div translate class="tb-cell" flex="30">event.event-time</div>
<div translate class="tb-cell" flex="20">event.server</div>
<div translate class="tb-cell" flex="20">event.alarm</div>
<div translate class="tb-cell" flex="30">alarm.created-time</div>
<div translate class="tb-cell" flex="15">alarm.originator</div>
<div translate class="tb-cell" flex="20">alarm.type</div>
<div translate class="tb-cell" flex="15">alarm.severity</div>
<div translate class="tb-cell" flex="20">alarm.status</div>
<div translate class="tb-cell" flex="15">alarm.details</div>

View File

@ -0,0 +1,67 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import alarmDetailsDialogTemplate from './alarm-details-dialog.tpl.html';
import alarmRowTemplate from './alarm-row.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function AlarmRowDirective($compile, $templateCache, types, $mdDialog, $document) {
var linker = function (scope, element, attrs) {
var template = $templateCache.get(alarmRowTemplate);
element.html(template);
scope.alarm = attrs.alarm;
scope.types = types;
scope.showAlarmDetails = function($event) {
var onShowingCallback = {
onShowing: function(){}
}
$mdDialog.show({
controller: 'AlarmDetailsDialogController',
controllerAs: 'vm',
templateUrl: alarmDetailsDialogTemplate,
locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback},
parent: angular.element($document[0].body),
targetEvent: $event,
fullscreen: true,
skipHide: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
}).then(function (alarm) {
if (alarm) {
scope.alarm = alarm;
}
});
}
$compile(element.contents())(scope);
}
return {
restrict: "A",
replace: false,
link: linker,
scope: false
};
}

View File

@ -15,14 +15,19 @@
limitations under the License.
-->
<div class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
<div class="tb-cell" flex="20">{{event.body.server}}</div>
<div class="tb-cell" flex="20">
<md-button ng-if="event.body.body" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.body, 'event.alarm')"
<div class="tb-cell" flex="30">{{alarm.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
<div class="tb-cell" flex="15">{{alarm.originatorName}}</div>
<div class="tb-cell" flex="20">{{alarm.type}}</div>
<div class="tb-cell tb-severity" flex="15" ng-class="types.alarmSeverity[alarm.severity].class">
{{ alarm ? (types.alarmSeverity[alarm.severity].name | translate) : '' }}
</div>
<div class="tb-cell" flex="20">{{ alarm ? (('alarm.display-status.' + alarm.status) | translate) : '' }}</div>
<div class="tb-cell" flex="15">
<md-button class="md-icon-button md-primary"
ng-click="showAlarmDetails($event)"
aria-label="{{ 'action.view' | translate }}">
<md-tooltip md-direction="top">
{{ 'action.view' | translate }}
{{ 'alarm.details' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.view' | translate }}"
class="material-icons">

View File

@ -0,0 +1,211 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './alarm.scss';
/* eslint-disable import/no-unresolved, import/default */
import alarmTableTemplate from './alarm-table.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function AlarmTableDirective($compile, $templateCache, $rootScope, types, alarmService) {
var linker = function (scope, element) {
var template = $templateCache.get(alarmTableTemplate);
element.html(template);
scope.types = types;
scope.alarmSearchStatus = types.alarmSearchStatus.any;
var pageSize = 20;
var startTime = 0;
var endTime = 0;
scope.timewindow = {
history: {
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
}
}
scope.topIndex = 0;
scope.theAlarms = {
getItemAtIndex: function (index) {
if (index > scope.alarms.data.length) {
scope.theAlarms.fetchMoreItems_(index);
return null;
}
var item = scope.alarms.data[index];
if (item) {
item.indexNumber = index + 1;
}
return item;
},
getLength: function () {
if (scope.alarms.hasNext) {
return scope.alarms.data.length + scope.alarms.nextPageLink.limit;
} else {
return scope.alarms.data.length;
}
},
fetchMoreItems_: function () {
if (scope.alarms.hasNext && !scope.alarms.pending) {
if (scope.entityType && scope.entityId && scope.alarmSearchStatus) {
var promise = alarmService.getAlarms(scope.entityType, scope.entityId,
scope.alarms.nextPageLink, scope.alarmSearchStatus, null, true, false);
if (promise) {
scope.alarms.pending = true;
promise.then(
function success(alarms) {
scope.alarms.data = scope.alarms.data.concat(alarms.data);
scope.alarms.nextPageLink = alarms.nextPageLink;
scope.alarms.hasNext = alarms.hasNext;
if (scope.alarms.hasNext) {
scope.alarms.nextPageLink.limit = pageSize;
}
scope.alarms.pending = false;
},
function fail() {
scope.alarms.hasNext = false;
scope.alarms.pending = false;
});
} else {
scope.alarms.hasNext = false;
}
} else {
scope.alarms.hasNext = false;
}
}
}
};
scope.$watch("entityId", function(newVal, prevVal) {
if (newVal && !angular.equals(newVal, prevVal)) {
resetFilter();
reload();
}
});
function destroyWatchers() {
if (scope.alarmSearchStatusWatchHandle) {
scope.alarmSearchStatusWatchHandle();
scope.alarmSearchStatusWatchHandle = null;
}
if (scope.timewindowWatchHandle) {
scope.timewindowWatchHandle();
scope.timewindowWatchHandle = null;
}
}
function initWatchers() {
scope.alarmSearchStatusWatchHandle = scope.$watch("alarmSearchStatus", function(newVal, prevVal) {
if (newVal && !angular.equals(newVal, prevVal)) {
reload();
}
});
scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) {
if (newVal && !angular.equals(newVal, prevVal)) {
reload();
}
}, true);
}
function resetFilter() {
destroyWatchers();
scope.timewindow = {
history: {
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
}
};
scope.alarmSearchStatus = types.alarmSearchStatus.any;
initWatchers();
}
function updateTimeWindowRange () {
if (scope.timewindow.history.timewindowMs) {
var currentTime = (new Date).getTime();
startTime = currentTime - scope.timewindow.history.timewindowMs;
endTime = currentTime;
} else {
startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;
endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;
}
}
function reload () {
scope.topIndex = 0;
scope.selected = [];
updateTimeWindowRange();
scope.alarms = {
data: [],
nextPageLink: {
limit: pageSize,
startTime: startTime,
endTime: endTime
},
hasNext: true,
pending: false
};
scope.theAlarms.getItemAtIndex(pageSize);
}
scope.noData = function() {
return scope.alarms.data.length == 0 && !scope.alarms.hasNext;
}
scope.hasData = function() {
return scope.alarms.data.length > 0;
}
scope.loading = function() {
return $rootScope.loading;
}
scope.hasScroll = function() {
var repeatContainer = scope.repeatContainer[0];
if (repeatContainer) {
var scrollElement = repeatContainer.children[0];
if (scrollElement) {
return scrollElement.scrollHeight > scrollElement.clientHeight;
}
}
return false;
}
reload();
initWatchers();
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
entityType: '=',
entityId: '='
}
};
}

View File

@ -0,0 +1,49 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-content flex class="md-padding tb-absolute-fill" layout="column">
<section layout="row">
<md-input-container class="md-block" style="width: 200px;">
<label translate>alarm.alarm-status</label>
<md-select ng-model="alarmSearchStatus" ng-disabled="loading()">
<md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
{{ ('alarm.search-status.' + searchStatus) | translate }}
</md-option>
</md-select>
</md-input-container>
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
</section>
<div flex layout="column" class="tb-alarm-container md-whiteframe-z1">
<md-list flex layout="column" class="tb-alarm-table">
<md-list class="tb-row tb-header" layout="row" tb-alarm-header>
</md-list>
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"
ng-show="loading()"></md-progress-linear>
<md-divider></md-divider>
<span translate layout-align="center center"
style="margin-top: 25px;"
class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span>
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
<md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
<md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}">
</md-list>
<md-divider flex></md-divider>
</md-list-item>
</md-virtual-repeat-container>
</md-list>
</div>
</md-content>

View File

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-alarm-container {
overflow-x: auto;
}
md-list.tb-alarm-table {
padding: 0px;
min-width: 700px;
md-list-item {
padding: 0px;
}
.tb-row {
height: 48px;
padding: 0px;
overflow: hidden;
}
.tb-row:hover {
background-color: #EEEEEE;
}
.tb-header:hover {
background: none;
}
.tb-header {
.tb-cell {
color: rgba(0,0,0,.54);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
background: none;
}
}
.tb-cell {
padding: 0 24px;
margin: auto 0;
color: rgba(0,0,0,.87);
font-size: 13px;
vertical-align: middle;
text-align: left;
overflow: hidden;
.md-button {
padding: 0;
margin: 0;
}
}
.tb-cell.tb-number {
text-align: right;
}
}
#tb-alarm-content {
min-width: 400px;
min-height: 50px;
width: 100%;
height: 100%;
}

27
ui/src/app/alarm/index.js Normal file
View File

@ -0,0 +1,27 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import AlarmDetailsDialogController from './alarm-details-dialog.controller';
import AlarmHeaderDirective from './alarm-header.directive';
import AlarmRowDirective from './alarm-row.directive';
import AlarmTableDirective from './alarm-table.directive';
export default angular.module('thingsboard.alarm', [])
.controller('AlarmDetailsDialogController', AlarmDetailsDialogController)
.directive('tbAlarmHeader', AlarmHeaderDirective)
.directive('tbAlarmRow', AlarmRowDirective)
.directive('tbAlarmTable', AlarmTableDirective)
.name;

View File

@ -21,6 +21,7 @@ export default angular.module('thingsboard.api.alarm', [])
function AlarmService($http, $q, $interval, $filter) {
var service = {
getAlarm: getAlarm,
getAlarmInfo: getAlarmInfo,
saveAlarm: saveAlarm,
ackAlarm: ackAlarm,
clearAlarm: clearAlarm,
@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) {
return deferred.promise;
}
function getAlarmInfo(alarmId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/alarm/info/' + alarmId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function saveAlarm(alarm, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/alarm';

View File

@ -48,11 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.asset"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.asset"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">

View File

@ -15,7 +15,6 @@
*/
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardEvent from '../event';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiAsset from '../api/asset.service';
import thingsboardApiCustomer from '../api/customer.service';
@ -29,7 +28,6 @@ import AssetDirective from './asset.directive';
export default angular.module('thingsboard.asset', [
uiRouter,
thingsboardGrid,
thingsboardEvent,
thingsboardApiUser,
thingsboardApiAsset,
thingsboardApiCustomer

View File

@ -72,6 +72,28 @@ export default angular.module('thingsboard.types', [])
ack: "ACK",
unack: "UNACK"
},
alarmSeverity: {
"CRITICAL": {
name: "alarm.severity-critical",
class: "tb-critical"
},
"MAJOR": {
name: "alarm.severity-major",
class: "tb-major"
},
"MINOR": {
name: "alarm.severity-minor",
class: "tb-minor"
},
"WARNING": {
name: "alarm.severity-warning",
class: "tb-warning"
},
"INDETERMINATE": {
name: "alarm.severity-indeterminate",
class: "tb-indeterminate"
}
},
aliasFilterType: {
entityList: {
value: 'entityList',
@ -215,10 +237,6 @@ export default angular.module('thingsboard.types', [])
manages: "Manages"
},
eventType: {
alarm: {
value: "ALARM",
name: "event.type-alarm"
},
error: {
value: "ERROR",
name: "event.type-error"

View File

@ -48,11 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.customer"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.customer"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">

View File

@ -49,11 +49,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.device"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.device"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">

View File

@ -15,7 +15,6 @@
*/
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardEvent from '../event';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiDevice from '../api/device.service';
import thingsboardApiCustomer from '../api/customer.service';
@ -30,7 +29,6 @@ import DeviceDirective from './device.directive';
export default angular.module('thingsboard.device', [
uiRouter,
thingsboardGrid,
thingsboardEvent,
thingsboardApiUser,
thingsboardApiDevice,
thingsboardApiCustomer

View File

@ -62,7 +62,7 @@ export default function EventContentDialogController($mdDialog, content, title,
}
newWidth = 8 * maxLineLength + 16;
}
$('#tb-content', element).height(newHeight.toString() + "px")
$('#tb-event-content', element).height(newHeight.toString() + "px")
.width(newWidth.toString() + "px");
vm.editor.resize();
}

View File

@ -27,7 +27,7 @@
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
<div flex id="tb-content" readonly
<div flex id="tb-event-content" readonly
ui-ace="vm.contentOptions"
ng-model="vm.content">
</div>

View File

@ -18,7 +18,6 @@
import eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html';
import eventHeaderStatsTemplate from './event-header-stats.tpl.html';
import eventHeaderErrorTemplate from './event-header-error.tpl.html';
import eventHeaderAlarmTemplate from './event-header-alarm.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -39,9 +38,6 @@ export default function EventHeaderDirective($compile, $templateCache, types) {
case types.eventType.error.value:
template = eventHeaderErrorTemplate;
break;
case types.eventType.alarm.value:
template = eventHeaderAlarmTemplate;
break;
}
return $templateCache.get(template);
}

View File

@ -20,7 +20,6 @@ import eventErrorDialogTemplate from './event-content-dialog.tpl.html';
import eventRowLcEventTemplate from './event-row-lc-event.tpl.html';
import eventRowStatsTemplate from './event-row-stats.tpl.html';
import eventRowErrorTemplate from './event-row-error.tpl.html';
import eventRowAlarmTemplate from './event-row-alarm.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -41,9 +40,6 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
case types.eventType.error.value:
template = eventRowErrorTemplate;
break;
case types.eventType.alarm.value:
template = eventRowAlarmTemplate;
break;
}
return $templateCache.get(template);
}

View File

@ -27,7 +27,7 @@
</md-input-container>
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
</section>
<md-list flex layout="column" class="md-whiteframe-z1 tb-table">
<md-list flex layout="column" class="md-whiteframe-z1 tb-event-table">
<md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}">
</md-list>
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
md-list.tb-table {
md-list.tb-event-table {
padding: 0px;
md-list-item {
@ -64,7 +64,7 @@ md-list.tb-table {
}
#tb-content {
#tb-event-content {
min-width: 400px;
min-height: 50px;
width: 100%;

View File

@ -32,6 +32,8 @@ import thingsboardDashboardAutocomplete from '../components/dashboard-autocomple
import thingsboardUserMenu from './user-menu.directive';
import thingsboardEntity from '../entity';
import thingsboardEvent from '../event';
import thingsboardAlarm from '../alarm';
import thingsboardTenant from '../tenant';
import thingsboardCustomer from '../customer';
import thingsboardUser from '../user';
@ -61,6 +63,8 @@ export default angular.module('thingsboard.home', [
thingsboardHomeLinks,
thingsboardUserMenu,
thingsboardEntity,
thingsboardEvent,
thingsboardAlarm,
thingsboardTenant,
thingsboardCustomer,
thingsboardUser,

View File

@ -411,7 +411,6 @@
},
"event": {
"event-type": "Tipo de evento",
"type-alarm": "Alarma",
"type-error": "Error",
"type-lc-event": "Ciclo de vida",
"type-stats": "Estadísticas",

View File

@ -378,7 +378,6 @@ export default function addLocaleKorean(locales) {
},
"event": {
"event-type": "이벤트 타입",
"type-alarm": "알람",
"type-error": "에러",
"type-lc-event": "주기적 이벤트",
"type-stats": "통계",

View File

@ -411,7 +411,6 @@ export default function addLocaleRussian(locales) {
},
"event": {
"event-type": "Тип события",
"type-alarm": "Аварийное оповещение",
"type-error": "Ошибка",
"type-lc-event": "Событие жизненного цикла",
"type-stats": "Статистика",

View File

@ -411,7 +411,6 @@ export default function addLocaleChinese(locales) {
},
"event" : {
"event-type": "事件类型",
"type-alarm": "报警",
"type-error": "错误",
"type-lc-event": "生命周期事件",
"type-stats": "类型统计",

View File

@ -108,9 +108,43 @@ export default angular.module('thingsboard.locale', [])
},
"alarm": {
"alarm": "Alarm",
"alarms": "Alarms",
"select-alarm": "Select alarm",
"no-alarms-matching": "No alarms matching '{{entity}}' were found.",
"alarm-required": "Alarm is required"
"alarm-required": "Alarm is required",
"alarm-status": "Alarm status",
"search-status": {
"ANY": "Any",
"ACTIVE": "Active",
"CLEARED": "Cleared",
"ACK": "Acknowledged",
"UNACK": "Unacknowledged"
},
"display-status": {
"ACTIVE_UNACK": "Active Unacknowledged",
"ACTIVE_ACK": "Active Acknowledged",
"CLEARED_UNACK": "Cleared Unacknowledged",
"CLEARED_ACK": "Cleared Acknowledged"
},
"no-alarms-prompt": "No alarms found",
"created-time": "Created time",
"type": "Type",
"severity": "Severity",
"originator": "Originator",
"details": "Details",
"status": "Status",
"alarm-details": "Alarm details",
"start-time": "Start time",
"end-time": "End time",
"ack-time": "Acknowledged time",
"clear-time": "Cleared time",
"severity-critical": "Critical",
"severity-major": "Major",
"severity-minor": "Minor",
"severity-warning": "Warning",
"severity-indeterminate": "Indeterminate",
"acknowledge": "Acknowledge",
"clear": "Clear"
},
"alias": {
"add": "Add alias",
@ -647,7 +681,6 @@ export default angular.module('thingsboard.locale', [])
},
"event": {
"event-type": "Event type",
"type-alarm": "Alarm",
"type-error": "Error",
"type-lc-event": "Lifecycle event",
"type-stats": "Statistics",

View File

@ -16,7 +16,6 @@
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardJsonForm from '../components/json-form.directive';
import thingsboardEvent from '../event';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
@ -28,7 +27,6 @@ export default angular.module('thingsboard.plugin', [
uiRouter,
thingsboardGrid,
thingsboardJsonForm,
thingsboardEvent,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor
])

View File

@ -48,12 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.plugin"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'plugin.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.plugin"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.lcEvent.value}}"
disabled-event-types="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.lcEvent.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">

View File

@ -17,7 +17,6 @@ import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardPluginSelect from '../components/plugin-select.directive';
import thingsboardComponent from '../component';
import thingsboardEvent from '../event';
import thingsboardApiRule from '../api/rule.service';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
@ -31,7 +30,6 @@ export default angular.module('thingsboard.rule', [
thingsboardGrid,
thingsboardPluginSelect,
thingsboardComponent,
thingsboardEvent,
thingsboardApiRule,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor

View File

@ -48,12 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.rule"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'rule.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.rule"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.lcEvent.value}}"
disabled-event-types="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.lcEvent.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">

View File

@ -46,11 +46,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.tenant"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'tenant.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.tenant"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.types.id.nullUid"
default-event-type="{{vm.types.eventType.alarm.value}}">
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">

View File

@ -309,6 +309,24 @@ pre.tb-highlight {
}
}
.tb-severity {
font-weight: bold;
&.tb-critical {
color: red !important;
}
&.tb-major {
color: orange !important;
}
&.tb-minor {
color: #ffca3d !important;
}
&.tb-warning {
color: #abab00 !important;
}
&.tb-indeterminate {
color: green !important;
}
}
/***********************
* Flow