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,7 +31,8 @@ function AssetService($http, $q, customerService, userService) {
31 getTenantAssets: getTenantAssets, 31 getTenantAssets: getTenantAssets,
32 getCustomerAssets: getCustomerAssets, 32 getCustomerAssets: getCustomerAssets,
33 findByQuery: findByQuery, 33 findByQuery: findByQuery,
34 - fetchAssetsByNameFilter: fetchAssetsByNameFilter 34 + fetchAssetsByNameFilter: fetchAssetsByNameFilter,
  35 + getAssetTypes: getAssetTypes
35 } 36 }
36 37
37 return service; 38 return service;
@@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) { @@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) {
152 return deferred.promise; 153 return deferred.promise;
153 } 154 }
154 155
155 - function getTenantAssets(pageLink, applyCustomersInfo, config) { 156 + function getTenantAssets(pageLink, applyCustomersInfo, config, type) {
156 var deferred = $q.defer(); 157 var deferred = $q.defer();
157 var url = '/api/tenant/assets?limit=' + pageLink.limit; 158 var url = '/api/tenant/assets?limit=' + pageLink.limit;
158 if (angular.isDefined(pageLink.textSearch)) { 159 if (angular.isDefined(pageLink.textSearch)) {
@@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) { @@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) {
164 if (angular.isDefined(pageLink.textOffset)) { 165 if (angular.isDefined(pageLink.textOffset)) {
165 url += '&textOffset=' + pageLink.textOffset; 166 url += '&textOffset=' + pageLink.textOffset;
166 } 167 }
  168 + if (angular.isDefined(type) && type.length) {
  169 + url += '&type=' + type;
  170 + }
167 $http.get(url, config).then(function success(response) { 171 $http.get(url, config).then(function success(response) {
168 if (applyCustomersInfo) { 172 if (applyCustomersInfo) {
169 customerService.applyAssignedCustomersInfo(response.data.data).then( 173 customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) { @@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) {
184 return deferred.promise; 188 return deferred.promise;
185 } 189 }
186 190
187 - function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) { 191 + function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config, type) {
188 var deferred = $q.defer(); 192 var deferred = $q.defer();
189 var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit; 193 var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
190 if (angular.isDefined(pageLink.textSearch)) { 194 if (angular.isDefined(pageLink.textSearch)) {
@@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) { @@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) {
196 if (angular.isDefined(pageLink.textOffset)) { 200 if (angular.isDefined(pageLink.textOffset)) {
197 url += '&textOffset=' + pageLink.textOffset; 201 url += '&textOffset=' + pageLink.textOffset;
198 } 202 }
  203 + if (angular.isDefined(type) && type.length) {
  204 + url += '&type=' + type;
  205 + }
199 $http.get(url, config).then(function success(response) { 206 $http.get(url, config).then(function success(response) {
200 if (applyCustomersInfo) { 207 if (applyCustomersInfo) {
201 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then( 208 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) { @@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) {
258 return deferred.promise; 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,12 +41,13 @@ function DeviceService($http, $q, attributeService, customerService, types) {
41 deleteDeviceAttributes: deleteDeviceAttributes, 41 deleteDeviceAttributes: deleteDeviceAttributes,
42 sendOneWayRpcCommand: sendOneWayRpcCommand, 42 sendOneWayRpcCommand: sendOneWayRpcCommand,
43 sendTwoWayRpcCommand: sendTwoWayRpcCommand, 43 sendTwoWayRpcCommand: sendTwoWayRpcCommand,
44 - findByQuery: findByQuery 44 + findByQuery: findByQuery,
  45 + getDeviceTypes: getDeviceTypes
45 } 46 }
46 47
47 return service; 48 return service;
48 49
49 - function getTenantDevices(pageLink, applyCustomersInfo, config) { 50 + function getTenantDevices(pageLink, applyCustomersInfo, config, type) {
50 var deferred = $q.defer(); 51 var deferred = $q.defer();
51 var url = '/api/tenant/devices?limit=' + pageLink.limit; 52 var url = '/api/tenant/devices?limit=' + pageLink.limit;
52 if (angular.isDefined(pageLink.textSearch)) { 53 if (angular.isDefined(pageLink.textSearch)) {
@@ -58,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) { @@ -58,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
58 if (angular.isDefined(pageLink.textOffset)) { 59 if (angular.isDefined(pageLink.textOffset)) {
59 url += '&textOffset=' + pageLink.textOffset; 60 url += '&textOffset=' + pageLink.textOffset;
60 } 61 }
  62 + if (angular.isDefined(type) && type.length) {
  63 + url += '&type=' + type;
  64 + }
61 $http.get(url, config).then(function success(response) { 65 $http.get(url, config).then(function success(response) {
62 if (applyCustomersInfo) { 66 if (applyCustomersInfo) {
63 customerService.applyAssignedCustomersInfo(response.data.data).then( 67 customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -78,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) { @@ -78,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) {
78 return deferred.promise; 82 return deferred.promise;
79 } 83 }
80 84
81 - function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) { 85 + function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config, type) {
82 var deferred = $q.defer(); 86 var deferred = $q.defer();
83 var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit; 87 var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
84 if (angular.isDefined(pageLink.textSearch)) { 88 if (angular.isDefined(pageLink.textSearch)) {
@@ -90,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) { @@ -90,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
90 if (angular.isDefined(pageLink.textOffset)) { 94 if (angular.isDefined(pageLink.textOffset)) {
91 url += '&textOffset=' + pageLink.textOffset; 95 url += '&textOffset=' + pageLink.textOffset;
92 } 96 }
  97 + if (angular.isDefined(type) && type.length) {
  98 + url += '&type=' + type;
  99 + }
93 $http.get(url, config).then(function success(response) { 100 $http.get(url, config).then(function success(response) {
94 if (applyCustomersInfo) { 101 if (applyCustomersInfo) {
95 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then( 102 customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -286,4 +293,15 @@ function DeviceService($http, $q, attributeService, customerService, types) { @@ -286,4 +293,15 @@ function DeviceService($http, $q, attributeService, customerService, types) {
286 return deferred.promise; 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,10 +160,6 @@ export default function AppConfig($provide,
160 indigoTheme(); 160 indigoTheme();
161 } 161 }
162 162
163 - $mdThemingProvider.theme('tb-search-input', 'default')  
164 - .primaryPalette('tb-primary')  
165 - .backgroundPalette('tb-primary');  
166 -  
167 $mdThemingProvider.setDefaultTheme('default'); 163 $mdThemingProvider.setDefaultTheme('default');
168 //$mdThemingProvider.alwaysWatchTheme(true); 164 //$mdThemingProvider.alwaysWatchTheme(true);
169 } 165 }
@@ -15,5 +15,8 @@ @@ -15,5 +15,8 @@
15 limitations under the License. 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,13 +56,13 @@
56 <div translate ng-message="required">asset.name-required</div> 56 <div translate ng-message="required">asset.name-required</div>
57 </div> 57 </div>
58 </md-input-container> 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 <md-input-container class="md-block"> 66 <md-input-container class="md-block">
67 <label translate>asset.description</label> 67 <label translate>asset.description</label>
68 <textarea ng-model="asset.additionalInfo.description" rows="2"></textarea> 68 <textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
@@ -47,7 +47,8 @@ export function AssetCardController(types) { @@ -47,7 +47,8 @@ export function AssetCardController(types) {
47 47
48 48
49 /*@ngInject*/ 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 var customerId = $stateParams.customerId; 53 var customerId = $stateParams.customerId;
53 54
@@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta @@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta
129 } 130 }
130 131
131 if (vm.assetsScope === 'tenant') { 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 deleteAssetFunction = function (assetId) { 136 deleteAssetFunction = function (assetId) {
136 return assetService.deleteAsset(assetId); 137 return assetService.deleteAsset(assetId);
@@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta @@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta
229 230
230 231
231 } else if (vm.assetsScope === 'customer' || vm.assetsScope === 'customer_user') { 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 deleteAssetFunction = function (assetId) { 236 deleteAssetFunction = function (assetId) {
236 return assetService.unassignAssetFromCustomer(assetId); 237 return assetService.unassignAssetFromCustomer(assetId);
@@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta @@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta
333 var deferred = $q.defer(); 334 var deferred = $q.defer();
334 assetService.saveAsset(asset).then( 335 assetService.saveAsset(asset).then(
335 function success(savedAsset) { 336 function success(savedAsset) {
  337 + $rootScope.$broadcast('assetSaved');
336 var assets = [ savedAsset ]; 338 var assets = [ savedAsset ];
337 customerService.applyAssignedCustomersInfo(assets).then( 339 customerService.applyAssignedCustomersInfo(assets).then(
338 function success(items) { 340 function success(items) {
@@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla @@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla
25 var template = $templateCache.get(assetFieldsetTemplate); 25 var template = $templateCache.get(assetFieldsetTemplate);
26 element.html(template); 26 element.html(template);
27 27
  28 + scope.types = types;
28 scope.isAssignedToCustomer = false; 29 scope.isAssignedToCustomer = false;
29 scope.isPublic = false; 30 scope.isPublic = false;
30 scope.assignedCustomer = null; 31 scope.assignedCustomer = null;
@@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html'; @@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html';
20 /* eslint-enable import/no-unresolved, import/default */ 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function AssetRoutes($stateProvider) { 23 +export default function AssetRoutes($stateProvider, types) {
24 $stateProvider 24 $stateProvider
25 .state('home.assets', { 25 .state('home.assets', {
26 url: '/assets', 26 url: '/assets',
@@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) { @@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) {
37 data: { 37 data: {
38 assetsType: 'tenant', 38 assetsType: 'tenant',
39 searchEnabled: true, 39 searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.asset,
40 pageTitle: 'asset.assets' 42 pageTitle: 'asset.assets'
41 }, 43 },
42 ncyBreadcrumb: { 44 ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) { @@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) {
58 data: { 60 data: {
59 assetsType: 'customer', 61 assetsType: 'customer',
60 searchEnabled: true, 62 searchEnabled: true,
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.asset,
61 pageTitle: 'customer.assets' 65 pageTitle: 'customer.assets'
62 }, 66 },
63 ncyBreadcrumb: { 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 -}  
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 -}  
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>  
@@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
197 }, 197 },
198 198
199 getLength: function () { 199 getLength: function () {
200 - if (vm.items.hasNext) { 200 + if (vm.items.hasNext && !vm.items.pending) {
201 return vm.items.rowData.length + pageSize; 201 return vm.items.rowData.length + pageSize;
202 } else { 202 } else {
203 return vm.items.rowData.length; 203 return vm.items.rowData.length;
@@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
206 206
207 fetchMoreItems_: function () { 207 fetchMoreItems_: function () {
208 if (vm.items.hasNext && !vm.items.pending) { 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 if (promise) { 210 if (promise) {
211 vm.items.pending = true; 211 vm.items.pending = true;
212 promise.then( 212 promise.then(
@@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
433 reload(); 433 reload();
434 }); 434 });
435 435
  436 + $scope.$on('searchEntitySubtypeUpdated', function () {
  437 + reload();
  438 + });
  439 +
436 vm.onGridInited(vm); 440 vm.onGridInited(vm);
437 441
438 vm.itemRows.getItemAtIndex(pageSize); 442 vm.itemRows.getItemAtIndex(pageSize);
@@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
441 if (vm.items && vm.items.pending) { 445 if (vm.items && vm.items.pending) {
442 vm.items.reloadPending = true; 446 vm.items.reloadPending = true;
443 } else { 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 vm.detailsConfig.isDetailsOpen = false; 458 vm.detailsConfig.isDetailsOpen = false;
457 vm.items.reloadPending = false; 459 vm.items.reloadPending = false;
458 vm.itemRows.getItemAtIndex(pageSize); 460 vm.itemRows.getItemAtIndex(pageSize);
@@ -24,9 +24,8 @@ @@ -24,9 +24,8 @@
24 <md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex> 24 <md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
25 <div class="md-padding" layout="column"> 25 <div class="md-padding" layout="column">
26 <section layout="row" md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight"> 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 class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}" 29 class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
31 ng-click="vm.clickItemFunc($event, rowItem[n])"> 30 ng-click="vm.clickItemFunc($event, rowItem[n])">
32 <section layout="row" layout-wrap> 31 <section layout="row" layout-wrap>
@@ -43,7 +42,7 @@ @@ -43,7 +42,7 @@
43 </md-card-title> 42 </md-card-title>
44 </section> 43 </section>
45 <md-card-content flex> 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 </md-card-content> 46 </md-card-content>
48 <md-card-actions layout="row" layout-align="end end"> 47 <md-card-actions layout="row" layout-align="end end">
49 <md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList" 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,6 +55,8 @@
56 </md-card-actions> 55 </md-card-actions>
57 </md-card> 56 </md-card>
58 </div> 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 </section> 60 </section>
60 </div> 61 </div>
61 </md-virtual-repeat-container> 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 */  
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>  
@@ -58,7 +58,7 @@ @@ -58,7 +58,7 @@
58 {{ 'dashboard.search-states' | translate }} 58 {{ 'dashboard.search-states' | translate }}
59 </md-tooltip> 59 </md-tooltip>
60 </md-button> 60 </md-button>
61 - <md-input-container md-theme="tb-search-input" flex> 61 + <md-input-container flex>
62 <label>&nbsp;</label> 62 <label>&nbsp;</label>
63 <input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/> 63 <input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/>
64 </md-input-container> 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 -}  
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>  
@@ -15,5 +15,8 @@ @@ -15,5 +15,8 @@
15 limitations under the License. 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,6 +66,13 @@
66 <div translate ng-message="required">device.name-required</div> 66 <div translate ng-message="required">device.name-required</div>
67 </div> 67 </div>
68 </md-input-container> 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 <md-input-container class="md-block"> 76 <md-input-container class="md-block">
70 <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}" 77 <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
71 ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }} 78 ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
@@ -48,7 +48,8 @@ export function DeviceCardController(types) { @@ -48,7 +48,8 @@ export function DeviceCardController(types) {
48 48
49 49
50 /*@ngInject*/ 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 var customerId = $stateParams.customerId; 54 var customerId = $stateParams.customerId;
54 55
@@ -131,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s @@ -131,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s
131 } 132 }
132 133
133 if (vm.devicesScope === 'tenant') { 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 deleteDeviceFunction = function (deviceId) { 138 deleteDeviceFunction = function (deviceId) {
138 return deviceService.deleteDevice(deviceId); 139 return deviceService.deleteDevice(deviceId);
@@ -242,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s @@ -242,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s
242 243
243 244
244 } else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') { 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 deleteDeviceFunction = function (deviceId) { 249 deleteDeviceFunction = function (deviceId) {
249 return deviceService.unassignDeviceFromCustomer(deviceId); 250 return deviceService.unassignDeviceFromCustomer(deviceId);
@@ -368,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s @@ -368,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s
368 var deferred = $q.defer(); 369 var deferred = $q.defer();
369 deviceService.saveDevice(device).then( 370 deviceService.saveDevice(device).then(
370 function success(savedDevice) { 371 function success(savedDevice) {
  372 + $rootScope.$broadcast('deviceSaved');
371 var devices = [ savedDevice ]; 373 var devices = [ savedDevice ];
372 customerService.applyAssignedCustomersInfo(devices).then( 374 customerService.applyAssignedCustomersInfo(devices).then(
373 function success(items) { 375 function success(items) {
@@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl @@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
25 var template = $templateCache.get(deviceFieldsetTemplate); 25 var template = $templateCache.get(deviceFieldsetTemplate);
26 element.html(template); 26 element.html(template);
27 27
  28 + scope.types = types;
28 scope.isAssignedToCustomer = false; 29 scope.isAssignedToCustomer = false;
29 scope.isPublic = false; 30 scope.isPublic = false;
30 scope.assignedCustomer = null; 31 scope.assignedCustomer = null;
@@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html'; @@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html';
20 /* eslint-enable import/no-unresolved, import/default */ 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function DeviceRoutes($stateProvider) { 23 +export default function DeviceRoutes($stateProvider, types) {
24 $stateProvider 24 $stateProvider
25 .state('home.devices', { 25 .state('home.devices', {
26 url: '/devices', 26 url: '/devices',
@@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) { @@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) {
37 data: { 37 data: {
38 devicesType: 'tenant', 38 devicesType: 'tenant',
39 searchEnabled: true, 39 searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.device,
40 pageTitle: 'device.devices' 42 pageTitle: 'device.devices'
41 }, 43 },
42 ncyBreadcrumb: { 44 ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) { @@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) {
58 data: { 60 data: {
59 devicesType: 'customer', 61 devicesType: 'customer',
60 searchEnabled: true, 62 searchEnabled: true,
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.device,
61 pageTitle: 'customer.devices' 65 pageTitle: 'customer.devices'
62 }, 66 },
63 ncyBreadcrumb: { 67 ncyBreadcrumb: {
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 {{ 'action.search' | translate }} 63 {{ 'action.search' | translate }}
64 </md-tooltip> 64 </md-tooltip>
65 </md-button> 65 </md-button>
66 - <md-input-container md-theme="tb-search-input" flex> 66 + <md-input-container flex>
67 <label>&nbsp;</label> 67 <label>&nbsp;</label>
68 <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/> 68 <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
69 </md-input-container> 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,14 +13,10 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 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 display: block; 18 display: block;
19 - line-height: 1.5;  
20 height: 48px; 19 height: 48px;
21 - .tb-no-entries {  
22 - line-height: 48px;  
23 - }  
24 } 20 }
25 li { 21 li {
26 height: auto !important; 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,12 +14,6 @@
14 * limitations under the License. 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,19 +15,14 @@
15 limitations under the License. 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,6 +16,8 @@
16 16
17 import EntityAliasesController from './entity-aliases.controller'; 17 import EntityAliasesController from './entity-aliases.controller';
18 import EntityTypeSelectDirective from './entity-type-select.directive'; 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 import EntityFilterDirective from './entity-filter.directive'; 21 import EntityFilterDirective from './entity-filter.directive';
20 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; 22 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
21 import AliasesEntitySelectDirective from './aliases-entity-select.directive'; 23 import AliasesEntitySelectDirective from './aliases-entity-select.directive';
@@ -29,6 +31,8 @@ export default angular.module('thingsboard.entity', []) @@ -29,6 +31,8 @@ export default angular.module('thingsboard.entity', [])
29 .controller('AddAttributeDialogController', AddAttributeDialogController) 31 .controller('AddAttributeDialogController', AddAttributeDialogController)
30 .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController) 32 .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
31 .directive('tbEntityTypeSelect', EntityTypeSelectDirective) 33 .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
  34 + .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
  35 + .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
32 .directive('tbEntityFilter', EntityFilterDirective) 36 .directive('tbEntityFilter', EntityFilterDirective)
33 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) 37 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
34 .directive('tbAttributeTable', AttributeTableDirective) 38 .directive('tbAttributeTable', AttributeTableDirective)
@@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg'; @@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
25 /* eslint-disable angular/angularelement */ 25 /* eslint-disable angular/angularelement */
26 26
27 /*@ngInject*/ 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 $window, $log, $mdMedia, $animate, $timeout) { 29 $window, $log, $mdMedia, $animate, $timeout) {
30 30
31 var siteSideNav = $('.tb-site-sidenav', $element); 31 var siteSideNav = $('.tb-site-sidenav', $element);
@@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService, @@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService,
38 if (angular.isUndefined($rootScope.searchConfig)) { 38 if (angular.isUndefined($rootScope.searchConfig)) {
39 $rootScope.searchConfig = { 39 $rootScope.searchConfig = {
40 searchEnabled: false, 40 searchEnabled: false,
  41 + searchByEntitySubtype: false,
  42 + searchEntityType: null,
41 showSearch: false, 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,6 +50,7 @@ export default function HomeController(loginService, userService, deviceService,
47 vm.isLockSidenav = false; 50 vm.isLockSidenav = false;
48 51
49 vm.displaySearchMode = displaySearchMode; 52 vm.displaySearchMode = displaySearchMode;
  53 + vm.displayEntitySubtypeSearch = displayEntitySubtypeSearch;
50 vm.openSidenav = openSidenav; 54 vm.openSidenav = openSidenav;
51 vm.goBack = goBack; 55 vm.goBack = goBack;
52 vm.searchTextUpdated = searchTextUpdated; 56 vm.searchTextUpdated = searchTextUpdated;
@@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService, @@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService,
54 vm.toggleFullscreen = toggleFullscreen; 58 vm.toggleFullscreen = toggleFullscreen;
55 59
56 $scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) { 60 $scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
  61 + watchEntitySubtype(false);
57 if (angular.isDefined(to.data.searchEnabled)) { 62 if (angular.isDefined(to.data.searchEnabled)) {
58 $scope.searchConfig.searchEnabled = to.data.searchEnabled; 63 $scope.searchConfig.searchEnabled = to.data.searchEnabled;
  64 + $scope.searchConfig.searchByEntitySubtype = to.data.searchByEntitySubtype;
  65 + $scope.searchConfig.searchEntityType = to.data.searchEntityType;
59 if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) { 66 if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
60 $scope.searchConfig.showSearch = false; 67 $scope.searchConfig.showSearch = false;
61 $scope.searchConfig.searchText = ""; 68 $scope.searchConfig.searchText = "";
  69 + $scope.searchConfig.searchEntitySubtype = "";
62 } 70 }
63 } else { 71 } else {
64 $scope.searchConfig.searchEnabled = false; 72 $scope.searchConfig.searchEnabled = false;
  73 + $scope.searchConfig.searchByEntitySubtype = false;
  74 + $scope.searchConfig.searchEntityType = null;
65 $scope.searchConfig.showSearch = false; 75 $scope.searchConfig.showSearch = false;
66 $scope.searchConfig.searchText = ""; 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 vm.isLockSidenav = true; 84 vm.isLockSidenav = true;
72 $animate.enabled(siteSideNav, false); 85 $animate.enabled(siteSideNav, false);
73 } 86 }
74 87
75 $scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) { 88 $scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
  89 + vm.isGtSm = isGtSm;
76 vm.isLockSidenav = isGtSm; 90 vm.isLockSidenav = isGtSm;
77 vm.isShowSidenav = isGtSm; 91 vm.isShowSidenav = isGtSm;
78 if (!isGtSm) { 92 if (!isGtSm) {
@@ -84,11 +98,28 @@ export default function HomeController(loginService, userService, deviceService, @@ -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 function displaySearchMode() { 114 function displaySearchMode() {
88 return $scope.searchConfig.searchEnabled && 115 return $scope.searchConfig.searchEnabled &&
89 $scope.searchConfig.showSearch; 116 $scope.searchConfig.showSearch;
90 } 117 }
91 118
  119 + function displayEntitySubtypeSearch() {
  120 + return $scope.searchConfig.searchByEntitySubtype && vm.isGtSm;
  121 + }
  122 +
92 function toggleFullscreen() { 123 function toggleFullscreen() {
93 if (Fullscreen.isEnabled()) { 124 if (Fullscreen.isEnabled()) {
94 Fullscreen.cancel(); 125 Fullscreen.cancel();
@@ -70,3 +70,11 @@ md-icon.tb-logo-title { @@ -70,3 +70,11 @@ md-icon.tb-logo-title {
70 z-index: 2; 70 z-index: 2;
71 white-space: nowrap; 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,7 +39,7 @@
39 </md-sidenav> 39 </md-sidenav>
40 40
41 <div flex layout="column" tabIndex="-1" role="main"> 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 <div layout="row" flex class="md-toolbar-tools"> 43 <div layout="row" flex class="md-toolbar-tools">
44 <md-button id="main" hide-gt-sm ng-show="!forceFullscreen" 44 <md-button id="main" hide-gt-sm ng-show="!forceFullscreen"
45 class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}"> 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,10 +55,18 @@
55 <div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools"> 55 <div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
56 <span ng-cloak ncy-breadcrumb></span> 56 <span ng-cloak ncy-breadcrumb></span>
57 </div> 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 <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch"> 70 <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
63 <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> 71 <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
64 </md-button> 72 </md-button>
@@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) { @@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) {
48 var dashboardUser = userService.getCurrentUser(); 48 var dashboardUser = userService.getCurrentUser();
49 49
50 vm.authorityName = authorityName; 50 vm.authorityName = authorityName;
51 - vm.displaySearchMode = displaySearchMode;  
52 vm.logout = logout; 51 vm.logout = logout;
53 vm.openProfile = openProfile; 52 vm.openProfile = openProfile;
54 vm.userDisplayName = userDisplayName; 53 vm.userDisplayName = userDisplayName;
55 54
56 - function displaySearchMode() {  
57 - return $scope.searchConfig.searchEnabled &&  
58 - $scope.searchConfig.showSearch;  
59 - }  
60 -  
61 function authorityName() { 55 function authorityName() {
62 var name = "user.anonymous"; 56 var name = "user.anonymous";
63 if (dashboardUser) { 57 if (dashboardUser) {
@@ -124,6 +124,9 @@ export default angular.module('thingsboard.locale', []) @@ -124,6 +124,9 @@ export default angular.module('thingsboard.locale', [])
124 "unassign-from-customer": "Unassign from customer", 124 "unassign-from-customer": "Unassign from customer",
125 "delete": "Delete asset", 125 "delete": "Delete asset",
126 "asset-public": "Asset is public", 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 "name": "Name", 130 "name": "Name",
128 "name-required": "Name is required.", 131 "name-required": "Name is required.",
129 "description": "Description", 132 "description": "Description",
@@ -477,6 +480,9 @@ export default angular.module('thingsboard.locale', []) @@ -477,6 +480,9 @@ export default angular.module('thingsboard.locale', [])
477 "rsa-key-required": "RSA public key is required.", 480 "rsa-key-required": "RSA public key is required.",
478 "secret": "Secret", 481 "secret": "Secret",
479 "secret-required": "Secret is required.", 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 "name": "Name", 486 "name": "Name",
481 "name-required": "Name is required.", 487 "name-required": "Name is required.",
482 "description": "Description", 488 "description": "Description",
@@ -521,6 +527,7 @@ export default angular.module('thingsboard.locale', []) @@ -521,6 +527,7 @@ export default angular.module('thingsboard.locale', [])
521 "entity-list-empty": "No entities selected.", 527 "entity-list-empty": "No entities selected.",
522 "entity-name-filter-required": "Entity name filter is required.", 528 "entity-name-filter-required": "Entity name filter is required.",
523 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", 529 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
  530 + "all-subtypes": "All",
524 "type": "Type", 531 "type": "Type",
525 "type-device": "Device", 532 "type-device": "Device",
526 "type-asset": "Asset", 533 "type-asset": "Asset",