thingsboard-aplcache
Changes
ui/src/app/api/asset.service.js 24(+21 -3)
ui/src/app/api/device.service.js 24(+21 -3)
ui/src/app/app.config.js 4(+0 -4)
ui/src/app/asset/asset.controller.js 12(+7 -5)
ui/src/app/asset/asset.directive.js 1(+1 -0)
ui/src/app/asset/asset.routes.js 6(+5 -1)
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
ui/src/app/components/grid.directive.js 28(+15 -13)
ui/src/app/components/grid.tpl.html 9(+5 -4)
ui/src/app/device/device.controller.js 12(+7 -5)
ui/src/app/device/device.routes.js 6(+5 -1)
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
ui/src/app/entity/entity-subtype-select.tpl.html 27(+11 -16)
ui/src/app/entity/index.js 4(+4 -0)
ui/src/app/layout/home.controller.js 37(+34 -3)
ui/src/app/layout/home.scss 8(+8 -0)
ui/src/app/layout/home.tpl.html 18(+13 -5)
ui/src/app/locale/locale.constant.js 7(+7 -0)
Details
ui/src/app/api/asset.service.js 24(+21 -3)
diff --git a/ui/src/app/api/asset.service.js b/ui/src/app/api/asset.service.js
index f685b1e..2fbb11d 100644
--- a/ui/src/app/api/asset.service.js
+++ b/ui/src/app/api/asset.service.js
@@ -31,7 +31,8 @@ function AssetService($http, $q, customerService, userService) {
getTenantAssets: getTenantAssets,
getCustomerAssets: getCustomerAssets,
findByQuery: findByQuery,
- fetchAssetsByNameFilter: fetchAssetsByNameFilter
+ fetchAssetsByNameFilter: fetchAssetsByNameFilter,
+ getAssetTypes: getAssetTypes
}
return service;
@@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getTenantAssets(pageLink, applyCustomersInfo, config) {
+ function getTenantAssets(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
+ function getAssetTypes() {
+ var deferred = $q.defer();
+ var url = '/api/asset/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/device.service.js 24(+21 -3)
diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js
index d12d025..b1d1bb6 100644
--- a/ui/src/app/api/device.service.js
+++ b/ui/src/app/api/device.service.js
@@ -41,12 +41,13 @@ function DeviceService($http, $q, attributeService, customerService, types) {
deleteDeviceAttributes: deleteDeviceAttributes,
sendOneWayRpcCommand: sendOneWayRpcCommand,
sendTwoWayRpcCommand: sendTwoWayRpcCommand,
- findByQuery: findByQuery
+ findByQuery: findByQuery,
+ getDeviceTypes: getDeviceTypes
}
return service;
- function getTenantDevices(pageLink, applyCustomersInfo, config) {
+ function getTenantDevices(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -58,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -78,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
- function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -90,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -286,4 +293,15 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
+ function getDeviceTypes() {
+ var deferred = $q.defer();
+ var url = '/api/device/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/app.config.js 4(+0 -4)
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
index fef3273..074437d 100644
--- a/ui/src/app/app.config.js
+++ b/ui/src/app/app.config.js
@@ -160,10 +160,6 @@ export default function AppConfig($provide,
indigoTheme();
}
- $mdThemingProvider.theme('tb-search-input', 'default')
- .primaryPalette('tb-primary')
- .backgroundPalette('tb-primary');
-
$mdThemingProvider.setDefaultTheme('default');
//$mdThemingProvider.alwaysWatchTheme(true);
}
ui/src/app/asset/asset.controller.js 12(+7 -5)
diff --git a/ui/src/app/asset/asset.controller.js b/ui/src/app/asset/asset.controller.js
index d0944ae..1253891 100644
--- a/ui/src/app/asset/asset.controller.js
+++ b/ui/src/app/asset/asset.controller.js
@@ -47,7 +47,8 @@ export function AssetCardController(types) {
/*@ngInject*/
-export function AssetController(userService, assetService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta
}
if (vm.assetsScope === 'tenant') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getTenantAssets(pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getTenantAssets(pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.deleteAsset(assetId);
@@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta
} else if (vm.assetsScope === 'customer' || vm.assetsScope === 'customer_user') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getCustomerAssets(customerId, pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getCustomerAssets(customerId, pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.unassignAssetFromCustomer(assetId);
@@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta
var deferred = $q.defer();
assetService.saveAsset(asset).then(
function success(savedAsset) {
+ $rootScope.$broadcast('assetSaved');
var assets = [ savedAsset ];
customerService.applyAssignedCustomersInfo(assets).then(
function success(items) {
ui/src/app/asset/asset.directive.js 1(+1 -0)
diff --git a/ui/src/app/asset/asset.directive.js b/ui/src/app/asset/asset.directive.js
index 8c13082..7110e6a 100644
--- a/ui/src/app/asset/asset.directive.js
+++ b/ui/src/app/asset/asset.directive.js
@@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla
var template = $templateCache.get(assetFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/asset/asset.routes.js 6(+5 -1)
diff --git a/ui/src/app/asset/asset.routes.js b/ui/src/app/asset/asset.routes.js
index c9a312d..732f74c 100644
--- a/ui/src/app/asset/asset.routes.js
+++ b/ui/src/app/asset/asset.routes.js
@@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function AssetRoutes($stateProvider) {
+export default function AssetRoutes($stateProvider, types) {
$stateProvider
.state('home.assets', {
url: '/assets',
@@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'asset.assets'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'customer.assets'
},
ncyBreadcrumb: {
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
diff --git a/ui/src/app/asset/asset-card.tpl.html b/ui/src/app/asset/asset-card.tpl.html
index 3c06558..30d0483 100644
--- a/ui/src/app/asset/asset-card.tpl.html
+++ b/ui/src/app/asset/asset-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+</div>
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
diff --git a/ui/src/app/asset/asset-fieldset.tpl.html b/ui/src/app/asset/asset-fieldset.tpl.html
index 8cf0c96..d921b2e 100644
--- a/ui/src/app/asset/asset-fieldset.tpl.html
+++ b/ui/src/app/asset/asset-fieldset.tpl.html
@@ -56,13 +56,13 @@
<div translate ng-message="required">asset.name-required</div>
</div>
</md-input-container>
- <md-input-container class="md-block">
- <label translate>asset.type</label>
- <input required name="type" ng-model="asset.type">
- <div ng-messages="theForm.name.$error">
- <div translate ng-message="required">asset.type-required</div>
- </div>
- </md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="asset.type"
+ entity-type="types.entityType.asset">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>asset.description</label>
<textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
ui/src/app/components/grid.directive.js 28(+15 -13)
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js
index 5664400..296456a 100644
--- a/ui/src/app/components/grid.directive.js
+++ b/ui/src/app/components/grid.directive.js
@@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
},
getLength: function () {
- if (vm.items.hasNext) {
+ if (vm.items.hasNext && !vm.items.pending) {
return vm.items.rowData.length + pageSize;
} else {
return vm.items.rowData.length;
@@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
fetchMoreItems_: function () {
if (vm.items.hasNext && !vm.items.pending) {
- var promise = vm.fetchItemsFunc(vm.items.nextPageLink);
+ var promise = vm.fetchItemsFunc(vm.items.nextPageLink, $scope.searchConfig.searchEntitySubtype);
if (promise) {
vm.items.pending = true;
promise.then(
@@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
reload();
});
+ $scope.$on('searchEntitySubtypeUpdated', function () {
+ reload();
+ });
+
vm.onGridInited(vm);
vm.itemRows.getItemAtIndex(pageSize);
@@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
if (vm.items && vm.items.pending) {
vm.items.reloadPending = true;
} else {
- vm.items = {
- data: [],
- rowData: [],
- nextPageLink: {
- limit: pageSize,
- textSearch: $scope.searchConfig.searchText
- },
- selections: {},
- selectedCount: 0,
- hasNext: true,
- pending: false
+ vm.items.data.length = 0;
+ vm.items.rowData.length = 0;
+ vm.items.nextPageLink = {
+ limit: pageSize,
+ textSearch: $scope.searchConfig.searchText
};
+ vm.items.selections = {};
+ vm.items.selectedCount = 0;
+ vm.items.hasNext = true;
+ vm.items.pending = false;
vm.detailsConfig.isDetailsOpen = false;
vm.items.reloadPending = false;
vm.itemRows.getItemAtIndex(pageSize);
ui/src/app/components/grid.tpl.html 9(+5 -4)
diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html
index c29c2e5..24285d8 100644
--- a/ui/src/app/components/grid.tpl.html
+++ b/ui/src/app/components/grid.tpl.html
@@ -24,9 +24,8 @@
<md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
<div class="md-padding" layout="column">
<section layout="row" md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight">
- <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}">
- <md-card ng-if="rowItem[n]"
- ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="rowItem[n]">
+ <md-card ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
ng-click="vm.clickItemFunc($event, rowItem[n])">
<section layout="row" layout-wrap>
@@ -43,7 +42,7 @@
</md-card-title>
</section>
<md-card-content flex>
- <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>
+ <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>
</md-card-content>
<md-card-actions layout="row" layout-align="end end">
<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 @@
</md-card-actions>
</md-card>
</div>
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="!rowItem[n]">
+ </div>
</section>
</div>
</md-virtual-repeat-container>
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
index 151c05a..9f86b9b 100644
--- a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
@@ -58,7 +58,7 @@
{{ 'dashboard.search-states' | translate }}
</md-tooltip>
</md-button>
- <md-input-container md-theme="tb-search-input" flex>
+ <md-input-container flex>
<label> </label>
<input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/>
</md-input-container>
ui/src/app/device/device.controller.js 12(+7 -5)
diff --git a/ui/src/app/device/device.controller.js b/ui/src/app/device/device.controller.js
index 55b7083..3e8c9ac 100644
--- a/ui/src/app/device/device.controller.js
+++ b/ui/src/app/device/device.controller.js
@@ -48,7 +48,8 @@ export function DeviceCardController(types) {
/*@ngInject*/
-export function DeviceController(userService, deviceService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -131,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s
}
if (vm.devicesScope === 'tenant') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getTenantDevices(pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getTenantDevices(pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.deleteDevice(deviceId);
@@ -242,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s
} else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getCustomerDevices(customerId, pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getCustomerDevices(customerId, pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.unassignDeviceFromCustomer(deviceId);
@@ -368,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s
var deferred = $q.defer();
deviceService.saveDevice(device).then(
function success(savedDevice) {
+ $rootScope.$broadcast('deviceSaved');
var devices = [ savedDevice ];
customerService.applyAssignedCustomersInfo(devices).then(
function success(items) {
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
index eb50a15..eda4fb2 100644
--- a/ui/src/app/device/device.directive.js
+++ b/ui/src/app/device/device.directive.js
@@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
var template = $templateCache.get(deviceFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/device/device.routes.js 6(+5 -1)
diff --git a/ui/src/app/device/device.routes.js b/ui/src/app/device/device.routes.js
index c1f5b27..b1ca7ed 100644
--- a/ui/src/app/device/device.routes.js
+++ b/ui/src/app/device/device.routes.js
@@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function DeviceRoutes($stateProvider) {
+export default function DeviceRoutes($stateProvider, types) {
$stateProvider
.state('home.devices', {
url: '/devices',
@@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'device.devices'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'customer.devices'
},
ncyBreadcrumb: {
diff --git a/ui/src/app/device/device-card.tpl.html b/ui/src/app/device/device-card.tpl.html
index bb84af7..58f4c85 100644
--- a/ui/src/app/device/device-card.tpl.html
+++ b/ui/src/app/device/device-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+</div>
diff --git a/ui/src/app/device/device-fieldset.tpl.html b/ui/src/app/device/device-fieldset.tpl.html
index fd6c9d1..767e47a 100644
--- a/ui/src/app/device/device-fieldset.tpl.html
+++ b/ui/src/app/device/device-fieldset.tpl.html
@@ -66,6 +66,13 @@
<div translate ng-message="required">device.name-required</div>
</div>
</md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="device.type"
+ entity-type="types.entityType.device">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html
index bf3b8cd..3b10902 100644
--- a/ui/src/app/entity/attribute/attribute-table.tpl.html
+++ b/ui/src/app/entity/attribute/attribute-table.tpl.html
@@ -63,7 +63,7 @@
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
- <md-input-container md-theme="tb-search-input" flex>
+ <md-input-container flex>
<label> </label>
<input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
new file mode 100644
index 0000000..98110b0
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -0,0 +1,141 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import './entity-subtype-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.subType = null;
+ scope.subTypeSearchText = '';
+ scope.entitySubtypes = null;
+
+ scope.fetchSubTypes = function(searchText) {
+ var deferred = $q.defer();
+ loadSubTypes().then(
+ function success(subTypes) {
+ var result = $filter('filter')(subTypes, {'$': searchText});
+ if (result && result.length) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([searchText]);
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ scope.subTypeSearchTextChanged = function() {
+ }
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ ngModelCtrl.$setViewValue(scope.subType);
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ scope.subType = ngModelCtrl.$viewValue;
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('subType', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+ function loadSubTypes() {
+ var deferred = $q.defer();
+ if (!scope.entitySubtypes) {
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes = [];
+ types.forEach(function (type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ deferred.resolve(scope.entitySubtypes);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.resolve(scope.entitySubtypes);
+ }
+ return deferred.promise;
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.selectEntitySubtypeText = 'asset.select-asset-type';
+ scope.entitySubtypeText = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.selectEntitySubtypeText = 'device.select-device-type';
+ scope.entitySubtypeText = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ scope.$on('deviceSaved', function() {
+ scope.entitySubtypes = null;
+ });
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled',
+ entityType: "="
+ }
+ };
+}
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.tpl.html b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
new file mode 100644
index 0000000..ce220ee
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
@@ -0,0 +1,41 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-autocomplete ng-required="tbRequired"
+ ng-disabled="disabled"
+ md-no-cache="true"
+ md-input-name="subType"
+ ng-model="subType"
+ md-selected-item="subType"
+ md-search-text="subTypeSearchText"
+ md-search-text-change="subTypeSearchTextChanged()"
+ md-items="item in fetchSubTypes(subTypeSearchText)"
+ md-item-text="item"
+ md-min-length="0"
+ placeholder="{{ selectEntitySubtypeText | translate }}"
+ md-floating-label="{{ entitySubtypeText | translate }}"
+ md-select-on-match="true"
+ md-menu-class="tb-entity-subtype-autocomplete">
+ <md-item-template>
+ <div class="tb-entity-subtype-item">
+ <span md-highlight-text="subTypeSearchText" md-highlight-flags="^i">{{item}}</span>
+ </div>
+ </md-item-template>
+ <div ng-messages="theForm.subType.$error">
+ <div translate ng-message="required">{{ entitySubtypeRequiredText }}</div>
+ </div>
+</md-autocomplete>
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
diff --git a/ui/src/app/entity/entity-subtype-select.directive.js b/ui/src/app/entity/entity-subtype-select.directive.js
new file mode 100644
index 0000000..4a2d33e
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-select.directive.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './entity-subtype-select.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeSelectTemplate);
+ element.html(template);
+
+ if (angular.isDefined(attrs.hideLabel)) {
+ scope.showLabel = false;
+ } else {
+ scope.showLabel = true;
+ }
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ scope.entitySubtypes = [];
+
+ scope.subTypeName = function(subType) {
+ if (subType && subType.length) {
+ if (scope.typeTranslatePrefix) {
+ return $translate.instant(scope.typeTranslatePrefix + '.' + subType);
+ } else {
+ return subType;
+ }
+ } else {
+ return $translate.instant('entity.all-subtypes');
+ }
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('entitySubtype', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.updateView = function () {
+ ngModelCtrl.$setViewValue(scope.entitySubtype);
+ };
+
+ ngModelCtrl.$render = function () {
+ scope.entitySubtype = ngModelCtrl.$viewValue;
+ };
+
+ function loadSubTypes() {
+ scope.entitySubtypes.length = 0;
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes.push('');
+ types.forEach(function(type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ if (scope.entitySubtypes.indexOf(scope.entitySubtype) == -1) {
+ scope.entitySubtype = '';
+ }
+ },
+ function fail() {}
+ );
+ }
+
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.entitySubtypeTitle = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.entitySubtypeTitle = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ }
+ scope.entitySubtypes.length = 0;
+ if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
+ scope.entitySubtypesList.forEach(function(subType) {
+ scope.entitySubtypes.push(subType);
+ });
+ } else {
+ loadSubTypes();
+ if (scope.entityType == types.entityType.asset) {
+ scope.$on('assetSaved', function() {
+ loadSubTypes();
+ });
+ } else if (scope.entityType == types.entityType.device) {
+ scope.$on('deviceSaved', function() {
+ loadSubTypes();
+ });
+ }
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ entityType: "=",
+ entitySubtypesList: "=?",
+ typeTranslatePrefix: "@?"
+ }
+ };
+}
ui/src/app/entity/index.js 4(+4 -0)
diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js
index 07c0862..8423f11 100644
--- a/ui/src/app/entity/index.js
+++ b/ui/src/app/entity/index.js
@@ -16,6 +16,8 @@
import EntityAliasesController from './entity-aliases.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
+import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
+import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
import EntityFilterDirective from './entity-filter.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
@@ -29,6 +31,8 @@ export default angular.module('thingsboard.entity', [])
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
+ .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
+ .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
ui/src/app/layout/home.controller.js 37(+34 -3)
diff --git a/ui/src/app/layout/home.controller.js b/ui/src/app/layout/home.controller.js
index 4979501..5091da8 100644
--- a/ui/src/app/layout/home.controller.js
+++ b/ui/src/app/layout/home.controller.js
@@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
/* eslint-disable angular/angularelement */
/*@ngInject*/
-export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
+export default function HomeController(types, loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
$window, $log, $mdMedia, $animate, $timeout) {
var siteSideNav = $('.tb-site-sidenav', $element);
@@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService,
if (angular.isUndefined($rootScope.searchConfig)) {
$rootScope.searchConfig = {
searchEnabled: false,
+ searchByEntitySubtype: false,
+ searchEntityType: null,
showSearch: false,
- searchText: ""
+ searchText: "",
+ searchEntitySubtype: ""
};
}
@@ -47,6 +50,7 @@ export default function HomeController(loginService, userService, deviceService,
vm.isLockSidenav = false;
vm.displaySearchMode = displaySearchMode;
+ vm.displayEntitySubtypeSearch = displayEntitySubtypeSearch;
vm.openSidenav = openSidenav;
vm.goBack = goBack;
vm.searchTextUpdated = searchTextUpdated;
@@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService,
vm.toggleFullscreen = toggleFullscreen;
$scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
+ watchEntitySubtype(false);
if (angular.isDefined(to.data.searchEnabled)) {
$scope.searchConfig.searchEnabled = to.data.searchEnabled;
+ $scope.searchConfig.searchByEntitySubtype = to.data.searchByEntitySubtype;
+ $scope.searchConfig.searchEntityType = to.data.searchEntityType;
if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
} else {
$scope.searchConfig.searchEnabled = false;
+ $scope.searchConfig.searchByEntitySubtype = false;
+ $scope.searchConfig.searchEntityType = null;
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
+ watchEntitySubtype($scope.searchConfig.searchByEntitySubtype);
});
- if ($mdMedia('gt-sm')) {
+ vm.isGtSm = $mdMedia('gt-sm');
+ if (vm.isGtSm) {
vm.isLockSidenav = true;
$animate.enabled(siteSideNav, false);
}
$scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
+ vm.isGtSm = isGtSm;
vm.isLockSidenav = isGtSm;
vm.isShowSidenav = isGtSm;
if (!isGtSm) {
@@ -84,11 +98,28 @@ export default function HomeController(loginService, userService, deviceService,
}
});
+ function watchEntitySubtype(enableWatch) {
+ if ($scope.entitySubtypeWatch) {
+ $scope.entitySubtypeWatch();
+ }
+ if (enableWatch) {
+ $scope.entitySubtypeWatch = $scope.$watch('searchConfig.searchEntitySubtype', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ $scope.$broadcast('searchEntitySubtypeUpdated');
+ }
+ });
+ }
+ }
+
function displaySearchMode() {
return $scope.searchConfig.searchEnabled &&
$scope.searchConfig.showSearch;
}
+ function displayEntitySubtypeSearch() {
+ return $scope.searchConfig.searchByEntitySubtype && vm.isGtSm;
+ }
+
function toggleFullscreen() {
if (Fullscreen.isEnabled()) {
Fullscreen.cancel();
ui/src/app/layout/home.scss 8(+8 -0)
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index edcdbc8..a809cf6 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -70,3 +70,11 @@ md-icon.tb-logo-title {
z-index: 2;
white-space: nowrap;
}
+
+.tb-entity-subtype-search {
+ margin-top: 15px;
+}
+
+.tb-entity-search {
+ margin-top: 34px;
+}
ui/src/app/layout/home.tpl.html 18(+13 -5)
diff --git a/ui/src/app/layout/home.tpl.html b/ui/src/app/layout/home.tpl.html
index bfb37eb..8e642eb 100644
--- a/ui/src/app/layout/home.tpl.html
+++ b/ui/src/app/layout/home.tpl.html
@@ -39,7 +39,7 @@
</md-sidenav>
<div flex layout="column" tabIndex="-1" role="main">
- <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
+ <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar">
<div layout="row" flex class="md-toolbar-tools">
<md-button id="main" hide-gt-sm ng-show="!forceFullscreen"
class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
@@ -55,10 +55,18 @@
<div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
<span ng-cloak ncy-breadcrumb></span>
</div>
- <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>
- <label> </label>
- <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
- </md-input-container>
+ <div layout="row" ng-show="vm.displaySearchMode()" md-theme="tb-dark" flex>
+ <div class="tb-entity-subtype-search" layout="row" layout-align="start center" ng-if="vm.displayEntitySubtypeSearch()">
+ <tb-entity-subtype-select
+ entity-type="searchConfig.searchEntityType"
+ ng-model="searchConfig.searchEntitySubtype">
+ </tb-entity-subtype-select>
+ </div>
+ <md-input-container ng-class="{'tb-entity-search': vm.displayEntitySubtypeSearch()}" flex>
+ <label> </label>
+ <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ </div>
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
</md-button>
diff --git a/ui/src/app/layout/user-menu.directive.js b/ui/src/app/layout/user-menu.directive.js
index 6d09e67..53e8c45 100644
--- a/ui/src/app/layout/user-menu.directive.js
+++ b/ui/src/app/layout/user-menu.directive.js
@@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) {
var dashboardUser = userService.getCurrentUser();
vm.authorityName = authorityName;
- vm.displaySearchMode = displaySearchMode;
vm.logout = logout;
vm.openProfile = openProfile;
vm.userDisplayName = userDisplayName;
- function displaySearchMode() {
- return $scope.searchConfig.searchEnabled &&
- $scope.searchConfig.showSearch;
- }
-
function authorityName() {
var name = "user.anonymous";
if (dashboardUser) {
ui/src/app/locale/locale.constant.js 7(+7 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 622754c..50f6d8f 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -124,6 +124,9 @@ export default angular.module('thingsboard.locale', [])
"unassign-from-customer": "Unassign from customer",
"delete": "Delete asset",
"asset-public": "Asset is public",
+ "asset-type": "Asset type",
+ "asset-type-required": "Asset type is required.",
+ "select-asset-type": "Select asset type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -477,6 +480,9 @@ export default angular.module('thingsboard.locale', [])
"rsa-key-required": "RSA public key is required.",
"secret": "Secret",
"secret-required": "Secret is required.",
+ "device-type": "Device type",
+ "device-type-required": "Device type is required.",
+ "select-device-type": "Select device type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -521,6 +527,7 @@ export default angular.module('thingsboard.locale', [])
"entity-list-empty": "No entities selected.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
+ "all-subtypes": "All",
"type": "Type",
"type-device": "Device",
"type-asset": "Asset",