Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/1068/head
Dima Landiak 8 years ago
parent
commit
7b2a9435a7
  1. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  2. 8
      ui/src/app/api/subscription.js
  3. 8
      ui/src/app/components/datetime-period.tpl.html
  4. 10
      ui/src/app/locale/locale.constant-en_US.json
  5. 31
      ui/src/app/widget/lib/alarm-status-filter-panel.scss
  6. 28
      ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
  7. 190
      ui/src/app/widget/lib/alarms-table-widget.js
  8. 30
      ui/src/app/widget/lib/alarms-table-widget.scss
  9. 40
      ui/src/app/widget/lib/alarms-table-widget.tpl.html
  10. 31
      ui/src/app/widget/lib/display-columns-panel.scss
  11. 24
      ui/src/app/widget/lib/display-columns-panel.tpl.html
  12. 79
      ui/src/app/widget/lib/entities-table-widget.js
  13. 30
      ui/src/app/widget/lib/entities-table-widget.scss
  14. 27
      ui/src/app/widget/lib/entities-table-widget.tpl.html

23
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -26,10 +26,8 @@ import org.thingsboard.rule.engine.api.*;
import org.thingsboard.rule.engine.api.util.DonAsynchron; import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -39,8 +37,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL; import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.*;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
import static org.thingsboard.server.common.data.kv.Aggregation.NONE; import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
/** /**
@ -64,6 +61,7 @@ public class TbGetTelemetryNode implements TbNode {
private long endTsOffset; private long endTsOffset;
private int limit; private int limit;
private ObjectMapper mapper; private ObjectMapper mapper;
private String fetchMode;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@ -72,6 +70,7 @@ public class TbGetTelemetryNode implements TbNode {
startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval()); startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval());
endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval()); endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1; limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
fetchMode = config.getFetchMode();
mapper = new ObjectMapper(); mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false); mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
@ -96,14 +95,18 @@ public class TbGetTelemetryNode implements TbNode {
} }
} }
//TODO: handle direction;
private List<ReadTsKvQuery> buildQueries() { private List<ReadTsKvQuery> buildQueries() {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
long startTs = ts - startTsOffset; long startTs = ts - startTsOffset;
long endTs = ts - endTsOffset; long endTs = ts - endTsOffset;
String orderBy;
if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
orderBy = "ASC";
} else {
orderBy = "DESC";
}
return tsKeyNames.stream() return tsKeyNames.stream()
.map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE)) .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -116,7 +119,7 @@ public class TbGetTelemetryNode implements TbNode {
} }
for (String key : tsKeyNames) { for (String key : tsKeyNames) {
if(resultNode.has(key)){ if (resultNode.has(key)) {
msg.getMetaData().putValue(key, resultNode.get(key).toString()); msg.getMetaData().putValue(key, resultNode.get(key).toString());
} }
} }
@ -127,11 +130,11 @@ public class TbGetTelemetryNode implements TbNode {
} }
private void processArray(ObjectNode node, TsKvEntry entry) { private void processArray(ObjectNode node, TsKvEntry entry) {
if(node.has(entry.getKey())){ if (node.has(entry.getKey())) {
ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey()); ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
ObjectNode obj = buildNode(entry); ObjectNode obj = buildNode(entry);
arrayNode.add(obj); arrayNode.add(obj);
}else { } else {
ArrayNode arrayNode = mapper.createArrayNode(); ArrayNode arrayNode = mapper.createArrayNode();
ObjectNode obj = buildNode(entry); ObjectNode obj = buildNode(entry);
arrayNode.add(obj); arrayNode.add(obj);

8
ui/src/app/api/subscription.js

@ -267,6 +267,14 @@ export default class Subscription {
} else { } else {
this.startWatchingTimewindow(); this.startWatchingTimewindow();
} }
registration = this.ctx.$scope.$watch(function () {
return subscription.alarmSearchStatus;
}, function (newAlarmSearchStatus, prevAlarmSearchStatus) {
if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) {
subscription.update();
}
}, true);
this.registrations.push(registration);
} }
initDataSubscription() { initDataSubscription() {

8
ui/src/app/components/datetime-period.tpl.html

@ -18,14 +18,14 @@
<section layout="column" layout-align="start start"> <section layout="column" layout-align="start start">
<section layout="row" layout-align="start start"> <section layout="row" layout-align="start start">
<mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}" <mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}"
mdp-max-date="maxStartDate"></mdp-date-picker> ></mdp-date-picker>
<mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}" <mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}"
mdp-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker> mdp-auto-switch="true"></mdp-time-picker>
</section> </section>
<section layout="row" layout-align="start start"> <section layout="row" layout-align="start start">
<mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}" <mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}"
mdp-min-date="minEndDate" mdp-max-date="maxEndDate"></mdp-date-picker> ></mdp-date-picker>
<mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}" <mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}"
mdp-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker> mdp-auto-switch="true"></mdp-time-picker>
</section> </section>
</section> </section>

10
ui/src/app/locale/locale.constant-en_US.json

@ -133,8 +133,13 @@
"min-polling-interval-message": "At least 1 sec polling interval is allowed.", "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
"aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }",
"aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?",
"aknowledge-alarm-title": "Acknowledge Alarm",
"aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?",
"clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }",
"clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?" "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?",
"clear-alarm-title": "Clear Alarm",
"clear-alarm-text": "Are you sure you want to clear Alarm?",
"alarm-status-filter": "Alarm Status Filter"
}, },
"alias": { "alias": {
"add": "Add alias", "add": "Add alias",
@ -748,7 +753,8 @@
"entity-name": "Entity name", "entity-name": "Entity name",
"details": "Entity details", "details": "Entity details",
"no-entities-prompt": "No entities found", "no-entities-prompt": "No entities found",
"no-data": "No data to display" "no-data": "No data to display",
"columns-to-display": "Columns to Display"
}, },
"event": { "event": {
"event-type": "Event type", "event-type": "Event type",

31
ui/src/app/widget/lib/alarm-status-filter-panel.scss

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2018 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-status-filter-panel {
min-width: 300px;
overflow: hidden;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
md-content {
overflow: hidden;
background-color: #fff;
}
}

28
ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html

@ -0,0 +1,28 @@
<!--
Copyright © 2016-2018 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 style="height: 100%" flex layout="column" class="md-padding">
<label class="tb-title" translate>alarm.alarm-status-filter</label>
<md-radio-group ng-model="vm.subscription.alarmSearchStatus" class="md-primary">
<md-radio-button ng-value="searchStatus"
aria-label="{{ ('alarm.search-status.' + searchStatus) | translate }}"
class="md-primary md-align-top-left md-radio-interactive" ng-repeat="searchStatus in vm.types.alarmSearchStatus">
{{ ('alarm.search-status.' + searchStatus) | translate }}
</md-radio-button>
</md-radio-group>
</md-content>

190
ui/src/app/widget/lib/alarms-table-widget.js

@ -14,11 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import './alarms-table-widget.scss'; import './alarms-table-widget.scss';
import './display-columns-panel.scss';
import './alarm-status-filter-panel.scss';
/* eslint-disable import/no-unresolved, import/default */ /* eslint-disable import/no-unresolved, import/default */
import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html'; import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html';
import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
@ -45,7 +49,7 @@ function AlarmsTableWidget() {
} }
/*@ngInject*/ /*@ngInject*/
function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) { function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) {
var vm = this; var vm = this;
vm.stylesInfo = {}; vm.stylesInfo = {};
@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.selectedAlarms = [] vm.selectedAlarms = []
vm.alarmSource = null; vm.alarmSource = null;
vm.alarmSearchStatus = null;
vm.allAlarms = []; vm.allAlarms = [];
vm.currentAlarm = null; vm.currentAlarm = null;
@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.onPaginate = onPaginate; vm.onPaginate = onPaginate;
vm.onRowClick = onRowClick; vm.onRowClick = onRowClick;
vm.onActionButtonClick = onActionButtonClick; vm.onActionButtonClick = onActionButtonClick;
vm.actionEnabled = actionEnabled;
vm.isCurrent = isCurrent; vm.isCurrent = isCurrent;
vm.openAlarmDetails = openAlarmDetails; vm.openAlarmDetails = openAlarmDetails;
vm.ackAlarms = ackAlarms; vm.ackAlarms = ackAlarms;
vm.ackAlarm = ackAlarm;
vm.clearAlarms = clearAlarms; vm.clearAlarms = clearAlarms;
vm.clearAlarm = clearAlarm;
vm.cellStyle = cellStyle; vm.cellStyle = cellStyle;
vm.cellContent = cellContent; vm.cellContent = cellContent;
vm.editAlarmStatusFilter = editAlarmStatusFilter;
vm.editColumnsToDisplay = editColumnsToDisplay;
$scope.$watch('vm.ctx', function() { $scope.$watch('vm.ctx', function() {
if (vm.ctx) { if (vm.ctx) {
vm.settings = vm.ctx.settings; vm.settings = vm.ctx.settings;
@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.ctx.widgetActions = [ vm.searchAction ]; vm.ctx.widgetActions = [ vm.searchAction ];
vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
if (vm.displayDetails) {
vm.actionCellDescriptors.push(
{
displayName: $translate.instant('alarm.details'),
icon: 'more_horiz',
details: true
}
);
}
if (vm.allowAcknowledgment) {
vm.actionCellDescriptors.push(
{
displayName: $translate.instant('alarm.acknowledge'),
icon: 'done',
acknowledge: true
}
);
}
if (vm.allowClear) {
vm.actionCellDescriptors.push(
{
displayName: $translate.instant('alarm.clear'),
icon: 'clear',
clear: true
}
);
}
vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton'));
if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle); vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true; vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
if (!vm.allowAcknowledgment && !vm.allowClear) { if (!vm.allowAcknowledgment && !vm.allowClear) {
vm.enableSelection = false; vm.enableSelection = false;
} }
@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
} }
function onActionButtonClick($event, alarm, actionDescriptor) { function onActionButtonClick($event, alarm, actionDescriptor) {
if ($event) { if (actionDescriptor.details) {
$event.stopPropagation(); vm.openAlarmDetails($event, alarm);
} else if (actionDescriptor.acknowledge) {
vm.ackAlarm($event, alarm);
} else if (actionDescriptor.clear) {
vm.clearAlarm($event, alarm);
} else {
if ($event) {
$event.stopPropagation();
}
var entityId;
var entityName;
if (alarm && alarm.originator) {
entityId = alarm.originator;
entityName = alarm.originatorName;
}
vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm});
} }
var entityId; }
var entityName;
if (alarm && alarm.originator) { function actionEnabled(alarm, actionDescriptor) {
entityId = alarm.originator; if (actionDescriptor.acknowledge) {
entityName = alarm.originatorName; return (alarm.status == types.alarmStatus.activeUnack ||
alarm.status == types.alarmStatus.clearedUnack);
} else if (actionDescriptor.clear) {
return (alarm.status == types.alarmStatus.activeAck ||
alarm.status == types.alarmStatus.activeUnack);
} }
vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm }); return true;
} }
function isCurrent(alarm) { function isCurrent(alarm) {
@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
} }
} }
function ackAlarm($event, alarm) {
if ($event) {
$event.stopPropagation();
}
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title($translate.instant('alarm.aknowledge-alarm-title'))
.htmlContent($translate.instant('alarm.aknowledge-alarm-text'))
.ariaLabel($translate.instant('alarm.acknowledge'))
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
alarmService.ackAlarm(alarm.id.id).then(function () {
vm.selectedAlarms = [];
vm.subscription.update();
});
});
}
function clearAlarms($event) { function clearAlarms($event) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
} }
} }
function clearAlarm($event, alarm) {
if ($event) {
$event.stopPropagation();
}
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title($translate.instant('alarm.clear-alarm-title'))
.htmlContent($translate.instant('alarm.clear-alarm-text'))
.ariaLabel($translate.instant('alarm.clear'))
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
alarmService.clearAlarm(alarm.id.id).then(function () {
vm.selectedAlarms = [];
vm.subscription.update();
});
});
}
function updateAlarms(preserveSelections) { function updateAlarms(preserveSelections) {
if (!preserveSelections) { if (!preserveSelections) {
@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
} }
} }
function editAlarmStatusFilter($event) {
var element = angular.element($event.target);
var position = $mdPanel.newPanelPosition()
.relativeTo(element)
.addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
var config = {
attachTo: angular.element($document[0].body),
controller: AlarmStatusFilterPanelController,
controllerAs: 'vm',
templateUrl: alarmStatusFilterPanelTemplate,
panelClass: 'tb-alarm-status-filter-panel',
position: position,
fullscreen: false,
locals: {
'subscription': vm.subscription
},
openFrom: $event,
clickOutsideToClose: true,
escapeToClose: true,
focusOnOpen: false
};
$mdPanel.open(config);
}
function editColumnsToDisplay($event) {
var element = angular.element($event.target);
var position = $mdPanel.newPanelPosition()
.relativeTo(element)
.addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
var config = {
attachTo: angular.element($document[0].body),
controller: DisplayColumnsPanelController,
controllerAs: 'vm',
templateUrl: displayColumnsPanelTemplate,
panelClass: 'tb-display-columns-panel',
position: position,
fullscreen: false,
locals: {
'columns': vm.alarmSource.dataKeys
},
openFrom: $event,
clickOutsideToClose: true,
escapeToClose: true,
focusOnOpen: false
};
$mdPanel.open(config);
}
function updateAlarmSource() { function updateAlarmSource() {
vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle); vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle);
@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
var dataKey = vm.alarmSource.dataKeys[d]; var dataKey = vm.alarmSource.dataKeys[d];
dataKey.title = utils.customTranslation(dataKey.label, dataKey.label); dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
dataKey.display = true;
var keySettings = dataKey.settings; var keySettings = dataKey.settings;
@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
} }
} }
} }
/*@ngInject*/
function DisplayColumnsPanelController(columns) { //eslint-disable-line
var vm = this;
vm.columns = columns;
}
/*@ngInject*/
function AlarmStatusFilterPanelController(subscription, types) { //eslint-disable-line
var vm = this;
vm.types = types;
vm.subscription = subscription;
}

