Details
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index 227bed0..b5adc32 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -19,16 +19,22 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
@@ -101,6 +107,84 @@ public class EntityViewController extends BaseController {
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST)
+ @ResponseBody
+ public EntityView assignEntityViewToCustomer(@PathVariable("customerId") String strCustomerId,
+ @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
+ checkParameter("customerId", strCustomerId);
+ checkParameter(ENTITY_VIEW_ID, strEntityViewId);
+ try {
+ CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+ Customer customer = checkCustomerId(customerId);
+
+ EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
+ checkEntityViewId(entityViewId);
+
+ EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId));
+
+ logEntityAction(entityViewId, savedEntityView,
+ savedEntityView.getCustomerId(),
+ ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName());
+
+ return savedEntityView;
+ } catch (Exception e) {
+ logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
+ null,
+ ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId);
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE)
+ @ResponseBody
+ public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
+ checkParameter(ENTITY_VIEW_ID, strEntityViewId);
+ try {
+ EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
+ EntityView entityView = checkEntityViewId(entityViewId);
+ if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+ throw new IncorrectParameterException("Entity View isn't assigned to any customer!");
+ }
+ Customer customer = checkCustomerId(entityView.getCustomerId());
+
+ EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId));
+
+ logEntityAction(entityViewId, entityView,
+ entityView.getCustomerId(),
+ ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName());
+
+ return savedEntityView;
+ } catch (Exception e) {
+ logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
+ null,
+ ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId);
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET)
+ @ResponseBody
+ public TextPageData<EntityView> getCustomerEntityViews(
+ @PathVariable("customerId") String strCustomerId,
+ @RequestParam int limit,
+ @RequestParam(required = false) String textSearch,
+ @RequestParam(required = false) String idOffset,
+ @RequestParam(required = false) String textOffset) throws ThingsboardException {
+ checkParameter("customerId", strCustomerId);
+ try {
+ TenantId tenantId = getCurrentUser().getTenantId();
+ CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+ checkCustomerId(customerId);
+ TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+ return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<EntityView> getTenantEntityViews(
diff --git a/ui/src/app/entity-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js
index 5a3d876..b07d437 100644
--- a/ui/src/app/entity-view/entity-view.directive.js
+++ b/ui/src/app/entity-view/entity-view.directive.js
@@ -20,7 +20,7 @@ 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) {
+export default function EntityViewDirective($compile, $templateCache, $filter, toast, $translate, types, clipboardService, entityViewService, customerService) {
var linker = function (scope, element) {
var template = $templateCache.get(entityViewFieldsetTemplate);
element.html(template);
@@ -30,6 +30,8 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr
scope.isPublic = false;
scope.assignedCustomer = null;
+ scope.allowedEntityTypes = [types.entityType.device, types.entityType.asset];
+
scope.$watch('entityView', function(newVal) {
if (newVal) {
if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) {
@@ -45,9 +47,41 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr
scope.isPublic = false;
scope.assignedCustomer = null;
}
+ scope.startTs = $filter('date')(scope.entityView.endTs, 'yyyy-MM-dd HH:mm:ss');
+ scope.endTs = $filter('date')(scope.entityView.startTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+ });
+
+
+ scope.$watch('startTs', function (newDate) {
+ if (newDate) {
+ if (newDate.getTime() > scope.maxStartTs) {
+ scope.startTs = angular.copy(scope.maxStartTs);
+ }
+ updateMinMaxDates();
+ }
+ });
+
+ scope.$watch('endTs', function (newDate) {
+ if (newDate) {
+ if (newDate.getTime() < scope.minEndTs) {
+ scope.endTs = angular.copy(scope.minEndTs);
+ }
+ updateMinMaxDates();
}
});
+ function updateMinMaxDates() {
+ if (scope.endTs) {
+ scope.maxStartTs = angular.copy(new Date(scope.endTs.getTime() - 1000));
+ scope.entityView.endTs = $filter('date')(scope.endTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+ if (scope.startTs) {
+ scope.minEndTs = angular.copy(new Date(scope.startTs.getTime() + 1000));
+ scope.entityView.startTs = $filter('date')(scope.startTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+ }
+
scope.onEntityViewIdCopied = function() {
toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
diff --git a/ui/src/app/entity-view/entity-view.routes.js b/ui/src/app/entity-view/entity-view.routes.js
index 481dfc8..c14d430 100644
--- a/ui/src/app/entity-view/entity-view.routes.js
+++ b/ui/src/app/entity-view/entity-view.routes.js
@@ -38,7 +38,7 @@ export default function EntityViewRoutes($stateProvider, types) {
entityViewsType: 'tenant',
searchEnabled: true,
searchByEntitySubtype: true,
- searchEntityType: types.entityType.entityview,
+ searchEntityType: types.entityType.entityView,
pageTitle: 'entity-view.entity-views'
},
ncyBreadcrumb: {
@@ -61,7 +61,7 @@ export default function EntityViewRoutes($stateProvider, types) {
entityViewsType: 'customer',
searchEnabled: true,
searchByEntitySubtype: true,
- searchEntityType: types.entityType.entityview,
+ searchEntityType: types.entityType.entityView,
pageTitle: 'customer.entity-views'
},
ncyBreadcrumb: {
diff --git a/ui/src/app/entity-view/entity-view-fieldset.tpl.html b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
index cb46f3d..c73b0a2 100644
--- a/ui/src/app/entity-view/entity-view-fieldset.tpl.html
+++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
@@ -52,9 +52,33 @@
<div translate ng-message="required">entity-view.name-required</div>
</div>
</md-input-container>
+ <tb-entity-select flex ng-disabled="!isEdit"
+ the-form="theForm"
+ tb-required="true"
+ allowed-entity-types="allowedEntityTypes"
+ ng-model="entityView.entityId">
+ </tb-entity-select>
<md-input-container class="md-block">
<label translate>entity-view.description</label>
<textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
</md-input-container>
+ <section layout="row" layout-align="start start">
+ <mdp-date-picker ng-model="startTs"
+ mdp-max-date="maxStartTs"
+ mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"></mdp-date-picker>
+ <mdp-time-picker ng-model="startTs"
+ mdp-max-date="maxStartTs"
+ mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"
+ mdp-auto-switch="true"></mdp-time-picker>
+ </section>
+ <section layout="row" layout-align="start start">
+ <mdp-date-picker ng-model="endTs"
+ mdp-min-date="minEndTs"
+ mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"></mdp-date-picker>
+ <mdp-time-picker ng-model="endTs"
+ mdp-min-date="minEndTs"
+ mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"
+ mdp-auto-switch="true"></mdp-time-picker>
+ </section>
</fieldset>
</md-content>
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 09769ed..e98d4fe 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -822,7 +822,9 @@
"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",
- "make-public": "Make entity view public"
+ "make-public": "Make entity view public",
+ "start-ts": "Start ts",
+ "end-ts": "End ts"
},
"event": {
"event-type": "Event type",