Commit 90ef91e3a1d5ab1d018b746004a6a6619c785d65

Authored by Igor Kulikov
1 parent a741a59e

UI: Implement Asset/Device type management.

Showing 52 changed files with 515 additions and 2756 deletions
... ... @@ -31,7 +31,8 @@ function AssetService($http, $q, customerService, userService) {
31 31 getTenantAssets: getTenantAssets,
32 32 getCustomerAssets: getCustomerAssets,
33 33 findByQuery: findByQuery,
34   - fetchAssetsByNameFilter: fetchAssetsByNameFilter
  34 + fetchAssetsByNameFilter: fetchAssetsByNameFilter,
  35 + getAssetTypes: getAssetTypes
35 36 }
36 37
37 38 return service;
... ... @@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) {
152 153 return deferred.promise;
153 154 }
154 155
155   - function getTenantAssets(pageLink, applyCustomersInfo, config) {
  156 + function getTenantAssets(pageLink, applyCustomersInfo, config, type) {
156 157 var deferred = $q.defer();
157 158 var url = '/api/tenant/assets?limit=' + pageLink.limit;
158 159 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) {
164 165 if (angular.isDefined(pageLink.textOffset)) {
165 166 url += '&textOffset=' + pageLink.textOffset;
166 167 }
  168 + if (angular.isDefined(type) && type.length) {
  169 + url += '&type=' + type;
  170 + }
167 171 $http.get(url, config).then(function success(response) {
168 172 if (applyCustomersInfo) {
169 173 customerService.applyAssignedCustomersInfo(response.data.data).then(
... ... @@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) {
184 188 return deferred.promise;
185 189 }
186 190
187   - function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) {
  191 + function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config, type) {
188 192 var deferred = $q.defer();
189 193 var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
190 194 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) {
196 200 if (angular.isDefined(pageLink.textOffset)) {
197 201 url += '&textOffset=' + pageLink.textOffset;
198 202 }
  203 + if (angular.isDefined(type) && type.length) {
  204 + url += '&type=' + type;
  205 + }
199 206 $http.get(url, config).then(function success(response) {
200 207 if (applyCustomersInfo) {
201 208 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
... ... @@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) {
258 265 return deferred.promise;
259 266 }
260 267
  268 + function getAssetTypes() {
  269 + var deferred = $q.defer();
  270 + var url = '/api/asset/types';
  271 + $http.get(url).then(function success(response) {
  272 + deferred.resolve(response.data);
  273 + }, function fail() {
  274 + deferred.reject();
  275 + });
  276 + return deferred.promise;
  277 + }
  278 +
261 279 }
... ...
... ... @@ -41,12 +41,13 @@ function DeviceService($http, $q, attributeService, customerService, types) {
41 41 deleteDeviceAttributes: deleteDeviceAttributes,
42 42 sendOneWayRpcCommand: sendOneWayRpcCommand,
43 43 sendTwoWayRpcCommand: sendTwoWayRpcCommand,
44   - findByQuery: findByQuery
  44 + findByQuery: findByQuery,
  45 + getDeviceTypes: getDeviceTypes
45 46 }
46 47
47 48 return service;
48 49
49   - function getTenantDevices(pageLink, applyCustomersInfo, config) {
  50 + function getTenantDevices(pageLink, applyCustomersInfo, config, type) {
50 51 var deferred = $q.defer();
51 52 var url = '/api/tenant/devices?limit=' + pageLink.limit;
52 53 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -58,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
58 59 if (angular.isDefined(pageLink.textOffset)) {
59 60 url += '&textOffset=' + pageLink.textOffset;
60 61 }
  62 + if (angular.isDefined(type) && type.length) {
  63 + url += '&type=' + type;
  64 + }
61 65 $http.get(url, config).then(function success(response) {
62 66 if (applyCustomersInfo) {
63 67 customerService.applyAssignedCustomersInfo(response.data.data).then(
... ... @@ -78,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) {
78 82 return deferred.promise;
79 83 }
80 84
81   - function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
  85 + function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config, type) {
82 86 var deferred = $q.defer();
83 87 var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
84 88 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -90,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
90 94 if (angular.isDefined(pageLink.textOffset)) {
91 95 url += '&textOffset=' + pageLink.textOffset;
92 96 }
  97 + if (angular.isDefined(type) && type.length) {
  98 + url += '&type=' + type;
  99 + }
93 100 $http.get(url, config).then(function success(response) {
94 101 if (applyCustomersInfo) {
95 102 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
... ... @@ -286,4 +293,15 @@ function DeviceService($http, $q, attributeService, customerService, types) {
286 293 return deferred.promise;
287 294 }
288 295
  296 + function getDeviceTypes() {
  297 + var deferred = $q.defer();
  298 + var url = '/api/device/types';
  299 + $http.get(url).then(function success(response) {
  300 + deferred.resolve(response.data);
  301 + }, function fail() {
  302 + deferred.reject();
  303 + });
  304 + return deferred.promise;
  305 + }
  306 +
289 307 }
... ...
... ... @@ -160,10 +160,6 @@ export default function AppConfig($provide,
160 160 indigoTheme();
161 161 }
162 162
163   - $mdThemingProvider.theme('tb-search-input', 'default')
164   - .primaryPalette('tb-primary')
165   - .backgroundPalette('tb-primary');
166   -
167 163 $mdThemingProvider.setDefaultTheme('default');
168 164 //$mdThemingProvider.alwaysWatchTheme(true);
169 165 }
... ...
... ... @@ -15,5 +15,8 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
19   -<div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
  18 +<div flex layout="column" style="margin-top: -10px;">
  19 + <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
  20 + <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
  21 + <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
  22 +</div>
... ...
... ... @@ -56,13 +56,13 @@
56 56 <div translate ng-message="required">asset.name-required</div>
57 57 </div>
58 58 </md-input-container>
59   - <md-input-container class="md-block">
60   - <label translate>asset.type</label>
61   - <input required name="type" ng-model="asset.type">
62   - <div ng-messages="theForm.name.$error">
63   - <div translate ng-message="required">asset.type-required</div>
64   - </div>
65   - </md-input-container>
  59 + <tb-entity-subtype-autocomplete
  60 + ng-disabled="loading || !isEdit"
  61 + tb-required="true"
  62 + the-form="theForm"
  63 + ng-model="asset.type"
  64 + entity-type="types.entityType.asset">
  65 + </tb-entity-subtype-autocomplete>
66 66 <md-input-container class="md-block">
67 67 <label translate>asset.description</label>
68 68 <textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
... ...
... ... @@ -47,7 +47,8 @@ export function AssetCardController(types) {
47 47
48 48
49 49 /*@ngInject*/
50   -export function AssetController(userService, assetService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
  50 +export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams,
  51 + $document, $mdDialog, $q, $translate, types) {
51 52
52 53 var customerId = $stateParams.customerId;
53 54
... ... @@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta
129 130 }
130 131
131 132 if (vm.assetsScope === 'tenant') {
132   - fetchAssetsFunction = function (pageLink) {
133   - return assetService.getTenantAssets(pageLink, true);
  133 + fetchAssetsFunction = function (pageLink, assetType) {
  134 + return assetService.getTenantAssets(pageLink, true, null, assetType);
134 135 };
135 136 deleteAssetFunction = function (assetId) {
136 137 return assetService.deleteAsset(assetId);
... ... @@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta
229 230
230 231
231 232 } else if (vm.assetsScope === 'customer' || vm.assetsScope === 'customer_user') {
232   - fetchAssetsFunction = function (pageLink) {
233   - return assetService.getCustomerAssets(customerId, pageLink, true);
  233 + fetchAssetsFunction = function (pageLink, assetType) {
  234 + return assetService.getCustomerAssets(customerId, pageLink, true, null, assetType);
234 235 };
235 236 deleteAssetFunction = function (assetId) {
236 237 return assetService.unassignAssetFromCustomer(assetId);
... ... @@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta
333 334 var deferred = $q.defer();
334 335 assetService.saveAsset(asset).then(
335 336 function success(savedAsset) {
  337 + $rootScope.$broadcast('assetSaved');
336 338 var assets = [ savedAsset ];
337 339 customerService.applyAssignedCustomersInfo(assets).then(
338 340 function success(items) {
... ...
... ... @@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla
25 25 var template = $templateCache.get(assetFieldsetTemplate);
26 26 element.html(template);
27 27
  28 + scope.types = types;
28 29 scope.isAssignedToCustomer = false;
29 30 scope.isPublic = false;
30 31 scope.assignedCustomer = null;
... ...
... ... @@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function AssetRoutes($stateProvider) {
  23 +export default function AssetRoutes($stateProvider, types) {
24 24 $stateProvider
25 25 .state('home.assets', {
26 26 url: '/assets',
... ... @@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) {
37 37 data: {
38 38 assetsType: 'tenant',
39 39 searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.asset,
40 42 pageTitle: 'asset.assets'
41 43 },
42 44 ncyBreadcrumb: {
... ... @@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) {
58 60 data: {
59 61 assetsType: 'customer',
60 62 searchEnabled: true,
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.asset,
61 65 pageTitle: 'customer.assets'
62 66 },
63 67 ncyBreadcrumb: {
... ...
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -import './datasource-device.scss';
17   -
18   -import 'md-color-picker';
19   -import tinycolor from 'tinycolor2';
20   -import $ from 'jquery';
21   -import thingsboardTypes from '../common/types.constant';
22   -import thingsboardDatakeyConfigDialog from './datakey-config-dialog.controller';
23   -import thingsboardTruncate from './truncate.filter';
24   -
25   -/* eslint-disable import/no-unresolved, import/default */
26   -
27   -import datasourceDeviceTemplate from './datasource-device.tpl.html';
28   -import datakeyConfigDialogTemplate from './datakey-config-dialog.tpl.html';
29   -
30   -/* eslint-enable import/no-unresolved, import/default */
31   -
32   -/* eslint-disable angular/angularelement */
33   -
34   -export default angular.module('thingsboard.directives.datasourceDevice', [thingsboardTruncate, thingsboardTypes, thingsboardDatakeyConfigDialog])
35   - .directive('tbDatasourceDevice', DatasourceDevice)
36   - .name;
37   -
38   -/*@ngInject*/
39   -function DatasourceDevice($compile, $templateCache, $q, $mdDialog, $window, $document, $mdColorPicker, $mdConstant, types) {
40   -
41   - var linker = function (scope, element, attrs, ngModelCtrl) {
42   - var template = $templateCache.get(datasourceDeviceTemplate);
43   - element.html(template);
44   -
45   - scope.ngModelCtrl = ngModelCtrl;
46   - scope.types = types;
47   -
48   - scope.selectedTimeseriesDataKey = null;
49   - scope.timeseriesDataKeySearchText = null;
50   -
51   - scope.selectedAttributeDataKey = null;
52   - scope.attributeDataKeySearchText = null;
53   -
54   - scope.updateValidity = function () {
55   - if (ngModelCtrl.$viewValue) {
56   - var value = ngModelCtrl.$viewValue;
57   - var dataValid = angular.isDefined(value) && value != null;
58   - ngModelCtrl.$setValidity('deviceData', dataValid);
59   - if (dataValid) {
60   - ngModelCtrl.$setValidity('deviceAlias',
61   - angular.isDefined(value.deviceAliasId) &&
62   - value.deviceAliasId != null);
63   - ngModelCtrl.$setValidity('deviceKeys',
64   - angular.isDefined(value.dataKeys) &&
65   - value.dataKeys != null &&
66   - value.dataKeys.length > 0);
67   - }
68   - }
69   - };
70   -
71   - scope.$watch('deviceAlias', function () {
72   - if (ngModelCtrl.$viewValue) {
73   - if (scope.deviceAlias) {
74   - ngModelCtrl.$viewValue.deviceAliasId = scope.deviceAlias.id;
75   - } else {
76   - ngModelCtrl.$viewValue.deviceAliasId = null;
77   - }
78   - scope.updateValidity();
79   - scope.selectedDeviceAliasChange();
80   - }
81   - });
82   -
83   - scope.$watch('timeseriesDataKeys', function () {
84   - if (ngModelCtrl.$viewValue) {
85   - var dataKeys = [];
86   - dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
87   - dataKeys = dataKeys.concat(scope.attributeDataKeys);
88   - ngModelCtrl.$viewValue.dataKeys = dataKeys;
89   - scope.updateValidity();
90   - }
91   - }, true);
92   -
93   - scope.$watch('attributeDataKeys', function () {
94   - if (ngModelCtrl.$viewValue) {
95   - var dataKeys = [];
96   - dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
97   - dataKeys = dataKeys.concat(scope.attributeDataKeys);
98   - ngModelCtrl.$viewValue.dataKeys = dataKeys;
99   - scope.updateValidity();
100   - }
101   - }, true);
102   -
103   - ngModelCtrl.$render = function () {
104   - if (ngModelCtrl.$viewValue) {
105   - var deviceAliasId = ngModelCtrl.$viewValue.deviceAliasId;
106   - if (scope.deviceAliases[deviceAliasId]) {
107   - scope.deviceAlias = {id: deviceAliasId, alias: scope.deviceAliases[deviceAliasId].alias,
108   - deviceId: scope.deviceAliases[deviceAliasId].deviceId};
109   - } else {
110   - scope.deviceAlias = null;
111   - }
112   - var timeseriesDataKeys = [];
113   - var attributeDataKeys = [];
114   - for (var d in ngModelCtrl.$viewValue.dataKeys) {
115   - var dataKey = ngModelCtrl.$viewValue.dataKeys[d];
116   - if (dataKey.type === types.dataKeyType.timeseries) {
117   - timeseriesDataKeys.push(dataKey);
118   - } else if (dataKey.type === types.dataKeyType.attribute) {
119   - attributeDataKeys.push(dataKey);
120   - }
121   - }
122   - scope.timeseriesDataKeys = timeseriesDataKeys;
123   - scope.attributeDataKeys = attributeDataKeys;
124   - }
125   - };
126   -
127   - scope.textIsNotEmpty = function(text) {
128   - return (text && text != null && text.length > 0) ? true : false;
129   - }
130   -
131   - scope.selectedDeviceAliasChange = function () {
132   - if (!scope.timeseriesDataKeySearchText || scope.timeseriesDataKeySearchText === '') {
133   - scope.timeseriesDataKeySearchText = scope.timeseriesDataKeySearchText === '' ? null : '';
134   - }
135   - if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') {
136   - scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : '';
137   - }
138   - };
139   -
140   - scope.transformTimeseriesDataKeyChip = function (chip) {
141   - return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
142   - };
143   -
144   - scope.transformAttributeDataKeyChip = function (chip) {
145   - return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
146   - };
147   -
148   - scope.showColorPicker = function (event, dataKey) {
149   - $mdColorPicker.show({
150   - value: dataKey.color,
151   - defaultValue: '#fff',
152   - random: tinycolor.random(),
153   - clickOutsideToClose: false,
154   - hasBackdrop: false,
155   - skipHide: true,
156   - preserveScope: false,
157   -
158   - mdColorAlphaChannel: true,
159   - mdColorSpectrum: true,
160   - mdColorSliders: true,
161   - mdColorGenericPalette: false,
162   - mdColorMaterialPalette: true,
163   - mdColorHistory: false,
164   - mdColorDefaultTab: 2,
165   -
166   - $event: event
167   -
168   - }).then(function (color) {
169   - dataKey.color = color;
170   - ngModelCtrl.$setDirty();
171   - });
172   - }
173   -
174   - scope.editDataKey = function (event, dataKey, index) {
175   -
176   - $mdDialog.show({
177   - controller: 'DatakeyConfigDialogController',
178   - controllerAs: 'vm',
179   - templateUrl: datakeyConfigDialogTemplate,
180   - locals: {
181   - dataKey: angular.copy(dataKey),
182   - dataKeySettingsSchema: scope.datakeySettingsSchema,
183   - deviceAlias: scope.deviceAlias,
184   - deviceAliases: scope.deviceAliases
185   - },
186   - parent: angular.element($document[0].body),
187   - fullscreen: true,
188   - targetEvent: event,
189   - skipHide: true,
190   - onComplete: function () {
191   - var w = angular.element($window);
192   - w.triggerHandler('resize');
193   - }
194   - }).then(function (dataKey) {
195   - if (dataKey.type === types.dataKeyType.timeseries) {
196   - scope.timeseriesDataKeys[index] = dataKey;
197   - } else if (dataKey.type === types.dataKeyType.attribute) {
198   - scope.attributeDataKeys[index] = dataKey;
199   - }
200   - ngModelCtrl.$setDirty();
201   - }, function () {
202   - });
203   - };
204   -
205   - scope.dataKeysSearch = function (searchText, type) {
206   - if (scope.deviceAlias) {
207   - var deferred = $q.defer();
208   - scope.fetchDeviceKeys({deviceAliasId: scope.deviceAlias.id, query: searchText, type: type})
209   - .then(function (dataKeys) {
210   - deferred.resolve(dataKeys);
211   - }, function (e) {
212   - deferred.reject(e);
213   - });
214   - return deferred.promise;
215   - } else {
216   - return $q.when([]);
217   - }
218   - };
219   -
220   - scope.createKey = function (event, chipsId) {
221   - var chipsChild = $(chipsId, element)[0].firstElementChild;
222   - var el = angular.element(chipsChild);
223   - var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
224   - event.preventDefault();
225   - event.stopPropagation();
226   - el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
227   - el.scope().$mdChipsCtrl.resetChipBuffer();
228   - }
229   -
230   - $compile(element.contents())(scope);
231   - }
232   -
233   - return {
234   - restrict: "E",
235   - require: "^ngModel",
236   - scope: {
237   - widgetType: '=',
238   - deviceAliases: '=',
239   - datakeySettingsSchema: '=',
240   - generateDataKey: '&',
241   - fetchDeviceKeys: '&',
242   - onCreateDeviceAlias: '&'
243   - },
244   - link: linker
245   - };
246   -}
247   -
248   -/* eslint-enable angular/angularelement */
1   -/**
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -@import '../../scss/constants';
17   -
18   -.tb-device-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete {
19   - .tb-not-found {
20   - display: block;
21   - line-height: 1.5;
22   - height: 48px;
23   - .tb-no-entries {
24   - line-height: 48px;
25   - }
26   - }
27   - li {
28   - height: auto !important;
29   - white-space: normal !important;
30   - }
31   -}
32   -
33   -tb-datasource-device {
34   - @media (min-width: $layout-breakpoint-gt-sm) {
35   - padding-left: 4px;
36   - padding-right: 4px;
37   - }
38   - tb-device-alias-select {
39   - @media (min-width: $layout-breakpoint-gt-sm) {
40   - width: 200px;
41   - max-width: 200px;
42   - }
43   - }
44   -}
\ No newline at end of file
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
19   - <tb-device-alias-select
20   - tb-required="true"
21   - device-aliases="deviceAliases"
22   - ng-model="deviceAlias"
23   - on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
24   - </tb-device-alias-select>
25   - <section flex layout='column'>
26   - <section flex layout='column' layout-align="center" style="padding-left: 4px;">
27   - <md-chips flex
28   - id="timeseries_datakey_chips"
29   - ng-required="true"
30   - ng-model="timeseriesDataKeys" md-autocomplete-snap
31   - md-transform-chip="transformTimeseriesDataKeyChip($chip)"
32   - md-require-match="false">
33   - <md-autocomplete
34   - md-no-cache="true"
35   - id="timeseries_datakey"
36   - md-selected-item="selectedTimeseriesDataKey"
37   - md-search-text="timeseriesDataKeySearchText"
38   - md-items="item in dataKeysSearch(timeseriesDataKeySearchText, types.dataKeyType.timeseries)"
39   - md-item-text="item.name"
40   - md-min-length="0"
41   - placeholder="{{'datakey.timeseries' | translate }}"
42   - md-menu-class="tb-timeseries-datakey-autocomplete">
43   - <span md-highlight-text="timeseriesDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
44   - <md-not-found>
45   - <div class="tb-not-found">
46   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(timeseriesDataKeySearchText)">
47   - <span translate>device.no-keys-found</span>
48   - </div>
49   - <div ng-if="textIsNotEmpty(timeseriesDataKeySearchText)">
50   - <span translate translate-values='{ key: "{{timeseriesDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
51   - <span>
52   - <a translate ng-click="createKey($event, '#timeseries_datakey_chips')">device.create-new-key</a>
53   - </span>
54   - </div>
55   - </div>
56   - </md-not-found>
57   - </md-autocomplete>
58   - <md-chip-template>
59   - <div layout="row" layout-align="start center" class="tb-attribute-chip">
60   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
61   - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
62   - </div>
63   - <div layout="row" flex>
64   - <div class="tb-chip-label">
65   - {{$chip.label}}
66   - </div>
67   - <div class="tb-chip-separator">: </div>
68   - <div class="tb-chip-label">
69   - <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
70   - <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
71   - </div>
72   - </div>
73   - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
74   - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
75   - </md-button>
76   - </div>
77   - </md-chip-template>
78   - </md-chips>
79   - <md-chips flex ng-if="widgetType === types.widgetType.latest.value"
80   - id="attribute_datakey_chips"
81   - ng-required="true"
82   - ng-model="attributeDataKeys" md-autocomplete-snap
83   - md-transform-chip="transformAttributeDataKeyChip($chip)"
84   - md-require-match="false">
85   - <md-autocomplete
86   - md-no-cache="true"
87   - id="attribute_datakey"
88   - md-selected-item="selectedAttributeDataKey"
89   - md-search-text="attributeDataKeySearchText"
90   - md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
91   - md-item-text="item.name"
92   - md-min-length="0"
93   - placeholder="{{'datakey.attributes' | translate }}"
94   - md-menu-class="tb-attribute-datakey-autocomplete">
95   - <span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
96   - <md-not-found>
97   - <div class="tb-not-found">
98   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(attributeDataKeySearchText)">
99   - <span translate>device.no-keys-found</span>
100   - </div>
101   - <div ng-if="textIsNotEmpty(attributeDataKeySearchText)">
102   - <span translate translate-values='{ key: "{{attributeDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
103   - <span>
104   - <a translate ng-click="createKey($event, '#attribute_datakey_chips')">device.create-new-key</a>
105   - </span>
106   - </div>
107   - </div>
108   - </md-not-found>
109   - </md-autocomplete>
110   - <md-chip-template>
111   - <div layout="row" layout-align="start center" class="tb-attribute-chip">
112   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
113   - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
114   - </div>
115   - <div layout="row" flex>
116   - <div class="tb-chip-label">
117   - {{$chip.label}}
118   - </div>
119   - <div class="tb-chip-separator">: </div>
120   - <div class="tb-chip-label">
121   - <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
122   - <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
123   - </div>
124   - </div>
125   - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
126   - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
127   - </md-button>
128   - </div>
129   - </md-chip-template>
130   - </md-chips>
131   - </section>
132   - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
133   - <div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div>
134   - <div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div>
135   - </div>
136   - </section>
137   -</section>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -import $ from 'jquery';
17   -
18   -import './device-alias-select.scss';
19   -
20   -/* eslint-disable import/no-unresolved, import/default */
21   -
22   -import deviceAliasSelectTemplate from './device-alias-select.tpl.html';
23   -
24   -/* eslint-enable import/no-unresolved, import/default */
25   -
26   -
27   -/* eslint-disable angular/angularelement */
28   -
29   -export default angular.module('thingsboard.directives.deviceAliasSelect', [])
30   - .directive('tbDeviceAliasSelect', DeviceAliasSelect)
31   - .name;
32   -
33   -/*@ngInject*/
34   -function DeviceAliasSelect($compile, $templateCache, $mdConstant) {
35   -
36   - var linker = function (scope, element, attrs, ngModelCtrl) {
37   - var template = $templateCache.get(deviceAliasSelectTemplate);
38   - element.html(template);
39   -
40   - scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
41   -
42   - scope.ngModelCtrl = ngModelCtrl;
43   - scope.deviceAliasList = [];
44   - scope.deviceAlias = null;
45   -
46   - scope.updateValidity = function () {
47   - var value = ngModelCtrl.$viewValue;
48   - var valid = angular.isDefined(value) && value != null || !scope.tbRequired;
49   - ngModelCtrl.$setValidity('deviceAlias', valid);
50   - };
51   -
52   - scope.$watch('deviceAliases', function () {
53   - scope.deviceAliasList = [];
54   - for (var aliasId in scope.deviceAliases) {
55   - var deviceAlias = {id: aliasId, alias: scope.deviceAliases[aliasId].alias, deviceId: scope.deviceAliases[aliasId].deviceId};
56   - scope.deviceAliasList.push(deviceAlias);
57   - }
58   - }, true);
59   -
60   - scope.$watch('deviceAlias', function () {
61   - scope.updateView();
62   - });
63   -
64   - scope.deviceAliasSearch = function (deviceAliasSearchText) {
65   - return deviceAliasSearchText ? scope.deviceAliasList.filter(
66   - scope.createFilterForDeviceAlias(deviceAliasSearchText)) : scope.deviceAliasList;
67   - };
68   -
69   - scope.createFilterForDeviceAlias = function (query) {
70   - var lowercaseQuery = angular.lowercase(query);
71   - return function filterFn(deviceAlias) {
72   - return (angular.lowercase(deviceAlias.alias).indexOf(lowercaseQuery) === 0);
73   - };
74   - };
75   -
76   - scope.updateView = function () {
77   - ngModelCtrl.$setViewValue(scope.deviceAlias);
78   - scope.updateValidity();
79   - }
80   -
81   - ngModelCtrl.$render = function () {
82   - if (ngModelCtrl.$viewValue) {
83   - scope.deviceAlias = ngModelCtrl.$viewValue;
84   - }
85   - }
86   -
87   - scope.textIsNotEmpty = function(text) {
88   - return (text && text != null && text.length > 0) ? true : false;
89   - }
90   -
91   - scope.deviceAliasEnter = function($event) {
92   - if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
93   - $event.preventDefault();
94   - if (!scope.deviceAlias) {
95   - var found = scope.deviceAliasSearch(scope.deviceAliasSearchText);
96   - found = found.length > 0;
97   - if (!found) {
98   - scope.createDeviceAlias($event, scope.deviceAliasSearchText);
99   - }
100   - }
101   - }
102   - }
103   -
104   - scope.createDeviceAlias = function (event, alias) {
105   - var autoChild = $('#device-autocomplete', element)[0].firstElementChild;
106   - var el = angular.element(autoChild);
107   - el.scope().$mdAutocompleteCtrl.hidden = true;
108   - el.scope().$mdAutocompleteCtrl.hasNotFound = false;
109   - event.preventDefault();
110   - var promise = scope.onCreateDeviceAlias({event: event, alias: alias});
111   - if (promise) {
112   - promise.then(
113   - function success(newAlias) {
114   - el.scope().$mdAutocompleteCtrl.hasNotFound = true;
115   - if (newAlias) {
116   - scope.deviceAliasList.push(newAlias);
117   - scope.deviceAlias = newAlias;
118   - }
119   - },
120   - function fail() {
121   - el.scope().$mdAutocompleteCtrl.hasNotFound = true;
122   - }
123   - );
124   - } else {
125   - el.scope().$mdAutocompleteCtrl.hasNotFound = true;
126   - }
127   - };
128   -
129   - $compile(element.contents())(scope);
130   - }
131   -
132   - return {
133   - restrict: "E",
134   - require: "^ngModel",
135   - link: linker,
136   - scope: {
137   - tbRequired: '=?',
138   - deviceAliases: '=',
139   - onCreateDeviceAlias: '&'
140   - }
141   - };
142   -}
143   -
144   -/* eslint-enable angular/angularelement */
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<section layout='column'>
19   - <md-autocomplete id="device-autocomplete"
20   - md-input-name="device_alias"
21   - ng-required="tbRequired"
22   - ng-model="deviceAlias"
23   - md-selected-item="deviceAlias"
24   - md-search-text="deviceAliasSearchText"
25   - md-items="item in deviceAliasSearch(deviceAliasSearchText)"
26   - md-item-text="item.alias"
27   - tb-keydown="deviceAliasEnter($event)"
28   - tb-keypress="deviceAliasEnter($event)"
29   - md-min-length="0"
30   - placeholder="{{ 'device.device-alias' | translate }}"
31   - md-menu-class="tb-device-alias-autocomplete">
32   - <md-item-template>
33   - <span md-highlight-text="deviceAliasSearchText" md-highlight-flags="^i">{{item.alias}}</span>
34   - </md-item-template>
35   - <md-not-found>
36   - <div class="tb-not-found">
37   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(deviceAliasSearchText)">
38   - <span translate>device.no-aliases-found</span>
39   - </div>
40   - <div ng-if="textIsNotEmpty(deviceAliasSearchText)">
41   - <span translate translate-values='{ alias: "{{deviceAliasSearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-alias-matching</span>
42   - <span>
43   - <a translate ng-click="createDeviceAlias($event, deviceAliasSearchText)">device.create-new-alias</a>
44   - </span>
45   - </div>
46   - </div>
47   - </md-not-found>
48   - </md-autocomplete>
49   - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
50   - <div translate ng-message="deviceAlias" class="tb-error-message">device.alias-required</div>
51   - </div>
52   -</section>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -
17   -/* eslint-disable import/no-unresolved, import/default */
18   -
19   -import deviceFilterTemplate from './device-filter.tpl.html';
20   -
21   -/* eslint-enable import/no-unresolved, import/default */
22   -
23   -import './device-filter.scss';
24   -
25   -export default angular.module('thingsboard.directives.deviceFilter', [])
26   - .directive('tbDeviceFilter', DeviceFilter)
27   - .name;
28   -
29   -/*@ngInject*/
30   -function DeviceFilter($compile, $templateCache, $q, deviceService) {
31   -
32   - var linker = function (scope, element, attrs, ngModelCtrl) {
33   -
34   - var template = $templateCache.get(deviceFilterTemplate);
35   - element.html(template);
36   -
37   - scope.ngModelCtrl = ngModelCtrl;
38   -
39   - scope.fetchDevices = function(searchText, limit) {
40   - var pageLink = {limit: limit, textSearch: searchText};
41   -
42   - var deferred = $q.defer();
43   -
44   - deviceService.getTenantDevices(pageLink, false).then(function success(result) {
45   - deferred.resolve(result.data);
46   - }, function fail() {
47   - deferred.reject();
48   - });
49   -
50   - return deferred.promise;
51   - }
52   -
53   - scope.updateValidity = function() {
54   - if (ngModelCtrl.$viewValue) {
55   - var value = ngModelCtrl.$viewValue;
56   - var valid;
57   - if (value.useFilter) {
58   - ngModelCtrl.$setValidity('deviceList', true);
59   - if (angular.isDefined(value.deviceNameFilter) && value.deviceNameFilter.length > 0) {
60   - ngModelCtrl.$setValidity('deviceNameFilter', true);
61   - valid = angular.isDefined(scope.model.matchingFilterDevice) && scope.model.matchingFilterDevice != null;
62   - ngModelCtrl.$setValidity('deviceNameFilterDeviceMatch', valid);
63   - } else {
64   - ngModelCtrl.$setValidity('deviceNameFilter', false);
65   - }
66   - } else {
67   - ngModelCtrl.$setValidity('deviceNameFilter', true);
68   - ngModelCtrl.$setValidity('deviceNameFilterDeviceMatch', true);
69   - valid = angular.isDefined(value.deviceList) && value.deviceList.length > 0;
70   - ngModelCtrl.$setValidity('deviceList', valid);
71   - }
72   - }
73   - }
74   -
75   - ngModelCtrl.$render = function () {
76   - destroyWatchers();
77   - scope.model = {
78   - useFilter: false,
79   - deviceList: [],
80   - deviceNameFilter: ''
81   - }
82   - if (ngModelCtrl.$viewValue) {
83   - var value = ngModelCtrl.$viewValue;
84   - var model = scope.model;
85   - model.useFilter = value.useFilter === true ? true: false;
86   - model.deviceList = [];
87   - model.deviceNameFilter = value.deviceNameFilter || '';
88   - processDeviceNameFilter(model.deviceNameFilter).then(
89   - function(device) {
90   - scope.model.matchingFilterDevice = device;
91   - if (value.deviceList && value.deviceList.length > 0) {
92   - deviceService.getDevices(value.deviceList).then(function (devices) {
93   - model.deviceList = devices;
94   - updateMatchingDevice();
95   - initWatchers();
96   - });
97   - } else {
98   - updateMatchingDevice();
99   - initWatchers();
100   - }
101   - }
102   - )
103   - }
104   - }
105   -
106   - function updateMatchingDevice() {
107   - if (scope.model.useFilter) {
108   - scope.model.matchingDevice = scope.model.matchingFilterDevice;
109   - } else {
110   - if (scope.model.deviceList && scope.model.deviceList.length > 0) {
111   - scope.model.matchingDevice = scope.model.deviceList[0];
112   - } else {
113   - scope.model.matchingDevice = null;
114   - }
115   - }
116   - }
117   -
118   - function processDeviceNameFilter(deviceNameFilter) {
119   - var deferred = $q.defer();
120   - if (angular.isDefined(deviceNameFilter) && deviceNameFilter.length > 0) {
121   - scope.fetchDevices(deviceNameFilter, 1).then(function (devices) {
122   - if (devices && devices.length > 0) {
123   - deferred.resolve(devices[0]);
124   - } else {
125   - deferred.resolve(null);
126   - }
127   - });
128   - } else {
129   - deferred.resolve(null);
130   - }
131   - return deferred.promise;
132   - }
133   -
134   - function destroyWatchers() {
135   - if (scope.deviceListDeregistration) {
136   - scope.deviceListDeregistration();
137   - scope.deviceListDeregistration = null;
138   - }
139   - if (scope.useFilterDeregistration) {
140   - scope.useFilterDeregistration();
141   - scope.useFilterDeregistration = null;
142   - }
143   - if (scope.deviceNameFilterDeregistration) {
144   - scope.deviceNameFilterDeregistration();
145   - scope.deviceNameFilterDeregistration = null;
146   - }
147   - if (scope.matchingDeviceDeregistration) {
148   - scope.matchingDeviceDeregistration();
149   - scope.matchingDeviceDeregistration = null;
150   - }
151   - }
152   -
153   - function initWatchers() {
154   - scope.deviceListDeregistration = scope.$watch('model.deviceList', function () {
155   - if (ngModelCtrl.$viewValue) {
156   - var value = ngModelCtrl.$viewValue;
157   - value.deviceList = [];
158   - if (scope.model.deviceList && scope.model.deviceList.length > 0) {
159   - for (var i in scope.model.deviceList) {
160   - value.deviceList.push(scope.model.deviceList[i].id.id);
161   - }
162   - }
163   - updateMatchingDevice();
164   - ngModelCtrl.$setViewValue(value);
165   - scope.updateValidity();
166   - }
167   - }, true);
168   - scope.useFilterDeregistration = scope.$watch('model.useFilter', function () {
169   - if (ngModelCtrl.$viewValue) {
170   - var value = ngModelCtrl.$viewValue;
171   - value.useFilter = scope.model.useFilter;
172   - updateMatchingDevice();
173   - ngModelCtrl.$setViewValue(value);
174   - scope.updateValidity();
175   - }
176   - });
177   - scope.deviceNameFilterDeregistration = scope.$watch('model.deviceNameFilter', function (newNameFilter, prevNameFilter) {
178   - if (ngModelCtrl.$viewValue) {
179   - if (!angular.equals(newNameFilter, prevNameFilter)) {
180   - var value = ngModelCtrl.$viewValue;
181   - value.deviceNameFilter = scope.model.deviceNameFilter;
182   - processDeviceNameFilter(value.deviceNameFilter).then(
183   - function(device) {
184   - scope.model.matchingFilterDevice = device;
185   - updateMatchingDevice();
186   - ngModelCtrl.$setViewValue(value);
187   - scope.updateValidity();
188   - }
189   - );
190   - }
191   - }
192   - });
193   -
194   - scope.matchingDeviceDeregistration = scope.$watch('model.matchingDevice', function (newMatchingDevice, prevMatchingDevice) {
195   - if (!angular.equals(newMatchingDevice, prevMatchingDevice)) {
196   - if (scope.onMatchingDeviceChange) {
197   - scope.onMatchingDeviceChange({device: newMatchingDevice});
198   - }
199   - }
200   - });
201   - }
202   -
203   - $compile(element.contents())(scope);
204   -
205   - }
206   -
207   - return {
208   - restrict: "E",
209   - require: "^ngModel",
210   - link: linker,
211   - scope: {
212   - isEdit: '=',
213   - onMatchingDeviceChange: '&'
214   - }
215   - };
216   -
217   -}
1   -/**
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -.tb-device-filter {
17   - #device_list_chips {
18   - .md-chips {
19   - padding-bottom: 1px;
20   - }
21   - }
22   - .device-name-filter-input {
23   - margin-top: 10px;
24   - margin-bottom: 0px;
25   - .md-errors-spacer {
26   - min-height: 0px;
27   - }
28   - }
29   - .tb-filter-switch {
30   - padding-left: 10px;
31   - .filter-switch {
32   - margin: 0;
33   - }
34   - .filter-label {
35   - margin: 5px 0;
36   - }
37   - }
38   - .tb-error-messages {
39   - margin-top: -11px;
40   - height: 35px;
41   - .tb-error-message {
42   - padding-left: 1px;
43   - }
44   - }
45   -}
\ No newline at end of file
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<section layout='column' class="tb-device-filter">
19   - <section layout='row'>
20   - <section layout="column" flex ng-show="!model.useFilter">
21   - <md-chips flex
22   - id="device_list_chips"
23   - ng-required="!useFilter"
24   - ng-model="model.deviceList" md-autocomplete-snap
25   - md-require-match="true">
26   - <md-autocomplete
27   - md-no-cache="true"
28   - id="device"
29   - md-selected-item="selectedDevice"
30   - md-search-text="deviceSearchText"
31   - md-items="item in fetchDevices(deviceSearchText, 10)"
32   - md-item-text="item.name"
33   - md-min-length="0"
34   - placeholder="{{ 'device.device-list' | translate }}">
35   - <md-item-template>
36   - <span md-highlight-text="deviceSearchText" md-highlight-flags="^i">{{item.name}}</span>
37   - </md-item-template>
38   - <md-not-found>
39   - <span translate translate-values='{ device: deviceSearchText }'>device.no-devices-matching</span>
40   - </md-not-found>
41   - </md-autocomplete>
42   - <md-chip-template>
43   - <span>
44   - <strong>{{$chip.name}}</strong>
45   - </span>
46   - </md-chip-template>
47   - </md-chips>
48   - </section>
49   - <section layout="row" flex ng-show="model.useFilter">
50   - <md-input-container flex class="device-name-filter-input">
51   - <label translate>device.name-starts-with</label>
52   - <input ng-model="model.deviceNameFilter" aria-label="{{ 'device.name-starts-with' | translate }}">
53   - </md-input-container>
54   - </section>
55   - <section class="tb-filter-switch" layout="column" layout-align="center center">
56   - <label class="tb-small filter-label" translate>device.use-device-name-filter</label>
57   - <md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher">
58   - </md-switch>
59   - </section>
60   - </section>
61   - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
62   - <div translate ng-message="deviceList" class="tb-error-message">device.device-list-empty</div>
63   - <div translate ng-message="deviceNameFilter" class="tb-error-message">device.device-name-filter-required</div>
64   - <div translate translate-values='{ device: model.deviceNameFilter }' ng-message="deviceNameFilterDeviceMatch"
65   - class="tb-error-message">device.device-name-filter-no-device-matched</div>
66   - </div>
67   -</section>
\ No newline at end of file
... ... @@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
197 197 },
198 198
199 199 getLength: function () {
200   - if (vm.items.hasNext) {
  200 + if (vm.items.hasNext && !vm.items.pending) {
201 201 return vm.items.rowData.length + pageSize;
202 202 } else {
203 203 return vm.items.rowData.length;
... ... @@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
206 206
207 207 fetchMoreItems_: function () {
208 208 if (vm.items.hasNext && !vm.items.pending) {
209   - var promise = vm.fetchItemsFunc(vm.items.nextPageLink);
  209 + var promise = vm.fetchItemsFunc(vm.items.nextPageLink, $scope.searchConfig.searchEntitySubtype);
210 210 if (promise) {
211 211 vm.items.pending = true;
212 212 promise.then(
... ... @@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
433 433 reload();
434 434 });
435 435
  436 + $scope.$on('searchEntitySubtypeUpdated', function () {
  437 + reload();
  438 + });
  439 +
436 440 vm.onGridInited(vm);
437 441
438 442 vm.itemRows.getItemAtIndex(pageSize);
... ... @@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
441 445 if (vm.items && vm.items.pending) {
442 446 vm.items.reloadPending = true;
443 447 } else {
444   - vm.items = {
445   - data: [],
446   - rowData: [],
447   - nextPageLink: {
448   - limit: pageSize,
449   - textSearch: $scope.searchConfig.searchText
450   - },
451   - selections: {},
452   - selectedCount: 0,
453   - hasNext: true,
454   - pending: false
  448 + vm.items.data.length = 0;
  449 + vm.items.rowData.length = 0;
  450 + vm.items.nextPageLink = {
  451 + limit: pageSize,
  452 + textSearch: $scope.searchConfig.searchText
455 453 };
  454 + vm.items.selections = {};
  455 + vm.items.selectedCount = 0;
  456 + vm.items.hasNext = true;
  457 + vm.items.pending = false;
456 458 vm.detailsConfig.isDetailsOpen = false;
457 459 vm.items.reloadPending = false;
458 460 vm.itemRows.getItemAtIndex(pageSize);
... ...
... ... @@ -24,9 +24,8 @@
24 24 <md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
25 25 <div class="md-padding" layout="column">
26 26 <section layout="row" md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight">
27   - <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}">
28   - <md-card ng-if="rowItem[n]"
29   - ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
  27 + <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="rowItem[n]">
  28 + <md-card ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
30 29 class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
31 30 ng-click="vm.clickItemFunc($event, rowItem[n])">
32 31 <section layout="row" layout-wrap>
... ... @@ -43,7 +42,7 @@
43 42 </md-card-title>
44 43 </section>
45 44 <md-card-content flex>
46   - <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
  45 + <tb-grid-card-content flex grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
47 46 </md-card-content>
48 47 <md-card-actions layout="row" layout-align="end end">
49 48 <md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList"
... ... @@ -56,6 +55,8 @@
56 55 </md-card-actions>
57 56 </md-card>
58 57 </div>
  58 + <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="!rowItem[n]">
  59 + </div>
59 60 </section>
60 61 </div>
61 62 </md-virtual-repeat-container>
... ...
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -
19   -<section class="tb-aliases-device-select" layout='row' layout-align="start center" ng-style="{minHeight: '32px', padding: '0 6px'}">
20   - <md-button class="md-icon-button" aria-label="{{ 'dashboard.select-devices' | translate }}" ng-click="openEditMode($event)">
21   - <md-tooltip md-direction="{{tooltipDirection}}">
22   - {{ 'dashboard.select-devices' | translate }}
23   - </md-tooltip>
24   - <md-icon aria-label="{{ 'dashboard.select-devices' | translate }}" class="material-icons">devices_other</md-icon>
25   - </md-button>
26   - <span hide-xs hide-sm ng-click="openEditMode($event)">
27   - <md-tooltip md-direction="{{tooltipDirection}}">
28   - {{ 'dashboard.select-devices' | translate }}
29   - </md-tooltip>
30   - {{displayValue}}
31   - </span>
32   -</section>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -
17   -/*@ngInject*/
18   -export default function AliasesDeviceSelectPanelController(mdPanelRef, $scope, types, deviceAliases, deviceAliasesInfo, onDeviceAliasesUpdate) {
19   -
20   - var vm = this;
21   - vm._mdPanelRef = mdPanelRef;
22   - vm.deviceAliases = deviceAliases;
23   - vm.deviceAliasesInfo = deviceAliasesInfo;
24   - vm.onDeviceAliasesUpdate = onDeviceAliasesUpdate;
25   -
26   - $scope.$watch('vm.deviceAliases', function () {
27   - if (onDeviceAliasesUpdate) {
28   - onDeviceAliasesUpdate(vm.deviceAliases);
29   - }
30   - }, true);
31   -}
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -
17   -import './aliases-device-select.scss';
18   -
19   -import $ from 'jquery';
20   -
21   -/* eslint-disable import/no-unresolved, import/default */
22   -
23   -import aliasesDeviceSelectButtonTemplate from './aliases-device-select-button.tpl.html';
24   -import aliasesDeviceSelectPanelTemplate from './aliases-device-select-panel.tpl.html';
25   -
26   -/* eslint-enable import/no-unresolved, import/default */
27   -
28   -/* eslint-disable angular/angularelement */
29   -/*@ngInject*/
30   -export default function AliasesDeviceSelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) {
31   -
32   - var linker = function (scope, element, attrs, ngModelCtrl) {
33   -
34   - /* tbAliasesDeviceSelect (ng-model)
35   - * {
36   - * "aliasId": {
37   - * alias: alias,
38   - * deviceId: deviceId
39   - * }
40   - * }
41   - */
42   -
43   - var template = $templateCache.get(aliasesDeviceSelectButtonTemplate);
44   -
45   - scope.tooltipDirection = angular.isDefined(attrs.tooltipDirection) ? attrs.tooltipDirection : 'top';
46   -
47   - element.html(template);
48   -
49   - scope.openEditMode = function (event) {
50   - if (scope.disabled) {
51   - return;
52   - }
53   - var position;
54   - var panelHeight = $mdMedia('min-height: 350px') ? 250 : 150;
55   - var panelWidth = 300;
56   - var offset = element[0].getBoundingClientRect();
57   - var bottomY = offset.bottom - $(window).scrollTop(); //eslint-disable-line
58   - var leftX = offset.left - $(window).scrollLeft(); //eslint-disable-line
59   - var yPosition;
60   - var xPosition;
61   - if (bottomY + panelHeight > $( window ).height()) { //eslint-disable-line
62   - yPosition = $mdPanel.yPosition.ABOVE;
63   - } else {
64   - yPosition = $mdPanel.yPosition.BELOW;
65   - }
66   - if (leftX + panelWidth > $( window ).width()) { //eslint-disable-line
67   - xPosition = $mdPanel.xPosition.CENTER;
68   - } else {
69   - xPosition = $mdPanel.xPosition.ALIGN_START;
70   - }
71   - position = $mdPanel.newPanelPosition()
72   - .relativeTo(element)
73   - .addPanelPosition(xPosition, yPosition);
74   - var config = {
75   - attachTo: angular.element($document[0].body),
76   - controller: 'AliasesDeviceSelectPanelController',
77   - controllerAs: 'vm',
78   - templateUrl: aliasesDeviceSelectPanelTemplate,
79   - panelClass: 'tb-aliases-device-select-panel',
80   - position: position,
81   - fullscreen: false,
82   - locals: {
83   - 'deviceAliases': angular.copy(scope.model),
84   - 'deviceAliasesInfo': scope.deviceAliasesInfo,
85   - 'onDeviceAliasesUpdate': function (deviceAliases) {
86   - scope.model = deviceAliases;
87   - scope.updateView();
88   - }
89   - },
90   - openFrom: event,
91   - clickOutsideToClose: true,
92   - escapeToClose: true,
93   - focusOnOpen: false
94   - };
95   - $mdPanel.open(config);
96   - }
97   -
98   - scope.updateView = function () {
99   - var value = angular.copy(scope.model);
100   - ngModelCtrl.$setViewValue(value);
101   - updateDisplayValue();
102   - }
103   -
104   - ngModelCtrl.$render = function () {
105   - if (ngModelCtrl.$viewValue) {
106   - var value = ngModelCtrl.$viewValue;
107   - scope.model = angular.copy(value);
108   - updateDisplayValue();
109   - }
110   - }
111   -
112   - function updateDisplayValue() {
113   - var displayValue;
114   - var singleValue = true;
115   - var currentAliasId;
116   - for (var aliasId in scope.model) {
117   - if (!currentAliasId) {
118   - currentAliasId = aliasId;
119   - } else {
120   - singleValue = false;
121   - break;
122   - }
123   - }
124   - if (singleValue && currentAliasId) {
125   - var deviceId = scope.model[currentAliasId].deviceId;
126   - var devicesInfo = scope.deviceAliasesInfo[currentAliasId];
127   - for (var i=0;i<devicesInfo.length;i++) {
128   - if (devicesInfo[i].id === deviceId) {
129   - displayValue = devicesInfo[i].name;
130   - break;
131   - }
132   - }
133   - } else {
134   - displayValue = $translate.instant('device.devices');
135   - }
136   - scope.displayValue = displayValue;
137   - }
138   -
139   - $compile(element.contents())(scope);
140   - }
141   -
142   - return {
143   - restrict: "E",
144   - require: "^ngModel",
145   - scope: {
146   - deviceAliasesInfo:'='
147   - },
148   - link: linker
149   - };
150   -
151   -}
152   -
153   -/* eslint-enable angular/angularelement */
\ No newline at end of file
1   -/**
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -
17   -.md-panel {
18   - &.tb-aliases-device-select-panel {
19   - position: absolute;
20   - }
21   -}
22   -
23   -.tb-aliases-device-select-panel {
24   - max-height: 150px;
25   - @media (min-height: 350px) {
26   - max-height: 250px;
27   - }
28   - min-width: 300px;
29   - background: white;
30   - border-radius: 4px;
31   - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
32   - 0 13px 19px 2px rgba(0, 0, 0, 0.14),
33   - 0 5px 24px 4px rgba(0, 0, 0, 0.12);
34   - overflow-x: hidden;
35   - overflow-y: auto;
36   - md-content {
37   - background-color: #fff;
38   - }
39   -}
40   -
41   -.tb-aliases-device-select {
42   - span {
43   - pointer-events: all;
44   - cursor: pointer;
45   - }
46   -}
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -import './device-aliases.scss';
17   -
18   -/*@ngInject*/
19   -export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate,
20   - types, config) {
21   -
22   - var vm = this;
23   -
24   - vm.isSingleDeviceAlias = config.isSingleDeviceAlias;
25   - vm.singleDeviceAlias = config.singleDeviceAlias;
26   - vm.deviceAliases = [];
27   - vm.title = config.customTitle ? config.customTitle : 'device.aliases';
28   - vm.disableAdd = config.disableAdd;
29   - vm.aliasToWidgetsMap = {};
30   -
31   -
32   - vm.onFilterDeviceChanged = onFilterDeviceChanged;
33   - vm.addAlias = addAlias;
34   - vm.removeAlias = removeAlias;
35   -
36   - vm.cancel = cancel;
37   - vm.save = save;
38   -
39   - initController();
40   -
41   - function initController() {
42   - var aliasId;
43   - if (config.widgets) {
44   - var widgetsTitleList, widget;
45   - if (config.isSingleWidget && config.widgets.length == 1) {
46   - widget = config.widgets[0];
47   - widgetsTitleList = [widget.config.title];
48   - for (aliasId in config.deviceAliases) {
49   - vm.aliasToWidgetsMap[aliasId] = widgetsTitleList;
50   - }
51   - } else {
52   - for (var w in config.widgets) {
53   - widget = config.widgets[w];
54   - if (widget.type === types.widgetType.rpc.value) {
55   - if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
56   - var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
57   - widgetsTitleList = vm.aliasToWidgetsMap[targetDeviceAliasId];
58   - if (!widgetsTitleList) {
59   - widgetsTitleList = [];
60   - vm.aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList;
61   - }
62   - widgetsTitleList.push(widget.config.title);
63   - }
64   - } else {
65   - for (var i in widget.config.datasources) {
66   - var datasource = widget.config.datasources[i];
67   - if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
68   - widgetsTitleList = vm.aliasToWidgetsMap[datasource.deviceAliasId];
69   - if (!widgetsTitleList) {
70   - widgetsTitleList = [];
71   - vm.aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList;
72   - }
73   - widgetsTitleList.push(widget.config.title);
74   - }
75   - }
76   - }
77   - }
78   - }
79   - }
80   -
81   - if (vm.isSingleDeviceAlias) {
82   - if (!vm.singleDeviceAlias.deviceFilter || vm.singleDeviceAlias.deviceFilter == null) {
83   - vm.singleDeviceAlias.deviceFilter = {
84   - useFilter: false,
85   - deviceNameFilter: '',
86   - deviceList: [],
87   - };
88   - }
89   - }
90   -
91   - for (aliasId in config.deviceAliases) {
92   - var deviceAlias = config.deviceAliases[aliasId];
93   - var alias = deviceAlias.alias;
94   - var deviceFilter;
95   - if (!deviceAlias.deviceFilter) {
96   - deviceFilter = {
97   - useFilter: false,
98   - deviceNameFilter: '',
99   - deviceList: [],
100   - };
101   - if (deviceAlias.deviceId) {
102   - deviceFilter.deviceList = [deviceAlias.deviceId];
103   - } else {
104   - deviceFilter.deviceList = [];
105   - }
106   - } else {
107   - deviceFilter = deviceAlias.deviceFilter;
108   - }
109   - var result = {id: aliasId, alias: alias, deviceFilter: deviceFilter, changed: true};
110   - vm.deviceAliases.push(result);
111   - }
112   - }
113   -
114   - function onFilterDeviceChanged(device, deviceAlias) {
115   - if (deviceAlias) {
116   - if (!deviceAlias.alias || deviceAlias.alias.length == 0) {
117   - deviceAlias.changed = false;
118   - }
119   - if (!deviceAlias.changed && device) {
120   - deviceAlias.alias = device.name;
121   - }
122   - }
123   - }
124   -
125   - function addAlias() {
126   - var aliasId = 0;
127   - for (var a in vm.deviceAliases) {
128   - aliasId = Math.max(vm.deviceAliases[a].id, aliasId);
129   - }
130   - aliasId++;
131   - var deviceAlias = {id: aliasId, alias: '', deviceFilter: {useFilter: false, deviceNameFilter: '', deviceList: []}, changed: false};
132   - vm.deviceAliases.push(deviceAlias);
133   - }
134   -
135   - function removeAlias($event, deviceAlias) {
136   - var index = vm.deviceAliases.indexOf(deviceAlias);
137   - if (index > -1) {
138   - var widgetsTitleList = vm.aliasToWidgetsMap[deviceAlias.id];
139   - if (widgetsTitleList) {
140   - var widgetsListHtml = '';
141   - for (var t in widgetsTitleList) {
142   - widgetsListHtml += '<br/>\'' + widgetsTitleList[t] + '\'';
143   - }
144   - var alert = $mdDialog.alert()
145   - .parent(angular.element($document[0].body))
146   - .clickOutsideToClose(true)
147   - .title($translate.instant('device.unable-delete-device-alias-title'))
148   - .htmlContent($translate.instant('device.unable-delete-device-alias-text', {deviceAlias: deviceAlias.alias, widgetsList: widgetsListHtml}))
149   - .ariaLabel($translate.instant('device.unable-delete-device-alias-title'))
150   - .ok($translate.instant('action.close'))
151   - .targetEvent($event);
152   - alert._options.skipHide = true;
153   - alert._options.fullscreen = true;
154   -
155   - $mdDialog.show(alert);
156   - } else {
157   - vm.deviceAliases.splice(index, 1);
158   - if ($scope.theForm) {
159   - $scope.theForm.$setDirty();
160   - }
161   - }
162   - }
163   - }
164   -
165   - function cancel() {
166   - $mdDialog.cancel();
167   - }
168   -
169   - function cleanupDeviceFilter(deviceFilter) {
170   - if (deviceFilter.useFilter) {
171   - deviceFilter.deviceList = [];
172   - } else {
173   - deviceFilter.deviceNameFilter = '';
174   - }
175   - return deviceFilter;
176   - }
177   -
178   - function save() {
179   -
180   - var deviceAliases = {};
181   - var uniqueAliasList = {};
182   -
183   - var valid = true;
184   - var aliasId, maxAliasId;
185   - var alias;
186   - var i;
187   -
188   - if (vm.isSingleDeviceAlias) {
189   - maxAliasId = 0;
190   - vm.singleDeviceAlias.deviceFilter = cleanupDeviceFilter(vm.singleDeviceAlias.deviceFilter);
191   - for (i in vm.deviceAliases) {
192   - aliasId = vm.deviceAliases[i].id;
193   - alias = vm.deviceAliases[i].alias;
194   - if (alias === vm.singleDeviceAlias.alias) {
195   - valid = false;
196   - break;
197   - }
198   - maxAliasId = Math.max(aliasId, maxAliasId);
199   - }
200   - maxAliasId++;
201   - vm.singleDeviceAlias.id = maxAliasId;
202   - } else {
203   - for (i in vm.deviceAliases) {
204   - aliasId = vm.deviceAliases[i].id;
205   - alias = vm.deviceAliases[i].alias;
206   - if (!uniqueAliasList[alias]) {
207   - uniqueAliasList[alias] = alias;
208   - deviceAliases[aliasId] = {alias: alias, deviceFilter: cleanupDeviceFilter(vm.deviceAliases[i].deviceFilter)};
209   - } else {
210   - valid = false;
211   - break;
212   - }
213   - }
214   - }
215   - if (valid) {
216   - $scope.theForm.$setPristine();
217   - if (vm.isSingleDeviceAlias) {
218   - $mdDialog.hide(vm.singleDeviceAlias);
219   - } else {
220   - $mdDialog.hide(deviceAliases);
221   - }
222   - } else {
223   - toast.showError($translate.instant('device.duplicate-alias-error', {alias: alias}));
224   - }
225   - }
226   -
227   -}
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<md-dialog class="tb-aliases-dialog" style="width: 700px;" aria-label="{{ vm.title | translate }}">
19   - <form name="theForm" ng-submit="vm.save()">
20   - <md-toolbar>
21   - <div class="md-toolbar-tools">
22   - <h2>{{ vm.isSingleDeviceAlias ? ('device.configure-alias' | translate:vm.singleDeviceAlias ) : (vm.title | translate) }}</h2>
23   - <span flex></span>
24   - <md-button class="md-icon-button" ng-click="vm.cancel()">
25   - <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
26   - </md-button>
27   - </div>
28   - </md-toolbar>
29   - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30   - <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
31   - <md-dialog-content>
32   - <div class="md-dialog-content">
33   - <fieldset ng-disabled="loading">
34   - <div ng-show="vm.isSingleDeviceAlias">
35   - <tb-device-filter ng-model="vm.singleDeviceAlias.deviceFilter">
36   - </tb-device-filter>
37   - </div>
38   - <div ng-show="!vm.isSingleDeviceAlias" flex layout="row" layout-align="start center">
39   - <span flex="5"></span>
40   - <div flex layout="row" layout-align="start center"
41   - style="padding: 0 0 0 10px; margin: 5px;">
42   - <span translate flex="40" style="min-width: 100px;">device.alias</span>
43   - <span translate flex="60" style="min-width: 190px; padding-left: 10px;">device.devices</span>
44   - <span style="min-width: 40px;"></span>
45   - </div>
46   - </div>
47   - <div ng-show="!vm.isSingleDeviceAlias" style="max-height: 500px; overflow: auto; padding-bottom: 20px;">
48   - <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="deviceAlias in vm.deviceAliases track by $index">
49   - <span flex="5">{{$index + 1}}.</span>
50   - <div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
51   - <md-input-container flex="40" style="min-width: 100px;" md-no-float class="md-block">
52   - <input required ng-change="deviceAlias.changed=true" name="alias" placeholder="{{ 'device.alias' | translate }}" ng-model="deviceAlias.alias">
53   - <div ng-messages="aliasForm.alias.$error">
54   - <div translate ng-message="required">device.alias-required</div>
55   - </div>
56   - </md-input-container>
57   - <section flex="60" layout="column">
58   - <tb-device-filter style="padding-left: 10px;"
59   - ng-model="deviceAlias.deviceFilter"
60   - on-matching-device-change="vm.onFilterDeviceChanged(device, deviceAlias)">
61   - </tb-device-filter>
62   - </section>
63   - <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
64   - ng-click="vm.removeAlias($event, deviceAlias)" aria-label="{{ 'action.remove' | translate }}">
65   - <md-tooltip md-direction="top">
66   - {{ 'device.remove-alias' | translate }}
67   - </md-tooltip>
68   - <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
69   - close
70   - </md-icon>
71   - </md-button>
72   - </div>
73   - </div>
74   - </div>
75   - <div ng-show="!vm.isSingleDeviceAlias && !vm.disableAdd" style="padding-bottom: 10px;">
76   - <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}">
77   - <md-tooltip md-direction="top">
78   - {{ 'device.add-alias' | translate }}
79   - </md-tooltip>
80   - <span translate>action.add</span>
81   - </md-button>
82   - </div>
83   - </fieldset>
84   - </div>
85   - </md-dialog-content>
86   - <md-dialog-actions layout="row">
87   - <span flex></span>
88   - <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
89   - {{ 'action.save' | translate }}
90   - </md-button>
91   - <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
92   - </md-dialog-actions>
93   - </form>
94   -</md-dialog>
\ No newline at end of file
... ... @@ -58,7 +58,7 @@
58 58 {{ 'dashboard.search-states' | translate }}
59 59 </md-tooltip>
60 60 </md-button>
61   - <md-input-container md-theme="tb-search-input" flex>
  61 + <md-input-container flex>
62 62 <label>&nbsp;</label>
63 63 <input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/>
64 64 </md-input-container>
... ...
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -/*@ngInject*/
17   -export default function AddAttributeDialogController($scope, $mdDialog, types, deviceService, deviceId, attributeScope) {
18   -
19   - var vm = this;
20   -
21   - vm.attribute = {};
22   -
23   - vm.valueTypes = types.valueType;
24   -
25   - vm.valueType = types.valueType.string;
26   -
27   - vm.add = add;
28   - vm.cancel = cancel;
29   -
30   - function cancel() {
31   - $mdDialog.cancel();
32   - }
33   -
34   - function add() {
35   - $scope.theForm.$setPristine();
36   - deviceService.saveDeviceAttributes(deviceId, attributeScope, [vm.attribute]).then(
37   - function success() {
38   - $mdDialog.hide();
39   - }
40   - );
41   - }
42   -
43   - $scope.$watch('vm.valueType', function() {
44   - if (vm.valueType === types.valueType.boolean) {
45   - vm.attribute.value = false;
46   - } else {
47   - vm.attribute.value = null;
48   - }
49   - });
50   -}
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<md-dialog aria-label="{{ 'attribute.add' | translate }}" style="min-width: 400px;">
19   - <form name="theForm" ng-submit="vm.add()">
20   - <md-toolbar>
21   - <div class="md-toolbar-tools">
22   - <h2 translate>attribute.add</h2>
23   - <span flex></span>
24   - <md-button class="md-icon-button" ng-click="vm.cancel()">
25   - <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
26   - </md-button>
27   - </div>
28   - </md-toolbar>
29   - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30   - <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
31   - <md-dialog-content>
32   - <div class="md-dialog-content">
33   - <md-content class="md-padding" layout="column">
34   - <fieldset ng-disabled="loading">
35   - <md-input-container class="md-block">
36   - <label translate>attribute.key</label>
37   - <input required name="key" ng-model="vm.attribute.key">
38   - <div ng-messages="theForm.key.$error">
39   - <div translate ng-message="required">attribute.key-required</div>
40   - </div>
41   - </md-input-container>
42   - <section layout="row">
43   - <md-input-container flex="40" class="md-block" style="width: 200px;">
44   - <label translate>value.type</label>
45   - <md-select ng-model="vm.valueType" ng-disabled="loading()">
46   - <md-option ng-repeat="type in vm.valueTypes" ng-value="type">
47   - <md-icon md-svg-icon="{{ type.icon }}"></md-icon>
48   - <span>{{type.name | translate}}</span>
49   - </md-option>
50   - </md-select>
51   - </md-input-container>
52   - <md-input-container ng-if="vm.valueType===vm.valueTypes.string" flex="60" class="md-block">
53   - <label translate>value.string-value</label>
54   - <input required name="value" ng-model="vm.attribute.value">
55   - <div ng-messages="theForm.value.$error">
56   - <div translate ng-message="required">attribute.value-required</div>
57   - </div>
58   - </md-input-container>
59   - <md-input-container ng-if="vm.valueType===vm.valueTypes.integer" flex="60" class="md-block">
60   - <label translate>value.integer-value</label>
61   - <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attribute.value">
62   - <div ng-messages="theForm.value.$error">
63   - <div translate ng-message="required">attribute.value-required</div>
64   - <div translate ng-message="pattern">value.invalid-integer-value</div>
65   - </div>
66   - </md-input-container>
67   - <md-input-container ng-if="vm.valueType===vm.valueTypes.double" flex="60" class="md-block">
68   - <label translate>value.double-value</label>
69   - <input required name="value" type="number" step="any" ng-model="vm.attribute.value">
70   - <div ng-messages="theForm.value.$error">
71   - <div translate ng-message="required">attribute.value-required</div>
72   - </div>
73   - </md-input-container>
74   - <div layout="column" layout-align="center" flex="60" ng-if="vm.valueType===vm.valueTypes.boolean">
75   - <md-checkbox ng-model="vm.attribute.value" style="margin-bottom: 0px;">
76   - {{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }}
77   - </md-checkbox>
78   - </div>
79   - </section>
80   - </fieldset>
81   - </md-content>
82   - </div>
83   - </md-dialog-content>
84   - <md-dialog-actions layout="row">
85   - <span flex></span>
86   - <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
87   - class="md-raised md-primary">
88   - {{ 'action.add' | translate }}
89   - </md-button>
90   - <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
91   - translate }}
92   - </md-button>
93   - </md-dialog-actions>
94   - </form>
95   -</md-dialog>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -/*@ngInject*/
17   -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, itembuffer, dashboardService, deviceId, deviceName, widget) {
18   -
19   - var vm = this;
20   -
21   - vm.widget = widget;
22   - vm.dashboardId = null;
23   - vm.addToDashboardType = 0;
24   - vm.newDashboard = {};
25   - vm.openDashboard = false;
26   -
27   - vm.add = add;
28   - vm.cancel = cancel;
29   -
30   - function cancel() {
31   - $mdDialog.cancel();
32   - }
33   -
34   - function add() {
35   - $scope.theForm.$setPristine();
36   - if (vm.addToDashboardType === 0) {
37   - dashboardService.getDashboard(vm.dashboardId).then(
38   - function success(dashboard) {
39   - addWidgetToDashboard(dashboard);
40   - },
41   - function fail() {}
42   - );
43   - } else {
44   - addWidgetToDashboard(vm.newDashboard);
45   - }
46   -
47   - }
48   -
49   - function addWidgetToDashboard(theDashboard) {
50   - var aliasesInfo = {
51   - datasourceAliases: {},
52   - targetDeviceAliases: {}
53   - };
54   - aliasesInfo.datasourceAliases[0] = {
55   - aliasName: deviceName,
56   - deviceFilter: {
57   - useFilter: false,
58   - deviceNameFilter: '',
59   - deviceList: [deviceId]
60   - }
61   - };
62   - theDashboard = itembuffer.addWidgetToDashboard(theDashboard, vm.widget, aliasesInfo, null, 48, -1, -1);
63   - dashboardService.saveDashboard(theDashboard).then(
64   - function success(dashboard) {
65   - $mdDialog.hide();
66   - if (vm.openDashboard) {
67   - $state.go('home.dashboards.dashboard', {dashboardId: dashboard.id.id});
68   - }
69   - }
70   - );
71   - }
72   -
73   -}
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<md-dialog aria-label="{{ 'attribute.add-widget-to-dashboard' | translate }}" style="min-width: 400px;">
19   - <form name="theForm" ng-submit="vm.add()">
20   - <md-toolbar>
21   - <div class="md-toolbar-tools">
22   - <h2 translate>attribute.add-widget-to-dashboard</h2>
23   - <span flex></span>
24   - <md-button class="md-icon-button" ng-click="vm.cancel()">
25   - <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
26   - </md-button>
27   - </div>
28   - </md-toolbar>
29   - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30   - <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
31   - <md-dialog-content>
32   - <div class="md-dialog-content">
33   - <md-content class="md-padding" layout="column">
34   - <fieldset ng-disabled="loading">
35   - <md-radio-group ng-model="vm.addToDashboardType" class="md-primary">
36   - <md-radio-button flex ng-value=0 class="md-primary md-align-top-left md-radio-interactive">
37   - <section flex layout="column" style="width: 300px;">
38   - <span translate style="padding-bottom: 10px;">dashboard.select-existing</span>
39   - <tb-dashboard-autocomplete the-form="theForm"
40   - ng-disabled="loading || vm.addToDashboardType != 0"
41   - tb-required="vm.addToDashboardType === 0"
42   - ng-model="vm.dashboardId"
43   - select-first-dashboard="false">
44   - </tb-dashboard-autocomplete>
45   - </section>
46   - </md-radio-button>
47   - <md-radio-button flex ng-value=1 class="md-primary md-align-top-left md-radio-interactive">
48   - <section flex layout="column" style="width: 300px;">
49   - <span translate>dashboard.create-new</span>
50   - <md-input-container class="md-block">
51   - <label translate>dashboard.new-dashboard-title</label>
52   - <input ng-required="vm.addToDashboardType === 1" name="title" ng-model="vm.newDashboard.title">
53   - <div ng-messages="theForm.title.$error">
54   - <div translate ng-message="required">dashboard.title-required</div>
55   - </div>
56   - </md-input-container>
57   - </section>
58   - </md-radio-button>
59   - </md-radio-group>
60   - </fieldset>
61   - </md-content>
62   - </div>
63   - </md-dialog-content>
64   - <md-dialog-actions layout="row">
65   - <span flex></span>
66   - <md-checkbox
67   - ng-model="vm.openDashboard"
68   - aria-label="{{ 'dashboard.open-dashboard' | translate }}"
69   - style="margin-bottom: 0px; padding-right: 20px;">
70   - {{ 'dashboard.open-dashboard' | translate }}
71   - </md-checkbox>
72   - <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
73   - class="md-raised md-primary">
74   - {{ 'action.add' | translate }}
75   - </md-button>
76   - <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
77   - translate }}
78   - </md-button>
79   - </md-dialog-actions>
80   - </form>
81   -</md-dialog>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -import 'angular-material-data-table/dist/md-data-table.min.css';
17   -import './attribute-table.scss';
18   -
19   -/* eslint-disable import/no-unresolved, import/default */
20   -
21   -import attributeTableTemplate from './attribute-table.tpl.html';
22   -import addAttributeDialogTemplate from './add-attribute-dialog.tpl.html';
23   -import addWidgetToDashboardDialogTemplate from './add-widget-to-dashboard-dialog.tpl.html';
24   -import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
25   -
26   -/* eslint-enable import/no-unresolved, import/default */
27   -
28   -import EditAttributeValueController from './edit-attribute-value.controller';
29   -
30   -/*@ngInject*/
31   -export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
32   - $document, $translate, $filter, utils, types, dashboardService, deviceService, widgetService) {
33   -
34   - var linker = function (scope, element, attrs) {
35   -
36   - var template = $templateCache.get(attributeTableTemplate);
37   -
38   - element.html(template);
39   -
40   - scope.types = types;
41   - scope.attributeScopes = types.deviceAttributesScope;
42   -
43   - var getAttributeScopeByValue = function(attributeScopeValue) {
44   - if (scope.types.latestTelemetry.value === attributeScopeValue) {
45   - return scope.types.latestTelemetry;
46   - }
47   - for (var attrScope in scope.attributeScopes) {
48   - if (scope.attributeScopes[attrScope].value === attributeScopeValue) {
49   - return scope.attributeScopes[attrScope];
50   - }
51   - }
52   - }
53   -
54   - scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
55   -
56   - scope.attributes = {
57   - count: 0,
58   - data: []
59   - };
60   -
61   - scope.selectedAttributes = [];
62   - scope.mode = 'default'; // 'widget'
63   - scope.subscriptionId = null;
64   -
65   - scope.query = {
66   - order: 'key',
67   - limit: 5,
68   - page: 1,
69   - search: null
70   - };
71   -
72   - scope.$watch("deviceId", function(newVal, prevVal) {
73   - if (newVal && !angular.equals(newVal, prevVal)) {
74   - scope.resetFilter();
75   - scope.getDeviceAttributes(false, true);
76   - }
77   - });
78   -
79   - scope.$watch("attributeScope", function(newVal, prevVal) {
80   - if (newVal && !angular.equals(newVal, prevVal)) {
81   - scope.mode = 'default';
82   - scope.query.search = null;
83   - scope.selectedAttributes = [];
84   - scope.getDeviceAttributes(false, true);
85   - }
86   - });
87   -
88   - scope.resetFilter = function() {
89   - scope.mode = 'default';
90   - scope.query.search = null;
91   - scope.selectedAttributes = [];
92   - scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
93   - }
94   -
95   - scope.enterFilterMode = function() {
96   - scope.query.search = '';
97   - }
98   -
99   - scope.exitFilterMode = function() {
100   - scope.query.search = null;
101   - scope.getDeviceAttributes();
102   - }
103   -
104   - scope.$watch("query.search", function(newVal, prevVal) {
105   - if (!angular.equals(newVal, prevVal) && scope.query.search != null) {
106   - scope.getDeviceAttributes();
107   - }
108   - });
109   -
110   - function success(attributes, update, apply) {
111   - scope.attributes = attributes;
112   - if (!update) {
113   - scope.selectedAttributes = [];
114   - }
115   - if (apply) {
116   - scope.$digest();
117   - }
118   - }
119   -
120   - scope.onReorder = function() {
121   - scope.getDeviceAttributes(false, false);
122   - }
123   -
124   - scope.onPaginate = function() {
125   - scope.getDeviceAttributes(false, false);
126   - }
127   -
128   - scope.getDeviceAttributes = function(forceUpdate, reset) {
129   - if (scope.attributesDeferred) {
130   - scope.attributesDeferred.resolve();
131   - }
132   - if (scope.deviceId && scope.attributeScope) {
133   - if (reset) {
134   - scope.attributes = {
135   - count: 0,
136   - data: []
137   - };
138   - }
139   - scope.checkSubscription();
140   - scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
141   - scope.query, function(attributes, update, apply) {
142   - success(attributes, update || forceUpdate, apply);
143   - }
144   - );
145   - } else {
146   - var deferred = $q.defer();
147   - scope.attributesDeferred = deferred;
148   - success({
149   - count: 0,
150   - data: []
151   - });
152   - deferred.resolve();
153   - }
154   - }
155   -
156   - scope.checkSubscription = function() {
157   - var newSubscriptionId = null;
158   - if (scope.deviceId && scope.attributeScope.clientSide && scope.mode != 'widget') {
159   - newSubscriptionId = deviceService.subscribeForDeviceAttributes(scope.deviceId, scope.attributeScope.value);
160   - }
161   - if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) {
162   - deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId);
163   - }
164   - scope.subscriptionId = newSubscriptionId;
165   - }
166   -
167   - scope.$on('$destroy', function() {
168   - if (scope.subscriptionId) {
169   - deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId);
170   - }
171   - });
172   -
173   - scope.editAttribute = function($event, attribute) {
174   - if (!scope.attributeScope.clientSide) {
175   - $event.stopPropagation();
176   - $mdEditDialog.show({
177   - controller: EditAttributeValueController,
178   - templateUrl: editAttributeValueTemplate,
179   - locals: {attributeValue: attribute.value,
180   - save: function (model) {
181   - var updatedAttribute = angular.copy(attribute);
182   - updatedAttribute.value = model.value;
183   - deviceService.saveDeviceAttributes(scope.deviceId, scope.attributeScope.value, [updatedAttribute]).then(
184   - function success() {
185   - scope.getDeviceAttributes();
186   - }
187   - );
188   - }},
189   - targetEvent: $event
190   - });
191   - }
192   - }
193   -
194   - scope.addAttribute = function($event) {
195   - if (!scope.attributeScope.clientSide) {
196   - $event.stopPropagation();
197   - $mdDialog.show({
198   - controller: 'AddAttributeDialogController',
199   - controllerAs: 'vm',
200   - templateUrl: addAttributeDialogTemplate,
201   - parent: angular.element($document[0].body),
202   - locals: {deviceId: scope.deviceId, attributeScope: scope.attributeScope.value},
203   - fullscreen: true,
204   - targetEvent: $event
205   - }).then(function () {
206   - scope.getDeviceAttributes();
207   - });
208   - }
209   - }
210   -
211   - scope.deleteAttributes = function($event) {
212   - if (!scope.attributeScope.clientSide) {
213   - $event.stopPropagation();
214   - var confirm = $mdDialog.confirm()
215   - .targetEvent($event)
216   - .title($translate.instant('attribute.delete-attributes-title', {count: scope.selectedAttributes.length}, 'messageformat'))
217   - .htmlContent($translate.instant('attribute.delete-attributes-text'))
218   - .ariaLabel($translate.instant('attribute.delete-attributes'))
219   - .cancel($translate.instant('action.no'))
220   - .ok($translate.instant('action.yes'));
221   - $mdDialog.show(confirm).then(function () {
222   - deviceService.deleteDeviceAttributes(scope.deviceId, scope.attributeScope.value, scope.selectedAttributes).then(
223   - function success() {
224   - scope.selectedAttributes = [];
225   - scope.getDeviceAttributes();
226   - }
227   - )
228   - });
229   - }
230   - }
231   -
232   - scope.nextWidget = function() {
233   - if (scope.widgetsCarousel.index < scope.widgetsList.length-1) {
234   - scope.widgetsCarousel.index++;
235   - }
236   - }
237   -
238   - scope.prevWidget = function() {
239   - if (scope.widgetsCarousel.index > 0) {
240   - scope.widgetsCarousel.index--;
241   - }
242   - }
243   -
244   - scope.enterWidgetMode = function() {
245   -
246   - if (scope.widgetsIndexWatch) {
247   - scope.widgetsIndexWatch();
248   - scope.widgetsIndexWatch = null;
249   - }
250   -
251   - if (scope.widgetsBundleWatch) {
252   - scope.widgetsBundleWatch();
253   - scope.widgetsBundleWatch = null;
254   - }
255   -
256   - scope.mode = 'widget';
257   - scope.checkSubscription();
258   - scope.widgetsList = [];
259   - scope.widgetsListCache = [];
260   - scope.widgetsLoaded = false;
261   - scope.widgetsCarousel = {
262   - index: 0
263   - }
264   - scope.widgetsBundle = null;
265   - scope.firstBundle = true;
266   - scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
267   -
268   - scope.aliasesInfo = {
269   - deviceAliases: {
270   - '1': {alias: scope.deviceName, deviceId: scope.deviceId}
271   - },
272   - deviceAliasesInfo: {
273   - '1': [
274   - {name: scope.deviceName, id: scope.deviceId}
275   - ]
276   - }
277   - };
278   -
279   - var dataKeyType = scope.attributeScope === types.latestTelemetry ?
280   - types.dataKeyType.timeseries : types.dataKeyType.attribute;
281   -
282   - var datasource = {
283   - type: types.datasourceType.device,
284   - deviceAliasId: '1',
285   - dataKeys: []
286   - }
287   - var i = 0;
288   - for (var attr =0; attr < scope.selectedAttributes.length;attr++) {
289   - var attribute = scope.selectedAttributes[attr];
290   - var dataKey = {
291   - name: attribute.key,
292   - label: attribute.key,
293   - type: dataKeyType,
294   - color: utils.getMaterialColor(i),
295   - settings: {},
296   - _hash: Math.random()
297   - }
298   - datasource.dataKeys.push(dataKey);
299   - i++;
300   - }
301   -
302   - scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) {
303   - if (scope.mode === 'widget' && (newVal != prevVal)) {
304   - var index = scope.widgetsCarousel.index;
305   - for (var i = 0; i < scope.widgetsList.length; i++) {
306   - scope.widgetsList[i].splice(0, scope.widgetsList[i].length);
307   - if (i === index) {
308   - scope.widgetsList[i].push(scope.widgetsListCache[i][0]);
309   - }
310   - }
311   - }
312   - });
313   -
314   - scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) {
315   - if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) {
316   - scope.widgetsList = [];
317   - scope.widgetsListCache = [];
318   - scope.widgetsCarousel.index = 0;
319   - scope.firstBundle = false;
320   - if (scope.widgetsBundle) {
321   - scope.widgetsLoaded = false;
322   - var bundleAlias = scope.widgetsBundle.alias;
323   - var isSystem = scope.widgetsBundle.tenantId.id === types.id.nullUid;
324   - widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then(
325   - function success(widgetTypes) {
326   -
327   - widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']);
328   -
329   - for (var i = 0; i < widgetTypes.length; i++) {
330   - var widgetType = widgetTypes[i];
331   - var widgetInfo = widgetService.toWidgetInfo(widgetType);
332   - if (widgetInfo.type !== types.widgetType.static.value) {
333   - var sizeX = widgetInfo.sizeX * 2;
334   - var sizeY = widgetInfo.sizeY * 2;
335   - var col = Math.floor(Math.max(0, (20 - sizeX) / 2));
336   - var widget = {
337   - isSystemType: isSystem,
338   - bundleAlias: bundleAlias,
339   - typeAlias: widgetInfo.alias,
340   - type: widgetInfo.type,
341   - title: widgetInfo.widgetName,
342   - sizeX: sizeX,
343   - sizeY: sizeY,
344   - row: 0,
345   - col: col,
346   - config: angular.fromJson(widgetInfo.defaultConfig)
347   - };
348   -
349   - widget.config.title = widgetInfo.widgetName;
350   - widget.config.datasources = [datasource];
351   - var length;
352   - if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
353   - length = scope.widgetsListCache.push([widget]);
354   - scope.widgetsList.push(length === 1 ? [widget] : []);
355   - } else if (widgetInfo.type === types.widgetType.latest.value) {
356   - length = scope.widgetsListCache.push([widget]);
357   - scope.widgetsList.push(length === 1 ? [widget] : []);
358   - }
359   - }
360   - }
361   - scope.widgetsLoaded = true;
362   - }
363   - );
364   - }
365   - }
366   - });
367   - }
368   -
369   - scope.exitWidgetMode = function() {
370   - if (scope.widgetsBundleWatch) {
371   - scope.widgetsBundleWatch();
372   - scope.widgetsBundleWatch = null;
373   - }
374   - if (scope.widgetsIndexWatch) {
375   - scope.widgetsIndexWatch();
376   - scope.widgetsIndexWatch = null;
377   - }
378   - scope.selectedWidgetsBundleAlias = null;
379   - scope.mode = 'default';
380   - scope.getDeviceAttributes(true);
381   - }
382   -
383   - scope.getServerTimeDiff = function() {
384   - return dashboardService.getServerTimeDiff();
385   - }
386   -
387   - scope.addWidgetToDashboard = function($event) {
388   - if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) {
389   - var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0];
390   - $event.stopPropagation();
391   - $mdDialog.show({
392   - controller: 'AddWidgetToDashboardDialogController',
393   - controllerAs: 'vm',
394   - templateUrl: addWidgetToDashboardDialogTemplate,
395   - parent: angular.element($document[0].body),
396   - locals: {deviceId: scope.deviceId, deviceName: scope.deviceName, widget: angular.copy(widget)},
397   - fullscreen: true,
398   - targetEvent: $event
399   - }).then(function () {
400   -
401   - });
402   - }
403   - }
404   -
405   - scope.loading = function() {
406   - return $rootScope.loading;
407   - }
408   -
409   - $compile(element.contents())(scope);
410   - }
411   -
412   - return {
413   - restrict: "E",
414   - link: linker,
415   - scope: {
416   - deviceId: '=',
417   - deviceName: '=',
418   - disableAttributeScopeSelection: '@?'
419   - }
420   - };
421   -}
1   -/**
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -@import '../../../scss/constants';
17   -
18   -$md-light: rgba(255, 255, 255, 100%);
19   -$md-edit-icon-fill: #757575;
20   -
21   -md-toolbar.md-table-toolbar.alternate {
22   - .md-toolbar-tools {
23   - md-icon {
24   - color: $md-light;
25   - }
26   - }
27   -}
28   -
29   -.md-table {
30   - .md-cell {
31   - ng-md-icon {
32   - fill: $md-edit-icon-fill;
33   - float: right;
34   - height: 16px;
35   - }
36   - }
37   -}
38   -
39   -.widgets-carousel {
40   - position: relative;
41   - margin: 0px;
42   -
43   - min-height: 150px !important;
44   -
45   - tb-dashboard {
46   - #gridster-parent {
47   - padding: 0 7px;
48   - }
49   - }
50   -}
\ No newline at end of file
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<md-content flex class="md-padding tb-absolute-fill" layout="column">
19   - <section layout="row" ng-show="!disableAttributeScopeSelection">
20   - <md-input-container class="md-block" style="width: 200px;">
21   - <label translate>attribute.attributes-scope</label>
22   - <md-select ng-model="attributeScope" ng-disabled="loading()">
23   - <md-option ng-repeat="scope in attributeScopes" ng-value="scope">
24   - {{scope.name | translate}}
25   - </md-option>
26   - </md-select>
27   - </md-input-container>
28   - </section>
29   - <div layout="column" class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}">
30   - <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
31   - && !selectedAttributes.length
32   - && query.search === null">
33   - <div class="md-toolbar-tools">
34   - <span translate>{{ attributeScope.name }}</span>
35   - <span flex></span>
36   - <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="addAttribute($event)">
37   - <md-icon>add</md-icon>
38   - <md-tooltip md-direction="top">
39   - {{ 'action.add' | translate }}
40   - </md-tooltip>
41   - </md-button>
42   - <md-button class="md-icon-button" ng-click="enterFilterMode()">
43   - <md-icon>search</md-icon>
44   - <md-tooltip md-direction="top">
45   - {{ 'action.search' | translate }}
46   - </md-tooltip>
47   - </md-button>
48   - <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="getDeviceAttributes()">
49   - <md-icon>refresh</md-icon>
50   - <md-tooltip md-direction="top">
51   - {{ 'action.refresh' | translate }}
52   - </md-tooltip>
53   - </md-button>
54   - </div>
55   - </md-toolbar>
56   - <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
57   - && !selectedAttributes.length
58   - && query.search != null">
59   - <div class="md-toolbar-tools">
60   - <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
61   - <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
62   - <md-tooltip md-direction="top">
63   - {{ 'action.search' | translate }}
64   - </md-tooltip>
65   - </md-button>
66   - <md-input-container md-theme="tb-search-input" flex>
67   - <label>&nbsp;</label>
68   - <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
69   - </md-input-container>
70   - <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitFilterMode()">
71   - <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
72   - <md-tooltip md-direction="top">
73   - {{ 'action.close' | translate }}
74   - </md-tooltip>
75   - </md-button>
76   - </div>
77   - </md-toolbar>
78   - <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='default' && selectedAttributes.length">
79   - <div class="md-toolbar-tools">
80   - <span translate="{{attributeScope === types.latestTelemetry
81   - ? 'attribute.selected-telemetry'
82   - : 'attribute.selected-attributes'}}"
83   - translate-values="{count: selectedAttributes.length}"
84   - translate-interpolation="messageformat"></span>
85   - <span flex></span>
86   - <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="deleteAttributes($event)">
87   - <md-icon>delete</md-icon>
88   - <md-tooltip md-direction="top">
89   - {{ 'action.delete' | translate }}
90   - </md-tooltip>
91   - </md-button>
92   - <md-button ng-show="attributeScope.clientSide" class="md-accent md-hue-2 md-raised" ng-click="enterWidgetMode()">
93   - <md-tooltip md-direction="top">
94   - {{ 'attribute.show-on-widget' | translate }}
95   - </md-tooltip>
96   - <md-icon>now_widgets</md-icon>
97   - <span translate>attribute.show-on-widget</span>
98   - </md-button>
99   - </div>
100   - </md-toolbar>
101   - <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='widget'">
102   - <div class="md-toolbar-tools">
103   - <div flex layout="row" layout-align="start">
104   - <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
105   - <tb-widgets-bundle-select flex-offset="5"
106   - flex
107   - ng-model="widgetsBundle"
108   - select-first-bundle="false"
109   - select-bundle-alias="selectedWidgetsBundleAlias">
110   - </tb-widgets-bundle-select>
111   - </div>
112   - <md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)">
113   - <md-tooltip md-direction="top">
114   - {{ 'attribute.add-to-dashboard' | translate }}
115   - </md-tooltip>
116   - <md-icon>dashboard</md-icon>
117   - <span translate>attribute.add-to-dashboard</span>
118   - </md-button>
119   - <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitWidgetMode()">
120   - <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
121   - <md-tooltip md-direction="top">
122   - {{ 'action.close' | translate }}
123   - </md-tooltip>
124   - </md-button>
125   - </div>
126   - </md-toolbar>
127   - <md-table-container ng-show="mode!='widget'">
128   - <table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
129   - <thead md-head md-order="query.order" md-on-reorder="onReorder">
130   - <tr md-row>
131   - <th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th>
132   - <th md-column md-order-by="key"><span>Key</span></th>
133   - <th md-column>Value</th>
134   - </tr>
135   - </thead>
136   - <tbody md-body>
137   - <tr md-row md-select="attribute" md-select-id="key" md-auto-select ng-repeat="attribute in attributes.data">
138   - <td md-cell>{{attribute.lastUpdateTs | date : 'yyyy-MM-dd HH:mm:ss'}}</td>
139   - <td md-cell>{{attribute.key}}</td>
140   - <td md-cell ng-click="editAttribute($event, attribute)">
141   - <span>{{attribute.value}}</span>
142   - <span ng-show="!attributeScope.clientSide"><ng-md-icon size="16" icon="edit"></ng-md-icon></span>
143   - </td>
144   - </tr>
145   - </tbody>
146   - </table>
147   - </md-table-container>
148   - <md-table-pagination ng-show="mode!='widget'" md-limit="query.limit" md-limit-options="[5, 10, 15]"
149   - md-page="query.page" md-total="{{attributes.count}}"
150   - md-on-paginate="onPaginate" md-page-select>
151   - </md-table-pagination>
152   - <ul flex rn-carousel ng-if="mode==='widget'" class="widgets-carousel"
153   - rn-carousel-index="widgetsCarousel.index"
154   - rn-carousel-buffered
155   - rn-carousel-transition="fadeAndSlide"
156   - rn-swipe-disabled="true">
157   - <li ng-repeat="widgets in widgetsList">
158   - <tb-dashboard
159   - aliases-info="aliasesInfo"
160   - widgets="widgets"
161   - get-st-diff="getServerTimeDiff()"
162   - columns="20"
163   - is-edit="false"
164   - is-mobile-disabled="true"
165   - is-edit-action-enabled="false"
166   - is-remove-action-enabled="false">
167   - </tb-dashboard>
168   - </li>
169   - <span translate ng-if="widgetsLoaded &&
170   - widgetsList.length === 0 &&
171   - widgetsBundle"
172   - layout-align="center center"
173   - style="text-transform: uppercase; display: flex;"
174   - class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
175   - <span translate ng-if="!widgetsBundle"
176   - layout-align="center center"
177   - style="text-transform: uppercase; display: flex;"
178   - class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
179   - <div ng-show="widgetsList.length > 1"
180   - style="position: absolute; left: 0; height: 100%;" layout="column" layout-align="center">
181   - <md-button ng-show="widgetsCarousel.index > 0"
182   - class="md-icon-button"
183   - ng-click="prevWidget()">
184   - <md-icon>navigate_before</md-icon>
185   - <md-tooltip md-direction="top">
186   - {{ 'attribute.prev-widget' | translate }}
187   - </md-tooltip>
188   - </md-button>
189   - </div>
190   - <div ng-show="widgetsList.length > 1"
191   - style="position: absolute; right: 0; height: 100%;" layout="column" layout-align="center">
192   - <md-button ng-show="widgetsCarousel.index < widgetsList.length-1"
193   - class="md-icon-button"
194   - ng-click="nextWidget()">
195   - <md-icon>navigate_next</md-icon>
196   - <md-tooltip md-direction="top">
197   - {{ 'attribute.next-widget' | translate }}
198   - </md-tooltip>
199   - </md-button>
200   - </div>
201   - <div style="position: absolute; bottom: 0; width: 100%; font-size: 24px;" layout="row" layout-align="center">
202   - <div rn-carousel-indicators
203   - ng-if="widgetsList.length > 1"
204   - slides="widgetsList"
205   - rn-carousel-index="widgetsCarousel.index">
206   - </div>
207   - </div>
208   - </ul>
209   - </div>
210   -</md-content>
1   -/*
2   - * Copyright © 2016-2017 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -/*@ngInject*/
17   -export default function EditAttributeValueController($scope, $q, $element, types, attributeValue, save) {
18   -
19   - $scope.valueTypes = types.valueType;
20   -
21   - $scope.model = {};
22   -
23   - $scope.model.value = attributeValue;
24   -
25   - if ($scope.model.value === true || $scope.model.value === false) {
26   - $scope.valueType = types.valueType.boolean;
27   - } else if (angular.isNumber($scope.model.value)) {
28   - if ($scope.model.value.toString().indexOf('.') == -1) {
29   - $scope.valueType = types.valueType.integer;
30   - } else {
31   - $scope.valueType = types.valueType.double;
32   - }
33   - } else {
34   - $scope.valueType = types.valueType.string;
35   - }
36   -
37   - $scope.submit = submit;
38   - $scope.dismiss = dismiss;
39   -
40   - function dismiss() {
41   - $element.remove();
42   - }
43   -
44   - function update() {
45   - if($scope.editDialog.$invalid) {
46   - return $q.reject();
47   - }
48   -
49   - if(angular.isFunction(save)) {
50   - return $q.when(save($scope.model));
51   - }
52   -
53   - return $q.resolve();
54   - }
55   -
56   - function submit() {
57   - update().then(function () {
58   - $scope.dismiss();
59   - });
60   - }
61   -
62   - $scope.$watch('valueType', function(newVal, prevVal) {
63   - if (newVal != prevVal) {
64   - if ($scope.valueType === types.valueType.boolean) {
65   - $scope.model.value = false;
66   - } else {
67   - $scope.model.value = null;
68   - }
69   - }
70   - });
71   -}
1   -<!--
2   -
3   - Copyright © 2016-2017 The Thingsboard Authors
4   -
5   - Licensed under the Apache License, Version 2.0 (the "License");
6   - you may not use this file except in compliance with the License.
7   - You may obtain a copy of the License at
8   -
9   - http://www.apache.org/licenses/LICENSE-2.0
10   -
11   - Unless required by applicable law or agreed to in writing, software
12   - distributed under the License is distributed on an "AS IS" BASIS,
13   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   - See the License for the specific language governing permissions and
15   - limitations under the License.
16   -
17   --->
18   -<md-edit-dialog>
19   - <form name="editDialog" ng-submit="submit()">
20   - <div layout="column" class="md-content" style="width: 400px;">
21   - <fieldset>
22   - <section layout="row">
23   - <md-input-container flex="40" class="md-block">
24   - <label translate>value.type</label>
25   - <md-select ng-model="valueType">
26   - <md-option ng-repeat="type in valueTypes" ng-value="type">
27   - <md-icon md-svg-icon="{{ type.icon }}"></md-icon>
28   - <span>{{type.name | translate}}</span>
29   - </md-option>
30   - </md-select>
31   - </md-input-container>
32   - <md-input-container ng-if="valueType===valueTypes.string" flex="60" class="md-block">
33   - <label translate>value.string-value</label>
34   - <input required name="value" ng-model="model.value">
35   - <div ng-messages="editDialog.value.$error">
36   - <div translate ng-message="required">attribute.value-required</div>
37   - </div>
38   - </md-input-container>
39   - <md-input-container ng-if="valueType===valueTypes.integer" flex="60" class="md-block">
40   - <label translate>value.integer-value</label>
41   - <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="model.value">
42   - <div ng-messages="editDialog.value.$error">
43   - <div translate ng-message="required">attribute.value-required</div>
44   - <div translate ng-message="pattern">value.invalid-integer-value</div>
45   - </div>
46   - </md-input-container>
47   - <md-input-container ng-if="valueType===valueTypes.double" flex="60" class="md-block">
48   - <label translate>value.double-value</label>
49   - <input required name="value" type="number" step="any" ng-model="model.value">
50   - <div ng-messages="editDialog.value.$error">
51   - <div translate ng-message="required">attribute.value-required</div>
52   - </div>
53   - </md-input-container>
54   - <div layout="column" layout-align="center" flex="60" ng-if="valueType===valueTypes.boolean">
55   - <md-checkbox ng-model="model.value" style="margin-bottom: 0px;">
56   - {{ (model.value ? 'value.true' : 'value.false') | translate }}
57   - </md-checkbox>
58   - </div>
59   - </section>
60   - </fieldset>
61   - </div>
62   - <div layout="row" layout-align="end" class="md-actions">
63   - <md-button ng-click="dismiss()">{{ 'action.cancel' |
64   - translate }}
65   - </md-button>
66   - <md-button ng-disabled="editDialog.$invalid || !editDialog.$dirty" type="submit"
67   - class="md-raised md-primary">
68   - {{ 'action.update' | translate }}
69   - </md-button>
70   - </div>
71   - </form>
72   -</md-edit-dialog>
\ No newline at end of file
... ... @@ -15,5 +15,8 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
19   -<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
  18 +<div flex layout="column" style="margin-top: -10px;">
  19 + <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
  20 + <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
  21 + <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
  22 +</div>
... ...
... ... @@ -66,6 +66,13 @@
66 66 <div translate ng-message="required">device.name-required</div>
67 67 </div>
68 68 </md-input-container>
  69 + <tb-entity-subtype-autocomplete
  70 + ng-disabled="loading || !isEdit"
  71 + tb-required="true"
  72 + the-form="theForm"
  73 + ng-model="device.type"
  74 + entity-type="types.entityType.device">
  75 + </tb-entity-subtype-autocomplete>
69 76 <md-input-container class="md-block">
70 77 <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
71 78 ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
... ...
... ... @@ -48,7 +48,8 @@ export function DeviceCardController(types) {
48 48
49 49
50 50 /*@ngInject*/
51   -export function DeviceController(userService, deviceService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
  51 +export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams,
  52 + $document, $mdDialog, $q, $translate, types) {
52 53
53 54 var customerId = $stateParams.customerId;
54 55
... ... @@ -131,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s
131 132 }
132 133
133 134 if (vm.devicesScope === 'tenant') {
134   - fetchDevicesFunction = function (pageLink) {
135   - return deviceService.getTenantDevices(pageLink, true);
  135 + fetchDevicesFunction = function (pageLink, deviceType) {
  136 + return deviceService.getTenantDevices(pageLink, true, null, deviceType);
136 137 };
137 138 deleteDeviceFunction = function (deviceId) {
138 139 return deviceService.deleteDevice(deviceId);
... ... @@ -242,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s
242 243
243 244
244 245 } else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') {
245   - fetchDevicesFunction = function (pageLink) {
246   - return deviceService.getCustomerDevices(customerId, pageLink, true);
  246 + fetchDevicesFunction = function (pageLink, deviceType) {
  247 + return deviceService.getCustomerDevices(customerId, pageLink, true, null, deviceType);
247 248 };
248 249 deleteDeviceFunction = function (deviceId) {
249 250 return deviceService.unassignDeviceFromCustomer(deviceId);
... ... @@ -368,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s
368 369 var deferred = $q.defer();
369 370 deviceService.saveDevice(device).then(
370 371 function success(savedDevice) {
  372 + $rootScope.$broadcast('deviceSaved');
371 373 var devices = [ savedDevice ];
372 374 customerService.applyAssignedCustomersInfo(devices).then(
373 375 function success(items) {
... ...
... ... @@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
25 25 var template = $templateCache.get(deviceFieldsetTemplate);
26 26 element.html(template);
27 27
  28 + scope.types = types;
28 29 scope.isAssignedToCustomer = false;
29 30 scope.isPublic = false;
30 31 scope.assignedCustomer = null;
... ...
... ... @@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function DeviceRoutes($stateProvider) {
  23 +export default function DeviceRoutes($stateProvider, types) {
24 24 $stateProvider
25 25 .state('home.devices', {
26 26 url: '/devices',
... ... @@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) {
37 37 data: {
38 38 devicesType: 'tenant',
39 39 searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.device,
40 42 pageTitle: 'device.devices'
41 43 },
42 44 ncyBreadcrumb: {
... ... @@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) {
58 60 data: {
59 61 devicesType: 'customer',
60 62 searchEnabled: true,
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.device,
61 65 pageTitle: 'customer.devices'
62 66 },
63 67 ncyBreadcrumb: {
... ...
... ... @@ -63,7 +63,7 @@
63 63 {{ 'action.search' | translate }}
64 64 </md-tooltip>
65 65 </md-button>
66   - <md-input-container md-theme="tb-search-input" flex>
  66 + <md-input-container flex>
67 67 <label>&nbsp;</label>
68 68 <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
69 69 </md-input-container>
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './entity-subtype-autocomplete.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
  26 +
  27 + var linker = function (scope, element, attrs, ngModelCtrl) {
  28 + var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
  29 + element.html(template);
  30 +
  31 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  32 + scope.subType = null;
  33 + scope.subTypeSearchText = '';
  34 + scope.entitySubtypes = null;
  35 +
  36 + scope.fetchSubTypes = function(searchText) {
  37 + var deferred = $q.defer();
  38 + loadSubTypes().then(
  39 + function success(subTypes) {
  40 + var result = $filter('filter')(subTypes, {'$': searchText});
  41 + if (result && result.length) {
  42 + deferred.resolve(result);
  43 + } else {
  44 + deferred.resolve([searchText]);
  45 + }
  46 + },
  47 + function fail() {
  48 + deferred.reject();
  49 + }
  50 + );
  51 + return deferred.promise;
  52 + }
  53 +
  54 + scope.subTypeSearchTextChanged = function() {
  55 + }
  56 +
  57 + scope.updateView = function () {
  58 + if (!scope.disabled) {
  59 + ngModelCtrl.$setViewValue(scope.subType);
  60 + }
  61 + }
  62 +
  63 + ngModelCtrl.$render = function () {
  64 + scope.subType = ngModelCtrl.$viewValue;
  65 + }
  66 +
  67 + scope.$watch('entityType', function () {
  68 + load();
  69 + });
  70 +
  71 + scope.$watch('subType', function (newValue, prevValue) {
  72 + if (!angular.equals(newValue, prevValue)) {
  73 + scope.updateView();
  74 + }
  75 + });
  76 +
  77 + scope.$watch('disabled', function () {
  78 + scope.updateView();
  79 + });
  80 +
  81 + function loadSubTypes() {
  82 + var deferred = $q.defer();
  83 + if (!scope.entitySubtypes) {
  84 + var entitySubtypesPromise;
  85 + if (scope.entityType == types.entityType.asset) {
  86 + entitySubtypesPromise = assetService.getAssetTypes();
  87 + } else if (scope.entityType == types.entityType.device) {
  88 + entitySubtypesPromise = deviceService.getDeviceTypes();
  89 + }
  90 + if (entitySubtypesPromise) {
  91 + entitySubtypesPromise.then(
  92 + function success(types) {
  93 + scope.entitySubtypes = [];
  94 + types.forEach(function (type) {
  95 + scope.entitySubtypes.push(type.type);
  96 + });
  97 + deferred.resolve(scope.entitySubtypes);
  98 + },
  99 + function fail() {
  100 + deferred.reject();
  101 + }
  102 + );
  103 + } else {
  104 + deferred.reject();
  105 + }
  106 + } else {
  107 + deferred.resolve(scope.entitySubtypes);
  108 + }
  109 + return deferred.promise;
  110 + }
  111 +
  112 + function load() {
  113 + if (scope.entityType == types.entityType.asset) {
  114 + scope.selectEntitySubtypeText = 'asset.select-asset-type';
  115 + scope.entitySubtypeText = 'asset.asset-type';
  116 + scope.entitySubtypeRequiredText = 'asset.asset-type-required';
  117 + } else if (scope.entityType == types.entityType.device) {
  118 + scope.selectEntitySubtypeText = 'device.select-device-type';
  119 + scope.entitySubtypeText = 'device.device-type';
  120 + scope.entitySubtypeRequiredText = 'device.device-type-required';
  121 + scope.$on('deviceSaved', function() {
  122 + scope.entitySubtypes = null;
  123 + });
  124 + }
  125 + }
  126 +
  127 + $compile(element.contents())(scope);
  128 + }
  129 +
  130 + return {
  131 + restrict: "E",
  132 + require: "^ngModel",
  133 + link: linker,
  134 + scope: {
  135 + theForm: '=?',
  136 + tbRequired: '=?',
  137 + disabled:'=ngDisabled',
  138 + entityType: "="
  139 + }
  140 + };
  141 +}
... ...
ui/src/app/entity/entity-subtype-autocomplete.scss renamed from ui/src/app/components/device-alias-select.scss
... ... @@ -13,14 +13,10 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -.tb-device-alias-autocomplete {
17   - .tb-not-found {
  16 +.tb-entity-subtype-autocomplete {
  17 + .tb-entity-subtype-item {
18 18 display: block;
19   - line-height: 1.5;
20 19 height: 48px;
21   - .tb-no-entries {
22   - line-height: 48px;
23   - }
24 20 }
25 21 li {
26 22 height: auto !important;
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-autocomplete ng-required="tbRequired"
  19 + ng-disabled="disabled"
  20 + md-no-cache="true"
  21 + md-input-name="subType"
  22 + ng-model="subType"
  23 + md-selected-item="subType"
  24 + md-search-text="subTypeSearchText"
  25 + md-search-text-change="subTypeSearchTextChanged()"
  26 + md-items="item in fetchSubTypes(subTypeSearchText)"
  27 + md-item-text="item"
  28 + md-min-length="0"
  29 + placeholder="{{ selectEntitySubtypeText | translate }}"
  30 + md-floating-label="{{ entitySubtypeText | translate }}"
  31 + md-select-on-match="true"
  32 + md-menu-class="tb-entity-subtype-autocomplete">
  33 + <md-item-template>
  34 + <div class="tb-entity-subtype-item">
  35 + <span md-highlight-text="subTypeSearchText" md-highlight-flags="^i">{{item}}</span>
  36 + </div>
  37 + </md-item-template>
  38 + <div ng-messages="theForm.subType.$error">
  39 + <div translate ng-message="required">{{ entitySubtypeRequiredText }}</div>
  40 + </div>
  41 +</md-autocomplete>
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import './entity-subtype-select.scss';
  18 +
  19 +/* eslint-disable import/no-unresolved, import/default */
  20 +
  21 +import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
  22 +
  23 +/* eslint-enable import/no-unresolved, import/default */
  24 +
  25 +/*@ngInject*/
  26 +export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
  27 +
  28 + var linker = function (scope, element, attrs, ngModelCtrl) {
  29 + var template = $templateCache.get(entitySubtypeSelectTemplate);
  30 + element.html(template);
  31 +
  32 + if (angular.isDefined(attrs.hideLabel)) {
  33 + scope.showLabel = false;
  34 + } else {
  35 + scope.showLabel = true;
  36 + }
  37 +
  38 + scope.ngModelCtrl = ngModelCtrl;
  39 +
  40 + scope.entitySubtypes = [];
  41 +
  42 + scope.subTypeName = function(subType) {
  43 + if (subType && subType.length) {
  44 + if (scope.typeTranslatePrefix) {
  45 + return $translate.instant(scope.typeTranslatePrefix + '.' + subType);
  46 + } else {
  47 + return subType;
  48 + }
  49 + } else {
  50 + return $translate.instant('entity.all-subtypes');
  51 + }
  52 + }
  53 +
  54 + scope.$watch('entityType', function () {
  55 + load();
  56 + });
  57 +
  58 + scope.$watch('entitySubtype', function (newValue, prevValue) {
  59 + if (!angular.equals(newValue, prevValue)) {
  60 + scope.updateView();
  61 + }
  62 + });
  63 +
  64 + scope.updateView = function () {
  65 + ngModelCtrl.$setViewValue(scope.entitySubtype);
  66 + };
  67 +
  68 + ngModelCtrl.$render = function () {
  69 + scope.entitySubtype = ngModelCtrl.$viewValue;
  70 + };
  71 +
  72 + function loadSubTypes() {
  73 + scope.entitySubtypes.length = 0;
  74 + var entitySubtypesPromise;
  75 + if (scope.entityType == types.entityType.asset) {
  76 + entitySubtypesPromise = assetService.getAssetTypes();
  77 + } else if (scope.entityType == types.entityType.device) {
  78 + entitySubtypesPromise = deviceService.getDeviceTypes();
  79 + }
  80 + if (entitySubtypesPromise) {
  81 + entitySubtypesPromise.then(
  82 + function success(types) {
  83 + scope.entitySubtypes.push('');
  84 + types.forEach(function(type) {
  85 + scope.entitySubtypes.push(type.type);
  86 + });
  87 + if (scope.entitySubtypes.indexOf(scope.entitySubtype) == -1) {
  88 + scope.entitySubtype = '';
  89 + }
  90 + },
  91 + function fail() {}
  92 + );
  93 + }
  94 +
  95 + }
  96 +
  97 + function load() {
  98 + if (scope.entityType == types.entityType.asset) {
  99 + scope.entitySubtypeTitle = 'asset.asset-type';
  100 + scope.entitySubtypeRequiredText = 'asset.asset-type-required';
  101 + } else if (scope.entityType == types.entityType.device) {
  102 + scope.entitySubtypeTitle = 'device.device-type';
  103 + scope.entitySubtypeRequiredText = 'device.device-type-required';
  104 + }
  105 + scope.entitySubtypes.length = 0;
  106 + if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
  107 + scope.entitySubtypesList.forEach(function(subType) {
  108 + scope.entitySubtypes.push(subType);
  109 + });
  110 + } else {
  111 + loadSubTypes();
  112 + if (scope.entityType == types.entityType.asset) {
  113 + scope.$on('assetSaved', function() {
  114 + loadSubTypes();
  115 + });
  116 + } else if (scope.entityType == types.entityType.device) {
  117 + scope.$on('deviceSaved', function() {
  118 + loadSubTypes();
  119 + });
  120 + }
  121 + }
  122 + }
  123 +
  124 + $compile(element.contents())(scope);
  125 + }
  126 +
  127 + return {
  128 + restrict: "E",
  129 + require: "^ngModel",
  130 + link: linker,
  131 + scope: {
  132 + theForm: '=?',
  133 + entityType: "=",
  134 + entitySubtypesList: "=?",
  135 + typeTranslatePrefix: "@?"
  136 + }
  137 + };
  138 +}
... ...
ui/src/app/entity/entity-subtype-select.scss renamed from ui/src/app/dashboard/device-aliases.scss
... ... @@ -14,12 +14,6 @@
14 14 * limitations under the License.
15 15 */
16 16
17   -.tb-aliases-dialog {
18   - .md-dialog-content {
19   - padding-bottom: 0px;
20   - }
21   - .tb-alias {
22   - padding: 10px 0 0 10px;
23   - margin: 5px;
24   - }
  17 +md-select.tb-entity-subtype-select {
  18 + min-width: 200px;
25 19 }
... ...
ui/src/app/entity/entity-subtype-select.tpl.html renamed from ui/src/app/dashboard/aliases-device-select-panel.tpl.html
... ... @@ -15,19 +15,14 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-content flex layout="column">
19   - <section flex layout="column">
20   - <md-content flex class="md-padding" layout="column">
21   - <div flex layout="row" ng-repeat="(aliasId, deviceAlias) in vm.deviceAliases">
22   - <md-input-container flex>
23   - <label>{{deviceAlias.alias}}</label>
24   - <md-select ng-model="vm.deviceAliases[aliasId].deviceId">
25   - <md-option ng-repeat="deviceInfo in vm.deviceAliasesInfo[aliasId]" ng-value="deviceInfo.id">
26   - {{deviceInfo.name}}
27   - </md-option>
28   - </md-select>
29   - </md-input-container>
30   - </div>
31   - </md-content>
32   - </section>
33   -</md-content>
  18 +<md-input-container>
  19 + <label ng-if="showLabel">{{ entitySubtypeTitle | translate }}</label>
  20 + <md-select name="subType" ng-model="entitySubtype" class="tb-entity-subtype-select" aria-label="{{ entitySubtypeTitle | translate }}">
  21 + <md-option ng-repeat="subType in entitySubtypes" ng-value="subType">
  22 + {{ subTypeName(subType) }}
  23 + </md-option>
  24 + </md-select>
  25 + <div ng-messages="theForm.subType.$error">
  26 + <div ng-message="required" translate>{{ entitySubtypeRequiredText }}</div>
  27 + </div>
  28 +</md-input-container>
... ...
... ... @@ -16,6 +16,8 @@
16 16
17 17 import EntityAliasesController from './entity-aliases.controller';
18 18 import EntityTypeSelectDirective from './entity-type-select.directive';
  19 +import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
  20 +import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
19 21 import EntityFilterDirective from './entity-filter.directive';
20 22 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
21 23 import AliasesEntitySelectDirective from './aliases-entity-select.directive';
... ... @@ -29,6 +31,8 @@ export default angular.module('thingsboard.entity', [])
29 31 .controller('AddAttributeDialogController', AddAttributeDialogController)
30 32 .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
31 33 .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
  34 + .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
  35 + .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
32 36 .directive('tbEntityFilter', EntityFilterDirective)
33 37 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
34 38 .directive('tbAttributeTable', AttributeTableDirective)
... ...
... ... @@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
25 25 /* eslint-disable angular/angularelement */
26 26
27 27 /*@ngInject*/
28   -export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
  28 +export default function HomeController(types, loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
29 29 $window, $log, $mdMedia, $animate, $timeout) {
30 30
31 31 var siteSideNav = $('.tb-site-sidenav', $element);
... ... @@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService,
38 38 if (angular.isUndefined($rootScope.searchConfig)) {
39 39 $rootScope.searchConfig = {
40 40 searchEnabled: false,
  41 + searchByEntitySubtype: false,
  42 + searchEntityType: null,
41 43 showSearch: false,
42   - searchText: ""
  44 + searchText: "",
  45 + searchEntitySubtype: ""
43 46 };
44 47 }
45 48
... ... @@ -47,6 +50,7 @@ export default function HomeController(loginService, userService, deviceService,
47 50 vm.isLockSidenav = false;
48 51
49 52 vm.displaySearchMode = displaySearchMode;
  53 + vm.displayEntitySubtypeSearch = displayEntitySubtypeSearch;
50 54 vm.openSidenav = openSidenav;
51 55 vm.goBack = goBack;
52 56 vm.searchTextUpdated = searchTextUpdated;
... ... @@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService,
54 58 vm.toggleFullscreen = toggleFullscreen;
55 59
56 60 $scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
  61 + watchEntitySubtype(false);
57 62 if (angular.isDefined(to.data.searchEnabled)) {
58 63 $scope.searchConfig.searchEnabled = to.data.searchEnabled;
  64 + $scope.searchConfig.searchByEntitySubtype = to.data.searchByEntitySubtype;
  65 + $scope.searchConfig.searchEntityType = to.data.searchEntityType;
59 66 if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
60 67 $scope.searchConfig.showSearch = false;
61 68 $scope.searchConfig.searchText = "";
  69 + $scope.searchConfig.searchEntitySubtype = "";
62 70 }
63 71 } else {
64 72 $scope.searchConfig.searchEnabled = false;
  73 + $scope.searchConfig.searchByEntitySubtype = false;
  74 + $scope.searchConfig.searchEntityType = null;
65 75 $scope.searchConfig.showSearch = false;
66 76 $scope.searchConfig.searchText = "";
  77 + $scope.searchConfig.searchEntitySubtype = "";
67 78 }
  79 + watchEntitySubtype($scope.searchConfig.searchByEntitySubtype);
68 80 });
69 81
70   - if ($mdMedia('gt-sm')) {
  82 + vm.isGtSm = $mdMedia('gt-sm');
  83 + if (vm.isGtSm) {
71 84 vm.isLockSidenav = true;
72 85 $animate.enabled(siteSideNav, false);
73 86 }
74 87
75 88 $scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
  89 + vm.isGtSm = isGtSm;
76 90 vm.isLockSidenav = isGtSm;
77 91 vm.isShowSidenav = isGtSm;
78 92 if (!isGtSm) {
... ... @@ -84,11 +98,28 @@ export default function HomeController(loginService, userService, deviceService,
84 98 }
85 99 });
86 100
  101 + function watchEntitySubtype(enableWatch) {
  102 + if ($scope.entitySubtypeWatch) {
  103 + $scope.entitySubtypeWatch();
  104 + }
  105 + if (enableWatch) {
  106 + $scope.entitySubtypeWatch = $scope.$watch('searchConfig.searchEntitySubtype', function (newVal, prevVal) {
  107 + if (!angular.equals(newVal, prevVal)) {
  108 + $scope.$broadcast('searchEntitySubtypeUpdated');
  109 + }
  110 + });
  111 + }
  112 + }
  113 +
87 114 function displaySearchMode() {
88 115 return $scope.searchConfig.searchEnabled &&
89 116 $scope.searchConfig.showSearch;
90 117 }
91 118
  119 + function displayEntitySubtypeSearch() {
  120 + return $scope.searchConfig.searchByEntitySubtype && vm.isGtSm;
  121 + }
  122 +
92 123 function toggleFullscreen() {
93 124 if (Fullscreen.isEnabled()) {
94 125 Fullscreen.cancel();
... ...
... ... @@ -70,3 +70,11 @@ md-icon.tb-logo-title {
70 70 z-index: 2;
71 71 white-space: nowrap;
72 72 }
  73 +
  74 +.tb-entity-subtype-search {
  75 + margin-top: 15px;
  76 +}
  77 +
  78 +.tb-entity-search {
  79 + margin-top: 34px;
  80 +}
... ...
... ... @@ -39,7 +39,7 @@
39 39 </md-sidenav>
40 40
41 41 <div flex layout="column" tabIndex="-1" role="main">
42   - <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
  42 + <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar">
43 43 <div layout="row" flex class="md-toolbar-tools">
44 44 <md-button id="main" hide-gt-sm ng-show="!forceFullscreen"
45 45 class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
... ... @@ -55,10 +55,18 @@
55 55 <div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
56 56 <span ng-cloak ncy-breadcrumb></span>
57 57 </div>
58   - <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>
59   - <label>&nbsp;</label>
60   - <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
61   - </md-input-container>
  58 + <div layout="row" ng-show="vm.displaySearchMode()" md-theme="tb-dark" flex>
  59 + <div class="tb-entity-subtype-search" layout="row" layout-align="start center" ng-if="vm.displayEntitySubtypeSearch()">
  60 + <tb-entity-subtype-select
  61 + entity-type="searchConfig.searchEntityType"
  62 + ng-model="searchConfig.searchEntitySubtype">
  63 + </tb-entity-subtype-select>
  64 + </div>
  65 + <md-input-container ng-class="{'tb-entity-search': vm.displayEntitySubtypeSearch()}" flex>
  66 + <label>&nbsp;</label>
  67 + <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
  68 + </md-input-container>
  69 + </div>
62 70 <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
63 71 <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
64 72 </md-button>
... ...
... ... @@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) {
48 48 var dashboardUser = userService.getCurrentUser();
49 49
50 50 vm.authorityName = authorityName;
51   - vm.displaySearchMode = displaySearchMode;
52 51 vm.logout = logout;
53 52 vm.openProfile = openProfile;
54 53 vm.userDisplayName = userDisplayName;
55 54
56   - function displaySearchMode() {
57   - return $scope.searchConfig.searchEnabled &&
58   - $scope.searchConfig.showSearch;
59   - }
60   -
61 55 function authorityName() {
62 56 var name = "user.anonymous";
63 57 if (dashboardUser) {
... ...
... ... @@ -124,6 +124,9 @@ export default angular.module('thingsboard.locale', [])
124 124 "unassign-from-customer": "Unassign from customer",
125 125 "delete": "Delete asset",
126 126 "asset-public": "Asset is public",
  127 + "asset-type": "Asset type",
  128 + "asset-type-required": "Asset type is required.",
  129 + "select-asset-type": "Select asset type",
127 130 "name": "Name",
128 131 "name-required": "Name is required.",
129 132 "description": "Description",
... ... @@ -477,6 +480,9 @@ export default angular.module('thingsboard.locale', [])
477 480 "rsa-key-required": "RSA public key is required.",
478 481 "secret": "Secret",
479 482 "secret-required": "Secret is required.",
  483 + "device-type": "Device type",
  484 + "device-type-required": "Device type is required.",
  485 + "select-device-type": "Select device type",
480 486 "name": "Name",
481 487 "name-required": "Name is required.",
482 488 "description": "Description",
... ... @@ -521,6 +527,7 @@ export default angular.module('thingsboard.locale', [])
521 527 "entity-list-empty": "No entities selected.",
522 528 "entity-name-filter-required": "Entity name filter is required.",
523 529 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
  530 + "all-subtypes": "All",
524 531 "type": "Type",
525 532 "type-device": "Device",
526 533 "type-asset": "Asset",
... ...