30
ui/src/app/widget/lib/alarms-table-widget.scss

@ -44,6 +44,27 @@
&.tb-data-table { &.tb-data-table {
table.md-table, table.md-table,
table.md-table.md-row-select { table.md-table.md-row-select {
th.md-column {
&.tb-action-cell {
.md-button {
/* stylelint-disable-next-line selector-max-class */
&.md-icon-button {
width: 36px;
height: 36px;
padding: 6px;
margin: 0;
/* stylelint-disable-next-line selector-max-class */
md-icon {
width: 24px;
height: 24px;
font-size: 24px !important;
line-height: 24px !important;
}
}
}
}
}
tbody { tbody {
tr { tr {
td { td {
@ -51,6 +72,15 @@
width: 36px; width: 36px;
min-width: 36px; min-width: 36px;
max-width: 36px; max-width: 36px;
.md-button[disabled] {
&.md-icon-button {
/* stylelint-disable-next-line selector-max-class */
md-icon {
color: rgba(0, 0, 0, .38);
}
}
}
} }
} }
} }

40
ui/src/app/widget/lib/alarms-table-widget.tpl.html

@ -62,33 +62,45 @@
<table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms"> <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms">
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row> <tr md-row>
<th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th> <th ng-if="key.display" md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
<th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th> <th md-column class="tb-action-cell" layout="row" layout-align="end center">
<th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th> <md-button class="md-icon-button"
aria-label="{{'alarm.alarm-status-filter' | translate}}"
ng-click="vm.editAlarmStatusFilter($event)">
<md-icon aria-label="{{'alarm.alarm-status-filter' | translate}}"
class="material-icons">filter_list
</md-icon>
<md-tooltip md-direction="top">
{{'alarm.alarm-status-filter' | translate}}
</md-tooltip>
</md-button>
<md-button class="md-icon-button"
aria-label="{{'entity.columns-to-display' | translate}}"
ng-click="vm.editColumnsToDisplay($event)">
<md-icon aria-label="{{'entity.columns-to-display' | translate}}"
class="material-icons">view_column
</md-icon>
<md-tooltip md-direction="top">
{{'entity.columns-to-display' | translate}}
</md-tooltip>
</md-button>
</th>
</tr> </tr>
</thead> </thead>
<tbody md-body> <tbody md-body>
<tr ng-show="vm.alarms.length" md-row md-select="alarm" <tr ng-show="vm.alarms.length" md-row md-select="alarm"
md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms" md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}"> ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
<td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys" <td ng-if="key.display" md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
ng-style="vm.cellStyle(alarm, key)" ng-style="vm.cellStyle(alarm, key)"
ng-bind-html="vm.cellContent(alarm, key)"> ng-bind-html="vm.cellContent(alarm, key)">
</td> </td>
<td md-cell ng-if="vm.displayDetails" class="tb-action-cell"> <td md-cell class="tb-action-cell"
<md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
ng-click="vm.openAlarmDetails($event, alarm)" ng-disabled="$root.loading">
<md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
<md-tooltip md-direction="top">
{{ 'alarm.details' | translate }}
</md-tooltip>
</md-button>
</td>
<td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px', ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px', maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}"> width: vm.actionCellDescriptors.length*36+'px'}">
<md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors" <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)"
aria-label="{{ actionDescriptor.displayName }}" aria-label="{{ actionDescriptor.displayName }}"
ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading"> ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
<md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon> <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>

31
ui/src/app/widget/lib/display-columns-panel.scss

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2018 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-display-columns-panel {
min-width: 300px;
overflow: hidden;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
md-content {
overflow: hidden;
background-color: #fff;
}
}

24
ui/src/app/widget/lib/display-columns-panel.tpl.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2018 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 style="height: 100%" flex layout="column" class="md-padding">
<label class="tb-title" translate>entity.columns-to-display</label>
<md-checkbox aria-label="{{ 'entity.columns-to-display' | translate }}" ng-repeat="column in vm.columns"
ng-model="column.display">{{ column.title }}
</md-checkbox>
</md-content>

79
ui/src/app/widget/lib/entities-table-widget.js

@ -14,11 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import './entities-table-widget.scss'; import './entities-table-widget.scss';
import './display-columns-panel.scss';
/* eslint-disable import/no-unresolved, import/default */ /* eslint-disable import/no-unresolved, import/default */
import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html'; import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
//import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html'; //import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
@ -45,7 +47,7 @@ function EntitiesTableWidget() {
} }
/*@ngInject*/ /*@ngInject*/
function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) { function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) {
var vm = this; var vm = this;
vm.stylesInfo = {}; vm.stylesInfo = {};
@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.cellStyle = cellStyle; vm.cellStyle = cellStyle;
vm.cellContent = cellContent; vm.cellContent = cellContent;
vm.editColumnsToDisplay = editColumnsToDisplay;
$scope.$watch('vm.ctx', function() { $scope.$watch('vm.ctx', function() {
if (vm.ctx && vm.ctx.defaultSubscription) { if (vm.ctx && vm.ctx.defaultSubscription) {
vm.settings = vm.ctx.settings; vm.settings = vm.ctx.settings;
@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
} }
} }
function editColumnsToDisplay($event) {
var element = angular.element($event.target);
var position = $mdPanel.newPanelPosition()
.relativeTo(element)
.addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
var config = {
attachTo: angular.element($document[0].body),
controller: DisplayColumnsPanelController,
controllerAs: 'vm',
templateUrl: displayColumnsPanelTemplate,
panelClass: 'tb-display-columns-panel',
position: position,
fullscreen: false,
locals: {
'columns': vm.columns
},
openFrom: $event,
clickOutsideToClose: true,
escapeToClose: true,
focusOnOpen: false
};
$mdPanel.open(config);
}
function updateDatasources() { function updateDatasources() {
vm.stylesInfo = {}; vm.stylesInfo = {};
vm.contentsInfo = {}; vm.contentsInfo = {};
vm.columnWidth = {}; vm.columnWidth = {};
vm.dataKeys = []; vm.dataKeys = [];
vm.columns = [];
vm.allEntities = []; vm.allEntities = [];
var datasource; var datasource;
@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle); vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
if (vm.displayEntityName) {
vm.columns.push(
{
name: 'entityName',
label: 'entityName',
title: vm.entityNameColumnTitle,
display: true
}
);
vm.contentsInfo['entityName'] = {
useCellContentFunction: false
};
vm.stylesInfo['entityName'] = {
useCellStyleFunction: false
};
vm.columnWidth['entityName'] = '0px';
}
if (vm.displayEntityType) {
vm.columns.push(
{
name: 'entityType',
label: 'entityType',
title: $translate.instant('entity.entity-type'),
display: true
}
);
vm.contentsInfo['entityType'] = {
useCellContentFunction: false
};
vm.stylesInfo['entityType'] = {
useCellStyleFunction: false
};
vm.columnWidth['entityType'] = '0px';
}
for (var d = 0; d < datasource.dataKeys.length; d++ ) { for (var d = 0; d < datasource.dataKeys.length; d++ ) {
dataKey = angular.copy(datasource.dataKeys[d]); dataKey = angular.copy(datasource.dataKeys[d]);
if (dataKey.type == types.dataKeyType.function) { if (dataKey.type == types.dataKeyType.function) {
@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
vm.columnWidth[dataKey.label] = columnWidth; vm.columnWidth[dataKey.label] = columnWidth;
dataKey.display = true;
vm.columns.push(dataKey);
} }
for (var i=0;i<vm.datasources.length;i++) { for (var i=0;i<vm.datasources.length;i++) {
@ -511,4 +579,11 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
} }
} }
/*@ngInject*/
function DisplayColumnsPanelController(columns) { //eslint-disable-line
var vm = this;
vm.columns = columns;
}

30
ui/src/app/widget/lib/entities-table-widget.scss

@ -44,6 +44,27 @@
&.tb-data-table { &.tb-data-table {
table.md-table, table.md-table,
table.md-table.md-row-select { table.md-table.md-row-select {
th.md-column {
&.tb-action-cell {
.md-button {
/* stylelint-disable-next-line selector-max-class */
&.md-icon-button {
width: 36px;
height: 36px;
padding: 6px;
margin: 0;
/* stylelint-disable-next-line selector-max-class */
md-icon {
width: 24px;
height: 24px;
font-size: 24px !important;
line-height: 24px !important;
}
}
}
}
}
tbody { tbody {
tr { tr {
td { td {
@ -51,6 +72,15 @@
width: 36px; width: 36px;
min-width: 36px; min-width: 36px;
max-width: 36px; max-width: 36px;
.md-button[disabled] {
&.md-icon-button {
/* stylelint-disable-next-line selector-max-class */
md-icon {
color: rgba(0, 0, 0, .38);
}
}
}
} }
} }
} }

27
ui/src/app/widget/lib/entities-table-widget.tpl.html

@ -41,23 +41,30 @@
<table md-table> <table md-table>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row> <tr md-row>
<th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th> <th ng-if="column.display" md-column md-order-by="{{ column.name }}" ng-repeat="column in vm.columns"><span>{{ column.title }}</span></th>
<th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th> <th md-column class="tb-action-cell" layout="row" layout-align="end center">
<th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th> <md-button class="md-icon-button"
<th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th> aria-label="{{'entity.columns-to-display' | translate}}"
ng-click="vm.editColumnsToDisplay($event)">
<md-icon aria-label="{{'entity.columns-to-display' | translate}}"
class="material-icons">view_column
</md-icon>
<md-tooltip md-direction="top">
{{'entity.columns-to-display' | translate}}
</md-tooltip>
</md-button>
</th>
</tr> </tr>
</thead> </thead>
<tbody md-body> <tbody md-body>
<tr ng-show="vm.entities.length" md-row md-select="entity" <tr ng-show="vm.entities.length" md-row md-select="entity"
md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities" md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}"> ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
<td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td> <td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns"
<td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td> ng-style="vm.cellStyle(entity, column)"
<td md-cell flex ng-repeat="key in vm.dataKeys" ng-bind-html="vm.cellContent(entity, column)">
ng-style="vm.cellStyle(entity, key)"
ng-bind-html="vm.cellContent(entity, key)">
</td> </td>
<td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell" <td md-cell class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px', ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px', maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}"> width: vm.actionCellDescriptors.length*36+'px'}">

Loading…
Cancel
Save