thingsboard-memoizeit
Changes
ui/src/app/api/entity-view.service.js 237(+237 -0)
ui/src/app/app.js 2(+2 -0)
ui/src/app/common/types.constant.js 3(+2 -1)
ui/src/app/entity-view/entity-view.controller.js 483(+483 -0)
ui/src/app/entity-view/entity-view.routes.js 72(+72 -0)
ui/src/app/entity-view/entity-views.tpl.html 83(+83 -0)
ui/src/app/entity-view/index.js 41(+41 -0)
ui/src/app/locale/locale.constant-en_US.json 73(+73 -0)
Details
ui/src/app/api/entity-view.service.js 237(+237 -0)
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 0000000..9bd8f8d
--- /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;i<entityViewIds.length;i++) {
+ if (i>0) {
+ 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;
+ }
+
+}
ui/src/app/app.js 2(+2 -0)
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index c8cdeb0..a3a179e 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,
ui/src/app/common/types.constant.js 3(+2 -1)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 1e34577..1d1b717 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 0000000..48a1788
--- /dev/null
+++ b/ui/src/app/entity-view/add-entity-view.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container">
+ <form name="theForm" ng-submit="vm.add()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>entity-view.add</h2>
+ <span flex></span>
+ <div id="help-container"></div>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <tb-entity-view entity-view="vm.item" is-edit="true" the-form="theForm"></tb-entity-view>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
+ {{ 'action.add' | translate }}
+ </md-button>
+ <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
\ 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 0000000..8e39546
--- /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 0000000..1149a1d
--- /dev/null
+++ b/ui/src/app/entity-view/add-entity-views-to-customer.tpl.html
@@ -0,0 +1,77 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'entity-view.assign-to-customer' | translate }}">
+ <form name="theForm" ng-submit="vm.assign()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>entity-view.assign-entity-view-to-customer</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset>
+ <span translate>entity-view.assign-entity-view-to-customer-text</span>
+ <md-input-container class="md-block" style='margin-bottom: 0px;'>
+ <label> </label>
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+ search
+ </md-icon>
+ <input id="entity-view-search" autofocus ng-model="vm.searchText"
+ ng-change="vm.searchEntityViewTextUpdated()"
+ placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ <div style='min-height: 150px;'>
+ <span translate layout-align="center center"
+ style="text-transform: uppercase; display: flex; height: 150px;"
+ class="md-subhead"
+ ng-show="vm.noData()">entity-view.no-entity-views-text</span>
+ <md-virtual-repeat-container ng-show="vm.hasData()"
+ tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+ style='min-height: 150px; width: 100%;'>
+ <md-list>
+ <md-list-item md-virtual-repeat="entityView in vm.theEntityViews" md-on-demand
+ class="repeated-item" flex>
+ <md-checkbox ng-click="vm.toggleEntityViewSelection($event, entityView)"
+ aria-label="{{ 'item.selected' | translate }}"
+ ng-checked="entityView.selected"></md-checkbox>
+ <span> {{ entityView.name }} </span>
+ </md-list-item>
+ </md-list>
+ </md-virtual-repeat-container>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="$root.loading || vm.entityViews.selectedCount == 0" type="submit"
+ class="md-raised md-primary">
+ {{ 'action.assign' | translate }}
+ </md-button>
+ <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
+ translate }}
+ </md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
\ 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 0000000..3e09ae6
--- /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 0000000..7c1fa25
--- /dev/null
+++ b/ui/src/app/entity-view/assign-to-customer.tpl.html
@@ -0,0 +1,76 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'entity-view.assign-entity-view-to-customer' | translate }}">
+ <form name="theForm" ng-submit="vm.assign()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>entity-view.assign-entity-view-to-customer</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset>
+ <span translate>entity-view.assign-to-customer-text</span>
+ <md-input-container class="md-block" style='margin-bottom: 0px;'>
+ <label> </label>
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+ search
+ </md-icon>
+ <input id="customer-search" autofocus ng-model="vm.searchText"
+ ng-change="vm.searchCustomerTextUpdated()"
+ placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ <div style='min-height: 150px;'>
+ <span translate layout-align="center center"
+ style="text-transform: uppercase; display: flex; height: 150px;"
+ class="md-subhead"
+ ng-show="vm.noData()">customer.no-customers-text</span>
+ <md-virtual-repeat-container ng-show="vm.hasData()"
+ tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+ style='min-height: 150px; width: 100%;'>
+ <md-list>
+ <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
+ class="repeated-item" flex>
+ <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
+ aria-label="{{ 'item.selected' | translate }}"
+ ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
+ <span> {{ customer.title }} </span>
+ </md-list-item>
+ </md-list>
+ </md-virtual-repeat-container>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
+ {{ 'action.assign' | translate }}
+ </md-button>
+ <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
+ translate }}
+ </md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
\ No newline at end of file
ui/src/app/entity-view/entity-view.controller.js 483(+483 -0)
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 0000000..fd3b5a8
--- /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 0000000..f980270
--- /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: '&'
+ }
+ };
+}
ui/src/app/entity-view/entity-view.routes.js 72(+72 -0)
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 0000000..14f5079
--- /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-view-card.tpl.html b/ui/src/app/entity-view/entity-view-card.tpl.html
new file mode 100644
index 0000000..1e90928
--- /dev/null
+++ b/ui/src/app/entity-view/entity-view-card.tpl.html
@@ -0,0 +1,22 @@
+<!--
+
+ 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.
+
+-->
+<div flex layout="column" style="margin-top: -10px;">
+ <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
+ <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
+ <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'entity-view.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+</div>
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 0000000..e0000c6
--- /dev/null
+++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
@@ -0,0 +1,64 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-button ng-click="onAssignToCustomer({event: $event})"
+ ng-show="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer"
+ class="md-raised md-primary">{{ 'entity-view.assign-to-customer' | translate }}</md-button>
+<md-button ng-click="onUnassignFromCustomer({event: $event})"
+ ng-show="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer"
+ class="md-raised md-primary">{{'entity-view.unassign-from-customer' | translate }}</md-button>
+<md-button ng-click="onDeleteEntityView({event: $event})"
+ ng-show="!isEdit && entityViewScope === 'tenant'"
+ class="md-raised md-primary">{{ 'entity-view.delete' | translate }}</md-button>
+
+<div layout="row">
+ <md-button ngclipboard data-clipboard-action="copy"
+ ngclipboard-success="onEntityViewIdCopied(e)"
+ data-clipboard-text="{{entityView.id.id}}" ng-show="!isEdit"
+ class="md-raised">
+ <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
+ <span translate>entity-view.copyId</span>
+ </md-button>
+</div>
+
+<md-content class="md-padding" layout="column">
+ <md-input-container class="md-block"
+ ng-show="!isEdit && isAssignedToCustomer && entityViewScope === 'tenant'">
+ <label translate>entity-view.assignedToCustomer</label>
+ <input ng-model="assignedCustomer.title" disabled>
+ </md-input-container>
+ <fieldset ng-disabled="$root.loading || !isEdit">
+ <md-input-container class="md-block">
+ <label translate>entity-view.name</label>
+ <input required name="name" ng-model="entityView.name">
+ <div ng-messages="theForm.name.$error">
+ <div translate ng-message="required">entity-view.name-required</div>
+ </div>
+ </md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="$root.loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="entityView.type"
+ entity-type="types.entityType.entityview">
+ </tb-entity-subtype-autocomplete>
+ <md-input-container class="md-block">
+ <label translate>entity-view.description</label>
+ <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
+ </md-input-container>
+ </fieldset>
+</md-content>
ui/src/app/entity-view/entity-views.tpl.html 83(+83 -0)
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 0000000..5398449
--- /dev/null
+++ b/ui/src/app/entity-view/entity-views.tpl.html
@@ -0,0 +1,83 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<tb-grid grid-configuration="vm.entityViewGridConfig">
+ <details-buttons tb-help="'entityViews'" help-container-id="help-container">
+ <div id="help-container"></div>
+ </details-buttons>
+ <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
+ id="tabs" md-border-bottom flex class="tb-absolute-fill">
+ <md-tab label="{{ 'entity-view.details' | translate }}">
+ <tb-entity-view entity-view="vm.grid.operatingItem()"
+ is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+ entity-view-scope="vm.entityViewsScope"
+ the-form="vm.grid.detailsForm"
+ on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
+ on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
+ on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
+ on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)"
+ on-delete-entity-view="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-entity-view>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
+ <tb-attribute-table flex
+ entity-id="vm.grid.operatingItem().id.id"
+ entity-type="{{vm.types.entityType.entityview}}"
+ entity-name="vm.grid.operatingItem().name"
+ default-attribute-scope="{{vm.types.attributesScope.client.value}}">
+ </tb-attribute-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
+ <tb-attribute-table flex
+ entity-id="vm.grid.operatingItem().id.id"
+ entity-type="{{vm.types.entityType.entityview}}"
+ entity-name="vm.grid.operatingItem().name"
+ default-attribute-scope="{{vm.types.latestTelemetry.value}}"
+ disable-attribute-scope-selection="true">
+ </tb-attribute-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.entityview"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'entity-view.events' | translate }}">
+ <tb-event-table flex entity-type="vm.types.entityType.entityview"
+ entity-id="vm.grid.operatingItem().id.id"
+ tenant-id="vm.grid.operatingItem().tenantId.id"
+ default-event-type="{{vm.types.eventType.error.value}}">
+ </tb-event-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
+ <tb-relation-table flex
+ entity-id="vm.grid.operatingItem().id.id"
+ entity-type="{{vm.types.entityType.entityview}}">
+ </tb-relation-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}">
+ <tb-extension-table flex
+ entity-id="vm.grid.operatingItem().id.id"
+ entity-name="vm.grid.operatingItem().name"
+ entity-type="{{vm.types.entityType.entityview}}">
+ </tb-extension-table>
+ </md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
+ <tb-audit-log-table flex entity-type="vm.types.entityType.entityview"
+ entity-id="vm.grid.operatingItem().id.id"
+ audit-log-mode="{{vm.types.auditLogMode.entity}}">
+ </tb-audit-log-table>
+ </md-tab>
+</tb-grid>
ui/src/app/entity-view/index.js 41(+41 -0)
diff --git a/ui/src/app/entity-view/index.js b/ui/src/app/entity-view/index.js
new file mode 100644
index 0000000..ebdd3ea
--- /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;
ui/src/app/locale/locale.constant-en_US.json 73(+73 -0)
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 6321d63..5e04a52 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}}'.<br>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):<br/>{{widgetsList}}",
+ "select-entity-view": "Select entity view"
+ },
"event": {
"event-type": "Event type",
"type-error": "Error",