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.TbNodeUtils;
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.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
@ -39,8 +37,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
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.MAX_FETCH_SIZE;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.*;
import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
/**
@ -64,6 +61,7 @@ public class TbGetTelemetryNode implements TbNode {
private long endTsOffset;
private int limit;
private ObjectMapper mapper;
private String fetchMode;
@Override
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());
endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
fetchMode = config.getFetchMode();
mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
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() {
long ts = System.currentTimeMillis();
long startTs = ts - startTsOffset;
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()
.map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE))
.map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
.collect(Collectors.toList());
}
@ -116,7 +119,7 @@ public class TbGetTelemetryNode implements TbNode {
}
for (String key : tsKeyNames) {
if(resultNode.has(key)){
if (resultNode.has(key)) {
msg.getMetaData().putValue(key, resultNode.get(key).toString());
}
}
@ -127,11 +130,11 @@ public class TbGetTelemetryNode implements TbNode {
}
private void processArray(ObjectNode node, TsKvEntry entry) {
if(node.has(entry.getKey())){
if (node.has(entry.getKey())) {
ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
ObjectNode obj = buildNode(entry);
arrayNode.add(obj);
}else {
} else {
ArrayNode arrayNode = mapper.createArrayNode();
ObjectNode obj = buildNode(entry);
arrayNode.add(obj);

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

@ -267,6 +267,14 @@ export default class Subscription {
} else {
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() {

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

@ -18,14 +18,14 @@
<section layout="column" layout-align="start start">
<section layout="row" layout-align="start start">
<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-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker>
mdp-auto-switch="true"></mdp-time-picker>
</section>
<section layout="row" layout-align="start start">
<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-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker>
mdp-auto-switch="true"></mdp-time-picker>
</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.",
"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-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-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": {
"add": "Add alias",
@ -748,7 +753,8 @@
"entity-name": "Entity name",
"details": "Entity details",
"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-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.
*/
import './alarms-table-widget.scss';
import './display-columns-panel.scss';
import './alarm-status-filter-panel.scss';
/* eslint-disable import/no-unresolved, import/default */
import alarmsTableWidgetTemplate from './alarms-table-widget.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 */
@ -45,7 +49,7 @@ function AlarmsTableWidget() {
}
/*@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;
vm.stylesInfo = {};
@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.selectedAlarms = []
vm.alarmSource = null;
vm.alarmSearchStatus = null;
vm.allAlarms = [];
vm.currentAlarm = null;
@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.onPaginate = onPaginate;
vm.onRowClick = onRowClick;
vm.onActionButtonClick = onActionButtonClick;
vm.actionEnabled = actionEnabled;
vm.isCurrent = isCurrent;
vm.openAlarmDetails = openAlarmDetails;
vm.ackAlarms = ackAlarms;
vm.ackAlarm = ackAlarm;
vm.clearAlarms = clearAlarms;
vm.clearAlarm = clearAlarm;
vm.cellStyle = cellStyle;
vm.cellContent = cellContent;
vm.editAlarmStatusFilter = editAlarmStatusFilter;
vm.editColumnsToDisplay = editColumnsToDisplay;
$scope.$watch('vm.ctx', function() {
if (vm.ctx) {
vm.settings = vm.ctx.settings;
@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
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) {
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.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) {
vm.enableSelection = false;
}
@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
function onActionButtonClick($event, alarm, actionDescriptor) {
if ($event) {
$event.stopPropagation();
if (actionDescriptor.details) {
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) {
entityId = alarm.originator;
entityName = alarm.originatorName;
}
function actionEnabled(alarm, actionDescriptor) {
if (actionDescriptor.acknowledge) {
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) {
@ -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) {
if ($event) {
$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) {
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() {
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];
dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
dataKey.display = true;
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 {
table.md-table,
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 {
tr {
td {
@ -51,6 +72,15 @@
width: 36px;
min-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">
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th 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 ng-if="vm.actionCellDescriptors.length"><span>&nbsp</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 class="tb-action-cell" layout="row" layout-align="end center">
<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>
</thead>
<tbody md-body>
<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"
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-bind-html="vm.cellContent(alarm, key)">
</td>
<td md-cell ng-if="vm.displayDetails" 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"
<td md-cell class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}">
<md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)"
aria-label="{{ actionDescriptor.displayName }}"
ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
<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.
*/
import './entities-table-widget.scss';
import './display-columns-panel.scss';
/* eslint-disable import/no-unresolved, import/default */
import entitiesTableWidgetTemplate from './entities-table-widget.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 */
@ -45,7 +47,7 @@ function EntitiesTableWidget() {
}
/*@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;
vm.stylesInfo = {};
@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.cellStyle = cellStyle;
vm.cellContent = cellContent;
vm.editColumnsToDisplay = editColumnsToDisplay;
$scope.$watch('vm.ctx', function() {
if (vm.ctx && vm.ctx.defaultSubscription) {
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() {
vm.stylesInfo = {};
vm.contentsInfo = {};
vm.columnWidth = {};
vm.dataKeys = [];
vm.columns = [];
vm.allEntities = [];
var datasource;
@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
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++ ) {
dataKey = angular.copy(datasource.dataKeys[d]);
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';
vm.columnWidth[dataKey.label] = columnWidth;
dataKey.display = true;
vm.columns.push(dataKey);
}
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 {
table.md-table,
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 {
tr {
td {
@ -51,6 +72,15 @@
width: 36px;
min-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>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
<th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
<th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
<th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</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 class="tb-action-cell" layout="row" layout-align="end center">
<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>
</thead>
<tbody md-body>
<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"
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 md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
<td md-cell flex ng-repeat="key in vm.dataKeys"
ng-style="vm.cellStyle(entity, key)"
ng-bind-html="vm.cellContent(entity, key)">
<td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns"
ng-style="vm.cellStyle(entity, column)"
ng-bind-html="vm.cellContent(entity, column)">
</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',
maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}">

Loading…
Cancel
Save