Commit 40017c46dfc8ec59e093cc91bc83f0eed6a914c8

Authored by Volodymyr Babak
1 parent 42a831a3

Entity View feature - UI

  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import thingsboardTypes from '../common/types.constant';
  17 +
  18 +export default angular.module('thingsboard.api.entityView', [thingsboardTypes])
  19 + .factory('entityViewService', EntityViewService)
  20 + .name;
  21 +
  22 +/*@ngInject*/
  23 +function EntityViewService($http, $q, $window, userService, attributeService, customerService, types) {
  24 +
  25 + var service = {
  26 + assignEntityViewToCustomer: assignEntityViewToCustomer,
  27 + deleteEntityView: deleteEntityView,
  28 + getCustomerEntityViews: getCustomerEntityViews,
  29 + getEntityView: getEntityView,
  30 + getEntityViews: getEntityViews,
  31 + getTenantEntityViews: getTenantEntityViews,
  32 + saveEntityView: saveEntityView,
  33 + unassignEntityViewFromCustomer: unassignEntityViewFromCustomer,
  34 + getEntityViewAttributes: getEntityViewAttributes,
  35 + subscribeForEntityViewAttributes: subscribeForEntityViewAttributes,
  36 + unsubscribeForEntityViewAttributes: unsubscribeForEntityViewAttributes,
  37 + findByQuery: findByQuery,
  38 + getEntityViewTypes: getEntityViewTypes
  39 + }
  40 +
  41 + return service;
  42 +
  43 + function getTenantEntityViews(pageLink, applyCustomersInfo, config, type) {
  44 + var deferred = $q.defer();
  45 + var url = '/api/tenant/entityViews?limit=' + pageLink.limit;
  46 + if (angular.isDefined(pageLink.textSearch)) {
  47 + url += '&textSearch=' + pageLink.textSearch;
  48 + }
  49 + if (angular.isDefined(pageLink.idOffset)) {
  50 + url += '&idOffset=' + pageLink.idOffset;
  51 + }
  52 + if (angular.isDefined(pageLink.textOffset)) {
  53 + url += '&textOffset=' + pageLink.textOffset;
  54 + }
  55 + if (angular.isDefined(type) && type.length) {
  56 + url += '&type=' + type;
  57 + }
  58 + $http.get(url, config).then(function success(response) {
  59 + if (applyCustomersInfo) {
  60 + customerService.applyAssignedCustomersInfo(response.data.data).then(
  61 + function success(data) {
  62 + response.data.data = data;
  63 + deferred.resolve(response.data);
  64 + },
  65 + function fail() {
  66 + deferred.reject();
  67 + }
  68 + );
  69 + } else {
  70 + deferred.resolve(response.data);
  71 + }
  72 + }, function fail() {
  73 + deferred.reject();
  74 + });
  75 + return deferred.promise;
  76 + }
  77 +
  78 + function getCustomerEntityViews(customerId, pageLink, applyCustomersInfo, config, type) {
  79 + var deferred = $q.defer();
  80 + var url = '/api/customer/' + customerId + '/entityViews?limit=' + pageLink.limit;
  81 + if (angular.isDefined(pageLink.textSearch)) {
  82 + url += '&textSearch=' + pageLink.textSearch;
  83 + }
  84 + if (angular.isDefined(pageLink.idOffset)) {
  85 + url += '&idOffset=' + pageLink.idOffset;
  86 + }
  87 + if (angular.isDefined(pageLink.textOffset)) {
  88 + url += '&textOffset=' + pageLink.textOffset;
  89 + }
  90 + if (angular.isDefined(type) && type.length) {
  91 + url += '&type=' + type;
  92 + }
  93 + $http.get(url, config).then(function success(response) {
  94 + if (applyCustomersInfo) {
  95 + customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
  96 + function success(data) {
  97 + response.data.data = data;
  98 + deferred.resolve(response.data);
  99 + },
  100 + function fail() {
  101 + deferred.reject();
  102 + }
  103 + );
  104 + } else {
  105 + deferred.resolve(response.data);
  106 + }
  107 + }, function fail() {
  108 + deferred.reject();
  109 + });
  110 +
  111 + return deferred.promise;
  112 + }
  113 +
  114 + function getEntityView(entityViewId, ignoreErrors, config) {
  115 + var deferred = $q.defer();
  116 + var url = '/api/entityView/' + entityViewId;
  117 + if (!config) {
  118 + config = {};
  119 + }
  120 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  121 + $http.get(url, config).then(function success(response) {
  122 + deferred.resolve(response.data);
  123 + }, function fail(response) {
  124 + deferred.reject(response.data);
  125 + });
  126 + return deferred.promise;
  127 + }
  128 +
  129 + function getEntityViews(entityViewIds, config) {
  130 + var deferred = $q.defer();
  131 + var ids = '';
  132 + for (var i=0;i<entityViewIds.length;i++) {
  133 + if (i>0) {
  134 + ids += ',';
  135 + }
  136 + ids += entityViewIds[i];
  137 + }
  138 + var url = '/api/entityViews?entityViewIds=' + ids;
  139 + $http.get(url, config).then(function success(response) {
  140 + var entityViews = response.data;
  141 + entityViews.sort(function (entityView1, entityView2) {
  142 + var id1 = entityView1.id.id;
  143 + var id2 = entityView2.id.id;
  144 + var index1 = entityViewIds.indexOf(id1);
  145 + var index2 = entityViewIds.indexOf(id2);
  146 + return index1 - index2;
  147 + });
  148 + deferred.resolve(entityViews);
  149 + }, function fail(response) {
  150 + deferred.reject(response.data);
  151 + });
  152 + return deferred.promise;
  153 + }
  154 +
  155 + function saveEntityView(entityView) {
  156 + var deferred = $q.defer();
  157 + var url = '/api/entityView';
  158 + $http.post(url, entityView).then(function success(response) {
  159 + deferred.resolve(response.data);
  160 + }, function fail() {
  161 + deferred.reject();
  162 + });
  163 + return deferred.promise;
  164 + }
  165 +
  166 + function deleteEntityView(entityViewId) {
  167 + var deferred = $q.defer();
  168 + var url = '/api/entityView/' + entityViewId;
  169 + $http.delete(url).then(function success() {
  170 + deferred.resolve();
  171 + }, function fail() {
  172 + deferred.reject();
  173 + });
  174 + return deferred.promise;
  175 + }
  176 +
  177 + function assignEntityViewToCustomer(customerId, entityViewId) {
  178 + var deferred = $q.defer();
  179 + var url = '/api/customer/' + customerId + '/entityView/' + entityViewId;
  180 + $http.post(url, null).then(function success(response) {
  181 + deferred.resolve(response.data);
  182 + }, function fail() {
  183 + deferred.reject();
  184 + });
  185 + return deferred.promise;
  186 + }
  187 +
  188 + function unassignEntityViewFromCustomer(entityViewId) {
  189 + var deferred = $q.defer();
  190 + var url = '/api/customer/entityView/' + entityViewId;
  191 + $http.delete(url).then(function success(response) {
  192 + deferred.resolve(response.data);
  193 + }, function fail() {
  194 + deferred.reject();
  195 + });
  196 + return deferred.promise;
  197 + }
  198 +
  199 + function getEntityViewAttributes(entityViewId, attributeScope, query, successCallback, config) {
  200 + return attributeService.getEntityAttributes(types.entityType.entityView, entityViewId, attributeScope, query, successCallback, config);
  201 + }
  202 +
  203 + function subscribeForEntityViewAttributes(entityViewId, attributeScope) {
  204 + return attributeService.subscribeForEntityAttributes(types.entityType.entityView, entityViewId, attributeScope);
  205 + }
  206 +
  207 + function unsubscribeForEntityViewAttributes(subscriptionId) {
  208 + attributeService.unsubscribeForEntityAttributes(subscriptionId);
  209 + }
  210 +
  211 + function findByQuery(query, ignoreErrors, config) {
  212 + var deferred = $q.defer();
  213 + var url = '/api/entityViews';
  214 + if (!config) {
  215 + config = {};
  216 + }
  217 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  218 + $http.post(url, query, config).then(function success(response) {
  219 + deferred.resolve(response.data);
  220 + }, function fail() {
  221 + deferred.reject();
  222 + });
  223 + return deferred.promise;
  224 + }
  225 +
  226 + function getEntityViewTypes(config) {
  227 + var deferred = $q.defer();
  228 + var url = '/api/entityView/types';
  229 + $http.get(url, config).then(function success(response) {
  230 + deferred.resolve(response.data);
  231 + }, function fail() {
  232 + deferred.reject();
  233 + });
  234 + return deferred.promise;
  235 + }
  236 +
  237 +}
