Showing
5 changed files
with
148 additions
and
4 deletions
@@ -19,16 +19,22 @@ import com.google.common.util.concurrent.ListenableFuture; | @@ -19,16 +19,22 @@ import com.google.common.util.concurrent.ListenableFuture; | ||
19 | import org.springframework.http.HttpStatus; | 19 | import org.springframework.http.HttpStatus; |
20 | import org.springframework.security.access.prepost.PreAuthorize; | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | import org.springframework.web.bind.annotation.*; | 21 | import org.springframework.web.bind.annotation.*; |
22 | +import org.thingsboard.server.common.data.Customer; | ||
23 | +import org.thingsboard.server.common.data.Device; | ||
22 | import org.thingsboard.server.common.data.EntitySubtype; | 24 | import org.thingsboard.server.common.data.EntitySubtype; |
23 | import org.thingsboard.server.common.data.EntityType; | 25 | import org.thingsboard.server.common.data.EntityType; |
24 | import org.thingsboard.server.common.data.EntityView; | 26 | import org.thingsboard.server.common.data.EntityView; |
25 | import org.thingsboard.server.common.data.audit.ActionType; | 27 | import org.thingsboard.server.common.data.audit.ActionType; |
26 | import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; | 28 | import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; |
27 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 29 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
30 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
31 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
28 | import org.thingsboard.server.common.data.id.EntityViewId; | 32 | import org.thingsboard.server.common.data.id.EntityViewId; |
29 | import org.thingsboard.server.common.data.id.TenantId; | 33 | import org.thingsboard.server.common.data.id.TenantId; |
30 | import org.thingsboard.server.common.data.page.TextPageData; | 34 | import org.thingsboard.server.common.data.page.TextPageData; |
31 | import org.thingsboard.server.common.data.page.TextPageLink; | 35 | import org.thingsboard.server.common.data.page.TextPageLink; |
36 | +import org.thingsboard.server.dao.exception.IncorrectParameterException; | ||
37 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
32 | import org.thingsboard.server.service.security.model.SecurityUser; | 38 | import org.thingsboard.server.service.security.model.SecurityUser; |
33 | 39 | ||
34 | import java.util.ArrayList; | 40 | import java.util.ArrayList; |
@@ -101,6 +107,84 @@ public class EntityViewController extends BaseController { | @@ -101,6 +107,84 @@ public class EntityViewController extends BaseController { | ||
101 | } | 107 | } |
102 | 108 | ||
103 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 109 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
110 | + @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) | ||
111 | + @ResponseBody | ||
112 | + public EntityView assignEntityViewToCustomer(@PathVariable("customerId") String strCustomerId, | ||
113 | + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | ||
114 | + checkParameter("customerId", strCustomerId); | ||
115 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | ||
116 | + try { | ||
117 | + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | ||
118 | + Customer customer = checkCustomerId(customerId); | ||
119 | + | ||
120 | + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); | ||
121 | + checkEntityViewId(entityViewId); | ||
122 | + | ||
123 | + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId)); | ||
124 | + | ||
125 | + logEntityAction(entityViewId, savedEntityView, | ||
126 | + savedEntityView.getCustomerId(), | ||
127 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName()); | ||
128 | + | ||
129 | + return savedEntityView; | ||
130 | + } catch (Exception e) { | ||
131 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, | ||
132 | + null, | ||
133 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId); | ||
134 | + throw handleException(e); | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
139 | + @RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE) | ||
140 | + @ResponseBody | ||
141 | + public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | ||
142 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | ||
143 | + try { | ||
144 | + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); | ||
145 | + EntityView entityView = checkEntityViewId(entityViewId); | ||
146 | + if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | ||
147 | + throw new IncorrectParameterException("Entity View isn't assigned to any customer!"); | ||
148 | + } | ||
149 | + Customer customer = checkCustomerId(entityView.getCustomerId()); | ||
150 | + | ||
151 | + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId)); | ||
152 | + | ||
153 | + logEntityAction(entityViewId, entityView, | ||
154 | + entityView.getCustomerId(), | ||
155 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName()); | ||
156 | + | ||
157 | + return savedEntityView; | ||
158 | + } catch (Exception e) { | ||
159 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, | ||
160 | + null, | ||
161 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId); | ||
162 | + throw handleException(e); | ||
163 | + } | ||
164 | + } | ||
165 | + | ||
166 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | ||
167 | + @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET) | ||
168 | + @ResponseBody | ||
169 | + public TextPageData<EntityView> getCustomerEntityViews( | ||
170 | + @PathVariable("customerId") String strCustomerId, | ||
171 | + @RequestParam int limit, | ||
172 | + @RequestParam(required = false) String textSearch, | ||
173 | + @RequestParam(required = false) String idOffset, | ||
174 | + @RequestParam(required = false) String textOffset) throws ThingsboardException { | ||
175 | + checkParameter("customerId", strCustomerId); | ||
176 | + try { | ||
177 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
178 | + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | ||
179 | + checkCustomerId(customerId); | ||
180 | + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); | ||
181 | + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); | ||
182 | + } catch (Exception e) { | ||
183 | + throw handleException(e); | ||
184 | + } | ||
185 | + } | ||
186 | + | ||
187 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
104 | @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) | 188 | @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) |
105 | @ResponseBody | 189 | @ResponseBody |
106 | public TextPageData<EntityView> getTenantEntityViews( | 190 | public TextPageData<EntityView> getTenantEntityViews( |
@@ -52,9 +52,33 @@ | @@ -52,9 +52,33 @@ | ||
52 | <div translate ng-message="required">entity-view.name-required</div> | 52 | <div translate ng-message="required">entity-view.name-required</div> |
53 | </div> | 53 | </div> |
54 | </md-input-container> | 54 | </md-input-container> |
55 | + <tb-entity-select flex ng-disabled="!isEdit" | ||
56 | + the-form="theForm" | ||
57 | + tb-required="true" | ||
58 | + allowed-entity-types="allowedEntityTypes" | ||
59 | + ng-model="entityView.entityId"> | ||
60 | + </tb-entity-select> | ||
55 | <md-input-container class="md-block"> | 61 | <md-input-container class="md-block"> |
56 | <label translate>entity-view.description</label> | 62 | <label translate>entity-view.description</label> |
57 | <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea> | 63 | <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea> |
58 | </md-input-container> | 64 | </md-input-container> |
65 | + <section layout="row" layout-align="start start"> | ||
66 | + <mdp-date-picker ng-model="startTs" | ||
67 | + mdp-max-date="maxStartTs" | ||
68 | + mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"></mdp-date-picker> | ||
69 | + <mdp-time-picker ng-model="startTs" | ||
70 | + mdp-max-date="maxStartTs" | ||
71 | + mdp-placeholder="{{ 'entity-view.start-ts' | translate }}" | ||
72 | + mdp-auto-switch="true"></mdp-time-picker> | ||
73 | + </section> | ||
74 | + <section layout="row" layout-align="start start"> | ||
75 | + <mdp-date-picker ng-model="endTs" | ||
76 | + mdp-min-date="minEndTs" | ||
77 | + mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"></mdp-date-picker> | ||
78 | + <mdp-time-picker ng-model="endTs" | ||
79 | + mdp-min-date="minEndTs" | ||
80 | + mdp-placeholder="{{ 'entity-view.end-ts' | translate }}" | ||
81 | + mdp-auto-switch="true"></mdp-time-picker> | ||
82 | + </section> | ||
59 | </fieldset> | 83 | </fieldset> |
60 | </md-content> | 84 | </md-content> |
@@ -20,7 +20,7 @@ import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html'; | @@ -20,7 +20,7 @@ import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html'; | ||
20 | /* eslint-enable import/no-unresolved, import/default */ | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | -export default function EntityViewDirective($compile, $templateCache, toast, $translate, types, clipboardService, entityViewService, customerService) { | 23 | +export default function EntityViewDirective($compile, $templateCache, $filter, toast, $translate, types, clipboardService, entityViewService, customerService) { |
24 | var linker = function (scope, element) { | 24 | var linker = function (scope, element) { |
25 | var template = $templateCache.get(entityViewFieldsetTemplate); | 25 | var template = $templateCache.get(entityViewFieldsetTemplate); |
26 | element.html(template); | 26 | element.html(template); |
@@ -30,6 +30,8 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr | @@ -30,6 +30,8 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr | ||
30 | scope.isPublic = false; | 30 | scope.isPublic = false; |
31 | scope.assignedCustomer = null; | 31 | scope.assignedCustomer = null; |
32 | 32 | ||
33 | + scope.allowedEntityTypes = [types.entityType.device, types.entityType.asset]; | ||
34 | + | ||
33 | scope.$watch('entityView', function(newVal) { | 35 | scope.$watch('entityView', function(newVal) { |
34 | if (newVal) { | 36 | if (newVal) { |
35 | if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) { | 37 | if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) { |
@@ -45,9 +47,41 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr | @@ -45,9 +47,41 @@ export default function EntityViewDirective($compile, $templateCache, toast, $tr | ||
45 | scope.isPublic = false; | 47 | scope.isPublic = false; |
46 | scope.assignedCustomer = null; | 48 | scope.assignedCustomer = null; |
47 | } | 49 | } |
50 | + scope.startTs = $filter('date')(scope.entityView.endTs, 'yyyy-MM-dd HH:mm:ss'); | ||
51 | + scope.endTs = $filter('date')(scope.entityView.startTs, 'yyyy-MM-dd HH:mm:ss'); | ||
52 | + } | ||
53 | + }); | ||
54 | + | ||
55 | + | ||
56 | + scope.$watch('startTs', function (newDate) { | ||
57 | + if (newDate) { | ||
58 | + if (newDate.getTime() > scope.maxStartTs) { | ||
59 | + scope.startTs = angular.copy(scope.maxStartTs); | ||
60 | + } | ||
61 | + updateMinMaxDates(); | ||
62 | + } | ||
63 | + }); | ||
64 | + | ||
65 | + scope.$watch('endTs', function (newDate) { | ||
66 | + if (newDate) { | ||
67 | + if (newDate.getTime() < scope.minEndTs) { | ||
68 | + scope.endTs = angular.copy(scope.minEndTs); | ||
69 | + } | ||
70 | + updateMinMaxDates(); | ||
48 | } | 71 | } |
49 | }); | 72 | }); |
50 | 73 | ||
74 | + function updateMinMaxDates() { | ||
75 | + if (scope.endTs) { | ||
76 | + scope.maxStartTs = angular.copy(new Date(scope.endTs.getTime() - 1000)); | ||
77 | + scope.entityView.endTs = $filter('date')(scope.endTs, 'yyyy-MM-dd HH:mm:ss'); | ||
78 | + } | ||
79 | + if (scope.startTs) { | ||
80 | + scope.minEndTs = angular.copy(new Date(scope.startTs.getTime() + 1000)); | ||
81 | + scope.entityView.startTs = $filter('date')(scope.startTs, 'yyyy-MM-dd HH:mm:ss'); | ||
82 | + } | ||
83 | + } | ||
84 | + | ||
51 | scope.onEntityViewIdCopied = function() { | 85 | scope.onEntityViewIdCopied = function() { |
52 | toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); | 86 | toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); |
53 | }; | 87 | }; |
@@ -38,7 +38,7 @@ export default function EntityViewRoutes($stateProvider, types) { | @@ -38,7 +38,7 @@ export default function EntityViewRoutes($stateProvider, types) { | ||
38 | entityViewsType: 'tenant', | 38 | entityViewsType: 'tenant', |
39 | searchEnabled: true, | 39 | searchEnabled: true, |
40 | searchByEntitySubtype: true, | 40 | searchByEntitySubtype: true, |
41 | - searchEntityType: types.entityType.entityview, | 41 | + searchEntityType: types.entityType.entityView, |
42 | pageTitle: 'entity-view.entity-views' | 42 | pageTitle: 'entity-view.entity-views' |
43 | }, | 43 | }, |
44 | ncyBreadcrumb: { | 44 | ncyBreadcrumb: { |
@@ -61,7 +61,7 @@ export default function EntityViewRoutes($stateProvider, types) { | @@ -61,7 +61,7 @@ export default function EntityViewRoutes($stateProvider, types) { | ||
61 | entityViewsType: 'customer', | 61 | entityViewsType: 'customer', |
62 | searchEnabled: true, | 62 | searchEnabled: true, |
63 | searchByEntitySubtype: true, | 63 | searchByEntitySubtype: true, |
64 | - searchEntityType: types.entityType.entityview, | 64 | + searchEntityType: types.entityType.entityView, |
65 | pageTitle: 'customer.entity-views' | 65 | pageTitle: 'customer.entity-views' |
66 | }, | 66 | }, |
67 | ncyBreadcrumb: { | 67 | ncyBreadcrumb: { |
@@ -822,7 +822,9 @@ | @@ -822,7 +822,9 @@ | ||
822 | "unable-entity-view-device-alias-title": "Unable to delete entity view alias", | 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}}", | 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", | 824 | "select-entity-view": "Select entity view", |
825 | - "make-public": "Make entity view public" | 825 | + "make-public": "Make entity view public", |
826 | + "start-ts": "Start ts", | ||
827 | + "end-ts": "End ts" | ||
826 | }, | 828 | }, |
827 | "event": { | 829 | "event": { |
828 | "event-type": "Event type", | 830 | "event-type": "Event type", |