From 40017c46dfc8ec59e093cc91bc83f0eed6a914c8 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 6 Sep 2018 10:23:58 +0300 Subject: [PATCH] Entity View feature - UI --- ui/src/app/api/entity-view.service.js | 237 +++++++++ ui/src/app/app.js | 2 + ui/src/app/common/types.constant.js | 3 +- .../app/entity-view/add-entity-view.tpl.html | 45 ++ ...add-entity-views-to-customer.controller.js | 123 +++++ .../add-entity-views-to-customer.tpl.html | 77 +++ .../assign-to-customer.controller.js | 123 +++++ .../entity-view/assign-to-customer.tpl.html | 76 +++ .../app/entity-view/entity-view-card.tpl.html | 22 + .../entity-view/entity-view-fieldset.tpl.html | 64 +++ .../app/entity-view/entity-view.controller.js | 483 ++++++++++++++++++ .../app/entity-view/entity-view.directive.js | 67 +++ ui/src/app/entity-view/entity-view.routes.js | 72 +++ ui/src/app/entity-view/entity-views.tpl.html | 83 +++ ui/src/app/entity-view/index.js | 41 ++ ui/src/app/locale/locale.constant-en_US.json | 73 +++ 16 files changed, 1590 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/api/entity-view.service.js create mode 100644 ui/src/app/entity-view/add-entity-view.tpl.html create mode 100644 ui/src/app/entity-view/add-entity-views-to-customer.controller.js create mode 100644 ui/src/app/entity-view/add-entity-views-to-customer.tpl.html create mode 100644 ui/src/app/entity-view/assign-to-customer.controller.js create mode 100644 ui/src/app/entity-view/assign-to-customer.tpl.html create mode 100644 ui/src/app/entity-view/entity-view-card.tpl.html create mode 100644 ui/src/app/entity-view/entity-view-fieldset.tpl.html create mode 100644 ui/src/app/entity-view/entity-view.controller.js create mode 100644 ui/src/app/entity-view/entity-view.directive.js create mode 100644 ui/src/app/entity-view/entity-view.routes.js create mode 100644 ui/src/app/entity-view/entity-views.tpl.html create mode 100644 ui/src/app/entity-view/index.js diff --git a/ui/src/app/api/entity-view.service.js b/ui/src/app/api/entity-view.service.js new file mode 100644 index 0000000000..9bd8f8db08 --- /dev/null +++ b/ui/src/app/api/entity-view.service.js @@ -0,0 +1,237 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import thingsboardTypes from '../common/types.constant'; + +export default angular.module('thingsboard.api.entityView', [thingsboardTypes]) + .factory('entityViewService', EntityViewService) + .name; + +/*@ngInject*/ +function EntityViewService($http, $q, $window, userService, attributeService, customerService, types) { + + var service = { + assignEntityViewToCustomer: assignEntityViewToCustomer, + deleteEntityView: deleteEntityView, + getCustomerEntityViews: getCustomerEntityViews, + getEntityView: getEntityView, + getEntityViews: getEntityViews, + getTenantEntityViews: getTenantEntityViews, + saveEntityView: saveEntityView, + unassignEntityViewFromCustomer: unassignEntityViewFromCustomer, + getEntityViewAttributes: getEntityViewAttributes, + subscribeForEntityViewAttributes: subscribeForEntityViewAttributes, + unsubscribeForEntityViewAttributes: unsubscribeForEntityViewAttributes, + findByQuery: findByQuery, + getEntityViewTypes: getEntityViewTypes + } + + return service; + + function getTenantEntityViews(pageLink, applyCustomersInfo, config, type) { + var deferred = $q.defer(); + var url = '/api/tenant/entityViews?limit=' + pageLink.limit; + if (angular.isDefined(pageLink.textSearch)) { + url += '&textSearch=' + pageLink.textSearch; + } + if (angular.isDefined(pageLink.idOffset)) { + url += '&idOffset=' + pageLink.idOffset; + } + 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( + function success(data) { + response.data.data = data; + deferred.resolve(response.data); + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.resolve(response.data); + } + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function getCustomerEntityViews(customerId, pageLink, applyCustomersInfo, config, type) { + var deferred = $q.defer(); + var url = '/api/customer/' + customerId + '/entityViews?limit=' + pageLink.limit; + if (angular.isDefined(pageLink.textSearch)) { + url += '&textSearch=' + pageLink.textSearch; + } + if (angular.isDefined(pageLink.idOffset)) { + url += '&idOffset=' + pageLink.idOffset; + } + 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( + function success(data) { + response.data.data = data; + deferred.resolve(response.data); + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.resolve(response.data); + } + }, function fail() { + deferred.reject(); + }); + + return deferred.promise; + } + + function getEntityView(entityViewId, ignoreErrors, config) { + var deferred = $q.defer(); + var url = '/api/entityView/' + entityViewId; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.get(url, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail(response) { + deferred.reject(response.data); + }); + return deferred.promise; + } + + function getEntityViews(entityViewIds, config) { + var deferred = $q.defer(); + var ids = ''; + for (var i=0;i0) { + ids += ','; + } + ids += entityViewIds[i]; + } + var url = '/api/entityViews?entityViewIds=' + ids; + $http.get(url, config).then(function success(response) { + var entityViews = response.data; + entityViews.sort(function (entityView1, entityView2) { + var id1 = entityView1.id.id; + var id2 = entityView2.id.id; + var index1 = entityViewIds.indexOf(id1); + var index2 = entityViewIds.indexOf(id2); + return index1 - index2; + }); + deferred.resolve(entityViews); + }, function fail(response) { + deferred.reject(response.data); + }); + return deferred.promise; + } + + function saveEntityView(entityView) { + var deferred = $q.defer(); + var url = '/api/entityView'; + $http.post(url, entityView).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function deleteEntityView(entityViewId) { + var deferred = $q.defer(); + var url = '/api/entityView/' + entityViewId; + $http.delete(url).then(function success() { + deferred.resolve(); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function assignEntityViewToCustomer(customerId, entityViewId) { + var deferred = $q.defer(); + var url = '/api/customer/' + customerId + '/entityView/' + entityViewId; + $http.post(url, null).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function unassignEntityViewFromCustomer(entityViewId) { + var deferred = $q.defer(); + var url = '/api/customer/entityView/' + entityViewId; + $http.delete(url).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function getEntityViewAttributes(entityViewId, attributeScope, query, successCallback, config) { + return attributeService.getEntityAttributes(types.entityType.entityView, entityViewId, attributeScope, query, successCallback, config); + } + + function subscribeForEntityViewAttributes(entityViewId, attributeScope) { + return attributeService.subscribeForEntityAttributes(types.entityType.entityView, entityViewId, attributeScope); + } + + function unsubscribeForEntityViewAttributes(subscriptionId) { + attributeService.unsubscribeForEntityAttributes(subscriptionId); + } + + function findByQuery(query, ignoreErrors, config) { + var deferred = $q.defer(); + var url = '/api/entityViews'; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.post(url, query, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function getEntityViewTypes(config) { + var deferred = $q.defer(); + var url = '/api/entityView/types'; + $http.get(url, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + +} diff --git a/ui/src/app/app.js b/ui/src/app/app.js index c8cdeb02b0..a3a179eb12 100644 --- a/ui/src/app/app.js +++ b/ui/src/app/app.js @@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service'; import thingsboardHome from './layout'; import thingsboardApiLogin from './api/login.service'; import thingsboardApiDevice from './api/device.service'; +import thingsboardApiEntityView from './api/entity-view.service'; import thingsboardApiUser from './api/user.service'; import thingsboardApiEntityRelation from './api/entity-relation.service'; import thingsboardApiAsset from './api/asset.service'; @@ -133,6 +134,7 @@ angular.module('thingsboard', [ thingsboardHome, thingsboardApiLogin, thingsboardApiDevice, + thingsboardApiEntityView, thingsboardApiUser, thingsboardApiEntityRelation, thingsboardApiAsset, diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 1e34577119..1d1b7171c3 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', []) dashboard: "DASHBOARD", alarm: "ALARM", rulechain: "RULE_CHAIN", - rulenode: "RULE_NODE" + rulenode: "RULE_NODE", + entityview: "ENTITY_VIEW" }, aliasEntityType: { current_customer: "CURRENT_CUSTOMER" diff --git a/ui/src/app/entity-view/add-entity-view.tpl.html b/ui/src/app/entity-view/add-entity-view.tpl.html new file mode 100644 index 0000000000..48a1788ed0 --- /dev/null +++ b/ui/src/app/entity-view/add-entity-view.tpl.html @@ -0,0 +1,45 @@ + + +
+ +
+

entity-view.add

+ +
+ + + +
+
+ + + +
+ +
+
+ + + + {{ 'action.add' | translate }} + + {{ 'action.cancel' | translate }} + +
+
\ No newline at end of file diff --git a/ui/src/app/entity-view/add-entity-views-to-customer.controller.js b/ui/src/app/entity-view/add-entity-views-to-customer.controller.js new file mode 100644 index 0000000000..8e39546ffd --- /dev/null +++ b/ui/src/app/entity-view/add-entity-views-to-customer.controller.js @@ -0,0 +1,123 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*@ngInject*/ +export default function AddEntityViewsToCustomerController(entityViewService, $mdDialog, $q, customerId, entityViews) { + + var vm = this; + + vm.entityViews = entityViews; + vm.searchText = ''; + + vm.assign = assign; + vm.cancel = cancel; + vm.hasData = hasData; + vm.noData = noData; + vm.searchEntityViewTextUpdated = searchEntityViewTextUpdated; + vm.toggleEntityViewSelection = toggleEntityViewSelection; + + vm.theEntityViews = { + getItemAtIndex: function (index) { + if (index > vm.entityViews.data.length) { + vm.theEntityViews.fetchMoreItems_(index); + return null; + } + var item = vm.entityViews.data[index]; + if (item) { + item.indexNumber = index + 1; + } + return item; + }, + + getLength: function () { + if (vm.entityViews.hasNext) { + return vm.entityViews.data.length + vm.entityViews.nextPageLink.limit; + } else { + return vm.entityViews.data.length; + } + }, + + fetchMoreItems_: function () { + if (vm.entityViews.hasNext && !vm.entityViews.pending) { + vm.entityViews.pending = true; + entityViewService.getTenantEntityViews(vm.entityViews.nextPageLink, false).then( + function success(entityViews) { + vm.entityViews.data = vm.entityViews.data.concat(entityViews.data); + vm.entityViews.nextPageLink = entityViews.nextPageLink; + vm.entityViews.hasNext = entityViews.hasNext; + if (vm.entityViews.hasNext) { + vm.entityViews.nextPageLink.limit = vm.entityViews.pageSize; + } + vm.entityViews.pending = false; + }, + function fail() { + vm.entityViews.hasNext = false; + vm.entityViews.pending = false; + }); + } + } + }; + + function cancel () { + $mdDialog.cancel(); + } + + function assign() { + var tasks = []; + for (var entityViewId in vm.entityViews.selections) { + tasks.push(entityViewService.assignEntityViewToCustomer(customerId, entityViewId)); + } + $q.all(tasks).then(function () { + $mdDialog.hide(); + }); + } + + function noData() { + return vm.entityViews.data.length == 0 && !vm.entityViews.hasNext; + } + + function hasData() { + return vm.entityViews.data.length > 0; + } + + function toggleEntityViewSelection($event, entityView) { + $event.stopPropagation(); + var selected = angular.isDefined(entityView.selected) && entityView.selected; + entityView.selected = !selected; + if (entityView.selected) { + vm.entityViews.selections[entityView.id.id] = true; + vm.entityViews.selectedCount++; + } else { + delete vm.entityViews.selections[entityView.id.id]; + vm.entityViews.selectedCount--; + } + } + + function searchEntityViewTextUpdated() { + vm.entityViews = { + pageSize: vm.entityViews.pageSize, + data: [], + nextPageLink: { + limit: vm.entityViews.pageSize, + textSearch: vm.searchText + }, + selections: {}, + selectedCount: 0, + hasNext: true, + pending: false + }; + } + +} diff --git a/ui/src/app/entity-view/add-entity-views-to-customer.tpl.html b/ui/src/app/entity-view/add-entity-views-to-customer.tpl.html new file mode 100644 index 0000000000..1149a1d8be --- /dev/null +++ b/ui/src/app/entity-view/add-entity-views-to-customer.tpl.html @@ -0,0 +1,77 @@ + + +
+ +
+

entity-view.assign-entity-view-to-customer

+ + + + +
+
+ + + +
+
+ entity-view.assign-entity-view-to-customer-text + + + + search + + + +
+ entity-view.no-entity-views-text + + + + + {{ entityView.name }} + + + +
+
+
+
+ + + + {{ 'action.assign' | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/entity-view/assign-to-customer.controller.js b/ui/src/app/entity-view/assign-to-customer.controller.js new file mode 100644 index 0000000000..3e09ae6e4f --- /dev/null +++ b/ui/src/app/entity-view/assign-to-customer.controller.js @@ -0,0 +1,123 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*@ngInject*/ +export default function AssignEntityViewToCustomerController(customerService, entityViewService, $mdDialog, $q, entityViewIds, customers) { + + var vm = this; + + vm.customers = customers; + vm.searchText = ''; + + vm.assign = assign; + vm.cancel = cancel; + vm.isCustomerSelected = isCustomerSelected; + vm.hasData = hasData; + vm.noData = noData; + vm.searchCustomerTextUpdated = searchCustomerTextUpdated; + vm.toggleCustomerSelection = toggleCustomerSelection; + + vm.theCustomers = { + getItemAtIndex: function (index) { + if (index > vm.customers.data.length) { + vm.theCustomers.fetchMoreItems_(index); + return null; + } + var item = vm.customers.data[index]; + if (item) { + item.indexNumber = index + 1; + } + return item; + }, + + getLength: function () { + if (vm.customers.hasNext) { + return vm.customers.data.length + vm.customers.nextPageLink.limit; + } else { + return vm.customers.data.length; + } + }, + + fetchMoreItems_: function () { + if (vm.customers.hasNext && !vm.customers.pending) { + vm.customers.pending = true; + customerService.getCustomers(vm.customers.nextPageLink).then( + function success(customers) { + vm.customers.data = vm.customers.data.concat(customers.data); + vm.customers.nextPageLink = customers.nextPageLink; + vm.customers.hasNext = customers.hasNext; + if (vm.customers.hasNext) { + vm.customers.nextPageLink.limit = vm.customers.pageSize; + } + vm.customers.pending = false; + }, + function fail() { + vm.customers.hasNext = false; + vm.customers.pending = false; + }); + } + } + }; + + function cancel() { + $mdDialog.cancel(); + } + + function assign() { + var tasks = []; + for (var i=0; i < entityViewIds.length;i++) { + tasks.push(entityViewService.assignEntityViewToCustomer(vm.customers.selection.id.id, entityViewIds[i])); + } + $q.all(tasks).then(function () { + $mdDialog.hide(); + }); + } + + function noData() { + return vm.customers.data.length == 0 && !vm.customers.hasNext; + } + + function hasData() { + return vm.customers.data.length > 0; + } + + function toggleCustomerSelection($event, customer) { + $event.stopPropagation(); + if (vm.isCustomerSelected(customer)) { + vm.customers.selection = null; + } else { + vm.customers.selection = customer; + } + } + + function isCustomerSelected(customer) { + return vm.customers.selection != null && customer && + customer.id.id === vm.customers.selection.id.id; + } + + function searchCustomerTextUpdated() { + vm.customers = { + pageSize: vm.customers.pageSize, + data: [], + nextPageLink: { + limit: vm.customers.pageSize, + textSearch: vm.searchText + }, + selection: null, + hasNext: true, + pending: false + }; + } +} diff --git a/ui/src/app/entity-view/assign-to-customer.tpl.html b/ui/src/app/entity-view/assign-to-customer.tpl.html new file mode 100644 index 0000000000..7c1fa2540e --- /dev/null +++ b/ui/src/app/entity-view/assign-to-customer.tpl.html @@ -0,0 +1,76 @@ + + +
+ +
+

entity-view.assign-entity-view-to-customer

+ + + + +
+
+ + + +
+
+ entity-view.assign-to-customer-text + + + + search + + + +
+ customer.no-customers-text + + + + + {{ customer.title }} + + + +
+
+
+
+ + + + {{ 'action.assign' | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/entity-view/entity-view-card.tpl.html b/ui/src/app/entity-view/entity-view-card.tpl.html new file mode 100644 index 0000000000..1e90928540 --- /dev/null +++ b/ui/src/app/entity-view/entity-view-card.tpl.html @@ -0,0 +1,22 @@ + +
+
{{vm.item.type}}
+
{{vm.item.additionalInfo.description}}
+
{{'entity-view.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
+
diff --git a/ui/src/app/entity-view/entity-view-fieldset.tpl.html b/ui/src/app/entity-view/entity-view-fieldset.tpl.html new file mode 100644 index 0000000000..e0000c6d9d --- /dev/null +++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html @@ -0,0 +1,64 @@ + +{{ 'entity-view.assign-to-customer' | translate }} +{{'entity-view.unassign-from-customer' | translate }} +{{ 'entity-view.delete' | translate }} + +
+ + + entity-view.copyId + +
+ + + + + + +
+ + + +
+
entity-view.name-required
+
+
+ + + + + + +
+
diff --git a/ui/src/app/entity-view/entity-view.controller.js b/ui/src/app/entity-view/entity-view.controller.js new file mode 100644 index 0000000000..fd3b5a836a --- /dev/null +++ b/ui/src/app/entity-view/entity-view.controller.js @@ -0,0 +1,483 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import addEntityViewTemplate from './add-entity-view.tpl.html'; +import entityViewCard from './entity-view-card.tpl.html'; +import assignToCustomerTemplate from './assign-to-customer.tpl.html'; +import addEntityViewsToCustomerTemplate from './add-entity-views-to-customer.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export function EntityViewCardController(types) { + + var vm = this; + + vm.types = types; + + vm.isAssignedToCustomer = function() { + if (vm.item && vm.item.customerId && vm.parentCtl.entityViewsScope === 'tenant' && + vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) { + return true; + } + return false; + } + + vm.isPublic = function() { + if (vm.item && vm.item.assignedCustomer && vm.parentCtl.entityViewsScope === 'tenant' && vm.item.assignedCustomer.isPublic) { + return true; + } + return false; + } +} + + +/*@ngInject*/ +export function EntityViewController($rootScope, userService, entityViewService, customerService, $state, $stateParams, + $document, $mdDialog, $q, $translate, types) { + + var customerId = $stateParams.customerId; + + var entityViewActionsList = []; + + var entityViewGroupActionsList = []; + + var vm = this; + + vm.types = types; + + vm.entityViewGridConfig = { + deleteItemTitleFunc: deleteEntityViewTitle, + deleteItemContentFunc: deleteEntityViewText, + deleteItemsTitleFunc: deleteEntityViewsTitle, + deleteItemsActionTitleFunc: deleteEntityViewsActionTitle, + deleteItemsContentFunc: deleteEntityViewsText, + + saveItemFunc: saveEntityView, + + getItemTitleFunc: getEntityViewTitle, + + itemCardController: 'EntityViewCardController', + itemCardTemplateUrl: entityViewCard, + parentCtl: vm, + + actionsList: entityViewActionsList, + groupActionsList: entityViewGroupActionsList, + + onGridInited: gridInited, + + addItemTemplateUrl: addEntityViewTemplate, + + addItemText: function() { return $translate.instant('entity-view.add-entity-view-text') }, + noItemsText: function() { return $translate.instant('entity-view.no-entity-views-text') }, + itemDetailsText: function() { return $translate.instant('entity-view.entity-view-details') }, + isDetailsReadOnly: isCustomerUser, + isSelectionEnabled: function () { + return !isCustomerUser(); + } + }; + + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { + vm.entityViewGridConfig.items = $stateParams.items; + } + + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { + vm.entityViewGridConfig.topIndex = $stateParams.topIndex; + } + + vm.entityViewsScope = $state.$current.data.entityViewsType; + + vm.assignToCustomer = assignToCustomer; + vm.makePublic = makePublic; + vm.unassignFromCustomer = unassignFromCustomer; + + initController(); + + function initController() { + var fetchEntityViewsFunction = null; + var deleteEntityViewFunction = null; + var refreshEntityViewsParamsFunction = null; + + var user = userService.getCurrentUser(); + + if (user.authority === 'CUSTOMER_USER') { + vm.entityViewsScope = 'customer_user'; + customerId = user.customerId; + } + if (customerId) { + vm.customerEntityViewsTitle = $translate.instant('customer.entity-views'); + customerService.getShortCustomerInfo(customerId).then( + function success(info) { + if (info.isPublic) { + vm.customerEntityViewsTitle = $translate.instant('customer.public-entity-views'); + } + } + ); + } + + if (vm.entityViewsScope === 'tenant') { + fetchEntityViewsFunction = function (pageLink, entityViewType) { + return entityViewService.getTenantEntityViews(pageLink, true, null, entityViewType); + }; + deleteEntityViewFunction = function (entityViewId) { + return entityViewService.deleteEntityView(entityViewId); + }; + refreshEntityViewsParamsFunction = function() { + return {"topIndex": vm.topIndex}; + }; + + entityViewActionsList.push( + { + onAction: function ($event, item) { + assignToCustomer($event, [ item.id.id ]); + }, + name: function() { return $translate.instant('action.assign') }, + details: function() { return $translate.instant('entity-view.assign-to-customer') }, + icon: "assignment_ind", + isEnabled: function(entityView) { + return entityView && (!entityView.customerId || entityView.customerId.id === types.id.nullUid); + } + } + ); + + entityViewActionsList.push( + { + onAction: function ($event, item) { + unassignFromCustomer($event, item, false); + }, + name: function() { return $translate.instant('action.unassign') }, + details: function() { return $translate.instant('entity-view.unassign-from-customer') }, + icon: "assignment_return", + isEnabled: function(entityView) { + return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && !entityView.assignedCustomer.isPublic; + } + } + ); + + entityViewActionsList.push({ + onAction: function ($event, item) { + unassignFromCustomer($event, item, true); + }, + name: function() { return $translate.instant('action.make-private') }, + details: function() { return $translate.instant('entity-view.make-private') }, + icon: "reply", + isEnabled: function(entityView) { + return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && entityView.assignedCustomer.isPublic; + } + }); + + entityViewActionsList.push( + { + onAction: function ($event, item) { + vm.grid.deleteItem($event, item); + }, + name: function() { return $translate.instant('action.delete') }, + details: function() { return $translate.instant('entity-view.delete') }, + icon: "delete" + } + ); + + entityViewGroupActionsList.push( + { + onAction: function ($event, items) { + assignEntiyViewsToCustomer($event, items); + }, + name: function() { return $translate.instant('entity-view.assign-entity-views') }, + details: function(selectedCount) { + return $translate.instant('entity-view.assign-entity-views-text', {count: selectedCount}, "messageformat"); + }, + icon: "assignment_ind" + } + ); + + entityViewGroupActionsList.push( + { + onAction: function ($event) { + vm.grid.deleteItems($event); + }, + name: function() { return $translate.instant('entity-view.delete-entity-views') }, + details: deleteEntityViewsActionTitle, + icon: "delete" + } + ); + + + + } else if (vm.entityViewsScope === 'customer' || vm.entityViewsScope === 'customer_user') { + fetchEntityViewsFunction = function (pageLink, entityViewType) { + return entityViewService.getCustomerEntityViews(customerId, pageLink, true, null, entityViewType); + }; + deleteentityViewFunction = function (entityViewId) { + return entityViewService.unassignEntityViewFromCustomer(entityViewId); + }; + refreshentityViewsParamsFunction = function () { + return {"customerId": customerId, "topIndex": vm.topIndex}; + }; + + if (vm.entityViewsScope === 'customer') { + entityViewActionsList.push( + { + onAction: function ($event, item) { + unassignFromCustomer($event, item, false); + }, + name: function() { return $translate.instant('action.unassign') }, + details: function() { return $translate.instant('entity-view.unassign-from-customer') }, + icon: "assignment_return", + isEnabled: function(entityView) { + return entityView && !entityView.assignedCustomer.isPublic; + } + } + ); + + entityViewGroupActionsList.push( + { + onAction: function ($event, items) { + unassignEntityViewsFromCustomer($event, items); + }, + name: function() { return $translate.instant('entity-view.unassign-entity-views') }, + details: function(selectedCount) { + return $translate.instant('entity-view.unassign-entity-views-action-title', {count: selectedCount}, "messageformat"); + }, + icon: "assignment_return" + } + ); + + vm.entityViewGridConfig.addItemAction = { + onAction: function ($event) { + addEntityViewsToCustomer($event); + }, + name: function() { return $translate.instant('entity-view.assign-entity-views') }, + details: function() { return $translate.instant('entity-view.assign-new-entity-view') }, + icon: "add" + }; + + + } else if (vm.entityViewsScope === 'customer_user') { + vm.entityViewGridConfig.addItemAction = {}; + } + } + + vm.entityViewGridConfig.refreshParamsFunc = refreshentityViewsParamsFunction; + vm.entityViewGridConfig.fetchItemsFunc = fetchentityViewsFunction; + vm.entityViewGridConfig.deleteItemFunc = deleteentityViewFunction; + + } + + function deleteEntityViewTitle(entityView) { + return $translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name}); + } + + function deleteEntityViewText() { + return $translate.instant('entity-view.delete-entity-view-text'); + } + + function deleteEntityViewsTitle(selectedCount) { + return $translate.instant('entity-view.delete-entity-views-title', {count: selectedCount}, 'messageformat'); + } + + function deleteEntityViewsActionTitle(selectedCount) { + return $translate.instant('entity-view.delete-entity-views-action-title', {count: selectedCount}, 'messageformat'); + } + + function deleteEntityViewsText () { + return $translate.instant('entity-view.delete-entity-views-text'); + } + + function gridInited(grid) { + vm.grid = grid; + } + + function getEntityViewTitle(entityView) { + return entityView ? entityView.name : ''; + } + + function saveEntityView(entityView) { + var deferred = $q.defer(); + entityViewService.saveEntityView(entityView).then( + function success(savedEntityView) { + $rootScope.$broadcast('entityViewSaved'); + var entityViews = [ savedEntityView ]; + customerService.applyAssignedCustomersInfo(entityViews).then( + function success(items) { + if (items && items.length == 1) { + deferred.resolve(items[0]); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); + } + ); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + function isCustomerUser() { + return vm.entityViewsScope === 'customer_user'; + } + + function assignToCustomer($event, entityViewIds) { + if ($event) { + $event.stopPropagation(); + } + var pageSize = 10; + customerService.getCustomers({limit: pageSize, textSearch: ''}).then( + function success(_customers) { + var customers = { + pageSize: pageSize, + data: _customers.data, + nextPageLink: _customers.nextPageLink, + selection: null, + hasNext: _customers.hasNext, + pending: false + }; + if (customers.hasNext) { + customers.nextPageLink.limit = pageSize; + } + $mdDialog.show({ + controller: 'AssignEntityViewToCustomerController', + controllerAs: 'vm', + templateUrl: assignToCustomerTemplate, + locals: {entityViewIds: entityViewIds, customers: customers}, + parent: angular.element($document[0].body), + fullscreen: true, + targetEvent: $event + }).then(function () { + vm.grid.refreshList(); + }, function () { + }); + }, + function fail() { + }); + } + + function addEntityViewsToCustomer($event) { + if ($event) { + $event.stopPropagation(); + } + var pageSize = 10; + entityViewService.getTenantEntityViews({limit: pageSize, textSearch: ''}, false).then( + function success(_entityViews) { + var entityViews = { + pageSize: pageSize, + data: _entityViews.data, + nextPageLink: _entityViews.nextPageLink, + selections: {}, + selectedCount: 0, + hasNext: _entityViews.hasNext, + pending: false + }; + if (entityViews.hasNext) { + entityViews.nextPageLink.limit = pageSize; + } + $mdDialog.show({ + controller: 'AddEntityViewsToCustomerController', + controllerAs: 'vm', + templateUrl: addEntityViewsToCustomerTemplate, + locals: {customerId: customerId, entityViews: entityViews}, + parent: angular.element($document[0].body), + fullscreen: true, + targetEvent: $event + }).then(function () { + vm.grid.refreshList(); + }, function () { + }); + }, + function fail() { + }); + } + + function assignEntityViewsToCustomer($event, items) { + var entityViewIds = []; + for (var id in items.selections) { + entityViewIds.push(id); + } + assignToCustomer($event, entityViewIds); + } + + function unassignFromCustomer($event, entityView, isPublic) { + if ($event) { + $event.stopPropagation(); + } + var title; + var content; + var label; + if (isPublic) { + title = $translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name}); + content = $translate.instant('entity-view.make-private-entity-view-text'); + label = $translate.instant('entity-view.make-private'); + } else { + title = $translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name}); + content = $translate.instant('entity-view.unassign-entity-view-text'); + label = $translate.instant('entity-view.unassign-entity-view'); + } + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(label) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + entityViewService.unassignEntityViewFromCustomer(entityView.id.id).then(function success() { + vm.grid.refreshList(); + }); + }); + } + + function unassignEntityViewsFromCustomer($event, items) { + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('entity-view.unassign-entity-views-title', {count: items.selectedCount}, 'messageformat')) + .htmlContent($translate.instant('entity-view.unassign-entity-views-text')) + .ariaLabel($translate.instant('entity-view.unassign-entity-view')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var tasks = []; + for (var id in items.selections) { + tasks.push(entityViewService.unassignEntityViewFromCustomer(id)); + } + $q.all(tasks).then(function () { + vm.grid.refreshList(); + }); + }); + } + + function makePublic($event, entityView) { + if ($event) { + $event.stopPropagation(); + } + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name})) + .htmlContent($translate.instant('entity-view.make-public-entity-view-text')) + .ariaLabel($translate.instant('entity-view.make-public')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + entityViewService.makeEntityViewPublic(entityView.id.id).then(function success() { + vm.grid.refreshList(); + }); + }); + } +} diff --git a/ui/src/app/entity-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js new file mode 100644 index 0000000000..f98027062e --- /dev/null +++ b/ui/src/app/entity-view/entity-view.directive.js @@ -0,0 +1,67 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function EntityViewDirective($compile, $templateCache, toast, $translate, types, clipboardService, entityViewService, customerService) { + var linker = function (scope, element) { + var template = $templateCache.get(entityViewFieldsetTemplate); + element.html(template); + + scope.types = types; + scope.isAssignedToCustomer = false; + scope.assignedCustomer = null; + + scope.$watch('entityView', function(newVal) { + if (newVal) { + if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) { + scope.isAssignedToCustomer = true; + customerService.getShortCustomerInfo(scope.entityView.customerId.id).then( + function success(customer) { + scope.assignedCustomer = customer; + } + ); + } else { + scope.isAssignedToCustomer = false; + scope.assignedCustomer = null; + } + } + }); + + scope.onEntityViewIdCopied = function() { + toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); + }; + + $compile(element.contents())(scope); + } + return { + restrict: "E", + link: linker, + scope: { + entityView: '=', + isEdit: '=', + entityViewScope: '=', + theForm: '=', + onAssignToCustomer: '&', + onUnassignFromCustomer: '&', + onDeleteEntityView: '&' + } + }; +} diff --git a/ui/src/app/entity-view/entity-view.routes.js b/ui/src/app/entity-view/entity-view.routes.js new file mode 100644 index 0000000000..14f5079d41 --- /dev/null +++ b/ui/src/app/entity-view/entity-view.routes.js @@ -0,0 +1,72 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import entityViewsTemplate from './entity-views.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function EntityViewRoutes($stateProvider, types) { + $stateProvider + .state('home.entityViews', { + url: '/entityViews', + params: {'topIndex': 0}, + module: 'private', + auth: ['TENANT_ADMIN', 'CUSTOMER_USER'], + views: { + "content@home": { + templateUrl: entityViewsTemplate, + controller: 'EntityViewController', + controllerAs: 'vm' + } + }, + data: { + entityViewsTypes: 'tenant', + searchEnabled: true, + searchByEntitySubtype: true, + searchEntityType: types.entityType.entityview, + pageTitle: 'entity-views.entity-views' + }, + ncyBreadcrumb: { + label: '{"icon": "devices_other", "label": "entity-view.entity-views"}' + } + }) + .state('home.customers.entityViews', { + url: '/:customerId/entityViews', + params: {'topIndex': 0}, + module: 'private', + auth: ['TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: entityViewsTemplate, + controllerAs: 'vm', + controller: 'EntityViewController' + } + }, + data: { + entityViewsTypes: 'customer', + searchEnabled: true, + searchByEntitySubtype: true, + searchEntityType: types.entityType.entityview, + pageTitle: 'customer.entity-views' + }, + ncyBreadcrumb: { + label: '{"icon": "devices_other", "label": "{{ vm.customerEntityViewsTitle }}", "translate": "false"}' + } + }); + +} diff --git a/ui/src/app/entity-view/entity-views.tpl.html b/ui/src/app/entity-view/entity-views.tpl.html new file mode 100644 index 0000000000..5398449273 --- /dev/null +++ b/ui/src/app/entity-view/entity-views.tpl.html @@ -0,0 +1,83 @@ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ui/src/app/entity-view/index.js b/ui/src/app/entity-view/index.js new file mode 100644 index 0000000000..ebdd3ea282 --- /dev/null +++ b/ui/src/app/entity-view/index.js @@ -0,0 +1,41 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import uiRouter from 'angular-ui-router'; +import thingsboardGrid from '../components/grid.directive'; +import thingsboardApiUser from '../api/user.service'; +import thingsboardApiEntityView from '../api/entity-view.service'; +import thingsboardApiCustomer from '../api/customer.service'; + +import EntityViewRoutes from './entity-view.routes'; +import EntityViewCardController from './entity-view.controller'; +import AssignEntityViewToCustomerController from './assign-to-customer.controller'; +import AddEntityViewsToCustomerController from './add-entity-views-to-customer.controller'; +import EntityViewDirective from './entity-view.directive'; + +export default angular.module('thingsboard.entityView', [ + uiRouter, + thingsboardGrid, + thingsboardApiUser, + thingsboardApiEntityView, + thingsboardApiCustomer +]) + .config(EntityViewRoutes) + .controller('EntityViewController', EntityViewCardController) + .controller('EntityViewCardController', EntityViewCardController) + .controller('AssignEntityViewToCustomerController', AssignEntityViewToCustomerController) + .controller('AddEntityViewsToCustomerController', AddEntityViewsToCustomerController) + .directive('tbEntityView', EntityViewDirective) + .name; diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 6321d63f46..5e04a52a00 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -338,10 +338,12 @@ "dashboard": "Customer Dashboard", "dashboards": "Customer Dashboards", "devices": "Customer Devices", + "entity-views": "Customer Entity Views", "assets": "Customer Assets", "public-dashboards": "Public Dashboards", "public-devices": "Public Devices", "public-assets": "Public Assets", + "public-entity-views": "Public Entity Views", "add": "Add Customer", "delete": "Delete customer", "manage-customer-users": "Manage customer users", @@ -750,6 +752,77 @@ "no-entities-prompt": "No entities found", "no-data": "No data to display" }, + "entity-view": { + "entity-view": "Entity View", + "entity-views": "Entity Views", + "management": "Entity View management", + "view-entity-views": "View Entity Views", + "entity-view-alias": "Entity View alias", + "aliases": "Entity View aliases", + "no-alias-matching": "'{{alias}}' not found.", + "no-aliases-found": "No aliases found.", + "no-key-matching": "'{{key}}' not found.", + "no-keys-found": "No keys found.", + "create-new-alias": "Create a new one!", + "create-new-key": "Create a new one!", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique whithin the dashboard.", + "configure-alias": "Configure '{{alias}}' alias", + "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", + "alias": "Alias", + "alias-required": "Entity View alias is required.", + "remove-alias": "Remove entity view alias", + "add-alias": "Add entity view alias", + "name-starts-with": "Entity View name starts with", + "entity-view-list": "Entity View list", + "use-entity-view-name-filter": "Use filter", + "entity-view-list-empty": "No entity views selected.", + "entity-view-name-filter-required": "Entity view name filter is required.", + "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", + "add": "Add Entity View", + "assign-to-customer": "Assign to customer", + "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", + "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", + "no-entity-views-text": "No entity views found", + "assign-to-customer-text": "Please select the customer to assign the entity view(s)", + "entity-view-details": "Entity view details", + "add-entity-view-text": "Add new entity view", + "delete": "Delete entity view", + "assign-entity-views": "Assign entity views", + "assign-entity-views-text": "Assign { count, plural, 1 {1 entityView} other {# entityViews} } to customer", + "delete-entity-views": "Delete entity views", + "unassign-from-customer": "Unassign from customer", + "unassign-entity-views": "Unassign entity views", + "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entityView} other {# entityViews} } from customer", + "assign-new-entity-view": "Assign new entity view", + "delete-entity-view-title": "Are you sure you want to delete the entity view '{{entityViewName}}'?", + "delete-entity-view-text": "Be careful, after the confirmation the entity view and all related data will become unrecoverable.", + "delete-entity-views-title": "Are you sure you want to entity view { count, plural, 1 {1 entityView} other {# entityViews} }?", + "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entityView} other {# entityViews} }", + "delete-entity-views-text": "Be careful, after the confirmation all selected entity views will be removed and all related data will become unrecoverable.", + "unassign-entity-view-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?", + "unassign-entity-view-text": "After the confirmation the entity view will be unassigned and won't be accessible by the customer.", + "unassign-entity-view": "Unassign entity view", + "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entityView} other {# entityViews} }?", + "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.", + "entity-view-type": "Entity View type", + "entity-view-type-required": "Entity View type is required.", + "select-entity-view-type": "Select entity view type", + "enter-entity-view-type": "Enter entity view type", + "any-entity-view": "Any entity view", + "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", + "entity-view-type-list-empty": "No entity view types selected.", + "entity-view-types": "Entity View types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "events": "Events", + "details": "Details", + "copyId": "Copy entity view Id", + "assignedToCustomer": "Assigned to customer", + "unable-entity-view-device-alias-title": "Unable to delete entity view alias", + "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "select-entity-view": "Select entity view" + }, "event": { "event-type": "Event type", "type-error": "Error",