@@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service'; @@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service';
67 import thingsboardHome from './layout'; 67 import thingsboardHome from './layout';
68 import thingsboardApiLogin from './api/login.service'; 68 import thingsboardApiLogin from './api/login.service';
69 import thingsboardApiDevice from './api/device.service'; 69 import thingsboardApiDevice from './api/device.service';
  70 +import thingsboardApiEntityView from './api/entity-view.service';
70 import thingsboardApiUser from './api/user.service'; 71 import thingsboardApiUser from './api/user.service';
71 import thingsboardApiEntityRelation from './api/entity-relation.service'; 72 import thingsboardApiEntityRelation from './api/entity-relation.service';
72 import thingsboardApiAsset from './api/asset.service'; 73 import thingsboardApiAsset from './api/asset.service';
@@ -133,6 +134,7 @@ angular.module('thingsboard', [ @@ -133,6 +134,7 @@ angular.module('thingsboard', [
133 thingsboardHome, 134 thingsboardHome,
134 thingsboardApiLogin, 135 thingsboardApiLogin,
135 thingsboardApiDevice, 136 thingsboardApiDevice,
  137 + thingsboardApiEntityView,
136 thingsboardApiUser, 138 thingsboardApiUser,
137 thingsboardApiEntityRelation, 139 thingsboardApiEntityRelation,
138 thingsboardApiAsset, 140 thingsboardApiAsset,
@@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', []) @@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', [])
327 dashboard: "DASHBOARD", 327 dashboard: "DASHBOARD",
328 alarm: "ALARM", 328 alarm: "ALARM",
329 rulechain: "RULE_CHAIN", 329 rulechain: "RULE_CHAIN",
330 - rulenode: "RULE_NODE" 330 + rulenode: "RULE_NODE",
  331 + entityview: "ENTITY_VIEW"
331 }, 332 },
332 aliasEntityType: { 333 aliasEntityType: {
333 current_customer: "CURRENT_CUSTOMER" 334 current_customer: "CURRENT_CUSTOMER"
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container">
  19 + <form name="theForm" ng-submit="vm.add()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>entity-view.add</h2>
  23 + <span flex></span>
  24 + <div id="help-container"></div>
  25 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  26 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  27 + </md-button>
  28 + </div>
  29 + </md-toolbar>
  30 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  31 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  32 + <md-dialog-content>
  33 + <div class="md-dialog-content">
  34 + <tb-entity-view entity-view="vm.item" is-edit="true" the-form="theForm"></tb-entity-view>
  35 + </div>
  36 + </md-dialog-content>
  37 + <md-dialog-actions layout="row">
  38 + <span flex></span>
  39 + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
  40 + {{ 'action.add' | translate }}
  41 + </md-button>
  42 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
  43 + </md-dialog-actions>
  44 + </form>
  45 +</md-dialog>
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/*@ngInject*/
  17 +export default function AddEntityViewsToCustomerController(entityViewService, $mdDialog, $q, customerId, entityViews) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.entityViews = entityViews;
  22 + vm.searchText = '';
  23 +
  24 + vm.assign = assign;
  25 + vm.cancel = cancel;
  26 + vm.hasData = hasData;
  27 + vm.noData = noData;
  28 + vm.searchEntityViewTextUpdated = searchEntityViewTextUpdated;
  29 + vm.toggleEntityViewSelection = toggleEntityViewSelection;
  30 +
  31 + vm.theEntityViews = {
  32 + getItemAtIndex: function (index) {
  33 + if (index > vm.entityViews.data.length) {
  34 + vm.theEntityViews.fetchMoreItems_(index);
  35 + return null;
  36 + }
  37 + var item = vm.entityViews.data[index];
  38 + if (item) {
  39 + item.indexNumber = index + 1;
  40 + }
  41 + return item;
  42 + },
  43 +
  44 + getLength: function () {
  45 + if (vm.entityViews.hasNext) {
  46 + return vm.entityViews.data.length + vm.entityViews.nextPageLink.limit;
  47 + } else {
  48 + return vm.entityViews.data.length;
  49 + }
  50 + },
  51 +
  52 + fetchMoreItems_: function () {
  53 + if (vm.entityViews.hasNext && !vm.entityViews.pending) {
  54 + vm.entityViews.pending = true;
  55 + entityViewService.getTenantEntityViews(vm.entityViews.nextPageLink, false).then(
  56 + function success(entityViews) {
  57 + vm.entityViews.data = vm.entityViews.data.concat(entityViews.data);
  58 + vm.entityViews.nextPageLink = entityViews.nextPageLink;
  59 + vm.entityViews.hasNext = entityViews.hasNext;
  60 + if (vm.entityViews.hasNext) {
  61 + vm.entityViews.nextPageLink.limit = vm.entityViews.pageSize;
  62 + }
  63 + vm.entityViews.pending = false;
  64 + },
  65 + function fail() {
  66 + vm.entityViews.hasNext = false;
  67 + vm.entityViews.pending = false;
  68 + });
  69 + }
  70 + }
  71 + };
  72 +
  73 + function cancel () {
  74 + $mdDialog.cancel();
  75 + }
  76 +
  77 + function assign() {
  78 + var tasks = [];
  79 + for (var entityViewId in vm.entityViews.selections) {
  80 + tasks.push(entityViewService.assignEntityViewToCustomer(customerId, entityViewId));
  81 + }
  82 + $q.all(tasks).then(function () {
  83 + $mdDialog.hide();
  84 + });
  85 + }
  86 +
  87 + function noData() {
  88 + return vm.entityViews.data.length == 0 && !vm.entityViews.hasNext;
  89 + }
  90 +
  91 + function hasData() {
  92 + return vm.entityViews.data.length > 0;
  93 + }
  94 +
  95 + function toggleEntityViewSelection($event, entityView) {
  96 + $event.stopPropagation();
  97 + var selected = angular.isDefined(entityView.selected) && entityView.selected;
  98 + entityView.selected = !selected;
  99 + if (entityView.selected) {
  100 + vm.entityViews.selections[entityView.id.id] = true;
  101 + vm.entityViews.selectedCount++;
  102 + } else {
  103 + delete vm.entityViews.selections[entityView.id.id];
  104 + vm.entityViews.selectedCount--;
  105 + }
  106 + }
  107 +
  108 + function searchEntityViewTextUpdated() {
  109 + vm.entityViews = {
  110 + pageSize: vm.entityViews.pageSize,
  111 + data: [],
  112 + nextPageLink: {
  113 + limit: vm.entityViews.pageSize,
  114 + textSearch: vm.searchText
  115 + },
  116 + selections: {},
  117 + selectedCount: 0,
  118 + hasNext: true,
  119 + pending: false
  120 + };
  121 + }
  122 +
  123 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'entity-view.assign-to-customer' | translate }}">
  19 + <form name="theForm" ng-submit="vm.assign()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>entity-view.assign-entity-view-to-customer</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <fieldset>
  34 + <span translate>entity-view.assign-entity-view-to-customer-text</span>
  35 + <md-input-container class="md-block" style='margin-bottom: 0px;'>
  36 + <label>&nbsp;</label>
  37 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
  38 + search
  39 + </md-icon>
  40 + <input id="entity-view-search" autofocus ng-model="vm.searchText"
  41 + ng-change="vm.searchEntityViewTextUpdated()"
  42 + placeholder="{{ 'common.enter-search' | translate }}"/>
  43 + </md-input-container>
  44 + <div style='min-height: 150px;'>
  45 + <span translate layout-align="center center"
  46 + style="text-transform: uppercase; display: flex; height: 150px;"
  47 + class="md-subhead"
  48 + ng-show="vm.noData()">entity-view.no-entity-views-text</span>
  49 + <md-virtual-repeat-container ng-show="vm.hasData()"
  50 + tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
  51 + style='min-height: 150px; width: 100%;'>
  52 + <md-list>
  53 + <md-list-item md-virtual-repeat="entityView in vm.theEntityViews" md-on-demand
  54 + class="repeated-item" flex>
  55 + <md-checkbox ng-click="vm.toggleEntityViewSelection($event, entityView)"
  56 + aria-label="{{ 'item.selected' | translate }}"
  57 + ng-checked="entityView.selected"></md-checkbox>
  58 + <span> {{ entityView.name }} </span>
  59 + </md-list-item>
  60 + </md-list>
  61 + </md-virtual-repeat-container>
  62 + </div>
  63 + </fieldset>
  64 + </div>
  65 + </md-dialog-content>
  66 + <md-dialog-actions layout="row">
  67 + <span flex></span>
  68 + <md-button ng-disabled="$root.loading || vm.entityViews.selectedCount == 0" type="submit"
  69 + class="md-raised md-primary">
  70 + {{ 'action.assign' | translate }}
  71 + </md-button>
  72 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
  73 + translate }}
  74 + </md-button>
  75 + </md-dialog-actions>
  76 + </form>
  77 +</md-dialog>
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/*@ngInject*/
  17 +export default function AssignEntityViewToCustomerController(customerService, entityViewService, $mdDialog, $q, entityViewIds, customers) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.customers = customers;
  22 + vm.searchText = '';
  23 +
  24 + vm.assign = assign;
  25 + vm.cancel = cancel;
  26 + vm.isCustomerSelected = isCustomerSelected;
  27 + vm.hasData = hasData;
  28 + vm.noData = noData;
  29 + vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
  30 + vm.toggleCustomerSelection = toggleCustomerSelection;
  31 +
  32 + vm.theCustomers = {
  33 + getItemAtIndex: function (index) {
  34 + if (index > vm.customers.data.length) {
  35 + vm.theCustomers.fetchMoreItems_(index);
  36 + return null;
  37 + }
  38 + var item = vm.customers.data[index];
  39 + if (item) {
  40 + item.indexNumber = index + 1;
  41 + }
  42 + return item;
  43 + },
  44 +
  45 + getLength: function () {
  46 + if (vm.customers.hasNext) {
  47 + return vm.customers.data.length + vm.customers.nextPageLink.limit;
  48 + } else {
  49 + return vm.customers.data.length;
  50 + }
  51 + },
  52 +
  53 + fetchMoreItems_: function () {
  54 + if (vm.customers.hasNext && !vm.customers.pending) {
  55 + vm.customers.pending = true;
  56 + customerService.getCustomers(vm.customers.nextPageLink).then(
  57 + function success(customers) {
  58 + vm.customers.data = vm.customers.data.concat(customers.data);
  59 + vm.customers.nextPageLink = customers.nextPageLink;
  60 + vm.customers.hasNext = customers.hasNext;
  61 + if (vm.customers.hasNext) {
  62 + vm.customers.nextPageLink.limit = vm.customers.pageSize;
  63 + }
  64 + vm.customers.pending = false;
  65 + },
  66 + function fail() {
  67 + vm.customers.hasNext = false;
  68 + vm.customers.pending = false;
  69 + });
  70 + }
  71 + }
  72 + };
  73 +
  74 + function cancel() {
  75 + $mdDialog.cancel();
  76 + }
  77 +
  78 + function assign() {
  79 + var tasks = [];
  80 + for (var i=0; i < entityViewIds.length;i++) {
  81 + tasks.push(entityViewService.assignEntityViewToCustomer(vm.customers.selection.id.id, entityViewIds[i]));
  82 + }
  83 + $q.all(tasks).then(function () {
  84 + $mdDialog.hide();
  85 + });
  86 + }
  87 +
  88 + function noData() {
  89 + return vm.customers.data.length == 0 && !vm.customers.hasNext;
  90 + }
  91 +
  92 + function hasData() {
  93 + return vm.customers.data.length > 0;
  94 + }
  95 +
  96 + function toggleCustomerSelection($event, customer) {
  97 + $event.stopPropagation();
  98 + if (vm.isCustomerSelected(customer)) {
  99 + vm.customers.selection = null;
  100 + } else {
  101 + vm.customers.selection = customer;
  102 + }
  103 + }
  104 +
  105 + function isCustomerSelected(customer) {
  106 + return vm.customers.selection != null && customer &&
  107 + customer.id.id === vm.customers.selection.id.id;
  108 + }
  109 +
  110 + function searchCustomerTextUpdated() {
  111 + vm.customers = {
  112 + pageSize: vm.customers.pageSize,
  113 + data: [],
  114 + nextPageLink: {
  115 + limit: vm.customers.pageSize,
  116 + textSearch: vm.searchText
  117 + },
  118 + selection: null,
  119 + hasNext: true,
  120 + pending: false
  121 + };
  122 + }
  123 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'entity-view.assign-entity-view-to-customer' | translate }}">
  19 + <form name="theForm" ng-submit="vm.assign()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>entity-view.assign-entity-view-to-customer</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <fieldset>
  34 + <span translate>entity-view.assign-to-customer-text</span>
  35 + <md-input-container class="md-block" style='margin-bottom: 0px;'>
  36 + <label>&nbsp;</label>
  37 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
  38 + search
  39 + </md-icon>
  40 + <input id="customer-search" autofocus ng-model="vm.searchText"
  41 + ng-change="vm.searchCustomerTextUpdated()"
  42 + placeholder="{{ 'common.enter-search' | translate }}"/>
  43 + </md-input-container>
  44 + <div style='min-height: 150px;'>
  45 + <span translate layout-align="center center"
  46 + style="text-transform: uppercase; display: flex; height: 150px;"
  47 + class="md-subhead"
  48 + ng-show="vm.noData()">customer.no-customers-text</span>
  49 + <md-virtual-repeat-container ng-show="vm.hasData()"
  50 + tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
  51 + style='min-height: 150px; width: 100%;'>
  52 + <md-list>
  53 + <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
  54 + class="repeated-item" flex>
  55 + <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
  56 + aria-label="{{ 'item.selected' | translate }}"
  57 + ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
  58 + <span> {{ customer.title }} </span>
  59 + </md-list-item>
  60 + </md-list>
  61 + </md-virtual-repeat-container>
  62 + </div>
  63 + </fieldset>
  64 + </div>
  65 + </md-dialog-content>
  66 + <md-dialog-actions layout="row">
  67 + <span flex></span>
  68 + <md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
  69 + {{ 'action.assign' | translate }}
  70 + </md-button>
  71 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
  72 + translate }}
  73 + </md-button>
  74 + </md-dialog-actions>
  75 + </form>
  76 +</md-dialog>
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div flex layout="column" style="margin-top: -10px;">
  19 + <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
  20 + <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
  21 + <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'entity-view.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
  22 +</div>
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-button ng-click="onAssignToCustomer({event: $event})"
  19 + ng-show="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer"
  20 + class="md-raised md-primary">{{ 'entity-view.assign-to-customer' | translate }}</md-button>
  21 +<md-button ng-click="onUnassignFromCustomer({event: $event})"
  22 + ng-show="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer"
  23 + class="md-raised md-primary">{{'entity-view.unassign-from-customer' | translate }}</md-button>
  24 +<md-button ng-click="onDeleteEntityView({event: $event})"
  25 + ng-show="!isEdit && entityViewScope === 'tenant'"
  26 + class="md-raised md-primary">{{ 'entity-view.delete' | translate }}</md-button>
  27 +
  28 +<div layout="row">
  29 + <md-button ngclipboard data-clipboard-action="copy"
  30 + ngclipboard-success="onEntityViewIdCopied(e)"
  31 + data-clipboard-text="{{entityView.id.id}}" ng-show="!isEdit"
  32 + class="md-raised">
  33 + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
  34 + <span translate>entity-view.copyId</span>
  35 + </md-button>
  36 +</div>
  37 +
  38 +<md-content class="md-padding" layout="column">
  39 + <md-input-container class="md-block"
  40 + ng-show="!isEdit && isAssignedToCustomer && entityViewScope === 'tenant'">
  41 + <label translate>entity-view.assignedToCustomer</label>
  42 + <input ng-model="assignedCustomer.title" disabled>
  43 + </md-input-container>
  44 + <fieldset ng-disabled="$root.loading || !isEdit">
  45 + <md-input-container class="md-block">
  46 + <label translate>entity-view.name</label>
  47 + <input required name="name" ng-model="entityView.name">
  48 + <div ng-messages="theForm.name.$error">
  49 + <div translate ng-message="required">entity-view.name-required</div>
  50 + </div>
  51 + </md-input-container>
  52 + <tb-entity-subtype-autocomplete
  53 + ng-disabled="$root.loading || !isEdit"
  54 + tb-required="true"
  55 + the-form="theForm"
  56 + ng-model="entityView.type"
  57 + entity-type="types.entityType.entityview">
  58 + </tb-entity-subtype-autocomplete>
  59 + <md-input-container class="md-block">
  60 + <label translate>entity-view.description</label>
  61 + <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
  62 + </md-input-container>
  63 + </fieldset>
  64 +</md-content>
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import addEntityViewTemplate from './add-entity-view.tpl.html';
  19 +import entityViewCard from './entity-view-card.tpl.html';
  20 +import assignToCustomerTemplate from './assign-to-customer.tpl.html';
  21 +import addEntityViewsToCustomerTemplate from './add-entity-views-to-customer.tpl.html';
  22 +
  23 +/* eslint-enable import/no-unresolved, import/default */
  24 +
  25 +/*@ngInject*/
  26 +export function EntityViewCardController(types) {
  27 +
  28 + var vm = this;
  29 +
  30 + vm.types = types;
  31 +
  32 + vm.isAssignedToCustomer = function() {
  33 + if (vm.item && vm.item.customerId && vm.parentCtl.entityViewsScope === 'tenant' &&
  34 + vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {
  35 + return true;
  36 + }
  37 + return false;
  38 + }
  39 +
  40 + vm.isPublic = function() {
  41 + if (vm.item && vm.item.assignedCustomer && vm.parentCtl.entityViewsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {
  42 + return true;
  43 + }
  44 + return false;
  45 + }
  46 +}
  47 +
  48 +
  49 +/*@ngInject*/
  50 +export function EntityViewController($rootScope, userService, entityViewService, customerService, $state, $stateParams,
  51 + $document, $mdDialog, $q, $translate, types) {
  52 +
  53 + var customerId = $stateParams.customerId;
  54 +
  55 + var entityViewActionsList = [];
  56 +
  57 + var entityViewGroupActionsList = [];
  58 +
  59 + var vm = this;
  60 +
  61 + vm.types = types;
  62 +
  63 + vm.entityViewGridConfig = {
  64 + deleteItemTitleFunc: deleteEntityViewTitle,
  65 + deleteItemContentFunc: deleteEntityViewText,
  66 + deleteItemsTitleFunc: deleteEntityViewsTitle,
  67 + deleteItemsActionTitleFunc: deleteEntityViewsActionTitle,
  68 + deleteItemsContentFunc: deleteEntityViewsText,
  69 +
  70 + saveItemFunc: saveEntityView,
  71 +
  72 + getItemTitleFunc: getEntityViewTitle,
  73 +
  74 + itemCardController: 'EntityViewCardController',
  75 + itemCardTemplateUrl: entityViewCard,
  76 + parentCtl: vm,
  77 +
  78 + actionsList: entityViewActionsList,
  79 + groupActionsList: entityViewGroupActionsList,
  80 +
  81 + onGridInited: gridInited,
  82 +
  83 + addItemTemplateUrl: addEntityViewTemplate,
  84 +
  85 + addItemText: function() { return $translate.instant('entity-view.add-entity-view-text') },
  86 + noItemsText: function() { return $translate.instant('entity-view.no-entity-views-text') },
  87 + itemDetailsText: function() { return $translate.instant('entity-view.entity-view-details') },
  88 + isDetailsReadOnly: isCustomerUser,
  89 + isSelectionEnabled: function () {
  90 + return !isCustomerUser();
  91 + }
  92 + };
  93 +
  94 + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
  95 + vm.entityViewGridConfig.items = $stateParams.items;
  96 + }
  97 +
  98 + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
  99 + vm.entityViewGridConfig.topIndex = $stateParams.topIndex;
  100 + }
  101 +
  102 + vm.entityViewsScope = $state.$current.data.entityViewsType;
  103 +
  104 + vm.assignToCustomer = assignToCustomer;
  105 + vm.makePublic = makePublic;
  106 + vm.unassignFromCustomer = unassignFromCustomer;
  107 +
  108 + initController();
  109 +
  110 + function initController() {
  111 + var fetchEntityViewsFunction = null;
  112 + var deleteEntityViewFunction = null;
  113 + var refreshEntityViewsParamsFunction = null;
  114 +
  115 + var user = userService.getCurrentUser();
  116 +
  117 + if (user.authority === 'CUSTOMER_USER') {
  118 + vm.entityViewsScope = 'customer_user';
  119 + customerId = user.customerId;
  120 + }
  121 + if (customerId) {
  122 + vm.customerEntityViewsTitle = $translate.instant('customer.entity-views');
  123 + customerService.getShortCustomerInfo(customerId).then(
  124 + function success(info) {
  125 + if (info.isPublic) {
  126 + vm.customerEntityViewsTitle = $translate.instant('customer.public-entity-views');
  127 + }
  128 + }
  129 + );
  130 + }
  131 +
  132 + if (vm.entityViewsScope === 'tenant') {
  133 + fetchEntityViewsFunction = function (pageLink, entityViewType) {
  134 + return entityViewService.getTenantEntityViews(pageLink, true, null, entityViewType);
  135 + };
  136 + deleteEntityViewFunction = function (entityViewId) {
  137 + return entityViewService.deleteEntityView(entityViewId);
  138 + };
  139 + refreshEntityViewsParamsFunction = function() {
  140 + return {"topIndex": vm.topIndex};
  141 + };
  142 +
  143 + entityViewActionsList.push(
  144 + {
  145 + onAction: function ($event, item) {
  146 + assignToCustomer($event, [ item.id.id ]);
  147 + },
  148 + name: function() { return $translate.instant('action.assign') },
  149 + details: function() { return $translate.instant('entity-view.assign-to-customer') },
  150 + icon: "assignment_ind",
  151 + isEnabled: function(entityView) {
  152 + return entityView && (!entityView.customerId || entityView.customerId.id === types.id.nullUid);
  153 + }
  154 + }
  155 + );
  156 +
  157 + entityViewActionsList.push(
  158 + {
  159 + onAction: function ($event, item) {
  160 + unassignFromCustomer($event, item, false);
  161 + },
  162 + name: function() { return $translate.instant('action.unassign') },
  163 + details: function() { return $translate.instant('entity-view.unassign-from-customer') },
  164 + icon: "assignment_return",
  165 + isEnabled: function(entityView) {
  166 + return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && !entityView.assignedCustomer.isPublic;
  167 + }
  168 + }
  169 + );
  170 +
  171 + entityViewActionsList.push({
  172 + onAction: function ($event, item) {
  173 + unassignFromCustomer($event, item, true);
  174 + },
  175 + name: function() { return $translate.instant('action.make-private') },
  176 + details: function() { return $translate.instant('entity-view.make-private') },
  177 + icon: "reply",
  178 + isEnabled: function(entityView) {
  179 + return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && entityView.assignedCustomer.isPublic;
  180 + }
  181 + });
  182 +
  183 + entityViewActionsList.push(
  184 + {
  185 + onAction: function ($event, item) {
  186 + vm.grid.deleteItem($event, item);
  187 + },
  188 + name: function() { return $translate.instant('action.delete') },
  189 + details: function() { return $translate.instant('entity-view.delete') },
  190 + icon: "delete"
  191 + }
  192 + );
  193 +
  194 + entityViewGroupActionsList.push(
  195 + {
  196 + onAction: function ($event, items) {
  197 + assignEntiyViewsToCustomer($event, items);
  198 + },
  199 + name: function() { return $translate.instant('entity-view.assign-entity-views') },
  200 + details: function(selectedCount) {
  201 + return $translate.instant('entity-view.assign-entity-views-text', {count: selectedCount}, "messageformat");
  202 + },
  203 + icon: "assignment_ind"
  204 + }
  205 + );
  206 +
  207 + entityViewGroupActionsList.push(
  208 + {
  209 + onAction: function ($event) {
  210 + vm.grid.deleteItems($event);
  211 + },
  212 + name: function() { return $translate.instant('entity-view.delete-entity-views') },
  213 + details: deleteEntityViewsActionTitle,
  214 + icon: "delete"
  215 + }
  216 + );
  217 +
  218 +
  219 +
  220 + } else if (vm.entityViewsScope === 'customer' || vm.entityViewsScope === 'customer_user') {
  221 + fetchEntityViewsFunction = function (pageLink, entityViewType) {
  222 + return entityViewService.getCustomerEntityViews(customerId, pageLink, true, null, entityViewType);
  223 + };
  224 + deleteentityViewFunction = function (entityViewId) {
  225 + return entityViewService.unassignEntityViewFromCustomer(entityViewId);
  226 + };
  227 + refreshentityViewsParamsFunction = function () {
  228 + return {"customerId": customerId, "topIndex": vm.topIndex};
  229 + };
  230 +
  231 + if (vm.entityViewsScope === 'customer') {
  232 + entityViewActionsList.push(
  233 + {
  234 + onAction: function ($event, item) {
  235 + unassignFromCustomer($event, item, false);
  236 + },
  237 + name: function() { return $translate.instant('action.unassign') },
  238 + details: function() { return $translate.instant('entity-view.unassign-from-customer') },
  239 + icon: "assignment_return",
  240 + isEnabled: function(entityView) {
  241 + return entityView && !entityView.assignedCustomer.isPublic;
  242 + }
  243 + }
  244 + );
  245 +
  246 + entityViewGroupActionsList.push(
  247 + {
  248 + onAction: function ($event, items) {
  249 + unassignEntityViewsFromCustomer($event, items);
  250 + },
  251 + name: function() { return $translate.instant('entity-view.unassign-entity-views') },
  252 + details: function(selectedCount) {
  253 + return $translate.instant('entity-view.unassign-entity-views-action-title', {count: selectedCount}, "messageformat");
  254 + },
  255 + icon: "assignment_return"
  256 + }
  257 + );
  258 +
  259 + vm.entityViewGridConfig.addItemAction = {
  260 + onAction: function ($event) {
  261 + addEntityViewsToCustomer($event);
  262 + },
  263 + name: function() { return $translate.instant('entity-view.assign-entity-views') },
  264 + details: function() { return $translate.instant('entity-view.assign-new-entity-view') },
  265 + icon: "add"
  266 + };
  267 +
  268 +
  269 + } else if (vm.entityViewsScope === 'customer_user') {
  270 + vm.entityViewGridConfig.addItemAction = {};
  271 + }
  272 + }
  273 +
  274 + vm.entityViewGridConfig.refreshParamsFunc = refreshentityViewsParamsFunction;
  275 + vm.entityViewGridConfig.fetchItemsFunc = fetchentityViewsFunction;
  276 + vm.entityViewGridConfig.deleteItemFunc = deleteentityViewFunction;
  277 +
  278 + }
  279 +
  280 + function deleteEntityViewTitle(entityView) {
  281 + return $translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name});
  282 + }
  283 +
  284 + function deleteEntityViewText() {
  285 + return $translate.instant('entity-view.delete-entity-view-text');
  286 + }
  287 +
  288 + function deleteEntityViewsTitle(selectedCount) {
  289 + return $translate.instant('entity-view.delete-entity-views-title', {count: selectedCount}, 'messageformat');
  290 + }
  291 +
  292 + function deleteEntityViewsActionTitle(selectedCount) {
  293 + return $translate.instant('entity-view.delete-entity-views-action-title', {count: selectedCount}, 'messageformat');
  294 + }
  295 +
  296 + function deleteEntityViewsText () {
  297 + return $translate.instant('entity-view.delete-entity-views-text');
  298 + }
  299 +
  300 + function gridInited(grid) {
  301 + vm.grid = grid;
  302 + }
  303 +
  304 + function getEntityViewTitle(entityView) {
  305 + return entityView ? entityView.name : '';
  306 + }
  307 +
  308 + function saveEntityView(entityView) {
  309 + var deferred = $q.defer();
  310 + entityViewService.saveEntityView(entityView).then(
  311 + function success(savedEntityView) {
  312 + $rootScope.$broadcast('entityViewSaved');
  313 + var entityViews = [ savedEntityView ];
  314 + customerService.applyAssignedCustomersInfo(entityViews).then(
  315 + function success(items) {
  316 + if (items && items.length == 1) {
  317 + deferred.resolve(items[0]);
  318 + } else {
  319 + deferred.reject();
  320 + }
  321 + },
  322 + function fail() {
  323 + deferred.reject();
  324 + }
  325 + );
  326 + },
  327 + function fail() {
  328 + deferred.reject();
  329 + }
  330 + );
  331 + return deferred.promise;
  332 + }
  333 +
  334 + function isCustomerUser() {
  335 + return vm.entityViewsScope === 'customer_user';
  336 + }
  337 +
  338 + function assignToCustomer($event, entityViewIds) {
  339 + if ($event) {
  340 + $event.stopPropagation();
  341 + }
  342 + var pageSize = 10;
  343 + customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
  344 + function success(_customers) {
  345 + var customers = {
  346 + pageSize: pageSize,
  347 + data: _customers.data,
  348 + nextPageLink: _customers.nextPageLink,
  349 + selection: null,
  350 + hasNext: _customers.hasNext,
  351 + pending: false
  352 + };
  353 + if (customers.hasNext) {
  354 + customers.nextPageLink.limit = pageSize;
  355 + }
  356 + $mdDialog.show({
  357 + controller: 'AssignEntityViewToCustomerController',
  358 + controllerAs: 'vm',
  359 + templateUrl: assignToCustomerTemplate,
  360 + locals: {entityViewIds: entityViewIds, customers: customers},
  361 + parent: angular.element($document[0].body),
  362 + fullscreen: true,
  363 + targetEvent: $event
  364 + }).then(function () {
  365 + vm.grid.refreshList();
  366 + }, function () {
  367 + });
  368 + },
  369 + function fail() {
  370 + });
  371 + }
  372 +
  373 + function addEntityViewsToCustomer($event) {
  374 + if ($event) {
  375 + $event.stopPropagation();
  376 + }
  377 + var pageSize = 10;
  378 + entityViewService.getTenantEntityViews({limit: pageSize, textSearch: ''}, false).then(
  379 + function success(_entityViews) {
  380 + var entityViews = {
  381 + pageSize: pageSize,
  382 + data: _entityViews.data,
  383 + nextPageLink: _entityViews.nextPageLink,
  384 + selections: {},
  385 + selectedCount: 0,
  386 + hasNext: _entityViews.hasNext,
  387 + pending: false
  388 + };
  389 + if (entityViews.hasNext) {
  390 + entityViews.nextPageLink.limit = pageSize;
  391 + }
  392 + $mdDialog.show({
  393 + controller: 'AddEntityViewsToCustomerController',
  394 + controllerAs: 'vm',
  395 + templateUrl: addEntityViewsToCustomerTemplate,
  396 + locals: {customerId: customerId, entityViews: entityViews},
  397 + parent: angular.element($document[0].body),
  398 + fullscreen: true,
  399 + targetEvent: $event
  400 + }).then(function () {
  401 + vm.grid.refreshList();
  402 + }, function () {
  403 + });
  404 + },
  405 + function fail() {
  406 + });
  407 + }
  408 +
  409 + function assignEntityViewsToCustomer($event, items) {
  410 + var entityViewIds = [];
  411 + for (var id in items.selections) {
  412 + entityViewIds.push(id);
  413 + }
  414 + assignToCustomer($event, entityViewIds);
  415 + }
  416 +
  417 + function unassignFromCustomer($event, entityView, isPublic) {
  418 + if ($event) {
  419 + $event.stopPropagation();
  420 + }
  421 + var title;
  422 + var content;
  423 + var label;
  424 + if (isPublic) {
  425 + title = $translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name});
  426 + content = $translate.instant('entity-view.make-private-entity-view-text');
  427 + label = $translate.instant('entity-view.make-private');
  428 + } else {
  429 + title = $translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name});
  430 + content = $translate.instant('entity-view.unassign-entity-view-text');
  431 + label = $translate.instant('entity-view.unassign-entity-view');
  432 + }
  433 + var confirm = $mdDialog.confirm()
  434 + .targetEvent($event)
  435 + .title(title)
  436 + .htmlContent(content)
  437 + .ariaLabel(label)
  438 + .cancel($translate.instant('action.no'))
  439 + .ok($translate.instant('action.yes'));
  440 + $mdDialog.show(confirm).then(function () {
  441 + entityViewService.unassignEntityViewFromCustomer(entityView.id.id).then(function success() {
  442 + vm.grid.refreshList();
  443 + });
  444 + });
  445 + }
  446 +
  447 + function unassignEntityViewsFromCustomer($event, items) {
  448 + var confirm = $mdDialog.confirm()
  449 + .targetEvent($event)
  450 + .title($translate.instant('entity-view.unassign-entity-views-title', {count: items.selectedCount}, 'messageformat'))
  451 + .htmlContent($translate.instant('entity-view.unassign-entity-views-text'))
  452 + .ariaLabel($translate.instant('entity-view.unassign-entity-view'))
  453 + .cancel($translate.instant('action.no'))
  454 + .ok($translate.instant('action.yes'));
  455 + $mdDialog.show(confirm).then(function () {
  456 + var tasks = [];
  457 + for (var id in items.selections) {
  458 + tasks.push(entityViewService.unassignEntityViewFromCustomer(id));
  459 + }
  460 + $q.all(tasks).then(function () {
  461 + vm.grid.refreshList();
  462 + });
  463 + });
  464 + }
  465 +
  466 + function makePublic($event, entityView) {
  467 + if ($event) {
  468 + $event.stopPropagation();
  469 + }
  470 + var confirm = $mdDialog.confirm()
  471 + .targetEvent($event)
  472 + .title($translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name}))
  473 + .htmlContent($translate.instant('entity-view.make-public-entity-view-text'))
  474 + .ariaLabel($translate.instant('entity-view.make-public'))
  475 + .cancel($translate.instant('action.no'))
  476 + .ok($translate.instant('action.yes'));
  477 + $mdDialog.show(confirm).then(function () {
  478 + entityViewService.makeEntityViewPublic(entityView.id.id).then(function success() {
  479 + vm.grid.refreshList();
  480 + });
  481 + });
  482 + }
  483 +}
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +/*@ngInject*/
  23 +export default function EntityViewDirective($compile, $templateCache, toast, $translate, types, clipboardService, entityViewService, customerService) {
  24 + var linker = function (scope, element) {
  25 + var template = $templateCache.get(entityViewFieldsetTemplate);
  26 + element.html(template);
  27 +
  28 + scope.types = types;
  29 + scope.isAssignedToCustomer = false;
  30 + scope.assignedCustomer = null;
  31 +
  32 + scope.$watch('entityView', function(newVal) {
  33 + if (newVal) {
  34 + if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) {
  35 + scope.isAssignedToCustomer = true;
  36 + customerService.getShortCustomerInfo(scope.entityView.customerId.id).then(
  37 + function success(customer) {
  38 + scope.assignedCustomer = customer;
  39 + }
  40 + );
  41 + } else {
  42 + scope.isAssignedToCustomer = false;
  43 + scope.assignedCustomer = null;
  44 + }
  45 + }
  46 + });
  47 +
  48 + scope.onEntityViewIdCopied = function() {
  49 + toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
  50 + };
  51 +
  52 + $compile(element.contents())(scope);
  53 + }
  54 + return {
  55 + restrict: "E",
  56 + link: linker,
  57 + scope: {
  58 + entityView: '=',
  59 + isEdit: '=',
  60 + entityViewScope: '=',
  61 + theForm: '=',
  62 + onAssignToCustomer: '&',
  63 + onUnassignFromCustomer: '&',
  64 + onDeleteEntityView: '&'
  65 + }
  66 + };
  67 +}
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import entityViewsTemplate from './entity-views.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +/*@ngInject*/
  23 +export default function EntityViewRoutes($stateProvider, types) {
  24 + $stateProvider
  25 + .state('home.entityViews', {
  26 + url: '/entityViews',
  27 + params: {'topIndex': 0},
  28 + module: 'private',
  29 + auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
  30 + views: {
  31 + "content@home": {
  32 + templateUrl: entityViewsTemplate,
  33 + controller: 'EntityViewController',
  34 + controllerAs: 'vm'
  35 + }
  36 + },
  37 + data: {
  38 + entityViewsTypes: 'tenant',
  39 + searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.entityview,
  42 + pageTitle: 'entity-views.entity-views'
  43 + },
  44 + ncyBreadcrumb: {
  45 + label: '{"icon": "devices_other", "label": "entity-view.entity-views"}'
  46 + }
  47 + })
  48 + .state('home.customers.entityViews', {
  49 + url: '/:customerId/entityViews',
  50 + params: {'topIndex': 0},
  51 + module: 'private',
  52 + auth: ['TENANT_ADMIN'],
  53 + views: {
  54 + "content@home": {
  55 + templateUrl: entityViewsTemplate,
  56 + controllerAs: 'vm',
  57 + controller: 'EntityViewController'
  58 + }
  59 + },
  60 + data: {
  61 + entityViewsTypes: 'customer',
  62 + searchEnabled: true,
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.entityview,
  65 + pageTitle: 'customer.entity-views'
  66 + },
  67 + ncyBreadcrumb: {
  68 + label: '{"icon": "devices_other", "label": "{{ vm.customerEntityViewsTitle }}", "translate": "false"}'
  69 + }
  70 + });
  71 +
  72 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<tb-grid grid-configuration="vm.entityViewGridConfig">
  19 + <details-buttons tb-help="'entityViews'" help-container-id="help-container">
  20 + <div id="help-container"></div>
  21 + </details-buttons>
  22 + <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
  23 + id="tabs" md-border-bottom flex class="tb-absolute-fill">
  24 + <md-tab label="{{ 'entity-view.details' | translate }}">
  25 + <tb-entity-view entity-view="vm.grid.operatingItem()"
  26 + is-edit="vm.grid.detailsConfig.isDetailsEditMode"
  27 + entity-view-scope="vm.entityViewsScope"
  28 + the-form="vm.grid.detailsForm"
  29 + on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
  30 + on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
  31 + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
  32 + on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)"
  33 + on-delete-entity-view="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-entity-view>
  34 + </md-tab>
  35 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
  36 + <tb-attribute-table flex
  37 + entity-id="vm.grid.operatingItem().id.id"
  38 + entity-type="{{vm.types.entityType.entityview}}"
  39 + entity-name="vm.grid.operatingItem().name"
  40 + default-attribute-scope="{{vm.types.attributesScope.client.value}}">
  41 + </tb-attribute-table>
  42 + </md-tab>
  43 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
  44 + <tb-attribute-table flex
  45 + entity-id="vm.grid.operatingItem().id.id"
  46 + entity-type="{{vm.types.entityType.entityview}}"
  47 + entity-name="vm.grid.operatingItem().name"
  48 + default-attribute-scope="{{vm.types.latestTelemetry.value}}"
  49 + disable-attribute-scope-selection="true">
  50 + </tb-attribute-table>
  51 + </md-tab>
  52 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
  53 + <tb-alarm-table flex entity-type="vm.types.entityType.entityview"
  54 + entity-id="vm.grid.operatingItem().id.id">
  55 + </tb-alarm-table>
  56 + </md-tab>
  57 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'entity-view.events' | translate }}">
  58 + <tb-event-table flex entity-type="vm.types.entityType.entityview"
  59 + entity-id="vm.grid.operatingItem().id.id"
  60 + tenant-id="vm.grid.operatingItem().tenantId.id"
  61 + default-event-type="{{vm.types.eventType.error.value}}">
  62 + </tb-event-table>
  63 + </md-tab>
  64 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
  65 + <tb-relation-table flex
  66 + entity-id="vm.grid.operatingItem().id.id"
  67 + entity-type="{{vm.types.entityType.entityview}}">
  68 + </tb-relation-table>
  69 + </md-tab>
  70 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}">
  71 + <tb-extension-table flex
  72 + entity-id="vm.grid.operatingItem().id.id"
  73 + entity-name="vm.grid.operatingItem().name"
  74 + entity-type="{{vm.types.entityType.entityview}}">
  75 + </tb-extension-table>
  76 + </md-tab>
  77 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
  78 + <tb-audit-log-table flex entity-type="vm.types.entityType.entityview"
  79 + entity-id="vm.grid.operatingItem().id.id"
  80 + audit-log-mode="{{vm.types.auditLogMode.entity}}">
  81 + </tb-audit-log-table>
  82 + </md-tab>
  83 +</tb-grid>
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import uiRouter from 'angular-ui-router';
  17 +import thingsboardGrid from '../components/grid.directive';
  18 +import thingsboardApiUser from '../api/user.service';
  19 +import thingsboardApiEntityView from '../api/entity-view.service';
  20 +import thingsboardApiCustomer from '../api/customer.service';
  21 +
  22 +import EntityViewRoutes from './entity-view.routes';
  23 +import EntityViewCardController from './entity-view.controller';
  24 +import AssignEntityViewToCustomerController from './assign-to-customer.controller';
  25 +import AddEntityViewsToCustomerController from './add-entity-views-to-customer.controller';
  26 +import EntityViewDirective from './entity-view.directive';
  27 +
  28 +export default angular.module('thingsboard.entityView', [
  29 + uiRouter,
  30 + thingsboardGrid,
  31 + thingsboardApiUser,
  32 + thingsboardApiEntityView,
  33 + thingsboardApiCustomer
  34 +])
  35 + .config(EntityViewRoutes)
  36 + .controller('EntityViewController', EntityViewCardController)
  37 + .controller('EntityViewCardController', EntityViewCardController)
  38 + .controller('AssignEntityViewToCustomerController', AssignEntityViewToCustomerController)
  39 + .controller('AddEntityViewsToCustomerController', AddEntityViewsToCustomerController)
  40 + .directive('tbEntityView', EntityViewDirective)
  41 + .name;
@@ -338,10 +338,12 @@ @@ -338,10 +338,12 @@
338 "dashboard": "Customer Dashboard", 338 "dashboard": "Customer Dashboard",
339 "dashboards": "Customer Dashboards", 339 "dashboards": "Customer Dashboards",
340 "devices": "Customer Devices", 340 "devices": "Customer Devices",
  341 + "entity-views": "Customer Entity Views",
341 "assets": "Customer Assets", 342 "assets": "Customer Assets",
342 "public-dashboards": "Public Dashboards", 343 "public-dashboards": "Public Dashboards",
343 "public-devices": "Public Devices", 344 "public-devices": "Public Devices",
344 "public-assets": "Public Assets", 345 "public-assets": "Public Assets",
  346 + "public-entity-views": "Public Entity Views",
345 "add": "Add Customer", 347 "add": "Add Customer",
346 "delete": "Delete customer", 348 "delete": "Delete customer",
347 "manage-customer-users": "Manage customer users", 349 "manage-customer-users": "Manage customer users",
@@ -750,6 +752,77 @@ @@ -750,6 +752,77 @@
750 "no-entities-prompt": "No entities found", 752 "no-entities-prompt": "No entities found",
751 "no-data": "No data to display" 753 "no-data": "No data to display"
752 }, 754 },
  755 + "entity-view": {
  756 + "entity-view": "Entity View",
  757 + "entity-views": "Entity Views",
  758 + "management": "Entity View management",
  759 + "view-entity-views": "View Entity Views",
  760 + "entity-view-alias": "Entity View alias",
  761 + "aliases": "Entity View aliases",
  762 + "no-alias-matching": "'{{alias}}' not found.",
  763 + "no-aliases-found": "No aliases found.",
  764 + "no-key-matching": "'{{key}}' not found.",
  765 + "no-keys-found": "No keys found.",
  766 + "create-new-alias": "Create a new one!",
  767 + "create-new-key": "Create a new one!",
  768 + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity View aliases must be unique whithin the dashboard.",
  769 + "configure-alias": "Configure '{{alias}}' alias",
  770 + "no-entity-views-matching": "No entity views matching '{{entity}}' were found.",
  771 + "alias": "Alias",
  772 + "alias-required": "Entity View alias is required.",
  773 + "remove-alias": "Remove entity view alias",
  774 + "add-alias": "Add entity view alias",
  775 + "name-starts-with": "Entity View name starts with",
  776 + "entity-view-list": "Entity View list",
  777 + "use-entity-view-name-filter": "Use filter",
  778 + "entity-view-list-empty": "No entity views selected.",
  779 + "entity-view-name-filter-required": "Entity view name filter is required.",
  780 + "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.",
  781 + "add": "Add Entity View",
  782 + "assign-to-customer": "Assign to customer",
  783 + "assign-entity-view-to-customer": "Assign Entity View(s) To Customer",
  784 + "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer",
  785 + "no-entity-views-text": "No entity views found",
  786 + "assign-to-customer-text": "Please select the customer to assign the entity view(s)",
  787 + "entity-view-details": "Entity view details",
  788 + "add-entity-view-text": "Add new entity view",
  789 + "delete": "Delete entity view",
  790 + "assign-entity-views": "Assign entity views",
  791 + "assign-entity-views-text": "Assign { count, plural, 1 {1 entityView} other {# entityViews} } to customer",
  792 + "delete-entity-views": "Delete entity views",
  793 + "unassign-from-customer": "Unassign from customer",
  794 + "unassign-entity-views": "Unassign entity views",
  795 + "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entityView} other {# entityViews} } from customer",
  796 + "assign-new-entity-view": "Assign new entity view",
  797 + "delete-entity-view-title": "Are you sure you want to delete the entity view '{{entityViewName}}'?",
  798 + "delete-entity-view-text": "Be careful, after the confirmation the entity view and all related data will become unrecoverable.",
  799 + "delete-entity-views-title": "Are you sure you want to entity view { count, plural, 1 {1 entityView} other {# entityViews} }?",
  800 + "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entityView} other {# entityViews} }",
  801 + "delete-entity-views-text": "Be careful, after the confirmation all selected entity views will be removed and all related data will become unrecoverable.",
  802 + "unassign-entity-view-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?",
  803 + "unassign-entity-view-text": "After the confirmation the entity view will be unassigned and won't be accessible by the customer.",
  804 + "unassign-entity-view": "Unassign entity view",
  805 + "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entityView} other {# entityViews} }?",
  806 + "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.",
  807 + "entity-view-type": "Entity View type",
  808 + "entity-view-type-required": "Entity View type is required.",
  809 + "select-entity-view-type": "Select entity view type",
  810 + "enter-entity-view-type": "Enter entity view type",
  811 + "any-entity-view": "Any entity view",
  812 + "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.",
  813 + "entity-view-type-list-empty": "No entity view types selected.",
  814 + "entity-view-types": "Entity View types",
  815 + "name": "Name",
  816 + "name-required": "Name is required.",
  817 + "description": "Description",
  818 + "events": "Events",
  819 + "details": "Details",
  820 + "copyId": "Copy entity view Id",
  821 + "assignedToCustomer": "Assigned to customer",
  822 + "unable-entity-view-device-alias-title": "Unable to delete entity view alias",
  823 + "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
  824 + "select-entity-view": "Select entity view"
  825 + },
753 "event": { 826 "event": {
754 "event-type": "Event type", 827 "event-type": "Event type",
755 "type-error": "Error", 828 "type-error": "Error",